Context
Follow-up to #830 (architect session revival). #829 shipped builder conversation resume via on-disk jsonl discovery. That same mechanism extends cleanly to the main architect (committed on the #829 branch) because main is the only architect using Tower's automatic launchInstance path. It does not extend to named sibling architects added via afx workspace add-architect (Spec 755), because they share cwd = workspacePath with each other and with main — and the jsonl-discovery heuristic (newest *.jsonl by mtime) cannot tell which jsonl belongs to which named architect.
This issue scopes the multi-architect disambiguation work that #830 left open.
Problem
A workspace with main + named siblings (e.g., architect-2, reviewer-bob) has multiple Tower-managed Claude PTYs running in the same cwd. Each writes its session jsonl to ~/.claude/projects/<encoded-workspacePath>/. After a reboot:
So named siblings come back as fresh sessions, losing all conversation context. Operators using multi-architect workspaces lose more on reboot than single-architect users do.
Why jsonl-discovery alone can't solve this
The jsonl filename is the Claude session UUID. The jsonl directory is encoded from cwd. There is no per-architect metadata in either the filename or the directory path. Peeking inside a jsonl to read its content for an architect-name marker is brittle (undocumented internal format, breaks on Claude version bumps).
The only robust fix is to persist a per-architect session identifier somewhere Tower controls.
Proposed approach
Add claude_session_id TEXT to the existing architect SQLite table (already created by Spec 755 for sibling persistence; Spec 786 Phase 3 extended it to main).
At spawn time (in both launchInstance for main and addArchitect for siblings):
- Generate
crypto.randomUUID().
- Pass
--session-id <uuid> to claude in cmdArgs.
- After successful PTY creation, store the UUID in the architect row via
setArchitect({ name, cmd, startedAt, terminalId, claudeSessionId }).
At revive time (launchInstance re-firing after reboot, or sibling restoration loop calling addArchitect):
- Look up the stored UUID via the architect-by-name getter.
- If present: pass
--resume <uuid> to claude (skip role injection — the saved conversation already contains the role/system prompt).
- If absent (legacy row): current behavior — fresh session with role injection.
Fallback chain for consistency with builders:
For architects, lookup priority becomes: stored UUID → (skip jsonl-discovery — ambiguous for shared cwd) → fresh spawn.
For builders, the chain stays: jsonl-discovery → fresh spawn. The two mechanisms coexist because the constraint differs (unique cwd for builders, shared cwd for architects). Documented in code.
Backwards compatibility
Architect rows from before this lands have no claude_session_id. On their first revival they fall back to fresh spawn (no regression vs current behavior). After that first revival their row gets a stored UUID and subsequent revivals resume cleanly.
Acceptance criteria
- After reboot of a workspace with main + N named siblings, running
afx workspace start revives every architect and each lands in its own prior conversation (no cross-attachment).
- Adding a new named architect via
afx workspace add-architect --name foo stores the new UUID at spawn time.
- Removing a named architect (
workspace remove-architect) clears its UUID alongside the architect row.
- Legacy architect rows (no stored UUID) gracefully fall back to fresh spawn.
- Tests cover: spawn-stores-UUID, revive-reads-UUID, legacy-fallback, removal-clears-UUID, two siblings revive independently to correct conversations.
Out of scope
References
Context
Follow-up to #830 (architect session revival). #829 shipped builder conversation resume via on-disk jsonl discovery. That same mechanism extends cleanly to the main architect (committed on the #829 branch) because main is the only architect using Tower's automatic launchInstance path. It does not extend to named sibling architects added via
afx workspace add-architect(Spec 755), because they sharecwd = workspacePathwith each other and with main — and the jsonl-discovery heuristic (newest*.jsonlby mtime) cannot tell which jsonl belongs to which named architect.This issue scopes the multi-architect disambiguation work that #830 left open.
Problem
A workspace with main + named siblings (e.g.,
architect-2,reviewer-bob) has multiple Tower-managed Claude PTYs running in the same cwd. Each writes its session jsonl to~/.claude/projects/<encoded-workspacePath>/. After a reboot:afx workspace start, Tower'slaunchInstancere-spawns main. Spec 786 Phase 3 then iteratesstate.db.architectto re-add each persisted sibling viaaddArchitect().addArchitect— deliberate) or, if naively enabled, attach to whichever architect's jsonl was most recent — possibly the wrong one.So named siblings come back as fresh sessions, losing all conversation context. Operators using multi-architect workspaces lose more on reboot than single-architect users do.
Why jsonl-discovery alone can't solve this
The jsonl filename is the Claude session UUID. The jsonl directory is encoded from cwd. There is no per-architect metadata in either the filename or the directory path. Peeking inside a jsonl to read its content for an architect-name marker is brittle (undocumented internal format, breaks on Claude version bumps).
The only robust fix is to persist a per-architect session identifier somewhere Tower controls.
Proposed approach
Add
claude_session_id TEXTto the existingarchitectSQLite table (already created by Spec 755 for sibling persistence; Spec 786 Phase 3 extended it to main).At spawn time (in both
launchInstancefor main andaddArchitectfor siblings):crypto.randomUUID().--session-id <uuid>to claude incmdArgs.setArchitect({ name, cmd, startedAt, terminalId, claudeSessionId }).At revive time (
launchInstancere-firing after reboot, or sibling restoration loop callingaddArchitect):--resume <uuid>to claude (skip role injection — the saved conversation already contains the role/system prompt).Fallback chain for consistency with builders:
For architects, lookup priority becomes: stored UUID → (skip jsonl-discovery — ambiguous for shared cwd) → fresh spawn.
For builders, the chain stays: jsonl-discovery → fresh spawn. The two mechanisms coexist because the constraint differs (unique cwd for builders, shared cwd for architects). Documented in code.
Backwards compatibility
Architect rows from before this lands have no
claude_session_id. On their first revival they fall back to fresh spawn (no regression vs current behavior). After that first revival their row gets a stored UUID and subsequent revivals resume cleanly.Acceptance criteria
afx workspace startrevives every architect and each lands in its own prior conversation (no cross-attachment).afx workspace add-architect --name foostores the new UUID at spawn time.workspace remove-architect) clears its UUID alongside the architect row.Out of scope
afx workspace startflow handles re-spawn; this issue only handles the conversation-context restoration during that re-spawn).References
afx workspace recover(builder process revival + conversation resume).packages/codev/src/agent-farm/servers/tower-instances.ts—launchInstance(main) andaddArchitect(siblings) spawn paths.packages/codev/src/agent-farm/utils/claude-session-discovery.ts— the jsonl-discovery helper this design intentionally bypasses for siblings.