# Agent Creation Playbook

End-to-end procedure for spinning up a new Veil agent. Use this when the user asks "create an agent that does X".

## 1. Clarify scope first
Ask the user (only if not obvious from the request):
- **Name** — folder + `agent.json.name` must match (kebab-case is conventional).
- **Scope** — global (`~/.veil/agents/<name>/`) or project (`<project>/.veil/agents/<name>/`).
- **Engine** — claude-cli (`model: "cc/opus"` / `"cc/sonnet"` / `"cc/haiku"` / `"cc/default"`) or openai-compatible (e.g. `"moonshotai/kimi-k2.6"`, `"deepseek/deepseek-v3"`). Claude-cli runs the agent through the Anthropic Claude Code SDK; openai routes through `core/loop.js`. They differ in tool surface and reasoning config.
- **Modes** — `chat` is the only mode (task/daemon/subagent modes were removed from VeilCLI; sub-agents are plain chat sessions reached via `agent_spawn`/`agent_message`).
- **Allowed tools** — file I/O? bash? web? memory? multi-agent (`agent_spawn`)? Match the user's intent rather than copying a template wholesale.

## 2. Look at neighbors
Before writing, read one existing agent in the same scope to mirror conventions:
```
ls ~/.veil/agents/
cat ~/.veil/agents/cc-opus/agent.json     # claude-cli example
cat <project>/.veil/agents/<name>/agent.json  # project examples if any
```
Note the `disallowedTools` list — claude-cli agents typically disallow the chat-incompatible orchestration tools (`tool_*`, `log_write`, `ScheduleWakeup`, `Cron*`, `RemoteTrigger`).

## 3. Write `agent.json`
Minimum viable shape (claude-cli, chat-only):
```json
{
  "name": "<name>",
  "description": "<one line>",
  "model": "cc/opus",
  "reasoning": { "effort": "high" },
  "skillDiscovery": false,
  "memory": { "enabled": false },
  "defaultCompaction": { "enabled": false },
  "modes": {
    "chat": {
      "enabled": true,
      "disallowedTools": []
    }
  }
}
```
The `reasoning` block is engine-blind — it works the same for claude-cli and openai-compatible agents. For openai-compatible agents just replace `model` with the provider/model string (e.g. `"moonshotai/kimi-k2.6"`). Do NOT use the legacy top-level `thinking` / `effort` keys: the create/update API validates strictly against the agent schema and rejects them (`INVALID_CONFIG`); only pre-existing on-disk files get the legacy auto-fold at load time.
See `docs/guide/04-agents.md` for the full field reference and `docs/guide/03-configuration.md` §`models` for valid model strings.

## 4. Write `AGENT.md`
Plain Markdown, becomes the system prompt. Keep it short and role-focused.
```markdown
You are <Name>, a <role> agent operating in $PROJECT_ROOT.

<2-4 sentences about responsibilities>

Rules:
- <constraint 1>
- <constraint 2>
```
`$AGENT_FOLDER` and `$PROJECT_ROOT` are substituted at load time. Use them instead of hardcoded paths.

Optional: split persona/tone into a separate `SOUL.md` if the prompt is long — it's appended after `AGENT.md`.

## 5. Permissions / tool gating
- Per-mode `permissions.allow` / `permissions.deny` — defined in `agent.json` under each mode. Globs supported. See `docs/guide/07-permissions.md`.
- Per-mode `disallowedTools` — explicit deny list, applied at SDK level on claude-cli and via `canUseTool` on both engines.
- Per-mode `tools` allowlist — gates **custom tools only** (the 14 built-in orchestration tools always register). Empty/absent = allow all custom tools.

## 6. Custom tools (if requested)
Custom tools are MCP tools registered via `settings.json` → `mcpServers` (see `docs/guide/03-configuration.md` §`mcpServers`) **or** declared inline in the agent. The full pattern is documented in `docs/guide/06-tools.md` §Custom Tools — including the "tool_activate succeeded but the tool isn't callable" gotcha (it's almost always a `tools` allowlist omission).

## 7. Verify
After writing, sanity-check from the user's CWD:
```
cat <path>/agent.json | python3 -m json.tool    # JSON valid
ls <path>/                                       # agent.json + AGENT.md present
```
Then suggest the user list agents (`veil agents list` or `GET /agents`) and run a smoke chat to confirm the agent loads.

## 8. Common pitfalls
- **Folder name mismatch** — `agent.json.name` MUST equal the folder name; mismatch silently fails on lookup.
- **Disallowed tools on claude-cli** — claude-cli SDK enforces `disallowedTools` strictly. Forgetting to disallow orchestration tools the agent shouldn't use (e.g. `tool_search`/`tool_activate`, `Cron*`) surfaces them in the LLM's tool list and confuses the model.
- **Memory + compaction defaults** — leaving `defaultCompaction.enabled: true` on a long-running agent will silently rewrite history at threshold; set explicitly per intent.
- **Model swap mid-session** — `PATCH /sessions/:id { agent_name }` only succeeds when the new agent produces the **same engine type** as the current one. Cross-engine swap (cc-opus → kimi) returns `400`.
