feat(telemetry): push bus for live agent frames
deploy / test (push) Successful in 4m25s
deploy / build (push) Successful in 5m20s

/api/world/live is a 2s database poll, which is right for queryable state and
wrong for a token stream: reasoning only became visible after a step finished
and its row was written. This adds a process-wide broadcast bus that
topology_exec publishes to as the runtime's WebSocket delivers frames, and the
SSE handler forwards without waiting for the next tick.

Measured: the pushed frame arrived ~2.2s before the polled copy of the same
text.

Design notes worth keeping:
- A global (OnceLock), not an AppState field. The publisher is reached through
  phase_runner -> topology_worker -> MissionTap, none of which hold AppState;
  threading a handle through all of them would put a UI concern into four
  layers that have no other reason to know about one.
- Lossy by design. A slow subscriber lags and skips rather than applying
  backpressure to the agent producing. mission_events remains the durable
  record; this bus is the fast path, never the source of truth.
- Only `claw_<uuid>` aliases are attributed. The governor, door and evaluator
  drive real turns under other names, and attributing their output to an agent
  would put words in someone's mouth. Asserted in a test.
- The poll no longer emits `reasoning`: with both paths live, every turn
  arrived TWICE — once pushed, once polled ~2s later. The row is still written;
  this feed just is not its second mouth.

CEILING, measured rather than assumed: turns are not token-level because the
runtime is not streaming. zeroclaw's claude_cli provider runs
`claude -p --output-format json`, which returns ONE result object when the turn
completes — there are no incremental tokens to forward. Making this genuinely
token-by-token needs `--output-format stream-json` and incremental parsing in
the zeroclaw fork, not here. The bus is in place and will carry them the day it
does.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-15 16:29:12 -07:00
co-authored by Claude Opus 5
parent ba9d7aa185
commit 43436d7181
5 changed files with 242 additions and 33 deletions
+1
View File
@@ -204,6 +204,7 @@ async fn run_job(
.map(
|(_, _, mission_id, phase_id)| crate::topology_exec::MissionTap {
pool: pool.clone(),
workspace_id: job.workspace_id,
mission_id: *mission_id,
phase_id: *phase_id,
run_id: Some(id),