Sub-Agents
Sub-agents are child agent instances spawned by the harness to run a scoped task with a filtered toolset. The Zellij host opens a plugin pane; Montana starts another component instance on a thread in the same process. Both use files to communicate with the parent. Montana peers share its preopened directories and HTTP client; spawning a peer does not create another OS security boundary.
Source: crates/core/src/subagent.rs, crates/core/src/spawn.rs,
crates/core/src/subagent_config.rs.
How spawning works
The parent calls the built-in spawn_subagent tool. The harness then:
- Writes the task to
prompt.mdand the filtered tool manifest totools.jsonin a new directory under<cowboy_dir>/subagents/. - Requests a peer through the host, passing a JSON-encoded
SubagentConfigunder thesubagent_configkey. Zellij loads a visible plugin pane; Montana’s supervisor creates a component instance with its own agent ID. - The child, on its first heartbeat tick, writes a
lockfile and itspane_id, readsprompt.md+tools.json, and processes the prompt as a user message. - When the child finishes, it writes
response.mdand removeslock. - The parent detects completion on its heartbeat poll and injects the response back into its own conversation.
The shared spawn protocol uses host-executed shell commands for its spool files. WebAssembly does not itself forbid file I/O: Montana also grants direct WASI access to preopened directories. Pane visibility is a Zellij affordance; Montana has no terminal panes.
Directory layout
Each sub-agent gets a directory named <type>-<short_uuid>:
<cowboy_dir>/subagents/<type>-<id>/
prompt.md # task + metadata, written by parent
tools.json # filtered tool manifest, written by parent
lock # present while running, written by child
pane_id # host instance id (a pane id under Zellij)
response.md # final output, written by child on completion
<cowboy_dir> is $HOME on a ranch install and
$XDG_DATA_HOME/cowboy (default ~/.local/share/cowboy) in lite mode.
Sub-agent types
Three types are defined in the SubAgentType enum. Each restricts the tools
the child may call:
| Type | Allowed tools |
|---|---|
| Research | read, search, find, web-search, ls |
| Code | read, write, search, find, bash, ls, __HASHLINE_READ__, __HASHLINE_EDIT__ |
| Review | read, search, find, bash, ls |
Only Code gets write and the hashline edit tool. Review still has bash,
so its tool list does not enforce read-only filesystem access. The configured
host and tool policy determine what commands can do.
Models
SubagentConfig::cheap_defaults() selects the child’s provider and model.
Its fallback model names are:
| Provider | Default sub-agent model |
|---|---|
| OpenAI | gpt-4o-mini |
| OpenRouter | openai/gpt-4o-mini |
| Anthropic | claude-3-5-haiku-latest |
| Ollama | mistral:7b |
| Codex | gpt-5.6-luna |
| Vercel | zai/glm-5.3-flash |
An explicit subagent_model wins when that provider has a non-empty key.
Otherwise the main harness provider/model is
used when it is usable (a key, or a keyless Ollama/Codex provider), falling back
to the first keyed provider in the order OpenRouter, Anthropic, OpenAI, Vercel —
so the Ollama and Codex defaults above are only reached through the main
model.
Status
The parent tracks each child with the SubAgentStatus enum:
Starting— directory created, peer startingRunning—lockpresentCompleted—response.mdpresent,lockremovedFailed(String)—lockremoved without aresponse.md
Polling happens on the heartbeat timer.