afx: gate sibling-architect respawn on liveness evidence; make remove-architect durable (PIR #1150)#1178
Merged
Conversation
…ation primitives
…ead registrations
…egistrations on retry
…mmitted removals
…urfacing, stale-row purge
… in removeArchitect retry
amrmelsayed
added a commit
that referenced
this pull request
Jul 15, 2026
amrmelsayed
added a commit
that referenced
this pull request
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PIR Review: Removed sibling architect resurrects after Tower recovery
Fixes #1150
Summary
Deliberately removed sibling architects were coming back to life (with their old conversation resumed) after machine crashes and recovery events, because
launchInstance's reconciliation loop respawned every persistedarchitectrow with no liveness check, and several mechanisms could leave or re-create rows for removed architects. This PR gates sibling respawn on liveness evidence (a matchingterminal_sessionsrow, or a resumable session artifact verified through #1145's harnesssession.verifyOwnership; session-less harnesses exempt) and prunes dead registrations; makesremove-architectsurface DB delete failures instead of swallowing them, and purge stale registration rows on retry; and switchesglobal.dbtosynchronous = FULLso a committed removal cannot be rolled back by an OS crash.A plan-review finding worth preserving: the issue's original root-cause ranking (swallowed SQLite errors, WAL loss) was reversed during review. The dominant injection path for the July reports was the #1118 state.db consolidation (shipped one week before the reports), whose upsert-if-newer merge re-inserts rows deleted from
global.dbsince the snapshot, because deletion leaves no tombstone. The rare-event defects were fixed too, but the consumer-side liveness gate is what covers all injection paths at once.Files Changed
packages/codev/src/agent-farm/servers/tower-instances.ts(+85 / -2) — liveness gate + prune in the sibling reconcile loop,hasArchitectTerminalSession()helper,removeArchitectfailure surfacing + stale-row purgepackages/codev/src/agent-farm/servers/tower-utils.ts(+29 / -0) —siblingRegistrationIsLive()(reuses codev adopt / launchInstance: main architect hijacks an unrelated Claude Code session instead of spawning fresh #1145'ssession.verifyOwnership)packages/codev/src/agent-farm/db/index.ts(+8 / -2) —synchronous = FULLpackages/codev/src/agent-farm/__tests__/tower-instances.test.ts(+274 / -0) — prune vs respawn, removal failure surfacing, stale-row purge testspackages/codev/src/agent-farm/__tests__/tower-utils.test.ts(+52 / -0) —siblingRegistrationIsLiveunit testscodev/plans/1150-afx-removed-sibling-architect-.md(+109) — plan (with plan-review revisions)codev/state/pir-1150_thread.md(+40) — builder threadcodev/resources/arch.md,codev/resources/lessons-learned.md— governance updates (this commit)Commits
69499e07[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Plan draft9dc054fb[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Plan revised: rank Consolidate state.db tables into global.db (single user-global database) to eliminate the cwd-dependent fragmentation that causes "missing architect state after restart" #1118 consolidation as primary resurrection vectorfd02adb1[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Plan rechecked post-rebase: reuse codev adopt / launchInstance: main architect hijacks an unrelated Claude Code session instead of spawning fresh #1145 ownership-verification primitivesa985270e[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Gate sibling reconciliation on liveness evidence; prune dead registrations0c393573[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Surface removeArchitect DB delete failures; purge stale registrations on retry3f124f91[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] SQLite synchronous=FULL: close the WAL-loss window for committed removalse2e08d28[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Tests: liveness gate, prune vs respawn, removal failure surfacing, stale-row purge2d189b9b[PIR afx: removed sibling architect resurrects after Tower recovery — reconciliation trusts persisted row without liveness check #1150] Thread: implement phase notesTest Results
pnpm build: pass (types → core → codev, incl. copy-skeleton)pnpm test(packages/codev): pass — 3463 passed, 48 skipped, 0 failed (11 new tests)porch donesynchronous = FULLcost of ~26µs per commit (500-write benchmark, NORMAL 0.008ms vs FULL 0.033ms per write on APFS SSD)Architecture Updates
Routed to the COLD tier (
codev/resources/arch.md), updated in this commit:architect) vs runtime state (terminal_sessions), fixed the stale schema line (addedsession_id, missing since Multi-architect conversation resume: disambiguate via per-architect session UUID #832), and added the consumers-must-not-trust-row-existence caveat.synchronous = FULLwith the measured cost and rationale.No HOT tier (
arch-critical.md) changes: the existing "state lives in global.db / never modify by hand" fact already covers the always-on essence; the liveness-gate mechanics are subsystem reference detail. Cap and map unchanged.Lessons Learned Updates
Routed to the COLD tier (
codev/resources/lessons-learned.md), updated in this commit:tailmasks its exit code; fresh worktrees need the root build before any suite result is trustworthy.No HOT tier (
lessons-critical.md) changes: closest existing entries ("verify reviewer/plan claims against the actual file", "single source of truth") already cover the always-on versions; nothing here displaces a current top-10 entry.Things to Look At During PR Review
removeArchitectdeleted only the stalearchitectregistration row, not leftoverterminal_sessionsrows, so a removal where only the terminal-session delete had failed was not retryable (retry reported "not found" while the stale runtime row remained) — a deviation from the approved plan, which specified purging both layers. Fixed: the purge branch now finds and deletes matchingterminal_sessionsrows (findArchitectTerminalSessionIds) alongside the registration row, and treats either layer's presence as purgeable. Two regression tests pin it (stale-terminal-row-only retry; both-layers purge). Claude's verdict was APPROVE with no issues. PIR's consultation is single-pass, so this fix was not independently re-reviewed — please eyeball the purge branch (tower-instances.ts, the!terminalIdblock inremoveArchitect) at theprgate.session_idand terminal rows are wiped on stop), so session-less harnesses are treated as live (siblingRegistrationIsLive, rule documented in the function comment).remove-architectpurge is the remedy; the consolidation-tombstone question is flagged as a possible follow-up.session_id = NULL, Claude harness) and long-idle siblings whose jsonl was pruned by Claude's retention are now pruned instead of respawned fresh; recovery is oneadd-architect.removeArchitectnow returnssuccess: falseafter the terminal is already dead when a registry delete fails; the error text explains the state and the retry path.How to Test Locally
pnpm build && pnpm -w run local-install, then:sqlite3 ~/.agent-farm/global.db "INSERT OR REPLACE INTO architect (workspace_path, id, pid, port, cmd, started_at, terminal_id, session_id) VALUES ('<workspace>', 'ghost', 0, 0, 'claude', datetime('now'), NULL, 'deadbeef-0000-0000-0000-000000000000')"afx workspace stop && afx workspace start→ expect the "Pruned dead sibling architect registration 'ghost'" log and no ghost inafx status.afx workspace add-architect --name tmp, stop, start → tmp resurrects with its conversation.afx workspace remove-architect --name tmp, stop, start → stays gone.Flaky Tests
None skipped. (An initial full-suite run showed 32 failures across 7 files; all traced to an unbuilt fresh worktree — missing core dist and skeleton copy — not to this diff, and were green after the root build.)