Skip to content

docs: add Agent Hooks page and recast Human-in-the-Loop as a before_tool hook - #11878

Merged
julian-risch merged 6 commits into
mainfrom
docs/v3-simple-doc-updates-6
Jul 10, 2026
Merged

docs: add Agent Hooks page and recast Human-in-the-Loop as a before_tool hook#11878
julian-risch merged 6 commits into
mainfrom
docs/v3-simple-doc-updates-6

Conversation

@julian-risch

@julian-risch julian-risch commented Jul 5, 2026

Copy link
Copy Markdown
Member

Related Issues

Proposed Changes:

  • feat: Add Agent Hooks #11747 and feat: add after_tool hook needed for tool result offloading #11843 — new "Hooks" page under Agents: the hooks dict on Agent, the four hook points and their run-loop semantics, hook-point validation incl. allowed_hook_points, the reserved state keys (continue_run, tools, hook_context), the @hook decorator (multi-hook example), class-based hooks with warm_up/close lifecycle and to_dict/from_dict serialization (LLM-judge on_exit example), and the ready-made ConfirmationHook and ToolResultOffloadHook.
  • feat!: Recast HITL as before tool hook #11831 — frame HITL as one application of hooks: ConfirmationHook (tuple-key and "*"-wildcard strategy mapping) replaces the removed confirmation_strategies Agent parameter in all examples; the Hayhooks/Open WebUI section documents the hook_context run argument and how ConfirmationHook forwards it to each strategy's run() as confirmation_strategy_context; notes that strategies see only the model-requested tool arguments. The Agent page swaps confirmation_strategies for hooks and adds hook_context to the runtime arguments.
  • multi-agent-systems.mdx and LangGraph migration page: serialization YAML examples regenerated from actual Agent.to_dict() output (hooks, tool_concurrency_limit, tool_streaming_callback_passthrough), and the removed tool_invoker_kwargs replaced everywhere.
  • Misc: after_tool also fires when a before_tool hook strips the pending tool calls, run-metadata keys documented as reserved with a link to State, close() added to the lifecycle example, and Hooks API-reference links added.

How did you test it?

  • Executed Agent run with MockChatGenerator and one hook per hook point: firing counts per hook point, hook_context/tools readable from state, ValueError on unknown hook points, allowed_hook_points failing fast, tuple/wildcard strategy keys, and confirmation_strategies/confirmation_strategy_context gone from the Agent API. YAML examples regenerated from real Agent.to_dict() output.

Notes for the reviewer

Checklist

🤖 Generated with Claude Code

…ool hook

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
haystack-docs Ready Ready Preview, Comment Jul 9, 2026 6:56pm

Request Review


- `tools`: Pass a list of `Tool`/`Toolset` objects, or a list of tool name strings to select a subset of the agent's configured tools for this run.
- `generation_kwargs`: Additional keyword arguments forwarded to the LLM, overriding any set at init time (e.g. `{“temperature”: 0.2}`).
- `hook_context`: A dict of request-scoped resources made available to [hooks](./hooks.mdx) via `state.get("hook_context")` — for example, a user ID or a WebSocket connection.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is still in draft but want to catch this now. The recommended pattern for getting hook_context is state.data["hook_context"] or state.data.get("hook_context"). The plain state.get method does a deep copy of the object before handing it over which will often fail for the types of items stored in this dict (e.g. a WebSocket)

@sjrl
sjrl changed the base branch from v3 to main July 8, 2026 13:21
State.get returns a deep copy, which often fails for the resources
stored in hook_context (e.g. a WebSocket). Addresses review feedback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

- `before_llm`: Runs before each chat-generator call.
- `before_tool`: Runs after the model requests tool calls, before any tools run. After these hooks run, the Agent re-reads the current last message from `state["messages"]`. If that message contains tool calls, those calls are executed. If it does not, no tools run for that step, no tool-based exit condition is triggered, and the Agent loops back to the next LLM call unless `max_agent_steps` has been reached.
- `after_tool`: Runs after tools execute, once their result messages are in `state["messages"]`, before the exit-condition check and the next LLM call. Use it to rewrite the freshly produced tool-result messages — for example, to offload, redact, truncate, or summarize results. It does not run on the plain-text exit step, where no tools run.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use a programmatically correct statement for access so state.data["messages"] or state.get("messages")

