A Redis-based Human-in-the-Loop implementation for Hayhooks / Haystack Agents, integrated with Open WebUI for interactive tool approval workflows.
The default pipeline is a Haystack 3.0 Launch Week Concierge: it answers questions about the launch week, explains new features, and recommends deepset-ai repos freely - but the moment it wants to post anonymous feedback to deepset's Slack on your behalf, a human has to approve it first.
- Walkthrough
- Overview
- Architecture
- Prerequisites
- Quick Start with Docker Compose
- Manual Installation (Alternative)
- Usage
- Configuration
- Available Tools
- Adding Custom Tools
- Troubleshooting
- How It Works
- License
This implementation allows users to approve or reject tool calls made by a Haystack Agent in real-time through Open WebUI's confirmation dialogs.
The system consists of two main components:
pipelines/hitl/pipeline_wrapper.py- A Hayhooks pipeline containing a Haystack Agent with Redis-based HITL confirmation strategyopen-webui-pipe.py- A Pipe function deployed in Open WebUI that bridges the frontend with Hayhooks
Redis is used as a message broker to coordinate approval decisions between the Pipe function and the backend pipeline.
┌─────────────────┐ ┌─────────────────┐ HTTP/SSE ┌─────────────────┐
│ │ │ │ ───────────────▶ │ │
│ Open WebUI │ ──────▶ │ Pipe Function │ │ Hayhooks │
│ (Frontend) │ ◀────── │ (open-webui- │ ◀─────────────── │ (Server) │
│ │ │ pipe.py) │ SSE Events │ │
└─────────────────┘ └────────┬────────┘ └────────┬────────┘
│ │
│ ┌─────────────────┐ │
│ │ │ │
└─▶│ Redis │◀──────────────┘
│ (Broker) │
│ │
└─────────────────┘
- User sends message → Open WebUI frontend calls the Pipe function (
open-webui-pipe.py) - Pipe forwards request → Pipe function makes HTTP POST to Hayhooks (
/hitl/run) - Agent processes → Hayhooks streams SSE events back to the Pipe function
- Tool call detected → Agent emits
tool_call_startevent via SSE - Approval requested → Pipe function shows confirmation dialog to user via Open WebUI
- User decides → Pipe function sends approval/rejection to Redis (
LPUSH) - Pipeline unblocks → Hayhooks reads from Redis (
BLPOP) and continues - Result streamed → Pipe function receives response and streams it back to Open WebUI frontend
Open WebUI Hayhooks
Frontend Pipe Function /Agent Redis
│ │ │ │
│"Tell the deepset team │ │ │
│ I love the HITL hooks"│ │ │
│───────────────────────▶│ │ │
│ │ POST /hitl/run │ │
│ │───────────────────────────────────▶│ │
│ │ │ │
│ │ SSE: tool_call_start │ │
│ │◀───────────────────────────────────│ │
│ │ │ BLPOP (waiting) │
│ │ │──────────────────────▶│
│ 🔧 Approve feedback? │ │ │
│◀───────────────────────│ │ │
│ │ │ │
│ ✅ Yes / ❌ No │ │ │
│───────────────────────▶│ │ │
│ │ LPUSH "approved" │ │
│ │───────────────────────────────────────────────────────────▶│
│ │ │ (unblocks) │
│ │ │◀──────────────────────│
│ │ │ │
│ │ SSE: text (result) │ │
│ │◀───────────────────────────────────│ │
│ "Feedback submitted │ │ │
│ anonymously" │ │ │
│◀───────────────────────│ │ │
│ │ │ │
- Docker and Docker Compose
- OpenAI API key (or compatible LLM endpoint)
The easiest way to get started is using Docker Compose, which sets up all services (Redis, Hayhooks, Open WebUI) automatically.
export OPENAI_API_KEY="your-api-key-here"
docker compose up -d --buildThis starts:
- Redis on port 6379
- Hayhooks on port 1416 (with the HITL pipeline loaded)
- Open WebUI on port 3000
Follow Deploy and Configure the Pipe Function section instructions to deploy the Pipe Function to Open WebUI.
If you prefer to run services manually without Docker Compose:
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# Install dependencies for Hayhooks server
pip install -r requirements.txtdocker run -d --name redis -p 6379:6379 redis:alpine# Set your OpenAI API key
export OPENAI_API_KEY="your-api-key-here"
# Redis defaults to localhost:6379, override if needed:
# export REDIS_HOST="localhost"
# export REDIS_PORT="6379"
# Start Hayhooks and deploy the pipeline
hayhooks runHayhooks will start on http://localhost:1416 and automatically load pipelines/hitl/pipeline_wrapper.py.
docker run -d \
--name open-webui \
-p 3000:8080 \
-e WEBUI_AUTH=False \
-v open-webui:/app/backend/data \
--add-host=host.docker.internal:host-gateway \
ghcr.io/open-webui/open-webui:mainNOTE: This step can be automated, but will require to enable authentication in Open WebUI, in order to get a JWT token and use it in the curl command. For this demo, since it's a one-time setup, we'll do it manually.
- Open http://localhost:3000 in your browser
- Go to Settings → Admin Settings
- Select Functions from top bar
- Copy the contents of
open-webui-pipe.pyinto the editor - Give a title, name and description to the function
- Save and enable the function
- Important: Update the Valves for manual setup:
BASE_URL:http://host.docker.internal:1416REDIS_HOST:host.docker.internal
- In Open WebUI, start a new chat
- Select the Hayhooks HITL Pipe as your model (this routes requests through the Pipe function)
- Ask something that triggers a tool call, e.g.:
- "What's new today in Haystack 3.0 Launch Week?" (
whats_new_today, read-only) - "What happened on day 2 of launch week?" (
get_launch_week_day, read-only) - "What are Agent hooks?" (
explain_feature, read-only) - "What repo should I look at for deploying pipelines?" (
recommend_repo, read-only) - "How do I write a custom Haystack component?" (
search_haystack_docs, read-only, queries Haystack's hosted docs MCP server live) - "Tell the deepset team I love the HITL hooks" (
submit_feedback_to_deepset, requires approval)
- "What's new today in Haystack 3.0 Launch Week?" (
- The Pipe function will forward your message to Hayhooks
- When the Agent decides to call a tool, a confirmation dialog will appear
- Click Confirm to approve or Cancel to reject the tool execution
- For
submit_feedback_to_deepset, approving posts the message straight to deepset's Slack - no email or other personal details are ever collected from you - The response will stream back through the Pipe function to your chat
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
- | Required. Your OpenAI API key |
REDIS_HOST |
localhost |
Redis server hostname |
REDIS_PORT |
6379 |
Redis server port |
DEMO_MODE |
false |
When false (default), posts your message for real to a Slack workspace we set up just for this. Set to true to only simulate posting |
SLACK_WEBHOOK_URL |
webhook (Compose) | Slack Incoming Webhook URL to post feedback to (used when DEMO_MODE=false; Compose defaults to a slack workspace we set up just for this - replace it with your own to post elsewhere) |
| Valve | Docker Compose | Manual Setup | Description |
|---|---|---|---|
BASE_URL |
http://hayhooks:1416 |
http://host.docker.internal:1416 |
Hayhooks server URL |
PIPELINE_NAME |
hitl |
hitl |
Pipeline endpoint name |
REDIS_HOST |
redis |
host.docker.internal |
Redis host |
REDIS_PORT |
6379 |
6379 |
Redis port |
Note: The defaults in
open-webui-pipe.pyare configured for Docker Compose. For manual setup, update the Valves via Admin Panel → Functions → Hayhooks HITL Pipe → ⚙️
The default configuration is a Haystack 3.0 Launch Week Concierge, with a deliberate split between read-only tools that execute immediately and the one consequential tool that requires human approval:
| Tool | Description | Requires Approval? |
|---|---|---|
get_launch_week_day |
Get the theme, summary, and link for a specific launch week day (1-5) | No (read-only) |
whats_new_today |
Get today's launch week drop, based on the current date | No (read-only) |
recommend_repo |
Recommend relevant deepset-ai GitHub repos for a given interest | No (read-only) |
explain_feature |
Explain a specific Haystack 3.0 feature (hooks, skills, agent pack, etc.) | No (read-only) |
search_haystack_docs |
Search the live Haystack documentation via deepset's hosted docs MCP server | No (read-only) |
submit_feedback_to_deepset |
Post anonymous feedback (a question, comment, or feature request) to deepset's Slack | Yes (posts to a real channel) |
Which tools require approval is controlled entirely by which ones are registered in the
ConfirmationHook's confirmation_strategies dict in pipeline_wrapper.py — a tool left out of
that dict executes immediately, with no human in the loop.
search_haystack_docs is a real MCPTool connecting
to deepset's public docs MCP server over Streamable HTTP - no API key needed. The connection is lazy
(eager_connect=False, the default), so it doesn't block pipeline startup on an external network call,
and only connects the first time the tool is actually used.
submit_feedback_to_deepset is the sensitive action itself: once approved, it posts straight to
a Slack channel via an Incoming Webhook, with nothing left for a human to do afterward - which is
exactly why it requires approval first, rather than just drafting something for the user to send.
No email or other personal details are ever collected, so the feedback is fully anonymous. See
Environment Variables (Hayhooks) for configuring the webhook.
Edit pipelines/hitl/pipeline_wrapper.py to add your own tools:
from haystack.tools import create_tool_from_function
def my_custom_tool(param: str) -> str:
"""Your tool description."""
return f"Result for {param}"
custom_tool = create_tool_from_function(
function=my_custom_tool,
name="my_custom_tool",
description="Description shown to the LLM",
)
# Add to the agent tools list. Only add it to the ConfirmationHook's confirmation_strategies
# dict if it should require human approval before running - leave it out for tools that are
# safe to auto-execute (e.g. read-only lookups).
self.agent = Agent(
# ...
tools=[get_launch_week_day_tool, whats_new_today_tool, recommend_repo_tool, custom_tool],
hooks={
"before_tool": [
ConfirmationHook(
confirmation_strategies={
# ...
custom_tool.name: self.confirmation_strategy,
}
)
]
},
)- Ensure Hayhooks is running:
curl http://localhost:1416/status - Docker Compose: Use
http://hayhooks:1416in Valve settings - Manual setup: Use
http://host.docker.internal:1416in Valve settings
- Check Redis is running:
redis-cli ping(should returnPONG) - Docker Compose: Set
REDIS_HOSTvalve toredis - Manual setup: Set
REDIS_HOSTvalve tohost.docker.internal - Verify host/port configuration in both pipeline and pipe
- Default timeout is 5 minutes
- Check Redis connectivity between all services
- Verify the pipe is correctly sending approvals to the same Redis instance
- Ensure
Accept: text/event-streamheader is set - Check for proxy/firewall issues that might buffer SSE
The RedisConfirmationStrategy class implements Haystack's ConfirmationStrategy interface:
- When a tool call is initiated, it emits a
tool_call_startevent to the async queue (obtained fromconfirmation_strategy_context) - It then waits on Redis
BLPOPfor an approval decision (using the Redis client fromconfirmation_strategy_context) - Once approval is received, it returns a
ToolExecutionDecisionobject - The agent proceeds to execute (or skip) the tool based on the decision
The per-request state (event_queue, redis_client) is passed as the hook_context argument when calling agent.run_async(). The ConfirmationHook (registered on the Agent's before_tool hook point) reads this dict from the Agent state and hands it to the strategy's run_async() as the confirmation_strategy_context keyword argument.
The Pipe function acts as a bridge between the Open WebUI frontend and Hayhooks:
- Receives chat messages from the Open WebUI frontend
- Forwards the request to Hayhooks via HTTP POST (
/hitl/run) - Streams SSE events from Hayhooks back to the frontend
- When it receives a
tool_call_startevent, it shows a confirmation dialog to the user via__event_call__ - Based on user response, it sends
approvedorrejectedto Redis viaLPUSH - Continues streaming the response back to the Open WebUI frontend
MIT

