Skip to content

[fix] Resume a gated turn under the config it was running (10/12) - #5689

Draft
ardaerzin wants to merge 8 commits into
feat/sessions-watch-and-livenessfrom
feat/effective-turn-config
Draft

[fix] Resume a gated turn under the config it was running (10/12)#5689
ardaerzin wants to merge 8 commits into
feat/sessions-watch-and-livenessfrom
feat/effective-turn-config

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

An approval answered from a phone could resume the turn under the wrong configuration. The gate carried references to the workflow, so the resume hydrated the referenced variant's HEAD revision rather than the config the paused turn was actually running. If the agent had been invoked with a draft config, or the variant moved on, the resumed turn ran as a different agent than the one you approved.

This is one contract across three tiers, which is why it is one PR: splitting it would leave a wire field written by nobody or read by nobody.

Changes

The SDK stamps the post-hydration config it is about to run onto the /run wire as effectiveParameters (session runs only, credential-stripped, capped at 64KB). The runner echoes it onto the interaction row of any gate that turn parks. The API replays it as the resume's data.parameters, which suppresses reference hydration server-side and reproduces the turn, tool permissions included.

Also fixed here: the SDK hydrated references even when the caller had sent an inline config, so a caller-supplied configuration was silently discarded. Hydration is now decided from what the caller actually sent.

Tests / notes

  • The wire contract is mirrored by hand between protocol.ts and wire.py and pinned by shared golden fixtures; both sides' contract tests are updated together.
  • Rows written before this field exists carry no parameters and resume via references exactly as before.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 3, 2026 10:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 687905c1-c224-44fd-b9c6-94655a671d7a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Approval resumes now preserve the original turn configuration, including model, instructions, and tool permissions.
    • Approval actions respond directly to individual pending interactions, with support for approving all or selected gates.
    • Sensitive configuration values are redacted and oversized configurations are safely omitted.
    • Interaction conflicts are detected and handled gracefully.
  • Bug Fixes

    • Legacy interactions remain compatible through reference-based configuration hydration.
    • Resumed interactions no longer lose their effective configuration.

Walkthrough

The change carries effective session configuration from SDK request hydration through runner interaction records and approval resumes. It adds redaction and size limits, preserves legacy references-only behavior, and updates web approval handling to answer interactions directly.

Changes

Effective configuration capture

Layer / File(s) Summary
SDK configuration stamping
sdks/python/agenta/sdk/agents/..., sdks/python/agenta/sdk/middlewares/..., docs/design/agenta-mobile/plans/...
The SDK stores post-hydration configuration, hydrates references before defaults, redacts credential-shaped values, and omits empty, invalid, or oversized snapshots.
SDK propagation validation
sdks/python/oss/tests/pytest/...
Tests cover session-only wire emission, compatibility, redaction, size limits, hydration behavior, and backend propagation.

Runner and API replay

Layer / File(s) Summary
Runner interaction persistence
services/runner/src/protocol.ts, services/runner/src/sessions/interactions.ts, services/runner/src/engines/sandbox_agent/run-turn.ts, services/runner/tests/unit/...
The runner carries effectiveParameters into interaction records without changing configFingerprint.
API persistence and resume
api/oss/src/core/sessions/interactions/..., api/oss/src/tasks/asyncio/..., api/oss/tests/pytest/...
Approval interactions persist optional parameters. Resume requests forward them while legacy rows continue using references only.

Web approval handling

Layer / File(s) Summary
Web approval response flow
web/packages/agenta-chat/..., web/packages/agenta-entities/..., web/mobile/src/features/chat/..., web/mobile/tests/unit/...
Schemas and resume requests accept stamped parameters. Mobile approval actions re-query interactions and answer each target directly, tolerating conflicts. The previous approval stamping and authorization helpers were removed.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SDK
  participant Runner
  participant InteractionStore
  participant ApprovalAPI
  participant MobileClient
  SDK->>Runner: Send session run with effectiveParameters
  Runner->>InteractionStore: Persist interaction parameters
  MobileClient->>ApprovalAPI: Respond to approval interaction
  ApprovalAPI->>InteractionStore: Read stored parameters
  ApprovalAPI->>Runner: Resume with parameters and references
  Runner-->>ApprovalAPI: Complete resumed turn
  ApprovalAPI-->>MobileClient: Return interaction response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: gated turns resume under the configuration used when they paused.
Description check ✅ Passed The description directly explains the configuration-resume bug, the cross-tier fix, backward compatibility, and related tests.
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.
✨ 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/effective-turn-config

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.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from c4025ac to 0cae609 Compare August 3, 2026 15:13
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from 6101ac7 to f957a88 Compare August 3, 2026 15:13
@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from 0cae609 to e46be44 Compare August 3, 2026 16:14
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from f957a88 to f34d0e1 Compare August 3, 2026 16:14
@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from e46be44 to 9d900a8 Compare August 3, 2026 16:47
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from f34d0e1 to 4223629 Compare August 3, 2026 16:47
@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from 9d900a8 to 5321274 Compare August 3, 2026 17:34
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from 4223629 to fed6233 Compare August 3, 2026 17:34
@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from 5321274 to 1c4c784 Compare August 3, 2026 17:54
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from fed6233 to 0e3c2cb Compare August 3, 2026 17:54
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
web/mobile/src/features/chat/useApprovalActions.ts (1)

