Skip to content

feat(sessions): deliver durable Stop directly to the runner - #6503

Open
mmabrouk wants to merge 29 commits into
feat/session-controlfrom
feat/session-durable-cancel
Open

feat(sessions): deliver durable Stop directly to the runner#6503
mmabrouk wants to merge 29 commits into
feat/session-controlfrom
feat/session-durable-cancel

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 2, 2026

Copy link
Copy Markdown
Member

Agent-generated, low weight. Draft. Do not merge before Mahmoud reviews it.

Implements the Spike B design with the direct adapter: the session_commands table, POST /sessions/{id}/cancel with an optional expected_execution_id, the control-delivery port, the runner POST /cancel beside /kill, the outcome route, and settlement that releases running under the owner check and leaves alive to its TTL. Long polling is designed (PR #6497) and parked in Linear AGE-4253. Heartbeats stay a health and ownership signal. Stacked on Spike A (PR #6496).

Evidence

Date Provider Harness Commit Scenario Result Timing
2026-09-02 local Pi 58bec4d382 Stop during a 60 s tool, then continue command applied/stopped, park-cancelled, next message recalled the codeword request to abort 82 ms, harness confirmed 106 ms, settled 126 ms, parked 993 ms
2026-09-02 local Pi 58bec4d382 Stop when idle; stale expected_execution_id; two Stops; Stop on a parked approval obsolete 200; 409 nothing written; one command; interaction cancelled, environment kept, resume warm
2026-09-03 local Pi 58bec4d382 after settlement, sample the row for 3 min row kept is_running: true (the runner's final beat landed 0.88 s after settlement and the tombstone check refused it before the mirror write); the collection query every liveness poll drives is served from Postgres alone
2026-09-03 local Pi 76e4b1368b same, with mirror_liveness at settlement is_running: false at Stop plus 0.15 s, is_alive: true; warm resume 2.01 s inside the park window against a 14.16 s cold rebuild after it
2026-09-03 local and Daytona Pi and Claude Code 9110c08000 (integration) Stop naming its turn while the turn waits on an approval 409 four of four (expectation compared against running only; a parked approval holds the turn in alive); the same Stop without an id cancelled the gate
2026-09-03 local Pi 89d7c7c90d same 202, gate pending to cancelled; a stale id still 409, naming the turn that would have been stopped
2026-09-03 local and Daytona Pi 9110c08000 Stop during output, outcome report 409 on four of nine turns; the command sat claimed until the watchdog settled it lost (one case exactly 120 s)
2026-09-03 local Pi 89d7c7c90d same the runner's report is accepted whether the row is pending or claimed; the duplicate gets 409
2026-09-03 local Pi 89d7c7c90d Stop fired the instant the prompt settles, before teardown the applier aborted a live registry entry, the aborted signal made the park rule refuse (no-park:end_turn), cold rebuild 7.21 s
2026-09-03 local Pi 38cbc92201 same run marked settled at prompt settle, applier no-op, command obsolete/not_running, parked, warm hit-continue 1.91 s
2026-09-03 local Pi 76e4b1368b Stop, then docker restart the runner, then continue refused until the Redis owner lease (120 s) expired: the runner never releases its owner claim at shutdown and a restarted runner has a new replica id; deleting only the owner key admitted the next message at once fix on the Spike A stack, in progress

Trade-off taken on 38cbc92201: past teardown, the API now discriminates lost from not_running on the running key instead of the heartbeat, so in a multi-runner deployment a Stop misrouted to a replica that holds a parked approval settles not_running instead of lost. Multi-replica direct delivery is not version one; open question 8 in the report.

Tests

  • API session unit tests in the api container against live Postgres: 564 pass (head 38cbc92201).
  • cd services/runner && pnpm test: 2671 pass, 160 files (run pnpm run build:extension once in a fresh worktree first).
  • @agenta/entities 1468, @agenta/navigation 46, @agenta/mobile 144.
  • uvx ruff@0.15.12 format and check: clean. pnpm lint-fix: clean.

Branch head

38cbc92201 on feat/session-durable-cancel, base spike/session-cancel-warm. Reports: slice-durable-cancel.md on this branch; evidence-2026-09-03/post-stop-mirror.md, post-stop-liveness.md, and daytona-pi-claude.md on PR #6505.

https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV

Restack of 2026-09-04

This PR now carries the rollback switch itself, so it is safe on its own: AGENTA_SESSIONS_DURABLE_STOP (default off) in env.py; with it off the cancel route returns the exact legacy response and takes the heartbeat-carried Stop, and no session_commands row is written. Commits 50bffdc8ef and e9858e64ac moved down from PR #6501. Also 5a66bf0e12 narrows the runner-control auth exemption from /sessions/control/ to /sessions/control/commands/, so a session whose id is literally control still needs project auth (a whole-PR review finding). Head 5a66bf0e12; API sessions suite 568 passed. Agent-generated, low weight.

Cancelled interaction record (2026-09-04, afternoon)

The browser pass showed an approval card that stayed pending after a Stop, because the cancel updated the interaction row and published a notice but wrote no transcript record. Commit e4cbd64ac9 writes one cancelled interaction record per cancelled interaction (deterministic id, identifiers only, published after commit); a later answer gets 409 "Interaction is no longer pending". Reviewed: passes. Known item, non-blocking: the desktop clears the card from the interaction row state, and its record handler ignores the new record's outcome: cancelled shape; a records-only consumer would need the client handler aligned (client side, PR #6504). The record append is a separate transaction from the cancel commit, as on the normal-answer path.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agenta-documentation Ready Ready Preview Sep 4, 2026 5:22pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 7d9ba61a-653e-4a63-b809-75d469ee8856

📥 Commits

Reviewing files that changed from the base of the PR and between 69db342 and cd21450.

⛔ Files ignored due to path filters (13)
  • api/uv.lock is excluded by !**/*.lock
  • clients/python/uv.lock is excluded by !**/*.lock
  • sdks/python/uv.lock is excluded by !**/*.lock
  • services/runner/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • services/uv.lock is excluded by !**/*.lock
  • 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 (172)
  • .agents/skills/agent-release-gate/SKILL.md
  • .agents/skills/agent-release-gate/resources/path_triggers.py
  • .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
  • .all-contributorsrc
  • README.md
  • api/ee/src/dbs/postgres/sessions/records/dao.py
  • api/entrypoints/routers.py
  • api/entrypoints/worker_streams.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/apis/fastapi/evaluations/router.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/interactions/interfaces.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/streams/dtos.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/interactions/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/middlewares/auth.py
  • api/oss/src/routers/user_profile.py
  • api/oss/src/services/db_manager.py
  • api/oss/src/tasks/asyncio/sessions/records_worker.py
  • api/oss/src/tasks/asyncio/shared/consumer.py
  • api/oss/src/tasks/taskiq/triggers/worker.py
  • api/oss/src/utils/caching.py
  • api/oss/src/utils/crypting.py
  • api/oss/src/utils/env.py
  • api/oss/src/utils/exceptions.py
  • api/oss/src/utils/helpers.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_release_owner.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.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_publish.py
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/tests/pytest/unit/triggers/test_triggers_worker_lifecycle.py
  • api/oss/tests/pytest/unit/utils/test_caching.py
  • api/pyproject.toml
  • clients/python/pyproject.toml
  • docs/design/agent-workflows/documentation/adapters/agenta.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/addendum-always-active.md
  • docs/design/session-control-and-live-events/README.md
  • docs/design/session-control-and-live-events/api-design.md
  • docs/design/session-control-and-live-events/context.md
  • docs/design/session-control-and-live-events/decisions.md
  • docs/design/session-control-and-live-events/plan.md
  • docs/design/session-control-and-live-events/records-invariants.md
  • docs/design/session-control-and-live-events/requirements.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-records-ack.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/env.ee.dev.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/kubernetes/helm/Chart.yaml
  • sdks/python/agenta/sdk/agents/adapters/vercel/stream.py
  • sdks/python/oss/tests/pytest/unit/agents/adapters/test_vercel_stream_conformance.py
  • sdks/python/pyproject.toml
  • services/pyproject.toml
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • services/runner/src/engines/sandbox_agent/agent-mount.ts
  • services/runner/src/engines/sandbox_agent/cancel-turn.ts
  • services/runner/src/engines/sandbox_agent/credential-preflight.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/engines/sandbox_agent/errors.ts
  • services/runner/src/engines/sandbox_agent/mount.ts
  • services/runner/src/engines/sandbox_agent/provider.ts
  • services/runner/src/engines/sandbox_agent/reap-exec.ts
  • services/runner/src/engines/sandbox_agent/reconstruct-history.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/session-identity.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • services/runner/src/environment/abortable-sandbox-provider.ts
  • services/runner/src/environment/acquire-abort.ts
  • services/runner/src/environment/harness-session-lifecycle.ts
  • services/runner/src/environment/mount-lifecycle.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/admission.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/stop-signal.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/acquire-abort.test.ts
  • services/runner/tests/unit/cancel-continuity.test.ts
  • services/runner/tests/unit/continuation.test.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/credential-preflight.test.ts
  • services/runner/tests/unit/environment-units.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/tests/unit/reap-exec.test.ts
  • services/runner/tests/unit/sandbox-agent-mount.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-lifecycle.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-dispatch.test.ts
  • services/runner/tests/unit/session-ownership-release.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/session-reconstruct-history.test.ts
  • services/runner/tests/unit/session-steer-mount-loss.test.ts
  • services/runner/tests/unit/teardown.test.ts
  • web/ee/package.json
  • web/mobile/package.json
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/chat/LiveConversation.tsx
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/oss/package.json
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/package.json
  • web/packages/agenta-api-client/package.json
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts
  • web/packages/agenta-chat/src/model/error.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/model/error.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/src/workflow/state/store.ts
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
🚧 Files skipped from review as they are similar to previous changes (58)
  • docs/design/session-control-and-live-events/README.md
  • web/packages/agenta-entities/src/session/core/schema.ts
  • api/oss/src/middlewares/auth.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • hosting/docker-compose/oss/env.oss.dev.example
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • api/oss/src/dbs/postgres/sessions/commands/dbas.py
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • hosting/docker-compose/ee/env.ee.dev.example
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/core/sessions/streams/runner_client.py
  • web/packages/agenta-entities/src/session/index.ts
  • api/oss/src/core/sessions/commands/types.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • api/oss/src/core/sessions/interactions/interfaces.py
  • services/runner/src/sessions/stop-signal.ts
  • api/oss/tests/pytest/unit/sessions/test_watch_interactions_publish.py
  • services/runner/src/engines/sandbox_agent/engine.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/dbs/postgres/sessions/commands/mappings.py
  • api/oss/src/dbs/postgres/sessions/interactions/dao.py
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • services/runner/src/tracing/otel.ts
  • api/oss/src/core/sessions/streams/dtos.py
  • services/runner/tests/unit/session-pool.test.ts
  • api/oss/src/apis/fastapi/sessions/models.py
  • services/runner/src/sessions/execution-registry.ts
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • services/runner/src/sessions/control-channel.ts
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.py
  • services/runner/src/server.ts
  • web/packages/agenta-entities/src/session/core/liveness.ts
  • services/runner/tests/unit/teardown.test.ts
  • services/runner/src/sessions/applied-commands.ts
  • api/oss/src/core/sessions/commands/interfaces.py
  • services/runner/tests/unit/control-command-apply.test.ts
  • api/oss/src/core/sessions/commands/dtos.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/dbs/postgres/sessions/commands/dbes.py
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • api/oss/src/core/sessions/commands/service.py
  • api/oss/tests/pytest/unit/sessions/test_session_commands_dao.py
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/dbs/postgres/sessions/commands/dao.py
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_admission.py
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • api/entrypoints/routers.py

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


📝 Summary

Summary by CodeRabbit

  • New Features

    • Added durable session Stop support with reliable delivery, idempotency, execution safeguards, and status reporting.
    • Stopped executions can preserve warm sessions when cancellation completes successfully.
    • Added session cancellation API support with optional execution matching and conflict responses.
    • Improved liveness polling based on session activity.
  • Bug Fixes

    • Prevented stale Stops from affecting newer executions.
    • Preserved unsent messages when a session is already busy.
    • Improved record delivery reliability and retry handling.
    • Persisted workflow build-kit preferences across reloads.
  • Documentation

    • Added design documentation for session control, cancellation, live events, and durable commands.

Walkthrough

The change adds durable Stop commands, runner-side cancellation and warm parking, single-turn admission, cancellation records, commit-aware record redelivery, adaptive liveness polling, client execution targeting, persisted build-kit state, and release-gate validation.

Changes

Session control and runner lifecycle

Layer / File(s) Summary
Durable Stop flow
api/oss/src/core/sessions/commands/*, api/oss/src/dbs/postgres/sessions/commands/*, api/oss/databases/postgres/migrations/..., api/oss/src/apis/fastapi/sessions/*, api/entrypoints/routers.py
Adds durable command contracts, PostgreSQL persistence, idempotent admission, direct runner delivery, outcome reporting, settlement guards, authentication, and feature-flagged legacy fallback.
Runner cancellation and warm parking
services/runner/src/server.ts, services/runner/src/sessions/*, services/runner/src/engines/sandbox_agent/*, services/runner/src/environment/*, services/runner/src/lifecycle/*, services/runner/patches/*
Adds execution registries, deduplicated Stop handling, harness settlement detection, cooperative abort labels, warm parking, stopped-session TTLs, acquisition cancellation, history verification, and Codex child reaping.
Single-turn admission and client handling
services/runner/src/sessions/admission.ts, services/runner/src/lifecycle/session-coordinator.ts, services/runner/src/protocol.ts, web/packages/agenta-chat/*, web/oss/src/components/AgentChatSlice/*, sdks/python/agenta/sdk/agents/adapters/vercel/stream.py
Refuses concurrent turns without evicting the active environment, emits the admitted turn ID, restores refused message text, and forwards turn metadata to clients.
Records and session-state reliability
api/oss/src/tasks/asyncio/sessions/records_worker.py, api/oss/src/tasks/asyncio/shared/consumer.py, api/oss/src/core/sessions/interactions/*, api/oss/src/core/sessions/streams/*, api/ee/src/dbs/postgres/sessions/records/dao.py
Acknowledges record messages only after committed writes or deliberate drops, reclaims pending entries, isolates rejected records, records cancelled interactions, and fixes retention keying.
Client APIs, polling, and persisted UI state
web/packages/agenta-entities/src/session/*, web/mobile/src/features/sessions/*, web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts, web/packages/agenta-navigation/*, web/packages/agenta-entities/src/workflow/state/store.ts
Adds validated cancellation calls, shared liveness cadence, session-stop integration, and per-revision localStorage persistence for build-kit controls.
Validation and design support
.agents/skills/agent-release-gate/*, api/oss/tests/*, services/runner/tests/*, web/packages/*/tests/*, docs/design/session-control-and-live-events/*, hosting/docker-compose/*
Adds unit and integration coverage, mandatory session-control release cells, recorded-result validation, design documentation, and development Durable Stop settings.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟠 High · up to cd214

Merging can cancel the wrong active turn, lose deferred transcript records or rejected-message state, and expose command control across insufficiently scoped runner boundaries. These issues should be fixed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionControlRouter
  participant PostgreSQL
  participant Runner
  participant SandboxAgent
  Client->>SessionControlRouter: POST /sessions/{session_id}/cancel
  SessionControlRouter->>PostgreSQL: Store pending command
  SessionControlRouter->>Runner: POST /cancel
  Runner->>SandboxAgent: Send session/cancel
  SandboxAgent-->>Runner: Confirm or timeout
  Runner->>SessionControlRouter: Report outcome
  SessionControlRouter->>PostgreSQL: Settle command
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.15% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 372 functions across 50 files. (117 skipp… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: direct delivery of durable session Stop commands to the runner.
Description check ✅ Passed The description directly explains the durable Stop implementation, feature flag, API routes, runner delivery, settlement behavior, tests, evidence, and known limitations.
Full details: Docstring Coverage

Explanation

Docstring coverage is 27.15% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 372 functions across 50 files. (117 skipped: 38 unsupported, 79 over the file limit.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/session-durable-cancel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mmabrouk mmabrouk changed the title [overnight] feat(sessions): durable Stop command with the direct-call adapter (package F) feat(sessions): deliver durable Stop directly to the runner Sep 3, 2026
@mmabrouk
mmabrouk changed the base branch from spike/session-cancel-warm to main September 4, 2026 07:04
mmabrouk added a commit that referenced this pull request Sep 4, 2026
…on id

PR #6503 already uses oss000000022 on the same parent. This file becomes oss000000025 so
both branches can merge; whichever lands second re-points down_revision to the new head.

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

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

AGENT-GENERATED review, low weight. A read guide and the points a human should check; not an approval.

Read in this order

  1. api/oss/src/core/sessions/commands/service.py — admission, the three late-Stop guards, delivery, settlement.
  2. api/oss/databases/.../oss000000022_add_session_commands.py — the table, the partial unique index, the two session_streams columns.
  3. api/oss/src/dbs/postgres/sessions/commands/dao.py — every transition is one guarded compare-and-set.
  4. api/oss/src/apis/fastapi/sessions/router.py + middlewares/auth.py — the public Stop route and the runner-token outcome route.
  5. web/oss/.../useAgentChatSession.ts — the Stop button, now on the durable endpoint.

Top findings

  • One coherent change; the DAO compare-and-set tests run against real Postgres and are strong.
  • The rollback flag AGENTA_SESSIONS_DURABLE_STOP is NOT in this PR — the frontend switches to the new endpoint unconditionally and the route is always mounted. The gate arrives in #6501, so #6503 is safe only landed together with it. Confirm against the design's rollback contract.
  • Auth exemption /sessions/control/ collides with a session literally named control (see inline).
  • Migration is additive with a downgrade and chains cleanly on oss000000021; the outcome route fails closed with a constant-time compare.

Comment thread web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
Comment thread api/oss/src/middlewares/auth.py Outdated
Comment thread api/entrypoints/routers.py
Comment thread api/oss/src/apis/fastapi/sessions/router.py
Comment thread api/oss/src/core/sessions/commands/service.py
Comment thread api/oss/src/dbs/http/sessions/control_delivery_direct.py Outdated
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📘 Docs preview

Status ✅ Ready
Preview https://pr-6503-agenta-docs-preview.mahmoud-637.workers.dev/docs
Inspect Actions run
Commit cd21450f7eaa17a48ba80602d81fc0032b23127e

This comment updates in place on every push.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review summary

  • Risky: services/runner/src/server.ts:753 - parked-session ownership ignores the project half of the pool key, so the same session ID in another project can make this runner accept a command it does not hold.
  • Risky: api/oss/src/core/sessions/commands/service.py:235 - an idempotency-key collision can return an old terminal command while the response names the newly running turn.
  • Missing tests: cross-project parked sessions with identical session IDs; reuse of one idempotency key after a later turn starts.

Reviewed at e4cbd64ac91a81f172c999667b9d6a3131b4467e. Advisory COMMENT review only.

Comment thread services/runner/src/server.ts Outdated
Comment thread api/oss/src/core/sessions/commands/service.py Outdated
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-6503.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-6503-7f3f063
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-09-04T17:31:28.954Z

@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: 17

Caution

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

⚠️ Outside diff range comments (1)
services/runner/patches/sandbox-agent@0.4.2.patch (1)

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

Restore child termination on Windows.

When process.platform === "win32", use child.kill(signal) instead of process.kill(-child.pid, signal). The detached child can remain running when the process-group call throws and the empty catch discards the error.

🧹 Nitpick comments (3)
docs/design/session-control-and-live-events/slice-durable-cancel.md (1)

122-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the Fern-generated client for Stop.

Add the cancellation route to Fern, regenerate the sessions client, and replace the raw axios.post in cancelSessionExecution. Preserve the request fields and 409 handling. Update this paragraph to state that the Fern client is used.

api/oss/tests/pytest/unit/sessions/test_session_cancel_admission.py (1)

879-880: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the late claim is attempted.

_ReportsBeforeTheClaimCommits.deliver settles the row before returning accepted, after which SessionCommandsService._deliver calls _dao.claim_for_delivery. Line 880 repeats line 877, so the test can pass if that late claim call is removed. Assert that dao.claims contains this command and "runner-1"; keep the existing outcome assertion.

api/oss/tests/pytest/unit/sessions/test_session_cancel_feature_flag.py (1)

94-94: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Pin the durable response body, not only the status code.

Assert the JSON returned by SessionCancelResponse with the command ID and pending state, plus execution ID and stopping state. This test should detect future wire-contract changes.


ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 9f7a6b40-593b-4f4a-8d51-dc8df0bfeef9

📥 Commits

Reviewing files that changed from the base of the PR and between ded2cc3 and e4cbd64.

⛔ Files ignored due to path filters (1)
  • services/runner/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (81)
  • api/entrypoints/routers.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.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/interactions/interfaces.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/streams/dtos.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/interactions/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/middlewares/auth.py
  • api/oss/src/utils/env.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.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_wp5_dao_fanout.py
  • docs/design/session-control-and-live-events/README.md
  • docs/design/session-control-and-live-events/api-design.md
  • docs/design/session-control-and-live-events/context.md
  • docs/design/session-control-and-live-events/decisions.md
  • docs/design/session-control-and-live-events/plan.md
  • docs/design/session-control-and-live-events/records-invariants.md
  • docs/design/session-control-and-live-events/requirements.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-durable-cancel.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/env.ee.dev.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • services/runner/src/engines/sandbox_agent/cancel-turn.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.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/stop-signal.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/teardown.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.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/index.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts

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

Comment thread api/oss/src/apis/fastapi/sessions/router.py Outdated
Comment thread api/oss/src/core/sessions/interactions/service.py Outdated
Comment thread api/oss/src/core/sessions/streams/runner_client.py
Comment thread api/oss/src/core/sessions/streams/runner_client.py Outdated
Comment thread docs/design/session-control-and-live-events/api-design.md Outdated
Comment thread web/mobile/src/features/sessions/useActionableInteractions.ts Outdated
Comment thread web/mobile/src/features/sessions/useActionableInteractions.ts Outdated
Comment thread web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
Comment thread web/packages/agenta-entities/src/session/api/api.ts Outdated
Comment thread web/packages/agenta-entities/src/session/api/api.ts Outdated
A user Stop reached the runner only as the absence of a Redis lock, noticed on
the next heartbeat up to 30 seconds later. Nothing recorded that a Stop had been
asked for, so a Stop against an unreachable runner was simply lost and no
execution ever reached a terminal outcome anyone could read.

Add session_commands: one row per durable request to change an execution. Two
columns that are never merged carry the two questions a caller actually asks.
state says where the COMMAND is (pending, claimed, applied, obsolete). outcome
says what happened to the EXECUTION (stopped, not_running,
superseded_by_newer_turn, failed, lost). A client drawing a Stop button reads the
execution; a client retrying safely reads the command id.

Every transition is one UPDATE ... WHERE <expected state> RETURNING *, decided by
scalar_one_or_none, the same compare-and-set transition_interaction already uses.
That is what stops two API replicas both winning a claim or both writing a
terminal outcome. Idempotency has two layers: the caller's Idempotency-Key on
(project_id, session_id, idempotency_key), and a collapse onto any open command
for the same target execution, which is what makes two Stops in a row correct
without asking the browser to send a key.

session_streams gains two columns. stopping_turn_id names the execution an
accepted Stop is waiting on, written in the same transaction as the command
insert. turn_started_at records when the row's current turn_id started, because
the stale-Stop guard has to compare a Stop's arrival time with the running
execution's start time and there was nowhere to read that: updated_at is the
heartbeat timestamp and moves every 30 seconds, runner-minted turn ids are uuid4
and carry no time, the Redis lock value is a bare turn id a Lua compare reads
whole, and the session_turns append is fire-and-forget so a running turn may have
no row. It is stamped only when the id actually changes, so the repeated
heartbeats that restamp the same id never move it.

Both columns backfill to NULL. A row written before this migration yields no
comparison and the guard does not fire, deliberately: a guard that refused every
Stop it could not verify would break the common case to protect a rare one.

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

Re-measures the live protocol against the rebased runner (82ms to the abort,
126ms to settlement, 993ms to the warm park), records the fourth defect the
rebase exposed, and rewrites the census entry: it is removed rather than
softened, on the revised design's guidance that the not_held detector is the one
worth building and the census is optional. That closes the one open question that
was a deviation from the brief.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Settlement released `running` in Redis and stopped there. The row kept
`is_running: true`, and the row is the only thing the product's liveness
polls read: `query_streams` serves Postgres and never looks at Redis.

Nothing else could correct it. Settlement tombstones the stopped execution
before it releases `running`, so the runner's own final `is_running=false`
heartbeat is refused by the tombstone check and returns before the mirror
write at the end of `heartbeat`. The order cannot be swapped: a late beat
that found `alive` free would take it straight back under the dead turn's
id. The runner reports its outcome as soon as it issues the abort, so the
tombstone always wins that race.

Measured on the local sandbox with Pi before the fix: the row read
`is_running: true` with a pre-Stop `updated_at` for the full 193 s of the
sample, while Redis had released `running` within 0.5 s. The tab that
pressed Stop therefore showed a "running somewhere else" strip over its own
session until the orphan sweep collapsed the row minutes later. After the
fix the row reads `is_running: false, is_alive: true` within 0.15 s of the
request, and the parked sandbox still resumes warm.

`mirror_liveness` re-reads Redis rather than writing a literal `false`, so a
newer turn that has already taken `running` is reported and not erased.

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Every liveness `refetchInterval` asked "is the alive set non-empty", which
is not the question. Stop ends the work and leaves the session alive so the
sandbox resumes warm, and an ordinary turn end does the same, so one stopped
session held all four polls at 15 s, in every open tab, for the hour that
`alive` lock lives.

One shared predicate now answers the real question: 15 s while something is
RUNNING, 60 s while a session is merely alive, and stop when nothing is
alive. The sidebar rail passes a 60 s idle floor instead of stopping,
because it must still discover a run it did not start; that baseline was
already deliberate and is unchanged.

The mobile gates poll keys on running directly, since a running turn is
what mints new gates.

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

Admission resolves the execution to stop from the Redis `running` key with a
fallback to `alive`, so a session parked on an approval is reachable. The
`expected_execution_id` guard then compared against `running` alone.

A parked approval has released `running` and still holds `alive` under the
same turn id, so the guard read `running` as none, refused the request with
a conflict naming "current: none", and left the gate pending. The identical
Stop sent without an expectation was accepted and cancelled the gate. The
browser always sends the id it streamed, so pressing Stop on an approval
card was refused in the product while the integration approval cell passed,
because its driver sent no expectation. The guard fired on the one case it
exists to allow.

It now compares against the resolved target. The guard still refuses a
stale id: a Stop naming a finished turn on a session parked under a newer
one is refused, and the conflict now names the turn that would have been
stopped instead of none.

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

Admission inserts the command `pending`, hands it to the runner, and writes
`claimed` on the runner's behalf only after the runner answers. A runner
that aborts fast reports its outcome inside that window, while the row
still says `pending`.

The outcome route guarded on `claimed` alone, so that report was refused
with a conflict. Observed on a Stop during model output: the runner aborted
and parked correctly, logged `[control] outcome HTTP 409`, and the command
sat `claimed` for 2 min 17 s until the sweep settled it `obsolete` with
outcome `lost`. The user watched "stopping" for the whole sweep window and
a Stop that worked was recorded as lost.

The guard is now a set, and it is still one statement, so it is evaluated
at the moment of the write. Reading the state first and updating after
would reopen the same race: the claim can commit in between.

The replica guard is widened only where there is nothing to guard. A
`pending` row holds no claim, so a null `claimed_by` passes; a claimed row
must still be claimed by the reporter, and a report from any other replica
is still refused.

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

A Stop pressed as the answer lands used to tear the sandbox down, so the
next message rebuilt cold. Reproduced on the local sandbox with Pi by
firing the Stop on the runner's own `prompt stopReason=end_turn` line:

  [control] aborted command=... turn=c81b783e...
  [control] outcome reported command=... state=stopped
  [keepalive] evict key=... reason=no-park:end_turn

and the next message took 7.2 s against 1.9 s warm.

Two things were wrong, in two places.

The execution stays registered through teardown, which writes the
transcript, exports the trace and parks the environment, and that takes
hundreds of milliseconds. A Stop arriving in that window found a live entry
and aborted it. The abort stopped nothing, because the prompt had already
settled, but the aborted signal then made `shouldPark` refuse to park a
healthy idle environment. The run is now marked settled the instant the
harness prompt settles, before teardown begins, and the applier does
nothing at all for a settled run. Nothing aborts, so the ordinary park path
runs. It reports `obsolete` with `not_running`, because the command stopped
nothing.

Past that window the runner has dropped the execution and answers
`not_held`, and the API judged that on whether the row was beating. A turn
that has just ended leaves `alive` set and a fresh beat behind it exactly
as a running one does, so every late Stop was settled `lost` and the user
was told their Stop failed when the work had simply finished. The
discriminator is now `running`: an execution holding it means a process is
running this session and it is not the one we called, which is the
wrong-replica failure the `lost` outcome exists for. With no `running`
owner nothing is executing anywhere, and `not_running` is the honest
answer.

Verified live on both windows: the command settles `obsolete` /
`not_running`, no eviction, and the next message reuses the parked sandbox
warm (`hit-continue`, 1.9 s).

Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Return whether command insertion won so a replay returns its original target without delivering the command to a newer execution. Cover the replay after the session advances to another turn.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Encode runner credentials before constant-time comparison so non-ASCII input is rejected with 401 instead of raising an internal error.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Treat audit-record persistence as best effort after pending interactions are cancelled. A record-store failure is logged while lifecycle publication and the cancellation result continue.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Accept only object-shaped runner acknowledgements when reading replica identity and make the optional delivery timeout explicit. Malformed successful JSON remains an accepted response without crashing admission.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk

@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.

Caution

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

⚠️ Outside diff range comments (1)
api/oss/src/dbs/postgres/sessions/commands/dao.py (1)

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

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

Reachability: External · Exploitability: Moderate

Propagate project scope through the runner outcome path.

report_outcome fetches the command and fallback state by command_id only. Carry ControlCommand.projectId in the outcome request, pass it to both DAO reads, and require the project filter. SessionCommandDBE uses (project_id, id) as its key, so the runner token alone must not select or settle another project's command.


ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: ba236d57-74ae-4478-a815-0f65d75e9bb0

📥 Commits

Reviewing files that changed from the base of the PR and between e4cbd64 and 2251b4d.

⛔ 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 (33)
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/sessions/commands/interfaces.py
  • api/oss/src/core/sessions/commands/service.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/streams/runner_client.py
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/dbs/postgres/sessions/commands/dao.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.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
  • 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/status.md
  • docs/design/session-control-and-live-events/tonight-handoff.md
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/control-channel.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/server.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.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/tests/unit/session-cancel-api.test.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
  • api/oss/src/core/sessions/streams/runner_client.py
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.py
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • docs/design/session-control-and-live-events/api-design.md
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_feature_flag.py
  • docs/design/session-control-and-live-events/status.md
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/core/sessions/interactions/service.py
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/packages/agenta-entities/src/session/core/liveness.ts
  • docs/design/session-control-and-live-events/decisions.md
  • api/oss/src/apis/fastapi/sessions/router.py

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

@mmabrouk mmabrouk added the lgtm This PR has been approved by a maintainer label Sep 4, 2026
Scope parked-session lookup by project, keep paused approval turns cancellable, clean execution registrations on rejected or failed setup, and refuse redirects when reporting outcomes. Add focused regressions for each boundary.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Regenerate the sessions cancellation route and related stream fields from the branch OpenAPI schema. Replace raw Axios with the typed sessions accessor, preserve project query scope and idempotency headers, and validate successful payloads with Zod.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Fetch the current session stream before aborting the client transport so the Stop command cannot observe and cancel a newer turn submitted during target acquisition.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Subscribe the actionable-interactions hook to the shared liveness query so a newly running turn restarts gate polling after the idle interval was disabled.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Keep the execution-versus-warm invariants at the affected call sites while moving each changed code comment back to one concise line.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Align the API design, decisions, status, and handoff with the implemented direct control adapter. Mark runner-initiated long polling as deferred behind the same delivery port.

Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
@mmabrouk
mmabrouk force-pushed the feat/session-durable-cancel branch from 2251b4d to cd21450 Compare September 4, 2026 17:20
@mmabrouk
mmabrouk changed the base branch from main to feat/session-control September 4, 2026 17:20
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: 15


ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 7d9ba61a-653e-4a63-b809-75d469ee8856

📥 Commits

Reviewing files that changed from the base of the PR and between 69db342 and cd21450.

⛔ Files ignored due to path filters (13)
  • api/uv.lock is excluded by !**/*.lock
  • clients/python/uv.lock is excluded by !**/*.lock
  • sdks/python/uv.lock is excluded by !**/*.lock
  • services/runner/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • services/uv.lock is excluded by !**/*.lock
  • 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 (172)
  • .agents/skills/agent-release-gate/SKILL.md
  • .agents/skills/agent-release-gate/resources/path_triggers.py
  • .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
  • .all-contributorsrc
  • README.md
  • api/ee/src/dbs/postgres/sessions/records/dao.py
  • api/entrypoints/routers.py
  • api/entrypoints/worker_streams.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/apis/fastapi/evaluations/router.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/interactions/interfaces.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/streams/dtos.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/interactions/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/middlewares/auth.py
  • api/oss/src/routers/user_profile.py
  • api/oss/src/services/db_manager.py
  • api/oss/src/tasks/asyncio/sessions/records_worker.py
  • api/oss/src/tasks/asyncio/shared/consumer.py
  • api/oss/src/tasks/taskiq/triggers/worker.py
  • api/oss/src/utils/caching.py
  • api/oss/src/utils/crypting.py
  • api/oss/src/utils/env.py
  • api/oss/src/utils/exceptions.py
  • api/oss/src/utils/helpers.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_release_owner.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.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_publish.py
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/tests/pytest/unit/triggers/test_triggers_worker_lifecycle.py
  • api/oss/tests/pytest/unit/utils/test_caching.py
  • api/pyproject.toml
  • clients/python/pyproject.toml
  • docs/design/agent-workflows/documentation/adapters/agenta.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/addendum-always-active.md
  • docs/design/session-control-and-live-events/README.md
  • docs/design/session-control-and-live-events/api-design.md
  • docs/design/session-control-and-live-events/context.md
  • docs/design/session-control-and-live-events/decisions.md
  • docs/design/session-control-and-live-events/plan.md
  • docs/design/session-control-and-live-events/records-invariants.md
  • docs/design/session-control-and-live-events/requirements.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-records-ack.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/env.ee.dev.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/kubernetes/helm/Chart.yaml
  • sdks/python/agenta/sdk/agents/adapters/vercel/stream.py
  • sdks/python/oss/tests/pytest/unit/agents/adapters/test_vercel_stream_conformance.py
  • sdks/python/pyproject.toml
  • services/pyproject.toml
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • services/runner/src/engines/sandbox_agent/agent-mount.ts
  • services/runner/src/engines/sandbox_agent/cancel-turn.ts
  • services/runner/src/engines/sandbox_agent/credential-preflight.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/engines/sandbox_agent/errors.ts
  • services/runner/src/engines/sandbox_agent/mount.ts
  • services/runner/src/engines/sandbox_agent/provider.ts
  • services/runner/src/engines/sandbox_agent/reap-exec.ts
  • services/runner/src/engines/sandbox_agent/reconstruct-history.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/session-identity.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • services/runner/src/environment/abortable-sandbox-provider.ts
  • services/runner/src/environment/acquire-abort.ts
  • services/runner/src/environment/harness-session-lifecycle.ts
  • services/runner/src/environment/mount-lifecycle.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/admission.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/stop-signal.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/acquire-abort.test.ts
  • services/runner/tests/unit/cancel-continuity.test.ts
  • services/runner/tests/unit/continuation.test.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/credential-preflight.test.ts
  • services/runner/tests/unit/environment-units.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/tests/unit/reap-exec.test.ts
  • services/runner/tests/unit/sandbox-agent-mount.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-lifecycle.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-dispatch.test.ts
  • services/runner/tests/unit/session-ownership-release.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/session-reconstruct-history.test.ts
  • services/runner/tests/unit/session-steer-mount-loss.test.ts
  • services/runner/tests/unit/teardown.test.ts
  • web/ee/package.json
  • web/mobile/package.json
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/chat/LiveConversation.tsx
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/oss/package.json
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/package.json
  • web/packages/agenta-api-client/package.json
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts
  • web/packages/agenta-chat/src/model/error.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/model/error.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/src/workflow/state/store.ts
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
🚧 Files skipped from review as they are similar to previous changes (58)
  • docs/design/session-control-and-live-events/README.md
  • web/packages/agenta-entities/src/session/core/schema.ts
  • api/oss/src/middlewares/auth.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • hosting/docker-compose/oss/env.oss.dev.example
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • api/oss/src/dbs/postgres/sessions/commands/dbas.py
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • hosting/docker-compose/ee/env.ee.dev.example
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/core/sessions/streams/runner_client.py
  • web/packages/agenta-entities/src/session/index.ts
  • api/oss/src/core/sessions/commands/types.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • api/oss/src/core/sessions/interactions/interfaces.py
  • services/runner/src/sessions/stop-signal.ts
  • api/oss/tests/pytest/unit/sessions/test_watch_interactions_publish.py
  • services/runner/src/engines/sandbox_agent/engine.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/dbs/postgres/sessions/commands/mappings.py
  • api/oss/src/dbs/postgres/sessions/interactions/dao.py
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • services/runner/src/tracing/otel.ts
  • api/oss/src/core/sessions/streams/dtos.py
  • services/runner/tests/unit/session-pool.test.ts
  • api/oss/src/apis/fastapi/sessions/models.py
  • services/runner/src/sessions/execution-registry.ts
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • services/runner/src/sessions/control-channel.ts
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.py
  • services/runner/src/server.ts
  • web/packages/agenta-entities/src/session/core/liveness.ts
  • services/runner/tests/unit/teardown.test.ts
  • services/runner/src/sessions/applied-commands.ts
  • api/oss/src/core/sessions/commands/interfaces.py
  • services/runner/tests/unit/control-command-apply.test.ts
  • api/oss/src/core/sessions/commands/dtos.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/dbs/postgres/sessions/commands/dbes.py
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • api/oss/src/core/sessions/commands/service.py
  • api/oss/tests/pytest/unit/sessions/test_session_commands_dao.py
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/dbs/postgres/sessions/commands/dao.py
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_admission.py
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • api/entrypoints/routers.py

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

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 15


ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 7d9ba61a-653e-4a63-b809-75d469ee8856

📥 Commits

Reviewing files that changed from the base of the PR and between 69db342 and cd21450.

⛔ Files ignored due to path filters (13)
  • api/uv.lock is excluded by !**/*.lock
  • clients/python/uv.lock is excluded by !**/*.lock
  • sdks/python/uv.lock is excluded by !**/*.lock
  • services/runner/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • services/uv.lock is excluded by !**/*.lock
  • 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 (172)
  • .agents/skills/agent-release-gate/SKILL.md
  • .agents/skills/agent-release-gate/resources/path_triggers.py
  • .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
  • .all-contributorsrc
  • README.md
  • api/ee/src/dbs/postgres/sessions/records/dao.py
  • api/entrypoints/routers.py
  • api/entrypoints/worker_streams.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/apis/fastapi/evaluations/router.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/interactions/interfaces.py
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/core/sessions/streams/dtos.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/interactions/dao.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/middlewares/auth.py
  • api/oss/src/routers/user_profile.py
  • api/oss/src/services/db_manager.py
  • api/oss/src/tasks/asyncio/sessions/records_worker.py
  • api/oss/src/tasks/asyncio/shared/consumer.py
  • api/oss/src/tasks/taskiq/triggers/worker.py
  • api/oss/src/utils/caching.py
  • api/oss/src/utils/crypting.py
  • api/oss/src/utils/env.py
  • api/oss/src/utils/exceptions.py
  • api/oss/src/utils/helpers.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_release_owner.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.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_publish.py
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/tests/pytest/unit/triggers/test_triggers_worker_lifecycle.py
  • api/oss/tests/pytest/unit/utils/test_caching.py
  • api/pyproject.toml
  • clients/python/pyproject.toml
  • docs/design/agent-workflows/documentation/adapters/agenta.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/addendum-always-active.md
  • docs/design/session-control-and-live-events/README.md
  • docs/design/session-control-and-live-events/api-design.md
  • docs/design/session-control-and-live-events/context.md
  • docs/design/session-control-and-live-events/decisions.md
  • docs/design/session-control-and-live-events/plan.md
  • docs/design/session-control-and-live-events/records-invariants.md
  • docs/design/session-control-and-live-events/requirements.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-records-ack.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/env.ee.dev.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/kubernetes/helm/Chart.yaml
  • sdks/python/agenta/sdk/agents/adapters/vercel/stream.py
  • sdks/python/oss/tests/pytest/unit/agents/adapters/test_vercel_stream_conformance.py
  • sdks/python/pyproject.toml
  • services/pyproject.toml
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • services/runner/src/engines/sandbox_agent/agent-mount.ts
  • services/runner/src/engines/sandbox_agent/cancel-turn.ts
  • services/runner/src/engines/sandbox_agent/credential-preflight.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/engines/sandbox_agent/errors.ts
  • services/runner/src/engines/sandbox_agent/mount.ts
  • services/runner/src/engines/sandbox_agent/provider.ts
  • services/runner/src/engines/sandbox_agent/reap-exec.ts
  • services/runner/src/engines/sandbox_agent/reconstruct-history.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/session-identity.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • services/runner/src/environment/abortable-sandbox-provider.ts
  • services/runner/src/environment/acquire-abort.ts
  • services/runner/src/environment/harness-session-lifecycle.ts
  • services/runner/src/environment/mount-lifecycle.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/admission.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/stop-signal.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/acquire-abort.test.ts
  • services/runner/tests/unit/cancel-continuity.test.ts
  • services/runner/tests/unit/continuation.test.ts
  • services/runner/tests/unit/control-command-apply.test.ts
  • services/runner/tests/unit/credential-preflight.test.ts
  • services/runner/tests/unit/environment-units.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/tests/unit/reap-exec.test.ts
  • services/runner/tests/unit/sandbox-agent-mount.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-lifecycle.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-dispatch.test.ts
  • services/runner/tests/unit/session-ownership-release.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/session-reconstruct-history.test.ts
  • services/runner/tests/unit/session-steer-mount-loss.test.ts
  • services/runner/tests/unit/teardown.test.ts
  • web/ee/package.json
  • web/mobile/package.json
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/chat/LiveConversation.tsx
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • web/oss/package.json
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/package.json
  • web/packages/agenta-api-client/package.json
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts
  • web/packages/agenta-chat/src/model/error.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/model/error.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/src/workflow/state/store.ts
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
🚧 Files skipped from review as they are similar to previous changes (58)
  • docs/design/session-control-and-live-events/README.md
  • web/packages/agenta-entities/src/session/core/schema.ts
  • api/oss/src/middlewares/auth.py
  • api/oss/tests/pytest/unit/middlewares/test_auth_public_endpoints.py
  • services/runner/patches/sandbox-agent@0.4.2.patch
  • hosting/docker-compose/oss/env.oss.dev.example
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • api/oss/src/dbs/postgres/sessions/commands/dbas.py
  • web/packages/agenta-entities/tests/unit/session-liveness.test.ts
  • hosting/docker-compose/ee/env.ee.dev.example
  • api/oss/src/dbs/http/sessions/control_delivery_direct.py
  • api/oss/src/core/sessions/streams/runner_client.py
  • web/packages/agenta-entities/src/session/index.ts
  • api/oss/src/core/sessions/commands/types.py
  • api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py
  • web/mobile/src/features/sessions/useLivenessPoll.ts
  • api/oss/src/core/sessions/interactions/interfaces.py
  • services/runner/src/sessions/stop-signal.ts
  • api/oss/tests/pytest/unit/sessions/test_watch_interactions_publish.py
  • services/runner/src/engines/sandbox_agent/engine.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • api/oss/src/core/sessions/interactions/service.py
  • api/oss/src/dbs/postgres/sessions/commands/mappings.py
  • api/oss/src/dbs/postgres/sessions/interactions/dao.py
  • web/packages/agenta-entities/tests/unit/session-cancel-api.test.ts
  • services/runner/src/tracing/otel.ts
  • api/oss/src/core/sessions/streams/dtos.py
  • services/runner/tests/unit/session-pool.test.ts
  • api/oss/src/apis/fastapi/sessions/models.py
  • services/runner/src/sessions/execution-registry.ts
  • api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py
  • api/oss/src/dbs/postgres/sessions/streams/dbes.py
  • services/runner/src/sessions/control-channel.ts
  • api/oss/tests/pytest/unit/sessions/test_interaction_cancel_records.py
  • services/runner/src/server.ts
  • web/packages/agenta-entities/src/session/core/liveness.ts
  • services/runner/tests/unit/teardown.test.ts
  • services/runner/src/sessions/applied-commands.ts
  • api/oss/src/core/sessions/commands/interfaces.py
  • services/runner/tests/unit/control-command-apply.test.ts
  • api/oss/src/core/sessions/commands/dtos.py
  • api/oss/src/dbs/postgres/sessions/streams/mappings.py
  • api/oss/src/dbs/postgres/sessions/commands/dbes.py
  • web/packages/agenta-navigation/src/dynamic/sessionsSource.ts
  • api/oss/src/core/sessions/commands/service.py
  • api/oss/tests/pytest/unit/sessions/test_session_commands_dao.py
  • web/packages/agenta-navigation/tests/unit/sidebarChildren.test.ts
  • services/runner/tests/unit/harness-cancel-park.test.ts
  • services/runner/src/engines/sandbox_agent/teardown.ts
  • api/oss/src/core/sessions/streams/types.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000022_add_session_commands.py
  • api/oss/src/dbs/postgres/sessions/commands/dao.py
  • api/oss/tests/pytest/unit/sessions/test_session_cancel_admission.py
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
  • web/mobile/src/features/sessions/useActionableInteractions.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • api/entrypoints/routers.py

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

🛑 Comments failed to post (15)
.agents/skills/agent-release-gate/resources/path_triggers.py (1)

88-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Trigger session-control evidence for persistence and composition-root changes.

A release limited to api/oss/src/dbs/postgres/sessions/**, the related session-command migration, or session-control wiring in api/entrypoints/** does not require session_control.py. Those paths can break durable Stop persistence or delivery while the release gate remains green.

  • .agents/skills/agent-release-gate/resources/path_triggers.py#L88-L91: Add path rules for session persistence, relevant migrations, and session-control composition roots.
  • .agents/skills/agent-release-gate/SKILL.md#L195-L199: Document the same expanded mandatory-path scope.
📍 Affects 2 files
  • .agents/skills/agent-release-gate/resources/path_triggers.py#L88-L91 (this comment)
  • .agents/skills/agent-release-gate/SKILL.md#L195-L199
api/oss/src/apis/fastapi/sessions/router.py (1)

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

Reject oversized idempotency keys. cancel_session_execution truncates the normalized key before request_cancel. The DAO uses that value for the unique command lookup, so two distinct keys with the same first 255 characters can replay the first command after a later execution starts. If the normalized key exceeds 255 characters, return HTTP 422 instead of truncating it.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        idempotency_key = request.headers.get("Idempotency-Key")
        if idempotency_key is not None:
            idempotency_key = idempotency_key.strip()
            if len(idempotency_key) > _MAX_IDEMPOTENCY_KEY_CHARACTERS:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    detail="Idempotency-Key is too long.",
                )
            idempotency_key = idempotency_key or None
api/oss/src/core/sessions/streams/service.py (1)

212-215: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make expected-turn cancellation atomic.

A new turn can replace the locks after the earlier owner check. This path then tombstones and clears that new turn. Use an atomic compare-and-delete or fencing operation that verifies expected_turn_id at the lock mutation point.

api/oss/src/tasks/asyncio/sessions/records_worker.py (1)

243-248: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🤖 get_repo_knowledge executed:

get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/conventions

Length of output: 12646


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- worker outline ---'
ast-grep outline api/oss/src/tasks/asyncio/sessions/records_worker.py

printf '%s\n' '--- worker focused source ---'
sed -n '1,125p' api/oss/src/tasks/asyncio/sessions/records_worker.py
sed -n '150,390p' api/oss/src/tasks/asyncio/sessions/records_worker.py

printf '%s\n' '--- directly bound reclaim symbols and callers ---'
rg -n -C 5 'reclaim_batch|drop_expired|max_deliveries|_last_commit_at|process_batch|quota|entitlement|org_deferred' api/oss/src/tasks/asyncio api/oss/src -g '*.py'

Repository: Agenta-AI/agenta

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- consumer size and outline ---'
wc -l api/oss/src/tasks/asyncio/shared/consumer.py
ast-grep outline api/oss/src/tasks/asyncio/shared/consumer.py

printf '%s\n' '--- consumer implementation ---'
sed -n '1,420p' api/oss/src/tasks/asyncio/shared/consumer.py

printf '%s\n' '--- focused tests and configuration ---'
rg -n -C 6 'reclaim_batch|drop_expired|mark_committed|last_commit|max_deliveries|reclaim_pending' api/oss/tests api/oss/src/tasks/asyncio/shared api/oss/src/utils/env.py -g '*.py'

Repository: Agenta-AI/agenta

Length of output: 44429


Exclude quota-deferred entries from StreamConsumer’s delivery budget.

When check_entitlements fails, RecordsWorker.process_batch leaves the entry pending. An unrelated successful append calls mark_committed(), so reclaim_batch can treat the path as healthy and drop_expired can acknowledge and delete the deferred entry after max_deliveries. Track deferred entries or retry the quota check before expiration.

Add a regression with one successful project and one project with repeated entitlement failures. The deferred entry must remain pending beyond max_deliveries.

api/oss/src/utils/env.py (1)

528-532: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject non-positive record recovery settings. AGENTA_RECORDS_RECLAIM_IDLE_MS=0 makes StreamConsumer claim pending entries on every loop. Negative idle values make Redis reclaim calls invalid. AGENTA_RECORDS_MAX_DELIVERIES<=0 can make a healthy consumer ACK/delete a pending record before its next retry. Use _parse_optional_positive_int_env for both settings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    reclaim_idle_ms: int = (
        _parse_optional_positive_int_env("AGENTA_RECORDS_RECLAIM_IDLE_MS")
        or 30_000
    )

    # Deliveries after which a record message is dropped instead of retried forever. A message
    # Postgres never accepts would otherwise hold every later message in the group.
    max_deliveries: int = (
        _parse_optional_positive_int_env("AGENTA_RECORDS_MAX_DELIVERIES") or 5
    )
docs/design/session-control-and-live-events/spike-b-durable-commands-design.md (1)

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file='docs/design/session-control-and-live-events/spike-b-durable-commands-design.md'
printf '%s\n' '--- relevant identifiers and authorization terms ---'
rg -n -C 4 'AGENTA_RUNNER_TOKEN|replica_id|sessions|lease|owner|claim|settle|consume|authorization|authenticate|project scope' "$file"
printf '%s\n' '--- target context ---'
sed -n '470,545p' "$file"

Repository: Agenta-AI/agenta

Length of output: 50374


🤖 get_repo_knowledge executed:

get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/conventions

Length of output: 23964


🏁 Script executed:

#!/bin/bash
set -eu
file='docs/design/session-control-and-live-events/spike-b-durable-commands-design.md'
printf '%s\n' '--- authentication and claim contract ---'
sed -n '494,545p' "$file"
printf '%s\n' '--- runner contract and ownership-related terms ---'
sed -n '637,670p' "$file"
sed -n '918,1015p' "$file"
printf '%s\n' '--- bounded ownership checks ---'
rg -n -C 3 'not_held|held_sessions|owns|owner|lease|replica_id|runner token|AGENTA_RUNNER_TOKEN|server-side|authorize|authorization' "$file" | tail -n 180

Repository: Agenta-AI/agenta

Length of output: 21726


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

Reachability: Internal · Exploitability: Difficult

Bind declared sessions to the authenticated runner.

The shared AGENTA_RUNNER_TOKEN does not bind a caller to its sessions or replica_id. The claim query and settlement guard trust both caller-supplied values, so a token holder can claim commands for another project and settle them. Require a server-side runner lease or owner check for every declared session, or use credentials scoped to the allowed sessions.

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

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

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -e
file="services/runner/src/environment/mount-lifecycle.ts"
wc -l "$file"
sed -n '1,290p' "$file"
printf '\n--- directly bound symbols ---\n'
rg -n --glob '*.ts' 'function (throwIfAcquireAborted|rethrowIfInvariant)|const (throwIfAcquireAborted|rethrowIfInvariant)|throwIfAcquireAborted|rethrowIfInvariant|commitLocalMount|mountStorage|AcquireAbortedError' services/runner/src

Repository: Agenta-AI/agenta

Length of output: 22542


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- acquire-abort.ts ---'
cat -n services/runner/src/environment/acquire-abort.ts
printf '%s\n' '--- acquire-context.ts: commit and mount state ---'
sed -n '90,155p' services/runner/src/environment/acquire-context.ts
sed -n '235,305p' services/runner/src/environment/acquire-context.ts
printf '%s\n' '--- context implementation: commit and cleanup-related methods ---'
sed -n '90,175p' services/runner/src/environment/acquire-context-impl.ts
printf '%s\n' '--- mountStorage success/abort path ---'
sed -n '300,455p' services/runner/src/engines/sandbox_agent/mount.ts
printf '%s\n' '--- callers and teardown references ---'
rg -n -C 8 --glob '*.ts' 'mountLocalDurableCwd|mountLocalAgentCwd|mountedCwd|agentMountedPath|unmountStorage\\(' services/runner/src/environment services/runner/src/engines/sandbox_agent/environment.ts

Repository: Agenta-AI/agenta

Length of output: 19676


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- mount lifecycle callers and remaining functions ---'
sed -n '290,391p' services/runner/src/environment/mount-lifecycle.ts
printf '%s\n' '--- environment acquisition and teardown around local mounts ---'
sed -n '410,490p' services/runner/src/engines/sandbox_agent/environment.ts
sed -n '520,590p' services/runner/src/engines/sandbox_agent/environment.ts
sed -n '740,785p' services/runner/src/engines/sandbox_agent/environment.ts
printf '%s\n' '--- all direct call sites ---'
rg -n -C 6 --glob '*.ts' 'mountLocalDurableCwd|mountLocalAgentCwd' services/runner/src

Repository: Agenta-AI/agenta

Length of output: 34364


Commit each successful local mount before propagating acquisition aborts. Both helpers can receive true from mountStorage, then throw before ctx.commitLocalMount; teardown therefore has no mountedCwd or agentMountedPath to unmount. Commit immediately after success, then propagate AcquireAbortedError. In mountLocalAgentCwd, rethrowIfInvariant does not rethrow AcquireAbortedError, so the catch logs it as a normal mount failure and returns false.

📍 Affects 1 file
  • services/runner/src/environment/mount-lifecycle.ts#L211-L211 (this comment)
  • services/runner/src/environment/mount-lifecycle.ts#L247-L255
services/runner/tests/unit/sandbox-agent-orchestration.test.ts (1)

2637-2640: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Assert that the paused execution remains registered

findExecution(... )?.settled is undefined both when the entry is present and unsettled and when the entry is absent. The test can therefore pass after the registry entry is removed, leaving the live-turn lookup and cancellation invariant untested.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const execution = findExecution(
      "11111111-1111-4111-8111-111111111111",
      sessionId,
    );
    assert.ok(execution, "the paused turn stays registered");
    assert.equal(execution.settled, undefined);
services/runner/tests/unit/session-alive-interrupt.test.ts (1)

178-187: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the interval heartbeat and assert onInterrupted. startAliveWatchdog starts its 30-second setInterval only after the first beat. flushMicrotasks() waits 0 ms, and this test does not provide an onInterrupted callback. Therefore it never observes the later interruption, so a broken production path can still pass. Use fake timers to advance one interval, then assert onInterrupted fires while watchdog.admitted remains true.

services/runner/tests/unit/session-steer-mount-loss.test.ts (1)

457-459: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match the fake teardown markers in the assertion. makeHost().teardown emits only unmount and rmSync, so includes("teardown") is always false. This test otherwise allows a refusal regression that acquires and tears down a rival environment to pass because it checks only the original environment's destroyed count.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    assert.ok(
      !host.trace.some(
        (line) => line.includes("unmount") || line.includes("rmSync"),
      ),
      `a teardown ran during the refusal: ${host.trace.join(" | ")}`,
web/mobile/src/features/chat/Composer.tsx (1)

52-53: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reduce the new in-code comments.

Each listed block exceeds one short line. Replace it with one brief reason-only comment, or remove it when the code is self-explanatory.

  • web/mobile/src/features/chat/Composer.tsx#L52-L53: reduce the placeholder comment to one short line.
  • web/mobile/src/features/chat/LiveConversation.tsx#L150-L151: reduce the pending-task comment to one short line.
  • web/mobile/src/features/chat/LiveConversation.tsx#L307-L311: remove or reduce the JSX comment to one short line.
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx#L430-L437: reduce the refusal-recovery comment to one short line.
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx#L162-L167: reduce the error-state comment to one short line.

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

📍 Affects 4 files
  • web/mobile/src/features/chat/Composer.tsx#L52-L53 (this comment)
  • web/mobile/src/features/chat/LiveConversation.tsx#L150-L151
  • web/mobile/src/features/chat/LiveConversation.tsx#L307-L311
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx#L430-L437
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx#L162-L167

Source: Coding guidelines

web/oss/src/components/AgentChatSlice/AgentConversation.tsx (1)

440-442: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore consumed attachments on session-busy refusal.

finishSubmit calls attachments.clearAttachments(consumedUids) immediately after submit. The queue retains fileParts, but this effect restores only sent.text, so the staged files are removed and no other path restores them. Retain the consumed staged entries and pass them to attachments.restoreAttachments before the retry.

web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts (2)

86-97: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reduce new code comments to one short line each.

The web guideline has a hard one-short-line limit per comment. Move detailed rationale to documentation or encode it in focused tests.

  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts#L86-L97: replace the long recovery rationale with one short comment.
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts#L212-L213: replace the two-line recovery comment with one short comment.
  • web/packages/agenta-chat/src/model/error.ts#L49-L59: replace the admission-contract block with one short comment.
  • web/packages/agenta-chat/src/model/error.ts#L65-L72: replace the matching rationale block with one short comment.
  • web/packages/agenta-chat/src/model/error.ts#L76-L78: remove the parity-history comment or reduce it to one short line.
  • web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts#L367-L371: reduce the scenario explanation to one short line.
  • web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts#L390-L391: reduce the queue-state explanation to one short line.
  • web/packages/agenta-chat/tests/unit/model/error.test.ts#L87-L90: reduce the runner-path explanation to one short line.
📍 Affects 4 files
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts#L86-L97 (this comment)
  • web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts#L212-L213
  • web/packages/agenta-chat/src/model/error.ts#L49-L59
  • web/packages/agenta-chat/src/model/error.ts#L65-L72
  • web/packages/agenta-chat/src/model/error.ts#L76-L78
  • web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts#L367-L371
  • web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts#L390-L391
  • web/packages/agenta-chat/tests/unit/model/error.test.ts#L87-L90

Source: Coding guidelines


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

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
file='web/packages/agenta-chat/src/hooks/useAgentChatQueue.ts'
printf '%s\n' '--- focused source ---'
sed -n '1,260p' "$file"
printf '%s\n' '--- bound symbols and callers ---'
rg -n -C 3 'lastSentRef|takeLastSent|refus|release|send' "$file" web/packages/agenta-chat/src
printf '%s\n' '--- focused diff ---'
git diff --unified=30 -- "$file"

Repository: Agenta-AI/agenta

Length of output: 50373


🤖 get_repo_knowledge executed:

get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings

Length of output: 4671


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- hook consumers ---'
rg -l 'useAgentChatQueue|takeLastSent' web --glob '*.ts' --glob '*.tsx' | sort
printf '%s\n' '--- exact consumer context ---'
rg -n -C 12 'useAgentChatQueue|takeLastSent' web/packages web/apps --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- refusal classification and handlers ---'
rg -n -C 12 'SESSION_TURN_IN_USE|isSessionTurnInUse|takeLastSent|sendQueued|onError' web/packages/agenta-chat web/apps --glob '*.ts' --glob '*.tsx'

Repository: Agenta-AI/agenta

Length of output: 31616


🏁 Script executed:

#!/bin/bash
set -eu
file='web/packages/agenta-chat/src/hooks/useAgentConversation.ts'
printf '%s\n' '--- hook outline ---'
ast-grep outline "$file"
printf '%s\n' '--- queue integration ---'
sed -n '100,190p' "$file"
sed -n '450,570p' "$file"
printf '%s\n' '--- all host reclaim/error references ---'
rg -n -C 10 'takeLastSent|sendQueued|isSessionTurnInUse|SESSION_TURN_IN_USE|onError|sendMessage' web/packages/agenta-chat/src/hooks/useAgentConversation.ts web/oss/src/components/AgentChatSlice/AgentConversation.tsx

Repository: Agenta-AI/agenta

Length of output: 38981


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- OSS refusal effect and surrounding hook order ---'
sed -n '330,455p' web/oss/src/components/AgentChatSlice/AgentConversation.tsx
printf '%s\n' '--- release predicate binding ---'
rg -n -l 'canReleaseQueuedMessage' web/packages web/oss
rg -n -C 18 'canReleaseQueuedMessage' web/packages/agenta-playground web/packages/agenta-* web/oss 2>/dev/null | head -240
printf '%s\n' '--- package test setup and relevant release tests ---'
sed -n '360,425p' web/packages/agenta-chat/tests/unit/hooks/useAgentChatQueue.test.ts

Repository: Agenta-AI/agenta

Length of output: 34540


Protect refusal recovery before releasing the queue head.

When a refusal exposes status === "error" with no pending HITL interaction, the queue can release B. Its effect stores B in the single-slot lastSentRef before the OSS refusal effect calls takeLastSent, so A can be lost. Preserve recovery by send identity or reclaim A before releasing B.

web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts (1)

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

Reduce these comments to one short line each.

  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts#L1-L6: replace the reload explanation with one short comment.
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts#L10-L12: replace the storage-stub explanation with one short comment.
  • web/packages/agenta-entities/src/workflow/state/store.ts#L1469-L1473: replace the persistence overview with one short comment.
  • web/packages/agenta-entities/src/workflow/state/store.ts#L1481-L1486: replace the normalization overview with one short comment.

As per coding guidelines: “Hard rule. At most ONE short line per comment.”

📍 Affects 2 files
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts#L1-L6 (this comment)
  • web/packages/agenta-entities/tests/unit/agent-build-kit-ui-state-atom.test.ts#L10-L12
  • web/packages/agenta-entities/src/workflow/state/store.ts#L1469-L1473
  • web/packages/agenta-entities/src/workflow/state/store.ts#L1481-L1486

Source: Coding guidelines

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agent-generated Codex review, low weight.

Verdict: CHANGES NEEDED

The rebase keeps the #6496 warm-Stop/Codex-reap work and the #6500 admission checks, but their integration with durable Stop leaves two release-blocking races. I also found one retry-contract defect and confirmed the flag-off desktop response still fails its client schema.

The flag-off transport itself is preserved: desktop reaches the legacy API service through /cancel, mobile continues to use the legacy stream command, and the runner receives the heartbeat-carried interrupt. The remaining client-side issue is that desktop waits for fetchSessionStream before aborting its local stream, so a slow read delays both local feedback and either Stop request.

No focused test was run; the failures follow from the current control flow, and the existing suites are green but do not compose registry replacement with asynchronous admission or abort a turn after PAUSED has won the prompt race.


// Register only a request that passed synchronous admission validation.
if (sessionOwned) {
registerExecution({

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P1] Register only after asynchronous admission succeeds

If turn A is admitted and a second turn B arrives for the same session, this call replaces A in the one-entry registry before B's first heartbeat says admitted: false. A Stop for A during that await sees B, reports the pinned A target as not_running, and never aborts A. B then unregisters itself on refusal, leaving A running with no registry entry, so later Stops return not_held as well. Keep A registered until B actually wins admission (or make registration an admission-aware compare-and-set), and cover Stop during a refused second turn.

// still make the teardown treat the run as aborted, which DESTROYS the warm environment
// instead of parking it. Marked here rather than where the caller awaits this function,
// because that window is precisely what lies between the two.
if (stopReason !== "paused" && request.sessionId && request.turnId) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P1] Convert a Stop during pause teardown into cancellation

Leaving a paused turn unset here keeps it discoverable, but it does not keep its abort actionable. Once pause.signal has won Promise.race, stopReason is fixed to paused; a later Stop during waitForEventDrain or tool-call closure aborts a promise nobody awaits. The control channel reports stopped, while this function skips cancelHarnessTurn, returns paused, and approvalToPark deliberately ignores the aborted signal and parks the same prompt. Re-check the Stop signal during pause teardown and take the cancel path (or otherwise make the parked prompt cancellable) before reporting a terminal stopped outcome.

session_id=session_id,
)

if (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P2] Replay an idempotency key before validating current execution

An exact retry can no longer replay once the target changes. For example, the first request uses key K plus expected_execution_id=A, A settles, and B starts; retrying the same request now raises a 409 here before the DAO can return K's original command. The existing replay test omits the expected ID even though the browser-facing call supports sending both. Resolve K first (and verify its request identity), then return its stored target/state without consulting B.

requestOptions,
)
.withRawResponse()
const validated = safeParseWithLogging(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[P2] Parse the legacy response when durable Stop is disabled

With AGENTA_SESSIONS_DURABLE_STOP=false, this same endpoint intentionally returns the legacy {mode, session_id, turn_id, ...} body. Parsing it only as sessionCancelExecutionResponseSchema fails and logs a Zod error on every desktop Stop, even though the API already performed the legacy cancellation. Accept and normalize the legacy schema here, or otherwise avoid treating the documented rollback response as malformed; add the flag-off payload to the frontend test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes requested lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant