# Agents

---

## POST /agents

Create a new agent at the project or global level.

**Request body**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | ✓ | Agent identifier — letters, numbers, hyphens, underscores only |
| `level` | string | | `"project"` (default) or `"global"` |
| `config` | object | ✓ | Agent configuration (must satisfy the agent schema — `model` required) |
| `agentMd` | string | | Content for `AGENT.md` (system prompt / instructions) |

**Request example**
```json
{
  "name": "researcher",
  "level": "project",
  "config": {
    "model": "anthropic/claude-sonnet-4-5",
    "description": "Deep research agent",
    "modes": {
      "chat": { "enabled": true, "tools": ["bash", "read_file", "write_file"] }
    }
  },
  "agentMd": "You are a research assistant. Be thorough and cite sources."
}
```

**Response** `201 Created`
```json
{
  "agent": {
    "name": "researcher",
    "level": "project",
    "folder": "/home/user/workspace/.veil/agents/researcher"
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `409 AGENT_EXISTS` | An agent with that name already exists at any level (project, global, or bundled) |
| `400 INVALID_NAME` | Name contains invalid characters |
| `400 INVALID_CONFIG` | Config fails schema validation (error details included) |
| `400 VALIDATION_ERROR` | Missing required body fields or invalid `level` |

**Example**
```bash
curl -X POST http://localhost:5050/agents \
  -H "Content-Type: application/json" \
  -d '{"name":"researcher","config":{"model":"anthropic/claude-sonnet-4-5"}}'
```

---

## GET /agents

Returns all agents discovered in the workspace's `.veil/agents/` directory (and the global cache if configured).

**Query parameters** — none

**Response**
```json
{
  "agents": [
    {
      "name": "hello",
      "description": "A simple conversational agent",
      "model": "moonshotai/kimi-k2.6",
      "modes": ["chat"],
      "source": "project"
    }
  ]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Agent identifier (folder name) |
| `description` | string | From `agent.json` |
| `model` | string | LLM model identifier |
| `modes` | string[] | Enabled modes (only `"chat"` is supported) |
| `source` | string | `"project"` or `"global"` or `"bundled"` |

**Example**
```bash
curl http://localhost:5050/agents
```

---

## GET /agents/:name

Returns the full configuration for a single agent.

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Agent name (must match a folder in `.veil/agents/`) |

**Response**
```json
{
  "agent": {
    "name": "assistant",
    "description": "General-purpose agent with file and shell access",
    "model": "moonshotai/kimi-k2.6",
    "temperature": 0.7,
    "reasoning": { "effort": "medium" },
    "modes": {
      "chat": { "enabled": true },
      "chat": {
        "enabled": true,
        "maxIterations": 20,
        "maxDurationSeconds": 120,
        "tools": ["read_file", "list_dir", "bash", "write_file"],
        "permissions": {
          "deny": ["bash(rm *)", "bash(sudo *)", "write_file(/etc/*)"]
        }
      }
    },
    "skillDiscovery": false,
    "memory": { "enabled": false },
    "agentFolder": "/home/user/workspace/.veil/agents/assistant",
    "source": "project",
    "agentMd": "You are a general-purpose assistant with file and shell access."
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Agent identifier |
| `description` | string | Human-readable description |
| `model` | string | LLM model string |
| `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 [05-sessions.md](05-sessions.md) "Reasoning unification" for cross-engine semantics. |
| `modes` | object | Full mode configs (see [Agent Configuration](../guide/04-agents.md)) |
| `skillDiscovery` | boolean | Whether the agent auto-discovers skills |
| `memory` | object | `{ enabled, maxLines }` |
| `agentFolder` | string | Absolute path to the agent's folder |
| `source` | string | Where the agent was loaded from: `"project"`, `"global"`, or `"bundled"` |
| `agentMd` | string | Full content of the agent's `AGENT.md` system prompt file |

**Error responses**

| Code | Condition |
|------|-----------|
| `404 AGENT_NOT_FOUND` | No agent with that name exists |

**Example**
```bash
curl http://localhost:5050/agents/assistant
```

---

## POST /agents/:name/reload

Hot-reload an agent's configuration from disk. Since `loadAgent` always reads from disk, this endpoint simply forces a fresh load and returns the current config. Useful for confirming that config file edits have taken effect, or for programmatically verifying the agent is valid after an update.

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Agent name |

**Response**
```json
{
  "reloaded": true,
  "agent": {
    "name": "assistant",
    "model": "anthropic/claude-sonnet-4-5",
    "description": "General-purpose assistant",
    "modes": { "chat": { "enabled": true } }
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `404 AGENT_NOT_FOUND` | No agent with that name exists |

**Example**
```bash
curl -X POST http://localhost:5050/agents/assistant/reload
```

---

## PUT /agents/:name

Update an existing agent's configuration and/or `AGENT.md`. Only project-level and global agents can be updated — bundled agents are read-only.

At least one of `config` or `agentMd` must be provided. Config fields are **shallow-merged** with the current `agent.json` (top-level keys are replaced).

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Agent name |

**Request body**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `config` | object | | Partial or full agent config — merged over current `agent.json` |
| `agentMd` | string | | New content for `AGENT.md` — fully replaces existing content |

**Request example** — update model and add a tool
```json
{
  "config": {
    "model": "anthropic/claude-opus-4-5",
    "modes": {
      "chat": { "enabled": true, "tools": ["read_file", "bash"] }
    }
  }
}
```

**Response**
```json
{
  "agent": {
    "name": "researcher",
    "level": "project",
    "folder": "/home/user/workspace/.veil/agents/researcher"
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `404 AGENT_NOT_FOUND` | No agent with that name exists |
| `403 AGENT_READ_ONLY` | Agent is bundled and cannot be modified |
| `400 INVALID_CONFIG` | Merged config fails schema validation |
| `400 VALIDATION_ERROR` | Neither `config` nor `agentMd` provided |

**Example**
```bash
curl -X PUT http://localhost:5050/agents/researcher \
  -H "Content-Type: application/json" \
  -d '{"config":{"model":"anthropic/claude-opus-4-5"}}'
```

---

## DELETE /agents/:name

Delete an agent folder and all its contents (config, AGENT.md, skills, etc.). Only project-level and global agents can be deleted — bundled agents are read-only.

> **Warning:** This is irreversible. The agent folder is permanently removed from disk.

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Agent name |

**Response**
```json
{
  "deleted": true,
  "agent": {
    "name": "researcher",
    "level": "project",
    "folder": "/home/user/workspace/.veil/agents/researcher"
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `404 AGENT_NOT_FOUND` | No agent with that name exists |
| `403 AGENT_READ_ONLY` | Agent is bundled and cannot be deleted |

**Example**
```bash
curl -X DELETE http://localhost:5050/agents/researcher
```

---

## POST /agents/:name/duplicate

Copy an existing agent into a sibling folder under `newName`. The source is resolved across all tiers (project → global → bundled) and the copy lands **in the same tier as the source**, with its `agent.json` `name` field rewritten to `newName`. Bundled agents **can** be duplicated (the copy is a normal, editable agent at the bundled tier's sibling location).

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Source agent name |

**Request body**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `newName` | string | ✓ | Name for the copy. Allowed characters: letters, numbers, hyphens, underscores (`[a-zA-Z0-9_-]+`). |

**Response** `201 Created`
```json
{
  "agent": {
    "name": "researcher-copy",
    "level": "project",
    "folder": "/home/user/workspace/.veil/agents/researcher-copy"
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `400 VALIDATION_ERROR` | `newName` missing |
| `400 INVALID_NAME` | `newName` contains disallowed characters |
| `404 AGENT_NOT_FOUND` | No source agent with that name |
| `409 AGENT_EXISTS` | An agent named `newName` already exists at any tier (project, global, or bundled) |

**Example**
```bash
curl -X POST http://localhost:5050/agents/researcher/duplicate \
  -H "Content-Type: application/json" \
  -d '{"newName": "researcher-copy"}'
```

---

## POST /agents/:name/rename

Rename an agent folder within its tier, rewrite its `agent.json` `name`, and follow the rename through the database: existing `sessions.agent_name` rows and **pending** inter-agent `agent_messages.target_agent` rows are repointed to the new name (delivered/expired rows are left as history). Bundled agents cannot be renamed.

**Path parameters**

| Param | Description |
|-------|-------------|
| `name` | Current agent name |

**Request body**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `newName` | string | ✓ | New name. Same character rules as duplicate (`[a-zA-Z0-9_-]+`); must differ from the current name. |

**Response** `200 OK`
```json
{
  "agent": {
    "name": "research-lead",
    "level": "project",
    "folder": "/home/user/workspace/.veil/agents/research-lead"
  }
}
```

**Error responses**

| Code | Condition |
|------|-----------|
| `400 VALIDATION_ERROR` | `newName` missing |
| `400 INVALID_NAME` | `newName` contains disallowed characters, or equals the current name |
| `403 AGENT_READ_ONLY` | The agent is bundled and cannot be renamed |
| `404 AGENT_NOT_FOUND` | No agent with that name |
| `409 AGENT_EXISTS` | An agent named `newName` already exists at any tier |

> The folder move happens first; if the follow-up DB reference update fails, the rename is still reported as successful (the failure is logged server-side).

**Example**
```bash
curl -X POST http://localhost:5050/agents/researcher/rename \
  -H "Content-Type: application/json" \
  -d '{"newName": "research-lead"}'
```

---

## GET /agents/:name/sessions

List sessions for a specific agent. This is a convenience alias for `GET /sessions?agentName=:name`.

**Query parameters**

| Param | Type | Description |
|-------|------|-------------|
| `status` | string | Filter by `active` or `closed` |
| `limit` | integer | Max results (default: 20) |
| `cursor` | string | Pagination cursor |

**Response**
```json
{
  "agentName": "assistant",
  "sessions": [
    { "id": "sess_...", "mode": "chat", "status": "active", ... }
  ]
}
```

---

## GET /agents/:name/skills

List skills configured for an agent and their load status.

**Response**
```json
{
  "agentName": "assistant",
  "skills": [
    { "name": "web-search", "loaded": true, "source": "project", "path": "/.../.veil/skills/web-search.md" },
    { "name": "missing-skill", "loaded": false, "error": "File not found" }
  ],
  "customTools": [
    { "name": "my_tool", "source": "project" }
  ]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `skills[].name` | string | Skill name from agent config |
| `skills[].loaded` | boolean | Whether the skill file was found |
| `skills[].source` | string | `"agent"` or `"project"` |
| `skills[].path` | string | Absolute path (if loaded) |
| `skills[].error` | string | Error message (if not loaded) |
| `customTools` | array | Custom tools discovered for this agent |
