4.5 KiB
4.5 KiB
Claude Provider Redesign Plan
Goal
Eliminate dropped/misrouted Claude messages and stuck lifecycle transitions under foreground/autonomous interleaving by replacing heuristics with an explicit state machine and deterministic routing.
Non-Negotiable Constraints
- Single-owner SDK consumption: only one internal pump calls
query.next(). - Task notifications are metadata only, never lifecycle correctness dependencies.
- Interrupt must work when lifecycle is
running, even with no foregroundpendingRun. - Public API semantics unchanged:
stream,streamHistory,interruptsignatures remain.waitForFinishremains status-based (running -> idle/error/permission/timeout).
Final Architecture
1) Session Turn Ownership State
turnState: idle | foreground | autonomous
Transitions:
idle -> foregroundon userstream()send.foreground -> idleon foreground terminal event.idle -> autonomouson first routable autonomous SDK event.autonomous -> idlewhen autonomous run(s) terminal.autonomous -> foregroundmust be atomic:- interrupt autonomous
- await teardown
- start foreground
2) Run Lifecycle Tracking
Per-run state (tracked independently from timeline):
queued | awaiting_response | streaming | finalizing | completed | interrupted | error
Session status derives from runs:
runningiff any run is inqueued|awaiting_response|streaming|finalizingpermissioniff permission pendingerroriff erroredidleotherwise
3) Deterministic Event Routing (single decision point)
In the query pump, normalize each SDK event, then route by this order:
- If
turnState=foreground: route all non-metadata events to foreground run queue. - Else route by identifiers:
task_idparent_message_idmessage_id- fallback: create autonomous run
- If no route: metadata-only event.
No timestamp windows. No suppression/diversion heuristics.
4) Timeline Assembly Decoupled from Run Ownership
Timeline is assembled from normalized message events keyed by message_id, independent of run ownership:
message_start-> create/update timeline entrymessage_delta-> appendmessage_stop-> finalize- Support out-of-order events (placeholder then reconcile)
Guarantee: assistant messages cannot be dropped from timeline due routing ambiguity.
Refactor Sequence (Low-Risk Migration)
- Introduce explicit types:
TurnStateRunTracker(run map + lifecycle)TimelineAssembler(message-id based)
- Refactor pump flow to:
- normalize event
- route via single decision point
- update run tracker
- always feed timeline assembler
- Implement atomic
autonomous -> foregroundtransition. - Remove old heuristics:
- suppression windows
- pre-prompt diversion/grace logic
- task-notification lifecycle branching
- Keep manager/provider external contracts unchanged.
Correctness Invariants
- Only query pump calls
query.next(). - Foreground exclusivity: while
turnState=foreground, non-metadata SDK events cannot route to autonomous path. - Lifecycle correctness:
runningemitted when any run active; terminal transitions settle back toidle. - Interrupt correctness:
cancelAgentRun()works for autonomous running with no foregroundpendingRun. - Timeline completeness: every assistant
message_start/delta/stopappears in timeline.
Test Plan
Unit Tests
- Deterministic routing by identifier priority.
- Foreground exclusivity under interleaving events.
- Run lifecycle transitions (success, interrupt, error).
- Timeline assembler out-of-order robustness.
- Manager autonomous cancel path without foreground
pendingRun.
Real Claude E2E Gates
repro: do-it-again + immediate hello can hang after autonomous wake under churn- acceptance: autonomous
runningtransition observed within timeout each cycle.
- acceptance: autonomous
repro: transcript/timeline parity after do-it-again + hello race- acceptance: assistant messages present in transcript (including HELLO and autonomous completion) are present in timeline.
Rollout / Risk Controls
- Add temporary structured debug logs for:
- turn state transitions
- routing decisions (
event -> run) - run lifecycle transitions
- timeline assembly (
message_id)
- Keep clear rollback boundaries by incremental commits:
- types + scaffolding
- routing replacement
- heuristic removal
- test hardening
- Run
npm run -w packages/server typecheckand targeted test suites after each stage.