69-105: 📐 Maintainability & Code Quality | 🔵 Trivial

Add unit test coverage for the submit conflict-handling path.

approvalTargets.test.ts covers only the pure selectApprovalTargets selector. The new logic in submit — tolerating isInteractionConflict, counting answered, and re-arming to idle when every target was already answered — is not covered by any test in this batch. This logic drives the mobile approval flow's resilience to already-answered gates.

Add a test (with queryInteractions and respondInteraction mocked) that exercises: all targets succeed, all targets conflict (re-arm to idle immediately), and a mixed success/conflict/non-conflict-failure case.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dafe74b-d5cb-4dd4-bf47-b9e5ebb3215b

📥 Commits

Reviewing files that changed from the base of the PR and between 1c4c784 and 0e3c2cb.

📒 Files selected for processing (39)
  • api/oss/src/core/sessions/interactions/dtos.py
  • api/oss/src/tasks/asyncio/sessions/interactions_dispatcher.py
  • api/oss/tests/pytest/unit/sessions/test_interactions_dispatcher.py
  • docs/design/agenta-mobile/plans/2026-07-27-mobile-approvals-steering.md
  • docs/design/agenta-mobile/plans/2026-07-29-effective-turn-config.md
  • sdks/python/agenta/sdk/agents/adapters/local.py
  • sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py
  • sdks/python/agenta/sdk/agents/dtos.py
  • sdks/python/agenta/sdk/agents/handler.py
  • sdks/python/agenta/sdk/agents/interfaces.py
  • sdks/python/agenta/sdk/agents/utils/effective_config.py
  • sdks/python/agenta/sdk/agents/utils/wire.py
  • sdks/python/agenta/sdk/agents/wire_models.py
  • sdks/python/agenta/sdk/middlewares/running/resolver.py
  • sdks/python/oss/tests/pytest/integration/agents/_fake_runner_backend.py
  • sdks/python/oss/tests/pytest/unit/agents/conftest.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/run_request.pi_core.json
  • sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • sdks/python/oss/tests/pytest/unit/test_batch_fold_stream_contract_routing.py
  • sdks/python/oss/tests/pytest/unit/test_invoke_real_handlers_negotiation_routing.py
  • sdks/python/oss/tests/pytest/utils/test_resolver_middleware.py
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/protocol.ts
  • services/runner/src/sessions/interactions.ts
  • services/runner/tests/unit/interactions-parameters.test.ts
  • services/runner/tests/unit/wire-contract.test.ts
  • web/mobile/src/features/chat/approvalStamp.ts
  • web/mobile/src/features/chat/approvalTargets.ts
  • web/mobile/src/features/chat/useApprovalActions.ts
  • web/mobile/src/lib/auth.ts
  • web/mobile/tests/unit/approvalStamp.test.ts
  • web/mobile/tests/unit/approvalTargets.test.ts
  • web/packages/agenta-chat/src/transport/agentResumeRequest.ts
  • web/packages/agenta-chat/tests/unit/transport/agentResumeRequest.test.ts
  • web/packages/agenta-entities/src/session/api/api.ts
  • web/packages/agenta-entities/src/session/core/schema.ts
  • web/packages/agenta-entities/src/session/index.ts
  • web/packages/agenta-entities/tests/unit/session-interaction-schema.test.ts
💤 Files with no reviewable changes (3)
  • web/mobile/src/lib/auth.ts
  • web/mobile/tests/unit/approvalStamp.test.ts
  • web/mobile/src/features/chat/approvalStamp.ts

Comment thread docs/design/agenta-mobile/plans/2026-07-29-effective-turn-config.md
@ardaerzin
ardaerzin force-pushed the feat/sessions-watch-and-liveness branch from 1c4c784 to 551dc6f Compare August 3, 2026 18:26
@ardaerzin
ardaerzin force-pushed the feat/effective-turn-config branch from 0e3c2cb to 86c74ff Compare August 3, 2026 18:26
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

A references-only /invoke (no data.parameters, no data.revision) never
hydrated its references, so the run silently used the service's registered
default configuration instead of the referenced revision's. This is the
mobile resume path; desktop was unaffected because it always sends inline
data.parameters.

The hydration gate consulted the revision returned by resolve_revision,
which falls back to RunningContext.revision — pre-seeded by the decorator
with the registered default config. That revision is always populated for
the agent builtin, so the gate read "already configured" and skipped
hydration. seed_empty_parameters_from_configuration also ran before the
gate, so it could never have observed empty parameters anyway.

