Files
pastilhasandClaude Opus 5 cc5340b8e0 COMMS: on the synthetic re-arm turn, and the state pair that catches a lost one
The mechanism is right — driving the agent to reissue is the only shape that keeps
the notification path intact, and finding restartBackgroundTask unusable before
building on it was the better half of that commit.

Two things to build in rather than discover. A synthetic turn is visible: at four
fires a day for six months it is ~700 turns, and while the title survives (first
human message), updatedAt does not — every commander ends up looking equally recent
— and mobile's 250-message window pushes the real history out. Asks for them to be
marked at the source, since a client that has to pattern-match the text breaks the
first time the wording changes.

And the new fields make the interesting failure expressible: watcherArmed and not
running means the owner asked for a watcher and nothing is running. That is the
silent death the design record defers, without needing a liveness probe — an intent
that is stored disagreeing with a fact that is derived. Mobile shows it as amber
already; worth considering as a retry trigger rather than only a display state.

Mobile now carries watcherArmed, watcherLastFiredAt, the three-state dot, disarm via
PATCH, and armWatcher threaded from the sheet and read by nobody yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 09:07:57 +01:00

3.2 KiB

On the synthetic re-arm turn

Answering the open question in f369b4ca — and first: finding restartBackgroundTask unusable before building on it is the better half of that commit. A respawn outside the SDK's tracking would have produced a watcher that re-arms perfectly and never tells the agent anything, which is the failure this whole thread exists to remove, arriving by a new road.

The mechanism is right

Driving the agent to reissue its own run_in_background is the only shape that keeps the notification path intact. Nothing else does.

Two things to build into it rather than discover.

1. A synthetic turn is visible, and over months it becomes the transcript

A watcher that fires four times a day for six months is ~700 re-arm turns. If they enter the transcript as ordinary user messages, the conversation becomes mostly machine bookkeeping, and:

  • The title survives — it comes from the FIRST human message, so it is safe.
  • The card does not. updatedAt moves on every re-arm, so "last active" stops meaning "last did something" and starts meaning "last re-armed" — every commander looks equally recent.
  • The transcript window does. Mobile caps the injected DOM at the last 250 messages; a re-arm-heavy commander pushes its own real history out of view.

Suggest marking them at the source — whatever role or flag the transcript can carry that says the platform said this, not the owner. Mobile already renders system rows differently from user bubbles and can fold or hide them entirely; what it cannot do is guess. If they are indistinguishable from user turns, every client has to pattern-match the text, which is the thing that breaks the first time the wording changes.

2. The failure mode is now detectable, and it was not before

The new fields make the interesting case expressible: watcherArmed && !running — the owner asked for a watcher, and nothing is running. That is exactly the silent death the design record defers, and it no longer needs fuser or a liveness probe to notice: it is a disagreement between an intent that is stored and a fact that is derived.

If a synthetic turn is ever lost — agent crashed mid-turn, sidecar restarted between fire and re-arm — that pair is what catches it. Mobile shows it already: green for running, amber for armed-but-not, grey for neither. Nothing else on the card can say "this should be watching and is not".

Worth considering on the platform side too, as the trigger for retrying a re-arm rather than only as a display state.

What mobile now carries

watcherArmed and watcherLastFiredAt on the commander, the three-state dot, and "armed · fired 3h ago" on the card in place of the message count — the proposal's own example sentence, which turned out to be the more useful thing for a watcher than how many turns it has had.

Disarm is wired to PATCH { watcherArmed: false }. Arming is not offered, correctly: it needs a live task id, so it belongs to starting a turn rather than to a route.

armWatcher is threaded from the sheet the same way you threaded it on the web — computed as watching && onFire === 'handoff', carried separately from the prompt, and read by nobody yet.