Skip to content

Interrupts in Multi-Agent Systems

Interrupts work across multi-agent orchestration, so you can pause a swarm or graph for human approval or input the same way you pause a single agent. Raise an interrupt from a BeforeNodeCallEvent hook that runs before each node, or from within the nodes themselves. Session management works here too, so you can persist an interrupted multi-agent run and resume it later.

The interfaces mirror those used for single-agent interrupts. Read that page first if you have not built an interrupt/resume loop before.

An agent used as a tool with Agents as Tools propagates its interrupts to the orchestrator, where they are raised and answered like any other interrupt. The interrupt ids are scoped to the orchestrator’s tool call; treat them as opaque.

A Swarm is a collaborative agent orchestration system where multiple agents work together as a team to solve complex tasks. The following example interrupts a swarm invocation through a BeforeNodeCallEvent hook.

import json
from strands import Agent
from strands.hooks import BeforeNodeCallEvent, HookProvider, HookRegistry
from strands.multiagent import Swarm, Status
class ApprovalHook(HookProvider):
def __init__(self, app_name: str) -> None:
self.app_name = app_name
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(BeforeNodeCallEvent, self.approve)
def approve(self, event: BeforeNodeCallEvent) -> None:
if event.node_id != "cleanup":
return
approval = event.interrupt(f"{self.app_name}-approval", reason={"resources": "example"})
if approval.lower() != "y":
event.cancel_node = "User denied permission to cleanup resources"
swarm = Swarm(
[
Agent(name="cleanup", system_prompt="You clean up resources older than 5 days.", callback_handler=None),
],
hooks=[ApprovalHook("myapp")],
)
result = swarm("Clean up my resources")
while result.status == Status.INTERRUPTED:
responses = []
for interrupt in result.interrupts:
if interrupt.name == "myapp-approval":
user_input = input(f"Do you want to cleanup {interrupt.reason['resources']} (y/N): ")
responses.append({
"interruptResponse": {
"interruptId": interrupt.id,
"response": user_input,
},
})
result = swarm(responses)
print(f"MESSAGE: {json.dumps(result.results['cleanup'].result.message, indent=2)}")

Swarms also support interrupts raised from within the nodes themselves, following any of the single-agent interrupt patterns.

  • event.interrupt - Raises an interrupt with a unique name and optional reason
    • The name must be unique across all interrupt calls configured on the BeforeNodeCallEvent. In the example above, we demonstrate using app_name to namespace the interrupt call. This is particularly helpful if you plan to vend your hooks to other users.
    • You can assign additional context for raising the interrupt to the reason field. Note, the reason must be JSON-serializable.
  • result.status - Check if the swarm stopped due to Status.INTERRUPTED
  • result.interrupts - List of interrupts that were raised
    • Each interrupt contains the user provided name and reason, along with an instance id.
  • interruptResponse - Content block type for configuring the interrupt responses.
    • Each response is uniquely identified by their interrupt’s id and will be returned from the associated interrupt call when invoked the second time around. Note, the response must be JSON-serializable.
  • event.cancel_node - Cancel node execution based on interrupt response
    • You can either set cancel_node to True or provide a custom cancellation message.

Strands enforces the following rules for interrupts in swarm:

  • All hooks configured on the interrupted event will execute
  • All hooks configured on the interrupted event are allowed to raise an interrupt
  • A single hook can raise multiple interrupts but only one at a time
    • In other words, within a single hook, you can interrupt, respond to that interrupt, and then proceed to interrupt again.
  • A single node can raise multiple interrupts following any of the single-agent interrupt patterns outlined above.

A Graph is a deterministic agent orchestration system based on a directed graph, where agents are nodes executed according to edge dependencies. The following example interrupts a graph invocation through a BeforeNodeCallEvent hook.

import json
from strands import Agent
from strands.hooks import BeforeNodeCallEvent, HookProvider, HookRegistry
from strands.multiagent import GraphBuilder, Status
class ApprovalHook(HookProvider):
def __init__(self, app_name: str) -> None:
self.app_name = app_name
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(BeforeNodeCallEvent, self.approve)
def approve(self, event: BeforeNodeCallEvent) -> None:
if event.node_id != "cleanup":
return
approval = event.interrupt(f"{self.app_name}-approval", reason={"resources": "example"})
if approval.lower() != "y":
event.cancel_node = "User denied permission to cleanup resources"
inspector_agent = Agent(name="inspector", system_prompt="You inspect resources.", callback_handler=None)
cleanup_agent = Agent(name="cleanup", system_prompt="You clean up resources older than 5 days.", callback_handler=None)
builder = GraphBuilder()
builder.add_node(inspector_agent, "inspector")
builder.add_node(cleanup_agent, "cleanup")
builder.add_edge("inspector", "cleanup")
builder.set_entry_point("inspector")
builder.set_hook_providers([ApprovalHook("myapp")])
graph = builder.build()
result = graph("Inspect and clean up my resources")
while result.status == Status.INTERRUPTED:
responses = []
for interrupt in result.interrupts:
if interrupt.name == "myapp-approval":
user_input = input(f"Do you want to cleanup {interrupt.reason['resources']} (y/N): ")
responses.append({
"interruptResponse": {
"interruptId": interrupt.id,
"response": user_input,
},
})
result = graph(responses)
print(f"MESSAGE: {json.dumps(result.results['cleanup'].result.message, indent=2)}")

Graphs also support interrupts raised from within the nodes themselves, following any of the single-agent interrupt patterns.

  • event.interrupt - Raises an interrupt with a unique name and optional reason
    • The name must be unique across all interrupt calls configured on the BeforeNodeCallEvent. In the example above, we demonstrate using app_name to namespace the interrupt call. This is particularly helpful if you plan to vend your hooks to other users.
    • You can assign additional context for raising the interrupt to the reason field. Note, the reason must be JSON-serializable.
  • result.status - Check if the graph stopped due to Status.INTERRUPTED
  • result.interrupts - List of interrupts that were raised
    • Each interrupt contains the user provided name and reason, along with an instance id.
  • interruptResponse - Content block type for configuring the interrupt responses
    • Each response is uniquely identified by their interrupt’s id and will be returned from the associated interrupt call when invoked the second time around. Note, the response must be JSON-serializable.
  • event.cancel_node - Cancel node execution based on interrupt response
    • You can either set cancel_node to True or provide a custom cancellation message.

Strands enforces the following rules for interrupts in graph:

  • All hooks configured on the interrupted event will execute
  • All hooks configured on the interrupted event are allowed to raise an interrupt
  • A single hook can raise multiple interrupts but only one at a time
    • In other words, within a single hook, you can interrupt, respond to that interrupt, and then proceed to interrupt again.
  • A single node can raise multiple interrupts following any of the single-agent interrupt patterns outlined above
  • All nodes running concurrently will execute
  • All nodes running concurrently are interruptible