> _**HTTP skill · `agent` namespace** · ~22,743 tokens_

# `agent` — run AI coding agents in a container: delegate work, subagents, memory, branches

## Purpose

This is the **programmatic control plane for running AI coding agents inside a container** — 47 services / 239 methods that let you delegate a task to a remote agent, drive it across turns, and read back its messages, diffs, and git branches, all over the API. It also exposes the agent's persistent **memory** (blocks + journal), its **capabilities** (model providers, MCP tool servers, skills, tools), a multi-agent **orchestration** layer ("God Mode": an orchestrator that plans, an executor that spawns worker sub-sessions, verifiers that gate each with a PASS/FAIL, a phase reviewer), and the **subagent roster** (configured CLI agents, RSI reviewers, verifiers).

**Workspaces is the GUI, not the namespace.** Hoody ships a full browser GUI — *Workspaces* — that is one optional human-facing client of this exact surface; everything it does (run sessions, branch/PR, wire MCP, edit memory, orchestrate) is reachable here via the SDK or raw HTTP, and vice-versa (the CLI can't authenticate to this kit — see the CLI caveat in Quirks). Hand a person the URL `https://{P}-{C}-workspaces-1.{N}.containers.hoody.icu` when they want a screen; reach for this namespace when you want automation. The kit slug is `workspaces` (`agent` ↔ `workspaces` rename; instance index `1`, i.e. the URL segment `workspaces-1`); resolve the URL with `getKitUrl('agent', container)`. Most endpoints live under `/api/v1/workspaces/*`; the one-shot `* /api/v1/agent/prompt*` delegate endpoints (and a couple of cross-session listers) live under `/api/v1/agent/*`.

## When to use

- **Delegate a coding/automation task to an agent in a remote container** and collect its result — quickest path is `POST /api/v1/agent/prompt` (no workspace bookkeeping); for multi-turn work use `POST /api/v1/workspaces/{workspaceID}/sessions` + `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message`/`POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` then read `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/diff`.
- **Run / query subagents** — orchestrate multiple agents and phases ("God Mode") via `* /api/v1/workspaces/{workspaceID}/orchestration/*`, and inspect the subagent roster with `GET /api/v1/workspaces/{workspaceID}/meta/agents`, `GET /api/v1/workspaces/{workspaceID}/config/cli-agents`, `GET /api/v1/workspaces/{workspaceID}/config/reviewers`, `GET /api/v1/workspaces/{workspaceID}/config/verifiers`.
- Give the agent persistent context it consults across runs — memory blocks + searchable journal (`* /api/v1/workspaces/{workspaceID}/memory/*`).
- Configure what the agent can do — providers (`GET /api/v1/workspaces/{workspaceID}/providers`), MCP tool servers (`GET /api/v1/workspaces/{workspaceID}/mcp`), skills (`PATCH /api/v1/workspaces/{workspaceID}/skills/{name}`), tools (`GET /api/v1/workspaces/{workspaceID}/tools`), and `PATCH /api/v1/workspaces/{workspaceID}/config`.
- Run an agent against a git worktree, then inspect its diff, push a branch, open a PR (`* /api/v1/workspaces/{workspaceID}/branches*`).
- Answer an agent's mid-run clarification questions to unblock it (`* /api/v1/workspaces/{workspaceID}/questions*`), or run an RSI multi-reviewer critique of a finished session (`* /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/r*`).
- Hand a human the **Workspaces GUI URL** when they want to drive any of the above through a browser.

## When NOT to use

- One-off shell command or script with no agent loop/session → see `exec` (or `terminal` for an interactive shell).
- Direct container filesystem reads/writes (the worktree itself) → see `files`.
- Projects, containers, container **claims/authorization**, auth, billing, server rentals → see `api`.
- VS Code in a browser tab (editor GUI, not an agent) → see `code`.
- Generic key/value or relational storage → see `sqlite`.
- User-authored knowledge notebooks (distinct from the agent's memory journal) → see `notes`.
- Scheduling recurring jobs → see `cron` (for a recurring agent prompt, schedule a call to `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message`).

## Prerequisites

- Container with `hoody-workspaces` kit installed.
- **A container claim on every kit call — this is the #1 thing that breaks first calls.** Mint one with `POST /api/v1/containers/{id}/authorize` (`POST /api/v1/containers/{C}/authorize`) and send BOTH `X-Hoody-Container-Claim: JSON.stringify(data.container_claim)` and `X-Hoody-Token: <your API token>` on every request; a bare `Authorization: Bearer` returns `401 CLAIM_REQUIRED`. The claim is signed and **reusable until `expires_in` (~6h)** — mint once, reuse, re-authorize when it lapses. The SDK wires both headers for you via `withContainer(containerOrId, { kitAuth: { type: 'containerClaim', claim, token } })` (a 2-arg call; `kitAuth` is the second arg, and it does NOT auto-mint — pass the claim in). Raw `curl`/HTTP must attach the two headers by hand. See § Capability URL for the failure taxonomy.
- Workspace created via `POST /api/v1/workspaces` and bound via `POST /api/v1/workspaces/{workspaceID}/container` before per-workspace calls (the worktree dir must already exist on disk). `workspaceID = "global"` is rejected on every route (`400`, code `global-workspace-rejected`) — it is the project-storage sentinel, not a routable workspace ID. The supported alias is `"home"`, which resolves to the auto-created default workspace; otherwise send the real 24-char hex ID. The top-level `POST /api/v1/agent/prompt` needs no pre-created workspace.
- For session prompts: a provider configured via `PATCH /api/v1/workspaces/{workspaceID}/config`. For `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr`/`POST /api/v1/workspaces/{workspaceID}/branches/{id}/push`: a configured git remote and resolvable forge token.

## Capability URL

The kit slug is `workspaces` (instance index `1`): `https://{P}-{C}-workspaces-1.{N}.containers.hoody.icu` — get `P`/`C`/`N` from `GET /api/v1/containers/{id}`. The **root of that URL is the Workspaces GUI** (an SPA; the page itself loads with no token — hand it to a human and they log in with their Hoody account, which mints the claim for them); the API lives mostly under `/api/v1/workspaces/*` (the one-shot `* /api/v1/agent/prompt*` endpoints under `/api/v1/agent/*`) and needs the claim headers above. You can also run `hoody agent open --url` to print that same browser URL. Claim failures from the kit auth gate: `401 CLAIM_REQUIRED` (no claim), `401 TOKEN_REQUIRED` (claim but no token), `403 CLAIM_MALFORMED` (unparseable), `403 CLAIM_INVALID` (bad signature / expired / wrong container), `403 BINDING_MISMATCH` (token owner ≠ claim subject), `401 TOKEN_INVALID`. → See `SKILL-HTTP.md § Proxy URLs` for the slug table and capability-token methodology.

**Reaching a service you host on a container port** (any port, any namespace):

- `https://{projectId}-{containerId}-http-<port>.{node}.containers.hoody.icu` — proxy speaks HTTP to `localhost:<port>`.
- `https://{projectId}-{containerId}-https-<port>.{node}.containers.hoody.icu` — proxy speaks HTTPS to `localhost:<port>` (target needs TLS).

Edge is always `https://`. No alias, firewall edit, or proxy registration needed; capability-token gates still apply.

## Common workflows

### 1. Delegate a task to a remote agent (bootstrap + session)

Single-shot, no workspace bookkeeping: `POST /api/v1/agent/prompt` (or `POST /api/v1/agent/prompt/sync` to block for the final reply). Full multi-turn control:

1. `POST /api/v1/workspaces`
2. `POST /api/v1/workspaces/{workspaceID}/container`
3. `PATCH /api/v1/workspaces/{workspaceID}/config`
4. `POST /api/v1/workspaces/{workspaceID}/sessions`
5. `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` (or `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` / `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/abort`)
6. `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/export` / `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/diff` (or `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages` to drain output)

### 2. Branch → push → open PR

1. `POST /api/v1/workspaces/{workspaceID}/branches`
2. `POST /api/v1/workspaces/{workspaceID}/sessions` + `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message`
3. `GET /api/v1/workspaces/{workspaceID}/branches/{id}/status` / `GET /api/v1/workspaces/{workspaceID}/branches/{id}/diff`
4. `POST /api/v1/workspaces/{workspaceID}/branches/{id}/push`
5. `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr` + `GET /api/v1/workspaces/{workspaceID}/branches/{id}/pr`
6. `POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge` (dry-run first) + `DELETE /api/v1/workspaces/{workspaceID}/branches/{id}`

### 3. Add MCP server and call its tools

1. `POST /api/v1/workspaces/{workspaceID}/mcp`
2. `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth` → `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth/callback` (OAuth servers only; `DELETE /api/v1/workspaces/{workspaceID}/mcp/{name}/auth` to revoke)
3. `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/connect` + `GET /api/v1/workspaces/{workspaceID}/mcp`
4. `GET /api/v1/workspaces/{workspaceID}/experimental/resource` / `GET /api/v1/workspaces/{workspaceID}/experimental/tool`
5. `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` + `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/disconnect`

### 4. Memory blocks and journal search

1. `PUT /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` (specify `scope: "workspace" | "global"`)
2. `PATCH /api/v1/workspaces/{workspaceID}/memory/blocks/{label}`
3. `POST /api/v1/workspaces/{workspaceID}/memory/journal`
4. `POST /api/v1/workspaces/{workspaceID}/memory/journal/search` / `GET /api/v1/workspaces/{workspaceID}/memory/journal`

### 5. RSI review of a finished session

1. `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/review`
2. `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/runs/{jobID}/stream`

### 6. Orchestrate / query subagents ("God Mode")

You don't spawn a worker directly — you enqueue work and the executor spawns a worker *session* per item; verifiers gate each, a phase reviewer summarizes. Inspect the roster first with `GET /api/v1/workspaces/{workspaceID}/meta/agents` / `GET /api/v1/workspaces/{workspaceID}/config/cli-agents` / `GET /api/v1/workspaces/{workspaceID}/config/reviewers` / `GET /api/v1/workspaces/{workspaceID}/config/verifiers`.

1. `POST /api/v1/workspaces/{workspaceID}/orchestration/todo/entries` — enqueue a task entry (the delegation primitive); group with `POST /api/v1/workspaces/{workspaceID}/orchestration/phases`.
2. `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/start` — turn on the dispatch loop (spawns worker sub-sessions).
3. Drive via the planner instead: `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/session` → `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/prompt`.
4. Query live subagents: `GET /api/v1/workspaces/{workspaceID}/orchestration/executor/workers` (→ `[{sessionID, entryID, phase, status}]`); drill into any worker as a normal session with `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}` / `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages`.
5. Observe: `GET /api/v1/workspaces/{workspaceID}/orchestration/events` (SSE event bus).

### 7. Answer the agent's questions (unblock a paused session)

When an agent hits ambiguity it calls its `question` tool, which BLOCKS the session until you respond.

1. `GET /api/v1/workspaces/{workspaceID}/questions` — pending questions across sessions (or watch `GET /api/v1/workspaces/{workspaceID}/meta/events` for `question.asked`).
2. `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` — optional: get a second model's recommendation (read-only; does NOT resolve the question).
3. `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reply` (body field `answers`: `string[][]`) or `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reject` — resumes the agent.

## Quirks & gotchas

- `workspaceID` is 24-char hex (validated via `WORKSPACE_ID_RE`); only container-bound workspaces appear in `GET /api/v1/workspaces`.
- `workspaceID = "global"` is rejected everywhere with `400 { code: "global-workspace-rejected" }`; use the `"home"` alias to target the default workspace (resolved via `ensureDefaultWorkspace()`; `503 no-default-workspace` if none exists).
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` returns 409 `session_busy` envelope `{ error, code }` if cancelled mid-init.
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` returns 204 immediately (status set at `:1353`), runs detached; provider errors only logged, no completion signal beyond event subscription.
- `DELETE /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` requires explicit `?scope=` query param, no default.
- Core memory block labels reject `readOnly: true`.
- `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr` is rate-limited per-project (429); requires configured remote, picks first remote if no `origin` (fallback at `:1270`).
- RSI gated by `config.rsi.enabled`; disabled returns 403 `rsi_disabled`. Inline reviewers with ad-hoc `name` require `model`.
- RSI runs idempotent on `Idempotency-Key`: retry within TTL replays same `jobID`. SSE emits snapshot first.
- Kit slug is `workspaces`; API reject-lists `workspaces` for proxy hooks/permissions.
- The container claim is **reusable until `expires_in` (~6h)** — mint once via `POST /api/v1/containers/{id}/authorize`, reuse, re-authorize when it lapses; you do NOT need a fresh claim per call.
- `withContainer(containerOrId, options)` is a **2-arg** call — `kitAuth` goes in the second arg — and it does NOT mint the claim for you; pass the already-minted claim in `options.kitAuth`.
- **The generic CLI cannot authenticate to this kit.** `kitAuthType` only supports `jwt|password|token` — there is no `containerClaim` branch — so `hoody agent …` / `hoody workspaces …` kit subcommands do not attach the claim and return `401 CLAIM_REQUIRED`. Only *interactive* `hoody shell` auto-mints a claim (the one-shot `hoody shell <id> -- cmd` form does not). For programmatic agent-kit access use the SDK or raw HTTP; the CLI blocks in §Examples show command shape only.
- SDK-only quirk: several generated SDK methods take a non-workspace ID as their FIRST positional argument, breaking the workspace-first norm — `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/connect` / `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/disconnect`, `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/authorize`, `POST /api/v1/workspaces/{workspaceID}/permissions/{requestID}/reply`, and all `branches` `/{id}/…` methods (`GET /api/v1/workspaces/{workspaceID}/branches/{id}/diff`). The HTTP paths themselves stay workspace-first, and the CLI uses order-independent named flags.
- Orchestration: workers are spawned ONLY by the executor (no direct "spawn worker" call — you enqueue with `POST /api/v1/workspaces/{workspaceID}/orchestration/todo/entries`); the verifier's `verdict` (PASS/FAIL) is authoritative. Note the MITM-overlay services that sit alongside it in this namespace (`* /api/v1/workspaces/{workspaceID}/mitm/diagnostics/dry-run*` → `/mitm/diagnostics/dry-run`, `* /api/v1/workspaces/{workspaceID}/mitm/overlay/rebase*` → `/mitm/overlay/rebase`) are NOT part of the multi-agent orchestration flow despite the workspace-scoped naming.

## Common errors

- `409 { error: "Prompt aborted before completion", code: "session_busy" }`.
- `409 { error: "Command aborted before completion" }`.
- `404 Workspace entry {workspaceID} not found` — create workspace before `POST /api/v1/workspaces/{workspaceID}/container`/`DELETE /api/v1/workspaces/{workspaceID}/container`.
- `400 { error: "Invalid workspace ID: <id>", code: "invalid-workspace-id" }` — must match 24-char lowercase hex; the alias `"global"` is NOT accepted on workspace routes (returns code `global-workspace-rejected`).
- `403 { error: "rsi_disabled", message: "RSI is disabled in configuration (config.rsi.enabled = false)." }`.
- `404 { error: "unknown_reviewer" }` — supply `model` for ad-hoc reviewer name.
- `429 Rate limit exceeded` from `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr` — per-project limiter.
- `ValidationError: No git remote configured` — bind a remote before forge ops.

## Related namespaces

- `api` — projects, containers, auth, billing, server rentals.
- `exec` — one-off scripts without a session.
- `files` — direct container filesystem access.
- `terminal` — interactive shell beyond `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/shell`.
- `notes` — user-authored notes (separate from agent memory journal).
- `cron` — schedule recurring `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` triggers.

## Examples

Every step in every example was live-tested against a real `workspaces-1` kit (kit slug is `workspaces`, NOT `agent`). The endpoints used below live under `/api/v1/workspaces/...` (the one-shot `* /api/v1/agent/prompt*` delegate endpoints, not exercised here, are under `/api/v1/agent/...`). Each step has a copy-pasteable code block in the mode you're reading. Set `P`, `C`, `N` (project id, container id, server name) from `GET /api/v1/containers/{id}` first, and mint a container claim once (`POST /api/v1/containers/{id}/authorize` returns it; it is reusable until `expires_in` (~6h), so reuse it across the calls below). SDK's `withContainer(container, { kitAuth: { type:'containerClaim', claim, token } })` wires the headers for you (2-arg call; it does not mint — pass the claim in).

⚠ Headers — when calling kit URLs directly (curl/HTTP), you MUST send BOTH `X-Hoody-Container-Claim: <json-stringified claim>` AND `X-Hoody-Token: <api token>` on every request. `Authorization: Bearer …` alone returns `401 { code: "CLAIM_REQUIRED" }`. Live-verified.

⚠ CLI mode caveat — the generic `hoody agent …` / `hoody workspaces …` commands do **not** attach the container claim (the CLI's kit-auth supports only `jwt|password|token`, not `containerClaim`), so they return `401 CLAIM_REQUIRED` against this kit. The `hoody …` blocks below are shown for command/argument shape; for working programmatic access use the SDK or raw HTTP (or interactive `hoody shell`, the only CLI path that auto-mints a claim). Local SDK/HTTP paths are unaffected; to just print the browser URL, run `hoody agent open --url`.

### 1. Bootstrap a workspace and run a session

**Goal:** create a fresh `examples-<random>` workspace, bind it to a container, run one async session prompt, then read messages back. Worktree must exist on disk inside the container BEFORE `POST /api/v1/workspaces` (live-verified — `400 worktree does not exist` otherwise).

**Step 1 — make the worktree directory** via the `files` kit, then create the workspace.

```bash
KIT="https://${P}-${C}-workspaces-1.${N}.containers.hoody.icu"
FILES="https://${P}-${C}-files-1.${N}.containers.hoody.icu"
CLAIM=$(curl -sf -X POST "https://api.hoody.icu/api/v1/containers/$C/authorize" \
  -H "Authorization: Bearer $TOKEN" | jq -c .data.container_claim)
H=(-H "X-Hoody-Container-Claim: $CLAIM" -H "X-Hoody-Token: $TOKEN" -H 'Content-Type: application/json')

WNAME="examples-$(openssl rand -hex 3)"
WORKTREE="/root/$WNAME"
curl -sf -X MKCOL "$FILES/${WORKTREE#/}" "${H[@]}" >/dev/null

W=$(curl -sf -X POST "$KIT/api/v1/workspaces" "${H[@]}" \
  -d "$(jq -nc --arg w "$WORKTREE" --arg n "$WNAME" '{worktree:$w,name:$n,color:"#22c55e",visible:true}')" \
  | jq -r .id)
curl -sf -X POST "$KIT/api/v1/workspaces/$W/container" "${H[@]}" \
  -d "$(jq -nc --arg c "$C" --arg p "$P" --arg n "$N" '{containerId:$c,projectId:$p,serverNode:$n}')"
echo "W=$W WORKTREE=$WORKTREE"
```
**Step 2 — create a session and fire an async prompt** (`POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` returns 204 immediately; provider runs detached).

```bash
SID=$(curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions" "${H[@]}" \
  -d '{"title":"bootstrap demo"}' | jq -r .id)
curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions/$SID/prompt_async" "${H[@]}" \
  -d '{"parts":[{"type":"text","text":"List the files in the worktree"}],"model":{"providerID":"hoody","modelID":"hoody-free"}}'
```
**Step 3 — poll for messages, read the diff, abort if needed.** `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` provides no completion signal — drain via `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages` or subscribe to `GET /api/v1/workspaces/{workspaceID}/meta/events` (SSE).

```bash
curl -sf "$KIT/api/v1/workspaces/$W/sessions/$SID/messages?limit=20" "${H[@]}" | jq '.[].info.role'
curl -sf "$KIT/api/v1/workspaces/$W/sessions/$SID/diff" "${H[@]}"
curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions/$SID/abort" "${H[@]}"   # if it's still running
```
### 2. Set memory blocks + journal entries (workspace vs global scope)

**Goal:** seed a free-form note for the agent to consult, replace part of it, write a journal entry, and search the journal. Reuse `$W` from example 1.

⚠ **Workspace-scoped blocks require a non-`global` projectID** — even on a `POST /api/v1/workspaces/{workspaceID}/container`-ed workspace the kit may report `pid is "global"` and reject `scope:"workspace"` with `400 Cannot create workspace-scoped block "<label>": no project detected (pid is "global"). Use global scope instead.` Live-verified. Default to `scope:"global"` unless your container deployment sets a real project context.

**Step 1 — set + read a block.** `value` is the text the agent sees; `description` is metadata.

```bash
curl -sf -X PUT "$KIT/api/v1/workspaces/$W/memory/blocks/notes" "${H[@]}" \
  -d '{"value":"hello world","scope":"global","description":"demo note"}'
curl -sf "$KIT/api/v1/workspaces/$W/memory/blocks/notes" "${H[@]}"
```
**Step 2 — replace** uses `old_str / new_str` (NOT `search/replace`; live-verified — wrong field names → `400`):

```bash
curl -sf -X PATCH "$KIT/api/v1/workspaces/$W/memory/blocks/notes" "${H[@]}" \
  -d '{"scope":"global","old_str":"world","new_str":"examples"}'
```
**Step 3 — journal create + search.** Body field names are `title` + `body` (NOT `content`). Live-verified — `{ content }` returns `400`.

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/memory/journal" "${H[@]}" \
  -d '{"title":"bootstrap done","body":"workspace examples-xxx ready","tags":["demo"]}'
curl -sf -X POST "$KIT/api/v1/workspaces/$W/memory/journal/search" "${H[@]}" \
  -d '{"text":"bootstrap","limit":5}'  # body field is named text (the search-query field is not called "q" or "query")
```
**Step 4 — delete the block** (DELETE requires `?scope=global|workspace` query param, no default — live-verified. The generated SDK and CLI do NOT expose a scope flag, so drop to raw `fetch()` whenever you need scoped deletion):

```bash
curl -sX DELETE "$KIT/api/v1/workspaces/$W/memory/blocks/notes?scope=global" "${H[@]}"
```
### 3. Configure the provider that drives sessions

**Goal:** read the current per-workspace agent config (provider, default model, permissions), see which models are available from the catalog, then point `model` at a different one. Container ships with `hoody/hoody-free` by default.

**Step 1 — read the active config + list available providers.**

```bash
curl -sf "$KIT/api/v1/workspaces/$W/config" "${H[@]}" | jq '{provider, model, agent}'
# /config/providers returns {providers, default}; the broader catalog with .all[]
# lives at /providers (no /config prefix).
curl -sf "$KIT/api/v1/workspaces/$W/config/providers" "${H[@]}" | jq '.providers'
curl -sf "$KIT/api/v1/workspaces/$W/providers" "${H[@]}" | jq '.all[].id'
curl -sf "$KIT/api/v1/workspaces/$W/providers" "${H[@]}" | jq '.all[] | select(.id=="hoody") | .models | keys'
```
**Step 2 — switch model** via `PATCH /api/v1/workspaces/{workspaceID}/config` (PATCH; partial — only fields you send change):

```bash
curl -sf -X PATCH "$KIT/api/v1/workspaces/$W/config" "${H[@]}" \
  -d '{"model":"hoody/hoody-free","provider":{"hoody":{"name":"Hoody AI","options":{"baseURL":"https://ai.hoody.icu/api/v1"}}}}'
```
**Step 3 — list auth methods for a provider you'd add (e.g. OAuth chain for OpenAI/Anthropic).** `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/authorize` returns a URL the user opens; `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/callback` finalises with the redirect's `?code=`.

```bash
curl -sf "$KIT/api/v1/workspaces/$W/providers/auth" "${H[@]}" | jq '.openai'
curl -sf -X POST "$KIT/api/v1/workspaces/$W/providers/openai/oauth/authorize" "${H[@]}" \
  -d '{"method":0}'
```
### 4. Register an MCP tool server

**Goal:** add a local-stdio MCP server (binary on PATH) and a remote one, list status, then remove. ⚠ Body shape is `{ name, config: { type: 'local' | 'remote', ... } }` — flat `{ name, type, command }` returns `400`. Live-verified.

**Step 1 — add a local stdio server.**

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp" "${H[@]}" \
  -d '{"name":"demo-local","config":{"type":"local","command":["/usr/local/bin/my-mcp"],"enabled":false,"environment":{"DEBUG":"1"}}}'
```
**Step 2 — add a remote SSE/streamable-HTTP server with OAuth.**

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp" "${H[@]}" \
  -d '{"name":"demo-remote","config":{"type":"remote","url":"https://mcp.example.com/sse","enabled":false,"oauth":true,"headers":{"X-API-Key":"abc"}}}'
# OAuth start (only for type:remote with oauth!=false):
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp/demo-remote/auth" "${H[@]}"  # → returns authorize URL
# After user redirect, finalise:
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp/demo-remote/auth/callback" "${H[@]}" -d '{"code":"<from-redirect>"}'
```
**Step 3 — connect, status, disconnect, delete.** `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/connect` brings the server up; status reports `connected | disabled | failed | needs_auth | needs_client_registration` (the `failed` and `needs_client_registration` variants carry an `error` string). ⚠ There is no `removeServer` SDK method and no `DELETE /mcp/{name}` route. To "remove" a server, disconnect it and clear stored OAuth via `DELETE /mcp/{name}/auth` (returns `200 {success:true}`).

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp/demo-local/connect" "${H[@]}"
curl -sf "$KIT/api/v1/workspaces/$W/mcp" "${H[@]}"
curl -sf -X POST "$KIT/api/v1/workspaces/$W/mcp/demo-local/disconnect" "${H[@]}"
# Note: `DELETE /mcp/{name}` is not a real route. To "remove" a server,
# disconnect (above) and clear stored auth: DELETE /mcp/{name}/auth.
curl -sX DELETE "$KIT/api/v1/workspaces/$W/mcp/demo-local/auth" "${H[@]}"
curl -sX DELETE "$KIT/api/v1/workspaces/$W/mcp/demo-remote/auth" "${H[@]}"
```
### 5. Branch-based agent run (worktree, diff, push, PR)

**Goal:** create a git worktree branch, send the agent prompts on it, inspect diff, push, open PR. ⚠ `POST /api/v1/workspaces/{workspaceID}/branches` requires the workspace's `worktree` to be a real git repository (`git init` + at least one commit). Live-verified — non-git worktree returns `400`. Workflow shown in HTTP for the canonical path; CLI/SDK mirror.

**Step 1 — git-init the worktree** (one-time, via the `files` kit's git operations or shell into the container with `terminal`/`exec`), then create the branch:

```bash
# Inside the container (terminal kit), git init + first commit:
#   cd $WORKTREE && git init -q && echo hi > README.md && git add . \
#     && git -c user.email=a@b -c user.name=alex commit -qm init
B=$(curl -sf -X POST "$KIT/api/v1/workspaces/$W/branches" "${H[@]}" \
  -d '{"name":"feature/agent-run","baseBranch":"master"}' | jq -r .id)
echo "branch=$B"
```
**Step 2 — drive the agent on this branch, then read the diff.** Sessions created on this workspace pick up the branch worktree directory automatically.

```bash
SID=$(curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions" "${H[@]}" \
  -d '{"title":"branch run"}' | jq -r .id)
curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions/$SID/prompt_async" "${H[@]}" \
  -d '{"parts":[{"type":"text","text":"Add a TODO.md with three items"}],"model":{"providerID":"hoody","modelID":"hoody-free"}}'
# Wait for completion (poll messages or events), then:
curl -sf "$KIT/api/v1/workspaces/$W/branches/$B/diff?format=full" "${H[@]}"  # only `summary` | `full`; `patch` is invalid
curl -sf "$KIT/api/v1/workspaces/$W/branches/$B/status" "${H[@]}"
```
**Step 3 — push, open PR, dry-run merge.** `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr` is rate-limited per project (429); requires a configured remote (else `ValidationError: No git remote configured`). `POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge` accepts `dry_run:true` — always run that first.

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/branches/$B/push" "${H[@]}" -d '{"setUpstream":true}'
curl -sf -X POST "$KIT/api/v1/workspaces/$W/branches/$B/pr" "${H[@]}" \
  -d '{"title":"Add TODO.md","body":"agent-generated","target":"master","draft":true}'
curl -sf -X POST "$KIT/api/v1/workspaces/$W/branches/$B/merge" "${H[@]}" \
  -d '{"strategy":"merge","dry_run":true}'
```
### 6. RSI multi-reviewer review of a finished session

**Goal:** after the agent finishes a session, run the configured Recursive-Self-Improvement reviewers (or supply ad-hoc ones inline) and stream their critique. ⚠ Disabled returns `403 { error: "rsi_disabled" }`. Inline reviewers with ad-hoc `name` REQUIRE `model` (else `404 unknown_reviewer`). RSI is idempotent on `Idempotency-Key` — retry within TTL replays the same `jobID`.

**Step 1 — kick off the review.** Omit `reviewers` to run all configured ones; pass an array to scope. Inline objects override per-call.

```bash
JOB=$(curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions/$SID/rsi/review" "${H[@]}" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "focus":"Check for off-by-one errors and missing tests",
    "reviewers":[{"name":"strict","model":"hoody/hoody-free"}]
  }' | jq -r .jobID)
echo "rsi job=$JOB"
```
**Step 2 — stream verdicts (SSE).** First event is a snapshot of state at subscription time, then incremental updates until completion.

```bash
curl -N "$KIT/api/v1/workspaces/$W/sessions/$SID/rsi/runs/$JOB/stream" "${H[@]}"
```
### 7. Image-gen and web-search availability check

**Goal:** sessions can call `imagegen` / `websearch` tools if enabled and authenticated. There's no `imageGen.create` / `webSearch.search` HTTP endpoint — the kit only exposes the *enablement* surface; actual generation runs as a tool call inside `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message`. To trigger one, send a prompt that asks for an image / web result and let the agent invoke the tool.

**Step 1 — check availability.** Live-verified responses: `{enabled:true, model:"hoody/google/gemini-3.1-flash-image-preview", provider:"hoody", authenticated:true}` for image-gen; similar for web-search.

```bash
curl -sf "$KIT/api/v1/workspaces/$W/image-gen/status" "${H[@]}"
curl -sf "$KIT/api/v1/workspaces/$W/web-search/status" "${H[@]}"
```
**Step 2 — invoke via a session prompt.** Tool gating happens server-side; agent picks the right tool based on instructions.

```bash
curl -sf -X POST "$KIT/api/v1/workspaces/$W/sessions/$SID/prompt_async" "${H[@]}" \
  -d '{"parts":[{"type":"text","text":"Generate a 512x512 PNG of a red square and save it under ./logo.png. Then web-search for the current Bun version and write it into VERSIONS.md."}],"model":{"providerID":"hoody","modelID":"hoody-free"}}'
```
### 8. Question-and-answer flow (orchestrator interrupts)

**Goal:** when an agent session emits a `question` (e.g. "should I rename `foo` → `bar`?"), it pauses until you `reply` or `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reject`. (`POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` is a separate read-only side-channel for getting a second model's recommendation before you reply — it does not resolve the question.) List → answer → resume.

**Step 1 — list pending questions.**

```bash
curl -sf "$KIT/api/v1/workspaces/$W/questions" "${H[@]}"
```
**Step 2a — answer it directly** (`reply`). **Step 2b — get a second opinion** (`POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` — runs the question through `providerID/modelID` and returns a recommended answer to YOU (the caller). It is a stateless side-channel: it does NOT notify the agent or resolve the question, so you must still `reply` (or `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reject`) with the recommendation to unblock the session.) ⚠ `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` REQUIRES both `providerID` and `modelID` (live-verified — `400 invalid_type` for missing fields). `note` is an optional human hint.

```bash
QID=<from step 1>
# Direct reply:
curl -sf -X POST "$KIT/api/v1/workspaces/$W/questions/$QID/reply" "${H[@]}" \
  -d '{"answers":[["yes, rename foo to bar"]]}'  # body field is `answers: QuestionAnswer[]`, where each QuestionAnswer is `string[]` — so the wire shape is `string[][]`.
# OR delegate:
curl -sf -X POST "$KIT/api/v1/workspaces/$W/questions/$QID/consult" "${H[@]}" \
  -d '{"providerID":"hoody","modelID":"hoody-free","note":"prefer bar"}'
# OR reject (closes the question, agent gets denial):
curl -sf -X POST "$KIT/api/v1/workspaces/$W/questions/$QID/reject" "${H[@]}"
```
### 9. List and inspect available agent tools (per provider/model)

**Goal:** discover what tools the agent has access to in this workspace. `GET /api/v1/workspaces/{workspaceID}/experimental/tool/ids` returns a catalog of bare tool-id strings (typical entries: bash, read, glob, grep, edit, write, task, webfetch, todowrite, websearch, imagegen, codesearch, skill, apply_patch, plus `hoody_*` capability tools); `GET /api/v1/workspaces/{workspaceID}/experimental/tool` returns full JSON-Schemas tailored to the chosen `providerID/modelID` combination so you can see what arguments each tool accepts.

```bash
curl -sf "$KIT/api/v1/workspaces/$W/experimental/tool/ids" "${H[@]}" | jq -r '.[]'
curl -sf "$KIT/api/v1/workspaces/$W/experimental/tool?provider=hoody&model=hoody-free" "${H[@]}" \
  | jq '.[] | {id, description}' | head -40
```
### 10. Tear down a workspace cleanly (cleanup template)

**Goal:** the inverse of example 1 — sessions, branches, MCP servers, memory blocks, then `DELETE /api/v1/workspaces/{workspaceID}/container` and `DELETE /api/v1/workspaces/{workspaceID}`. Always do this for `examples-*` workspaces; they accumulate forever otherwise (worktree on disk too).

**Step 1 — cascade-delete the children** (sessions, journal, blocks, branches, MCP). Each list-then-delete loop scopes only the resources you created.

```bash
# Sessions
for SID in $(curl -sf "$KIT/api/v1/workspaces/$W/sessions?limit=200" "${H[@]}" | jq -r '.items[].id'); do
  curl -sX DELETE "$KIT/api/v1/workspaces/$W/sessions/$SID" "${H[@]}" >/dev/null
done
# Journal entries
for ID in $(curl -sf "$KIT/api/v1/workspaces/$W/memory/journal" "${H[@]}" | jq -r '.[].id'); do
  curl -sX DELETE "$KIT/api/v1/workspaces/$W/memory/journal/$ID" "${H[@]}" >/dev/null
done
# Memory blocks (must include scope)
for L in $(curl -sf "$KIT/api/v1/workspaces/$W/memory/blocks" "${H[@]}" | jq -r '.[] | select(.label!="persona" and .label!="human") | .label'); do
  curl -sX DELETE "$KIT/api/v1/workspaces/$W/memory/blocks/$L?scope=global" "${H[@]}" >/dev/null
done
# Branches
for B in $(curl -sf "$KIT/api/v1/workspaces/$W/branches" "${H[@]}" | jq -r '.[].id'); do
  curl -sX DELETE "$KIT/api/v1/workspaces/$W/branches/$B" "${H[@]}" >/dev/null
done
# MCP servers — there is no DELETE /mcp/{name}; clear stored auth instead.
for N in $(curl -sf "$KIT/api/v1/workspaces/$W/mcp" "${H[@]}" | jq -r 'keys[]'); do
  curl -sX POST "$KIT/api/v1/workspaces/$W/mcp/$N/disconnect" "${H[@]}" >/dev/null
  curl -sX DELETE "$KIT/api/v1/workspaces/$W/mcp/$N/auth" "${H[@]}" >/dev/null
done
```
**Step 2 — unbind the container, delete the workspace entry, drop the worktree dir.** The kit does NOT delete the worktree on disk — `DELETE /api/v1/files/{path}` it yourself.

```bash
curl -sX DELETE "$KIT/api/v1/workspaces/$W/container" "${H[@]}"
curl -sX DELETE "$KIT/api/v1/workspaces/$W" "${H[@]}"
curl -sX DELETE "$FILES/${WORKTREE#/}" "${H[@]}"
```
**Step 3 — verify it's gone.**

```bash
curl -sf "$KIT/api/v1/workspaces?limit=50" "${H[@]}" | jq '.items | length'
```

## Reference

### `branches` (17) — Branches

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/branches` | Create a new branch | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pr` | Create pull/merge request | `body*:agent_CreatePRInput` |
| `DELETE /api/v1/workspaces/{workspaceID}/branches/{id}` | Delete a branch |  |
| `GET /api/v1/workspaces/{workspaceID}/branches/{id}/diff` | Get branch diff | `?base` `?file` `?format` |
| `GET /api/v1/workspaces/{workspaceID}/branches/disk-usage` | Get branch disk usage | `?id` |
| `GET /api/v1/workspaces/{workspaceID}/branches/{id}/status` | Get branch git status |  |
| `GET /api/v1/workspaces/{workspaceID}/branches/{id}/pr` | Get PR/MR status |  |
| `GET /api/v1/workspaces/{workspaceID}/branches/remote` | Get remote info |  |
| `GET /api/v1/workspaces/{workspaceID}/branches/{id}/remote-status` | Get remote tracking status |  |
| `GET /api/v1/workspaces/{workspaceID}/branches` | List all branches |  |
| `GET /api/v1/workspaces/{workspaceID}/branches/remote-refs` | List remote branches/tags | `?remote` |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge` | Merge branch | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/pull` | Pull from remote | `body*:agent_PullInput` |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/push` | Push branch to remote | `body*:agent_PushInput` |
| `PATCH /api/v1/workspaces/{workspaceID}/branches/{id}` | Rename branch display name | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/reset` | Reset branch to base |  |
| `POST /api/v1/workspaces/{workspaceID}/branches/{id}/retry` | Retry failed branch |  |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/branches` body — `{ name: string, startCommand: string, baseBranch: string }`
- `POST /api/v1/workspaces/{workspaceID}/branches/{id}/merge` body — `{ strategy: "squash" | "rebase" | "merge", message: string, dry_run: bool, deleteBranch: bool }`
- `PATCH /api/v1/workspaces/{workspaceID}/branches/{id}` body — `{ name*: string }`

### `cliAgents` (1) — List configured CLI agents

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/config/cli-agents` | List configured CLI agents |  |

### `config` (3) — Config operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/config` | Get configuration |  |
| `GET /api/v1/workspaces/{workspaceID}/config/tool-overrides` | Get workspace tool overrides |  |
| `PATCH /api/v1/workspaces/{workspaceID}/config` | Update configuration | `body*` |

**Body shapes:**

- `PATCH /api/v1/workspaces/{workspaceID}/config` body — `{ permission: object | null, tool_overrides: object | null, tool_wake_policy: object | null, yolo: bool | null, provider: object | null, disabled_providers: string[] | null, enabled_providers: string[] | null, model: string | null, small_model: string | null, default_agent: string | null, instructions: string[] | null }`

### `dryRun` (1) — Simulate rule firing without side effects

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/diagnostics/dry-run` | Simulate rule firing without side effects | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/diagnostics/dry-run` body — `{ event*: "session.created" | "session.idle" | "session.error" | "chat.message" | "tool.execute.before" | "tool.execute.after" | "chat.system.transform", sessionTags: string[], depth: int=0, toolName: string, role: "user" | "assistant", messageContent: string }`

### `enable` (1) — Persistent enable/disable of a rule via overlay enabledOverride

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/rules/{id}/enable` | Persistent enable/disable of a rule via overlay enabledOverride | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/rules/{id}/enable` body — `{ enabled*: bool }`

### `events` (1) — SSE stream of MITM rule firings (live)

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/events` | SSE stream of MITM rule firings (live) |  |

### `experimental` (3) — Experimental operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/experimental/resource` | Get MCP resources |  |
| `GET /api/v1/workspaces/{workspaceID}/experimental/tool/ids` | List tool IDs |  |
| `GET /api/v1/workspaces/{workspaceID}/experimental/tool` | List tools | `?provider*` `?model*` |

### `files` (6) — Files operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/files/find/file` | Find files | `?query*` `?dirs` `?type` `?limit` |
| `GET /api/v1/workspaces/{workspaceID}/files/find/symbol` | Find symbols | `?query*` |
| `GET /api/v1/workspaces/{workspaceID}/files/file/status` | Get file status |  |
| `GET /api/v1/workspaces/{workspaceID}/files/file` | List files | `?path*` |
| `GET /api/v1/workspaces/{workspaceID}/files/file/content` | Read file | `?path*` |
| `GET /api/v1/workspaces/{workspaceID}/files/find` | Find text | `?pattern*` |

### `git` (1) — Git

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/git/init` | Initialize git repository for this workspace |  |

### `health` (1) — Health

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/health` | Service health check |  |

### `imageGen` (1) — Get image generation status

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/image-gen/status` | Get image generation status |  |

### `matchTrace` (1) — Augmented dry-run with per-rule trace info

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/diagnostics/match-trace` | Augmented dry-run with per-rule trace info | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/diagnostics/match-trace` body — `{ event*: "session.created" | "session.idle" | "session.error" | "chat.message" | "tool.execute.before" | "tool.execute.after" | "chat.system.transform", sessionTags: string[], depth: int=0, toolName: string, role: "user" | "assistant", messageContent: string }`

### `mcp` (8) — Mcp operations

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mcp` | Add MCP server | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth/authenticate` | Authenticate MCP OAuth |  |
| `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth/callback` | Complete MCP OAuth | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/connect` | Connect an MCP server |  |
| `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/disconnect` | Disconnect an MCP server |  |
| `GET /api/v1/workspaces/{workspaceID}/mcp` | Get MCP status |  |
| `DELETE /api/v1/workspaces/{workspaceID}/mcp/{name}/auth` | Remove MCP OAuth |  |
| `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth` | Start MCP OAuth |  |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mcp` body — `{ name*: string, config*: agent_McpLocalConfig | agent_McpRemoteConfig }`
- `POST /api/v1/workspaces/{workspaceID}/mcp/{name}/auth/callback` body — `{ code*: string }`
  - `code` — Authorization code from OAuth callback

### `memory` (14) — Memory operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/memory/journal/count` | Count journal entries |  |
| `POST /api/v1/workspaces/{workspaceID}/memory/journal` | Write journal entry | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` | Delete memory block |  |
| `DELETE /api/v1/workspaces/{workspaceID}/memory/journal/{id}` | Delete journal entry |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` | Get memory block |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/config` | Get memory config |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/history/{id}` | Get history event |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/journal/{id}` | Get journal entry |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/blocks` | List memory blocks |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/history` | List history events |  |
| `GET /api/v1/workspaces/{workspaceID}/memory/journal` | List journal entries |  |
| `PATCH /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` | Replace in memory block | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/memory/journal/search` | Search journal entries | `body*` |
| `PUT /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` | Set memory block | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/memory/journal` body — `{ title*: string, body*: string, tags: string[], projectID: string, model: string, provider: string }`
- `PATCH /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` body — `{ scope*: "global" | "workspace", old_str*: string, new_str*: string }`
- `POST /api/v1/workspaces/{workspaceID}/memory/journal/search` body — `{ text: string, projectID: string, tags: string[], limit: number }`
- `PUT /api/v1/workspaces/{workspaceID}/memory/blocks/{label}` body — `{ scope*: "global" | "workspace", value*: string, description: string, limit: number, readOnly: bool }`

### `meta` (9) — Workspace Meta

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/meta/dispose` | Dispose instance |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/formatter/status` | Get formatter status |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/lsp/status` | Get LSP status |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/path` | Get paths |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/vcs` | Get VCS info |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/agents` | List agents |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/commands` | List commands |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/skills` | List skills |  |
| `GET /api/v1/workspaces/{workspaceID}/meta/events` | Subscribe to events |  |

### `orchestration` (61) — Orchestration

| Method | Summary | Params |
|--------|---------|--------|
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/budget/entries/{entryID}` | Edit entry budget (sets budget_human_locked) | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/budget` | Get global budget status with per-entry breakdown |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/budget/entries/{entryID}/lock` | Toggle budget_human_locked on an entry |  |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/budget` | Update global budget (max project spend) | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/force-dispatch` | Force an executor dispatch cycle with diagnostics |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/executor/locks` | Get file locks per entry |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/executor/status` | Get executor status |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/executor/workers` | List active worker sessions |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/pause` | Pause executor dispatching |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/resume` | Resume executor dispatching |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/entries/{entryID}/reverify` | Re-run verification only (skip worker) |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/start` | Start executor dispatch loop |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/stop-all` | Stop all workers and pause executor |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/executor/workers/{sessionID}/stop` | Stop a specific worker |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/config` | Get orchestration config |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/debug-dump` | Export full orchestration debug dump |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/events/connections` | Get SSE connection count |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/import/{jobID}` | Get import job status |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/log` | Read tool call log (paginated, filterable) |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/session` | Create or resume orchestrator session |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/phases/{phaseID}/session` | Get phase orchestrator session info |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/session` | Get orchestrator session info |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/sessions` | Get all orchestrator sessions (planning + per-phase) |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/phases/{phaseID}/prompt` | Send prompt to phase orchestrator session | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/prompt` | Send prompt to orchestrator (with @todo mention resolution) | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/entries` | Add entry to phase | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/memory` | Add a note to phase memory | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/memory` | Clear phase memory |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/phases` | Create phases | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}` | Delete a phase (entries are unphased, not deleted) |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}` | Get single phase detail |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/phases/memory` | Get memory for all phases |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/summary` | Get phase summary |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/phases` | List all phases |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/memory` | Get phase memory notes |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/review` | Manually trigger phase review |  |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/rounds` | Update phase rounds budget | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/status` | Manually update phase status | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/verify` | Manually trigger phase verification |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/purge` | Purge all orchestration data for this workspace |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/questions/{questionID}/answer` | Answer a pending question | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/questions/{questionID}` | Get question detail |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/questions` | List pending questions |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/import` | Start a repo import | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/events` | SSE stream of all orchestration events (supports ?since_seq=N for reconnection) |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/log/stream` | Tool call log SSE stream |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/todo/entries` | Append entries to Master TODO | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}` | Delete a task entry |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/spec/freeze` | Freeze entry spec |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}` | Get a single Master TODO entry |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/todo/events` | Read Master TODO event log | `?page` `?limit` |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/todo` | Read full Master TODO state |  |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/spec` | Read entry spec |  |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/priority` | Update entry priority | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/rounds` | Set entry budget_rounds | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/status` | Update entry status | `body*` |
| `PUT /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/spec` | Update entry spec | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/orchestration/config` | Patch orchestration config (partial update) | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/orchestration/vault/discover` | Discover Master TODOs stored in Vault |  |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/vault/import` | Import a TODO from Vault into local storage | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/orchestration/vault/sync` | Sync local Master TODO snapshot to Vault |  |

**Body shapes:**

- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/budget/entries/{entryID}` body — `{ budget_usd*: number }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/budget` body — `{ max_project_spend_usd*: number }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/phases/{phaseID}/prompt` body — `{ text*: string }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/orchestrator/prompt` body — `{ text*: string }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/entries` body — `{ entryID*: string }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/memory` body — `{ text*: string }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/phases` body — `{ phases*: { name*: string, description*: string, entry_ids: string[], phase_rounds: number, container_id: string }[] }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/rounds` body — `{ phase_rounds*: int }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/phases/{phaseID}/status` body — `{ status*: "pending" | "active" | "verifying" | "fixing" | "done" | "failed" }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/questions/{questionID}/answer` body — `{ answers*: string[][] }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/import` body — `{ repoUrl*: string }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/todo/entries` body — `{ entries*: ({ type*: "task" | "correction" | "review" | "snapshot" | "note" | "parent", content*: string, spec: object, priority*: "critical" | "high" | "medium" | "low", depends_on: string[], parallelizable: bool=true, parallel_group: string, budget_usd: number, budget_rounds: number=3, phase_id: string, container_id: string })[] }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/priority` body — `{ priority*: "critical" | "high" | "medium" | "low" }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/rounds` body — `{ budget_rounds*: int }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/status` body — `{ status*: "pending" | "blocked" | "in_progress" | "done" | "failed" | "skipped" | "superseded", context_for_next: string, mistakes_learned: string[] }`
- `PUT /api/v1/workspaces/{workspaceID}/orchestration/todo/entries/{entryID}/spec` body — `{ requirements*: string, acceptance_criteria*: string[], files_to_create: string[], files_to_modify: string[], patterns: string, api_contract: string, examples: string, integration_points: string }`
- `PATCH /api/v1/workspaces/{workspaceID}/orchestration/config` body — `{ [key: string]: any }`
- `POST /api/v1/workspaces/{workspaceID}/orchestration/vault/import` body — `{ sourceWorkspaceID*: string, targetWorkspaceID: string }`

### `permissions` (3) — Permissions operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/config/permission` | Get workspace permission overrides |  |
| `GET /api/v1/workspaces/{workspaceID}/permissions` | List pending permissions |  |
| `POST /api/v1/workspaces/{workspaceID}/permissions/{requestID}/reply` | Respond to permission request | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/permissions/{requestID}/reply` body — `{ reply*: "once" | "always" | "reject", message: string }`

### `project` (2) — Project operations

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/project/current` | Get current project |  |
| `PATCH /api/v1/workspaces/{workspaceID}/project/{projectID}` | Update project | `body*` |

**Body shapes:**

- `PATCH /api/v1/workspaces/{workspaceID}/project/{projectID}` body — `{ name: string, icon: { url: string, override: string, color: string }, commands: { start: string } }`

### `prompt` (3) — Prompt

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/agent/prompt` | Execute prompt | `body*` |
| `GET /api/v1/agent/prompt` | Execute prompt via query | `?ai*` `?sessionID` `?providerID` `?modelID` `?endpoint` `?baseURL` `?apiKey` `?key` `?wait` `?autoApprove` `?agent` `?system` `?workspace` `?directory` |
| `POST /api/v1/agent/prompt/sync` | Execute prompt (synchronous) | `body*` |

**Body shapes:**

- `POST /api/v1/agent/prompt` body — `{ parts*: { type*: "text", text*: string }[], sessionID: string, model: { providerID*: string, modelID*: string }, endpoint: string, apiKey: string, wait: bool, autoApprove: bool, agent: string, system: string, workspace: string, directory: string }`
- `POST /api/v1/agent/prompt/sync` body — `{ parts*: { type*: "text", text*: string }[], sessionID: string, model: { providerID*: string, modelID*: string }, endpoint: string, apiKey: string, wait: bool, autoApprove: bool, agent: string, system: string, workspace: string, directory: string }`

### `providers` (5) — Providers operations

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/authorize` | OAuth authorize | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/callback` | OAuth callback | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/providers/auth` | Get provider auth methods |  |
| `GET /api/v1/workspaces/{workspaceID}/providers` | List providers |  |
| `GET /api/v1/workspaces/{workspaceID}/config/providers` | List config providers |  |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/authorize` body — `{ method*: number }`
  - `method` — Auth method index
- `POST /api/v1/workspaces/{workspaceID}/providers/{providerID}/oauth/callback` body — `{ method*: number, code: string }`
  - `code` — OAuth authorization code

### `questions` (4) — Questions operations

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` | Consult AI about a question | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/questions` | List pending questions |  |
| `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reject` | Reject question request |  |
| `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reply` | Reply to question request | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/consult` body — `{ providerID*: string, modelID*: string, note: string, questionIndex: int, system: string }`
- `POST /api/v1/workspaces/{workspaceID}/questions/{requestID}/reply` body — `{ answers*: agent_QuestionAnswer[] }`
  - `answers` — User answers in order of questions (each answer is an array of selected labels)

### `rebase` (1) — Re-validate overlay against new base; mark stale entries fresh

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/overlay/rebase` | Re-validate overlay against new base; mark stale entries fresh |  |

### `reset` (1) — Drop the entire overlay for this scope

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/overlay/reset` | Drop the entire overlay for this scope |  |

### `reviewers` (1) — List configured RSI reviewers

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/config/reviewers` | List configured RSI reviewers |  |

### `rsi` (2) — RSI

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/review` | Start an RSI review on a session | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/runs/{jobID}/stream` | SSE progress stream for an RSI review run |  |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/rsi/review` body — `{ focus: string, reviewers: (string | object)[] }`
  - `focus` — Optional focus instructions appended to each reviewer prompt. Max 10K chars.
  - `reviewers` — Reviewers to run for this call. Each entry is either a string (filter by name into config) or an inline object that overrides config fields per-call. Omit to use all configured reviewers. Max 20 entries.

### `rules` (1) — Replace overlay rule (full)

| Method | Summary | Params |
|--------|---------|--------|
| `PUT /api/v1/workspaces/{workspaceID}/mitm/rules/{id}` | Replace overlay rule (full) | `body*` |

**Body shapes:**

- `PUT /api/v1/workspaces/{workspaceID}/mitm/rules/{id}` body — `{ id*: string, name*: string, enabled: bool=true, description: string, severity: "info" | "warn" | "error" | "critical", trigger*: { event*: "session.created" | "session.idle" | "session.error" | "chat.message" | "tool.execute.before" | "tool.execute.after" | "chat.system.transform", tags: string[], toolName: string, role: "user" | "assistant", contentMatch: string }, action*: object, cooldownMs: number=0, maxDepth: number=1, blocking: bool=false }`

### `selfTuning` (3) — Self-Tuning

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/self-tuning/amplify` | Start a self-tuning amplify run on a session | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/self-tuning/runs/{jobID}/stream` | SSE progress stream for a self-tuning run |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/self-tuning/tune` | Start a self-tuning tune run on a session | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/self-tuning/amplify` body — `{ task*: string, verifier_name*: string, n*: int, model: { providerID*: string, modelID*: string } }`
  - `task` — The task / goal description for the amplify run. Max 100K chars.
  - `verifier_name` — Name of a configured verifier program. Max 128 chars.
  - `n` — Number of trials (odd, max 11) for majority voting.
  - `model` — Override the worker LLM for this call only. providerID/modelID must already be configured (or registered via PATCH /config beforehand).
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/self-tuning/tune` body — `{ task*: string, verifier_name*: string, max_iterations: int, model: { providerID*: string, modelID*: string } }`
  - `task` — The task / goal description for the tune run. Max 100K chars.
  - `max_iterations` — Cap on iteration count (1-20).

### `sessionMitmTags` (1) — Replace mitm_tags on a session

| Method | Summary | Params |
|--------|---------|--------|
| `PATCH /api/v1/workspaces/{workspaceID}/mitm/sessions/{sessionID}/tags` | Replace mitm_tags on a session | `body*` |

**Body shapes:**

- `PATCH /api/v1/workspaces/{workspaceID}/mitm/sessions/{sessionID}/tags` body — `{ tags*: string[] }`

### `sessions` (29) — Workspace Session

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/abort` | Abort workspace session |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/command` | Send command | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions` | Create workspace session | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/sessions/{sessionID}` | Delete workspace session |  |
| `DELETE /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message/{messageID}/part/{partID}` | Delete message part |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/export` | Export session (workspace) |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/fork` | Fork workspace session | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}` | Get workspace session |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/children` | Get child sessions |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/diff` | Get workspace session diff | `?messageID` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages/{messageID}` | Get workspace session message |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/status` | Get all workspace session statuses |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/summary` | Get workspace session summary |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/todo` | Get workspace session todos |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/init` | Initialize workspace session config | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions` | List workspace sessions | `?page` `?limit` `?roots` `?search` |
| `GET /api/v1/agent/all` | Sessions wall (alias) | `?workspace` `?directory` `?readonly` `?read_only` `?cardWidth` `?limit` `?sub` `?archived` `?containerId` `?projectId` `?serverNode` `?containerAlias` |
| `GET /api/v1/agent/sessions/live` | Sessions wall (HTML) | `?workspace` `?directory` `?readonly` `?read_only` `?cardWidth` `?limit` `?sub` `?archived` `?containerId` `?projectId` `?serverNode` `?containerAlias` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages` | List workspace session messages | `?limit` `?role` `?after` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` | Send message | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` | Send async message | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/revert` | Revert workspace session message | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/shell` | Run shell command | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/summarize` | Summarize session | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/unrevert` | Unrevert workspace session |  |
| `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}` | Update workspace session | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message/{messageID}` | Update message | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message/{messageID}/part/{partID}` | Update message part | `body*:agent_Part` |
| `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/tags` | Update session tags | `body*` |

**Param notes:**

- `messageID` — Optional message cursor for message-scoped diff
- `page` — Page number (1-indexed)
- `limit` — Items per page (max 200)
- `roots` — Only return root sessions (no parentID)
- `search` — Filter by title (case-insensitive)
- `limit` — Maximum messages to return
- `role` — Filter by role
- `after` — Cursor: only return messages with ID strictly greater than this (newer)

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/command` body — `{ messageID: string, agent: string, model: string, arguments*: string, command*: string, variant: string, parts: object[] }`
- `POST /api/v1/workspaces/{workspaceID}/sessions` body — `{ parentID: string, title: string, permission: agent_PermissionRuleset, metadata: { [key: string]: any } }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/fork` body — `{ messageID: string }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/init` body — `{ modelID*: string, providerID*: string, messageID*: string }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message` body — `{ messageID: string, model: { providerID*: string, modelID*: string }, agent: string, noReply: bool, tools: { [key: string]: bool }, system: string, variant: string, parts*: (agent_TextPartInput | agent_FilePartInput | agent_AgentPartInput | agent_SubtaskPartInput)[] }`
  - `tools` — @deprecated tools and permissions have been merged, you can set permissions on the session itself now
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/prompt_async` body — `{ messageID: string, model: { providerID*: string, modelID*: string }, agent: string, noReply: bool, tools: { [key: string]: bool }, system: string, variant: string, parts*: (agent_TextPartInput | agent_FilePartInput | agent_AgentPartInput | agent_SubtaskPartInput)[] }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/revert` body — `{ messageID*: string, partID: string }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/shell` body — `{ agent*: string, model: { providerID*: string, modelID*: string }, command*: string }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/summarize` body — `{ providerID*: string, modelID*: string, auto: any, systemPrompt: string }`
- `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}` body — `{ title: string, time: { archived: number }, permission: agent_PermissionRuleset | null, compaction_override: object | null }`
  - `title` — New session title
  - `time` — Timestamp fields to update
  - `permission` — Session-scoped permission ruleset. Each rule is `{ permission, pattern, action }` with `action ∈ {allow, deny, ask}`. Sent as an array, replaces the session's existing rules; `[]` clears overrides while keeping the field; `null` removes the field entirely.
  - `compaction_override` — Per-session auto-compaction override (stored in session metadata). Set fields win over the global `config.compaction.*`; `null` removes the override entirely (revert to global).
- `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/message/{messageID}` body — `{ model*: { providerID*: string, modelID*: string } }`
- `PATCH /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/tags` body — `{ tags*: string[] }`

### `skills` (7) — Agent Skills

| Method | Summary | Params |
|--------|---------|--------|
| `DELETE /api/v1/workspaces/{workspaceID}/skills/{name}` | Delete skill |  |
| `GET /api/v1/exec-skills` | Discover agent skills |  |
| `GET /api/v1/workspaces/{workspaceID}/skills/{name}` | Get skill |  |
| `GET /api/v1/workspaces/{workspaceID}/skills/marketplace` | Browse marketplace |  |
| `PATCH /api/v1/workspaces/{workspaceID}/skills/builtin/{name}` | Toggle built-in skill | `body*` |
| `PATCH /api/v1/workspaces/{workspaceID}/skills/{name}` | Partially update skill | `body*` |
| `PUT /api/v1/workspaces/{workspaceID}/skills/{name}` | Create or update skill | `body*` |

**Body shapes:**

- `PATCH /api/v1/workspaces/{workspaceID}/skills/builtin/{name}` body — `{ enabled*: bool }`
- `PATCH /api/v1/workspaces/{workspaceID}/skills/{name}` body — `{ description: string, content: string, enabled: bool }`
- `PUT /api/v1/workspaces/{workspaceID}/skills/{name}` body — `{ description*: string, content*: string, scope: "project" | "global"="project", enabled: bool }`

### `tools` (1) — List all tools

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/tools` | List all tools |  |

