After I shipped the headless conductor and the HTTP bridge, the rest of the day was closing the gaps that only show up once a run is actually unattended. First, Sparra had no way to call out to anything of mine while it worked, short of editing its source, so I added scriptHooks: seven lifecycle events (phase start/end, run start/end, unit start/end, and a run parking on a decision) each mapped to a shell command in config. A hook on a start event is a gate, if it fails or times out the phase, run, or unit aborts rather than pushing ahead on a bad assumption, while a hook on an end event is best-effort so a broken notification script can never take down a run that already succeeded. The env namespace is reserved and cleared between calls so nothing from one hook leaks into the next, and the same seam that fires the script hook on a parked decision now also fires a bridge dashboard event, so I did not end up building two notification paths for one moment.
Second, the bridge dashboard itself stopped scaling the moment I actually used it the way I built it for: a run kicked off from my laptop, another triggered from my phone on the train, and a browser tab that started neither. It only knew a job existed if it polled that job's own status endpoint, one request per running job per tick, so anything started elsewhere simply did not show up. I replaced that with an append-only events log on the bridge, GET /events?since=<cursor>, so one request per tick tells the dashboard everything new across every job instead of asking each one individually. The dashboard still hits the per-job endpoint for whichever run is open, since that is the only place that needs a streaming log, but discovery and status now ride the shared feed, with the poll loop reading the live cursor at apply-time so a slow, out-of-order response can never regress a job's state on screen.
The last gap was smaller but easy to miss: the cross-model evaluator that grades generated code against its contract would occasionally hit its own turn or budget cap mid-thought and get killed before returning parseable JSON, and the loop had no way to tell that apart from an honest failed review, so it counted both as a hard fail. I gave the evaluator the same recovery already in place for the writer role, one more turn, text-only, capped tight enough that it cannot run the clock out twice, asking for nothing but the verdict in the expected shape. Only if that second, narrower attempt still comes back unparseable does it count as a real fail. None of these three problems mattered while I was watching the terminal by hand. They only surfaced once I started actually trusting Sparra to run without me in the room, which was the entire point of building the headless conductor in the first place.