feat(strands): bridge native Strands interrupts to AG-UI interrupt round-trip - #2286
Open
ciolo wants to merge 2 commits into
Open
feat(strands): bridge native Strands interrupts to AG-UI interrupt round-trip#2286ciolo wants to merge 2 commits into
ciolo wants to merge 2 commits into
Conversation
1 task
…und-trip
Translate native Strands `tool_context.interrupt()` pauses into the AG-UI
interrupt lifecycle so human-in-the-loop flows work over the protocol:
- Consume `RunAgentInput.resume`, building the Strands resume prompt
`[{"interruptResponse": {"interruptId", "response"}}]`; `status="cancelled"`
resumes with the `INTERRUPT_CANCELLED` denial sentinel.
- Detect a paused run after the stream loop via the terminal `AgentResult`
(`stop_reason == "interrupt"`), falling back to the agent's
`_interrupt_state` when the result event is consumed by the early-break path.
- Emit `RunFinishedInterruptOutcome` with one AG-UI `Interrupt` per Strands
interrupt (name -> categorical `reason`, original reason under
`metadata.strands_reason`); non-interrupt runs still finish bare.
- Bump `ag-ui-protocol` floor to >=0.1.19 (ships the interrupt types).
- Document the HITL round-trip and the durable `SessionManager` requirement
for stateless deployments; add unit tests for all four behaviors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ciolo
force-pushed
the
feat/strands-native-interrupts
branch
from
July 30, 2026 10:10
269a418 to
062cb45
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2205
Summary
Bridges native Strands
tool_context.interrupt()pauses to the AG-UI interrupt lifecycle, so human-in-the-loop flows work over the protocol. Previously a native interrupt either surfaced asRUN_ERRORor silently finished as a completed run, and a client'sresumepayload was dropped.This is a wrapper-only change in
integrations/aws-strands/python/— the AG-UI protocol types it relies on already ship inag-ui-protocol0.1.19.I filed #2205 ~2 weeks ago and pinged
#-💎-contributingon Discord; opening this PR to move the discussion forward. Happy to adjust scope or approach.Changes
RunAgentInput.resume— builds the Strands resume prompt[{"interruptResponse": {"interruptId", "response"}}]and drivesstream_asyncwith it (takes precedence over other stream paths, since a resume run carries no fresh prompt).status="cancelled"resumes with the documented denial sentinelINTERRUPT_CANCELLED = {"cancelled": True}.AgentResult(stop_reason == "interrupt"), captured before thecomplete/force_stopearly-break so it's never dropped; falls back to the agent's_interrupt_state.activatedif the result event was consumed.RunFinishedInterruptOutcome— one AG-UIInterruptper Strands interrupt. The Strands interrupt name maps to the categorical AG-UIreason; the free-form Strands reason object is preserved undermetadata.strands_reason. Non-interrupt runs still finish bare (no behavior change).ag-ui-protocolfloor to>=0.1.19(ships the interrupt types) and re-lock.SessionManagerrequirement for stateless / multi-container deployments (the in-memory per-thread cache only preserves interrupt state within one process).Test plan
tests/test_interrupt.py(6 cases): pause → interrupt outcome, state-fallback detection, no-interrupt-finishes-bare, resolved-resume prompt shape, cancelled sentinel, multi-entry resume.uv run pytest tests/→ 182 passed, 2 skipped.human_in_the_loopexample not included in this PR — happy to add if maintainers want it.🤖 Generated with Claude Code