### `transientEnable` (1) — Transient (TTL'd) enable/disable of a rule

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/rules/{id}/transient-enable` | Transient (TTL'd) enable/disable of a rule | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/rules/{id}/transient-enable` body — `{ enabled*: bool, ttlMs: int=300000 }`

### `verifiers` (1) — List configured self-tuning verifiers

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/config/verifiers` | List configured self-tuning verifiers |  |

### `verify` (1) — Send a synthetic webhook delivery for diagnostics

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/webhooks/verify` | Send a synthetic webhook delivery for diagnostics | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/webhooks/verify` body — `{ url*: string, method: "POST" | "GET"="POST", headers: { [key: string]: string }, bodyJson: any }`

### `webSearch` (1) — Get web search status

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/web-search/status` | Get web search status |  |

### `workspace` (7) — Workspace

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/container` | Bind container to workspace | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/container` | Unbind container from workspace |  |
| `POST /api/v1/workspaces` | Create workspace entry | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}` | Delete workspace entry |  |
| `GET /api/v1/workspaces/{workspaceID}` | Get workspace |  |
| `GET /api/v1/workspaces` | List workspaces | `?page` `?limit` |
| `PATCH /api/v1/workspaces/{workspaceID}` | Update workspace | `body*` |

**Param notes:**

- `page` — Page number (1-indexed)
- `limit` — Items per page (max 200)

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/container` body — `{ containerId*: string, projectId*: string, serverNode*: string }`
- `POST /api/v1/workspaces` body — `{ worktree*: string, name: string, color: string, visible: bool, container*: { containerId*: string, projectId: string, serverName: string, serverNode: string } }`
- `PATCH /api/v1/workspaces/{workspaceID}` body — `{ name: string, icon: { url: string, override: string, color: string }, commands: { start: string } }`

