Skip to content

[feat] Session control milestone 1: warm Stop, durable Stop, recovery - #6553

Merged
mmabrouk merged 241 commits into
release/v0.115.0from
feat/session-control
Sep 5, 2026
Merged

[feat] Session control milestone 1: warm Stop, durable Stop, recovery#6553
mmabrouk merged 241 commits into
release/v0.115.0from
feat/session-control

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 4, 2026

Copy link
Copy Markdown
Member

Context

Today a Stop in an agent session destroys the warm sandbox and the native session, a second message during a running turn can kill both turns, and a runner that dies leaves the session marked running with no record of what happened. This branch collects the first milestone of the session-control design in PR #6495: increments 1 to 3, the Stop package. It is cut from release/v0.114.8 so that its diff shows only this work, and it merges into release/v0.115.0 when that branch exists.

What this branch contains, in merge order (all merged, head 86201077e4)

  1. #6502 records are acknowledged only after the Postgres commit.
  2. #6500 a second message during a running turn is refused before the sandbox is touched.
  3. #6496 Stop keeps the warm sandbox and the native session; Codex shell children are reaped; continuity after a cold rebuild.
  4. #6503 the durable Stop command with direct delivery and redelivery, behind AGENTA_SESSIONS_DURABLE_STOP.
  5. #6501 the execution watchdog: one terminal outcome per execution, lost turns settled, late output quarantined.
  6. #6504 desktop and mobile show a real "stopping" state and keep approvals correct across Stop and refresh.
  7. #6518 the session-control cells in the agent release gate.
  8. #6554 and #6556 the milestone review fixes: no record loss after a database recovery, no per-record retries during an outage, Stop delivery reviewed.
  9. #6557 the runner parks a stopped session warm for 600 seconds and decides a settled user Stop before it looks at a client disconnect (the browser aborts its stream in the same tick as the Stop).
  10. #6558 the answers to the 22 CodeRabbit threads on this PR (13 commits; Codex SHIP after four rounds), and #6567 the one finding of the CodeRabbit re-review (a watchdog test now uses its own database).

