Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions src/content/docs/user-guide/concepts/agents/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).

</Tab>
<Tab label="TypeScript">

Expand Down Expand Up @@ -931,6 +935,137 @@ result = agent("Look up the weather")
</Tab>
</Tabs>

### 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:

<Tabs>
<Tab label="Python">

```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")
```
</Tab>
<Tab label="TypeScript">

```ts
// This feature is not yet available in TypeScript SDK
```
</Tab>
</Tabs>

You can also use `resume` to chain multiple re-invocations. Make sure to include a termination condition to avoid infinite loops:

<Tabs>
<Tab label="Python">

```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")
```
</Tab>
<Tab label="TypeScript">

```ts
// This feature is not yet available in TypeScript SDK
```
</Tab>
</Tabs>

#### 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`.

<Tabs>
<Tab label="Python">

```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")
```
</Tab>
<Tab label="TypeScript">

```ts
// This feature is not yet available in TypeScript SDK
```
</Tab>
</Tabs>

## 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:
Expand Down