You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current Plugin interface (Plugin.initAgent + hook callbacks) is powerful for observing and lightly modifying agent behavior — cancelling tool calls, retrying, logging, adding tools. However, it cannot express features that need to alter the agent loop's control flow, such as human-in-the-loop interrupts.
PR #586 implements a full interrupt system, but it requires ~400 lines of changes directly inside agent.ts because the plugin system lacks the extension points needed to build it externally. Specifically, the interrupt system needs to:
Halt the agent loop mid-execution with a custom stop reason and payload
Store per-invocation state on the agent (interrupt tracking, partial tool results)
Resume execution from where it left off (skip model call, re-execute only pending tools)
Intercept tool execution flow (skip already-completed tools on resume)
None of these are possible through the current Plugin interface, which only offers initAgent(agent) and getTools().
Proposed Solution
Extend the Plugin interface and agent loop with richer extension points. There are several options, roughly ordered from minimal to comprehensive:
Option A: Minimal — HaltSignal + Plugin State (smallest change)
Add two things:
1. Plugin-scoped state on the agent
// On LocalAgent interfacegetPluginState<T>(pluginName: string): TsetPluginState<T>(pluginName: string,state: T): void
Plugins currently resort to Object.defineProperty hacks or closures to store state. A sanctioned API would make this clean and discoverable.
2. HaltSignal — a way for hooks to stop the loop
interfaceHaltSignal{stopReason: string// custom stop reason (e.g. 'interrupt')data: Record<string,unknown>// plugin-specific payload for AgentResultresumable: boolean// hint: can the caller resume?}
Hook callbacks could throw or return a HaltSignal to tell the agent loop "stop here, return this to the caller." The agent loop would catch it generically and produce an AgentResult with the custom stop reason.
This alone doesn't solve resume, but it's the minimum viable change that unblocks a class of plugins that need to pause execution.
Option B: Add Resume Path
Build on Option A with a generic resume mechanism:
// On Agentresume(pluginName: string,data: unknown): Promise<AgentResult>
When called, the agent loop would:
Look up the plugin's saved state
Let the plugin decide how to re-enter the loop (skip model call, restore partial results, etc.)
Continue execution
This requires a plugin lifecycle hook for resume:
interfacePlugin{// ... existingonResume?(agent: LocalAgent,data: unknown): ResumeDirective}interfaceResumeDirective{skipModelCall: booleanrestoreMessage?: Message// the assistant message to re-execute tools forcompletedToolResults?: ToolResultBlock[]// tools already done}
This is the most powerful but also the most complex to implement and maintain. It would enable not just interrupts but any plugin that needs to modify how tools are executed (rate limiting, caching, parallel execution strategies, etc.).
Option D: Keep interrupts in core, don't plugin-ify
The Python SDK treats interrupt as a first-class agent loop concept rather than a plugin. Given how deeply interrupts touch the loop's control flow (halt, state save, resume, tool skip), it may be more pragmatic to keep it in core and accept that some features are inherently part of the agent loop, not extensions to it.
The argument for this: the agent loop is already the orchestrator for model calls, tool execution, cancellation, structured output, and conversation management. Interrupts are arguably in the same category — a core orchestration concern, not an add-on.
Use Case
The primary motivating use case is human-in-the-loop workflows — pausing agent execution before sensitive tool calls to get human approval, then resuming. See #586 for the full implementation.
Beyond interrupts, richer plugin extension points would enable:
Custom approval gates — pause for async approval from external systems
Checkpoint/restore — save agent loop state for long-running workflows
The Python Strands SDK has interrupts as a core concept — worth considering parity
The current Plugin interface was designed for hooks + tools, which covers observation and light modification but not control flow changes
ConversationManager is already a plugin that extends the loop (via reduce) — interrupts would be a similar pattern but for tool execution rather than message management
Looking for the Strands team's perspective on which option (or combination) makes sense for the SDK's direction
Problem Statement
The current Plugin interface (
Plugin.initAgent+ hook callbacks) is powerful for observing and lightly modifying agent behavior — cancelling tool calls, retrying, logging, adding tools. However, it cannot express features that need to alter the agent loop's control flow, such as human-in-the-loop interrupts.PR #586 implements a full interrupt system, but it requires ~400 lines of changes directly inside
agent.tsbecause the plugin system lacks the extension points needed to build it externally. Specifically, the interrupt system needs to:None of these are possible through the current
Plugininterface, which only offersinitAgent(agent)andgetTools().Proposed Solution
Extend the Plugin interface and agent loop with richer extension points. There are several options, roughly ordered from minimal to comprehensive:
Option A: Minimal — HaltSignal + Plugin State (smallest change)
Add two things:
1. Plugin-scoped state on the agent
Plugins currently resort to
Object.definePropertyhacks or closures to store state. A sanctioned API would make this clean and discoverable.2. HaltSignal — a way for hooks to stop the loop
Hook callbacks could throw or return a
HaltSignalto tell the agent loop "stop here, return this to the caller." The agent loop would catch it generically and produce anAgentResultwith the custom stop reason.This alone doesn't solve resume, but it's the minimum viable change that unblocks a class of plugins that need to pause execution.
Option B: Add Resume Path
Build on Option A with a generic resume mechanism:
When called, the agent loop would:
This requires a plugin lifecycle hook for resume:
Option C: Comprehensive — Tool Execution Middleware
The most flexible approach. Let plugins wrap the tool execution pipeline:
This is the most powerful but also the most complex to implement and maintain. It would enable not just interrupts but any plugin that needs to modify how tools are executed (rate limiting, caching, parallel execution strategies, etc.).
Option D: Keep interrupts in core, don't plugin-ify
The Python SDK treats
interruptas a first-class agent loop concept rather than a plugin. Given how deeply interrupts touch the loop's control flow (halt, state save, resume, tool skip), it may be more pragmatic to keep it in core and accept that some features are inherently part of the agent loop, not extensions to it.The argument for this: the agent loop is already the orchestrator for model calls, tool execution, cancellation, structured output, and conversation management. Interrupts are arguably in the same category — a core orchestration concern, not an add-on.
Use Case
The primary motivating use case is human-in-the-loop workflows — pausing agent execution before sensitive tool calls to get human approval, then resuming. See #586 for the full implementation.
Beyond interrupts, richer plugin extension points would enable:
Alternative Solutions
The current interrupt PR (#586) works by modifying
agent.tsdirectly. This is functional but means:The plugin approach trades upfront framework complexity for long-term extensibility.
Additional Context
ConversationManageris already a plugin that extends the loop (viareduce) — interrupts would be a similar pattern but for tool execution rather than message management