# SubAgents

A SubAgent delegates a self-contained task to a child agent. Two concepts to keep separate:

- `agentRole` controls the child agent's behavior, tool boundary, and prompt constraints.
- `profile` controls which runtime the child uses — DotCraft native or an external CLI.

If you only want safe one-level delegation, you usually do not need to change anything. The defaults allow the root agent to spawn a first-level SubAgent and prevent SubAgents from spawning further SubAgents.

## Quick Start

The default config equals:

```json
{
  "SubAgent": {
    "MaxDepth": 1
  }
}
```

Which means:

- The root agent can call `SpawnAgent` to create a first-level SubAgent.
- First-level SubAgents are at the depth limit and cannot spawn new ones.
- `agentRole` defaults to `default` when omitted.
- Native SubAgents use the lightweight `subagent-light` prompt profile by default.

## Built-in Roles

| Role | Best for | Tool policy |
|---|---|---|
| `default` | General first-level collaboration, summary, local analysis | Disable AgentTools, conservative tool set |
| `worker` | Implementation, validation, file changes | Allow read/write, shell, web; AgentTools still bound by `MaxDepth` |
| `explorer` | Read-only code exploration, research | Only read-only exploration + web; disable writes, shell, Plan/Todo, SkillManage, AgentTools |

`worker` has the capability model for recursive delegation, but `SubAgent.MaxDepth = 1` keeps recursion off by default. Recursive SubAgents are an explicit opt-in.

## Configuration

SubAgent config can live under global `~/.craft/config.json` or workspace `<workspace>/.craft/config.json`. Team projects should keep it in workspace config.

### Enable Second-Level Worker Delegation

```json
{
  "SubAgent": {
    "MaxDepth": 2
  }
}
```

Now:

- The root agent's first-level `worker` SubAgent sees `SpawnAgent`.
- Second-level SubAgents reach the cap and cannot spawn further.
- `default` and `explorer` still disable AgentTools per their role policy.

### Override the Built-in Worker

A same-named role overrides the built-in. The example below keeps `worker`'s lightweight prompt and adds team instructions:

```json
{
  "SubAgent": {
    "Roles": [
      {
        "Name": "worker",
        "Description": "Team worker role for bounded implementation tasks.",
        "AgentControlToolAccess": "Full",
        "PromptProfile": "subagent-light",
        "ToolDenyList": ["UpdateTodos", "TodoWrite", "CreatePlan"],
        "Instructions": "Complete the assigned task within the requested files. Summarize changed paths and validation results."
      }
    ]
  }
}
```

### Add a Read-Only Explorer Role

```json
{
  "SubAgent": {
    "Roles": [
      {
        "Name": "docs-explorer",
        "Description": "Read-only documentation and code explorer.",
        "ToolAllowList": ["ReadFile", "GrepFiles", "FindFiles", "WebSearch", "WebFetch", "SkillView"],
        "AgentControlToolAccess": "Disabled",
        "PromptProfile": "subagent-light",
        "Instructions": "Inspect files and web sources only. Do not edit files, execute shell commands, manage skills, or spawn agents."
      }
    ]
  }
}
```

The root agent calls `SpawnAgent` with `agentPrompt` and `agentRole = "docs-explorer"`.

### Use the Full Prompt

Native SubAgents default to `subagent-light`. To use the full root prompt for a role:

```json
{
  "SubAgent": {
    "Roles": [
      {
        "Name": "full-context-worker",
        "Description": "Worker role with the full root prompt context.",
        "AgentControlToolAccess": "Disabled",
        "PromptProfile": "full",
        "Instructions": "Use the full project context to complete the assigned task."
      }
    ]
  }
}
```

## The Lightweight Prompt

`subagent-light` is the default prompt profile for native session-backed SubAgents. It keeps:

- DotCraft base identity
- Current workspace and environment info
- Role instructions
- Tool capabilities and limits
- `.craft/AGENTS.md`
- Necessary file references and working style

It skips by default:

- Full Memory context
- Long Skill self-learning instructions
- Custom commands summary
- Deferred MCP discovery instructions
- Plan / Todo injection

This makes short-task SubAgents start faster and avoids copying the main thread's long-term context to the child.

## Profile: Choosing a Runtime

`profile` decides which runtime the child uses:

| Profile | Description |
|---|---|
| `native` | DotCraft native SubAgent with role-resolved tool filtering |
| `codex-cli` | One-shot external SubAgent backed by Codex CLI |
| `cursor-cli` | One-shot external SubAgent backed by Cursor CLI |
| `custom-cli-oneshot` | Template profile; provide a same-named override and `bin` |

DotCraft passes role instructions to external CLIs but cannot enforce tool filtering on the CLI itself. For strong isolation, prefer `native` and combine it with role allow/deny lists and [Security & Sandbox](./security.md).

---

## Using External CLIs as SubAgents

External CLI SubAgents wrap an existing coding agent CLI as a short-lived process. Compared with `native`, an external CLI typically gives stage-level progress, not per-tool-call detail.

### Built-in Profiles

| Name | Runtime | Default `bin` | Headless entry | Resume |
|---|---|---|---|---|
| `native` | `native` | - | DotCraft native runtime | N/A |
| `codex-cli` | `cli-oneshot` | `codex` | `codex exec` / `codex exec resume` | Yes |
| `cursor-cli` | `cli-oneshot` | `cursor-agent` | `cursor-agent -p --output-format json --resume ...` | Yes |
| `custom-cli-oneshot` | `cli-oneshot` | - | Template profile | Optional |

When a `cli-oneshot` profile's `bin` is not resolvable on the host, DotCraft hides it from the system prompt.

### Reuse External CLI Sessions

DotCraft can save an external CLI's session/chat/thread id on the same thread and resume it on subsequent user feedback. The ability is off by default. Turn it on in Desktop's **Settings → Sub Agents** as `Reuse external CLI sessions`.

When off:

- Every delegation creates a new external CLI session
- Saved session ids are not deleted, just unused

### When DotCraft Resumes a Previous Session

Never blindly. The matching rules:

- Prefer exact match on `profile + label + workingDirectory`
- Without `label`, auto-resume only when the same `profile + workingDirectory` has a single candidate
- Otherwise create a new session

To keep "first delegate → user feedback → second delegate" on the same external CLI session, the safest setup is:

- Same profile
- Same label
- Same working directory

### Custom External CLI Profile

SubAgent profiles live under `SubAgentProfiles` in `config.json`. Workspace config overrides same-named global profiles.

```json
{
  "SubAgent": {
    "EnableExternalCliSessionResume": true
  },
  "SubAgentProfiles": {
    "my-cli": {
      "runtime": "cli-oneshot",
      "bin": "my-cli",
      "workingDirectoryMode": "workspace",
      "inputMode": "arg",
      "outputFormat": "text",
      "supportsResume": true,
      "resumeArgTemplate": "--resume {sessionId}",
      "resumeSessionIdJsonPath": "session_id"
    }
  }
}
```

### Field Reference

| Field | Description |
|---|---|
| `runtime` | Runtime type; external short-process CLIs use `cli-oneshot` |
| `bin` | CLI executable name or absolute path |
| `args` | Fixed argument list |
| `workingDirectoryMode` | `workspace` / `specified` |
| `inputMode` | `stdin` / `arg` / `arg-template` / `env` |
| `inputArgTemplate` | Template for `arg-template` mode |
| `inputEnvKey` | Env-var name receiving task text in `env` mode |
| `env` | Fixed env vars injected into the subprocess |
| `envPassthrough` | Names of env vars to copy from parent |
| `outputFormat` | `text` or `json` |
| `outputJsonPath` | JSON path to extract the final result in `json` mode |
| `readOutputFile` | Prefer reading the output file as the final result |
| `outputFileArgTemplate` | Output-file argument template, supports `{path}` |
| `supportsResume` | Allow DotCraft to store and reuse the external session id |
| `resumeArgTemplate` | Resume argument template, supports `{sessionId}` |
| `resumeSessionIdJsonPath` | JSON path to extract session id from stdout |
| `resumeSessionIdRegex` | Regex fallback when stdout is not a single JSON object |
| `timeout` | Per-run timeout in seconds |
| `maxOutputBytes` | Maximum captured output bytes |
| `trustLevel` | `trusted` / `prompt` / `restricted` |
| `permissionModeMapping` | Map DotCraft approval modes to CLI arguments |

`supportsResume = true` requires:

- `resumeArgTemplate`
- At least one of `resumeSessionIdJsonPath` or `resumeSessionIdRegex`

### Approval & Permission Forwarding

**Native SubAgents**

- A native SubAgent's internal `ReadFile / WriteFile / Exec` reuses the current session's `IApprovalService`.
- Approval requests automatically gain the prefix `[subagent:<label>] ...`.

**External CLI SubAgents**

- DotCraft does not intercept the CLI's internal tool calls. It translates the current approval mode into startup arguments.
- Mapping is configured by the profile's `permissionModeMapping`.
- The resume argument is inserted before approval arguments, but DotCraft still decides whether to resume.

### Vendor Headless Reference

**`cursor-cli`** — DotCraft injects `-p --output-format json` and appends `--resume {sessionId}` when resuming.

**`codex-cli`** — DotCraft injects `exec` plus `--skip-git-repo-check --json --output-last-message {path}` via the output-file argument. Resume becomes `exec resume {sessionId}`. Interactive / restricted modes append `--sandbox read-only` by default; auto-approve mode appends `--dangerously-bypass-approvals-and-sandbox`. Session id is parsed from `thread_id` in stdout.

## Full Reference

See [Configuration Reference](../developing/configuration.md#subagent) for every field.

## Related

- [Project Workspace](./workspace.md)
- [Security & Sandbox](./security.md) — bound SubAgent behavior with workspace boundary and sandbox
- [Observability](./observability.md) — view SubAgent calls and approvals in Dashboard
