# Agent Configuration

An agent is a folder in `.veil/agents/<name>/` containing at minimum `agent.json` and `AGENT.md`.

---

## `agent.json` — Full Reference

```json
{
  "name": "assistant",
  "description": "General-purpose agent with file and shell access",
  "model": "moonshotai/kimi-k2.6",
  "temperature": 0.7,
  "reasoning": { "effort": "medium" },
  "maxTokens": 4096,
  "memory": {
    "enabled": true,
    "maxLines": 300
  },
  "skillDiscovery": false,
  "defaultCompaction": {
    "enabled": true,
    "compactionCount": 50,
    "autoThreshold": 80,
    "model": "default",
    "customInstructions": ""
  },
  "modes": {
    "chat": { ... }
  }
}
```

### Top-level fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | ✓ | Agent identifier. Must match the folder name. |
| `description` | string | | Human-readable description |
| `model` | string | ✓ | LLM model string (e.g. `moonshotai/kimi-k2.6`) |
| `temperature` | number | | 0–2. Sampling temperature. |
| `reasoning` | object | | Engine-blind reasoning config: `{ effort, max_tokens? }`. `effort` accepts `none`, `auto`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, or any custom string. See [API reference 05-sessions.md](../api/05-sessions.md#reasoning-unification) for cross-engine routing. |
| `maxTokens` | integer | | Max output tokens per LLM call |
| `budget` | object | | Phase 3 chat-side budget caps applied when this agent is the target of `agent_spawn` / `agent_message`. See [`budget` block](#budget-block) below. |
| `memory.enabled` | boolean | | Whether memory files are injected. Overrides settings-level default. |
| `memory.maxLines` | integer | | Max lines to read from memory file |
| `skillDiscovery` | boolean | | Auto-discover skill files in the agent's folder |

> **Backward compat.** Legacy `reasoning: "high"` (bare string) and the old `thinking: { type, budget_tokens }` / `effort: "high"` pair on agent.json are auto-folded into the new `reasoning` object at load time (no on-disk rewrite required). Mixing the new `reasoning` object with the old `thinking`/`effort` keys in the same agent.json raises a load error.

> **Per-session LLM-param overrides.** Sessions (rows in the `sessions` table) carry their own optional `model`, `temperature`, `max_tokens`, and `reasoning` columns. Each unset column falls back to the agent default above. Read the resolved values via `GET /sessions/:id/config`, write them with `PATCH /sessions/:id`, and override per call via the `overrides` field on `POST /agents/:name/chat`. Resolution order: per-call > session > agent.

> **Mid-session agent swap (May 2026).** `PATCH /sessions/:id { agent_name }` replaces the agent for an existing session. Validated: agent must exist, have `modes.chat.enabled`, and produce the same engine type as the current agent (cross-engine swap rejected). Subsequent turns rebuild the system prompt + reload tools/MCP/permissions from the new agent. The session is rejected with `409 SESSION_BUSY` if a turn is currently running. See [API reference 05-sessions.md](../api/05-sessions.md#patch-sessionsid).

### `budget` block

Phase 3 chat-side budget caps. Apply when this agent is the target of `agent_spawn` or `agent_message`. All three axes default to `null` = unlimited.

```json
{
  "budget": {
    "max_tokens": 200000,
    "max_wall_seconds": 600,
    "max_spawn_depth": 3
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `max_tokens` | integer \| null | Per-call cap on combined input + output tokens. |
| `max_wall_seconds` | integer \| null | Per-call wall-clock cap. |
| `max_spawn_depth` | integer \| null | Cap on nested `agent_spawn` depth measured from this agent's own session. |

Resolution order: per-call `budget_override` (passed to `agent_spawn` / `agent_message`) > this agent's `budget` block > harness `settings.budget`. See [Multi-Agent → Depth and Budget](09-multi-agent.md#depth-and-budget) for the breach behavior and `BUDGET_EXCEEDED` error shape.

### `defaultCompaction`

Controls how sessions for this agent handle context compaction. All values are snapshotted into the session at creation — changing them later does not affect existing sessions.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | boolean | `true` | When `false`, disables all compaction: auto-compact never fires and `POST /sessions/:id/compact` returns an error. |
| `compactionCount` | number (1–99) | `50` | Percentage of uncompacted context to summarize per run. |
| `autoThreshold` | number (1–99) | — | Auto-compact when context usage (`context_size / context_size_limit × 100`) reaches this percentage. Omit to require manual compaction only. |
| `model` | string | `"default"` | Model used for the compaction LLM call. `"default"` uses the same model as the session. Set to a cheaper model (e.g. `"google/gemini-flash-1.5-8b"`) to reduce cost. |
| `customInstructions` | string | — | Extra instructions appended to the compaction system prompt. Use this to tell the compaction model what domain-specific state to preserve (e.g. active task lists, open decisions). |

See [Memory & Compaction](08-memory.md) for the full explanation of how compaction works.

---

## Mode Configuration

Every mode shares a common set of fields plus mode-specific ones.

### Common mode fields

```json
{
  "enabled": true,
  "tools": ["read_file", "list_dir", "bash"],
  "disallowedTools": ["write_file"],
  "preActivatedTools": ["my_custom_tool"],
  "skills": ["my-skill"],
  "autoLoadSkills": ["boot-skill"],
  "mcpServers": ["my-mcp"],
  "allowedAgents": ["coder", "writer"],
  "disallowedAgents": []
}
```

| Field | Type | Description |
|-------|------|-------------|
| `enabled` | boolean | Whether this mode is active |
| `tools` | string[] | Whitelist: only these tools are offered to the LLM. Equivalent to `permissions.allow`. |
| `disallowedTools` | string[] | Blacklist: these tools are hidden from the LLM. Equivalent to `permissions.deny`. |
| `preActivatedTools` | string[] | Custom tools to load with full schema on turn one — skips the `tool_search` / `tool_activate` dance. Resolved through agent → project → global discovery. Still subject to `tools` / `disallowedTools` gating. See [Custom Tools § Pre-activating](06-tools.md#pre-activating-a-custom-tool-skip-the-searchactivate-dance). |
| `skills` | string[] | Skill files to load (by name) |
| `autoLoadSkills` | string[] | Skills auto-injected into the system prompt |
| `mcpServers` | string[] | MCP server names to load tools from |
| `allowedAgents` | string[] | Sub-agents this agent may spawn |
| `disallowedAgents` | string[] | Sub-agents explicitly blocked |
| `permissions.allow` | string[] | Runtime allow list — tools allowed to execute (checked at call time, supports glob patterns) |
| `permissions.deny` | string[] | Runtime deny list — tools blocked from executing (checked at call time, wins over allow) |

**`tools`/`disallowedTools` vs `permissions`**: `tools` and `disallowedTools` control which tools the LLM *sees* (the tool list sent in the API call). `permissions.allow`/`deny` control which tools are *allowed to execute* at runtime. You can use either, both, or combine them. See [Permissions](07-permissions.md).

---

## Chat Mode

> Chat is the only agent mode. Sub-agents (spawned via `agent_spawn`) are regular chat sessions of the target agent — there is no separate subagent mode. Legacy `task`/`subagent`/`daemon` blocks in existing `agent.json` files are tolerated and ignored.

```json
{
  "modes": {
    "chat": {
      "enabled": true,
      "permissions": {
        "allow": ["read_file", "list_dir"],
        "deny": []
      }
    }
  }
}
```

Chat mode is used by `POST /agents/:name/chat`. Each call is synchronous — the agent runs its full loop (including any tool calls) before returning.

---

## `AGENT.md`

The agent's system prompt. Plain Markdown, loaded and injected at the top of every conversation.

### Variable substitution

| Variable | Value |
|----------|-------|
| `$AGENT_FOLDER` | Absolute path to this agent's folder |
| `$PROJECT_ROOT` | Absolute path to the workspace root |

### Tips

- Keep it focused — describe the agent's role, constraints, and tool usage style.
- Avoid putting volatile data (dates, paths) directly — use variables.
- For long instructions, split between `AGENT.md` (capabilities) and `SOUL.md` (identity/tone).

Example:
```markdown
You are Coder, a focused code analysis agent operating in $PROJECT_ROOT.

You specialise in:
- Reading and understanding source files
- Identifying patterns, bugs, and improvements
- Answering questions about code structure

Rules:
- Never write or modify files unless explicitly asked
- Always cite the file path and line number when referencing code
- Keep responses concise and technical
```

---

## `SOUL.md`

Optional. Same format as `AGENT.md`. Appended to the system prompt after `AGENT.md`. Useful for separating stable capabilities from swappable persona/tone.

---

## Complete Example

`.veil/agents/assistant/agent.json`:
```json
{
  "name": "assistant",
  "description": "General-purpose agent with file and shell access",
  "model": "moonshotai/kimi-k2.6",
  "temperature": 0.7,
  "reasoning": { "effort": "medium" },
  "memory": { "enabled": false },
  "skillDiscovery": false,
  "defaultCompaction": {
    "enabled": true,
    "compactionCount": 50,
    "autoThreshold": 80,
    "model": "default"
  },
  "modes": {
    "chat": {
      "enabled": true,
      "permissions": {
        "allow": ["read_file", "list_dir", "bash", "grep", "glob"],
        "deny": []
      }
    }
  }
}
```

`.veil/agents/assistant/AGENT.md`:
```markdown
You are Assistant, a capable general-purpose agent running in $PROJECT_ROOT.

You can read, write, and analyse files, run shell commands, and search the web.
Always explain what you are doing and why. Be direct and concise.
```
