openheim 0.5.0

A fast, multi-provider LLM agent runtime written in Rust
Documentation
# 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](skills.md) 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.

---

## 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.

If the directory contains at least one valid profile, the orchestrating agent gains
a `delegate_task` tool whose description lists every available subagent by name and
description, so the LLM knows what's available and when to reach for it. If the
directory is empty, `delegate_task` is not added — there's no overhead for agents
that don't use subagents.

---

## Writing a profile

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

```markdown
+++
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. |
| `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.

---

## How a delegated task runs

When the orchestrator calls `delegate_task` with an `agent` name and a `task`
description, 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) and
   wrapped in the **same sandbox boundary** (`work_dir` / `allow_shell`) as 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.
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`:

```markdown
+++
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.