Architecture review

The operator isn't one process — is that strong, or should we consolidate?

A review of the fleet-control system that ran the 2026-08-10/11 batch merge, written from what actually broke that night rather than from theory.

Asked by Gera Written by Agent Smith Date 2026-08-11 Trigger "So the operator isn't really just one process, it's fragments of abilities that do their own thing. How would you review this, should we consolidate or is this strong?"

The shape is right. The seams are wrong. Don't merge the fragments into one process — consolidate three specific primitives that are currently reimplemented differently, badly, in six or more places. Every one of tonight's stalls traces back to one of these three gaps.

In plain terms

The fleet isn't run by one big "operator" program. It's a bunch of tiny scripts — a watcher, a stop-gate, a decision box, an escalation filter — each doing one small job, talking to each other through files and a terminal. That's a good design, the same way a toolbox of screwdrivers beats one tool that tries to do everything. The problem last night wasn't the toolbox. It's that three of the tools each had their own broken way of answering the same question — "is this thing actually gone, or did I just fail to check?" — and each one got it wrong differently.

1The fragmentation is a genuine strength — keep it

The "operator" is a constellation of small, single-purpose tools — nudge, drive-gate.py, context-budget.py, fable-decide, blocked, start-operator, the Supervisor — coordinating through the filesystem and tmux panes, with all the actual reasoning living only inside the Claude session itself. That's Unix philosophy applied to an agent fleet, and it earned its keep tonight.

Keep

The watchdog bug was cheap to fix because it lived in ~20 lines of bash, not tangled into a reasoning loop. A monolithic "operator brain" would have made the same bug expensive and risky to touch.

Keep

The token split is clean. The watchdog — the thing polling and waking sessions all night — spends zero bearer tokens; it's a dumb bash loop. All LLM spend happens only in the session actually doing the work. Cheap surveillance, expensive thinking kept separate.

Keep

Everything is inspectable. State lives in plain files and tmux panes, not hidden in a process's memory. Diffing the "live" and "canonical" copies of a script, counting armed watchers, reading a task's state — all of it is a cat or a grep away, which is exactly how tonight's diagnosis happened.

Keep

It degrades in pieces. One dead watchdog doesn't take down the session it was watching, and one stalled operator doesn't take down the other nineteen. A monolith fails all-or-nothing; this fails one piece at a time.

Collapsing this into a single process would throw away every one of those properties in exchange for nothing. No monolith.

2Three seams are actively costing us — and it's the same failure each time

The tell that something genuinely needs consolidating isn't "there are a lot of small files." It's the same decision getting made in many different places, differently, and each place getting it wrong independently. That is exactly what happened on the night this review is written about.

2.1 — No shared liveness primitive

Three separate tools each reimplemented "is this thing alive, dead, or could-I-just-not-check" — and each one collapsed "could not check" into "dead," which is the wrong answer every time it happens.

# the watchdog, before the fix [ "$st" = "gone" ] && { log "target gone — watcher exiting"; return 0; } # a transient probe failure read as "gone" — watcher exits FOREVER # measured 2026-08-08: 9 watchers "confirmed gone" inside 35 seconds, # every one of those sessions was alive and answered mail later that day
ToolWhat it botchedReal-world cost
nudge (the watchdog) Unreadable roster read as "target gone" → watcher exits permanently 7 of 10 operator lanes had no live watchdog while their tasks were active
blocked adopt --list A liveness probe timing out (30s × 104 dirs) read as "not orphaned" ~50 minutes of serial timeouts ending in a confident, false "no orphaned blocks"
roster read (agentflow-relay) An unreadable question ledger rendered as "0 open blocks" A session with real open blocks looked idle and safe to ignore

The fix: one shared alive / dead / unknown result that every tool calls into, with unknown always retrying and never being read as a negative. Killing this class once kills it everywhere at once, instead of three (or six) separate teams independently re-discovering the same bug.

2.2 — Two stores for one answer

Gera's decisions landed in the docs KV store the instant he answered them. The operators, though, read blocked check — a different store — which was timing out fleet-wide. Result: 40 real, already-given answers sat completely invisible to the sessions that needed them, for the better part of an hour, with nobody lying to anybody — just two ledgers that don't talk to each other.

# from the tool's own header comment, written before tonight even happened # ⚠️ THE ANSWER MAY BE IN THE ORIGINAL'S LEDGER, NOT IN ANY ANSWER STORE. # ── THE ANSWER STORE AND THE LEDGER ARE TWO STORES. SAY SO. ─────────────

The fix: one answer store, one reader path. Every "did the human already tell us" check goes through the exact same read — never a race between "the page shows it" and "the session can see it."

2.3 — No single health read

Answering "is this operator alive and making progress" required stitching together four different sources by hand, every time: task.json, the tmux pane content, the watcher's PID, and the block ledger. None of them alone told the truth; only the combination did — and that combination isn't written down anywhere, it's re-derived on the spot each time a human (or Smith) asks.

The fix: the fleet-health-tab lane that merged tonight is the right shape — it should become the canonical health read, with every other "is it alive / is it stuck" check routed through it instead of re-deriving the answer from raw sources on each ask.

Bonus debt, same family: the script tier duplication — the same file living in godloop (canonical), live (running), and loop-canon (reported) — has already caused one fix to be silently reverted by the nightly sweep 3m48s after it landed. Worth collapsing to one source with the other tiers generated from it, not hand-copied.

3The verdict, and what changes

This is strong architecture with three under-built shared primitives — not a system that needs a rewrite. Add a shared liveness result, one answer store, and one canonical health read; then delete the per-tool reimplementations sitting behind them. Every strength above survives untouched — small tools, inspectability, the token split, graceful degradation — and the one property that hurt every stalled lane tonight (the same wrong assumption, made independently in six places) goes away for good.

4On the board

Three operators, one per consolidation, each scoped to add the shared primitive and delete the reimplementations it replaces — not just patch the symptom in place.

Shared liveness primitive Driving

One alive / dead / unknown read, called by nudge, blocked adopt, and the roster reader — unknown always retries, never reads as a negative.

Single answer store Driving

Collapse the decisions KV store and the blocked ledger into one read path, so an answer landing on the page is immediately visible to the session waiting on it — no more "answered but invisible" hour.

Canonical health read Driving

Make fleet-health-tab the one place "is this operator alive and progressing" gets answered, replacing the four-source hand-stitch with a single call.

Source: this thread's live investigation of ~/.local/bin/nudge, blocked, and the decisions/ledger split, verified against running code and the merged batch of 2026-08-10/11 — not asserted from memory.
PropFlow Docs