### `workspaceMitmCooldowns` (1) — List active per-(rule,session) cooldowns

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/cooldowns` | List active per-(rule,session) cooldowns |  |

### `workspaceMitmLogEntry` (1) — Get single MITM log entry (redacted by default)

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/logs/{id}` | Get single MITM log entry (redacted by default) |  |

### `workspaceMitmLogsPaginated` (1) — Paginated MITM log (redacted)

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/logs` | Paginated MITM log (redacted) | `?page` `?limit` `?sessionID` |

### `workspaceMitmPluginDescriptors` (1) — List plugin descriptors

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/plugin-descriptors` | List plugin descriptors |  |

### `workspaceMitmRule` (3) — Workspace Mitm Rule operations

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/rules` | Create overlay rule | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/mitm/rules/{id}` | Delete overlay rule (or tombstone a base rule) |  |
| `PATCH /api/v1/workspaces/{workspaceID}/mitm/rules/{id}` | Patch overlay rule (partial) | `body*` |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/rules` body — `{ id*: string, name*: string, enabled: bool=true, description: string, severity: "info" | "warn" | "error" | "critical", trigger*: { event*: "session.created" | "session.idle" | "session.error" | "chat.message" | "tool.execute.before" | "tool.execute.after" | "chat.system.transform", tags: string[], toolName: string, role: "user" | "assistant", contentMatch: string }, action*: object, cooldownMs: number=0, maxDepth: number=1, blocking: bool=false }`
- `PATCH /api/v1/workspaces/{workspaceID}/mitm/rules/{id}` body — `{ [key: string]: any }`

### `workspaceMitmRules` (1) — List effective MITM rules

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/rules` | List effective MITM rules |  |

