---
title: "Hooks"
id: hooks-api
description: "Hooks that run at points in the Agent's run loop and influence it by mutating State, including built-in tool result offloading."
slug: "/hooks-api"
---
## from_function
### FunctionHook
Wraps a function (or a sync/async pair) into a serializable `Hook`.
Produced by the `@hook` decorator for the single-function case. To give a hook both an optimized sync and async
path, construct it directly with both `function` and `async_function` set.
#### __init__
```python
__init__(
function: Callable[[State], None] | None = None,
async_function: Callable[[State], Awaitable[None]] | None = None,
) -> None
```
Initialize the hook with a synchronous function, an async function, or both.
**Parameters:**
- **function** (Callable\\[[State\], None\] | None) – The synchronous function invoked by `run`. Must be a regular function — coroutine functions
should be passed to `async_function` instead. Either `function` or `async_function` (or both) must be set.
- **async_function** (Callable\\[[State\], Awaitable[None]\] | None) – Optional coroutine function awaited by `run_async`. When only `async_function` is set,
`run` raises a `RuntimeError`. When only `function` is set, `run_async` calls `function`.
**Raises:**
- ValueError – If neither is set, if `function` is a coroutine function, if `async_function` is not, or
if a provided function does not declare a `State`-typed parameter.
#### run
```python
run(state: State) -> None
```
Run the synchronous function against the live `State`.
**Parameters:**
- **state** (State) – The Agent's live `State`, mutated in place by the wrapped function.
**Raises:**
- RuntimeError – If the hook only has an `async_function`; use the Agent's async run methods instead.
#### run_async
```python
run_async(state: State) -> None
```
Await the async function if set, otherwise call the synchronous function.
**Parameters:**
- **state** (State) – The Agent's live `State`, mutated in place by the wrapped function.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the hook, storing each wrapped function as an importable reference.
**Returns:**
- dict\[str, Any\] – A dictionary with the hook's type and the import paths of its sync/async functions.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> FunctionHook
```
Deserialize the hook, resolving each function from its importable reference.
**Parameters:**
- **data** (dict\[str, Any\]) – The serialized hook dictionary produced by `to_dict`.
**Returns:**
- FunctionHook – The reconstructed `FunctionHook`.
### hook
```python
hook(function: Callable[[State], None | Awaitable[None]]) -> FunctionHook
```
Wrap a function into a `Hook` the Agent can invoke during its run loop.
The decorated function receives the Agent's `State` and influences the run by mutating it in place. A coroutine
function is wrapped as the hook's async path; a regular function as its sync path. To give a single hook both
paths, construct a `FunctionHook` directly with both `function` and `async_function`.
### Usage example
```python
from haystack.components.agents import Agent
from haystack.hooks import hook
from haystack.components.agents.state import State
from haystack.dataclasses import ChatMessage
@hook
def require_save(state: State) -> None:
if state.get("tool_call_counts", {}).get("save", 0) == 0:
state.set("messages", [ChatMessage.from_system("You must call `save` before finishing.")])
state.set("continue_run", True)
agent = Agent(chat_generator=..., tools=[...], hooks={"on_exit": [require_save]})
```
**Parameters:**
- **function** (Callable\\[[State\], None | Awaitable[None]\]) – A callable taking the Agent's `State` and returning `None` (sync or async).
**Returns:**
- FunctionHook – A `FunctionHook` wrapping the function.
## protocol
### Hook
Bases: Protocol
A callable the Agent invokes at a point in its run loop, receiving the live `State`.
A hook influences the run only by mutating `State` in place. At least `messages` (the conversation),
`step_count`, `token_usage` and `tool_call_counts` are available; any additional keys defined in the Agent's
`state_schema` are available too. The same hook object can be registered under multiple hook points.
Implement this protocol directly for stateful hooks (e.g. one wrapping a component), or use the `@hook` decorator to
wrap a plain `(State) -> None` function.
A hook may additionally define `async def run_async(self, state: State) -> None` for true async behavior; when
absent, the Agent calls `run` during async runs. It is left off this protocol on purpose so sync-only hooks
don't have to implement it.
A hook may also implement the optional lifecycle methods `warm_up` / `warm_up_async` and `close` / `close_async`.
The Agent calls them from its own `warm_up` / `warm_up_async` and `close` / `close_async`, so a hook can defer
opening clients or reading credentials until warm-up and release them on close.
#### run
```python
run(state: State) -> None
```
Run the hook against the live `State`, mutating it in place.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the hook to a dictionary.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> Hook
```
Deserialize the hook from a dictionary.
## tool_result_offloading/hooks
### ToolResultOffloadHook
Offload tool results to a `ToolResultStore`, replacing them in the conversation with a compact pointer.
This `after_tool` Agent hook writes the full result to the store so the next LLM call sees a reference instead of
the full result. Register it on an `Agent` under the `after_tool` hook point. Which tools offload, and under what
condition, is controlled per tool by `offload_strategies`:
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.hooks.tool_result_offloading import (
AlwaysOffload,
FileSystemToolResultStore,
NeverOffload,
OffloadOverChars,
ToolResultOffloadHook,
)
hook = ToolResultOffloadHook(
store=FileSystemToolResultStore(root="tool_results"),
offload_strategies={
"web_search": AlwaysOffload(), # force offload
"get_time": NeverOffload(), # opt out
("read_file", "list_dir"): OffloadOverChars(4000), # tuple key: shared policy
"*": OffloadOverChars(8000), # wildcard default for any unlisted tool
},
)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[web_search, get_time, read_file, list_dir],
hooks={"after_tool": [hook]},
)
```
A key may be a single tool name, a tuple of tool names sharing one policy, or the wildcard `"*"` which applies to
any tool without a more specific entry. More specific keys win. A tool with no matching key (and no `"*"`) is not
offloaded.
Only successful, text tool output is offloaded. Error results (including `before_tool` human-in-the-loop
rejections) are always left in context. Non-text results (image or file content) are also left in context, and a
warning is logged when such a result has a matching offload policy; supporting only text is a deliberate choice
for now. Each result is offloaded at most once, even though the hook runs on every tool step.
The hook keeps no mutable state, so a single instance can be shared across concurrent runs. The constructor
`store`, however, is shared by every run that does not override it — fine for single-user or local use, but in a
multi-user server give each run its own isolated store (a per-session directory or sandbox) via `hook_context`
under the key `RESULT_STORE_CONTEXT_KEY`
(`agent.run(messages=[...], hook_context={RESULT_STORE_CONTEXT_KEY: per_request_store})`); it overrides the
constructor store for that run. Isolating the store per run keeps concurrent users from colliding on store keys or
reading each other's offloaded results — important especially when a bash/read tool is scoped to the store.
#### __init__
```python
__init__(
store: ToolResultStore,
offload_strategies: dict[str | tuple[str, ...], OffloadPolicy],
*,
preview_chars: int = 200
) -> None
```
Initialize the hook with a store and per-tool offload strategies.
**Parameters:**
- **store** (ToolResultStore) – Where offloaded results are written. Can be overridden per run via `hook_context`.
- **offload_strategies** (dict\[str | tuple\[str, ...\], OffloadPolicy\]) – Mapping of tool name (or a tuple of tool names, or the wildcard `"*"`) to the
`OffloadPolicy` that decides whether that tool's results are offloaded.
- **preview_chars** (int) – Number of leading characters of the original result to include in the pointer left in
the conversation, so the model knows roughly what was offloaded.
#### run
```python
run(state: State) -> None
```
Offload the freshly produced tool results in `state.data["messages"]` according to `offload_strategies`.
Considers only the trailing block of tool-result messages (the current step's results); earlier history is
left untouched. Offloads each of those messages its policy opts in for, and writes the rewritten conversation
back to `messages` only if at least one message changed.
Results are written to the store this run resolves to: a per-run store passed in `state`'s `hook_context`
under `RESULT_STORE_CONTEXT_KEY` if present, otherwise the store the hook was constructed with. Supply the
per-run store when calling the Agent, e.g.
`agent.run(messages=[...], hook_context={RESULT_STORE_CONTEXT_KEY: per_request_store})`. In a multi-user
server, pass an isolated store per run this way so concurrent users write to separate locations and never
read each other's results.
The hook keeps no mutable state, so a single instance is safe to share across concurrent runs; isolation
comes entirely from giving each run its own store via `hook_context`.
**Parameters:**
- **state** (State) – The Agent's live `State`. Reads the per-run store from `hook_context` and rewrites the offloaded
tool-result messages back into `messages`.
**Returns:**
- None – None. The hook mutates `state` in place.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the hook, including its store and per-tool offload strategies.
**Returns:**
- dict\[str, Any\] – A dictionary representation of the hook.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> ToolResultOffloadHook
```
Deserialize the hook, reconstructing its store and offload strategies.
**Parameters:**
- **data** (dict\[str, Any\]) – A dictionary representation produced by `to_dict`.
**Returns:**
- ToolResultOffloadHook – The deserialized `ToolResultOffloadHook`.
## tool_result_offloading/policies
### AlwaysOffload
Bases: OffloadPolicy
Offload every result of the tool it is assigned to.
#### should_offload
```python
should_offload(tool_name: str, result: str, state: State) -> bool
```
Decide whether to offload the given tool result.
**Parameters:**
- **tool_name** (str) – The name of the tool that produced the result (unused; this policy always offloads).
- **result** (str) – The tool result string (unused; this policy always offloads).
- **state** (State) – The Agent's live `State` (unused; this policy always offloads).
**Returns:**
- bool – Always True.
### NeverOffload
Bases: OffloadPolicy
Never offload; keep the tool's full result in context. Use to opt a tool out of a wildcard default.
#### should_offload
```python
should_offload(tool_name: str, result: str, state: State) -> bool
```
Decide whether to offload the given tool result.
**Parameters:**
- **tool_name** (str) – The name of the tool that produced the result (unused; this policy never offloads).
- **result** (str) – The tool result string (unused; this policy never offloads).
- **state** (State) – The Agent's live `State` (unused; this policy never offloads).
**Returns:**
- bool – Always False.
### OffloadOverChars
Bases: OffloadPolicy
Offload a result only when its string length exceeds `threshold` characters.
#### __init__
```python
__init__(threshold: int) -> None
```
Initialize the policy with its character threshold.
**Parameters:**
- **threshold** (int) – Offload the result when its length in characters is strictly greater than this value.
#### should_offload
```python
should_offload(tool_name: str, result: str, state: State) -> bool
```
Decide whether to offload the given tool result based on its length.
**Parameters:**
- **tool_name** (str) – The name of the tool that produced the result (unused; only length is considered).
- **result** (str) – The tool result string whose length is compared against the threshold.
- **state** (State) – The Agent's live `State` (unused; only length is considered).
**Returns:**
- bool – True when `result` is longer than `threshold` characters, otherwise False.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the policy, including its threshold.
**Returns:**
- dict\[str, Any\] – A dictionary representation of the policy.
## tool_result_offloading/stores
### FileSystemToolResultStore
Bases: ToolResultStore
A `ToolResultStore` that writes offloaded tool results to files under a root directory on the local file system.
```python
from haystack.hooks.tool_result_offloading import FileSystemToolResultStore
store = FileSystemToolResultStore(root="tool_results")
reference = store.write(key="search_1.txt", content="...")
store.read(reference)
```
#### __init__
```python
__init__(root: str | Path) -> None
```
Initialize the store with the root directory results are written under.
**Parameters:**
- **root** (str | Path) – Directory under which result files are written. Created on first write if it does not exist.
#### write
```python
write(*, key: str, content: str) -> str
```
Write `content` to `/`, creating parent directories, and return the file path.
The resolved target must stay within the root directory: a `key` that escapes it (e.g. containing `../` or an
absolute path) is rejected, so a tool-provided key cannot write outside the store.
**Parameters:**
- **key** (str) – Relative file name for the result within the store root.
- **content** (str) – The tool result to persist.
**Returns:**
- str – The absolute path the content was written to, as a string, for use with `read`.
**Raises:**
- ValueError – If `key` resolves to a location outside the store root.
#### read
```python
read(reference: str) -> str
```
Read back the content previously written to `reference`.
**Parameters:**
- **reference** (str) – A path returned by `write`.
**Returns:**
- str – The stored content.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the store, storing its root directory as a string.
**Returns:**
- dict\[str, Any\] – A dictionary representation of the store.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> FileSystemToolResultStore
```
Deserialize the store from a dictionary.
**Parameters:**
- **data** (dict\[str, Any\]) – A dictionary representation produced by `to_dict`.
**Returns:**
- FileSystemToolResultStore – The deserialized `FileSystemToolResultStore`.
## tool_result_offloading/types/protocol
### ToolResultStore
Bases: Protocol
A place a `ToolResultOffloadHook` writes offloaded tool results to, and reads them back from.
Implementations decide where and how the content lives (local disk, an isolated sandbox filesystem, object
storage, ...). `write` returns an opaque reference string that the Agent puts in the conversation in place of the
full result; `read` resolves that reference back to the original content.
Implement both `to_dict` and `from_dict` to make a custom store serializable; the default implementations below
cover stores whose constructor takes no arguments.
#### write
```python
write(*, key: str, content: str) -> str
```
Persist `content` under `key` and return an opaque reference to it.
**Parameters:**
- **key** (str) – A stable, per-result identifier the hook derives from the tool call (e.g. a file name).
- **content** (str) – The tool result to persist.
**Returns:**
- str – A reference string (e.g. a path or URI) that `read` can later resolve.
#### read
```python
read(reference: str) -> str
```
Return the content previously stored under `reference`.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the store to a dictionary.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> ToolResultStore
```
Deserialize the store from a dictionary.
### OffloadPolicy
Bases: Protocol
Decides, per tool result, whether the `ToolResultOffloadHook` offloads it to the store or leaves it in context.
A `ToolResultOffloadHook` maps tool names to policies, so different tools can offload under different conditions
(always, never, or a custom rule such as a size threshold).
Implement both `to_dict` and `from_dict` to make a custom policy serializable; the default implementations below
cover policies whose constructor takes no arguments.
#### should_offload
```python
should_offload(tool_name: str, result: str, state: State) -> bool
```
Return whether the given tool result should be offloaded.
**Parameters:**
- **tool_name** (str) – The name of the tool that produced the result.
- **result** (str) – The tool result as a string (the content that would otherwise stay in the conversation).
- **state** (State) – The Agent's live `State`, for policies that decide based on run context.
**Returns:**
- bool – True to offload the result to the store, False to leave it in context.
#### to_dict
```python
to_dict() -> dict[str, Any]
```
Serialize the policy to a dictionary.
#### from_dict
```python
from_dict(data: dict[str, Any]) -> OffloadPolicy
```
Deserialize the policy from a dictionary.