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 —
.mdfiles in~/.openheim/agents/, written by you ahead of time. - Inline subagents — defined by the orchestrating agent itself, on the fly, in
the
delegate_taskcall (asystem_promptplus 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:
- Resolves the subagent's model/provider/iteration cap from the profile (falling back to the parent's configuration for anything left unset).
- Builds a tool executor scoped to the profile's
toolsallowlist (if any). It runs under the samework_dirsandbox boundary as the parent (the boundary travels in the turn context every tool receives) andexecute_commandis only present ifallow_shellenabled it for the parent — subagents cannot escalate privileges beyond what the parent has. - Starts a fresh, isolated agent run: its own message history (just the
taskas the first user message) and its own system prompt (the profile's persona — not the parent'ssystem.mdor 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 seessession/request_permissionfor a subagent's shell command), and asession/cancelon 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 afrom subagent '<name>'line, and ACP titles the request<tool> (subagent '<name>'). An "Allow Always" answer covers the whole session, parent and subagents alike. - 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
Tasksubagents.
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
taskdescriptions 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 yoursystem.mdor 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.