Subagents
Agents a run hands tasks to, defined as Markdown files in Claude Code's format, with approvals that bubble up, background runs and git worktree isolation.
Overview
A subagent is an agent a run delegates a task to. It starts fresh, with its own system prompt, tools and model, does the work in its own context and hands back one result. Cogitator reads subagents in the format of Claude Code: a Markdown file with YAML frontmatter in an agents/ folder, or a definition in code shaped like Claude Code's --agents JSON. A repository that has .claude/agents/ for Claude Code works here as it is, the differences are listed below.
---
name: reviewer
description: Reviews code changes. Use it after edits.
tools: Read, Grep, Glob
model: sonnet
permissionMode: plan
maxTurns: 8
---
You are a senior reviewer. Report concrete problems with file and line.import { Cogitator, Agent, workspaceTools, LocalWorkspace } from '@cogitator-ai/core';
const workspace = new LocalWorkspace({ root: '/srv/repo' });
const cog = new Cogitator({
llm: { modelAliases: { sonnet: 'anthropic/claude-sonnet-5' } },
subagents: {
files: { root: '/srv/repo', trusted: true },
agents: {
researcher: { description: 'Researches a topic on the web', prompt: 'You research.' },
},
},
});
const lead = new Agent({
name: 'lead',
model: 'anthropic/claude-sonnet-5',
instructions: 'You lead the work and delegate reviews.',
tools: workspaceTools(),
workspace,
});
const result = await cog.run(lead, { input: 'Fix the login bug, then get it reviewed' });Nothing changes until you set subagents: unset, runs get no delegation tools. With it, every run gets an agent tool that takes the input of Claude Code's Agent tool:
| Parameter | Meaning |
|---|---|
description | A short description of the task |
prompt | The task, with everything the subagent needs |
subagent_type | Which subagent runs it, one of those listed in the tool |
model | inherit or a key of llm.modelAliases for this task |
run_in_background | Start it and go on, its result arrives later |
name | A name to reach it by with send_message and task_output |
The tool answers with { agentId, status, output, success, error?, note? }. The call's model may only be inherit, a key of llm.modelAliases or a Claude Code alias, never an arbitrary model, and when a cost limit applies to the delegating run or a run above it (a session or agent maxCostUsd of the runtime, the run or the agent, or a cost routing budget), a subagent whose model differs from the delegating agent's and has no known price is refused, since its calls would count as nothing against the budget. Calls of the tool are safe to run together, so with parallelToolCalls the delegations of one step run side by side. With subagents.tools: 'each' every subagent is also (or with 'both' additionally) a tool of its own, named after it, that takes a task.
Where Subagents Come From
| Source | Path | Read |
|---|---|---|
| Managed | .agents/agents, .cogitator/agents, .claude/agents of the managed directories | Always |
| Code | subagents.agents | Always |
| Host folders | files.dirs | Always |
| Project | .agents/agents, .cogitator/agents, .claude/agents from the working directory up | Only once the project is trusted |
| User | agents/ of ~/.agents, ~/.cogitator and ~/.claude (or CLAUDE_CONFIG_DIR) | Always |
The first definition of a name wins, in the order of the table. Folders are read recursively and again for every run, so an edited file counts from the next delegation on. The project root is files.root, else the root of permissions.settings, else the run's workspace root. As in Claude Code the project folders of every directory from the workspace's current directory up to the project root count, the closest first, and above the root up to the repository's top folder through directories that are trusted themselves. A project counts as trusted with trusted: true (for root only) or when it is one of trustedRoots. files: true takes the places of permissions.settings (its root, user and managed directories), and its trusted: true trusts that root here too, as it does for hooks. A repository you did not vet never adds subagents, which run tools, hooks and MCP servers.
A file without frontmatter or without a name is documentation kept next to the agents and skipped quietly. Frontmatter that is not valid YAML, a name that is not lowercase letters, digits and hyphens (code-reviewer, reviewer-v2) or is longer than 256 characters, and a missing description skip the file with an error. A field of the wrong type or an unknown field is left out with a warning, with the field it likely meant (maxturns, did you mean maxTurns). Each problem is logged once and handed to subagents.onDiagnostic. cog.listSubagents({ root }) returns what a run would see.
Frontmatter
| Field | What it does |
|---|---|
name | The subagent_type and the agent_type hooks see (required) |
description | When to delegate to it, shown in the agent tool (required) |
tools | The tools it may use, in the grammar of permission rules, inherited when unset |
disallowedTools | Tools taken away first. Bash(git push *) takes the whole tool, as in Claude Code |
model | inherit (default), an alias of llm.modelAliases (sonnet, opus, haiku, fable), a Claude model id (run on Anthropic), or provider/model |
permissionMode | Its mode, by Claude Code's rules and never wider than the delegating run's |
maxTurns | Its maxIterations |
skills | Skills of the delegating agent whose full instructions go into its prompt |
mcpServers | Names of MCP servers the run has tools of, or inline servers connected through subagents.mcp |
hooks | Hooks that run only while it runs, Stop ones as SubagentStop |
memory | user, project or local: a notes folder it keeps across sessions |
background | Always run it in the background |
isolation | worktree: run it in a git worktree of its own |
color, effort | Its display color, and the reasoning effort of its model calls |
The Markdown body is its system prompt. tools and disallowedTools take Claude Code's names (Read, Edit, Bash, Glob, Grep, WebFetch, Agent), a tool's own name, and mcp__server__*. Agent keeps the agent tool and its companions, so a subagent without it cannot delegate further. The lists of every subagent above hold too: a subagent delegated by a subagent gets only the tools each layer above it kept, the runtime's tools (cog.tools) among them, plus its own agent_memory and the tools of its own mcpServers, which no list takes away. A tools list that names none of the tools available fails the delegation with an error the model reads, as Claude Code refuses an agent with zero tools.
Permissions
A subagent runs under the delegating run's permission mode and session rules, with their path patterns resolved as in the delegating run, and every rule of the settings files applies to its calls too. Its own permissionMode follows Claude Code: under bypassPermissions, acceptEdits and auto the subagent runs in the delegating run's mode whatever it sets, under plan it stays in plan, and under default and dontAsk its own mode applies, except acceptEdits, auto and bypassPermissions, which would let it do more than the run that delegates and are ignored. A subagent never gets permissions the run that delegates lacks. An Agent(reviewer) deny rule refuses delegating to reviewer, and AgentConfig.subagents (false, or a list of names) limits which subagents an agent may use, for the subagents below it too.
Every tool call of a subagent is charged to the session budget of the run that delegated, background subagents included, so a budget holds across delegation.
Approvals That Bubble Up
By default (approvalMode: 'decline') a subagent's call that needs approval is declined: nobody can approve it from inside a delegated run, and the subagent is told why. With approvalMode: 'bubble' the subagent's approvals go to the delegating run's onApproval first, with the qualified toolCallId and the subagent's name in subagent (releaser, releaser/operator a layer down), and when the subagent pauses (an approval nobody decided, or a question one of its tools asked with context.interrupt()), the run that delegated pauses too:
const cog = new Cogitator({ subagents: { files: true, approvalMode: 'bubble' } });
const paused = await cog.run(lead, { input: 'Release 2.4', threadId });
paused.pendingApprovals; // [{ toolCallId: 'call_1/call_7', toolName: 'run_command', ... }]
paused.interrupts?.[0]?.details?.subagent; // 'releaser'
const done = await cog.resume(lead, threadId, {
responses: { 'call_1/call_7': { approved: true } },
});The subagent's checkpoint is kept inside the delegating run's, in checkpoint.turn.children[toolCallId], and its interrupts and approvals come back under ids that start with the delegating call's id and /, one level more for each layer (call_1/call_4/call_9). A resume that answers them goes on inside the subagent, which checks its calls against the permission rules again. A resume that answers only other interrupts of the turn leaves the subagent waiting. agentAsTool(cog, agent, { approvalMode: 'bubble' }) does the same for an agent you wire up yourself.
Background Subagents
run_in_background: true (or background: true in the frontmatter) starts the subagent on its own run handle and returns at once with its agentId. The run goes on, and three more tools reach it:
| Tool | What it does |
|---|---|
send_message | Steers a running subagent, or continues one that finished with its whole conversation and the message |
task_output | Reads its status and result, waiting for it unless block: false |
task_stop | Stops it |
A run that would stop waits for the background subagents it started and reads their results as a <task-notification> message, marked as an automated notice whose instructions carry no authority from the user. A run that pauses or fails stops the ones it started. subagents.maxBackground (20) caps how many run at once, a delegation past it fails with an error the model reads. Foreground subagents are reachable by their agentId too, so send_message continues them in the background. A subagent that goes on is set up again as it was first: in its own mode, in the same worktree, with its MCP servers connected again, and with no more than the run that continues it allows (its tools and tool filter, a command's disallowed-tools among them, and the subagents it may use). Approvals of a background subagent go to the delegating run's onApproval and are declined when it has none or it answers 'pause'. When an agent has a tool named send_message (task_output, task_stop) of its own, such as a team member, the subagent tool is named subagent_send_message and the agent tool says so.
Worktrees and Memory
isolation: worktree runs the subagent in a git worktree of the repository the run's workspace is in, under .cogitator/worktrees/ (ignored by git), on a branch cogitator/<name>-<hash>, made from the current commit. When the subagent changed nothing (no change, no commit, no ignored file it made) the worktree and its branch are removed. Otherwise they are kept, and the result's note says where. The worktree's workspace keeps every setting of the run's LocalWorkspace (file limits, command environment, an exec.launch sandbox), and tools bound to the run's workspace (workspaceTools(workspace), on the agent or cog.tools) work in the worktree. Subagents below an isolated one work in its worktree too. A subagent that paused with a bubbled approval goes on in the same worktree. A workspace that is not local, or not in a git repository, runs the subagent without isolation and logs a warning.
memory gives the subagent a notes folder where Claude Code keeps it, so both share it: ~/.claude/agent-memory/<name>/ for user, .claude/agent-memory/<name>/ of the project for project, .claude/agent-memory-local/<name>/ for local, always a folder of its own under those. The first 200 lines (at most 25 KB) of its MEMORY.md go into its prompt, and an agent_memory tool lists, reads, writes and appends notes inside the folder only.
Context and Hooks
A subagent reads its prompt, the preloaded skills, its memory and the task. subagents.messages: 'conversation' shows it the delegating run's user messages and answers first, and a function picks messages of your own, tool calls without a result and system messages always left out. onDelegationStart and onDelegationComplete hear every delegation with its status (completed, paused, failed, stopped), output and usage. The SubagentStart and SubagentStop hooks of the runtime fire for every subagent run, matched by its name, and the frontmatter hooks of a subagent run only inside it, as hooks of the layer its file comes from: the managed disableAllHooks and allowManagedHooksOnly turn them off, and the HTTP allowlists of the settings files and of hooks hold for them (without hooks.settings only the managed files are read for this). maxDepth (3) caps how many layers of subagents run below a run nobody delegated: past it a subagent gets no agent tool.
Differences from Claude Code
- The tool is named
agentand takes Claude Code'sAgentinput. Hooks and rules that nameAgent,Task,SendMessage,TaskOutputandTaskStopmatch it and its companions, never other tools of those names. - Model aliases resolve through
llm.modelAliases, an alias without an entry runs on the delegating agent's model with a warning. acceptEdits,autoandbypassPermissionsof a subagent under adefaultordontAskrun are ignored, where Claude Code appliesacceptEditsandauto.- The call's
modeltakes onlyinheritand aliases, and an unpriced model is refused under a cost budget. - Background subagents keep the tools their definition gives them, approvals they hit are declined unless the run's
onApprovaldecides them, and a run that pauses or fails stops them. - Subagent names are lowercase letters, digits and hyphens only.
- Inline MCP servers need
subagents.mcpto connect them.initialPrompt,autoCompactWindow,omitClaudeMdandexperimentalare read but not applied, plugin folders are not read yet. - A worktree is made from the current commit, not from the default branch.