You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
afx workspace remove-architect <name> succeeds unconditionally when the target architect has in-flight builders attached to it (spawnedByArchitect == <name>). The architect's state.db.architect row is deleted; the builders' state.db.builders rows are left untouched — so each orphan builder's spawnedByArchitect column still points at the removed name even though no live architect by that name is registered.
This is the Spec 786 OQ-A resolution, documented in the existing handler:
"Removing a sibling with in-flight builders is permitted (per OQ-A) — the builders' subsequent afx send architect calls fall back to main via tower-messages.ts:336."
That decision was defensible at the time the spec landed because routing was the only consequence. Since then, three changes have made the stale-pointer surface visibly worse:
state.removeArchitect (packages/codev/src/agent-farm/state.ts): single DELETE FROM architect WHERE workspace_path = ? AND id = ?. The builders table is untouched.
removeArchitect Tower handler (packages/codev/src/agent-farm/servers/tower-instances.ts): no builder-count check; refuses only main (workspace-defining) and unknown names.
workspace-remove-architect.ts CLI: refuses empty name + main; otherwise dispatches.
Routing fallback (packages/codev/src/agent-farm/servers/tower-messages.ts:336): builder-sender + architect target → look up spawnedByArchitect, if registered route there, else fall back to main. Routing-only; never back-writes the DB row.
Consequences of the keep-stale-pointer design
Surface
Symptom
state.db consistency
spawnedByArchitect = '<removed-name>' rows linger, querying by spawnedByArchitect returns orphans pointing at no live architect
Agents view (architect-axis)
Stale-owner group header for a removed architect
Click-to-open on stale header
codev.openArchitectTerminal('<dead-name>') fails gracefully but the affordance is dead
afx send architect:<name> from non-builder
NOT_FOUND until the name is re-added
Re-adding the same name
Silent identity collision: orphan builders attribute to a fresh architect with zero context for their history; the new architect's brief never mentions them
Conversation history
The removed architect's codev/state/<name>.md working memory is gone — orphan builders can't recover what their owning architect knew about their plan / gates / scope
Proposed fix
Refuse the removal by default when any builder has spawnedByArchitect == <name>, and offer two opt-in escape hatches at the CLI:
--reassign-to <target> (default suggestion: main) — reassigns each orphan builder's spawnedByArchitect to <target> (must be an existing architect; refuses if <target> is not registered), then completes the removal.
--force — proceeds with the removal and leaves the orphan builders pointing at the dead name (today's behaviour, preserved as an opt-out for cases where the user explicitly wants the old semantics).
Default (no flag) refuses with an actionable message listing the orphan builders + the two recovery commands:
Refusing to remove architect 'vscode': 2 in-flight builder(s) own this architect.
- builder-pir-1234 (#1234, implement phase)
- builder-air-1235 (#1235, review phase)
Choose one:
afx workspace remove-architect vscode --reassign-to main
afx workspace remove-architect vscode --force (leaves builders with a stale owner)
Why this shape
Doesn't trap the user (the Spec 786 OQ-A concern): --force preserves the old behaviour as an explicit opt-out.
Default is safe: orphan builders are surfaced before they can become invisible problems.
Cleans the stale pointer in the common case: reassigning to main (or another named architect) closes the routing degradation, the Agents-view stale-header rendering, and the identity-collision-on-re-add path in one DB write.
Adds visibility: even users who choose --force see the count + IDs of what they're orphaning.
Acceptance criteria
afx workspace remove-architect <name> with no flags refuses when ≥1 builder has spawnedByArchitect == <name>. Error names each orphan builder (id + phase) and surfaces both --reassign-to and --force as remediation paths.
--reassign-to <target> writes spawnedByArchitect = <target> for every orphan builder before removing the architect. Refuses if <target> is not a currently-registered architect, or equals <name>.
--force proceeds with today's behaviour: removes the architect, leaves builders with stale spawnedByArchitect. Logs a WARN naming the orphan count.
A new state.reassignBuildersToArchitect(workspacePath, fromName, toName) helper performs the bulk update atomically (single transaction) so a partial failure doesn't leave half the builders reassigned.
Tower-side handler accepts a reassignTo?: string option in its removeArchitect call and dispatches the new state helper before the architect-row delete.
The CLI flow validates --reassign-to target's existence client-side before calling Tower, so the user gets a clear "no such target architect" message instead of a server-side error.
Unit tests cover: refuses on orphans without flag; reassign-to-main happy path; reassign target validation; force-with-orphans logs WARN; no-orphans path unchanged (no extra prompts, no log spam).
Behavioural test asserts the Agents view's stale-owner header is gone after a --reassign-to main removal (the orphan builders should now group under MAIN).
Out of scope
Conversation-history transfer from the removed architect to main (the removed architect's codev/state/<name>.md notes are lost; revisiting that would be a separate Spec 1090-adjacent issue).
The "what happens when afx tower start doesn't auto-launch a workspace's persisted roster" gap discussed separately; orthogonal.
An interactive Quick Pick in VS Code for "pick a target architect to reassign to" — could be a follow-up, but the CLI fix is the load-bearing path.
Changing state.db schema. spawnedByArchitect is already nullable / mutable text.
Protocol
PIR. This reverses a Spec 786 design decision (OQ-A) and changes user-visible CLI behaviour for a command they've been using; the design-shift wants a plan-gate sanity check + a dev-gate run-through of all three flow paths (default-refuse, --reassign-to main, --force) before PR.
Related
Spec 786 OQ-A — the original keep-stale-pointer resolution this issue revisits.
Problem
afx workspace remove-architect <name>succeeds unconditionally when the target architect has in-flight builders attached to it (spawnedByArchitect == <name>). The architect'sstate.db.architectrow is deleted; the builders'state.db.buildersrows are left untouched — so each orphan builder'sspawnedByArchitectcolumn still points at the removed name even though no live architect by that name is registered.This is the Spec 786 OQ-A resolution, documented in the existing handler:
That decision was defensible at the time the spec landed because routing was the only consequence. Since then, three changes have made the stale-pointer surface visibly worse:
codev.openArchitectTerminal('<dead-name>'), which fails gracefully but is a dead affordance.Verified against source
state.removeArchitect(packages/codev/src/agent-farm/state.ts): singleDELETE FROM architect WHERE workspace_path = ? AND id = ?. Thebuilderstable is untouched.removeArchitectTower handler (packages/codev/src/agent-farm/servers/tower-instances.ts): no builder-count check; refuses onlymain(workspace-defining) and unknown names.workspace-remove-architect.tsCLI: refuses empty name +main; otherwise dispatches.packages/codev/src/agent-farm/servers/tower-messages.ts:336): builder-sender +architecttarget → look upspawnedByArchitect, if registered route there, else fall back tomain. Routing-only; never back-writes the DB row.Consequences of the keep-stale-pointer design
state.dbconsistencyspawnedByArchitect = '<removed-name>'rows linger, querying by spawnedByArchitect returns orphans pointing at no live architectcodev.openArchitectTerminal('<dead-name>')fails gracefully but the affordance is deadafx send architect:<name>from non-builderNOT_FOUNDuntil the name is re-addedcodev/state/<name>.mdworking memory is gone — orphan builders can't recover what their owning architect knew about their plan / gates / scopeProposed fix
Refuse the removal by default when any builder has
spawnedByArchitect == <name>, and offer two opt-in escape hatches at the CLI:--reassign-to <target>(default suggestion:main) — reassigns each orphan builder'sspawnedByArchitectto<target>(must be an existing architect; refuses if<target>is not registered), then completes the removal.--force— proceeds with the removal and leaves the orphan builders pointing at the dead name (today's behaviour, preserved as an opt-out for cases where the user explicitly wants the old semantics).Default (no flag) refuses with an actionable message listing the orphan builders + the two recovery commands:
Why this shape
--forcepreserves the old behaviour as an explicit opt-out.--forcesee the count + IDs of what they're orphaning.Acceptance criteria
afx workspace remove-architect <name>with no flags refuses when ≥1 builder hasspawnedByArchitect == <name>. Error names each orphan builder (id + phase) and surfaces both--reassign-toand--forceas remediation paths.--reassign-to <target>writesspawnedByArchitect = <target>for every orphan builder before removing the architect. Refuses if<target>is not a currently-registered architect, or equals<name>.--forceproceeds with today's behaviour: removes the architect, leaves builders with stalespawnedByArchitect. Logs a WARN naming the orphan count.state.reassignBuildersToArchitect(workspacePath, fromName, toName)helper performs the bulk update atomically (single transaction) so a partial failure doesn't leave half the builders reassigned.reassignTo?: stringoption in itsremoveArchitectcall and dispatches the new state helper before the architect-row delete.--reassign-totarget's existence client-side before calling Tower, so the user gets a clear "no such target architect" message instead of a server-side error.--reassign-to mainremoval (the orphan builders should now group underMAIN).Out of scope
main(the removed architect'scodev/state/<name>.mdnotes are lost; revisiting that would be a separate Spec 1090-adjacent issue).afx tower startdoesn't auto-launch a workspace's persisted roster" gap discussed separately; orthogonal.state.dbschema.spawnedByArchitectis already nullable / mutable text.Protocol
PIR. This reverses a Spec 786 design decision (OQ-A) and changes user-visible CLI behaviour for a command they've been using; the design-shift wants a plan-gate sanity check + a dev-gate run-through of all three flow paths (default-refuse,
--reassign-to main,--force) before PR.Related
vscodeno longer inherits the previous one's session).state.removeArchitect(packages/codev/src/agent-farm/state.ts),removeArchitectTower handler (packages/codev/src/agent-farm/servers/tower-instances.ts),workspace-remove-architect.tsCLI.