AI Agents Guide¶
Build intelligent, tool-using AI agents with Helix. This guide covers all 7 agent types, 17 LLM providers, tool calling, 8 memory backends, multi-agent orchestration, and agent flows — all configurable via declarative YAML or Go code.
Table of Contents¶
- Overview
- Agent Types (Deep Dive)
- LLM Providers
- Memory Configuration
- Tools — incl. middleware, custom orchestration, checkpointing, async interrupts
- Governance — kill switches, rings, policy engine, audit chain
- Agent Flows
- Exposing Agents via Protocols
- RAG-Augmented Agents
- Advanced Patterns
- Production Deployment
- Complete Examples
- Strict YAML Validation
- Human-in-the-Loop
- Agent Memory with MemX Backend
- Multimodal Agents
- Built-in Function Registry
- SmartMemory -- Token-Managed Memory
- Playwright MCP Browser Tools
1. Overview¶
What Are Helix Agents?¶
Helix agents are AI-powered processing units that live inside your integration pipelines. Unlike standalone agent frameworks, Helix agents are first-class integration components: they receive messages from Kafka, respond to HTTP requests, process database events, and route results to downstream systems -- all within the same workflow definition.
An agent in Helix combines four capabilities:
- LLM reasoning via pluggable providers (OpenAI, Anthropic, Ollama)
- Tool use for interacting with external systems during reasoning
- Memory for maintaining conversational and long-term context
- Orchestration patterns for multi-agent coordination
Top-Level agents: Primitive¶
Agents can be defined as top-level agents: primitives in YAML, not just as inline steps inside workflows:. The top-level syntax is simpler and AI-first:
# Top-level agents: primitive (recommended for standalone agents)
agents:
- id: assistant
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: openai
model: gpt-4o
system_prompt: "You are a helpful assistant."
tools:
- name: search
type: function
description: "Search the knowledge base"
memory:
enabled: true
type: sliding-window
window_size: 20
Compare with the equivalent inline syntax inside a route:
# Inline agent step inside workflows: (use when the agent is part of a larger pipeline)
workflows:
- id: chat-route
trigger:
uri: http-server://0.0.0.0:8080/chat
steps:
- agent:
id: assistant
type: react
model:
provider: openai
model: gpt-4o
system_prompt: "You are a helpful assistant."
Use agents: when the agent IS the endpoint. Use inline agent: steps when the agent is one step in a larger integration pipeline (e.g., consuming from Kafka, then processing with an agent, then routing to another system).
Architecture¶
+------------------+
| Exchange |
| (Message Body) |
+--------+---------+
|
+--------v---------+
| Agent Step |
| (agent://id) |
+--------+---------+
|
+--------------+--------------+
| | |
+--------v---+ +------v-----+ +-----v------+
| LLM Provider| | Tools | | Memory |
| (OpenAI, | | (FuncTool, | | (Sliding, |
| Anthropic,| | MCP, | | Vector, |
| Ollama) | | SimpleTool)| | SQL, ...) |
+--------+---+ +------+-----+ +-----+------+
| | |
+--------------+--------------+
|
+--------v---------+
| Agent Output |
| (set on Exchange|
| body/headers) |
+------------------+
|
+--------------+--------------+-----------+
| | | |
+--------v---+ +------v-----+ +-----v----+ +---v-----+
| AG-UI | | A2A | | A2UI | | MCP |
| (Frontend | | (Agent-to- | | (Rich UI)| | (Tool |
| Streaming)| | Agent) | | | | Export) |
+------------+ +------------+ +----------+ +---------+
When to Use Which Agent Type¶
| Agent Type | Use When |
|---|---|
| ReAct | General-purpose tasks with tool use. Default choice. |
| Supervisor | You have specialist sub-agents and need intelligent routing. |
| Plan-Execute | Complex tasks that benefit from upfront planning before execution. |
| Deep | Hierarchically complex problems that need recursive decomposition. |
| Sequential | Pipeline of agents where each refines the previous output. |
| Parallel | Independent subtasks that can run concurrently (e.g., multi-perspective analysis). |
| Loop | Iterative refinement until a quality threshold is met. |
2. Agent Types (Deep Dive)¶
2.1 ReAct Agent¶
What it does: Implements the ReAct (Reason + Act) loop -- the most widely used agent pattern. The agent reasons about what to do, optionally calls tools, observes the results, and repeats until it has a final answer.
How it works internally:
1. Loads conversation history from memory
2. Appends the user message
3. Sends messages + tool definitions to the LLM
4. If the LLM returns tool calls, executes them and appends results
5. Loops back to step 3 (up to max_iterations)
6. If no tool calls, the LLM response is the final answer
YAML example:
agents:
- id: my-assistant
type: react
trigger: direct://assistant
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a helpful assistant with access to tools.
Always verify information before responding.
memory:
enabled: true
type: sliding-window
window_size: 20
max_iterations: 15
tools:
- name: search_web
type: function
description: "Search the web for current information"
parameters:
type: object
properties:
query:
type: string
description: "Search query"
required: ["query"]
Key parameters:
| Parameter | Default | Description |
|---|---|---|
| max_iterations | 10 | Maximum ReAct loop iterations before failing |
| system_prompt | (empty) | System message prepended to all conversations |
| memory.type | conversation | Memory backend to use |
Use cases: Customer support chatbots, research assistants, code generation agents, data analysis assistants.
2.2 Supervisor Agent¶
What it does: Routes incoming requests to the most appropriate specialist sub-agent. The supervisor uses the LLM to analyze the request and decide which sub-agent should handle it.
How it works internally:
1. Presents the LLM with available sub-agent IDs and the user request
2. Asks the LLM to respond with JSON: {"agent": "<id>", "message": "<refined message>"}
3. Parses the routing decision and delegates to the chosen sub-agent
4. Falls back to the first sub-agent if JSON parsing fails
YAML example:
agents:
- id: support-supervisor
type: supervisor
trigger: direct://support
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: |
You are a customer support supervisor. Route requests to the
appropriate specialist agent based on the customer's needs.
- billing-agent: payment issues, invoices, refunds
- technical-agent: bugs, errors, configuration problems
- sales-agent: pricing, plans, upgrades
sub_agents:
- id: billing-agent
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "You handle billing and payment inquiries."
max_iterations: 5
- id: technical-agent
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a technical support specialist."
max_iterations: 10
tools:
- name: query_logs
type: function
description: "Query application logs"
parameters:
type: object
properties:
service:
type: string
timerange:
type: string
required: ["service"]
- id: sales-agent
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a friendly sales specialist."
max_iterations: 5
Key parameters:
| Parameter | Default | Description |
|---|---|---|
| sub_agents | (required) | List of specialist sub-agents |
| system_prompt | (empty) | Instructions for the routing decision |
Use cases: Customer support triage, multi-department helpdesk, domain-specific routing.
2.3 Plan-Execute Agent¶
What it does: Separates planning from execution. First asks the LLM to create a step-by-step plan (JSON array), then executes each step sequentially using the ReAct pattern, passing accumulated context forward.
How it works internally:
1. Asks the LLM to create a plan: [{"step": 1, "action": "description"}, ...]
2. For each step, runs a ReAct loop with the step description + accumulated results
3. Returns the combined results from all steps
YAML example:
agents:
- id: research-agent
type: plan-execute
trigger: direct://research
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a research analyst. When given a research topic,
create a structured plan and execute each step thoroughly.
max_iterations: 20
tools:
- name: search_papers
type: function
description: "Search academic papers"
parameters:
type: object
properties:
query:
type: string
limit:
type: integer
required: ["query"]
- name: summarize_paper
type: function
description: "Get and summarize a specific paper"
parameters:
type: object
properties:
paper_id:
type: string
required: ["paper_id"]
Key parameters:
| Parameter | Default | Description |
|---|---|---|
| max_iterations | 10 | Max ReAct iterations per step (not total) |
| system_prompt | (empty) | Instructions for both planning and execution |
Use cases: Research tasks, report generation, multi-step data analysis, complex problem solving.
2.4 Deep Agent¶
What it does: Performs hierarchical task decomposition. Recursively breaks complex tasks into subtasks, solves leaf tasks via ReAct, and synthesizes results back up the tree.
How it works internally:
1. Asks the LLM whether the task can be solved directly or needs decomposition
2. If direct: solves with ReAct
3. If decomposition needed: LLM returns {"direct": false, "subtasks": ["task1", "task2", ...]}
4. Recursively decomposes each subtask (up to deep_max_depth)
5. At max depth, solves directly with ReAct
6. After all subtasks complete, synthesizes a final answer from combined results
YAML example:
agents:
- id: deep-analyst
type: deep
trigger: direct://analyze
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a deep analysis agent. Break complex problems into
manageable subtasks and solve them systematically.
max_iterations: 10
deep_max_depth: 4
tools:
- name: query_database
type: function
description: "Run a SQL query against the analytics database"
parameters:
type: object
properties:
sql:
type: string
required: ["sql"]
Key parameters:
| Parameter | Default | Description |
|---|---|---|
| deep_max_depth | 3 | Maximum recursion depth for task decomposition |
| max_iterations | 10 | Max ReAct iterations at each leaf node |
Use cases: Complex data analysis, competitive landscape assessment, architectural reviews, multi-faceted research.
2.5 Sequential Agent¶
What it does: Runs sub-agents in sequence, passing each agent's output as the next agent's input. This creates a processing pipeline where each agent refines or transforms the previous result.
How it works internally: 1. Runs the first sub-agent with the original user message 2. Takes the response content and passes it as the input to the next sub-agent 3. Repeats until all sub-agents have processed 4. Returns the last sub-agent's response
YAML example:
agents:
- id: content-pipeline
type: sequential
trigger: direct://create-content
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
sub_agents:
- id: researcher
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Research the given topic and provide key facts and data points."
max_iterations: 5
- id: writer
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "Write a well-structured article based on the research provided."
max_iterations: 3
- id: editor
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Edit the article for clarity, grammar, and style. Output the final version."
max_iterations: 3
Use cases: Content creation pipelines, code review chains, translation + localization, data enrichment.
2.6 Parallel Agent¶
What it does: Runs all sub-agents concurrently with the same input, collects results, and combines them. Each sub-agent processes independently in its own goroutine.
How it works internally:
1. Launches all sub-agents concurrently (one goroutine per sub-agent)
2. Each sub-agent receives the same user message
3. Collects all responses (errors are logged but do not block other agents)
4. Combines results as [agent-id]: response\n\n for each agent
5. Returns the combined response
YAML example:
agents:
- id: perspectives
type: parallel
trigger: direct://analyze
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
sub_agents:
- id: technical-reviewer
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Analyze this from a technical feasibility perspective."
- id: business-reviewer
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "Analyze this from a business impact perspective."
- id: risk-reviewer
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Analyze this from a risk and compliance perspective."
Use cases: Multi-perspective analysis, ensemble responses, parallel data processing, A/B response testing.
parallel_error_policy controls what happens when one sub-agent fails:
| Value | Behaviour |
|---|---|
"" (default) |
First error returned; siblings keep running and their results are collected for later inspection. |
fail_fast |
Cancel siblings on the first error and return it. Best for "all or nothing" pipelines. |
collect |
Wait for all sub-agents, then return errors.Join(err1, err2, …) of every failure. Best for "give me everything that worked plus everything that didn't". |
Failures always surface as delegate_error events on OnEvent regardless of policy, so dashboards see every miss.
2.7 Loop Agent¶
What it does: Iteratively refines a response until the agent signals completion (by including "DONE", "COMPLETE", or "FINISHED" in its output) or reaches max_iterations.
How it works internally:
1. Runs a ReAct loop with the user message
2. Checks the response for completion keywords (case-insensitive)
3. If not complete, prepends "Previous iteration result:" and asks the agent to continue refining
4. Repeats up to max_iterations
YAML example:
agents:
- id: refinement-agent
type: loop
trigger: direct://refine
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a code quality agent. Review and iteratively improve
the given code. When you are satisfied with the quality,
include "DONE" in your response along with the final code.
max_iterations: 5
Completion signals: The agent must include one of these keywords (case-insensitive) in its response to stop iterating: DONE, COMPLETE, FINISHED.
Key parameters:
| Parameter | Default | Description |
|---|---|---|
| max_iterations | 10 | Maximum refinement iterations |
Use cases: Code quality improvement, essay refinement, iterative problem solving, self-correcting agents.
3. LLM Providers¶
Helix supports 17 LLM providers (plus a gemini alias for Vertex), all implementing the ChatModel interface with both synchronous and streaming modes. The three quick-start examples below cover the most common cases; the full provider list with auth and endpoint details lives in the dedicated LLM & Embedding Providers guide.
Quick reference:
| Provider | Use for |
|---|---|
openai, anthropic, gemini / vertex |
General-purpose, multimodal |
groq, together, fireworks, deepinfra, mistral, xai, cohere, huggingface, replicate |
Hosted open + frontier models |
qwen |
Qwen3 thinking mode + Qwen-Omni audio I/O (DashScope compatible-mode/v1) |
ollama, vllm |
Self-hosted / air-gapped |
azure, bedrock |
Enterprise compliance |
Reasoning, finish-reason, and audio I/O are uniform across the chat-completions providers — see Reasoning, FinishReason & Audio I/O for WithReasoningEffort, WithEnableThinking, WithAudioOutput, and the Message.FinishReason / Message.ReasoningContent surfaces.
3.1 OpenAI¶
API endpoint: POST {base_url}/chat/completions
Authentication: Authorization: Bearer {api_key}
Default base URL: https://api.openai.com/v1
HTTP timeout: 120 seconds
model:
provider: openai
model: gpt-4o # Required: model identifier
api_key: "${OPENAI_API_KEY}" # Required: API key (env var recommended)
base_url: "https://api.openai.com/v1" # Optional: custom endpoint (Azure, vLLM, etc.)
temperature: 0.7 # Optional: 0.0 - 2.0 (default: provider default)
max_tokens: 4096 # Optional: max output tokens
top_p: 0.9 # Optional: nucleus sampling
stop: ["END", "STOP"] # Optional: stop sequences
response_format: "json_object" # Optional: "text" or "json_object"
Recommended models:
| Model | Best For | Cost |
|---|---|---|
| gpt-4o | General purpose, tool use | Medium |
| gpt-4o-mini | Fast responses, simple tasks | Low |
| o3 | Complex reasoning | High |
Compatible services (via base_url): Azure OpenAI, vLLM, LiteLLM, Groq, Together AI, any OpenAI-compatible API.
3.2 Anthropic¶
API endpoint: POST {base_url}/v1/messages
Authentication: x-api-key: {api_key} + anthropic-version: 2023-06-01
Default base URL: https://api.anthropic.com
HTTP timeout: 120 seconds
Default max_tokens: 4096
model:
provider: anthropic
model: claude-sonnet-4-20250514 # Required: model identifier
api_key: "${ANTHROPIC_API_KEY}" # Required: API key
base_url: "https://api.anthropic.com" # Optional: custom endpoint
max_tokens: 8192 # Optional: default 4096
temperature: 0.7 # Optional
top_p: 0.9 # Optional
Recommended models:
| Model | Best For | Cost |
|---|---|---|
| claude-sonnet-4-20250514 | General purpose, tool use | Medium |
| claude-opus-4-20250514 | Complex reasoning, analysis | High |
| claude-haiku-3-20250414 | Fast responses, simple tasks | Low |
Note: Anthropic uses a different message format internally. The system prompt is sent as a top-level system field (not a message). Tool results are sent as user messages with tool_result content blocks. Helix handles all format translation automatically.
3.3 Ollama (Local)¶
API endpoint: POST {base_url}/api/chat
Authentication: None (local inference)
Default base URL: http://localhost:11434
HTTP timeout: 300 seconds (local models can be slow on first load)
Streaming format: Newline-delimited JSON (not SSE)
model:
provider: ollama
model: llama3.1:8b # Required: model name:tag
base_url: "http://localhost:11434" # Optional: custom Ollama endpoint
temperature: 0.7 # Optional
max_tokens: 2048 # Optional: maps to num_predict
top_p: 0.9 # Optional
stop: ["\n\n"] # Optional
Recommended models:
| Model | Parameters | Best For |
|---|---|---|
| llama3.1:8b | 8B | General purpose, fast |
| llama3.1:70b | 70B | Complex reasoning |
| qwen2.5:7b | 7B | Coding, reasoning |
| mistral:7b | 7B | Fast, multilingual |
Prerequisites: Install and run Ollama (ollama serve), then pull models (ollama pull llama3.1:8b).
3.4 API Key Configuration¶
Environment variables (recommended):
Vault integration (for production):
helix:
config:
vault:
addr: "https://vault.example.com"
token: "${VAULT_TOKEN}"
path: "secret/data/helix/openai"
key: "api_key"
Direct (development only -- never commit to source control):
3.5 Streaming vs Non-Streaming¶
All three providers support both modes:
- Non-streaming (default):
agent.Run(ctx, message)-- waits for the full response. Used when the exchange does not setHelixStream=true. - Streaming:
runner.QueryStream(ctx, message)-- returns a*llm.StreamReaderthat yieldsStreamChunkvalues. Enabled by setting the exchange headerHelixStreamto"true". Used by AG-UI, A2A, and A2UI protocol servers.
3.6 Token Usage Tracking¶
All LLM components set token usage on exchange headers after each call:
| Header | Description |
|---|---|
llm.prompt_tokens |
Input tokens consumed |
llm.completion_tokens |
Output tokens generated |
llm.total_tokens |
Total tokens (prompt + completion) |
Use these headers for cost tracking, rate limiting, and monitoring.
4. Memory Configuration¶
Memory determines what conversational context the agent sees. All memory types implement the Memory interface:
type Memory interface {
Add(ctx context.Context, message *llm.Message) error
Get(ctx context.Context) ([]*llm.Message, error)
Search(ctx context.Context, query string, limit int) ([]*llm.Message, error)
Clear(ctx context.Context) error
}
4.1 Conversation Memory¶
What it does: Stores the full, unabridged conversation history. Every message ever added is returned by Get(). Simple and unbounded. In-memory by default, or durable via a store.
When to use: Short conversations, debugging, when you never want to lose context.
This is the default if no memory is configured. With no store it lives in
process memory (lost on restart). To persist the full history, add a store:
memory:
enabled: true
type: conversation
store: mongodb # redis | file | memx | mongodb
connection: "mongodb://localhost:27017/helix" # db in the path (default: helix)
collection: agent_memory # optional (default: agent_memory)
session_id: "session-abc-123"
store |
Backend | Connection field |
|---|---|---|
| (unset) | in-process (ephemeral) | — |
redis |
Redis list | redis_url |
file |
local JSON file | file_path |
memx |
memx (postgres wire) | memx_url |
mongodb |
MongoDB collection | connection (mongodb:// URI) |
For a SQL database (PostgreSQL/Oracle/SQL Server/MySQL) with the same full-history
semantics, use type: sql-long-term.
Caution: Unbounded growth. For long conversations, token limits will eventually be exceeded.
4.2 Sliding Window Memory¶
What it does: Keeps only the last N messages. The system message (first message with role system) is always preserved and does not count toward the window size.
When to use: Chat applications where recent context matters most.
| Parameter | Required | Default | Description |
|---|---|---|---|
window_size |
Yes | -- | Maximum number of messages to retain |
Behavior: When the window is exceeded, the oldest non-system messages are dropped. The system message always occupies one slot.
4.3 Summary Memory¶
What it does: When message count exceeds max_messages, uses the LLM to summarize the oldest half of messages into a running summary. The summary is prepended as a system message on Get(), followed by the most recent messages.
When to use: Long-running conversations where you need both context compression and recent detail.
| Parameter | Required | Default | Description |
|---|---|---|---|
max_messages |
Yes | -- | Threshold that triggers summarization |
Note: Summary memory requires an LLM call each time summarization triggers. When configured via YAML, the agent's own model is used for summarization. The prompt is: "Summarize the following conversation concisely, preserving key facts and decisions."
4.4 Token Window Memory¶
What it does: Keeps messages within an estimated token budget. Token count is estimated as len(content) / 4. The system message is always preserved.
When to use: When you want precise control over context window usage to avoid exceeding model limits.
| Parameter | Required | Default | Description |
|---|---|---|---|
max_tokens |
Yes | -- | Maximum estimated token budget |
Behavior: Drops the oldest non-system messages until the total estimated token count fits within the budget.
4.5 Long-Term Memory¶
What it does: Key-value memory with categories, tags, and search. Backed by a Store interface.
long-term is only durable with a store
Despite the name, type: long-term with no store (or store: memory) is
in-memory only and is lost on restart — the engine logs a warning at load
time. For persistence set store: redis, store: file, or store: memx
(or use type: sql-long-term with a dsn).
When to use: Agents that need to remember facts across sessions (with a durable store).
steps:
- agent:
id: persistent-agent
memory:
enabled: true
type: long-term
store: file # memory (ephemeral) | redis | file | memx
file_path: .helix/memory/user-123.json
namespace: "user-123"
| Parameter | Required | Default | Description |
|---|---|---|---|
store |
No | memory |
Backend: memory (ephemeral), redis, file, memx |
redis_url / file_path / memx_url |
No | — | Connection for the chosen store |
namespace |
No | agent ID | Scope for stored memories |
Each stored entry has: ID, Namespace, Content, Category (fact/preference/decision/procedure), Role, Tags, CreatedAt.
4.6 SQL Database Long-Term Memory¶
What it does: Stores all messages persistently in a PostgreSQL (or any database/sql compatible) database. Messages are scoped by session ID. Search uses ILIKE for content matching.
When to use: Production agents that need durable, queryable conversation history.
steps:
- agent:
id: production-agent
memory:
enabled: true
type: sql-long-term
dsn: "${DATABASE_URL}"
session_id: "session-abc-123"
| Parameter | Required | Default | Description |
|---|---|---|---|
dsn |
Yes | -- | Database connection string (e.g., postgres://user:pass@host/db) |
session_id |
Yes | -- | Scopes messages to a specific session |
Table schema (auto-created via EnsureTable):
CREATE TABLE IF NOT EXISTS agent_memory (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
4.7 Vector Memory¶
What it does: Provides semantic search over past messages using vector embeddings. Messages are embedded and stored in a vector store. Search() finds semantically similar messages. Get() returns an empty slice (vector memory is search-only, not sequential).
When to use: When the agent needs to recall relevant past interactions by meaning rather than recency.
steps:
- agent:
id: semantic-agent
memory:
enabled: true
type: vector
top_k: 5
store: qdrant # durable — persists across restarts
connection: "http://localhost:6333"
collection: agent-semantic
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
| Parameter | Required | Default | Description |
|---|---|---|---|
top_k |
No | 5 | Number of results to return from similarity search |
store |
No | memory |
Vector backend (see below) |
connection |
Depends | -- | Backend URL/addr (qdrant/chromadb/weaviate/opensearch/elasticsearch URL, milvus/redis addr, pgvector/postgis connstring; for memx use memx_url) |
collection |
No | namespace | Collection/table/index name |
api_key / username / password |
Depends | -- | Backend auth (qdrant/pinecone/milvus/weaviate/elasticsearch key; opensearch/elasticsearch user+pass) |
embed.provider |
Yes | -- | Embedding provider: openai, ollama |
embed.model |
Yes | -- | Embedding model name |
embed.api_key |
Depends | -- | API key for embedding provider |
Supported vector stores (dimension is derived from the embedding model, and the collection/table is created automatically):
memory— in-process, brute-force cosine (default, ephemeral)memx— durable, viamemx_url(see §14)- External vector databases (durable):
qdrant,pinecone,milvus,pgvector,postgis,chromadb,weaviate,opensearch,elasticsearch,redis— the same backends the RAG pipeline uses, bridged into agent memory.pineconetakes its index host inconnectionplusapi_key(and reusesnamespace).
4.8 Composite Memory¶
What it does: Combines multiple memory backends. Add() writes to all backends. Get() and Search() merge results from all backends, deduplicating by content.
When to use: When you need both sequential history (sliding window) and semantic search (vector) simultaneously.
steps:
- agent:
id: hybrid-agent
memory:
enabled: true
type: composite
backends:
- type: sliding-window
window_size: 20
- type: vector
top_k: 5
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
| Parameter | Required | Default | Description |
|---|---|---|---|
backends |
Yes | -- | List of memory configurations to combine |
Deduplication: Messages are deduplicated by role:content key across all backends.
4.9 Per-Session Memory Isolation¶
By default, all requests to an agent share a single memory instance. This means User A's conversation leaks into User B's context — a critical problem for any multi-user deployment.
The session_id_header field solves this. When set, Helix extracts the session ID from the specified request header and creates an isolated memory instance per session. Each unique session ID gets its own memory, created on first access and cached for subsequent requests.
Basic Usage¶
agents:
- id: support-bot
type: react
trigger: http-server://0.0.0.0:8080/api/chat
model:
provider: openai
model: gpt-4o
system_prompt: "You are a helpful support agent."
memory:
enabled: true
type: sliding-window
window_size: 30
session_id_header: X-Session-ID
Clients include the session header with each request:
# User Alice
curl -X POST http://localhost:8080/api/chat \
-H "X-Session-ID: alice-session-001" \
-d "What's your return policy?"
# User Bob (completely isolated memory)
curl -X POST http://localhost:8080/api/chat \
-H "X-Session-ID: bob-session-042" \
-d "How do I reset my password?"
Alice and Bob each have their own conversation history. Neither can see the other's messages.
How It Works¶
- Request arrives — the
agentProcessorreads the header specified bysession_id_header - Session lookup — the
SessionMemoryManagerchecks its cache (sync.Map) for that session ID - First access — if not found, the
MemoryFactorycreates a new memory instance using the same config (type, window size, DSN, etc.) but scoped to that session - Subsequent requests — the cached memory is returned instantly
- Agent runs — the agent's memory is swapped to the session-scoped instance before
Run()executes
If the header is missing from a request, the exchange ID is used as a fallback — effectively giving that request a one-shot memory with no persistence.
The session ID is also set as an exchange header agent.session.id for downstream processors to reference.
With SQL Database Persistence¶
For production deployments where memory must survive restarts, combine session_id_header with sql-long-term:
steps:
- agent:
id: production-agent
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
memory:
enabled: true
type: sql-long-term
dsn: "${DATABASE_URL}"
session_id_header: X-Session-ID
Each session ID becomes the session_id column value in the agent_memory table. This means:
- Memory persists across server restarts
- Sessions are queryable:
SELECT * FROM agent_memory WHERE session_id = 'alice-session-001' - Cleanup is straightforward:
DELETE FROM agent_memory WHERE session_id = '...'
With Vector Memory¶
Semantic search scoped per session:
steps:
- agent:
id: research-agent
memory:
enabled: true
type: vector
top_k: 10
session_id_header: X-Session-ID
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
Each session gets its own vector namespace, so similarity search only returns results from that user's conversation history.
With Composite Memory¶
Combine per-session sliding window with per-session vector search:
steps:
- agent:
id: advanced-agent
memory:
enabled: true
type: composite
session_id_header: X-Session-ID
backends:
- type: sliding-window
window_size: 30
- type: vector
top_k: 5
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
Both sub-backends are isolated per session.
Session ID Sources¶
The session_id_header can reference any header set by upstream processors or the transport layer:
| Source | Header Name | How to Set |
|---|---|---|
| Custom header | X-Session-ID |
Client sends directly |
| JWT claim | jwt.sub or jwt.session_id |
Set by JWT security interceptor |
| Cookie | Cookie-Session |
Set by HTTP server component |
| Tenant ID | helix.tenant |
Set by TenantInterceptor |
| Auto-generated | X-Request-ID |
Set by HTTP server (per-request, no persistence) |
Configuration Reference¶
| Parameter | Required | Default | Description |
|---|---|---|---|
session_id_header |
No | -- | Header name to extract session ID from. When set, enables per-session memory isolation |
When session_id_header is not set, the agent uses a single shared memory instance (legacy behavior). This is fine for single-user CLIs, batch processing, or agents that intentionally share context across all requests.
5. Tools¶
Tools give agents the ability to interact with external systems during their reasoning loop.
5.1 Tool Interface¶
All tools implement:
type Tool interface {
Info() *ToolInfo // Name, description, JSON Schema parameters
Invoke(ctx context.Context, arguments string) (string, error)
}
The arguments string is raw JSON matching the tool's parameter schema. The return value is a string that the LLM sees as the tool result.
5.2 Defining Tools in YAML¶
steps:
- agent:
id: my-agent
tools:
- name: get_weather
type: function
description: "Get current weather for a city"
parameters:
type: object
properties:
city:
type: string
description: "City name"
units:
type: string
enum: ["celsius", "fahrenheit"]
description: "Temperature units"
required: ["city"]
5.3 Tool Types Reference¶
Helix agents support 8 tool types. Each type lets an agent interact with a different kind of resource during its reasoning loop.
| Type | Description | Use Case |
|---|---|---|
function |
Generic function tool -- implemented in Go via FuncTool or SimpleTool |
Custom logic in Go code |
tool |
Reference a top-level tools: or pipelines: definition by ID |
Reuse schema-validated tools across agents; RAG retrieval; data processing |
a2a |
Call an external A2A agent over the A2A protocol | Remote agent delegation |
mcp |
Connect to an external MCP server and import its tools | Remote tool discovery and invocation |
connector |
Invoke any Helix connector by URI (439 integrations) | HTTP REST APIs, SaaS, databases, messaging |
type: tool -- Reference Top-Level Tools¶
Reference a tool defined in the top-level tools: block. The agent inherits the tool's description, input schema, and output schema.
tools:
- id: search
description: "Search knowledge base"
input:
query: { type: string, required: true }
output:
results: { type: array }
steps:
- transform:
starlang: '"[]"'
agents:
- id: assistant
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: openai
model: gpt-4o
tools:
- name: search
type: tool
tool_id: search # references tools.[].id above
RAG Pipelines as Tools (via type: tool)¶
Expose a RAG pipeline to an agent by giving it an id you can reference. A pipeline defaults to direct://<id> as its trigger, so a type: tool reference (which resolves tool:// then direct://) can address it directly.
pipelines:
- id: knowledge-base
source: direct://knowledge-base
steps:
- rag-chunk: { strategy: semantic }
- rag-embed: { provider: openai, model: text-embedding-3-small }
- rag-store: { store: pgvector, connection: "${env:PG_URL}" }
- rag-retrieve: { strategy: hybrid, top_k: 10 }
agents:
- id: support-bot
type: react
trigger: http-server://0.0.0.0:8080/support
model:
provider: anthropic
model: claude-sonnet-4-20250514
tools:
- name: query_docs
type: tool
tool_id: knowledge-base # references the pipeline above
description: "Search the knowledge base for relevant documents"
type: a2a -- Call External A2A Agents¶
Delegate work to a remote agent that speaks the A2A protocol. This is the correct choice for calling remote agents. For local agents within the same flow, use sub_agents instead.
agents:
- id: coordinator
type: supervisor
trigger: http-server://0.0.0.0:8080/coordinate
model:
provider: openai
model: gpt-4o
tools:
- name: research_agent
type: a2a
description: "Delegate research tasks to a remote research agent"
a2a_server:
url: "http://research-agent.internal:9000"
auth:
type: bearer
token: "${env:A2A_TOKEN}"
- name: finance_agent
type: a2a
description: "Get financial analysis from the finance team agent"
a2a_server:
url: "http://finance-agent.internal:9000"
auth:
type: oauth2
token_url: "https://auth.example.com/token"
client_id: "${env:OAUTH_CLIENT_ID}"
client_secret: "${env:OAUTH_CLIENT_SECRET}"
scopes: ["agent:invoke"]
type: mcp -- Call External MCP Tools (with Auth)¶
agents:
- id: coder
type: react
trigger: http-server://0.0.0.0:8080/code
model:
provider: anthropic
model: claude-sonnet-4-20250514
tools:
- name: code-tools
type: mcp
description: "Code analysis and formatting tools"
mcp_server:
url: "http://mcp-tools.internal:3100"
transport: http
tools: ["format-code", "lint"]
auth:
type: api-key
header: "X-API-Key"
key: "${env:MCP_API_KEY}"
type: connector -- Use Any Registered Connector¶
Bind any of the 439 built-in connectors (HTTP REST APIs, SaaS, databases, queues, etc.) as a tool by URI.
agents:
- id: lookup-bot
type: react
trigger: http-server://0.0.0.0:8080/lookup
model:
provider: openai
model: gpt-4o
tools:
- name: get_customer
type: connector
connector: "http://customer-api.internal:8080/v1/customers"
description: "Look up a customer by ID"
parameters:
type: object
properties:
id: { type: string, description: "Customer ID" }
required: ["id"]
For inline expression logic, define a type: tool referencing a top-level tools: entry whose steps: use a transform: { starlang: ... } step — the canonical [[feedback_tools_route_split]] pattern.
auth: on Tools¶
The auth: block can be added to type: a2a, type: mcp, and type: connector tools to secure outbound calls. Four auth types are supported:
| Auth Type | Fields |
|---|---|
bearer |
token |
api-key |
header, key |
oauth2 |
token_url, client_id, client_secret, scopes |
mtls |
cert_file, key_file, ca_file |
# Bearer token
auth:
type: bearer
token: "${env:API_TOKEN}"
# API key in custom header
auth:
type: api-key
header: "X-API-Key"
key: "${env:SERVICE_KEY}"
# OAuth2 client credentials
auth:
type: oauth2
token_url: "https://auth.example.com/token"
client_id: "${env:CLIENT_ID}"
client_secret: "${env:CLIENT_SECRET}"
scopes: ["read", "write"]
# Mutual TLS
auth:
type: mtls
cert_file: "/certs/client.crt"
key_file: "/certs/client.key"
ca_file: "/certs/ca.crt"
All ${env:...} placeholders are resolved at load time via Helix's secret resolution system (environment variables, Vault, AWS Secrets Manager).
The Clear Rule: Local vs Remote¶
| Scenario | Mechanism | Example |
|---|---|---|
| Local agents (same flow) | sub_agents: |
sub_agents: [researcher, writer] |
| Remote agents (A2A) | type: a2a tool |
a2a_server: { url: "http://..." } |
| Remote tools (MCP) | type: mcp tool |
mcp_server: { url: "http://..." } |
5.4 MCP Server Tools¶
Connect an external MCP server as a tool source. All tools from the MCP server are automatically registered with the agent.
steps:
- agent:
id: my-agent
tools:
- name: external-tools
type: mcp
description: "Tools from external MCP server"
mcp_server:
url: "http://localhost:3000"
transport: http # http, sse, or stdio
tools: ["search", "fetch"] # Optional: filter specific tools (empty = all)
- name: local-tools
type: mcp
mcp_server:
transport: stdio
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
MCP server tool parameters:
| Parameter | Required | Description |
|---|---|---|
url |
For http/sse | Server URL |
transport |
No | http (default), sse, or stdio |
command |
For stdio | Command to run |
args |
For stdio | Command arguments |
headers |
No | Extra HTTP headers (e.g., auth) |
tools |
No | Filter: only import these tool names |
5.5 FuncTool (Go API)¶
Type-safe tool with automatic JSON deserialization:
type SearchArgs struct {
Query string `json:"query"`
Limit int `json:"limit"`
}
searchTool := tool.NewFuncTool[SearchArgs](
"search_docs",
"Search the documentation",
map[string]any{
"type": "object",
"properties": map[string]any{
"query": map[string]any{"type": "string"},
"limit": map[string]any{"type": "integer"},
},
"required": []string{"query"},
},
func(ctx context.Context, args SearchArgs) (string, error) {
results := doSearch(args.Query, args.Limit)
return fmt.Sprintf("Found %d results: %v", len(results), results), nil
},
)
5.6 SimpleTool (Go API)¶
For tools that process raw JSON arguments:
echoTool := tool.NewSimpleTool(
"echo",
"Echo the input back",
map[string]any{"type": "object"},
func(ctx context.Context, args string) (string, error) {
return "Echo: " + args, nil
},
)
5.7 Tool Registry and Middleware¶
registry := tool.NewRegistry()
registry.Register(searchTool)
registry.Register(echoTool)
// Apply middleware to a tool
logged := tool.WithLogging()(searchTool)
registry.Register(logged)
// Use in agent config
agent := adk.New(adk.AgentConfig{
Tools: registry,
})
5.8 Model & Memory Middleware¶
Both the LLM model and the memory backend can be wrapped with named middleware that the DSL resolves at load time. Use this to add structured logging, redaction, caching, rate-limiting, or tracing without forking the agent core.
agents:
- id: traced-agent
type: react
model: { provider: openai, name: gpt-4o-mini }
model_middleware: [logging] # wraps the ChatModel
memory: { enabled: true, type: sliding-window, max_messages: 20 }
memory_middleware: [logging] # wraps the Memory backend
Built-in logging middleware emits structured slog records for every model call (request + response, tokens, finish reason) and every memory op (add / get / search / clear). Register custom middleware from Go via llm.RegisterModelMiddleware(name, factory) and memory.RegisterMiddleware(name, factory); an unknown name in YAML fails the load with the registered set in the error message — no silent fallback.
Middleware list order is outermost-first (the first entry sees raw input, the last entry is closest to the underlying model/memory).
5.9 Custom Agent Orchestration¶
The seven built-in types (react, supervisor, plan-execute, deep, sequential, parallel, loop) cover most patterns. To add your own:
adk.RegisterAgentType("my-orchestrator",
func(ctx context.Context, a *adk.Agent, input string) (string, error) {
// … your orchestration logic …
})
Reference it from YAML as type: my-orchestrator. The agent dispatcher rejects unknown types with the full list of built-ins plus the registered set, so configuration drift surfaces at load time, not at first invocation.
5.10 Checkpointing & Resumable Runs¶
adk.Runner persists interrupt/resume checkpoints. The default in-process MemoryCheckpointStore is fast but loses state on restart; supply a durable backend for production:
import (
"github.com/arvenza/helix/pkg/agent/adk"
"github.com/arvenza/helix/pkg/agent/kvstore"
)
kv := kvstore.NewRedis(redisClient) // or memx, SQL, etc.
runner := adk.NewRunner(adk.RunnerConfig{
Agent: agent,
CheckpointStore: adk.NewKVCheckpointStore(kv, "helix:agent:checkpoint:"),
})
// Run; if a tool calls tool.Interrupt, a checkpoint is saved
result, err := runner.Run(ctx, runID, input)
if ie, ok := adk.AsInterrupt(err); ok {
// ie.Tool / ie.Info — surface to human; persist the runID
}
// Later, after the human responds:
result, err = runner.Resume(ctx, runID, humanResponse)
The checkpoint shape is JSON-tagged so any KV backend that can hold opaque bytes works.
5.11 Human Input & Async Interrupts¶
Two ways for a tool to ask a human:
Synchronous (RequestHumanInput) — the tool blocks in place until a human responds via the approval queue. Use when the conversation is interactive.
Asynchronous (tool.Interrupt) — the tool returns a typed *tool.InterruptError, the agent aborts cleanly and surfaces a *adk.InterruptError from Run, plus an interrupt event on the OnEvent stream. The caller persists a checkpoint (via CheckpointStore) and resumes later with the human's reply. Use for long-running workflows, ticket-based approvals, or queue-driven flows.
import "github.com/arvenza/helix/pkg/agent/tool"
func (t *escalateTool) Invoke(ctx context.Context, args string) (string, error) {
if needsHuman(args) {
return "", tool.Interrupt("escalate to compliance — needs human reviewer")
}
return process(args)
}
// Caller side
result, err := agent.Run(ctx, input)
if ie, ok := adk.AsInterrupt(err); ok {
persistTicket(ie.Tool, ie.Info) // surface to human queue
return // resume later via Runner with a CheckpointStore
}
Governance (cross-cuts every tool call)¶
The runtime-governance layer wires deterministic, app-layer guardrails around every tool call: scoped kill switches, 4-tier privilege rings, a rule-based policy engine with Starlang predicates, and a hash-chained tamper-evident audit log. Configure under agent.governance:.
agents:
- id: finance-agent
type: react
model: { provider: openai, name: gpt-4o-mini }
tools: [transfer, get_balance]
governance:
audit_chain: true
rings:
assignment: standard
permissions:
standard: [get_balance, transfer]
admin: ["*"]
policy:
- action: transfer
effect: review
starlang_when: |
ctx["args"]["amount"] > 10000
- action: get_balance
effect: allow
max_calls: 100
window: 1m
- action: "*"
effect: allow
Evaluation order per tool call: kill switches → privilege rings → policy engine → audit. First non-allow decision wins; the dispatcher feeds a synthetic Tool call denied by governance: <reason> tool_result back to the LLM so it can adjust its plan.
Blocks emit a governance_block AgentEvent:
agentCfg.OnEvent = func(e adk.AgentEvent) {
if e.Type == adk.EventGovernanceBlock {
slog.Warn("governance.block",
"agent", e.AgentID, "tool", e.Tool, "reason", e.Err)
}
}
Runtime control via the management API: POST /api/kill-switch (operator), GET /api/audit (viewer), POST /api/approvals/{id} (operator). See Governance Guide for the full schema, Go API, conflict resolver, MCP scanner, and prompt-defense classifier.
6. Agent Flows¶
Agent flows are composable processor patterns that implement engine.Processor. They can be nested, combined, and used anywhere a processor is expected -- including inside workflow steps.
6.1 SequentialFlow¶
Runs processors in order. Each processor receives the same exchange (mutations accumulate). Stops at the first error.
Exchange behavior: All steps share the same exchange. Step 2 sees the body and headers set by step 1.
6.2 ParallelFlow¶
Runs processors concurrently. Each processor receives a copy of the exchange. Results are aggregated back into the original exchange.
pf := flow.NewParallelFlow(
technicalReview,
businessReview,
riskReview,
)
// Optional: custom aggregation
pf.Aggregator = func(results []*exchange.Exchange, target *exchange.Exchange) {
var combined map[string]any
for _, r := range results {
// merge results...
}
target.In().SetBody(combined)
}
Default aggregation: Concatenates all branch bodies with newlines.
6.3 ConditionalFlow¶
Routes to different processors based on a predicate.
cf := flow.NewConditionalFlow(
func(ctx context.Context, ex *exchange.Exchange) (bool, error) {
return ex.In().HeaderString("priority") == "high", nil
},
urgentProcessor,
).WithElse(normalProcessor)
6.4 LoopFlow¶
Repeats a processor until a condition returns false or max iterations is reached. The condition is evaluated after each iteration.
lf := flow.NewLoopFlow(
refinementProcessor,
func(ctx context.Context, ex *exchange.Exchange, iteration int) (bool, error) {
score := ex.In().HeaderString("quality_score")
return score != "excellent", nil // continue if not excellent
},
10, // max iterations
)
6.5 PipelineFlow¶
Chains processors where each step gets a fresh exchange seeded with the previous step's output body and headers. Unlike SequentialFlow, steps are isolated.
pf := flow.NewPipelineFlow(
extractProcessor, // extracts data -> sets body
transformProcessor, // transforms body -> sets new body
loadProcessor, // loads body into destination
)
Key difference from SequentialFlow: PipelineFlow creates new exchanges between steps (only body and headers carry forward). SequentialFlow shares the same exchange throughout.
6.6 Combining Flows¶
Flows implement engine.Processor, so they compose naturally:
pipeline := flow.NewSequentialFlow(
flow.NewParallelFlow(researchAgent, dataAgent),
flow.NewConditionalFlow(isComplex, deepAnalysis).WithElse(quickSummary),
flow.NewLoopFlow(refinement, qualityCheck, 5),
)
7. Exposing Agents via Protocols¶
Helix supports four standard protocols for exposing agents to external systems. Each is implemented as a native component with its own URI scheme.
7.1 AG-UI (Frontend Streaming)¶
When to use: You want to expose agents to web or mobile frontends with real-time streaming.
Protocol: AG-UI (Agent-User Interaction). Used by CopilotKit and other frontend frameworks. Streams Server-Sent Events (SSE) back to the client.
URI scheme: agui://host:port/path?name=agent-name
Default port: 4100
SSE event flow:
RUN_STARTED
TEXT_MESSAGE_START (messageID, role=assistant)
TEXT_MESSAGE_CONTENT (messageID, "Hello")
TEXT_MESSAGE_CONTENT (messageID, " world")
...
TEXT_MESSAGE_END (messageID)
RUN_FINISHED
Complete YAML example:
helix:
agui:
servers:
- enabled: true
addr: "0.0.0.0:4100"
path: "/agent"
name: "my-assistant"
agents:
- id: chat-agent
type: react
trigger: direct://chat
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a helpful assistant."
memory:
enabled: true
type: sliding-window
window_size: 20
workflows:
- id: agui-server-chat
trigger: "agui://0.0.0.0:4100/agent?name=my-assistant"
steps:
- target: "direct://chat"
Frontend integration (JavaScript):
const response = await fetch('http://localhost:4100/agent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
thread_id: 'thread-1',
run_id: 'run-1',
messages: [
{ role: 'user', content: 'Explain quantum computing' }
]
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const event = JSON.parse(line.slice(6));
if (event.type === 'TEXT_MESSAGE_CONTENT') {
process.stdout.write(event.content);
}
}
}
}
CORS: The AG-UI server sets Access-Control-Allow-Origin: * by default.
7.2 A2A (Agent-to-Agent)¶
When to use: You want agents to communicate with other AI agents using the Google A2A protocol. Enables multi-system agent orchestration.
Protocol: A2A (Agent-to-Agent). Uses JSON-RPC over HTTP. Includes agent discovery via Agent Card.
URI schemes:
- Server: a2a-server://host:port?name=...&description=...&version=...
- Client: a2a://host:port (for calling external A2A agents)
Default port: 9000
Agent Card: Automatically published at /.well-known/agent-card.json. Contains agent name, description, skills, capabilities, and preferred transport.
Complete YAML example (Server):
helix:
a2a:
servers:
- enabled: true
addr: "0.0.0.0:9000"
name: "helix-support-agent"
description: "Customer support agent with billing and technical expertise"
version: "1.0.0"
agents:
- id: support-agent
type: supervisor
trigger: direct://support
description: "Handle customer support requests"
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "Route support requests to the appropriate specialist."
sub_agents:
- id: billing
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "Handle billing inquiries."
- id: technical
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Handle technical support requests."
workflows:
- id: a2a-server
trigger: "a2a-server://0.0.0.0:9000?name=helix-support&description=Support+Agent"
steps:
- target: "direct://support"
Calling an external A2A agent from a route:
workflows:
- id: delegate-to-external
trigger: "direct://delegate"
steps:
- target: "a2a://external-agent.example.com:9000"
Streaming: The A2A server sends streaming artifacts when the agent produces a *llm.StreamReader or chan string body. Chunks are sent as TaskArtifactUpdateEvent messages.
Skill discovery: The server auto-discovers agents registered via the agent component and invocable workflows (direct:// and seda://) to populate the Agent Card's skills list.
7.3 A2UI (Rich Declarative UI)¶
When to use: You want agents to render rich UIs -- cards, forms, tables, icons -- not just text. Used for dashboard agents, interactive assistants, and form-driven workflows.
Protocol: A2UI (Agent to UI). Google protocol. Streams JSONL messages over SSE with declarative UI components.
URI scheme: a2ui://host:port/path?name=agent-name
Default port: 4200
Request methods:
- GET /path?message=<text>&surface=<id> -- Initiates SSE stream with A2UI messages
- POST /path -- Sends client-to-server events (button clicks, form submissions)
A2UI message types:
- beginRendering -- Start rendering a surface
- updateComponents -- Update UI components (Column, Card, Row, Text, Icon, Form, Table, etc.)
- dataModelUpdate -- Update data model
Complete YAML example:
helix:
a2ui:
servers:
- enabled: true
addr: "0.0.0.0:4200"
path: "/surface"
name: "dashboard-agent"
agents:
- id: dashboard-agent
type: react
trigger: direct://dashboard
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a dashboard assistant. Analyze data and
provide insights in a structured format.
workflows:
- id: a2ui-server
trigger: "a2ui://0.0.0.0:4200/surface?name=dashboard-agent"
steps:
- target: "direct://dashboard"
Streaming behavior: When the agent produces a streaming body, the A2UI server progressively updates a Card component with accumulated text. Each chunk sends an updateComponents message with the full text so far.
Client events: The POST endpoint accepts ClientMessage JSON with a userAction event containing type, surfaceID, and componentID.
7.4 MCP (Model Context Protocol)¶
When to use: You want to expose Helix workflows as tools that other LLMs (Claude, ChatGPT, Cursor, etc.) can call. Or you want to consume tools from external MCP servers.
Protocol: MCP (Model Context Protocol). Standard for LLM tool use.
URI schemes:
- Server: mcp-server://host:port?name=...&transport=http&workflows=workflow1,workflow2
- Client: mcp://host:port/tool-name?transport=http
Transports: http (Streamable HTTP, default), sse (Server-Sent Events), stdio (standard I/O)
Complete YAML example (Server):
helix:
mcp:
servers:
- enabled: true
transport: http
addr: "0.0.0.0:8080"
name: "helix-tools"
version: "1.0.0"
workflows: ["search-docs", "query-db"] # Optional: filter routes (empty = all)
agents:
- id: search-agent
type: react
trigger: direct://search-docs
description: "Search the documentation knowledge base"
model:
provider: openai
model: gpt-4o-mini
api_key: "${OPENAI_API_KEY}"
system_prompt: "Search and summarize documentation."
workflows:
- id: query-db
trigger: "direct://query-db"
description: "Query the analytics database"
steps:
- target: "postgres://analytics?query=SELECT * FROM metrics WHERE name = :input"
- id: mcp-server
trigger: "mcp-server://0.0.0.0:8080?name=helix-tools&transport=http"
steps:
- log: "MCP server running"
What gets exposed: Only workflows with direct:// or seda:// trigger-URIs are exposed as MCP tools. Workflows with other triggers (timer, cron, kafka) are consumer-driven and cannot be called on demand.
Resources: The MCP server also exposes helix://workflows as a resource that lists all registered workflows with their status.
Consuming external MCP tools in a route:
workflows:
- id: use-external-tool
trigger: "direct://process"
steps:
- target: "mcp://localhost:3000/search?transport=http"
7.5 Multi-Protocol Gateway¶
Expose the same agent logic via all four protocols simultaneously:
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
agui:
servers:
- enabled: true
addr: "0.0.0.0:4100"
path: "/agent"
name: "gateway-agent"
a2a:
servers:
- enabled: true
addr: "0.0.0.0:9000"
name: "gateway-agent"
description: "Multi-protocol AI agent gateway"
a2ui:
servers:
- enabled: true
addr: "0.0.0.0:4200"
path: "/surface"
name: "gateway-agent"
mcp:
servers:
- enabled: true
addr: "0.0.0.0:8080"
name: "gateway-tools"
workflows:
# Core agent logic -- single route, multiple protocol frontends
- id: core-agent
trigger: "direct://agent"
description: "Core AI agent with tools and memory"
steps:
- agent:
id: gateway-agent
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a versatile AI assistant."
memory:
enabled: true
type: composite
backends:
- type: sliding-window
window_size: 20
- type: vector
top_k: 5
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
# Protocol servers -- all point to the same core-agent route
- id: agui-server
trigger: "agui://0.0.0.0:4100/agent?name=gateway-agent"
steps:
- target: "direct://agent"
- id: a2a-server
trigger: "a2a-server://0.0.0.0:9000?name=gateway-agent"
steps:
- target: "direct://agent"
- id: a2ui-server
trigger: "a2ui://0.0.0.0:4200/surface?name=gateway-agent"
steps:
- target: "direct://agent"
- id: mcp-server
trigger: "mcp-server://0.0.0.0:8080?name=gateway-tools"
steps:
- log: "MCP server active"
When to use: You have a single agent that serves multiple client types -- web frontends (AG-UI), other agents (A2A), rich UIs (A2UI), and LLM tools (MCP).
8. RAG-Augmented Agents¶
RAG Pipeline Overview¶
Document --> Ingest --> Chunk --> Embed --> Store
|
Query ------> Embed ------> Retrieve -------> +----> Augment --> Generate
Pipeline Types¶
| Stage | Type | Options |
|---|---|---|
| Ingest | rag-ingest |
Parsers: pdf, html, markdown, text, docx, csv, json, xml, starlang (10 total) |
| Chunk | rag-chunk |
Strategies: fixed, sentence, semantic, recursive, parent-child, token (6 total) |
| Embed | rag-embed |
Providers: openai, ollama, voyage, cohere (4 total) |
| Store | Vector store | Milvus, Qdrant, Pinecone, in-memory |
| Rerank | rag-rerank |
Strategies: mmr (Maximal Marginal Relevance), cross-encoder |
| Augment | rag-augment |
Template-based context injection |
Data Types¶
type Document struct {
ID string
Content string
Metadata map[string]string
Source string
}
type Chunk struct {
ID string
DocumentID string
Content string
Index int
Metadata map[string]string
}
type SearchResult struct {
Chunk *Chunk
Score float64
Embedding *Embedding
}
YAML Example: Agent with RAG Tool¶
workflows:
# Ingestion pipeline (run on startup or via API)
- id: ingest-docs
trigger: "direct://ingest"
steps:
- rag-ingest:
parser: markdown
- rag-chunk:
strategy: recursive
chunk_size: 512
overlap: 64
- rag-embed:
provider: openai
model: text-embedding-3-small
- target: "qdrant://knowledge-base?collection=docs"
# Query agent with RAG tool
agents:
- id: knowledge-bot
type: react
trigger: direct://ask
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a knowledge base assistant. Use the search_docs tool
to find relevant information before answering questions.
Always cite the source of your information.
memory:
enabled: true
type: sliding-window
window_size: 10
tools:
- name: search_docs
type: rag-query
description: "Search the documentation knowledge base"
config:
collection: "docs"
store: "qdrant://knowledge-base"
embed_provider: "openai"
embed_model: "text-embedding-3-small"
top_k: 5
rerank: "mmr"
rerank_lambda: 0.7
Supported Vector Stores¶
| Store | URI Scheme | Notes |
|---|---|---|
| Milvus | milvus://host:port |
Production-grade, distributed |
| Qdrant | qdrant://host:port |
High performance, filtering |
| Pinecone | pinecone://index-name |
Fully managed, serverless |
| In-memory | (default) | For development and testing only |
9. Advanced Patterns¶
9.1 Agent Processing Messages from Kafka¶
workflows:
- id: kafka-agent
trigger: "kafka://support-tickets?brokers=kafka:9092&group=agent-group"
steps:
- agent:
id: ticket-classifier
type: react
model:
provider: openai
model: gpt-4o-mini
api_key: "${OPENAI_API_KEY}"
system_prompt: |
Classify the support ticket into one of: billing, technical,
feature-request, complaint. Respond with JSON:
{"category": "...", "priority": "high|medium|low", "summary": "..."}
max_iterations: 3
- target: "kafka://classified-tickets?brokers=kafka:9092"
9.2 Agent with Database Context¶
workflows:
- id: contextual-agent
trigger: "direct://customer-query"
steps:
# Enrich with customer data before agent processing
- poll-enrich:
uri: "postgres://crm?query=SELECT * FROM customers WHERE id = ${header.customer_id}"
- set-header:
name: customer_context
expression: "${body}"
- agent:
id: customer-agent
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: |
You are a customer service agent. The customer's account
information is provided in the system context. Use it to
provide personalized assistance.
9.3 Multi-Agent Pipeline with Supervisor¶
agents:
- id: analysis-supervisor
type: supervisor
trigger: direct://analyze
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You supervise three analysis teams. Route each request to
the most appropriate specialist:
- data-analyst: for quantitative analysis and SQL queries
- research-agent: for qualitative research and web search
- report-writer: for generating final reports
sub_agents:
- id: data-analyst
type: plan-execute
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a data analyst. Create plans for data analysis."
max_iterations: 15
tools:
- name: run_sql
type: function
description: "Execute SQL query"
parameters:
type: object
properties:
query: { type: string }
required: ["query"]
- id: research-agent
type: deep
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "You are a research specialist."
deep_max_depth: 3
- id: report-writer
type: loop
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
Write a professional report based on the analysis.
Refine until the quality is excellent, then say DONE.
max_iterations: 3
9.4 Agent with Circuit Breaker¶
workflows:
- id: resilient-agent
trigger: "direct://resilient"
on-exception:
- exceptions: ["*"]
handled: true
steps:
- set-body:
expression: "I'm temporarily unable to process your request. Please try again later."
steps:
- circuit-breaker:
threshold: 5
half-open-after: "30s"
steps:
- agent:
id: main-agent
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a helpful assistant."
9.5 Agent with Audit Trail¶
helix:
audit:
enabled: true
store: "memory"
capacity: 10000
workflows:
- id: audited-agent
trigger: "direct://audited"
steps:
- agent:
id: audited-bot
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: "You are a compliance-aware assistant."
memory:
enabled: true
type: sql-long-term
dsn: "${DATABASE_URL}"
session_id: "${header.session_id}"
- target: "log://audit?level=INFO&showBody=true"
Every exchange processed through the workflow is recorded in the audit store with: exchange ID, workflow ID, timestamps, body snapshots (before/after), headers, and any errors.
10. Production Deployment¶
10.1 Monitoring Agents¶
Metrics (Prometheus-compatible):
Key metrics to monitor:
- helix_route_exchange_total{route="agent-route"} -- Total exchanges processed
- helix_route_exchange_duration_seconds -- Processing latency (with p50/p95/p99)
- helix_route_exchange_errors_total -- Error count
- helix_inflight_count -- Currently in-flight exchanges
Tracing (OpenTelemetry-compatible): - Each agent invocation generates a span with agent ID, type, and model info - Tool calls are traced as child spans - LLM calls include token usage attributes
Unified event stream (OnEvent). The recommended observability surface — a stable, mapped projection of every step the agent takes. Distinct from OnDebugEvent (raw, IDE-grade, gated by Debug):
agentCfg := adk.AgentConfig{
OnEvent: func(e adk.AgentEvent) {
slog.Info("agent.event",
"type", e.Type, // model_request / model_response / tool_call /
// tool_result / tool_error / approval_request /
// approval_decision / budget_exceeded / delegate /
// delegate_error / guardrail_blocked /
// governance_block / interrupt / loop_iteration /
// decompose / context_overflow_retry / ...
"iter", e.Iteration,
"tool", e.Tool,
"args", e.Args,
"result", e.Result,
"err", e.Err,
"ms", e.DurationMS, // milliseconds (int64)
)
},
}
AgentEvent is the stable wire form. RawType preserves the underlying internal type string for new event classes that fall through to EventOther. DurationMS is milliseconds end-to-end — the JSON name is duration_ms and the value is consistent across DebugEvent, AgentEvent, and the workflow trace surface.
10.2 Rate Limiting LLM Calls¶
Use the throttle processor before agent steps:
workflows:
- id: rate-limited-agent
trigger: "http://0.0.0.0:8080/chat"
steps:
- throttle:
rate: 10 # max 10 requests
period: "1m" # per minute
- agent:
id: chat-agent
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "You are a helpful assistant."
10.3 Cost Tracking via Token Usage Headers¶
After each agent invocation, the exchange carries token usage headers. Route these to your monitoring system:
workflows:
- id: tracked-agent
trigger: "direct://tracked"
steps:
- agent:
id: tracked-bot
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: "Respond concisely."
- target: "kafka://token-usage?brokers=kafka:9092"
Headers available after agent step: llm.prompt_tokens, llm.completion_tokens, llm.total_tokens, agent.id, agent.type, agent.model.
10.4 Graceful Shutdown¶
helix:
shutdown:
timeout: "30s" # Wait for in-flight exchanges to complete
drain: true # Stop accepting new exchanges before shutting down
The engine tracks all in-flight exchanges via InflightRepository. On shutdown:
1. Stops accepting new exchanges
2. Waits for in-flight exchanges to complete (up to timeout)
3. Stops all protocol servers (AG-UI, A2A, A2UI, MCP)
4. Stops stoppable processors (Throttle, Aggregate, Resequencer)
10.5 Scaling with Clustering¶
helix:
cluster:
enabled: true
bind_addr: "0.0.0.0"
bind_port: 7946
seeds: ["node2:7946", "node3:7946"]
Clustering provides: - Leader election for singleton agents - Distributed locks for exclusive processing - Redis-backed state sharing
11. Complete Examples¶
Example 1: Simple Chat Agent (Ollama, Conversation Memory, AG-UI)¶
A local chat agent using Ollama for inference, exposed via AG-UI for frontend streaming.
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
agui:
servers:
- enabled: true
addr: "0.0.0.0:4100"
path: "/chat"
name: "local-chat"
agents:
- id: local-assistant
type: react
trigger: direct://chat
description: "Local chat agent powered by Ollama"
model:
provider: ollama
model: llama3.1:8b
base_url: "http://localhost:11434"
system_prompt: |
You are a friendly, helpful local AI assistant. You run entirely
on the user's machine with no cloud dependencies. Be concise
but thorough in your responses.
memory:
enabled: true
type: conversation
max_iterations: 5
workflows:
- id: agui-server
trigger: "agui://0.0.0.0:4100/chat?name=local-chat"
steps:
- target: "direct://chat"
Run: helix run chat-agent.yaml
Access: POST to http://localhost:4100/chat with AG-UI RunAgentInput JSON.
Example 2: RAG Knowledge Agent (OpenAI, Vector Memory, Qdrant, A2A)¶
A knowledge base agent that ingests documents, stores embeddings in Qdrant, and answers questions using RAG. Exposed via A2A for inter-agent communication.
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
a2a:
servers:
- enabled: true
addr: "0.0.0.0:9000"
name: "knowledge-agent"
description: "Knowledge base agent with RAG-powered search"
version: "1.0.0"
agents:
# Knowledge query agent
- id: knowledge-bot
type: react
trigger: direct://ask
description: "Ask questions about the knowledge base"
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a knowledge base assistant. Always use the search_docs
tool to find relevant information before answering. Cite your
sources by mentioning the document name and section.
If you cannot find relevant information, say so clearly.
memory:
enabled: true
type: composite
backends:
- type: sliding-window
window_size: 10
- type: vector
top_k: 3
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
max_iterations: 8
tools:
- name: search_docs
type: rag-query
description: "Search the knowledge base for relevant documents"
config:
collection: "knowledge"
store: "qdrant://localhost:6333"
embed_provider: "openai"
embed_model: "text-embedding-3-small"
top_k: 5
rerank: "mmr"
rerank_lambda: 0.7
workflows:
# Document ingestion pipeline
- id: ingest-pipeline
trigger: "direct://ingest"
description: "Ingest documents into the knowledge base"
steps:
- rag-ingest:
parser: markdown
- rag-chunk:
strategy: recursive
chunk_size: 512
overlap: 64
- rag-embed:
provider: openai
model: text-embedding-3-small
- target: "qdrant://localhost:6333?collection=knowledge"
- target: "log://ingest?level=INFO&showBody=false"
# A2A server
- id: a2a-server
trigger: "a2a-server://0.0.0.0:9000?name=knowledge-agent&description=Knowledge+Agent"
steps:
- target: "direct://ask"
Agent Card: Automatically available at http://localhost:9000/.well-known/agent-card.json.
Example 3: Multi-Agent Supervisor (3 Sub-Agents, Plan-Execute, Tools)¶
A supervisor agent that delegates to three specialist sub-agents, each with different capabilities and LLM providers.
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
audit:
enabled: true
store: memory
capacity: 5000
agents:
- id: enterprise-supervisor
type: supervisor
trigger: direct://assist
description: "Enterprise AI assistant with specialized sub-agents"
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are an enterprise assistant supervisor. Analyze each request
and route to the best specialist:
- data-analyst: SQL queries, data analysis, reporting, metrics
- code-assistant: code generation, debugging, code review
- research-agent: market research, competitive analysis, trend reports
sub_agents:
- id: data-analyst
type: plan-execute
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a data analyst. Plan your analysis steps, then
execute them using available tools. Always validate your
SQL before running it.
max_iterations: 15
memory:
enabled: true
type: sliding-window
window_size: 15
tools:
- name: run_sql
type: function
description: "Execute a SQL query against the analytics database"
parameters:
type: object
properties:
query:
type: string
description: "SQL query to execute"
database:
type: string
enum: ["analytics", "warehouse"]
required: ["query"]
- name: create_chart
type: function
description: "Create a chart from query results"
parameters:
type: object
properties:
data:
type: string
description: "JSON data for the chart"
chart_type:
type: string
enum: ["bar", "line", "pie", "scatter"]
required: ["data", "chart_type"]
- id: code-assistant
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: |
You are an expert software engineer. Help with code
generation, debugging, and code review. Always explain
your reasoning and suggest best practices.
max_iterations: 10
memory:
enabled: true
type: sliding-window
window_size: 20
tools:
- name: run_tests
type: function
description: "Run unit tests for a code snippet"
parameters:
type: object
properties:
language:
type: string
enum: ["go", "python", "javascript"]
code:
type: string
required: ["language", "code"]
- name: external-code-tools
type: mcp
mcp_server:
url: "http://localhost:3001"
transport: http
tools: ["lint", "format", "analyze"]
- id: research-agent
type: deep
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
system_prompt: |
You are a research specialist. Break complex research
questions into sub-questions, research each thoroughly,
and synthesize comprehensive answers.
deep_max_depth: 3
max_iterations: 10
tools:
- name: web_search
type: function
description: "Search the web for information"
parameters:
type: object
properties:
query:
type: string
required: ["query"]
workflows:
- id: http-interface
trigger: "http://0.0.0.0:8080/assist"
steps:
- target: "direct://assist"
Example 4: Customer Support Agent (Anthropic, SQL Memory, Kafka Integration)¶
A production customer support agent that reads tickets from Kafka, persists conversation history in PostgreSQL, and writes classified results back to Kafka.
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
shutdown:
timeout: "30s"
drain: true
audit:
enabled: true
store: memory
capacity: 10000
logging:
level: INFO
format: json
workflows:
# Process incoming support tickets from Kafka
- id: ticket-processor
trigger: "kafka://support-tickets?brokers=${KAFKA_BROKERS}&group=support-agents&autoOffsetReset=earliest"
steps:
# Classify and respond to the ticket
- agent:
id: support-agent
type: react
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
max_tokens: 2048
system_prompt: |
You are a customer support agent for Acme Corp. Process each
support ticket and provide:
1. A classification (billing, technical, feature-request, complaint)
2. A priority level (critical, high, medium, low)
3. A draft response to the customer
4. Internal notes for the support team
Respond with JSON:
{
"classification": "...",
"priority": "...",
"customer_response": "...",
"internal_notes": "...",
"escalate": false
}
Use tools to look up customer information and check the
knowledge base before responding.
memory:
enabled: true
type: sql-long-term
dsn: "${DATABASE_URL}"
session_id: "${header.kafka.key}"
max_iterations: 8
tools:
- name: lookup_customer
type: function
description: "Look up customer information by email or ID"
parameters:
type: object
properties:
customer_id:
type: string
email:
type: string
required: []
- name: search_kb
type: rag-query
description: "Search the support knowledge base"
config:
collection: "support-kb"
store: "qdrant://qdrant:6333"
embed_provider: "openai"
embed_model: "text-embedding-3-small"
top_k: 3
- name: create_ticket
type: function
description: "Create or update a support ticket in the ticketing system"
parameters:
type: object
properties:
action:
type: string
enum: ["create", "update", "escalate"]
ticket_id:
type: string
data:
type: object
required: ["action"]
# Route based on classification
- choice:
when:
- expression: "${bodyAs(map).escalate} == true"
steps:
- target: "kafka://escalated-tickets?brokers=${KAFKA_BROKERS}"
- expression: "${bodyAs(map).priority} == 'critical'"
steps:
- target: "kafka://critical-tickets?brokers=${KAFKA_BROKERS}"
otherwise:
- target: "kafka://processed-tickets?brokers=${KAFKA_BROKERS}"
# Always log the result
- target: "log://support?level=INFO"
# Expose via AG-UI for internal support dashboard
agents:
- id: interactive-support
type: react
trigger: direct://support-chat
description: "Interactive support agent chat"
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${ANTHROPIC_API_KEY}"
system_prompt: |
You are an interactive support agent for the internal team.
Help agents resolve customer issues by searching the knowledge
base and customer records.
memory:
enabled: true
type: sliding-window
window_size: 30
max_iterations: 10
tools:
- name: lookup_customer
type: function
description: "Look up customer by ID"
parameters:
type: object
properties:
customer_id:
type: string
required: ["customer_id"]
- name: search_kb
type: rag-query
description: "Search support knowledge base"
config:
collection: "support-kb"
store: "qdrant://qdrant:6333"
embed_provider: "openai"
embed_model: "text-embedding-3-small"
top_k: 5
workflows:
- id: agui-support
trigger: "agui://0.0.0.0:4100/support?name=support-dashboard"
steps:
- target: "direct://support-chat"
Example 5: Multi-Protocol Agent Gateway (All 4 Protocols, Audit, Metrics)¶
A full-featured agent gateway exposing the same intelligent agent via all four protocols with enterprise features enabled.
helix:
management:
port: 8081
enabled: true
health:
enabled: true
port: 8082
shutdown:
timeout: "45s"
drain: true
logging:
level: INFO
format: json
components:
agent: DEBUG
mcp: DEBUG
audit:
enabled: true
store: memory
capacity: 50000
tenant:
enabled: true
header: "X-Tenant-ID"
required: false
agui:
servers:
- enabled: true
addr: "0.0.0.0:4100"
path: "/agent"
name: "helix-gateway"
a2a:
servers:
- enabled: true
addr: "0.0.0.0:9000"
name: "helix-gateway"
description: "Helix AI Gateway - multi-protocol agent with tools, memory, and RAG"
version: "2.0.0"
a2ui:
servers:
- enabled: true
addr: "0.0.0.0:4200"
path: "/surface"
name: "helix-gateway"
mcp:
servers:
- enabled: true
transport: http
addr: "0.0.0.0:8080"
name: "helix-gateway-tools"
version: "2.0.0"
workflows: ["search-docs", "query-analytics", "manage-tasks"]
workflows:
# ---- Core Agent ----
- id: gateway-agent
trigger: "direct://gateway"
description: "Primary AI gateway agent"
steps:
- throttle:
rate: 100
period: "1m"
- agent:
id: gateway-bot
type: react
model:
provider: openai
model: gpt-4o
api_key: "${OPENAI_API_KEY}"
temperature: 0.7
max_tokens: 4096
system_prompt: |
You are the Helix AI Gateway agent. You have access to:
- Document search (search_docs): Search the knowledge base
- Analytics queries (query_analytics): Query business data
- Task management (manage_tasks): Create and update tasks
- External MCP tools: Code analysis and formatting
Always search for relevant information before answering.
Be precise, cite sources, and offer to help further.
memory:
enabled: true
type: composite
backends:
- type: sliding-window
window_size: 25
- type: vector
top_k: 5
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
max_iterations: 12
tools:
- name: search_docs
type: rag-query
description: "Search the documentation knowledge base"
config:
collection: "docs"
store: "qdrant://qdrant:6333"
embed_provider: "openai"
embed_model: "text-embedding-3-small"
top_k: 5
rerank: "mmr"
- name: query_analytics
type: function
description: "Query the analytics database"
parameters:
type: object
properties:
query:
type: string
description: "SQL query"
database:
type: string
enum: ["analytics", "warehouse"]
required: ["query"]
- name: manage_tasks
type: function
description: "Create, update, or list tasks"
parameters:
type: object
properties:
action:
type: string
enum: ["create", "update", "list", "close"]
task_id:
type: string
title:
type: string
assignee:
type: string
required: ["action"]
- name: code-tools
type: mcp
description: "Code analysis and formatting tools"
mcp_server:
url: "http://code-tools:3000"
transport: http
# ---- Invocable utility workflows (exposed via MCP) ----
- id: search-docs
trigger: "direct://search-docs"
description: "Search the documentation knowledge base for relevant articles"
steps:
- target: "agent://gateway-bot"
- id: query-analytics
trigger: "direct://query-analytics"
description: "Query the analytics database with SQL"
steps:
- target: "postgres://analytics-db?query=${body}"
- id: manage-tasks
trigger: "direct://manage-tasks"
description: "Create, update, or list project tasks"
steps:
- target: "http://task-api:8080/tasks"
# ---- Protocol servers ----
- id: agui-server
trigger: "agui://0.0.0.0:4100/agent?name=helix-gateway"
steps:
- target: "direct://gateway"
- id: a2a-server
trigger: "a2a-server://0.0.0.0:9000?name=helix-gateway&description=Helix+AI+Gateway"
steps:
- target: "direct://gateway"
- id: a2ui-server
trigger: "a2ui://0.0.0.0:4200/surface?name=helix-gateway"
steps:
- target: "direct://gateway"
- id: mcp-server
trigger: "mcp-server://0.0.0.0:8080?name=helix-gateway-tools&transport=http"
steps:
- log: "MCP server active"
# ---- Ingestion pipeline ----
- id: doc-ingestion
trigger: "direct://ingest-docs"
description: "Ingest documents into the knowledge base"
steps:
- rag-ingest:
parser: markdown
- rag-chunk:
strategy: recursive
chunk_size: 512
overlap: 64
- rag-embed:
provider: openai
model: text-embedding-3-small
- target: "qdrant://qdrant:6333?collection=docs"
- target: "log://ingestion?level=INFO"
Ports summary: | Port | Protocol | Purpose | |---|---|---| | 4100 | AG-UI | Frontend streaming (CopilotKit) | | 4200 | A2UI | Rich declarative UI | | 8080 | MCP | Tool exposure for LLMs | | 8081 | HTTP | Management API + metrics | | 8082 | HTTP | Health checks | | 9000 | A2A | Agent-to-agent communication |
Run: helix run gateway.yaml
Appendix: Quick Reference¶
Agent YAML Schema¶
agent:
id: string # Required: unique agent identifier
type: string # react|supervisor|plan-execute|deep|sequential|parallel|loop
model: # Required: LLM configuration
provider: string # openai|anthropic|ollama
model: string # Model identifier
api_key: string # API key (supports ${ENV_VAR})
base_url: string # Custom API endpoint
temperature: float # 0.0-2.0
max_tokens: int # Maximum output tokens
top_p: float # Nucleus sampling
stop: [string] # Stop sequences
response_format: string # text|json_object
system_prompt: string # System message for the agent
max_iterations: int # Max reasoning iterations (default: 10)
deep_max_depth: int # Max recursion for deep agent (default: 3)
memory: # Memory configuration
type: string # conversation|sliding-window|summary|token-window|
# long-term|sql-long-term|vector|composite
store: string # memory(default)|redis|file|memx|postgres
window_size: int # For sliding-window
max_tokens: int # For token-window
max_messages: int # For summary
dsn: string # For sql-long-term
redis_url: string # For store: redis
file_path: string # For store: file
memx_url: string # For store: memx (postgres:// wire)
session_id: string # For sql-long-term / memx
top_k: int # For vector
connection: string # For external vector stores (qdrant/pinecone/milvus/pgvector/…)
collection: string # For external vector stores: collection/table/index name
api_key: string # For external vector stores: key/token
username: string # For external vector stores: opensearch/elasticsearch
password: string # For external vector stores: redis/opensearch/elasticsearch
embed: # For vector
provider: string
model: string
api_key: string
backends: [memory] # For composite
tools: # Tool definitions
- name: string
type: string # function|tool|a2a|mcp|connector
description: string
parameters: object # JSON Schema
config: object # Type-specific configuration
mcp_server: # For type=mcp
url: string
transport: string # http|sse|stdio
command: string # For stdio
args: [string] # For stdio
headers: {string: string}
tools: [string] # Filter specific tools
sub_agents: [agent] # For supervisor|sequential|parallel
URI Schemes¶
| Scheme | Direction | Purpose |
|---|---|---|
agent://id |
Producer (To) | Send messages to an agent |
agui://host:port/path |
Consumer (From) | AG-UI server |
a2a-server://host:port |
Consumer (From) | A2A server |
a2a://host:port |
Producer (To) | A2A client |
a2ui://host:port/path |
Consumer (From) | A2UI server |
mcp-server://host:port |
Consumer (From) | MCP server |
mcp://host:port/tool |
Producer (To) | MCP client |
Exchange Headers Set by Agents¶
| Header | Description |
|---|---|
agent.id |
Agent identifier |
agent.type |
Agent type (react, supervisor, etc.) |
agent.model |
Provider/model (e.g., openai/gpt-4o) |
llm.prompt_tokens |
Input tokens used |
llm.completion_tokens |
Output tokens generated |
llm.total_tokens |
Total tokens consumed |
HelixStream |
Set to "true" to enable streaming mode |
HelixAGUIThreadID |
AG-UI thread ID |
HelixAGUIRunID |
AG-UI run ID |
HelixA2ATaskID |
A2A task ID |
HelixA2UISurfaceID |
A2UI surface ID |
HelixMcpToolName |
MCP tool name |
Content-Type |
Set to "text/event-stream" for streaming responses |
12. Strict YAML Validation¶
Helix enforces strict validation on all agent YAML definitions at load time. Invalid configurations are rejected with clear error messages before any route starts.
Required Fields¶
| Field | Required For | Description |
|---|---|---|
id |
All agents | Unique agent identifier |
type |
All agents | Agent type: react, supervisor, plan-execute, deep, sequential, parallel, loop |
model.provider |
All agents | LLM provider: openai, anthropic, ollama |
model.model |
All agents | Model name (e.g., gpt-4o, claude-sonnet-4-20250514) |
sub_agents |
supervisor, sequential, parallel |
List of sub-agent definitions |
trigger |
Top-level agents: |
URI that triggers the agent (e.g., http-server://..., direct://...) |
Enum Validation¶
| Field | Valid Values |
|---|---|
type |
react, supervisor, plan-execute, deep, sequential, parallel, loop |
model.provider |
openai, anthropic, ollama |
memory.type |
conversation, sliding-window, summary, token-window, long-term, sql-long-term, vector, composite |
tools[].type |
function, tool, a2a, mcp, connector |
approval_policy |
never, always, tagged |
Tool Required Fields¶
Each tool type has specific required fields:
| Tool Type | Required Fields |
|---|---|
function |
name, description, parameters |
tool |
name, tool_id |
a2a |
name, description, url |
mcp |
name, mcp_server.url or mcp_server.command |
connector |
name, description, connector |
13. Human-in-the-Loop¶
Control when agents can act autonomously and when they must pause for human approval. The approval_policy field determines tool execution behavior.
Approval Policies¶
| Policy | Behavior |
|---|---|
never |
All tool calls execute automatically (default) |
always |
Every tool call pauses and waits for human approval |
tagged |
Only tools marked with requires_approval: true pause |
Configuration¶
agents:
- id: autonomous-agent
type: react
trigger: http-server://0.0.0.0:8080/agent
model:
provider: openai
model: gpt-4o
approval_policy: tagged
tools:
- name: search
type: function
description: "Search the knowledge base"
parameters:
type: object
properties:
query: { type: string }
required: ["query"]
# No requires_approval -- executes automatically
- name: delete_record
type: function
description: "Delete a database record"
requires_approval: true # Pauses for human approval
parameters:
type: object
properties:
record_id: { type: string }
required: ["record_id"]
- name: send_email
type: function
description: "Send an email to a customer"
requires_approval: true # Pauses for human approval
parameters:
type: object
properties:
target: { type: string }
subject: { type: string }
body: { type: string }
required: ["to", "subject", "body"]
When a tool with requires_approval: true is called and approval_policy: tagged is set, the agent sets the exchange header agent.approval.pending to true and returns the pending tool call details as a JSON body. The frontend or orchestrator can then approve or reject the call.
14. Agent Memory with MemX Backend¶
For production agents that need durable memory that survives restarts, set
store: memx. memx is reached over the PostgreSQL wire, so the connection is a
postgres:// URL supplied in memx_url. It works with every non-sql-long-term
memory type:
- conversation / long-term → a native durable, append-only message store
(optionally semantic when
embedis set). - sliding-window / token-window / summary / smart → the in-memory type keeps its retrieval semantics on read while every message is persisted to memx, so a restart replays the history and re-derives the window.
- vector → a durable semantic store (
type: vector); embeddings survive a restart. The table is sized to the embedding model automatically.
The backing table(s) are created automatically on startup.
Configuration¶
agents:
- id: production-agent
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: openai
model: gpt-4o
memory:
enabled: true
type: vector
top_k: 10
session_id_header: X-Session-ID
store: memx
memx_url: "postgres://user:pass@localhost:5432/memx" # postgres:// wire
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
| Parameter | Required | Description |
|---|---|---|
store |
Yes | memx |
memx_url |
Yes | memx connection (postgres://…); dsn is accepted as a fallback |
session_id / session_id_header |
No | scope key (static, or per-request from a header) |
embed |
Vector only | embedding provider/model (also enables semantic search on conversation/long-term) |
Composite Memory with MemX¶
Combine a sliding window (recent context) with memx vector search (durable long-term recall):
agents:
- id: advanced-agent
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: anthropic
model: claude-sonnet-4-20250514
memory:
enabled: true
type: composite
session_id_header: X-Session-ID
backends:
- type: sliding-window
window_size: 20
- type: vector
top_k: 10
store: memx
memx_url: "postgres://user:pass@localhost:5432/memx"
embed:
provider: openai
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
15. Multimodal Agents¶
Agents can process images, voice, and file uploads natively via the AG-UI protocol. This enables rich chat interfaces where users can send any combination of text, images, documents, and audio.
Image Analysis Agent¶
agents:
- id: vision-agent
type: react
trigger: agui://0.0.0.0:4100/agent
model:
provider: openai
model: gpt-4o
system_prompt: |
You are a multimodal assistant. You can see images, read documents,
and listen to audio. Respond based on whatever the user provides.
Voice-Enabled Agent¶
agents:
- id: voice-agent
type: react
trigger: agui://0.0.0.0:4100/voice
model:
provider: openai
model: gpt-4o
system_prompt: "You are a voice-enabled assistant. Keep responses concise and conversational."
When audio is received without text, the agent automatically transcribes the audio before processing. Supported formats: WAV, WebM, MP3, OGG, M4A.
File Processing Agent¶
agents:
- id: file-agent
type: react
trigger: agui://0.0.0.0:4100/files
model:
provider: openai
model: gpt-4o
system_prompt: |
You process uploaded files:
- Images: describe and analyze
- PDFs: extract text, summarize, answer questions
- CSV: analyze data, compute statistics
- Text: summarize and explain
Multimodal Content Types¶
| Input Type | How It Arrives | Agent Behavior |
|---|---|---|
| Image (JPEG, PNG, etc.) | ContentPart with type: image |
Sent to vision-capable LLM |
| Voice recording | ContentPart with type: audio |
Auto-transcribed, then processed as text |
| PDF document | ContentPart with type: file |
Extracted via reader or OCR, then processed |
| CSV / data file | ContentPart with type: file |
Parsed into structured text |
| Plain text | ContentPart with type: text |
Standard text processing |
For PDF OCR, configure the helix.ocr section in helix.yaml. See the Configuration Guide for backend options.
16. Built-in Function Registry¶
Helix ships with 14 ready-to-use built-in functions that agents can call without writing any Go code. Declare them in YAML with type: function and set the function: field to a registry key. No implementation needed -- Helix provides the logic.
16.1 Available Functions¶
| Category | Function | Description |
|---|---|---|
| File System | fs.read |
Read a file and return its contents |
| File System | fs.write |
Write content to a file |
| File System | fs.edit |
Apply a targeted string replacement to a file |
| File System | fs.list |
List files and directories at a path |
| File System | fs.glob |
Find files matching a glob pattern |
| File System | fs.grep |
Search file contents with a regex pattern |
| File System | fs.mkdir |
Create a directory (with parents) |
| File System | fs.delete |
Delete a file or empty directory |
| Shell | shell.exec |
Execute a shell command (sandboxed) |
| Web | web.fetch |
Fetch a URL, strip HTML to text, truncate to 30KB |
| Data | data.json_parse |
Parse JSON string with optional path extraction |
| Data | data.json_format |
Pretty-print a JSON value |
| Data | data.csv_parse |
Parse CSV text into an array of objects |
| Math | math.eval |
Evaluate a math expression |
16.2 YAML Syntax¶
Use type: function with a function: field referencing the registry key:
agents:
- id: code-agent
type: react
trigger: http-server://0.0.0.0:8080/code
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${env:ANTHROPIC_API_KEY}"
system_prompt: |
You are a coding assistant. Use the tools to read, edit,
and search files in the project directory.
tools:
- name: read-file
type: function
function: fs.read
description: Read a file from disk
- name: write-file
type: function
function: fs.write
description: Write content to a file
- name: edit-file
type: function
function: fs.edit
description: Apply a targeted edit to a file
- name: list-files
type: function
function: fs.list
description: List directory contents
- name: find-files
type: function
function: fs.glob
description: Find files matching a pattern
- name: search-code
type: function
function: fs.grep
description: Search file contents with regex
- name: run-command
type: function
function: shell.exec
description: Execute a shell command
- name: fetch-url
type: function
function: web.fetch
description: Fetch a web page
16.3 Security -- shell.exec¶
The shell.exec function is sandboxed with the following protections:
- Timeout: Commands are killed after a configurable timeout (default 30 seconds)
- Blocked commands: Dangerous commands are rejected (e.g.,
rm -rf /,shutdown,reboot,mkfs,dd) - Working directory: Constrained to the agent's configured
--dirpath - No shell expansion: Commands are executed directly, not through a shell interpreter
16.4 Complete Code Agent Example¶
agents:
- id: code-assistant
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${env:ANTHROPIC_API_KEY}"
system_prompt: |
You are an expert coding assistant. You can read, write, and search
files in the current project. Always read a file before editing it.
Use grep to find relevant code. Use shell.exec to run tests.
memory:
enabled: true
type: smart
max_tokens: 100000
tools:
- name: read-file
type: function
function: fs.read
description: Read a file
- name: write-file
type: function
function: fs.write
description: Write content to a file
- name: edit-file
type: function
function: fs.edit
description: Apply a string replacement to a file
- name: list-dir
type: function
function: fs.list
description: List directory contents
- name: find-files
type: function
function: fs.glob
description: Find files matching a glob pattern
- name: search
type: function
function: fs.grep
description: Search file contents with regex
- name: run
type: function
function: shell.exec
description: Run a shell command
- name: fetch
type: function
function: web.fetch
description: Fetch a URL
- name: parse-json
type: function
function: data.json_parse
description: Parse a JSON string
- name: calc
type: function
function: math.eval
description: Evaluate a math expression
17. SmartMemory -- Token-Managed Memory¶
SmartMemory is a token-aware memory backend designed for tool-using agents (especially code agents). Unlike sliding-window memory which drops messages by count, SmartMemory tracks token usage and intelligently compresses the conversation to stay within budget.
17.1 Why SmartMemory?¶
Tool-using agents produce large messages -- a single fs.read result can be thousands of tokens. Traditional turn-based memory (sliding window of N messages) doesn't account for this. SmartMemory solves this by:
- Tracking tokens, not turns -- eviction is based on actual token count, not message count
- Compressing large tool results -- tool outputs over a threshold are compressed to first 10 + last 10 lines with a summary
- Maintaining a context map -- evicted messages are summarized so the agent retains awareness of what it previously saw
- Preserving pairs -- tool call and tool response messages are always dropped together (never orphaned)
- Protecting system messages -- the system prompt is never evicted
17.2 Configuration¶
agents:
- id: code-agent
type: react
trigger: http-server://0.0.0.0:8080/chat
model:
provider: anthropic
model: claude-sonnet-4-20250514
memory:
enabled: true
type: smart # or "code" (alias for smart)
max_tokens: 100000 # token budget for conversation history
| Field | Default | Description |
|---|---|---|
type |
-- | smart or code (alias) |
max_tokens |
100000 |
Maximum tokens allowed in conversation history before compression kicks in |
17.3 How It Works¶
- After each agent turn, SmartMemory counts the total tokens in the conversation
- If the total exceeds
max_tokens, the oldest non-system messages are evicted - Tool call + tool result pairs are evicted together
- Large tool results (e.g., file contents) are compressed: first 10 lines + "..." + last 10 lines
- A context map entry is created for each evicted block summarizing what was discussed
- The context map is prepended to the system message so the agent knows what it previously explored
18. Playwright MCP Browser Tools¶
Agents can browse the web interactively using Playwright MCP. This gives agents 22 browser automation tools including navigation, clicking, typing, screenshots, and DOM inspection.
18.1 Configuration¶
agents:
- id: browser-agent
type: react
trigger: http-server://0.0.0.0:8080/browse
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${env:ANTHROPIC_API_KEY}"
system_prompt: |
You are a web research agent. Use the browser tools to navigate
websites, extract information, and take screenshots.
tools:
- name: browser
type: mcp
description: "Browser automation via Playwright"
mcp_server:
command: npx
args: ["@playwright/mcp"]
transport: stdio
18.2 How It Works¶
The type: mcp tool with transport: stdio spawns the Playwright MCP server as a subprocess. Helix discovers all 22 tools via the MCP protocol and makes them available to the agent. The agent can then call individual tools like browser_navigate, browser_click, browser_type, etc.
18.3 Available Browser Tools¶
| Tool | Description |
|---|---|
browser_navigate |
Navigate to a URL |
browser_click |
Click an element |
browser_type |
Type text into an input |
browser_snapshot |
Get an accessibility snapshot of the page |
browser_take_screenshot |
Capture a screenshot |
browser_go_back |
Go back in browser history |
browser_go_forward |
Go forward in browser history |
browser_wait |
Wait for a condition |
browser_select_option |
Select a dropdown option |
browser_hover |
Hover over an element |
browser_drag |
Drag an element |
browser_press_key |
Press a keyboard key |
browser_scroll |
Scroll the page |
browser_fill |
Fill a form field |
browser_get_text |
Get text content of an element |
browser_get_attribute |
Get an attribute value |
browser_evaluate |
Evaluate JavaScript in the page |
browser_new_tab |
Open a new tab |
browser_close_tab |
Close a tab |
browser_switch_tab |
Switch to a tab |
browser_file_upload |
Upload a file |
browser_pdf |
Save page as PDF |
18.4 Prerequisites¶
Install Playwright MCP:
18.5 Browser Agent Example¶
agents:
- id: web-researcher
type: react
trigger: http-server://0.0.0.0:8080/research
model:
provider: anthropic
model: claude-sonnet-4-20250514
api_key: "${env:ANTHROPIC_API_KEY}"
system_prompt: |
You are a web research agent. Navigate to websites, read their content,
and summarize findings. Use browser_snapshot to read page content
(it's faster than screenshots). Take screenshots only when visual
context matters.
memory:
enabled: true
type: smart
max_tokens: 100000
tools:
- name: browser
type: mcp
description: "Browse the web with Playwright"
mcp_server:
command: npx
args: ["@playwright/mcp"]
transport: stdio
- name: fetch
type: function
function: web.fetch
description: "Quick-fetch a URL as plain text (no JS rendering)"