Skip to content

Unify builder conversation resume onto persisted session IDs (harness.session), retiring the jsonl-discovery heuristic #1112

Description

@amrmelsayed

Context

#832 introduced a persisted, agent-neutral conversation-session mechanism for architects: a session_id column on the architect table, written at spawn (Tower generates a UUID and passes it via the new HarnessProvider.session.newSessionArgs), and read back at every revive surface to --resume. It was needed because named sibling architects (Spec 755) share cwd = workspacePath, which breaks the newest-jsonl-by-mtime heuristic.

Builders still resume via that jsonl-discovery heuristic (findLatestSessionId in spawn.ts, shipped in #831/#829). It works for builders because each owns a unique worktree cwd (.builders/<id>/), so "newest jsonl" is unambiguous. So this is not a bug — builders resume correctly today.

Why unify anyway

Now that the robust mechanism exists, builders are the lone holdout on a weaker one:

  • Single mechanism over two. One persisted-id path for all agent terminals beats maintaining jsonl-discovery for builders + stored-id for architects (consolidate duplicates / single source of truth).
  • More robust. A stored id is immune to the heuristic's edge cases — a stray manual claude session run in the worktree, mtime races, or --resume picking an unintended lineage.
  • Reuse. The HarnessProvider.session capability (newSessionArgs/resumeArgs) added in Multi-architect conversation resume: disambiguate via per-architect session UUID #832 drops straight into the builder spawn path; Codex/Gemini/OpenCode builders degrade gracefully (no session capability -> fresh spawn), same as architects.

Proposed approach

  1. Add a persisted session_id for builders (migration on the builders table, or reuse terminal_sessionsopen question, see below).
  2. At builder spawn (spawn.ts), generate a UUID, pass it via harness.session?.newSessionArgs, and store it.
  3. On resume (afx spawn --resume, afx workspace recover), read the stored id and --resume via harness.session?.resumeArgs.
  4. Fallback chain: stored id -> jsonl-discovery (kept as a legacy fallback) -> fresh. Unlike architects, builders need no transitional capture command — jsonl-discovery already resolves legacy builders correctly (unique cwd), so it's a clean, self-healing fallback with zero migration ceremony.

Open questions

  • Storage location: builders.session_id (parallels architect.session_id) vs a shared terminal_sessions.session_id (one column covering every terminal type). The latter is more "single source of truth" but a larger change to the global-db path.
  • Whether to eventually retire jsonl-discovery entirely (once all builders carry stored ids) or keep it permanently as the legacy fallback.

Acceptance criteria

  • A builder spawned after this lands stores its session id and resumes from it on --resume / recover, independent of jsonl mtime.
  • Legacy builders (no stored id) still resume via the jsonl-discovery fallback — no regression, no capture step.
  • Non-Claude builder harnesses spawn fresh (no claude flags leak), consistent with architects.
  • The architect path from Multi-architect conversation resume: disambiguate via per-architect session UUID #832 is untouched.

Out of scope

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Fields

    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