diff --git a/src/content/docs/user-guide/concepts/agents/hooks.mdx b/src/content/docs/user-guide/concepts/agents/hooks.mdx index 71d296f99..6bb9c6466 100644 --- a/src/content/docs/user-guide/concepts/agents/hooks.mdx +++ b/src/content/docs/user-guide/concepts/agents/hooks.mdx @@ -311,6 +311,10 @@ Most event properties are read-only to prevent unintended modifications. However - [`AfterToolCallEvent`](@api/python/strands.hooks.events#AfterToolCallEvent) - `result` - Modify the tool result. See [Result Modification](#result-modification). - `retry` - Request a retry of the tool invocation. See [Tool Call Retry](#tool-call-retry). + +- [`AfterInvocationEvent`](@api/python/strands.hooks.events#AfterInvocationEvent) + - `resume` - Trigger a follow-up agent invocation with new input. See [Invocation resume](#invocation-resume). + @@ -931,6 +935,137 @@ result = agent("Look up the weather") +### Invocation resume + +The `AfterInvocationEvent.resume` property enables a hook to trigger a follow-up agent invocation after the current one completes. When you set `resume` to any valid agent input (a string, content blocks, or messages), the agent automatically re-invokes itself with that input instead of returning to the caller. This starts a full new invocation cycle, including firing `BeforeInvocationEvent`. + +This is useful for building autonomous looping patterns where the agent continues processing based on its previous result—for example, re-evaluating after tool execution, injecting additional context, or implementing multi-step workflows within a single call. + +:::note[Resume input types] +The `resume` value accepts any valid `AgentInput`: a string, a list of content blocks, a list of messages, or interrupt responses. When the agent is in an interrupt state, you must provide interrupt responses (not a plain string) to resume correctly. +::: + +The following example checks the agent result and triggers one follow-up invocation to ask the model to summarize its work: + + + + +```python +from strands import Agent +from strands.hooks import AfterInvocationEvent + +resume_count = 0 + +async def summarize_after_tools(event: AfterInvocationEvent): + """Resume once to ask the model to summarize its work.""" + global resume_count + if resume_count == 0 and event.result and event.result.stop_reason == "end_turn": + resume_count += 1 + event.resume = "Now summarize what you just did in one sentence." + +agent = Agent() +agent.add_hook(summarize_after_tools) + +# The agent processes the initial request, then automatically +# performs a second invocation to generate the summary +result = agent("Look up the weather in Seattle") +``` + + + +```ts +// This feature is not yet available in TypeScript SDK +``` + + + +You can also use `resume` to chain multiple re-invocations. Make sure to include a termination condition to avoid infinite loops: + + + + +```python +from strands import Agent +from strands.hooks import AfterInvocationEvent + +MAX_ITERATIONS = 3 +iteration = 0 + +async def iterative_refinement(event: AfterInvocationEvent): + """Re-invoke the agent up to MAX_ITERATIONS times for iterative refinement.""" + global iteration + if iteration < MAX_ITERATIONS and event.result: + iteration += 1 + event.resume = f"Review your previous response and improve it. Iteration {iteration} of {MAX_ITERATIONS}." + +agent = Agent() +agent.add_hook(iterative_refinement) + +result = agent("Draft a haiku about programming") +``` + + + +```ts +// This feature is not yet available in TypeScript SDK +``` + + + +#### Handling interrupts with resume + +The `resume` property integrates with the [interrupt](../../tools/index.md) system. When an agent invocation ends because of an interrupt, a hook can automatically handle the interrupt by resuming with interrupt responses. This avoids returning the interrupt to the caller. + +When the agent is in an interrupt state, you must resume with a list of `interruptResponse` objects. Passing a plain string raises a `TypeError`. + + + + +```python +from strands import Agent, tool +from strands.hooks import AfterInvocationEvent, BeforeToolCallEvent + +@tool +def send_email(to: str, body: str) -> str: + """Send an email. + + Args: + to: Recipient address. + body: Email body. + """ + return f"Email sent to {to}" + +def require_approval(event: BeforeToolCallEvent): + """Interrupt before sending emails to require approval.""" + if event.tool_use["name"] == "send_email": + event.interrupt("email_approval", reason="Approve this email?") + +async def auto_approve(event: AfterInvocationEvent): + """Automatically approve all interrupted tool calls.""" + if event.result and event.result.stop_reason == "interrupt": + responses = [ + {"interruptResponse": {"interruptId": intr.id, "response": "approved"}} + for intr in event.result.interrupts + ] + event.resume = responses + +agent = Agent(tools=[send_email]) +agent.add_hook(require_approval) +agent.add_hook(auto_approve) + +# The interrupt is handled automatically by the hook— +# the caller receives the final result directly +result = agent("Send an email to alice@example.com saying hello") +``` + + + +```ts +// This feature is not yet available in TypeScript SDK +``` + + + ## HookProvider Protocol For advanced use cases, you can implement the `HookProvider` protocol to create objects that register multiple callbacks at once. This is useful when building reusable hook collections without the full plugin infrastructure: