Cogitator
Core

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.

.claude/agents/reviewer.md
---
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:

ParameterMeaning
descriptionA short description of the task
promptThe task, with everything the subagent needs
subagent_typeWhich subagent runs it, one of those listed in the tool
modelinherit or a key of llm.modelAliases for this task
run_in_backgroundStart it and go on, its result arrives later
nameA 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

SourcePathRead
Managed.agents/agents, .cogitator/agents, .claude/agents of the managed directoriesAlways
Codesubagents.agentsAlways
Host foldersfiles.dirsAlways
Project.agents/agents, .cogitator/agents, .claude/agents from the working directory upOnly once the project is trusted
Useragents/ 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

FieldWhat it does
nameThe subagent_type and the agent_type hooks see (required)
descriptionWhen to delegate to it, shown in the agent tool (required)
toolsThe tools it may use, in the grammar of permission rules, inherited when unset
disallowedToolsTools taken away first. Bash(git push *) takes the whole tool, as in Claude Code
modelinherit (default), an alias of llm.modelAliases (sonnet, opus, haiku, fable), a Claude model id (run on Anthropic), or provider/model
permissionModeIts mode, by Claude Code's rules and never wider than the delegating run's
maxTurnsIts maxIterations
skillsSkills of the delegating agent whose full instructions go into its prompt
mcpServersNames of MCP servers the run has tools of, or inline servers connected through subagents.mcp
hooksHooks that run only while it runs, Stop ones as SubagentStop
memoryuser, project or local: a notes folder it keeps across sessions
backgroundAlways run it in the background
isolationworktree: run it in a git worktree of its own
color, effortIts 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:

ToolWhat it does
send_messageSteers a running subagent, or continues one that finished with its whole conversation and the message
task_outputReads its status and result, waiting for it unless block: false
task_stopStops 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 agent and takes Claude Code's Agent input. Hooks and rules that name Agent, Task, SendMessage, TaskOutput and TaskStop match 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, auto and bypassPermissions of a subagent under a default or dontAsk run are ignored, where Claude Code applies acceptEdits and auto.
  • The call's model takes only inherit and 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 onApproval decides them, and a run that pauses or fails stops them.
  • Subagent names are lowercase letters, digits and hyphens only.
  • Inline MCP servers need subagents.mcp to connect them. initialPrompt, autoCompactWindow, omitClaudeMd and experimental are read but not applied, plugin folders are not read yet.
  • A worktree is made from the current commit, not from the default branch.

On this page