docs(sessions): define durable commands and replaceable control delivery - #6497
docs(sessions): define durable commands and replaceable control delivery#6497mmabrouk wants to merge 3 commits into
Conversation
Work package B of the session-control RFC. An implementation-ready design for reliable API-to-runner commands, version one, with Cancel as the only command kind. - Traces today's Stop path and its 0 to 30 second delay chain, with path:line. - Defines the `session_commands` table, its columns by semantic role, indexes, two-layer idempotency, and retention. - Defines the pending/claimed/applied/obsolete machine with the compare-and-set guard for each transition, so two API replicas cannot both claim or settle. - Defines the claim lease, expiry and re-delivery, and runner-side deduplication. - Defines the long-poll and outcome routes, their runner-token authentication, the hold, and the backoff table. - Defines the heartbeat fallback field and how the runner deduplicates. - Walks Stop end to end plus five failure cases, holding Redis ownership until settlement and leaving `alive` in place so the session resumes warm. - Adds the control-delivery port in Python and TypeScript, a seven-PR migration sequence, and the unit, integration and live-stack test plan. `api-design.md` carries only the version-one route contracts, in the house DTO style, after a field-by-field interface review. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Answers sections 3 and 4 of the architecture review. - H-2: the claim loop is session-scoped and process-level, alive for the session's warm lifetime. The runner declares the sessions it holds, taken from the execution registry and the keep-alive pool keys, so a parked approval keeps a channel after its heartbeat stops. Stop cancels that session's pending interaction, the call the kill route already makes. - H-3: three guards. The target is pinned at admission, the runner refuses to abort an execution that started after the command's created_at, and first-party clients always send expected_execution_id. - H-4: settlement keys off heartbeat age, not the 3600 second Redis TTL. Lease is 90 seconds, three heartbeat intervals. Names the watchdog branch as the settlement owner and states the one-writer rule. - H-5: the applied-command set lives beside the session pool, outlives a loop restart, and re-applying only re-sends the acknowledgement. - H-6: claims are queries over durable state, never a cursor. - Interfaces: target, input, policy and delivery groups in the envelope; no owner_replica_id and no runner_url in the record. Adds a direct-call adapter section. The runner gains POST /cancel beside POST /kill and the API a client beside kill_runner_sandbox, both small, both behind the same port and settling through the same outcome route. States what the durable record adds over a bare direct call and what breaks with two replicas, so the default adapter is a one-line choice. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…uard Two amendments from the team lead's review. Section 9 becomes "the direct-call adapter as an alternative first adapter", the review's 8b. It now states the insert-then-call ordering, the parked-approval lookup through SessionPool.awaitingApproval that both adapters need, the three things the adapter cannot do, and a recommendation to build it first. The wrong-replica failure is made loud without a census: a not_held answer for a session whose row is alive with a fresh heartbeat is the mis-route and nothing else produces it, so it logs, counts, and settles as lost rather than not_running. A required single-replica flag and a heartbeat-fed replica census are offered as optional preventive checks. Retrying across the load balancer is rejected. The stale-Stop guard becomes four guards, led by an API-side comparison of the command's arrival time with the current turn's start time. There is nowhere to read that time today: updated_at is the heartbeat, runner-minted turn ids are uuid4, the Redis value is compared whole by the release script, and the session_turns append is fire-and-forget. So add session_streams.turn_started_at, stamped by the same statement that writes turn_id, guarded by a CASE so repeated heartbeats do not move it. A null start time does not fire the guard; the runner's in-memory check catches that case. The outcome value is superseded_by_newer_turn. Section 5 states that a claim is a query over durable state with no cursor, so a command created while nothing was listening is returned by the next claim. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📘 Docs preview
This comment updates in place on every push. |
Session commands need durable acceptance and a transport-independent route to the runner.
This design specifies the session command table, guarded state transitions, claim recovery, direct API-to-runner delivery, and runner-initiated long polling. The RFC now selects direct delivery for version one. Long polling is parked in Linear AGE-4253.
Issue coverage
Design only. It supports the Stop, concurrent-send, Queue, Steer, and approval issue groups but closes no runtime issue.
Dependencies
This pull request depends only on the session RFC branch. Runtime implementation is in #6503.
How to review