Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
249 commits
Select commit Hold shift + click to select a range
531bbca
docs: start session control and live events RFC
mmabrouk Sep 2, 2026
643417a
docs: clarify commands and Stop dependencies
mmabrouk Sep 2, 2026
43ffd1a
docs: sketch public session control API
mmabrouk Sep 2, 2026
5c6a8f3
docs: compare session control API patterns
mmabrouk Sep 2, 2026
84f5e1b
docs: define public session interface direction
mmabrouk Sep 2, 2026
0184645
docs: keep queued session inputs immutable
mmabrouk Sep 2, 2026
bffd37a
docs: focus session RFC on architecture
mmabrouk Sep 2, 2026
f30eb91
docs: define durable session input acceptance
mmabrouk Sep 2, 2026
b185904
docs: clarify shared session visibility
mmabrouk Sep 2, 2026
5ac7405
docs: distinguish proposed session endpoints
mmabrouk Sep 2, 2026
e121b83
docs: confirm side-by-side session API migration
mmabrouk Sep 2, 2026
1fcfa55
docs: analyze session record invariants
mmabrouk Sep 2, 2026
fd78406
docs: compare durable event history options
mmabrouk Sep 2, 2026
c953cba
docs: clarify event retry and execution fencing
mmabrouk Sep 2, 2026
48948fc
docs: update session RFC status
mmabrouk Sep 2, 2026
bf592e1
docs: constrain stop delivery for remote runners
mmabrouk Sep 2, 2026
6d63a9d
docs: isolate runner control transport
mmabrouk Sep 2, 2026
579899f
docs: define stop command lifecycle
mmabrouk Sep 2, 2026
f4a6834
docs: add session work handoff
mmabrouk Sep 2, 2026
ff99064
fix(api): acknowledge records only after Postgres commits
mmabrouk Sep 2, 2026
9a81f38
fix(api): delete records by their real primary key in EE retention
mmabrouk Sep 2, 2026
b1924fa
feat(qa): add session-control cells to the agent release gate
mmabrouk Sep 3, 2026
d843d39
fix(qa): accept HTTP 202 as a successful Stop in session_control.py
mmabrouk Sep 3, 2026
d76cb67
feat(qa): add a claude harness and Daytona startup slack to session_c…
mmabrouk Sep 3, 2026
f299efc
fix(qa): capture the resume reply text in cell_stop_approval's evidence
mmabrouk Sep 3, 2026
26314c7
fix(qa): add the PID to the run-folder name in session_control.py
mmabrouk Sep 3, 2026
4c61517
fix(qa-driver): don't crash stop-during-completion on a driver-side t…
mmabrouk Sep 3, 2026
12250e5
feat(qa-driver): port the runner-gone cell into session_control.py
mmabrouk Sep 4, 2026
5ce3d7e
feat(qa-driver): guarantee runner recovery in a finally block, add co…
mmabrouk Sep 4, 2026
b438fa0
feat(qa-driver): make the runner-gone hard race deterministic, accept…
mmabrouk Sep 4, 2026
86eafe2
test(qa-driver): cover _judge_runner_gone's race classification directly
mmabrouk Sep 4, 2026
3c07ceb
feat(qa-driver): add --client-shape full|last-message
mmabrouk Sep 4, 2026
bb19c17
feat(qa-driver): Daytona-aware sandbox-gone and codex-child hooks
mmabrouk Sep 4, 2026
2356ce8
fix(qa-driver): require a real signal beyond recall for restart-after…
mmabrouk Sep 4, 2026
d5bbdc3
fix(qa-driver): assert command settlement after every Stop, not just …
mmabrouk Sep 4, 2026
e33a1aa
feat(qa-driver): per-harness stream read timeout, Codex and Claude at…
mmabrouk Sep 4, 2026
a769ce9
fix(qa): target the tested session's own sandbox in the sandbox-gone …
mmabrouk Sep 4, 2026
f6bf53c
fix(qa): poll for the session's sandbox before the sandbox-gone kill
mmabrouk Sep 4, 2026
74ad9f9
fix(qa): wait for the runner to recover before the runner-gone-late Send
mmabrouk Sep 4, 2026
4fbfcb6
fix(qa): assert runner-gone is_running while the runner is still paused
mmabrouk Sep 4, 2026
97b42fd
fix(qa): accept a Stop that lands after a natural finish in the share…
mmabrouk Sep 4, 2026
402d24e
merge: #6502 records ack after Postgres commit into feat/session-control
mmabrouk Sep 4, 2026
b897267
fix(qa): enforce session-control release results
mmabrouk Sep 4, 2026
a15ab5f
docs(qa): clarify session-control hook coverage
mmabrouk Sep 4, 2026
e2a25fe
chore(qa): format session-control result tests
mmabrouk Sep 4, 2026
dac7bdf
merge: #6518 session-control cells in the release gate into feat/sess…
mmabrouk Sep 4, 2026
253c454
fix(runner): refuse a second turn instead of destroying the running one
mmabrouk Sep 2, 2026
35fded9
fix(frontend): keep the typed message when a session refuses a second…
mmabrouk Sep 2, 2026
95d9940
test(api): pin the heartbeat answers single-turn admission depends on
mmabrouk Sep 2, 2026
e1243db
docs(sessions): record the single-turn admission slice
mmabrouk Sep 2, 2026
242b0b5
feat(runner): stream the admitted turn id so a client can name the ex…
mmabrouk Sep 2, 2026
303a2e0
docs(sessions): record the turn-id frame and the corrected test counts
mmabrouk Sep 2, 2026
366fe0e
feat(sdk): carry the admitted turn id on the message metadata
mmabrouk Sep 2, 2026
e4fb78e
docs(sessions): the turn id rides message metadata, not a data part
mmabrouk Sep 2, 2026
17787d8
merge: #6500 refuse a second turn before the sandbox into feat/sessio…
mmabrouk Sep 4, 2026
61efa86
feat(runner): keep the sandbox warm when a user stops a turn
mmabrouk Sep 2, 2026
bd67e2e
docs(sessions): record the spike A findings on cancelling a turn warm
mmabrouk Sep 2, 2026
d61c30e
fix(runner): park only an abort the runner can prove was a user Stop
mmabrouk Sep 2, 2026
f2dd073
docs(sessions): record the per-harness cancel behaviour and the park …
mmabrouk Sep 2, 2026
94a7f31
test: pin the cancelled stop reason on the terminal record, both sides
mmabrouk Sep 2, 2026
90e9d0c
refactor(runner): default the stopped-session park window to the idle…
mmabrouk Sep 2, 2026
7e1c516
docs(sessions): add the per-harness coverage table and correct the pa…
mmabrouk Sep 2, 2026
370572c
fix(runner): kill the shell child a stopped Codex turn leaves in the …
mmabrouk Sep 3, 2026
72d80c0
fix(runner): round a stopped turn's age DOWN before reaping its leake…
mmabrouk Sep 3, 2026
9c662c7
fix(runner): let a stopped turn write the continuity record a complet…
mmabrouk Sep 3, 2026
c4f6ab5
fix(runner): release the session owner claim at shutdown, not after a…
mmabrouk Sep 3, 2026
deb4701
fix(runner): scope Codex reap to its sandbox daemon
mmabrouk Sep 3, 2026
a4e58ff
fix(runner): persist early stop endings
mmabrouk Sep 4, 2026
b880a5f
fix(runner): preserve acquire failure errors
mmabrouk Sep 4, 2026
b099906
fix(runner): replay history when native load is unverified
mmabrouk Sep 4, 2026
53568cd
fix(runner): verify local Pi transcript durability
mmabrouk Sep 4, 2026
8a73a7c
fix(runner): let Stop preempt sandbox acquisition
mmabrouk Sep 4, 2026
3451938
fix(runner): harden stopped sandbox cleanup
mmabrouk Sep 4, 2026
fda2098
fix(runner): scope native history proof to each load
mmabrouk Sep 4, 2026
c7761be
fix(runner): reject truncated replay records
mmabrouk Sep 4, 2026
308ab23
docs(sessions): reconcile stop readiness records
mmabrouk Sep 4, 2026
4558f0d
test(chat): shorten Stop replay note
mmabrouk Sep 4, 2026
3008ac7
fix(runner): preserve preflight Stop cancellation
mmabrouk Sep 4, 2026
a497bfa
fix(runner): replay smart-truncated records
mmabrouk Sep 4, 2026
bbae565
test(runner): count Stop endings after turn admission
mmabrouk Sep 4, 2026
40e8f80
merge: #6496 warm sandbox after Stop, Codex reap, continuity into fea…
mmabrouk Sep 4, 2026
4bdcad8
feat(api): record a session command, and stamp when a turn started
mmabrouk Sep 2, 2026
6287072
feat(api): reach the runner directly to cancel a turn
mmabrouk Sep 2, 2026
ace9f45
feat(api): add POST /sessions/{session_id}/cancel and the outcome route
mmabrouk Sep 2, 2026
a4c5652
feat(runner): accept a cancel command and stop the turn it names
mmabrouk Sep 2, 2026
deb51f1
feat(web): point the desktop Stop button at the cancel route
mmabrouk Sep 2, 2026
ba52d3a
fix(sessions): make the Stop actually reach the run, and settle it
mmabrouk Sep 2, 2026
059b0a5
docs(sessions): record what the durable Stop slice built and verified
mmabrouk Sep 2, 2026
9c42f0a
fix(sessions): label the control-plane abort, and drop the replica ce…
mmabrouk Sep 2, 2026
04ebb96
docs(sessions): update the slice record for the rebase and the census…
mmabrouk Sep 2, 2026
fecca18
fix(sessions): make Stop settlement write the stream row, not only Redis
mmabrouk Sep 3, 2026
bb2d14b
fix(web): key the liveness polls on running, not on the alive set
mmabrouk Sep 3, 2026
14c4eff
fix(sessions): compare a Stop's expectation against the target it res…
mmabrouk Sep 3, 2026
c5ccd22
fix(sessions): settle a Stop outcome whether the row is pending or cl…
mmabrouk Sep 3, 2026
5e466af
fix(sessions): a Stop that lost the race must not destroy the warm sa…
mmabrouk Sep 3, 2026
78861bb
feat(sessions): gate durable stop and late output
mmabrouk Sep 3, 2026
5b06659
fix(sessions): preserve legacy cancel contract
mmabrouk Sep 3, 2026
442db48
fix(auth): narrow session control exemption
mmabrouk Sep 4, 2026
76db4d7
fix(sessions): preserve legacy cancel response shape
mmabrouk Sep 4, 2026
4889c3d
fix(sessions): persist cancelled interaction records
mmabrouk Sep 4, 2026
113cad4
fix(sessions): preserve idempotent Stop targets
mmabrouk Sep 4, 2026
d6e93c9
fix(auth): compare runner tokens as bytes
mmabrouk Sep 4, 2026
290820f
fix(sessions): keep cancellation publishing fail-open
mmabrouk Sep 4, 2026
28f4b98
fix(sessions): validate direct cancel responses
mmabrouk Sep 4, 2026
8d6d019
fix(runner): harden durable Stop delivery
mmabrouk Sep 4, 2026
24aab68
fix(frontend): route session Stop through Fern
mmabrouk Sep 4, 2026
e13661e
fix(frontend): capture Stop target before unlocking sends
mmabrouk Sep 4, 2026
f184867
fix(mobile): react to session liveness when polling gates
mmabrouk Sep 4, 2026
f43cfef
style(sessions): trim liveness rationale comments
mmabrouk Sep 4, 2026
cd21450
docs(sessions): record direct delivery as version one
mmabrouk Sep 4, 2026
19e06e1
fix(qa): wait for watchdog terminal records
mmabrouk Sep 4, 2026
d91ed34
fix(runner): preserve admitted stop handle
mmabrouk Sep 4, 2026
347cb5c
fix(runner): honor stop during pause teardown
mmabrouk Sep 4, 2026
0791279
fix(api): replay cancel idempotency before targeting
mmabrouk Sep 4, 2026
1c202ae
fix(entities): normalize legacy stop response
mmabrouk Sep 4, 2026
adcc987
fix(frontend): abort locally while resolving stop target
mmabrouk Sep 4, 2026
00fae4b
test(runner): preserve cancel fixture type
mmabrouk Sep 4, 2026
d81f935
fix(qa): honor durable stop mode in session driver
mmabrouk Sep 4, 2026
630a676
merge: #6554 driver fixes 6 and 7 (poll for the watchdog ending; flag…
mmabrouk Sep 4, 2026
56531f6
fix(frontend): pin stop target before queued sends
mmabrouk Sep 4, 2026
bac1a42
fix(frontend): pin durable Stop to the streamed turn
mmabrouk Sep 4, 2026
d4c4e43
fix(frontend): keep new turns unpinned before metadata
mmabrouk Sep 4, 2026
3935e0c
fix(sessions): make unfenced cancel running-only
mmabrouk Sep 4, 2026
3f18f74
merge: #6503 durable Stop command, direct delivery, the flag into fea…
mmabrouk Sep 4, 2026
af14418
feat(sessions): settle executions whose runner cannot report an outcome
mmabrouk Sep 2, 2026
5eb30cf
fix(runner): probe the sandbox over HTTP, not through a local cache
mmabrouk Sep 2, 2026
208be17
docs(sessions): record the execution watchdog slice
mmabrouk Sep 2, 2026
59b5059
fix(sessions): key the watchdog off heartbeat age, not a lease
mmabrouk Sep 2, 2026
4e554e5
docs(sessions): name the risk in the threshold question
mmabrouk Sep 2, 2026
c540a64
docs(sessions): record the re-run at the 90-second threshold
mmabrouk Sep 2, 2026
4d6e363
docs(sessions): the watchdog stack is down, and how to bring it back
mmabrouk Sep 2, 2026
559a8d1
feat(sessions): give records a place to mark a late write
mmabrouk Sep 3, 2026
a630aff
feat(sessions): stamp the watchdog as the writer of the ending it writes
mmabrouk Sep 3, 2026
54923d4
feat(sessions): keep a quarantined record out of every transcript read
mmabrouk Sep 3, 2026
8f567b0
feat(sessions): quarantine records that arrive after the watchdog end…
mmabrouk Sep 3, 2026
1eabbb5
fix(sessions): write the ending for a stopped row whose runner died b…
mmabrouk Sep 3, 2026
2301f91
fix(sessions): release the dead turn's alive lock when the sweep writ…
mmabrouk Sep 3, 2026
df82ad2
feat(sessions): gate durable stop and late output
mmabrouk Sep 3, 2026
1ffffbd
fix(sessions): redeliver abandoned stop commands
mmabrouk Sep 3, 2026
c4e7cb9
fix(sessions): enforce one terminal execution outcome
mmabrouk Sep 3, 2026
16cf06e
fix(sessions): make stop settlement atomic
mmabrouk Sep 3, 2026
354f319
fix(sessions): make terminal settlement authoritative
mmabrouk Sep 3, 2026
378fc27
fix(sessions): bound terminal redis repair
mmabrouk Sep 3, 2026
76d3579
fix(sessions): default invalid late output policy
mmabrouk Sep 3, 2026
f43076f
fix(runner): release parked approvals on stop
mmabrouk Sep 3, 2026
a0d15a8
fix(runner): preserve fresh prompts after approvals
mmabrouk Sep 3, 2026
b77432c
fix(sessions): publish settled interaction cancellations
mmabrouk Sep 3, 2026
4ffabe4
chore(sessions): remove dead settlement field
mmabrouk Sep 3, 2026
0e398a3
fix(runner): settle parked approvals before repark
mmabrouk Sep 3, 2026
4342a65
fix(runner): watch re-gates while settling approvals
mmabrouk Sep 3, 2026
50418b3
fix(api): select watchdog endings by execution
mmabrouk Sep 4, 2026
cd7117a
fix(api): wire commands service into session watchdog
mmabrouk Sep 4, 2026
356c176
fix(api): clear dead session owner in watchdog
mmabrouk Sep 4, 2026
b76b701
fix(sessions): document cancel execution guard
mmabrouk Sep 4, 2026
7146130
fix(api): bound watchdog ending candidates
mmabrouk Sep 4, 2026
9160f68
chore(sessions): number the ending-marker migration 026
mmabrouk Sep 4, 2026
2fe0e92
fix(runner): repark a parked-approval Stop warm when no harness cance…
mmabrouk Sep 4, 2026
5d8058e
fix(api): keep the execution watchdog alive when a sweep pass raises
mmabrouk Sep 4, 2026
55db87f
fix(api): give MultiLogger an exception method
mmabrouk Sep 4, 2026
2727465
fix(api): let the watchdog settle commands past a row it cannot map
mmabrouk Sep 4, 2026
a629cf2
fix(api): let a runner claim commands past a row it cannot map
mmabrouk Sep 4, 2026
80531f0
fix(api): clear the running flag when the watchdog marks an execution…
mmabrouk Sep 4, 2026
1fc9aea
fix(api): tombstone a swept turn so a returning runner cannot re-set …
mmabrouk Sep 4, 2026
ca43e25
fix(sessions): reclaim session affinity from a replica that holds no …
mmabrouk Sep 4, 2026
7904daa
fix(sessions): persist the watchdog collapse through a Core UPDATE
mmabrouk Sep 4, 2026
fbc8259
fix(sessions): write the lost-turn is_running clear as a Core UPDATE
mmabrouk Sep 4, 2026
0f85872
test(sessions): pin watchdog settlement invariants
mmabrouk Sep 4, 2026
5251677
test(runner): pin watchdog teardown and probe routes
mmabrouk Sep 4, 2026
d7e58dc
fix(runner): end a turn when the provider says its sandbox is gone
mmabrouk Sep 4, 2026
8229202
test(runner): pin the sandbox-gone terminal end to end
mmabrouk Sep 4, 2026
4aecd1f
fix(sessions): reconcile watchdog rebase
mmabrouk Sep 4, 2026
9a15766
fix(sessions): guard watchdog stream updates
mmabrouk Sep 4, 2026
91fbab0
fix(sessions): retry watchdog lookup failures
mmabrouk Sep 4, 2026
c0a5ab9
fix(runner): expose quiet-run timeout overrides
mmabrouk Sep 4, 2026
0446be9
test(sessions): model guarded sweep rowcounts
mmabrouk Sep 4, 2026
7202cea
fix(api): fence watchdog settlement cleanup
mmabrouk Sep 4, 2026
3b3d8cc
fix(api): fence heartbeat row mirrors
mmabrouk Sep 4, 2026
b10767e
fix(api): generation-fence session affinity
mmabrouk Sep 4, 2026
e51ca17
fix(api): restore legacy watchdog cleanup
mmabrouk Sep 4, 2026
5c5355e
test(api): follow generated owner values
mmabrouk Sep 4, 2026
0e27a42
fix(api): fence heartbeat on stream state
mmabrouk Sep 4, 2026
9609237
fix(api): preserve owner generation on reclaim
mmabrouk Sep 4, 2026
1460da7
merge: #6501 execution watchdog, settlement, quarantine, sandbox-gone…
mmabrouk Sep 4, 2026
dc68ce0
fix(api): preserve outage backlog during recovery
mmabrouk Sep 4, 2026
06142b6
fix(api): split record batches only on row rejection
mmabrouk Sep 4, 2026
994fbbe
style(chat): format the agent turn test
mmabrouk Sep 4, 2026
83d0a24
fix(runner): decide a settled user Stop before the client-disconnect …
mmabrouk Sep 4, 2026
8d9cacc
fix(runner): carry and park a first turn's allowed sibling call
mmabrouk Sep 4, 2026
7b8b533
fix(runner): keep stopped sessions warm for ten minutes
mmabrouk Sep 4, 2026
f0dab5a
merge: #6557 warm Stop from the browser, first-turn sibling park, 600…
mmabrouk Sep 4, 2026
8180554
merge: #6556 keep outage-backlog records, split batches only per reco…
mmabrouk Sep 4, 2026
5526f9e
fix(api): guard Stop against the turn it did not mean, and cancel its…
mmabrouk Sep 2, 2026
0a35ca1
fix(frontend): close the approval card when the user stops the turn
mmabrouk Sep 2, 2026
5e2fbd2
docs(sessions): record the Stop guard slice, its live results, and it…
mmabrouk Sep 2, 2026
b3af4f9
fix(api): exempt Stop from the per-project concurrency limit
mmabrouk Sep 2, 2026
31b6097
fix(frontend): tell the user when a Stop was refused, and make mobile…
mmabrouk Sep 2, 2026
0dbc968
docs(sessions): record the added scope and its live results
mmabrouk Sep 2, 2026
31ab82b
feat(frontend): send the turn id with Stop so it cancels that turn or…
mmabrouk Sep 2, 2026
fd9c61f
docs(sessions): record the browser half of the turn id
mmabrouk Sep 2, 2026
2660548
fix(frontend): read the turn id from message metadata, not a data part
mmabrouk Sep 2, 2026
80ef6f5
docs(sessions): correct the browser half to the metadata mechanism
mmabrouk Sep 2, 2026
c10d9fd
docs(sessions): record why the turn id is read from the messages, not…
mmabrouk Sep 2, 2026
6ce81fb
fix(frontend): wait for durable stop acceptance
mmabrouk Sep 3, 2026
9bc0e19
fix(frontend): apply durable stop review
mmabrouk Sep 3, 2026
2ab4fd8
style(api): format cancel stop guard test
mmabrouk Sep 4, 2026
d850edb
fix(frontend): keep Stop available during approvals
mmabrouk Sep 4, 2026
1b48b8d
fix(frontend): render remote Stops as neutral
mmabrouk Sep 4, 2026
f252bd6
fix(frontend): settle parked Stops immediately
mmabrouk Sep 4, 2026
563f608
fix(api): make Stop displacement atomic
mmabrouk Sep 4, 2026
31ceae6
fix(chat): preserve Stop cancellation evidence
mmabrouk Sep 4, 2026
874559a
fix(frontend): scope Stop state to its request
mmabrouk Sep 4, 2026
9326550
docs(sessions): clarify Stop contracts
mmabrouk Sep 4, 2026
7fcd676
test(api): cover atomic Stop races
mmabrouk Sep 4, 2026
71084ae
style(frontend): trim Stop implementation comments
mmabrouk Sep 4, 2026
dc5d5d9
fix(frontend): clear stale stop guards before turns
mmabrouk Sep 4, 2026
e1ee2b0
fix(frontend): reset stopped latch on newer turns
mmabrouk Sep 4, 2026
63fb017
fix(mobile): settle stops after runs park
mmabrouk Sep 4, 2026
57d73c2
fix(frontend): disable interactions while stopping
mmabrouk Sep 4, 2026
5e54914
test(frontend): stabilize desktop stop hook harness
mmabrouk Sep 4, 2026
93ad612
style(frontend): order desktop hook test imports
mmabrouk Sep 4, 2026
e07deb1
test(api): preserve Stop scopes after rebase
mmabrouk Sep 4, 2026
fff1540
fix(frontend): collapse rebased Stop guard wrappers
mmabrouk Sep 4, 2026
8a8b2ae
fix(frontend): rehydrate durable stop guards
mmabrouk Sep 4, 2026
e65e624
Merge pull request #6504 from Agenta-AI/feat/session-stop-guard
mmabrouk Sep 4, 2026
d4c3d7e
fix(api): validate session record retry bounds
mmabrouk Sep 4, 2026
f1fedce
fix(sessions): harden admission and owner release
mmabrouk Sep 4, 2026
0478ce1
fix(runner): make stopped cleanup fail closed
mmabrouk Sep 4, 2026
ffb780c
fix(runner): record mounts before abort handling
mmabrouk Sep 4, 2026
4cadbd3
fix(runner): persist terminal records for escaped errors
mmabrouk Sep 4, 2026
afafc5c
fix(frontend): recover refused sends safely
mmabrouk Sep 4, 2026
fe131e8
fix(qa): reject incomplete session control evidence
mmabrouk Sep 4, 2026
712373d
docs(sessions): clarify control plane safety contracts
mmabrouk Sep 4, 2026
988552c
fix(runner): keep settled Codex stops warm
mmabrouk Sep 5, 2026
889dbad
fix(frontend): retain refused sends until restored
mmabrouk Sep 5, 2026
3dfd9e4
docs(sessions): restore best-effort reap contract
mmabrouk Sep 5, 2026
2c95ee4
chore(frontend): format user Stop unit test
mmabrouk Sep 5, 2026
d3d2030
fix(frontend): restore held refused sends
mmabrouk Sep 5, 2026
e5a7fa4
fix(frontend): capture refused send before restore frame
mmabrouk Sep 5, 2026
52ffa77
Merge pull request #6558 from Agenta-AI/fix/session-control-coderabbit
mmabrouk Sep 5, 2026
a9d68a7
test(sessions): point the watchdog collapse test at its own database
mmabrouk Sep 5, 2026
8620107
Merge pull request #6567 from Agenta-AI/fix/session-control-coderabbit-2
mmabrouk Sep 5, 2026
0664a8e
v0.115.0
mmabrouk Sep 5, 2026
f72e4aa
Merge pull request #6553 from Agenta-AI/feat/session-control
mmabrouk Sep 5, 2026
5c4c2ef
fix: resolve v0.115 release QA blockers (#6573)
mmabrouk Sep 5, 2026
480e020
[fix] Keep the flag-off watchdog and late-output defaults unchanged (…
mmabrouk Sep 5, 2026
7496c66
fix: prevent approval resume after stop (#6579)
mmabrouk Sep 5, 2026
e3ef284
fix(web): scope test cleanup and render messages once (#6581)
mmabrouk Sep 5, 2026
1a8f02e
fix(api): enable durable Stop by default
mmabrouk Sep 5, 2026
362d1a7
fix(api): enable durable Stop by default (#6584)
mmabrouk Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .agents/skills/agent-release-gate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,62 @@ cell, promoted after the platform-guidance fix closed that exact gap; it reuses
the same way the separate one-shot benchmark (Tier B) does — check there before writing a new
mechanism-blind cell from scratch, to avoid duplicating scaffolding.

## Session control cells

`resources/session_control.py` is a second, standalone driver: sixteen cells that cover Stop,
durable commands, and the runner's recovery paths (owner release, park/resume, watchdog
quarantine). It drives the same product endpoint and asserts on the same wire, but it needs its
own account bootstrap, so it runs as a separate process rather than as `qa_product.py` cells. See
`resources/path_triggers.py` for the exact mandatory-cell mechanism.

**These cells are MANDATORY** — run them, not just the standing gate — whenever the release diff
touches any of:

- `services/runner/src/sessions/**`
- `services/runner/src/engines/sandbox_agent/**`
- `api/oss/src/core/sessions/**`
- `api/oss/src/tasks/asyncio/sessions/**`
- `api/oss/src/apis/fastapi/sessions/**`

Run every cell with one line:

```bash
uv run resources/session_control.py --cells all --harness pi_core --sandbox local
```

Add `--project <docker-compose project name>` to run the eight cells that need direct Docker and
Postgres access (`sandbox-gone`, `records-outage`, `restart-after-stop`, `runner-gone`,
`runner-gone-late`, `post-stop-row`, `codex-child`, `stale-tail`) and the abort-log subcheck inside
`stop-after-finish`. Without `--project`, those eight cells SKIP with a named reason. The
`stop-after-finish` HTTP check still runs, but only its abort-log subcheck is unavailable. The
other eight cells
(`stop-warm`, `double-send`, `stale-stop`, `stop-approval`, `stop-after-finish`,
`repeat-stop`, `concurrent-stops`, `stop-during-completion`) run over HTTP alone against any
deployment. Add
`--resume <path to a prior run's results.json>` to pick a lost run back up: any cell already
recorded there is loaded instead of re-run.

Results land in a timestamped folder under `~/agenta-qa-evidence/` (override with
`AGENTA_QA_RUNS_DIR`), as `results.json` and `summary.md` — the same PASS/FAIL/SKIP shape as the
rest of the gate. When a release path makes session control mandatory, pass that artifact to the
standing gate with `--session-control-results <path>`: a missing or incomplete artifact stops the
gate before the matrix runs, and a recorded FAIL makes the final gate exit nonzero.

**Environment, by name.** Same three-variable discipline as the rest of the gate, no env-file
fallback:

- `AGENTA_BASE` — the deployment origin.
- `AGENTA_ADMIN_KEY` — mints the ephemeral account this driver runs under. Lives in
`~/.agenta-qa-secrets.env`.
- `QA_OPENAI_API_KEY` — stocked into that account's vault so the `pi_core` and `codex` harnesses
have a provider key. Lives in `~/.agenta-qa-openai.env`.
- `ANTHROPIC_API_KEY` — only required for `--harness claude`, stocked into the same vault the
same way. Lives in `~/.agenta-qa-secrets.env`. A pi_core- or codex-only run does not need it.

A Daytona run additionally needs a Secrets-capable Daytona key on the runner; the key in most
session env files returns 403 on the Secrets endpoint, so check that before trusting a Daytona
result.

## When results lie

The runtime **fails open**: a component can break, get logged, and the turn still succeeds with a
Expand Down
18 changes: 17 additions & 1 deletion .agents/skills/agent-release-gate/resources/path_triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
# the second kind as required, because a standalone cell is a separate process it cannot observe.
GATEWAY_TOOLS = ("matrix_gw1_gateway_tools.py",)

# The standing session-control regression cells: Stop, durable commands, and the runner's
# recovery paths (owner release, park/resume, watchdog quarantine). A separate standalone driver
# because it needs its own account bootstrap and, for most cells, a docker-compose project name —
# see resources/session_control.py and SKILL.md "Session control cells".
SESSION_CONTROL = ("session_control.py",)

# The cells that run a REMOTE sandbox and need no extra flag. A release that touches the sandbox
# engine or the Daytona provider changes how a cold sandbox gets built and how its credentials are
# delivered, and the `burst` and `crosstalk` journeys are the only ones that see that path under
Expand Down Expand Up @@ -71,8 +77,18 @@
# A fault here shows up only when many sandboxes start at once, which is what `burst` and
# `crosstalk` do on these cells. Production hit it as one first message in five failing with
# a credential error (AGE-4249 / #6485) while the sequential gate stayed green.
"services/runner/src/engines/sandbox_agent/**": DAYTONA_CELLS,
# A dict literal keeps only the last value for a repeated key, so a glob that already names
# DAYTONA_CELLS lists SESSION_CONTROL alongside it in the SAME tuple rather than as a second
# entry that would silently drop the Daytona rule.
"services/runner/src/engines/sandbox_agent/**": DAYTONA_CELLS + SESSION_CONTROL,
"services/runner/src/providers/daytona*": DAYTONA_CELLS,
# Session control: Stop, durable commands, park/resume, and the owner-release and watchdog
# sweeps. A change here can silently break a warm resume or leave a command stuck, and
# nothing in the fixed matrix drives Stop at all. See qa-audit-2026-09-03.md section 4.
"services/runner/src/sessions/**": SESSION_CONTROL,
"api/oss/src/core/sessions/**": SESSION_CONTROL,
"api/oss/src/tasks/asyncio/sessions/**": SESSION_CONTROL,
"api/oss/src/apis/fastapi/sessions/**": SESSION_CONTROL,
}

# Glob -> journeys that MUST run when the rule fires. Same matching as PATH_TRIGGERS, kept as a
Expand Down
104 changes: 100 additions & 4 deletions .agents/skills/agent-release-gate/resources/qa_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -3118,6 +3118,68 @@ def approval(i: int) -> dict:
}


def _load_session_control_result(path: str) -> dict:
"""Load and summarize a complete standalone session-control result."""
result_path = pathlib.Path(path).expanduser()
try:
payload = json.loads(result_path.read_text())
except (OSError, json.JSONDecodeError) as exc:
raise SystemExit(
f"Cannot read --session-control-results {result_path}: {exc}"
) from exc

cells = payload.get("cells")
if not isinstance(cells, dict):
raise SystemExit(
f"Invalid session-control result {result_path}: expected a top-level cells object."
)

# Import the standalone driver's registry instead of copying its cell names here. A newly
# added session-control cell must become release-mandatory without a second list to update.
from session_control import CELLS as session_control_cells

missing = sorted(set(session_control_cells) - set(cells))
if missing:
raise SystemExit(
f"Incomplete session-control result {result_path}: missing cells: "
+ ", ".join(missing)
)

statuses: dict[str, str] = {}
for name in session_control_cells:
entry = cells.get(name)
verdict = entry.get("verdict") if isinstance(entry, dict) else None
if (
not isinstance(verdict, dict)
or not isinstance(verdict.get("pass"), bool)
or not isinstance(verdict.get("skip"), bool)
or (verdict["pass"] and verdict["skip"])
):
raise SystemExit(
f"Invalid session-control result {result_path}: cell {name!r} has no valid "
"PASS/FAIL/SKIP verdict."
)
statuses[name] = (
"SKIP" if verdict["skip"] else ("PASS" if verdict["pass"] else "FAIL")
)

failed = sorted(name for name, status in statuses.items() if status == "FAIL")
skipped = sorted(name for name, status in statuses.items() if status == "SKIP")
return {
"path": str(result_path),
"status": "FAIL" if failed else ("INCOMPLETE" if skipped else "PASS"),
"failed": failed,
"skipped": skipped,
}


def _session_control_result_label(result: dict) -> str:
label = f"recorded {result['status']}"
if result["skipped"]:
label += "; SKIPPED, UNTESTED: " + ", ".join(result["skipped"])
return label


def main() -> int:
# Declared here, not beside the assignments below, because the flag help strings read these
# module defaults and a `global` statement must precede every use of the name in a function.
Expand Down Expand Up @@ -3271,6 +3333,13 @@ def main() -> int:
"--repo",
help="repository the release diff is read from (default: the current directory)",
)
p.add_argument(
"--session-control-results",
help=(
"results.json written by resources/session_control.py. Required when a path rule "
"makes that standalone driver mandatory; all of its cells must be recorded."
),
)
args = p.parse_args()

resolve_credentials(args.env_file)
Expand Down Expand Up @@ -3368,6 +3437,16 @@ def main() -> int:
external_cells = [
cell for cell in triggered if cell not in CELLS and cell not in missing_cells
]
session_control_result = None
if "session_control.py" in external_cells:
if not args.session_control_results:
raise SystemExit(
"This release makes session_control.py mandatory. Run it separately, then pass "
"its results.json with --session-control-results."
)
session_control_result = _load_session_control_result(
args.session_control_results
)
for cell in triggered:
if cell in CELLS and cell not in cells:
cells.append(cell)
Expand All @@ -3380,7 +3459,11 @@ def main() -> int:
else (
"MISSING — no such cell exists"
if cell in missing_cells
else "run it separately"
else (
_session_control_result_label(session_control_result)
if cell == "session_control.py" and session_control_result
else "run it separately"
)
)
)
print(f" {cell} ({where})")
Expand Down Expand Up @@ -3479,9 +3562,19 @@ def main() -> int:
table += "\n\nMandatory for this release, by path rule:\n\n"
table += "| cell | run here | because this release changed |\n|---|---|---|\n"
for cell, why in triggered.items():
here = "yes" if cell in CELLS else "no — run it separately"
if cell in CELLS:
here = "yes"
elif cell == "session_control.py" and session_control_result:
here = _session_control_result_label(session_control_result)
else:
here = "no — run it separately"
table += f"| {cell} | {here} | {', '.join(why)} |\n"
if external_cells:
unrecorded_external_cells = [
cell
for cell in external_cells
if not (cell == "session_control.py" and session_control_result)
]
if unrecorded_external_cells:
table += (
"\nThis release is NOT green until every cell above marked "
"`run it separately` has a recorded result.\n"
Expand All @@ -3506,7 +3599,10 @@ def main() -> int:
for cell in results.values()
for journey in cell["journeys"].values()
)
return 1 if failed else 0
standalone_failed = bool(
session_control_result and session_control_result["status"] != "PASS"
)
return 1 if failed or standalone_failed else 0


if __name__ == "__main__":
Expand Down
Loading
Loading