Skip to content

ReAct agent

ReAct (Reason + Act) is the default Helix agent orchestration: a single LLM that interleaves "thoughts" and tool calls in one loop until it produces an answer without further tool calls. It is the right choice for almost every agent — pick another type only when you have a specific structural need (supervisor for delegation, sequential/parallel for fixed pipelines, plan-execute when planning explicitly helps, etc.). When type: is omitted in YAML, the framework falls back to react.

Type identifier

YAML form:

agents:
  - id: my-agent
    type: react      # default when omitted
    model: { ... }
    tools: [ ... ]

What it does

runReAct in pkg/agent/adk/agent.go drives the loop:

  1. Build the message list: rendered system prompt → memory snapshot (a.memory().Get(ctx)) → the user message. If InputGuardrails are configured the user message passes through them before being appended (block aborts the run; a Modified value rewrites it in place — e.g. PII redaction).
  2. Convert the registry to LLM-shaped tool definitions (a.config.Tools.Definitions()) and pass them with tool_choice: auto so the model can decide whether to call any.
  3. Iterate up to MaxIterations (default 10):
  4. Call the model with the running message list, recording a llm_request debug event and an exported OTel span when tracing is on. generateWithOverflowRecovery will summarise and retry if the provider returns a context-overflow error.
  5. Emit llm_response, feed the response's prompt/completion tokens to the per-run Budget, and append the assistant message to the working history.
  6. No tool calls in this turn? Run OutputGuardrails against the final answer, write it to memory, finalize the trace, and return.
  7. Tool calls? Dispatch each via runToolCalls — either sequentially (default) or with bounded concurrency when ParallelTools.Enabled. For every call: unknown-tool synthetic error → Budget.AddToolCall → Governance gate → human-in-the-loop approval (when required by policy / category / explicit list, skipped if an active skill's allowed-tools covers it) → ToolArgsGuardrails → tool.Invoke(ctx, args) → ToolResultGuardrails → append the result as a role: tool message. A tool.Interrupt error aborts the run cleanly with a typed *InterruptError so the caller can resume from a checkpoint.
  8. If MaxIterations is reached without a tool-call-free response, the run errors with max iterations (N) reached — a misconfiguration signal, not a normal completion.

Streaming (RunStream) reuses the same loop in runReActStream but returns a *llm.StreamReader that emits chunks live. Streaming only streams the FINAL response — intermediate tool-call iterations still go through Generate, then the final text streams either as real tokens (no output guardrails) or via the chosen OutputStreamMode (synthesize / window / passthrough).

Multimodal input goes through runReActMultimodal — same shape, but the user turn carries Parts (image / audio / file content) on top of the text. RAG-style retrieval is typically wired in by giving the agent a retrieval tool rather than by changing the loop.

Configuration

Every YAML field below maps to adk.AgentConfig via the reifier (pkg/dsl/reifier.go). Only ReAct-relevant fields are listed; see AgentDefinition in pkg/workflow/definition.go for the full struct.

Field Type Default Description
id string — Stable identifier. Used in error messages, traces, and as the supervisor's delegate_to_<id> tool name.
type string react Set explicitly or omit.
model block required LLM provider config (provider + model + api_key etc.).
system_prompt string "" Free-form. {{ vars.X }} substitutions from prompt_vars are rendered per call.
prompt_vars map — Static substitutions exposed as vars.X in system_prompt.
tools list — Tool refs by name + type (function / pipeline / mcp / a2a / connector).
memory block conversation See memory. Defaults to in-process ConversationMemory when absent.
max_iterations int 10 Hard cap on the think-act-observe loop. Reaching it errors.
guardrails block — Input / output / tool_args / tool_result chains. See guardrails.
skills list — Skill IDs. Attaching any skill auto-wires the three list_skills / load_skill / load_skill_resource tools. See skills.
approval_policy string never One of never, always, tagged.
approval_tools list — Tool names requiring human approval when policy: tagged.
approval_categories list — Tool category labels requiring approval when policy: tagged.
tool_retry block — Default RetryPolicy wrapping every registered tool.
parallel_tools block sequential { enabled, max_concurrency, skip_when_approval }. When enabled, multiple tool calls in one LLM turn dispatch concurrently.
budget block — Per-run caps: max_invocations, max_tool_calls, max_input_tokens, max_output_tokens. Zero = no limit per counter.
governance block — Runtime governance: kill-switch → rings → policy → audit. Block decisions surface as synthetic tool results, not aborts.
injection_defense block on { enabled, prefix }. Auto-prepends an anti-prompt-injection preamble to the system prompt.
model_middleware list — Named LLM middleware (e.g. logging).
memory_middleware list — Named memory middleware (e.g. logging).
debug bool false Emit the rich DebugEvent stream (system prompt, memory snapshot, full tool defs).

Behavior notes

  • ReAct is the only orchestration type that supports true token streaming via RunStream. The agent's helper falls back to a word-chunked replay for non-ReAct types.
  • OnEvent is the recommended observability surface — one ordered stream of start / llm_request / llm_response / tool_call / tool_result / approval_* / guardrail_* / etc. OnDebugEvent is the IDE-flavoured rich-field variant, gated by Debug: true.
  • Per-run state is keyed by a tool.RunID stamped on the context, which is what WithIdempotency(scope: "run") middleware reads to dedupe repeat calls inside one run.
  • A tool.Interrupt(info) from inside any tool aborts the loop with a typed *InterruptError. Use Runner.Interrupt to checkpoint and Runner.Resume to continue.

Example

A research agent that searches the web, reads a PDF, and answers:

agents:
  - id: researcher
    type: react
    model:
      provider: anthropic
      model: claude-sonnet-4-7
    system_prompt: |
      You are a research assistant. Cite sources from web results.
    memory:
      type: sliding-window
      window_size: 30
    tools:
      - { name: web-fetch, type: function, function: web.fetch }
      - { name: read-file, type: function, function: fs.read }
    guardrails:
      output:
        - { type: pii, action: redact }
    budget:
      max_tool_calls: 20
      max_input_tokens: 200000

Source

  • pkg/agent/adk/agent.go — runReAct, runReActStream, runReActMultimodal, Run, the AgentType switch.
  • pkg/agent/adk/parallel_tools.go — runToolCalls dispatcher.
  • pkg/agent/adk/stream_guardrails.go — streaming guardrail modes.
  • pkg/workflow/definition.go — AgentDefinition YAML schema.
  • pkg/dsl/reifier.go — reifyAgent builds the adk.AgentConfig.

See also