Decide hydration purely from caller-supplied config (data.parameters or
data.revision), and seed the registered default only after a hydration
attempt has been made and come back empty, so it stays a fallback rather
than a pre-emption.
Grounded in two live experiments on the EE dev stack:

- A dirty-config run's session_interactions row carries only
  workflow + workflow_variant references (no workflow_revision), so a
  references-only resume hydrates the variant HEAD, not the draft the
  turn ran under. A committed run's revision reference is pinned and is
  therefore already immune to later commits.
- A warm approval resume keeps the sandbox's acquire-time model and
  secrets while re-reading the permission map from the incoming
  (hydrated, committed) request -- a split brain where the approval UI
  enforced one policy and the resumed turn enforces another. Cold replay
  runs the committed config end to end against a draft transcript.

Recommends stamping the effective parameters on the interaction row at
gate creation (SDK emits, runner echoes, API replays inline), which needs
no migration, with a detect-and-defer fallback for pre-change rows.
A HITL gate answered from a client that cannot reproduce the turn's config (mobile, the
M2 dispatcher) resumes references-only, so the SDK hydrates the referenced variant's HEAD
revision instead of what the gated turn was running - wrong model, wrong instructions, and
wrong tool permissions on a dirty run.

The SDK is the only component holding the post-hydration config, so it emits it: the
handler carries its resolved data.parameters onto SessionConfig, and request_to_wire puts
it on the wire as the opaque effectiveParameters. Emitted only for a session run (nothing
else can park a gate), so an ad-hoc run's payload stays byte-identical to the golden
contract. Redacted (an MCP connection's static headers are the one place the schema
permits a raw credential; the vault-key refs survive) and capped at 64 KB, dropped whole
with a log line rather than truncated into invalid JSON.

The runner echoes the blob onto the interaction row; the answering client replays it as
data.parameters, which suppresses hydration and reproduces the turn.
The runner writes the durable row at the exact moment a gate parks, so it is where the
turn's config has to be recorded - but it had no access to it (there was no parameters
field on the /run wire at all). The SDK now stamps effectiveParameters; this echoes it
verbatim into the row's data.parameters, alongside data.request and data.references.

Opaque by design: the runner never reads inside the blob and derives no behavior from it.
It is deliberately NOT in configFingerprint - that hash decides warm resume vs cold replay,
and the blob is a projection of fields already hashed, so including it would let a cosmetic
serialization change evict every warm session. A turn with no stamped config omits the KEY
(not null, not {}): a legacy row keeps its exact shape, and an empty inline config would
still suppress hydration server-side and resume a toolless agent.
Two links in the same chain. The DTO: SessionInteractionData is a closed pydantic model
with the default extra="ignore", and the postgres mapping round-trips through it on write
and on read even though data is a schemaless json column - so the runner's new
data.parameters was being dropped twice with no error anywhere. Declaring the field is the
whole fix; no migration.

The dispatcher: when the row carries a config, send it inline on the resume. The SDK
resolver decides hydration purely from what the caller sent, so inline parameters suppress
it and the run continues under the config the gate was raised against instead of the
referenced variant's HEAD revision. References still ride along as attribution. A row
written before the runner stamped configs has none and produces the byte-identical
references-only body this dispatcher has always sent.
Records what landed on the backend lanes of the effective-turn-config plan and notes in the
approvals round-trip that a pre-change row (or one over the stamp cap) is still answerable,
degrading to reference hydration.
The lite resume answered a HITL gate with a references-only body, so the SDK
hydrated the referenced variant's HEAD revision. For a dirty run that is a
different model and — the security-relevant half — a different tool-permission
map than the gate was approved under.

The runner now stamps the turn's post-hydration config onto the interaction row
as data.parameters. Read it off the same row the references come from and send
it inline, which suppresses hydration and reproduces the turn exactly.

Emit the key ONLY when the stamped config is a non-empty object: an empty {}
also suppresses hydration and would run an unconfigured agent. Rows without it
(legacy, over-cap, pre-stamping runner) keep today's references-only path.

Declare parameters on the interaction zod schema too — objects strip unknown
keys by default, so an undeclared field would be silently dropped and the fix
would no-op with everything green.
The phone built its own /invoke resume from a stamped records replay. That lands as a
NEW turn (the keepalive trips approval-mismatch (history) -> evict + cold), so the parked
gate is never matched and the interaction row stays pending — the desktop keeps showing
"Approval needed to continue" even after the tool ran.

Call POST /sessions/interactions/{id}/respond instead: the backend CAS-flips the row to
responded and the interactions worker rebuilds the history server-side and replays the
gate's stamped effective config, so the resume lands warm. Approve-all fans out one
respond per pending gate; a 409 (already answered) settles to idle instead of erroring.

respondInteraction now throws instead of swallowing the failure — a mutation's caller has
to tell a real failure from an already-answered gate (isInteractionConflict).

Drops the mobile-only invoke plumbing: approvalStamp.ts and the invoke bearer header.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant