Linked Entities Pro: Keep entities synced. No loops. No drift. No recursion-guard blind spots

The kitchen counter fixture in my house has two controllers: a Zigbee wall switch by the doorway and a Tasmota relay behind the cabinet. They kept drifting out of sync. The wall switch would say on, the lights would be off. The next press always lands wrong.

I built this sync three times before getting it right. The first version was a pair of mirrored automations that looped endlessly. The second added a context filter that asked "did a human do that?" It worked but dashboard and scene toggles and voice commands slipped past it. No matter which version was running, a Home Assistant restart often left the two controllers settling on whatever stale state they had before.

Linked Entities Pro is the final version, generalized to any number of entities and any mix of domains. It is packaged as a blueprint so you can skip the first two.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

Full README and parameter reference: https://github.com/TheThinkingHome/Automations/blob/a265692717a5c4db42e480112b7ac33a5a7b525d/blueprints/automation/linked_entities_pro.md

What's different about this approach?

The blueprint replaces the recursion guard with a state-equality gate. The automation is built to be recursive. Every state change on a linked entity triggers the automation (again), which runs in mode: restart. Each new trigger cancels any run still in flight. The gate asks one question of the other linked entities: do they all match (now)? If yes, the run stops. No command, no loop. If no, it dispatches another command. The followers respond, their new state changes trigger the automation again, and the cycle continues until everyone agrees.

The gate is context-blind: it does not care who or what caused the change. A physical press, a dashboard toggle, a scene, an automation, and a voice command all flow through the same path and propagate the same way. And because the gate's only question is whether the group agrees yet, the same mechanism scales: two entities or a dozen, switches and lights and input_booleans and fans and groups mixed in the same linked group, all kept in lockstep.

Restart drift is handled separately. A designated authority entity wins the tiebreaker. After a restart, the group settles on truth instead of each member's current stale state.

What You Can Configure?

  • Linked entities. Two or more, any mix of switches, lights, input_booleans, fans, and groups in the same linked group.
  • Authority entity. One entity wins the tiebreaker after Home Assistant restarts and bridge reconnects. During ordinary use, any entity can drive the others.
  • Restart reconcile. A reconcile branch runs on Home Assistant start, and optionally on a bridge-online sensor coming back online (Zigbee2MQTT users: binary_sensor.zigbee2mqtt_running; ZHA, Z-Wave JS, and WiFi users can leave blank), to pull the group into alignment with the authority.
  • HA-startup peer-sync block. Optionally gate on the built-in Uptime integration. Allows the system to stabilize before acting.
  • Sync delay. Pause between trigger and dispatched commands. Acts as the cascade absorber on larger groups.
  • Debug logging. Log lines for every trigger and action. Useful while confirming the setup and tuning the sync delay.

What's in scope? On/off state only, by design. Brightness, color, and fan speed do not propagate, but lights and fans can still be linked for their on/off behavior. If there's enough interest in a domain-specific companion (for instance a Linked Lights with brightness and color), I'll build it as a separate blueprint, since each domain handles attributes differently.

Worked example: a four-entity passageway

Three hallway lights on separate switches (upstairs, downstairs, and stairwell) plus an input_boolean helper that the dashboard reads as "is the passageway lit." All four kept in sync:

automation:
  - alias: "Passageway Lights Sync"
    use_blueprint:
      path: TheThinkingHome/linked_entities_pro.yaml
      input:
        linked_entities:
          - input_boolean.passageway_lights
          - light.upstairs_hall
          - light.downstairs_hall
          - light.stairwell
        authority_entity: input_boolean.passageway_lights
        reconcile_on_ha_start: true
        reconcile_trigger_sensor: binary_sensor.zigbee2mqtt_running
        use_uptime_sensor: true
        uptime_sensor: sensor.uptime
        ha_start_block_seconds: 120
        sync_delay_ms: 200

The input_boolean is the authority because helpers restore cleanly across restarts. The dashboard tile reads the helper. Tap any switch or toggle the helper from the dashboard, and the others follow. After a restart, the lights are pulled into alignment with the helper.

Common questions

  • How do you tune sync delay? For fast hardware and small groups, a delay close to 0ms will proably work. For slower hardware, slow networks, and larger groups, the default 200ms absorbs the response cascade. To find the right value in between, turn on debug logging and watch your logs.

  • Why not just use a group? Two problems. First, native groups are typed: a Light Group takes only lights, a switch group only switches, and a wall switch, a Tasmota relay, and an input_boolean cannot share one. Second, even when the members match, the group only flows one way. Toggle the group and the members follow. Toggle a member directly and the siblings stay where they were. A group reflects state. It does not keep independent controllers in sync.

  • Why not use mode: single? Naive single floods larger groups: each follower reports back at a different moment, the run re-checks on the first arrival, sees a mismatch, and dispatches again and again. The obvious fix is pushing back automation termination until the group agrees, but then any press during the wait gets ignored. By design, we want to detect every toggle. mode: restart does both: a new trigger cancels the run in flight, the latest press wins, and the cascade collapses to agreement.

  • What about unavailable or frozen devices? Unknown and unavailable devices are filtered from the start: the trigger fires only on on/off transitions, and the dispatch excludes any entity in unavailable or unknown. Frozen devices cannot be caught, but they cause no harm. The available devices settle, the frozen remain frozen, and the automation terminates without error.

Other blueprints in The Thinking Home series:

  • Recently Active — Create a "was active recently" sensor from any source, with a configurable look-back window.
  • Sensor Failover — Fall back to a secondary sensor when the primary goes unavailable.
  • Linked Entities Pro — Two-way sync for any number of entities across switches, lights, input_booleans, fans, and groups.
  • Sensor Watchdog — Detect sensors that have stopped responding and power-cycle them back.

Version 1.0.0 stable as of June 2026.

Questions, bug reports, and use cases I didn't think of are welcome here. Half the fun of this is seeing where other people aim it.

1 Like
use_blueprint:
  path: TheThinkingHome/linked_entities_pro.yaml
  input:
    linked_entities:
      - light.garage_relay_1
      - light.garage_lights_2
alias: Garage Sync

Something isn't right. I have two relay/light sets I want to keep in sync.

Let me take a look. I'll get back to you.

breakone9r, Good news: I reproduced your error.

The Reconcile Trigger Sensor is meant to be optional, the docs say so, but I didn't write the automation that way. It defaulted that field to an empty value, and Home Assistant rejects an empty entity as malformed, which is the validation error you hit on save. You did nothing wrong here. Leaving it blank is exactly how a non-Z2M setup like yours is supposed to work. The reason I never caught it is that my own config points that field at binary_sensor.zigbee2mqtt_running, so it was never blank on my end.

Easy fix, and it's already done. The blueprint is now version 1.0.1, with that field defaulting to a value that loads a do-nothing trigger when the field is left blank. Re-import the blueprint (it'll overwrite the old copy), create your Garage Sync automation again, and you should be golden.

Thanks for reporting this. This is exactly the kind of thing that's hard to catch on my own setup.

1 Like

Awesome. Reimported and created a couple of automations for several grouped lights. Now we see if there's enough demand to sync brightness and color. :rofl:

1 Like