Architecture assessment · operator fleet
Nine Doors, One Store
There is no role system. There is a body-management system that creates and kills tmux panes, and separately a prose system that tells a model what it is. Nothing connects them — and every mechanism built to bridge the gap learned its lesson alone.
01The verdict in one paragraph
Every individual mechanism in this fleet is well-reasoned and carries a scar comment naming the incident that produced it. What is missing is not care. It is that no lesson propagates across doors. start-operator learned to arm its own watcher; the Supervisor — the only component that runs continuously — never did. operator-here learned that the allowlist erases fields and wrote a sidecar; start-operator wrote a comment claiming it had been fixed instead. That comment is false, and it was itself written to warn that an earlier comment lied about the identical field. The correction did not survive. Only the confidence did.
origin survived a sweepThe recurring defect is not any one of these bugs. It is the class: one question, two stores that can disagree about the answer. Identity has two answers. Revival has two authorities. Done-ness has nine derivations. Terminal states are declared in five places and superseded is in none of them.
02The shape of the problem
Nine ways a session comes into existence. One shared store on disk. Two independent authorities that revive from it, with no field anywhere saying which one owns a given task.
grep -rn "operatorRespawns" agent-smith/src/ returns zero hits across the whole tree.03The door matrix
measured Nine doors, six questions each. The gaps are the finding.
| Door | writes task.json | arms watcher | validates acceptance | stamps name | full sid | declares watchOnly |
|---|---|---|---|---|---|---|
start-operator | yes | nudge | exit 2 pre-spawn | yes | refuses sid8 | yes → erased |
start-operator --adopt | yes | skipped | relaxed | yes | yes | yes → erased |
operator-here (/operator) | yes | Temporal | + monotone | self-rename | yes | no |
spawn-pair | yes | none | no | op-/bare | sid8 bug | no |
pf/mpf → /api/adopt | via intake | none | no | sess-slug | resolved later | no |
POST /api/intake | intake.ts | none | no | slug | resolved later | no |
| Supervisor respawn | rewrites | none | no | op-<task> | yes | no |
| subagent worker | no | no | no | no | has no sid | n/a |
start-architect | SID pin | no | n/a | architect | yes | n/a |
Three gaps
(a) Two of nine doors arm a watcher, and they arm different, mutually unaware watchers. start-operator arms nudge --idle 600. operator-here arms a Temporal OperatorTaskWorkflow. grep -c temporal ~/.local/bin/nudge → 1, and it is a comment.
(b) The Supervisor can remove watchers and has no code path that adds one. In supervisor/src/spawn.ts, the occurrence counts for nudge, temporal, acceptance.json, operator-here and OperatorTaskWorkflow are all zero. Meanwhile releases.ts:55 runs nudge --stop --sid. It is a one-way ratchet: every respawn silently strips the Operator's alarm clock, and revival is the path that actually runs — 391 attempts today alone.
If the system gates on the agent doing something, the PROMPT must say it… So the launcher arms it. start-operator, ~line 669 — a lesson learned at one door and never carried to the door that fires 391 times a day
(c) Acceptance is validated at exactly one door and re-checked at none. 32 of 53 active tasks have no acceptance.json at all — no finish condition, so they can never mechanically complete.
16 of 54 operator panes are wedged on the Claude Code onboarding/trust dialog. 34 are genuinely live, 4 are something else. Eleven of the sixteen are op-sess-* — the pf-arm-operator → /api/adopt door, which never sets a repo, so spawnOperator falls back to $HOME and lands in a directory that was never trusted.
The repoPathProblem guard that shipped to prevent exactly this exempts the case by design: if (raw.length === 0) return null; — an empty repo is treated as a deliberate fallback rather than a problem. The fix reasoned about the path being wrong and never checked whether the destination was usable. A chosen fallback into an untrusted directory is exactly as fatal as a stumbled one.
Because the pane exists, sessionStillExists reports the operator as fine. Nothing checks whether the body ever got past a modal.
04Two revival authorities — live, and already firing
verified The question was: what prevents double-revival today — timing, convention, or nothing? The answer is nothing, and the overlap is 100%, not partial. All six running OperatorTaskWorkflow subjects have a task.json, and all six are non-terminal by the Supervisor's own predicate.
| Authority A — agentflow Supervisor | Authority B — OperatorTaskWorkflow | |
|---|---|---|
| Cadence | 60s sweep (SWEEP_MS, index.ts:33) | 60–120s tick (op_task.py:331) |
| Death test | isSessionAlive(operatorSid, …), 2 confirmations | roster row matched on name, 2 confirmations |
| Revives by | fresh tmux op-<task> + kickoff — no memory | claude --resume <sid> — keeps the transcript |
| Cap | 5, in task.json.operatorRespawns | 5, in self._respawns |
| Sees the other's cap | no | no |
Why nothing separates them
- Timing doesn't — it synchronizes them. 60s vs 60–120s, both requiring two confirmations. Both cross the threshold within 2–4 minutes of the same death.
- The budgets are invisible to each other.
grep -rn "operatorRespawns" agent-smith/src/→ zero hits, whole tree. Each side independently permits five. One task can burn ten revivals and neither cap will ever fire. - No partition field exists. Union of every key across the six live files:
acceptance, createdAt, criterion, lastSupervisedAt, name, note, operatorFirst, operatorRespawns, operatorSid, repo, requestedBy, startedFrom, state, task, workerSid. Noowner, noengine, nowatchedBy. - The successors are incompatible. A double-revival yields two live sessions answering to one name, working from different context.
In smith-worker.err.log right now: operator-task sess-propflowai-ea: 2 live sessions answer to name 'propflowai-ea' — the revive/resume race remainder — 1,238 occurrences, plus 1,240 for three-oldest-prs. The code names the condition and explicitly declines to resolve it: "DETECTED, never resolved by force — the watcher kills nothing."
The only reason it is survivable is that Authority B refuses to revive while any live row exists — a unilateral de-escalation on one side of a two-sided race. Authority A has no equivalent restraint.
And there are five authorities, not two
co.propflow.watcher-doctor (re-arms nudge lanes every 180s), co.propflow.operator-heartbeat (120s) and co.propflow.decision-block-sweep (600s) each independently restore state. The watcher-doctor's own plist comment concedes the underlying deploy problem:
The AgentFlow Supervisor is the obvious host… It runs from supervisor/dist/index.js, a BUILD ARTIFACT, on a checkout that is presently an unmerged live-deploy branch — so a change there is code that cannot run until somebody builds it, which is not a fix, it is a plan.
co.propflow.watcher-doctor.plist
05The allowlist is not a filter — it is a destructor
readTasks (supervisor/src/tasks.ts) rebuilds a fresh object from 15 named fields. writeTask then does JSON.stringify(t). Every Supervisor sweep therefore permanently erases every unnamed field from disk. Measured across 176 live task files:
| Field | Written by | Survivors / 176 |
|---|---|---|
watchOnly | start-operator:620 | 0 |
origin | start-operator:643 | 0 |
startedFrom | start-operator:597 | 1 (never swept) |
acceptance | start-operator:651, operator-here | 13 (all pre-sweep) |
workerIsSubagentOf | operator-SKILL:542 | 1 |
resolution / pr / note | Operators, ad hoc | 2 each |
branch / worktree / findingStatus | Operators, ad hoc | 1 each |
The end-to-end receipt. ci-green-main was minted carrying startedFrom: "tmux" and a full Slack origin block, captured verbatim in the minting session's transcript. On disk today both are gone, while every allowlisted field survived and grew (operatorRespawns 0→2, lastSupervisedAt and releasedAt added).
start-operator:638 claims: "Fixed in agentflow (readTasks now carries origin, pinned by a round-trip test in supervisor/tests/taskOrigin.test.ts). The claim is true NOW; it was not when it was written."
Verified: TaskOrigin → 0 hits repo-wide. taskOrigin.test.ts → the file does not exist.
That comment was written specifically to warn that a previous comment lied about this exact field — and it is now false in the identical way. The correction did not survive; only the confidence did. This is the whole architecture problem in one line of prose.
Two independent audits computed the dropped-field inventory separately and arrived at the same nine fields with identical counts. The system has now invented two separate sidecar files — acceptance.json and the heartbeat — whose entire reason for existing is that a field cannot survive the Supervisor. awaiting-human exists as a state rather than a field for the same reason.
06Nine done-ness rules across twenty-one code sites
Three surfaces once gave three answers about one task: the table said ✅ done, the container said deploy verdict=-, the watchdog said "needs a human." That was not a bug. It is the designed state of a system with nine independent derivations of the same fact.
The two worth acting on
E9 — session-scope self-report (completion.ts:254-271). A directory matching /^[0-9a-f]{8}$/ whose events.jsonl contains any row with kind === 'closed' counts as complete. No state corroboration is possible — a sid8 dir has no task.json. A bare {"kind":"closed"} line yields a valid report that every consumer renders as a completion, with no validation of the actor.
Two aggravating facts: preferReport (:219-228) picks newest at regardless of scope, and 8 of 123 task-scope reports carry at: null — so any dated self-report outranks them. And the relay manufactures the write target: digester.ts:456 mkdirs tasks/<sid8>/ for every live roster row every 45 seconds. That is the actual cause of the directory sprawl.
0 of 2,879 sid8 directories contain an events.jsonl. All 177 that exist are in named task dirs. E9 is a loaded gun, not a smoking one — but it is one task-event <sid8> --kind closed away, and that CLI exists and is documented at completion.ts:50.
The acceptance grammar the evaluator implements is not the one operators write. parse_check accepts exactly four forms — pr:…merged, run:…green, file:…exists, exit:name N. Anything else returns None = open forever. Fleet-wide: 67 acceptance items, 55 evaluable, 12 unevaluable. Ten of the twelve use the prefix cmd:, which does not exist. All twelve are on one task, sess-propflow-9f, where 12 of 12 are unevaluable — that workflow could never have reached DONE by any amount of correct work, and it has logged 34,134 lines saying so.
07Absence reads as success — the full inventory
| Site | What is absent | What it reads as |
|---|---|---|
tasks.ts:305 | no state field | 'active' — supervised forever |
digester.ts:331 | no state field | invented 'active', served on /api/feed and written into STATE.md |
operator_task.py:773 | deleted task.json | task_ok=False ⇒ terminal check can never pass. Escape only via 2-day idle or 7-day ceiling |
intake.ts:358 | readdirSync throws | return null; // nothing can conflict — mints a second Operator onto a session that may already have one |
roster.ts:447 | task file unreadable | operatorConnected: false — "couldn't read" and "none exists" collapse into one boolean |
| the five terminal sets | superseded | not terminal anywhere — concessions-email-formatting is supervised by both authorities right now |
intake.ts:358 is the sharpest of these because its own sibling check two branches earlier is deliberately three-valued and returns retryable: true. The ledger check has no such branch. An I/O failure reads as permission.
The atomicity is applied in inverse proportion to who reads the file
verified writeTask does tmp + fsync + rename + directory fsync, with a docblock explaining that "a torn read would give the Operator a criterion that is half a sentence, and it would act on it." Meanwhile:
digest/digester.ts:444 fs.writeFileSync(target, renderTaskStateMd(...), 'utf8')
digest/digester.ts:458 fs.writeFileSync(path.join(dir,'STATE.md'), ..., 'utf8')
intervalMs = 45_000 · ~297 writes per sweep
Bare writeFileSync — no temp file, no rename, no fsync — on the file that start-operator:359, 699, 701 and spawn-pair:188 tell every Operator to read instead of the transcript, at every wake. STATE.md copies that same criterion in verbatim and never got the treatment. Elsewhere, audit.ts:175 stages-and-renames for an audit request under no contention at all.
08What is actually deployed
| Component | Runs from | Git state |
|---|---|---|
| Temporal worker | agent-smith working tree, python -m agent_smith.worker | smith/url-label-keep-path @ 93c2eae — 1 ahead / 3 behind main |
| agentflow Supervisor | supervisor/dist/index.js --loop, PID 804 since 08:22 | deploy/spawn-refuses-missing-repo @ 85ebb01 — 2 ahead / 1 behind, +3 uncommitted files |
Neither production component runs main. The worker runs a checked-out feature branch's working tree — no build, no artifact, no pin. There is no Temporal Worker Versioning: Worker(...) sets only task_queue=. A git checkout in that directory is a production deploy that hits every in-flight days-long workflow at its next workflow task, with workflow.patched() as the only guard.
Running but not on main: repoPathProblem cwd validation, the bootSessionWithKickoff refusal, the task-repo-unusable alarm. On main but not running: PR #67's multi-question blocks and AND-gate. Merging main would be a regression — main still has the log-only MALFORMED task.json line measured to have logged 2,947 times over eleven days with zero escalations.
Nobody can currently answer "what is running?" from a git ref. That is why "we fixed it" and "it is fixed on the mini" are independent claims.
09Outdated, and still interfering
watchOnly— the doctrine was abolished 2026-08-19 ("THERE IS ONLY ONE KIND OF OPERATOR"), andstart-operator:620, edited six days later, still writes it. Read by nothing (isWatchOnly: 0 hits). Its comment actively lies to the next reader.pf-arm-operator.sh—operator-here:41states the/operatorshape deleted it, thependingmarker, the/api/adoptintake, the Supervisor's operator-spawn, and idle mode. It deleted none of them.aliases.sh:185and:654still invoke it, and its output is the 14 repo-less tasks now failing respawn.spawn-pair— writes the two-peer-session shapestart-operator's own header calls abandoned, arms no watcher, has a fixedsleep 25, no acceptance gate, and the sid8 defect. Still blessed atoperator-SKILL:587.register-work— vestigial as a tool (one call site, last successful run 2026-08-19, its only caller under a fleet-wide killswitch) but its marker is load-bearing:derive-registryis the sole disk signal that produces the"architect"role. Decouple before deleting.worker-SKILL.md— describes a peer session with its own sid, pane and/rename. The default Worker is now a subagent with none of those. Every kickoff pointing there hands a subagent instructions it cannot execute. Same for the peer-worker machinery inoperator-SKILL(msg <worker-sid>, capture-pane,self-compact <worker-sid>).- 6 of 26 workflow types have zero executions in 30 days. Two have no schedule and no in-code starter at all. They are inert code the worker still loads and every redeploy must keep deterministic.
The decision-block treadmill
40 of 55 running workflows (73%) are SmithDecisionBlockWorkflow. They are not long-lived watchers; they are a relay race. Armed with a 24-hour lifetime, they exit after ~30 minutes, and decision-block-sweep re-arms them ten minutes later. smith-decision-b86386853 has been re-created 296 times in seven days — for a block that already carries an answer, and three of whose four ledger copies say adopted. Three such blocks account for 69% of all decision-block volume.
The sweep's own comment predicted this exact failure — for a different row class. The prediction was right; the case it was written for was the wrong one.
open row outlives the answer
The answers exist. Read directly from the decisions KV store: b86386853 → "Review and merge it, then I run the deploy — four related changes are queued behind it and the longer it waits the messier the merge gets", answered 2026-08-10. b86388306 → "Merge it and deploy now", same day. b86228042 → the SMOKE_PASSWORD placement, same day. Sixteen days ago.
The ledger rows on disk say this:
b86386853 3 copies adopted · 1 copy open b86388306 3 copies adopted · 1 copy open b86228042 3 copies adopted · 1 copy resolved
Each block was adopted and answered in three sessions. A fourth row, in a different session's directory, was never updated. open_block_records skips state != "open" and keys the workflow on the block id alone — so it finds that single stale row, re-arms a driver, and nags about a question that was decided two weeks ago. b86386853 has been re-created 296 times on the strength of one row nobody closed.
The decision loop is broken at the consumption end, not the answering end. Capping the sweep's re-arms (S3.2) treats the symptom. The fix is that resolving a block must resolve every copy of it, or the ledger must stop being multi-copy.
answer store UNREADABLE — the check activity failed: Activity task timed out appears 447 times in the worker log. The KV read genuinely does time out under load: a single wrangler kv key get measured >13 minutes today, and a full site deploy took 62 minutes. This is a feedback loop — more stuck drivers means more KV reads, which means slower reads, which means more UNREADABLE, which means more stuck drivers.
It accounts for only 8, 7 and 3 of the re-arms on the three hot blocks, so it is not their cause. It is a separate bug on the same path, and it will keep firing after the stale rows are closed.
state:blocked-19728752 returns 404 — the key does not exist. read_store's docblock is explicit that an absent artifact must be ({}, None) — empty — and only a failed read may be (None, why), because "reporting 'no answer' for a store we could not read would re-strand the session this whole function exists to unstrand." But wrangler exits non-zero on a 404, so the two collapse: a session whose store was never written can never learn that nobody has answered. It waits on a store that does not exist.
~/.claude/jobs/blocked/<sid>/<block-id>.json — the same block id lives under every session that ever adopted it. 222 distinct block ids; 55 exist in more than one directory; all 55 have copies that disagree about state. The workflow id is keyed on the block id alone, so N contradictory rows collapse onto one driver, which then runs blocked check --sid against a session that may not be the block's owner. 25% of the ledger is in this state.
10What not to change
Half of a reassessment is naming what is already right, so the next pass does not spend itself re-litigating it.
- Subject-derived workflow ids +
USE_EXISTING.op-task-<slug>,smith-decision-<block>,smith-pr-drive-<pr>. Any door, any machine, any day — a duplicate arm is a free no-op. This is the single best decision in the architecture, and the argument for it is inverted: the one place this fleet built its own locking instead — the block ledger — is exactly where 25% of records now contradict each other. - The
dataclasses.replace(req, …)pattern forcontinue_as_new. All four hopping workflows use it and all four carry their deadline correctly. The worst variant — a resetting deadline making a workflow immortal — does not exist here. Make it the written rule: state that must survive a hop is a request field, neverself.*, and the bug class closes by construction. OperatorTaskWorkflow's exit table. 2-day idle, 24-hour blocked grace, 10-nudge ceiling, 5-respawn cap, 7-day hard lifetime, 15-task concurrency cap, every terminal named. Best-bounded thing in the fleet. The problem is the second authority beside it, not it.- The patch-gate discipline. Zero unguarded determinism-affecting changes in 30 commits.
#275's two-disjunct gate —patched()plus a hardcoded start-time cutoff for runs minted during the ungated window — is a sophisticated fix to a genuinely hard problem. Do not simplify it. - The
op-/operator-two-name split. Looks like duplication; is not.op-is the Supervisor's kill/revive namespace,operator-is the human-facing roster row. Collapsing them was tried on 2026-08-07 and made an Operator invisible to the thing reviving it. - The Supervisor's zero-token, file-and-tmux-only design. The one layer that cannot rot. Every fix below stays inside that constraint.
- Fail-toward-open in the check evaluator, and
isStalled()returning false on unknown staleness. Unknown is not evidence. Fix the grammar and the log rate; keep the posture — but pair the latter with a visibletranscript moved: unknownbanner inSTATE.md, because an Operator told to "read STATE.md and judge" currently sees a page with no warning on it. loop-keeplistreturning"undetermined". It changed fromreturn "worker"on 2026-08-21 and that was right. An honest "I don't know" is the correct output for a system where role genuinely is not durable state. Fix the architecture; do not fix the symptom by guessing again.
11The staged plan
Ordered by harm removed ÷ blast radius. Stages 1–2 are reversible and touch no structure; 3–4 are code with a deploy; 5–6 need a decision first.
Stage 1 · today, reversible
b86386853, b86388306, b86228042 all carry an answer and are burning 106 executions/day between them. decision-block-cli stop <id> signals cleanly, then close the open copy.
Removes ~69% of all decision-block volume. Touches nothing structural.
Backfill repo on the repo-less sess-* tasks so spawnOperator stops falling back to an untrusted $HOME. The alternative — accepting folder-trust for /Users/miniclaw — is faster but makes the symptom survivable rather than removing it.
verified Unrotated on the mini: whatsapp-bridge.out.log 118 MB, trello-bridge.err.log 83 MB, smith-worker.err.log 56 MB (spanning 2026-05-26 → today), link-sessions.log 48 MB. A 595 MB relay log was also reported but could not be located under ~/Library/Logs or the agentflow tree — treat that one as unconfirmed.
Stage 2 · merge what is already written
The dedupe guard for the 2,478-lines/day name-collision warning is written, wired and pushed — types.py:2419, seeded at operator_task.py:651, called at :745, carried across continue_as_new at :1217. It is guard state held as a request field, not self.*, which is the correct pattern per §10. It has no effect until the mini deploys.
digester.ts:444, digester.ts:458, operator-heartbeat.py:238. No behavioural blast radius, and it protects the file every Operator is told to judge from. The cheapest real fix in the whole audit.
superseded to all five terminal sets5 sitestasks.ts:98, redmain.ts:208, operator_task.py:531, plus three in the relay. Better: export one and import it — the second hardcoded copy of a derived list is drift with a start date.
Stage 3 · make the erasure stop
Read raw whole, overwrite the 15 managed fields, write the merged object. Strictly additive; nothing depends on erasure. Today the schema is enforced by silent deletion, which means no component can ever add a fact to a task — the property that forced both sidecar files into existence.
Then delete watchOnly and the two false comments at start-operator:612 and :638. Do not fold acceptance.json back in until S5 removes the second writer.
intake.ts, and cap the sweep's re-arms2 call sitesGive sidTaskConflict the same three-valued retryable branch its own sibling already has. Separately, teach decision-block-sweep to skip a block whose record carries an answer — the driver has a lifetime cap, but the sweep has no cap on how many lifetimes it grants.
Stage 4 · close the watcher gap
spawnOperator must arm a watcher as part of spawning and refuse to report success without one. Same argument start-operator already won at its own door — but applied to the path that actually fires 391 times a day. Closes the 46/53 gap permanently.
Requires S5 decided first: it must arm exactly one kind.
Stage 5 · the decision, not a patch
Cheapest correct version: add watchedBy: "temporal" to task.json, add it to the allowlist (it must be done there or it will not survive a sweep — S3.1 first), and make superviseOperators skip those tasks. Six tasks change hands.
The recommendation is Temporal, on a measurement neither doctrine predicted: the mini rebooted at 08:21. Of 33 nudge arms, one process survived. Of the Temporal workflows, six survived and continued-as-new. A watcher hosted on the box cannot watch a box that reboots.
Either way: superviseOperators and _revive must stop being able to fire on the same slug in the same minute.
spawn-pair and pf-arm → /api/adopt14 in-flight tasksoperator-here already documents itself as having replaced them. Finish the job rather than leaving the claim false — then architect-SKILL:333 becomes true instead of aspirational.
Stage 6 · make deploys reproducible
Both components run branch checkouts; the worker runs a working tree. Adopt Temporal Worker Versioning (build_id) so a redeploy cannot hit in-flight days-long workflows at all. Today workflow.patched() is the only thing standing between a git checkout and [TMPRL1100].
12Open decisions
These are yours, not mine. The answer store is currently unreachable — ~/.config/cloudflare/token is empty on the MacBook, and the fallback read through the mini has been sitting on a single wrangler kv key get for over ten minutes under load 4–7 — so they are listed here rather than filed as blocks.
Recommended: backfill repo (S1.2). The alternative, accepting folder-trust for /Users/miniclaw, is a security setting and faster, but leaves the next repo-less task to wedge the same way.
#309 is the flood fix and is inert until the mini deploys. #303 (session-gc) was awaiting your hand-review. Neither will be merged without your word.
Stopping the drivers is reversible and removes 69% of decision-block volume, but it terminates live workflows, so it waits on you. It is also only half the fix: the single open ledger copy behind each one has to be closed too, or the next sweep re-arms them within ten minutes.
Recommended: close the stale rows first, then stop the drivers — in that order, so the sweep cannot undo the stop.
Merging main into production would be a regression in agentflow. The choice is: land the two deploy branches into main, or make the drift visible with a build stamp. Recommended: land them — S6 depends on it and every "we fixed it" claim is unfalsifiable until then.
Argued both ways. Temporal is the right substrate, and it is being used as cron in the two places where it hurts — a launchd timer was put in front of the durable engine (the block sweep re-arming) and beside it (the Supervisor as a second authority). Both inject exactly the non-determinism Temporal was adopted to remove.
Recommended: keep Temporal, remove the cron in front of and beside it. The three properties that cannot be had from cron — subject-derived ids as free mutual exclusion, durable timers that survive the worker, and budgets enforceable across a reboot — are all load-bearing here.
13Method, and the corrections
Three independent audits ran against the live fleet, not against recollection. Every figure in this document is a measurement taken 2026-08-26. Four claims were wrong and are recorded here rather than quietly dropped, because a document about comments that lied should not become one.
| Claim as reported | Correction |
|---|---|
The dup_reported flood fix is "not in any git tree anywhere — 60 remote branches scanned" | False. It is on feat/name-collision-audit, local and origin, fully wired, as PR #309. The audit read the mini's checkout, whose remote refs are stale. The operational conclusion — the flood is still running — holds; the reason is unmerged, not unwritten. |
"operator-heartbeat.py:238 is the only non-atomic writer in the store" | Self-corrected by the auditor. There are three; the Digester's two are far higher-volume and write the file Operators are told to judge from. |
| The pinned-table renderer "is not on this machine" | False, and caught before it propagated. It is in agent-smith/phase_table.py. The relay half of that finding stands: relay's contract is clean and the defect is entirely downstream in Python. |
| All 54 operator panes are stuck on the trust dialog | Refined by direct measurement: 16 stuck, 34 live, 4 other. |
| The relay log is 595 MB, unrotated | Unconfirmed. No log of that size exists under ~/Library/Logs or the agentflow tree. The four sizes that are verified appear in S1.3; this one is carried as a claim, not a measurement. |
The pattern is worth naming, because it is the same one the document is about: "I grepped and found nothing" is not "it does not exist." Three of the four errors above are that exact substitution. The rule that governs the store governs the audit.