Haystack ships two ready-made hooks:

- `ConfirmationHook`: A `before_tool` hook that applies Human-in-the-Loop confirmation strategies to pending tool calls — a human can confirm, modify, or reject the tool calls the model requested before they run. See [Human in the Loop](./human-in-the-loop.mdx).
- `ToolResultOffloadHook`: An `after_tool` hook that offloads tool results to a `ToolResultStore` (such as `FileSystemToolResultStore`) and replaces them in the conversation with a compact pointer, so the next LLM call sees a reference instead of the full result. Per-tool policies (`AlwaysOffload`, `NeverOffload`, `OffloadOverChars`) control which results are offloaded. Import it from `haystack.hooks.tool_result_offloading`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if needs to be mentioned here, but eventually when the dedicated page for tool result offloading is made we need to make it clear some sort of tool capable of reading the offloaded tool result is needed. E.g. a BashTool would be a good choice when using the default FileSystemToolResultStore

- Use programmatically correct state access: state.data["messages"]
- Note that reading offloaded tool results requires a file-reading tool

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Update Agent serialization YAML examples to current to_dict() output
  (hooks, tool_concurrency_limit, tool_streaming_callback_passthrough)
- Replace the removed tool_invoker_kwargs parameter with
  tool_concurrency_limit and tool_streaming_callback_passthrough in the
  Agent parameter list and the multi-agent parallelism docs
- Document how ConfirmationHook forwards hook_context to each strategy's
  run() as confirmation_strategy_context (HITL Hayhooks section)
- Clarify that after_tool also fires when a before_tool hook removed the
  pending tool calls
- Note that run-metadata keys are reserved too and link the State page
  from the Hooks state-keys section
- Add close() to the GradeFinalAnswer lifecycle example
- Link the Hooks API reference from the Hooks page; add Hooks to the
  Agent page's Additional References
- Update the LangGraph migration table HITL row to ConfirmationHook

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@julian-risch
julian-risch marked this pull request as ready for review July 9, 2026 19:15
@julian-risch
julian-risch requested a review from a team as a code owner July 9, 2026 19:15
@julian-risch
julian-risch requested review from davidsbatista and sjrl and removed request for a team and davidsbatista July 9, 2026 19:15
The [hitl-hayhooks-redis-openwebui](https://github.com/deepset-ai/hitl-hayhooks-redis-openwebui) repository shows a full production-style HITL setup using a Haystack Agent served via [Hayhooks](https://github.com/deepset-ai/hayhooks) with approval dialogs rendered in [Open WebUI](https://github.com/open-webui/open-webui).

The key pattern it demonstrates is a custom `RedisConfirmationStrategy` that uses `confirmation_strategy_context` to pass per-request resources - a Redis client and an async event queue - into the strategy at runtime:
The key pattern it demonstrates is a custom `RedisConfirmationStrategy` that receives per-request resources - a Redis client and an async event queue - at runtime. Pass such resources via the generic `hook_context` run argument (`agent.run(messages=[...], hook_context={"redis": client})`). `ConfirmationHook` reads this dict from state with `state.data["hook_context"]` (not `state.get`, which returns a deep copy that fails for live resources like clients and queues - see [Hooks](./hooks.mdx)) and passes it to each strategy's `run()` as the `confirmation_strategy_context` keyword argument, which is how a custom strategy receives the Redis client and event queue:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo we reference here will need to be updated to work with Haystack 3.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor comment that should be addressed as a separate PR. Otherwise looks good!

@julian-risch
julian-risch merged commit c13cc91 into main Jul 10, 2026
21 checks passed
@julian-risch
julian-risch deleted the docs/v3-simple-doc-updates-6 branch July 10, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants