Haystack 3.0: Agents with hooks, skills, and built-in introspection

kacperlukawski1 pts0 comments

Haystack 3.0.0 | Haystack

Get Enterprise Support

Get Started

🚀 Day 1/5: Haystack 3.0 is here. Follow Launch Week for 5 days of new drops

Haystack 3.0.0

Haystack 3.0.0

Check on Github

⭐️ Highlights

Haystack 3.0 is a major release for building production-grade agents with full control and flexibility . It ships a wave of new capabilities: a more capable Agent with hooks and first-class skills, built-in run introspection, first-class async for serving, a leaner core, and safer pipeline loading.

A few small, intentional breaking changes come with it but our<br>Migration Guide and Upgrading to Haystack 3.0 make it easy to upgrade.

⚠️ marks a breaking change. See Upgrade Notes below for migration details.

🤖 A more capable, adaptable Agent

The Agent gained a general-purpose hooks system and now owns tool execution end to end (the standalone ToolInvoker has been removed ). Hooks at before_run, before_llm, before_tool, after_tool, on_exit, and after_run let you shape behavior, enforce guardrails, and add human-in-the-loop checkpoints without touching the agent&rsquo;s internals:

from haystack.components.agents import Agent<br>from haystack.components.generators.chat import OpenAIChatGenerator<br>from haystack.hooks import hook

@hook<br>def audit_tool_calls(state):<br>pending = state.data["messages"][-1].tool_calls<br>print(f"about to run: {[tc.tool_name for tc in pending]}")

agent = Agent(<br>chat_generator=OpenAIChatGenerator(),<br>tools=[...],<br>hooks={"before_tool": [audit_tool_calls]},

Built on this foundation:

Skills as first-class citizens — SkillToolset discovers skills through progressive disclosure , so the model sees only names and one-line descriptions until it loads one, keeping context lean.

Dynamic tool selection at runtime — pass tools=... to run / run_async so one reusable Agent serves different teams, tenants, and tasks.

Native async tools — @tool routes async def callables to a Tool&rsquo;s new async_function.

Human-in-the-Loop is now a before_tool hook (ConfirmationHook). ⚠️

Tool result offloading (ToolResultOffloadHook) writes large results to a store and leaves a compact pointer in the conversation.

🔎 Built-in introspection and observability

Agents now expose step_count, token_usage, and tool_call_counts as built-in state. React to them to compact context when the window fills, cap runaway tool loops, or route to a cheaper approach past a budget threshold. Tracing now emits dedicated step-level spans (haystack.agent.step with nested .llm / .tool children) tagged with the tools actually used, so you can see exactly what your agent did. ⚠️

⚡ A core built for serving: first-class async

Pipeline and AsyncPipeline are now one class — no more switching between them. A single Pipeline exposes run, run_async, run_async_generator, and stream; serialized pipelines, SuperComponents, and PipelineTools load as Pipeline instances. ⚠️

from haystack import Pipeline

pipeline = Pipeline()<br>result = pipeline.run(data) # synchronous<br>result = await pipeline.run_async(data) # asynchronous

Concurrent tool calls and token-by-token streaming come standard: Pipeline.stream() yields StreamingChunks as they&rsquo;re produced and exposes the final output on handle.result — the lower latency and streaming UX serving apps need. Components and Pipeline also get a symmetric lifecycle — warm_up to acquire, close to release — so long-running services don&rsquo;t leak connections, GPU memory, or file handles. External resources (and API keys) are now created at warm_up, not in __init__. ⚠️

🧱 A leaner, faster-moving framework ⚠️

Legacy Generators are gone, haystack-experimental is no longer a core dependency, and 30 components now live in independently released packages in<br>haystack-core-integrations: Sentence Transformers, local & API Hugging Face, Whisper, spaCy/langdetect, Tika and Azure OCR, SerperDev/SearchApi, OpenAPI connectors, and the Datadog/OpenTelemetry tracers. As a result, there are fewer transitive dependencies to audit and we can ship independent of the core release cycle. Migration is an install plus an import change:

# pip install sentence-transformers-haystack<br># Before: from haystack.components.embedders import SentenceTransformersTextEmbedder<br>from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder

The full old → new import table is in<br>MIGRATION.md.

💬 Chat Generators everywhere (legacy Generators removed) ⚠️

OpenAIGenerator, AzureOpenAIGenerator, HuggingFaceAPIGenerator, and HuggingFaceLocalGenerator have been removed in favor of their Chat Generator counterparts. To ease the switch, all Chat Generators now accept a plain str for messages:

# Before<br>from haystack.components.generators import OpenAIGenerator<br>text = OpenAIGenerator().run("What is NLP?")["replies"][0] # str

# After<br>from haystack.components.generators.chat import OpenAIChatGenerator<br>reply = OpenAIChatGenerator().run("What is NLP?")["replies"][0] # ChatMessage<br>text = reply.text # str;...

haystack pipeline agent import tool from

Related Articles