Subagents

Subagents let the agent delegate a self-contained task to another, independently configured agent — its own persona, its own model/provider, optionally its own restricted tool set — and get back only the final answer. They're useful for splitting work across specialised personas (a reviewer, a researcher, a fact-checker, …) or for routing routine subtasks to a cheaper/faster model while the orchestrator runs on a stronger one.

This mirrors how skills work — drop a Markdown file in a directory and it's picked up automatically — except a subagent profile defines a whole separate agent rather than a snippet appended to the current one.

Subagents come in two flavours:

  • Named profiles — .md files in ~/.openheim/agents/, written by you ahead of time.
  • Inline subagents — defined by the orchestrating agent itself, on the fly, in the delegate_task call (a system_prompt plus optional overrides). These are ephemeral: they exist only for that one call and are never persisted.

Where subagent profiles live

Profiles are .md files in ~/.openheim/agents/:

~/.openheim/agents/
├── code-reviewer.md
├── researcher.md
└── fact-checker.md

The filename (without extension) is the subagent's name. Names are case-sensitive and must not contain spaces.

The delegate_task tool's description lists every available profile by name and description, so the LLM knows what's available and when to reach for it. The tool is always present — even with no profiles configured, the orchestrator can still spin up inline subagents (see below).


Writing a profile

A profile file is Markdown with an optional +++-delimited TOML frontmatter block, followed by the subagent's system prompt:

+++
description = "Reviews code changes for correctness, security, and style. Use for a second opinion on a diff."
model = "claude-haiku-4-5"
provider = "anthropic"
tools = ["read_file"]
max_iterations = 6
+++
You are a meticulous code reviewer focused on correctness, security, and idiomatic style.
Always cite file:line references for issues you find.

All frontmatter fields are optional:

Field Type Effect
description string Shown to the orchestrator so it knows when to delegate to this subagent. Omitting it is allowed but makes the subagent harder for the LLM to discover.
model string Overrides the parent's model for this subagent's run. Omitted = the parent session's current model, including after a mid-session model switch.
provider string Overrides the parent's provider. Only used when model is also set.
tools array of strings Restricts the subagent to this set of tool names. Omitted = inherits the full tool set the parent has access to.
max_iterations integer Caps the subagent's agent-loop iterations. Omitted = inherits the parent's max_iterations.

A file with no frontmatter is treated as a profile with an empty description whose entire contents are the system prompt. A profile whose frontmatter fails to parse is skipped (with a warning logged) rather than preventing startup.


Inline subagents

The orchestrating agent can also create a subagent on the fly, without any profile file, by calling delegate_task with a system_prompt instead of an agent name:

{
  "system_prompt": "You are a fact-checker. Respond with a verdict and a one-paragraph explanation.",
  "tools": ["read_file"],
  "model": "claude-haiku-4-5",
  "max_iterations": 4,
  "task": "Verify the claim: water boils at 100°C at sea level."
}

tools, model, provider, and max_iterations are optional and mean exactly what the corresponding profile frontmatter fields mean, except that an inline max_iterations can't exceed the parent's own. Exactly one of agent or system_prompt must be provided. Giving both, neither, or an unknown agent name fails the tool call with an error explaining the fix.

Inline subagents are ephemeral by design: nothing is written to ~/.openheim/agents/ and nothing survives the call. They run through the same machinery as named profiles, so all the sandboxing, permission, and no-recursion guarantees below apply identically.


How a delegated task runs

When the orchestrator calls delegate_task with a task and either an agent name or an inline system_prompt, openheim:

  1. Resolves the subagent's model/provider/iteration cap from the profile (falling back to the parent's configuration for anything left unset).
  2. Builds a tool executor scoped to the profile's tools allowlist (if any). It runs under the same work_dir sandbox boundary as the parent (the boundary travels in the turn context every tool receives) and execute_command is only present if allow_shell enabled it for the parent — subagents cannot escalate privileges beyond what the parent has.
  3. Starts a fresh, isolated agent run: its own message history (just the task as the first user message) and its own system prompt (the profile's persona — not the parent's system.md or skills) — but the same cancellation token and permission gate as the orchestrating turn. Every tool call the subagent makes is checked exactly like the orchestrator's own (e.g. an interactive client still sees session/request_permission for a subagent's shell command), and a session/cancel on the parent turn stops an in-flight subagent too. There is no separate, more-trusting policy for subagents. Since the subagent's own tool calls aren't shown, the prompt names the subagent asking: the TUI adds a from subagent '<name>' line, and ACP titles the request <tool> (subagent '<name>'). An "Allow Always" answer covers the whole session, parent and subagents alike.
  4. Runs that agent loop to completion and returns only its final answer to the orchestrator. Intermediate tool calls and reasoning are not visible to the parent — the subagent genuinely runs "in the background", much like Claude Code's Task subagents.

Because the subagent is built from a tool executor that does not include delegate_task itself, subagents cannot delegate further — there is no recursive chain to worry about.

Tip: since the subagent cannot see the parent conversation, write task descriptions in the calling prompt (or trust the orchestrator to write them) as complete, self-contained briefs. The LLM is told this in the tool description, but it helps to reinforce it in your system.md or skills if you rely on subagents heavily.


Example

~/.openheim/agents/fact-checker.md:

+++
description = "Verifies a specific factual claim and reports whether it's accurate, with sources if possible."
max_iterations = 4
+++
You are a fact-checker. Given a claim, determine whether it is accurate.
Respond with a verdict (Accurate / Inaccurate / Uncertain) followed by a one-paragraph explanation.
Be concise — you have a limited number of steps to reach a conclusion.

With this file in place, asking the orchestrator something like "use the fact-checker subagent to verify that water boils at 100°C at sea level" causes it to call delegate_task with {"agent": "fact-checker", "task": "Verify the claim: water boils at 100°C at sea level."} and relay the fact-checker's verdict back to you.