Skip to content

Multi-architect conversation resume: disambiguate via per-architect session UUID #832

@amrmelsayed

Description

@amrmelsayed

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):

  1. Generate crypto.randomUUID().
  2. Pass --session-id <uuid> to claude in cmdArgs.
  3. 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):

  1. Look up the stored UUID via the architect-by-name getter.
  2. If present: pass --resume <uuid> to claude (skip role injection — the saved conversation already contains the role/system prompt).
  3. 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

Metadata

Metadata

Assignees

Labels

area/towerArea: Tower server / agent farm CLI

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions