Summary
deepchat run watch can exit when one provider stream finishes even though the detached Agent run is still active or waiting for a user interaction.
Priority: P1
Reproduction
Using a sanitized detached run:
- Start an Agent run that reaches a tool requiring approval.
- Start
deepchat run watch --run <run-id>.
- The provider round emits
chat.stream.completed before the permission interaction is resolved.
run watch exits successfully.
- An immediate
run get still reports an active Session with a pending assistant message.
The permission wait is valid behavior. The defect is that the watcher reports terminal completion too early.
Root cause
RunService.isTerminalEvent() in src/main/cli/runService.ts treats either of these root-Session events as terminal:
chat.stream.completed
chat.stream.failed
Those events describe one stream/provider-round boundary, not necessarily the entire durable run lifecycle. A tool loop, follow-up provider round, permission interaction, question interaction, pending-input handoff, or resume can still follow.
The existing tests in test/main/cli/runService.test.ts explicitly expect a root chat.stream.completed event to resolve the watcher, so the incorrect lifecycle assumption is currently encoded as a contract.
Related contracts and documentation
docs/architecture/local-control-plane/spec.md requires --jsonl to emit one terminal result/error record and says thin CLI invocations must exit after that terminal result or EOF.
- The same document defines detached runs as recoverable from Session state and event cursors after disconnection.
docs/architecture/durable-execution-journal/spec.md makes execution/run_terminal the authoritative physical-run terminal fact before terminal projections.
The watcher should terminate on the durable run/session contract, not a provider-stream implementation event.
Impact
- Automation can start downstream work while the Agent is still running.
- Benchmarks record incomplete latency and output.
- Scripts may omit required interaction handling or cancellation.
- Users interpret a normal interaction pause as an unexplained stop.
Proposed direction
- Stop using
chat.stream.completed/failed as whole-run terminal signals.
- Prefer an explicit run-terminal event or a final Session state transition to
idle/error with matching run ownership.
- Add a public phase such as
running | awaiting_interaction | terminal; do not represent an interaction wait as ordinary active generation only.
- Preserve cursor catch-up, descendant-run filtering, overflow handling, and already-terminal fast paths.
Acceptance criteria
User benefit
CLI users and harnesses can trust that “watch finished” means the run actually reached a terminal state.
Summary
deepchat run watchcan exit when one provider stream finishes even though the detached Agent run is still active or waiting for a user interaction.Priority: P1
Reproduction
Using a sanitized detached run:
deepchat run watch --run <run-id>.chat.stream.completedbefore the permission interaction is resolved.run watchexits successfully.run getstill reports an active Session with a pending assistant message.The permission wait is valid behavior. The defect is that the watcher reports terminal completion too early.
Root cause
RunService.isTerminalEvent()insrc/main/cli/runService.tstreats either of these root-Session events as terminal:chat.stream.completedchat.stream.failedThose events describe one stream/provider-round boundary, not necessarily the entire durable run lifecycle. A tool loop, follow-up provider round, permission interaction, question interaction, pending-input handoff, or resume can still follow.
The existing tests in
test/main/cli/runService.test.tsexplicitly expect a rootchat.stream.completedevent to resolve the watcher, so the incorrect lifecycle assumption is currently encoded as a contract.Related contracts and documentation
docs/architecture/local-control-plane/spec.mdrequires--jsonlto emit one terminal result/error record and says thin CLI invocations must exit after that terminal result or EOF.docs/architecture/durable-execution-journal/spec.mdmakesexecution/run_terminalthe authoritative physical-run terminal fact before terminal projections.The watcher should terminate on the durable run/session contract, not a provider-stream implementation event.
Impact
Proposed direction
chat.stream.completed/failedas whole-run terminal signals.idle/errorwith matching run ownership.running | awaiting_interaction | terminal; do not represent an interaction wait as ordinary active generation only.Acceptance criteria
run watchwhile another round or interaction remains possible.awaiting_interactionis observable without treating the permission/question wait as an error.User benefit
CLI users and harnesses can trust that “watch finished” means the run actually reached a terminal state.