fix(cli): an explicit resume starts a second conversation instead of continuing the run's own
Problem
archon workflow resume <run-id> creates a second CLI conversation for a run that already has one, and the resumed segment's output lands in that new thread instead of the run's own.
workflowResumeCommand builds its continuation options without a conversationId (packages/cli/src/commands/workflow.ts:4925-4931). runWorkflowWithOwnedSource then takes options.conversationId ?? generateConversationId() (workflow.ts:2384) and creates a fresh conversation row (:2389), sending at least the Dispatching workflow message to it (:3105). The run row's conversation_id is written only at creation, so it keeps pointing at the original thread while the new output goes somewhere else.
The sibling shape at workflow.ts:4927 also covers archon workflow run <name> --resume, whose options from cli.ts:710 carry no conversationId unless a caller passes one.
Proved on a scratch ARCHON_HOME (created and removed, no live resource touched): a failed run left one CLI conversation and a run row pointing at it; archon workflow resume <id> added a second conversation and printed Dispatching workflow: **resume-fork** to it, while the run row's conversation_id was unchanged.
Why it matters
A run's conversation is where an operator reads what happened. Splitting a resumed segment into a thread the run does not reference means the run's own conversation silently stops at the pause, and the work after the resume is only findable by knowing a second id exists. Every other continuation path in the CLI already avoids this deliberately: approve, respond, and the new owner-resume site all pass conversationId: platformConversationId under the comment "keep all messages in one thread" (workflow.ts:5226, :5364, :5490).
The value of resume goes up as durable waits become normal. #3316 just made a CLI-owned run resume its own deadline, so resumed segments stop being an exception.
Why now
Found while reviewing #3316. That PR fixed exactly this mechanism for the automatic owner-resume path (finding R1) because the new site had copied workflowResumeCommand's incomplete four-field shape. The explicit resume command is the original of that copy and was left as-is, since it sits outside that PR's accepted outcome. It is pre-existing at a7e300b2.
Desired outcome
archon workflow resume <run-id> continues the run's existing conversation, so all of a run's messages stay in one thread regardless of how many times it is resumed. The same holds for archon workflow run <name> --resume.
Invariants
- The run row's
conversation_idremains the single source of truth for which thread a run belongs to; the fix reads it rather than introducing a second record of the same fact. - A run with no prior conversation still gets one — the fallback to
generateConversationId()stays for that case and must not become a silent no-op. - No change to the approve, respond, or automatic owner-resume paths, which already pass the id.
- Resume semantics themselves are untouched: this is about which thread output goes to, not about what gets re-executed.
Acceptance
- Seen red first: on
dev, resuming an existing run creates a second CLI conversation row while the run row'sconversation_idis unchanged. - After: resuming that run produces no new conversation, and the resumed segment's messages are readable in the run's original thread.
- The same for
archon workflow run <name> --resume. - A test covers the observable outcome — one run, one conversation, across a resume — rather than asserting the call shape.
Evidence
Review of #3316; the discovery is recorded in that run's discoveries.md. Anchors: packages/cli/src/commands/workflow.ts:4925-4931, :2384, :2389, :3105, :5226, :5364, :5490; packages/cli/src/cli.ts:710.
Source: coleam00/Archon