### `workspaceMitmSnapshot` (1) — Get effective MITM state

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/snapshot` | Get effective MITM state |  |

### `workspaceMitmTag` (2) — Workspace Mitm Tag operations

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/mitm/tags` | Create overlay tag | `body*` |
| `DELETE /api/v1/workspaces/{workspaceID}/mitm/tags/{id}` | Delete overlay tag (or tombstone a base tag) |  |

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/mitm/tags` body — `{ id*: string, label*: string, description: string="", color: "green" | "blue" | "yellow" | "purple" | "red" | "orange" | "gray"="gray" }`

### `workspaceMitmTags` (1) — List effective MITM tags

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/tags` | List effective MITM tags |  |

### `workspaceMitmValidationRules` (1) — Introspect MITM rule validation constraints

| Method | Summary | Params |
|--------|---------|--------|
| `GET /api/v1/workspaces/{workspaceID}/mitm/validation-rules` | Introspect MITM rule validation constraints |  |

### `workspaceSession` (22) — Workspace Session

| Method | Summary | Params |
|--------|---------|--------|
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/autocontext` | Gather conversation context (on-demand, stateless) | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/cli-agent` | Start a CLI agent run on a session | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/cli-agent/runs/{jobID}/stream` | SSE progress stream for a CLI agent run |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/followup` | Suggest a follow-up question (on-demand, stateless) | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/permissions` | Get effective permission ruleset for a session | `?agent` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages/{messageID}/tools/{callID}` | Get a tool-call result by callID |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/{jobId}/cancel` | Cancel a background job |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/cancel` | Cancel multiple background jobs | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/{jobId}` | Get a background job by ID |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/{jobId}/output` | Get full output of a background job |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/inject` | Inject completed job results into the session context | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs` | List background jobs for a session | `?status` `?limit` `?cursor` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/{jobId}/retry` | Retry a terminal-failed background job |  |
| `DELETE /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/loop` | Stop the active session loop |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/loop` | Start a session loop | `body*` |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/loop` | Get active session loop directive |  |
| `DELETE /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue` | Clear the queue |  |
| `DELETE /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue/{entryID}` | Delete a queued message |  |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue` | Enqueue a message while busy | `body*` |
| `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue/flush` | Send the queue now |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue` | List queued messages |  |
| `GET /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/messages/{messageID}/stream` | Stream live updates for a single message via SSE |  |

**Param notes:**

- `limit` — Max jobs to return per page. Defaults to no cap (returns the full filtered set).
- `cursor` — Pagination cursor. Pass the previous response's `nextCursor` to fetch the next page. Cursor is the last seen job ID; jobs are sorted ascending by creation time, so results begin at the job AFTER `cursor`.

**Body shapes:**

- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/autocontext` body — `{ model: { providerID*: string, modelID*: string }, window: int, system: string, depth: "conversation" | "repo" }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/cli-agent` body — `{ agent*: string, prompt*: string, model: string, git: bool, timeout: int }`
  - `agent` — Configured CLI agent name (case-insensitive). e.g. "Gemini Flash", "Codex".
  - `prompt` — Prompt to send to the CLI agent. Max 100K chars.
  - `model` — Override the agent's default model.
  - `git` — Request git access (only honoured if agent's allow_git is true, or codex-rw).
  - `timeout` — Override timeout (ms); clamped to the agent's configured ceiling.
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/followup` body — `{ model: { providerID*: string, modelID*: string }, window: int, system: string }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/cancel` body — `{ jobIds*: string[] }`
  - `jobIds` — Job IDs to cancel. Minimum 1, maximum 1000 per call. Unknown IDs are reported in `failed`, not 404.
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/jobs/inject` body — `{ jobIds: string[] }`
  - `jobIds` — Optional subset of job IDs to inject. Omit the field to drain all manual-policy unprocessed jobs (default behavior). Pass an empty array `[]` to explicitly inject nothing — distinct from omitting the field. Max 1000 IDs per call.
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/loop` body — `{ prompt*: string, iters*: int, stop_when: string, target: "self" | "child", agent: string, model: { providerID*: string, modelID*: string }, label: string, interval_ms: int }`
- `POST /api/v1/workspaces/{workspaceID}/sessions/{sessionID}/queue` body — `{ userParts: { type*: string }[], syntheticParts: { type*: string }[], agent*: string, model*: { providerID*: string, modelID*: string }, system: string, container: { containerId*: string, projectId*: string, serverNode*: string } }`


### Body schemas

- `agent_PermissionRuleset` — `agent_PermissionRule[]`
- `agent_TextPartInput` — `{ id: string, type*: "text", text*: string, synthetic: bool, ignored: bool, time: { start*: number, end: number }, metadata: { [key: string]: any } }`
- `agent_FilePartInput` — `{ id: string, type*: "file", mime*: string, filename: string, url*: string, source: agent_FilePartSource }`
- `agent_AgentPartInput` — `{ id: string, type*: "agent", name*: string, source: { value*: string, start*: int, end*: int } }`
- `agent_SubtaskPartInput` — `{ id: string, type*: "subtask", prompt*: string, description*: string, agent*: string, model: { providerID*: string, modelID*: string }, command: string }`
- `agent_FilePartSource` — `agent_FileSource | agent_SymbolSource | agent_ResourceSource`
- `agent_Part` — `agent_TextPart | agent_SubtaskPart | agent_ReasoningPart | agent_FilePart | agent_ToolPart | agent_JobResultPart | agent_StepStartPart | agent_StepFinishPart | agent_SnapshotPart | agent_PatchPart | agent_AgentPart | agent_RetryPart | agent_CompactionPart`
- `agent_PermissionRuleConfig` — `agent_PermissionActionConfig | agent_PermissionObjectConfig`
- `agent_ProviderConfig` — `{ api: string, name: string, env: string[], id: string, npm: string, models: { [key: string]: { id: string, name: string, family: string, release_date: string, attachment: bool, reasoning: bool, temperature: bool, tool_call: bool, interleaved: true | object, cost: object, limit: object, modalities: object, experimental: bool, status: "alpha" | "beta" | "deprecated", options: object, headers: object, provider: object, variants: object } }, whitelist: string[], blacklist: string[], options: { apiKey: string, baseURL: string, enterpriseUrl: string, setCacheKey: bool, timeout: int | false } }`
- `agent_McpLocalConfig` — `{ type*: "local", command*: string[], environment: { [key: string]: string }, enabled: bool, timeout: int }`
- `agent_McpRemoteConfig` — `{ type*: "remote", url*: string, enabled: bool, headers: { [key: string]: string }, oauth: agent_McpOAuthConfig | false, timeout: int }`
- `agent_QuestionAnswer` — `string[]`
- `agent_PushInput` — `{ remote: string, force: bool, setUpstream: bool }`
- `agent_PullInput` — `{ remote: string }`
- `agent_CreatePRInput` — `{ title*: string, body: string, target: string, draft: bool }`
- `agent_PermissionRule` — `{ permission*: string, pattern*: string, action*: agent_PermissionAction }`
- `agent_FileSource` — `{ text*: agent_FilePartSourceText, type*: "file", path*: string }`
- `agent_SymbolSource` — `{ text*: agent_FilePartSourceText, type*: "symbol", path*: string, range*: agent_Range, name*: string, kind*: int }`
- `agent_ResourceSource` — `{ text*: agent_FilePartSourceText, type*: "resource", clientName*: string, uri*: string }`
- `agent_TextPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "text", text*: string, synthetic: bool, ignored: bool, time: { start*: number, end: number }, metadata: { [key: string]: any } }`
- `agent_SubtaskPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "subtask", prompt*: string, description*: string, agent*: string, model: { providerID*: string, modelID*: string }, command: string }`
- `agent_ReasoningPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "reasoning", text*: string, metadata: { [key: string]: any }, time*: { start*: number, end: number } }`
- `agent_FilePart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "file", mime*: string, filename: string, url*: string, source: agent_FilePartSource }`
- `agent_ToolPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "tool", callID*: string, tool*: string, state*: agent_ToolState, metadata: { [key: string]: any } }`
- `agent_JobResultPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "job-result", jobId*: string, originCallID*: string, tool*: string, status*: "completed" | "failed" | "cancelled" | "expired", summary*: string, attachments: { mime*: string, url*: string }[] }`
- `agent_StepStartPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "step-start", snapshot: string }`
- `agent_StepFinishPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "step-finish", reason*: string, snapshot: string, cost*: number, tokens*: { total: number, input*: number, output*: number, reasoning*: number, cache*: object } }`
- `agent_SnapshotPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "snapshot", snapshot*: string }`
- `agent_PatchPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "patch", hash*: string, files*: string[] }`
- `agent_AgentPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "agent", name*: string, source: { value*: string, start*: int, end*: int } }`
- `agent_RetryPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "retry", attempt*: number, error*: agent_APIError, time*: { created*: number } }`
- `agent_CompactionPart` — `{ id*: string, sessionID*: string, messageID*: string, type*: "compaction", auto*: bool, systemPrompt: string }`
- `agent_PermissionActionConfig` — `"ask" | "allow" | "deny"`
- `agent_PermissionObjectConfig` — `{ [key: string]: agent_PermissionActionConfig }`
- `agent_McpOAuthConfig` — `{ clientId: string, clientSecret: string, scope: string }`
- `agent_PermissionAction` — `"allow" | "deny" | "ask"`
- `agent_FilePartSourceText` — `{ value*: string, start*: int, end*: int }`
- `agent_Range` — `{ start*: { line*: number, character*: number }, end*: { line*: number, character*: number } }`
- `agent_ToolState` — `agent_ToolStatePending | agent_ToolStateRunning | agent_ToolStateCompleted | agent_ToolStateError | agent_ToolStateBackgrounded`
- `agent_APIError` — `{ name*: "APIError", data*: { message*: string, statusCode: number, isRetryable*: bool, responseHeaders: object, responseBody: string, metadata: object } }`
- `agent_ToolStatePending` — `{ status*: "pending", input*: { [key: string]: any }, raw*: string }`
- `agent_ToolStateRunning` — `{ status*: "running", input*: { [key: string]: any }, title: string, metadata: { [key: string]: any }, time*: { start*: number } }`
- `agent_ToolStateCompleted` — `{ status*: "completed", input*: { [key: string]: any }, output*: string, title*: string, metadata*: { [key: string]: any }, time*: { start*: number, end*: number, compacted: number }, attachments: agent_FilePart[] }`
- `agent_ToolStateError` — `{ status*: "error", input*: { [key: string]: any }, error*: string, metadata: { [key: string]: any }, time*: { start*: number, end*: number } }`
- `agent_ToolStateBackgrounded` — `{ status*: "backgrounded", input*: { [key: string]: any }, output*: string, title*: string, metadata*: { [key: string]: any }, jobId*: string, time*: { start*: number, backgrounded*: number, compacted: number } }`
