Skip to content

Shadow Runner & Replay

The difference between a suggestion and a decision is a number from history.

Every proposal brAIn could make — automate this, add this condition, this rule is wrong for your house — is a guess until somebody enables it and lives with it for a week. That is exactly the commitment nobody wants to make on a guess, so the proposals never got made and the house went on being programmed by hand.

The shadow runner is the other half. It takes an automation — one brAIn wrote, or one already in your house — and evaluates its triggers and conditions against the recorded past, reporting when it would have fired and what it would have done. It calls no service. It touches nothing.

What you get back

Over the last 30 days this would have fired 26 times. On 4 of those a condition would have blocked it, so it would have run 22 times. It would have called light.turn_off on light.kitchen.

Answered in seconds, before anything is enabled.

Where to use it

Where What it answers
Proposals tab Every proposal shows its replay before you accept it
Any existing automation “What has this actually been doing?” — including ones you did not write
POST /api/replay The same answer, for an automation config you supply

The trigger kinds, and why the refusal is the feature

Replay covers time, state, numeric_state and template triggers. Those are the four classic kinds the recorder can reconstruct: a time trigger is arithmetic, and the other three are answered by state changes Home Assistant already keeps.

The new editor’s triggers

Since Home Assistant 2026.7 the automation editor writes purpose-specific triggers by default — “When a light turns on”, “When a door opens” — with the device under target:. brAIn replays the common ones as the state changes Home Assistant itself fires them on: lights, switches, fans, motion and occupancy, locks, doors, windows, covers, climate, media players and alarm panels (light.turned_on, occupancy.detected, door.opened, climate.started_heating, media_player.started_playing and their siblings), plus the matching conditions. So an automation built in the new editor gets its “would have fired” numbers, can be trialled for a week, and shows evidence on a condition suggestion.

A target that is a whole area, floor, label or device is refused, because which entities those were at the time is something brAIn would have to guess.

What is refused

A webhook, an mqtt message or an event is not in the recorder in a form anything can replay. A sun trigger or a brightness threshold is in the recorder, but brAIn does not know how to replay it yet. The refusal says which of those two kinds of no it is.

A partial replay is worse than no replay

Reporting “this would have fired twice” about an automation whose webhook fires forty times a day is a confident wrong number that reads exactly like a right one — and it is the number somebody would decide on.

So an automation carrying any unsupported trigger is refused whole, naming the kind. Never trimmed to the replayable subset.

You will see the refusal in as many words:

A webhook trigger cannot be replayed — the recorder does not keep what it fires on, so brAIn would be guessing at how often it fired.

Templates are replayed against the world as it was

A template trigger is a Jinja expression over the whole state machine, and a replay has a state machine only for the entities it fetched history for. So:

  • The template is parsed for the entities it names, and every one of them has to be in the timeline. A template reading a sensor with no recorded history is refused, not rendered against a blank — a blank reads as unknown, which reads as false, which is a confident no.
  • Only a named set of Home Assistant’s own helpers is allowed: states, is_state, state_attr, is_state_attr, has_value, now, as_timestamp, float, int. Anything else — a custom filter, expand, a bare states iteration — is refused by name.

The details that decide whether a number is right

for: is a promise about a stretch of time, not an instant. A state trigger with for: 00:05:00 fires only where the entity then stayed that way for five minutes — a question about the next sample, not this one. A three-minute door blip does not count — and a light dimmed halfway through the hold is still a light that stayed on, so a change that only touches an attribute does not cut the stretch short.

numeric_state is a crossing, never a level. Home Assistant fires when a value moves into the range, not for every sample it spends inside one. A replay that reported the level would be reporting a reading rather than a trigger.

An area or device target is recorded and deliberately not resolved. Expanding one needs the entity registry as it was at the time, and a wrong expansion would tell you a proposal touches lights it does not.

The bounds

Bound Why
30 days maximum window The recorder’s default purge is ten days and most houses leave it there — past this the window quietly becomes a window over nothing
500 firings maximum More than that is a trigger watching something that changes constantly rather than an automation, and the number is the sampling. brAIn says so and suggests a to: or a for:
A condition it cannot evaluate Refused, not assumed — a guessed condition silently changes the count

What it is not

It is not a simulator of your house. It replays triggers and conditions against what was recorded; it does not model what would have happened differently had the automation been running. If the proposal would have turned a light off at 23:05, the replay says so — it does not then re-simulate the evening with that light off.

That is the honest limit, and a trial does not lift it — a trial is the same replay over the week you are living through, but it is graded against the presses you actually made, which is evidence a replay over old history cannot give you.