Every new behaviour ships dark. AGENTA_SESSIONS_DURABLE_STOP and AGENTA_SESSIONS_LATE_OUTPUT default to off. Migrations 022, 023, 024 and 026 on the core database and 005 on tracing are additive and nullable with downgrades. The history producer (#6517) is out of scope and is not part of this branch; the chain skips 025.

Known consequence of the flag

With AGENTA_SESSIONS_DURABLE_STOP off (the production default at merge time), a Stop pressed on an approval card does not cancel the pending approval, so a late answer given a few seconds after the Stop is still accepted. That is today's behaviour; the durable path cancels it. The flag-off matrix run of 4 September recorded it.

Tests

  • Each PR carries its own unit suites (API sessions, runner, chat, entities, mobile) and its own reviews; see each PR.
  • The live matrix on the integration stack (Pi, Codex, and Claude Code on the local provider; Pi and Codex on Daytona; flag off; flip back) is recorded in the release readiness page and in the run folders under ~/agenta-qa-evidence/.
  • Final matrix on the assembled head e65e6246c7 (5 September, driver 43e29546c6), no unexpected failure: Pi local 16 of 16; Codex local 15 plus 1 n/a; Claude Code local 13 plus 2 n/a plus the five re-run cells 5 of 5 (all five concurrent Stops resumed in the same sandbox); Pi Daytona 14 plus 1 n/a plus 1 known limitation; Codex Daytona 7 plus 1 n/a; flag off 9 of 9; flip back 2 of 2; Daytona continuity 2 of 2 by sandbox id. Run folders under ~/agenta-qa-evidence/2026-09-05-final-matrix/.
  • Two findings on the way, both closed: a driver defect (the concurrent-stops cell fired its Stops before any model call; fix 8 waits for the first model event and proves reuse by sandbox id) and an environment defect of the integration store (an orphaned multipart upload made geesefs crash on every mount, 30 seconds per cold start; repaired; runner follow-up #6559).
  • Known limitation for the release notes: native session reuse across a runner restart on Daytona is unsupported until the Secret registry follow-up ([docs] Plan durable managed-resource reconciliation #5278 PR B); the turn recovers by server-side reconstruct.
  • After fix: address CodeRabbit review for session control #6558 merged, the six cells that cover its changes ran on the merged head: Codex local (stop-warm, codex-child, sandbox-gone) 3 of 3 and Pi local (stop-warm, runner-gone-late, double-send) 3 of 3.
  • Release-ready. The branch retargets to release/v0.115.0 once that branch exists.

What to QA

  • Start a long turn, press Stop: the turn ends within a second, the next message continues in the same sandbox and remembers the earlier turns.
  • Press Stop twice fast: one terminal record, no error.
  • Send a message while a turn runs: a clear refusal, the running turn survives.
  • With the flag off (the production default): Stop behaves exactly as before this branch.

Release readiness page: https://claude.ai/code/artifact/62f080fc-6be0-457d-aed5-a566443d8f20

Agent-generated, low weight. Nothing here merges to main or a release branch on its own.

https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk

mmabrouk and others added 30 commits September 2, 2026 15:05
The records stream worker added every decoded Redis message id to its
acknowledged list during deserialization, before it attempted the Postgres
write. A failed `append_many` logged an error and continued, and the shared
consumer loop then acknowledged and deleted those messages from the stream.
Every Postgres failure was therefore permanent, silent record loss, and the
worker reported success while doing it (#5496).

`append_many` is one statement in one transaction, so one record Postgres
rejected also took its whole batch with it, losing up to fifty unrelated
records per rejection (#5594).

Three changes:

- `process_batch` returns a message id only once its rows are committed, or
  once the worker has decided to drop it on purpose (undecodable, or over
  quota). A failed entitlements check now defers instead of dropping, because
  an unreachable meter is transient.
- A failed group is rewritten one record at a time, so a rejected record no
  longer discards the rest of its batch.
- `StreamConsumer` gains an opt-in reclaim pass. `read_batch` only ever asks
  for `>`, so without it an unacknowledged entry is invisible to every later
  read and "leave it pending" would still lose the record. The pass claims the
  group's pending entries, and drops one after `max_deliveries` failures with
  an error log naming the lost record.

The drop applies only while other records are committing. The delivery counter
cannot tell a rejected record apart from a database that is down, so dropping
on the count alone would delete every record in flight once an outage outlasts
the budget. A live run against a real Redis found that hole; the guard closes
it.

The reclaim pass is off for the tracing and events workers, so their behaviour
is unchanged.

Verified against a real Redis 8: five records published during a twenty second
write outage stayed pending, then all landed on recovery with no duplicates and
an empty stream; a permanently rejected record let its batch mates through and
was dropped loudly once traffic resumed.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
`RecordsRetentionDAO.delete_records_before_cutoff` selected and deleted on
`RecordDBE.id`. That attribute does not exist. The records key is
`(project_id, record_id)`, so every call to the retention flush raised before
it deleted anything and records have never been aged out.

Scope added on purpose: this defect is clear, obvious and one line, it sits in
the records durability area this branch already touches, and Spike D found it
while auditing the same pipeline. It is kept in its own commit so it can be
reverted or landed alone.

Verified: `hasattr(RecordDBE, "id")` is False, the primary key constraint at
`dbes.py:18` is `(project_id, record_id)`, and the corrected statement compiles
against the Postgres dialect.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Port the durable-cancel spike's 13-cell driver (refresh_live.py) into the
release-gate skill as resources/session_control.py, per qa-audit-2026-09-03.md
section 4, so the standing regression check survives outside one evidence
folder. Matches the gate's env contract (AGENTA_BASE, AGENTA_ADMIN_KEY,
QA_OPENAI_API_KEY, no file fallback), moves the Docker/Postgres-only helpers
behind an OperatorHooks interface so six cells run over HTTP against any
deployment and the rest SKIP by name without --project, emits the gate's
PASS/FAIL/SKIP result shape into a timestamped ~/agenta-qa-evidence/ run
folder, and adds --resume so a lost agent costs one cell, not the run. Adds
two new cells (repeat-stop, stop-during-completion) from qa-audit section 3,
a path_triggers.py rule that makes the suite mandatory for session-code
changes, a SKILL.md section naming the command and the model-key locations,
and a pytest-and-standalone-runnable unit test for the pure parts (cell
registry, hooks skip path, resume, verdict shape, env resolution).

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
The live smoke run against the integration stack showed /sessions/{id}/cancel
returns 202 Accepted (a pending command plus a stopping execution), the
correct async-acceptance status. The verdict checks in stop-warm,
stop-approval, repeat-stop, and stop-during-completion hardcoded 200 and
FAILed every real Stop. Accept 200 or 202 in each.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…ontrol.py

Add HARNESSES["claude"] (kind claude, model sonnet, provider anthropic, vault
connection) so the session-control cells can drive the Claude Code harness,
and stock an Anthropic provider key into the bootstrapped account's vault
the same way the OpenAI key is stocked, gated on --harness claude so a
pi_core/codex-only run does not need ANTHROPIC_API_KEY set. Also widen
wait_for_turn/wait_for_tool by a configurable SANDBOX_STARTUP_SLACK_S (25s)
when --sandbox daytona is selected, since a Daytona sandbox takes 10 to 20s
to start on top of local timings. Record the session's distinct sandbox ids
(via /sessions/turns/query, HTTP-only) in every HTTP-only cell's evidence as
sandbox_ids / warm_same_sandbox, so a resume that silently rebuilt the
sandbox is visible in the result instead of only in the recalled codeword.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
The Daytona smoke run FAILed stop-approval with only
"resume did not recall the codeword" and no reply text to check why, so a
driver replay bug (the reconstructed output-denied tool part) could not be
told apart from a genuine product miss. Add resume_text, resume_frames, and
resume_errors to the cell's evidence.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Two invocations started in the same second (e.g. Claude Code and Daytona
smoke runs fired in parallel tonight) shared a run folder, since the
timestamp alone has 1-second resolution -- the second writer silently
overwrote the first one's results.json mid-run and one run's evidence was
lost until recovered from its redirected stdout log. Add the PID to the
folder name so concurrent invocations never collide.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…imeout

assistant_message() indexed turn["segments"] unconditionally. When the
driver's own wait for a turn times out (handle["out"] stays None,
observed when the runner is unhealthy after a restart), the cell passed
an empty {} dict in and the KeyError masked the real signal, which is a
driver-side timeout rather than a cell result.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Add cell_runner_gone, ported from cell_runner_gone in refresh_live.py, as
cell "runner-gone" in the session-control driver's registry. It restarts
the runner right after a Stop is claimed, then checks that the sweep
settles the command as lost (not claimed) in session_commands, the
session_streams row reads is_running: false, and a Send sent after that
runs. Register it in CELLS and in the registry's stable-names unit test,
and update SKILL.md's cell count and Docker-needing cell list.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…ncurrent-stops

Add OperatorHooks.ensure_runner_healthy(), implemented in DockerComposeHooks, that
unpauses, restarts, and health-checks the runner as needed. Extract the per-cell
execution in main() into run_cell(), which calls it in a finally block after every
needs_hooks cell, so a cell that raises before its own restore code runs (as
cell_stale_tail did tonight, leaving the runner paused) cannot strand the runner
for the next cell. Also wrap cell_stale_tail's pause/unpause and
cell_records_outage's stop/start Postgres in their own try/finally, so each cell
restores what it touched even on an exception in between.

Add cell "concurrent-stops": five sessions started at once with a long turn, Stop
sent to all five within about a second, each expected to return HTTP 202, settle
exactly one terminal record, and recall its own codeword on a warm resume.
HTTP-only, no hooks needed.

Add unit tests for run_cell's finally path (NullHooks skips the recovery call
without crashing; a stub hooks object confirms the recovery call fires when a
cell raises, and is skipped for a cell that does not need hooks) and add
"ensure_runner_healthy" to the NullHooks-raises coverage. Update SKILL.md's cell
count and lists.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
… both races

Split cell_runner_gone into two: the new cell_runner_gone pauses the runner
BEFORE sending the Stop, so the command can never be claimed or reported and
must settle lost off a deterministic sweep, with an explicit check for the
watchdog's execution_lost ending. cell_runner_gone_late keeps the old
restart-after-stop timing, which mostly loses that hard race because the
runner often reports the Stop's outcome before it actually dies.

Both races satisfy the same invariant: exactly one effective terminal
outcome, no command left pending or claimed, is_running false, and the next
Send succeeds. Factor that shared PASS rule into _judge_runner_gone(), used
by cell_runner_gone_late (cell_runner_gone keeps its own stricter assertion
since pausing first is meant to force the lost/execution_lost shape every
time). Both record which race landed on evidence["race"].

Register runner-gone-late in CELLS and the registry's stable-names test.
Update SKILL.md's cell count and Docker-needing cell list.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Keep one guard-clearing wrapper around the aliased chat send and regenerate methods so hook initialization and immediate-Stop fencing both remain correct.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
mmabrouk and others added 2 commits September 5, 2026 01:09
Preserve the backend stopping turn marker through the frontend session schema.

Recover matching desktop and mobile stop guards across remounts until settlement clears the marker.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
fix(sessions): guard Stop by execution and cancel pending approvals
Reject invalid reclaim and delivery settings during configuration load.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Fail closed until the first heartbeat confirms ownership and authenticate runner-initiated ownership release.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Bound cancel requests, reject unknown Codex reap outcomes, and isolate all keepalive TTL settings in tests.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Publish successful local mounts to teardown state before observing cancellation.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Guarantee one done record when a session-owned run throws outside the engine.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Retain sends until admission, restore attachments, and avoid overwriting a newer composer draft.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Expose skipped cells as untested, make the offline script fixture-aware, and require the durable late-answer conflict.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Document owner routing limits, transport requirements, fail-closed admission, cleanup continuity, and sanitized test evidence.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Treat post-cancel process reaping as best effort without changing the harness-confirmed cancellation outcome. Log cleanup misses for QA and cover failed and unknown reap paths through parking and continuity.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Keep the recovery slot when a newer composer draft blocks restoration. Consume it only after refused text and staged attachments are placed safely, with regressions for the occupied-composer case.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Document that Codex cleanup misses are QA evidence rather than a teardown signal. Keep settled Stop parking and continuity under the 600-second stopped-session window.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Apply the repository Prettier style to the inherited test file so the TypeScript format check passes.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Move a refused send into a conversation-local holding slot before a newer submission can replace the queue recovery value. Restore its text and staged attachments once the composer becomes empty, without submitting it automatically.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Capture the refused send into the conversation-local holding slot before scheduling editor placement. Cover the interleaving where a newer submission replaces the queue recovery value before the frame runs.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
fix: address CodeRabbit review for session control
@mmabrouk

mmabrouk commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py (1)

64-118: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the hand-rolled account fixture.

The new DAO test uses project, which creates users, organizations, workspaces, and projects directly. Reuse an account fixture from api/oss/tests/pytest/utils/accounts.py.

As per coding guidelines, reuse foo_account/cls_account/mod_account and do not hand-roll account creation.

Source: Coding guidelines

🟠 Major comments (24)
api/oss/src/tasks/asyncio/sessions/records_worker.py-109-109 (1)

109-109: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Persist permanent rejection state across workers.

Line 109 stores the rejection state only in one process. Any replica can claim the pending entry, and a restart clears this set. A later claimant then treats the same DataError or IntegrityError as retryable, so it never reaches drop_expired after max_deliveries. Persist the classification by stream message ID, or drop the isolated rejected entry immediately. Add a cross-worker or restart redelivery test.

docs/design/session-control-and-live-events/spike-b-durable-commands-design.md-806-808 (1)

806-808: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Align the parked-session outcome with the state machine.

The state machine defines claimed -> obsolete for not_running, but this flow says to settle the command as applied with execution.state = "not_running". Choose one terminal command state and update the transition table, DTO contract, settlement route, and tests together.

docs/design/session-control-and-live-events/spike-b-durable-commands-design.md-1005-1010 (1)

1005-1010: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Define the claim transition for direct delivery.

The direct sequence inserts a pending command and then calls the runner. Outcome settlement only updates state='claimed' AND claimed_by=<replica_id>, while the direct adapter's acknowledge is a no-op. A successful direct call therefore has no defined claim owner and can fail settlement with 409. Claim the command atomically before delivery, or define an equivalent direct-delivery claim path and test it.

docs/design/session-control-and-live-events/spike-b-durable-commands-design.md-979-981 (1)

979-981: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal · Exploitability: Moderate

Require encrypted transport for AGENTA_RUNNER_TOKEN.

AGENTA_RUNNER_INTERNAL_URL accepts http:// values, and the client sends the bearer token without a transport check. Reject cleartext URLs and require HTTPS with certificate validation or an equivalent authenticated encrypted channel.

docs/design/session-control-and-live-events/spike-b-durable-commands-design.md-539-543 (1)

539-543: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Bind settlement to a claim-specific lease identity.

claimed_by uses the caller-supplied replica_id, but claims expire and can be re-delivered. The settlement predicate has no claim generation. An old outcome can therefore settle a newer claim when the same or a reused replica_id holds it. Add a server-issued claim token or generation to the claim and outcome, and include it in the settlement predicate.

services/runner/src/environment/mount-lifecycle.ts-227-227 (1)

227-227: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Record confirmed detachment before checking cancellation.

If mountStorage returns false and the signal is aborted, this throws before ctx.markCwdDetachConfirmed(). The failure teardown then skips cleanupWorkspace() because the cwd remains unsafe, although the mount helper confirmed that it is detached. Mark detachment first, then throw for cancellation. Add a regression case where mountStorage aborts the signal and returns false.

Proposed fix
-    throwIfAcquireAborted(deps.signal);
-    ctx.markCwdDetachConfirmed();
+    ctx.markCwdDetachConfirmed();
+    throwIfAcquireAborted(deps.signal);
services/runner/src/engines/sandbox_agent/cancel-turn.ts-116-121 (1)

116-121: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Distinguish a stalled cancel request from a client that cannot cancel.

The request-timeout branch returns the shared unsettled result, so requested stays false. A stalled cancelSession may already have put the notification on the wire, and the harness prompt may still be open.

stopParkedApprovalSession in services/runner/src/server.ts (lines 977-990) reads that shape as "no ACP cancel could be SENT", logs reject-only (client has no cancelSession), and reparks the session warm. The comment there states the only cause is an unpatched client. This branch adds a second cause, so a possibly-running turn is now presented as idle instead of failing closed, and the log line names the wrong reason.

Report the timeout as its own outcome so the caller can fail closed.

🛠️ Proposed change
   /** True when the cancel notification left the runner, whatever the harness did next. */
   requested: boolean;
+  /** True when the cancel request itself never completed inside the budget. */
+  requestTimedOut?: boolean;
     if (requested === TIMED_OUT) {
       input.log(
         `stage=harness_cancel sent=false reason=request-timeout budget_ms=${timeoutMs}`,
       );
-      return unsettled;
+      return { ...unsettled, requestTimedOut: true };
     }

Then treat requestTimedOut as unsafe-to-park in stopParkedApprovalSession.

services/runner/src/sessions/control-channel.ts-271-287 (1)

271-287: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the outcome report with a timeout.

reportOutcome awaits fetch with no deadline. applyCommand awaits report, and the /cancel route awaits applyCommand. If the API accepts the connection and never answers, the runner holds the caller's /cancel request open for the platform's full socket lifetime, and the Stop looks stuck to the user. The comment above the report says a Stop that worked must not look stuck, so a hung report defeats that intent.

Add an AbortSignal.timeout(...) to the request. A failed report is already safe: the log records it and the API's settlement sweep repairs the command.

♻️ Proposed fix
   const res = await fetch(url, {
     method: "POST",
     redirect: "error",
+    signal: AbortSignal.timeout(OUTCOME_REPORT_TIMEOUT_MS),
     headers: {

Declare the budget beside the other module constants:

/** A report that cannot land promptly must not hold the `/cancel` caller open. */
const OUTCOME_REPORT_TIMEOUT_MS = 10_000;
api/oss/src/dbs/postgres/sessions/streams/dao.py-544-551 (1)

544-551: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The guarded update path diverges from the shared edit mapper. The new expected_turn_id branch hand-builds a values dict instead of writing the field set map_stream_dto_to_dbe_edit owns. Two fields are lost: tags/meta, and the turn_started_at stamp that fires when turn_id changes.

  • api/oss/src/dbs/postgres/sessions/streams/dao.py#L544-L551: add tags and meta to values, and set turn_started_at to the current UTC time when stream.turn_id differs from stream.expected_turn_id.
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py#L209-L213: this stamp is the invariant the DBE comment relies on. Keep the two write paths in agreement, or route the guarded path through a shared helper so a third path inherits both rules.
api/oss/src/apis/fastapi/sessions/router.py-1988-1994 (1)

1988-1994: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Publish the cancellation response contract.

Because cancel_session_execution returns JSONResponse and the route declares no response_model or responses, FastAPI does not publish schemas for its successful responses in OpenAPI. Register SessionStreamCommandResponse for the legacy 200 response and SessionCancelResponse for the durable 200 and 202 responses so generated clients can consume this public API reliably.

Source: Coding guidelines

api/oss/src/core/sessions/records/service.py-273-274 (1)

273-274: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the durable branch default to quarantine.

env.agenta.sessions.late_output accepts "quarantine" and "reject". When it is "reject", _handle_by_execution_state currently drops late records because it appends only when action == "quarantine". This differs from _handle_late_events, which drops only for "reject".

-            if action == "quarantine":
-                guarded.append(event.model_copy(update={"quarantined_at": now}))
+            if action == "reject":
+                continue
+            guarded.append(event.model_copy(update={"quarantined_at": now}))
api/oss/src/dbs/postgres/sessions/commands/dao.py-303-305 (1)

303-305: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not claim command kinds that this replica cannot map.

claim_commands marks every selected row as claimed before this helper drops unknown kinds. The row then remains leased to a replica that cannot execute it, so a compatible replica cannot claim it until lease expiry. Filter selectable to supported kinds before the update, and test the full claim path.

api/oss/src/dbs/postgres/sessions/commands/dao.py-221-221 (1)

221-221: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

IDOR (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)

Reachability: External · Exploitability: Difficult

Bind runner outcome handling to the command’s project.

The outcome endpoint accepts a command UUID under one shared runner token. fetch_command then loads any matching command without project_id, and the service can settle that command. Pass an authenticated project scope or a command-scoped capability through the runner request. Do not rely on the command UUID alone.

Source: Coding guidelines

api/oss/src/core/sessions/interactions/service.py-117-117 (1)

117-117: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Return a typed result DTO.

cancel_session_pending returns a raw int. Return a Pydantic DTO such as SessionPendingCancellationResult and update its callers.

As per coding guidelines: “Service methods must return typed DTOs (Pydantic BaseModel subclasses), not raw dicts, tuples, or Any.”

Source: Coding guidelines

api/oss/src/dbs/postgres/sessions/executions/dao.py-129-147 (1)

129-147: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Scope this DAO read by project_id.

list_redis_unreconciled returns rows from every project. Keep this DAO read project-scoped. If reconciliation must run globally, enumerate projects in privileged orchestration and call a project-scoped DAO method.

As per coding guidelines: “Always enforce tenant scope (project_id minimum) in DAO reads and writes.”

Source: Coding guidelines

api/oss/src/core/sessions/streams/service.py-347-351 (1)

347-351: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fence lifecycle writes against a newer turn.

After Stop clears the old locks, a new turn can start before either row write completes. Both paths can overwrite the new turn's flags with stale terminal state. Use expected_turn_id or an equivalent transaction fence, and publish ended only when that fence succeeds.

  • api/oss/src/core/sessions/streams/service.py#L347-L351: fence _mark_stream_ended to the cancelled turn before writing terminal flags.
  • api/oss/src/core/sessions/streams/service.py#L1282-L1286: pass the settled turn into the mirror write and reject a write after a newer generation replaces it.
api/oss/src/core/sessions/streams/dtos.py-178-193 (1)

178-193: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the duplicate expected_execution_id declaration.

This redeclares the field and validator from Lines 155-171. It also drops the first field description. Flake8 reports F811 for the duplicate validator.

Proposed fix
-    expected_execution_id: Optional[str] = Field(
-        default=None,
-        description=(
-            "Optional stale-request guard honored only in cancel mode; ignored for send, "
-            "steer, and attach."
-        ),
-    )
-
-    `@field_validator`("expected_execution_id")
-    `@classmethod`
-    def _blank_expected_execution_id_means_absent(
-        cls, value: Optional[str]
-    ) -> Optional[str]:
-        if value is None:
-            return None
-        return value.strip() or None
-
     # Cancel guard (RFC D-010). Public name; internally this IS a turn id — the coordination

Sources: Coding guidelines, Linters/SAST tools

services/runner/src/sessions/alive.ts-395-395 (1)

395-395: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal · Exploitability: Difficult

Require authenticated transport before sending the runner token.

apiBase() explicitly permits the HTTP URL http://api:8000. This request sends AGENTA_RUNNER_TOKEN in cleartext, allowing a network observer to capture the shared runner credential.

web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts-152-154 (1)

152-154: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep each new code comment to one short line.

  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts#L152-L154: Replace with one short reason comment.
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts#L270-L271: Replace with one short reason comment.
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts#L280-L281: Replace with one short reason comment.
  • web/oss/src/components/AgentChatSlice/hooks/useSessionRecordsWatch.ts#L46-L50: Replace with one short reason comment.
    As per coding guidelines, “Hard rule. At most ONE short line per comment.”

Source: Coding guidelines

docs/design/session-control-and-live-events/api-design.md-231-236 (1)

231-236: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal · Exploitability: Moderate

Require encrypted transport for every runner-control hop.

The API sends AGENTA_RUNNER_TOKEN to the runner over AGENTA_RUNNER_INTERNAL_URL, which defaults to http://runner:8765. The runner documentation defers TLS and relies only on network isolation and bearer authentication. Require HTTPS with certificate validation for every credential-bearing request, and reject plaintext runner URLs outside explicit development configurations. Keep redirects disabled or prevent any HTTPS downgrade.

docs/design/session-control-and-live-events/api-design.md-58-60 (1)

58-60: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Ensure desktop Stop does not omit expected_execution_id.

The desktop path passes expectedExecutionId when its in-memory turn ID exists. However, useAgentConversation clears that ID before each send and restores it only after a streamed turn ID appears. A Stop during this gap sends no expected_execution_id; if a newer execution starts before the request arrives, Stop may cancel the newer execution. Keep the ID available before enabling Stop, or reject the unguarded request.

docs/design/session-control-and-live-events/slice-durable-cancel.md-192-200 (1)

192-200: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Add abandoned-command settlement before enabling durable Stop.

When the runner never reports, the command remains claimed and stopping_turn_id remains set indefinitely. This blocks the session and violates the Stop recovery objective. Add claim expiry and terminal settlement before enabling AGENTA_SESSIONS_DURABLE_STOP, and make the release gate cover this failure path.

Also applies to: 217-218

docs/design/session-control-and-live-events/slice-stop-guard.md-113-116 (1)

113-116: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the documented cancel-result contract with the clients.

This section documents cancelSessionStream as returning cancelled | stale | failed, but the supplied consumers also handle idle for the no-running case (web/mobile/src/features/chat/LiveConversation.tsx:272-339 and web/mobile/src/features/chat/StopButton.tsx:10-27). Define one result union and update the documentation and consumers together.

docs/design/session-control-and-live-events/slice-watchdog.md-77-79 (1)

77-79: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep watchdog rows recoverable until settlement cleanup completes.

The sweep can remove retry eligibility before all effects are durable. If settled_turns lookup fails, it writes no terminal record but still collapses the row. If it crashes after row collapse and before Redis cleanup, the next pass skips the row while alive and running keys remain. Keep the row in a cleanup-pending state, or retry settlement before clearing is_running.

Also applies to: 95-96

🟡 Minor comments (12)
services/runner/src/engines/sandbox_agent/reap-exec.ts-42-43 (1)

42-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align this header with the shipped park decision.

The header states that a failed or inconclusive reap prevents a safe park. The only consumer disagrees: run-turn.ts (lines 1387-1405) calls reapResultHasCleanupMiss and logs cleanup_miss=true, then keeps the harness-confirmed park. Its own comment states the cleanup is best effort and does not change the park decision. cancel-continuity.test.ts pins that behavior for both reap-failure modes.

Update this header to describe the log-only outcome, so a later reader does not treat the reap result as a park gate.

services/runner/src/engines/sandbox_agent/sandbox-liveness.ts-270-270 (1)

270-270: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the disable flag value-aware.

Any non-empty value for AGENTA_RUNNER_SANDBOX_PROBE_DISABLED disables the poll, including false and 0. An operator who sets AGENTA_RUNNER_SANDBOX_PROBE_DISABLED=false turns off the exact detection this module adds, and only the informational log in run-turn.ts reports it.

run-turn.ts line 304 shows the repository's opposite convention for a compose-supplied flag: only the literal "false" opts out. Parse the value here so the two flags cannot be read in opposite directions.

🛠️ Proposed change
-  if (probe && !process.env[PROBE_DISABLED_ENV]) schedule();
+  const disabled = /^(1|true|yes)$/i.test(
+    (process.env[PROBE_DISABLED_ENV] ?? "").trim(),
+  );
+  if (probe && !disabled) schedule();
services/runner/tests/unit/turn-settle.test.ts-212-215 (1)

212-215: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stub the limit env vars to empty before asserting the defaults.

resolveTurnSettleLimits() reads HARD_DEADLINE_ENV and ABANDON_GRACE_ENV. This test asserts the exact defaults without neutralizing them first. If either variable is set in the shell or by a loaded dev env file, the test fails for a reason unrelated to the code under test. The same problem was fixed in services/runner/tests/unit/harness-cancel-park.test.ts by isolating the TTL variables around the default assertions.

vi.stubEnv with undefined deletes the variable, and the existing afterEach already calls vi.unstubAllEnvs().

♻️ Proposed fix
   it("keeps the hard deadline above the longest legitimate run", () => {
     // A backstop that fired before the run limits would shorten real runs, which is the
     // opposite of what users have asked for (issues `#6084`, `#5356`).
+    vi.stubEnv(HARD_DEADLINE_ENV, undefined);
+    vi.stubEnv(ABANDON_GRACE_ENV, undefined);
     expect(DEFAULT_HARD_DEADLINE_MS).toBeGreaterThan(DEFAULT_TOTAL_DEADLINE_MS);
     expect(resolveTurnSettleLimits()).toEqual({
       hardDeadlineMs: DEFAULT_HARD_DEADLINE_MS,
       abandonGraceMs: DEFAULT_ABANDON_GRACE_MS,
     });
   });
api/oss/src/apis/fastapi/sessions/router.py-2040-2040 (1)

2040-2040: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject overlength idempotency keys.

Line 2040 truncates keys instead of rejecting them. Two distinct keys with the same first 255 characters then address one command, causing an unintended replay or a conflict. Reject keys over _MAX_IDEMPOTENCY_KEY_CHARACTERS before normalization.

services/runner/src/server.ts-1129-1132 (1)

1129-1132: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject missing or invalid createdAt with HTTP 400. When createdAt is absent or not parseable, Date.parse returns NaN, so the applier skips the stale-Stop guard and can call live.abort() on a newer turn. Validate that createdAt is a parseable timestamp before enqueueing the cancel command.

web/packages/agenta-chat/src/components/RunningElsewhereStrip.tsx-20-23 (1)

20-23: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reduce this block comment to one short line.

Keep only the non-obvious reason for the state. As per coding guidelines: “At most ONE short line per comment.”

Source: Coding guidelines

api/oss/tests/pytest/unit/sessions/test_orphan_sweep_thresholds.py-341-341 (1)

341-341: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the threshold test independent of environment variables.

ORPHAN_THRESHOLD_SECONDS and IDLE_THRESHOLD_SECONDS import values from SessionWatchdogConfig, whose fields evaluate the environment during import. The assertion can therefore fail when either variable is set. Clear or set both variables before importing the module, then assert the intended defaults.

web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx-160-162 (1)

160-162: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reduce this to one short comment line.

The frontend rule limits each comment to one short line.

Source: Coding guidelines

api/oss/src/dbs/redis/sessions/locks.py-12-12 (1)

12-12: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the unused import.

validate_session_id is unused, so ruff check reports F401. Remove it or use it.

As per coding guidelines, run ruff format then ruff check --fix and fix all errors before committing.

Sources: Coding guidelines, Linters/SAST tools

api/oss/src/utils/logging.py-212-215 (1)

212-215: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reduce the added explanatory comments. Keep only a short non-obvious rationale.

  • api/oss/src/utils/logging.py#L212-L215: replace the four-line comment with one short invariant comment, or remove it.
  • api/oss/tests/pytest/unit/test_multilogger.py#L1-L9: reduce the module docstring to a short test-purpose statement.
    As per coding guidelines: “Keep AI-generated in-code comments minimal. Comment only the non-obvious why.”

Source: Coding guidelines

web/mobile/src/features/chat/LiveConversation.tsx-98-99 (1)

98-99: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep each new code comment to one short line.

  • web/mobile/src/features/chat/LiveConversation.tsx#L98-L99: reduce the prop comment to one short line.
  • web/mobile/src/features/chat/LiveConversation.tsx#L1317-L1319: reduce the routing comment to one short line.

As per coding guidelines, “At most ONE short line per comment.”

Source: Coding guidelines

docs/design/session-control-and-live-events/spike-a-sandbox-cancel.md-387-389 (1)

387-389: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Define the Codex reap result for every outcome.

Step 4 requires stage=harness_reap killed=..., but Step 9 allows failed or unknown reaping and only says to record cleanup_miss=true. Define distinct reap records and the gate assertions for success, failure, and unknown outcomes. Otherwise, make the reap assertion conditional on successful cleanup.

🧹 Nitpick comments (4)
api/oss/src/core/sessions/commands/interfaces.py (1)

172-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align this docstring with the expected_states guard.

SessionCommandSettle carries expected_states (default [claimed]) and admits a null claimed_by for a pending row. SessionCommandsService.report_outcome passes [pending, claimed]. The docstring states a fixed state='claimed' AND claimed_by=:replica_id guard, which no longer describes the contract.

♻️ Proposed doc fix
-        """Terminal transition, guarded on `state='claimed' AND claimed_by=:replica_id`.
-        None means the claim had expired or somebody else settled it first."""
+        """Terminal transition, guarded on `state IN settle.expected_states` and, for a
+        claimed row, `claimed_by = settle.replica_id`. None means the guard did not match:
+        the claim had expired, or somebody else settled it first."""
api/oss/tests/pytest/unit/sessions/test_session_commands_dao.py (1)

606-621: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound this concurrent settle with asyncio.wait_for.

Two other concurrency tests in this module wrap asyncio.gather with asyncio.wait_for(..., timeout=30), and line 325 states the reason: contention on the same row must fail the run, not hang it. test_runner_and_watchdog_have_one_terminal_winner contends on the same (project_id, session_id, execution_id) settlement without that bound. A regression that blocks instead of conflicting hangs CI rather than reporting a failure.

♻️ Proposed change
-    runner, watchdog = await asyncio.gather(
-        dao.settle(
-            project_id=command_scope["project_id"],
-            session_id=command_scope["session_id"],
-            execution_id="turn-A",
-            terminal_outcome="stopped",
-            settled_by="runner",
-        ),
-        dao.settle(
-            project_id=command_scope["project_id"],
-            session_id=command_scope["session_id"],
-            execution_id="turn-A",
-            terminal_outcome="lost",
-            settled_by="watchdog",
-        ),
-    )
+    runner, watchdog = await asyncio.wait_for(
+        asyncio.gather(
+            dao.settle(
+                project_id=command_scope["project_id"],
+                session_id=command_scope["session_id"],
+                execution_id="turn-A",
+                terminal_outcome="stopped",
+                settled_by="runner",
+            ),
+            dao.settle(
+                project_id=command_scope["project_id"],
+                session_id=command_scope["session_id"],
+                execution_id="turn-A",
+                terminal_outcome="lost",
+                settled_by="watchdog",
+            ),
+        ),
+        timeout=30,
+    )
api/oss/tests/pytest/unit/sessions/test_heartbeat_release_auth.py (1)

78-97: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Broken Authentication (CWE-287): Improper Authentication

Reachability: External

Add a wrong-token test for release_owner.

The tests cover absent and exact-match tokens, but not a mismatched token. Add a test that sends "wrong-secret" and asserts 401 plus service.heartbeat.assert_not_awaited().

💚 Proposed test
`@pytest.mark.asyncio`
async def test_release_owner_rejects_a_wrong_runner_token(monkeypatch):
    monkeypatch.setattr(env.runner, "token", "runner-secret")
    monkeypatch.setattr(
        router_module, "check_action_access", AsyncMock(return_value=True)
    )
    service = SimpleNamespace(heartbeat=AsyncMock())

    with pytest.raises(HTTPException) as exc_info:
        await _router(service).heartbeat_session_stream(
            _request({"X-Agenta-Runner-Token": "wrong-secret"}),
            SessionHeartbeatRequest(
                session_id="session-1",
                replica_id="replica-1",
                release_owner=True,
            ),
        )

    assert exc_info.value.status_code == 401
    service.heartbeat.assert_not_awaited()
web/oss/src/components/AgentChatSlice/AgentConversation.tsx (1)

554-555: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep this code comment to one short line. Replace it with one concise line or remove it. As per coding guidelines: “At most ONE short line per comment.”

Proposed change
-            // A rewrite of a held message: nothing is sent, so the transcript must not move.
-            // The input clears itself on submit, so the displaced draft goes back after that.
+            // Restore the displaced draft after the editor clears it.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Team

Run ID: 5ba59c97-d20a-4d19-abf8-d1ae491ceefa

📥 Commits

Reviewing files that changed from the base of the PR and between 630a676 and 52ffa77.

⛔ Files ignored due to path filters (8)
  • web/packages/agenta-api-client/src/generated/api/resources/sessions/client/Client.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/CancelSessionExecutionRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/index.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/SessionCancelRequest.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/SessionStream.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponse.ts is excluded by !**/generated/**
  • web/packages/agenta-api-client/src/generated/api/types/index.ts is excluded by !**/generated/**
📒 Files selected for processing (205)
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • .agents/skills/agent-release-gate/resources/session_control.py
  • .agents/skills/agent-release-gate/resources/test_qa_product_concurrency.py
  • .agents/skills/agent-release-gate/resources/test_session_control.py
  • api/entrypoints/routers.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000023_add_session_executions.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000024_add_execution_redis_reconciliation.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000026_add_execution_ending_marker.py
  • api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000005_add_records_quarantined_at.py
  • api/oss/src/apis/fastapi/sessions/models.py
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/sessions/commands/__init__.py
  • api/oss/src/core/sessions/commands/dtos.py
  • api/oss/src/core/sessions/commands/interfaces.py
  • api/oss/src/core/sessions/commands/service.py
  • api/oss/src/core/sessions/commands/types.py
  • api/oss/src/core/sessions/executions/__init__.py
  • api/oss/src/core/sessions/executions/dtos.py
  • api/oss/src/core/sessions/executions/interfaces.py
  • api/oss/src/core/sessions/interactions/interfaces.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/records/dtos.py
  • api/oss/src/core/sessions/records/interfaces.py
  • api/oss/src/core/sessions/records/service.py
  • api/oss/src/core/sessions/streams/dtos.py
  • api/oss/src/core/sessions/streams/interfaces.py
  • api/oss/src/core/sessions/streams/runner_client.py
  • api/oss/src/core/sessions/streams/service.py
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/src/dbs/http/__init__.py
  • api/oss/src/dbs/http/sessions/__init__.py
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/dbs/postgres/sessions/commands/__init__.py
  • api/oss/src/dbs/postgres/sessions/commands/dao.py
  • api/oss/src/dbs/postgres/sessions/commands/dbas.py
  • api/oss/src/dbs/postgres/sessions/commands/dbes.py
  • api/oss/src/dbs/postgres/sessions/commands/mappings.py
  • api/oss/src/dbs/postgres/sessions/executions/__init__.py
  • api/oss/src/dbs/postgres/sessions/executions/dao.py
  • api/oss/src/dbs/postgres/sessions/executions/dbes.py
  • api/oss/src/dbs/postgres/sessions/interactions/dao.py
  • api/oss/src/dbs/postgres/sessions/records/dao.py
  • api/oss/src/dbs/postgres/sessions/records/dbas.py
  • api/oss/src/dbs/postgres/sessions/records/mappings.py
  • api/oss/src/dbs/postgres/sessions/streams/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/dbs/redis/sessions/contract.py
  • api/oss/src/dbs/redis/sessions/locks.py
  • api/oss/src/middlewares/auth.py
  • api/oss/src/tasks/asyncio/sessions/orphan_sweep.py
  • api/oss/src/tasks/asyncio/sessions/records_worker.py
  • api/oss/src/tasks/asyncio/shared/consumer.py
  • api/oss/src/utils/env.py
  • api/oss/src/utils/logging.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • api/oss/tests/pytest/unit/sessions/test_cancel_cancels_pending_interactions.py
  • api/oss/tests/pytest/unit/sessions/test_cancel_stop_guard.py
  • api/oss/tests/pytest/unit/sessions/test_command_claim_unknown_kind.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • api/oss/tests/pytest/unit/sessions/test_command_settle_unknown_kind.py
  • api/oss/tests/pytest/unit/sessions/test_execution_watchdog.py
  • api/oss/tests/pytest/unit/sessions/test_execution_watchdog_loop.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_departed_replica_affinity.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_lock_races.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_release_auth.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_turn_handover.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.py
  • api/oss/tests/pytest/unit/sessions/test_late_record_quarantine.py
  • api/oss/tests/pytest/unit/sessions/test_late_record_quarantine_dao.py
  • api/oss/tests/pytest/unit/sessions/test_orphan_sweep_clears_redis.py
  • api/oss/tests/pytest/unit/sessions/test_orphan_sweep_thresholds.py
  • api/oss/tests/pytest/unit/sessions/test_owner_claim.py
  • api/oss/tests/pytest/unit/sessions/test_project_scoped_locks.py
  • api/oss/tests/pytest/unit/sessions/test_records_config.py
  • api/oss/tests/pytest/unit/sessions/test_records_worker_durability.py
  • api/oss/tests/pytest/unit/sessions/test_runner_client_kill.py
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_admission.py
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_feature_flag.py
  • api/oss/tests/pytest/unit/sessions/test_session_commands_dao.py
  • api/oss/tests/pytest/unit/sessions/test_watch_interactions_publish.py
  • api/oss/tests/pytest/unit/sessions/test_watch_lifecycle_publish.py
  • api/oss/tests/pytest/unit/sessions/test_watchdog_collapse_persistence.py
  • api/oss/tests/pytest/unit/sessions/test_watchdog_lifespan_wiring.py
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/tests/pytest/unit/test_multilogger.py
  • docs/design/session-control-and-live-events/api-design.md
  • docs/design/session-control-and-live-events/decisions.md
  • docs/design/session-control-and-live-events/research.md
  • docs/design/session-control-and-live-events/rfc.md
  • docs/design/session-control-and-live-events/slice-admission.md
  • docs/design/session-control-and-live-events/slice-durable-cancel.md
  • docs/design/session-control-and-live-events/slice-stop-guard.md
  • docs/design/session-control-and-live-events/slice-watchdog.md
  • docs/design/session-control-and-live-events/spike-a-sandbox-cancel.md
  • docs/design/session-control-and-live-events/spike-b-durable-commands-design.md
  • docs/design/session-control-and-live-events/status.md
  • docs/design/session-control-and-live-events/tonight-handoff.md
  • hosting/docker-compose/ee/docker-compose.dev.yml
  • hosting/docker-compose/ee/docker-compose.gh.local.yml
  • hosting/docker-compose/ee/docker-compose.gh.yml
  • hosting/docker-compose/ee/env.ee.dev.example
  • hosting/docker-compose/oss/docker-compose.dev.yml
  • hosting/docker-compose/oss/docker-compose.gh.local.yml
  • hosting/docker-compose/oss/docker-compose.gh.ssl.yml
  • hosting/docker-compose/oss/docker-compose.gh.yml
  • hosting/docker-compose/oss/env.oss.dev.example
  • services/runner/src/engines/sandbox_agent/acp-fetch.ts
  • services/runner/src/engines/sandbox_agent/cancel-turn.ts
  • services/runner/src/engines/sandbox_agent/daytona.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/engines/sandbox_agent/errors.ts
  • services/runner/src/engines/sandbox_agent/reap-exec.ts
  • services/runner/src/engines/sandbox_agent/run-limits.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/engines/sandbox_agent/sandbox-gone.ts
  • services/runner/src/engines/sandbox_agent/sandbox-liveness.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • services/runner/src/environment/mount-lifecycle.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/alive.ts
  • services/runner/src/sessions/applied-commands.ts
  • services/runner/src/sessions/control-channel.ts
  • services/runner/src/sessions/execution-registry.ts
  • services/runner/src/sessions/turn-settle.ts
  • services/runner/tests/unit/cancel-continuity.test.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/tests/unit/mount-lifecycle.test.ts
  • services/runner/tests/unit/reap-exec.test.ts
  • services/runner/tests/unit/sandbox-agent-acp-fetch.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-gone.test.ts
  • services/runner/tests/unit/sandbox-liveness.test.ts
  • services/runner/tests/unit/server.test.ts
  • services/runner/tests/unit/session-admission.test.ts
  • services/runner/tests/unit/session-alive-interrupt.test.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/session-keepalive-dispatch.test.ts
  • services/runner/tests/unit/session-ownership-release.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/turn-settle.test.ts
  • web/mobile/src/features/chat/ChatScreen.tsx
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/chat/LiveConversation.tsx
  • web/mobile/src/features/chat/StopButton.tsx
  • web/mobile/src/features/chat/stopHereState.ts
  • web/mobile/src/features/chat/useSessionWatch.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/mobile/tests/unit/stopHereState.test.ts
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/assets/refusedMessageRecovery.test.ts
  • web/oss/src/components/AgentChatSlice/assets/refusedMessageRecovery.ts
  • web/oss/src/components/AgentChatSlice/assets/stopState.test.ts
  • web/oss/src/components/AgentChatSlice/assets/stopState.ts
  • web/oss/src/components/AgentChatSlice/assets/stopWhileResolvingExecution.test.ts
  • web/oss/src/components/AgentChatSlice/assets/stopWhileResolvingExecution.ts
  • web/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.test.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionRecordsWatch.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/packages/agenta-chat/src/assets/agentTurn.ts
  • web/packages/agenta-chat/src/assets/composerState.ts
  • web/packages/agenta-chat/src/assets/index.ts
  • web/packages/agenta-chat/src/assets/transcriptToMessages.ts
  • web/packages/agenta-chat/src/components/ChatComposer.tsx
  • web/packages/agenta-chat/src/components/RunningElsewhereStrip.tsx
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts
  • web/packages/agenta-chat/src/hooks/useAgentConversation.ts
  • web/packages/agenta-chat/src/hooks/useComposerAttachments.ts
  • web/packages/agenta-chat/src/model/approvals.ts
  • web/packages/agenta-chat/src/model/error.ts
  • web/packages/agenta-chat/src/model/index.ts
  • web/packages/agenta-chat/src/model/interactionAvailability.ts
  • web/packages/agenta-chat/src/model/userStop.ts
  • web/packages/agenta-chat/src/state/sessionEphemera.ts
  • web/packages/agenta-chat/tests/unit/assets/agentTurn.test.ts
  • web/packages/agenta-chat/tests/unit/assets/composerState.test.ts
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
  • web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts
  • web/packages/agenta-chat/tests/unit/hooks/useAgentConversation.test.ts
  • web/packages/agenta-chat/tests/unit/model/error.test.ts
  • web/packages/agenta-chat/tests/unit/model/interactionAvailability.test.ts
  • web/packages/agenta-chat/tests/unit/model/liveApprovals.test.ts
  • web/packages/agenta-chat/tests/unit/model/userStop.test.ts
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/packages/agenta-entities/src/session/core/liveness.ts
  • web/packages/agenta-entities/src/session/core/schema.ts
  • web/packages/agenta-entities/src/session/index.ts
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • web/packages/agenta-entities/tests/unit/session-cancel-stream.test.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-entities/tests/unit/session-query-schema.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
  • web/packages/agenta-ui/src/RichChatInput/RichChatInput.tsx
  • web/packages/agenta-ui/src/RichChatInput/plugins/SendButton.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/packages/agenta-chat/tests/unit/model/error.test.ts
  • docs/design/session-control-and-live-events/rfc.md
  • web/packages/agenta-chat/src/model/error.ts
  • docs/design/session-control-and-live-events/research.md
  • docs/design/session-control-and-live-events/decisions.md

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

mmabrouk and others added 2 commits September 5, 2026 09:25
Expose the fixture-generated database name to the concurrency test. Connect the raw sweep and observer clients to that isolated database.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
[fix] Point the watchdog collapse test at its own database
@mmabrouk
mmabrouk changed the base branch from release/v0.114.8 to release/v0.115.0 September 5, 2026 09:49
@mmabrouk
mmabrouk merged commit f72e4aa into release/v0.115.0 Sep 5, 2026
75 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant