> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents plane

> The agents plane runs one registry agent per request and hands back its result. It is the execution plane you reach with an ozk_ key (see Authentication) at POST /v1/agents/:id/execute. Every call wraps exactly one minimal CNL task in the engine envelope, so the agents plane is the single-task …


The agents plane runs one registry agent per request and hands back its result. It is the
execution plane you reach with an `ozk_` key (see [Authentication](/one-api/authentication)) at
`POST /v1/agents/:id/execute`. Every call wraps exactly **one** minimal CNL task in the engine
envelope, so the agents plane is the single-task front door; multi-task orchestration lives on the
[Workflows plane](/one-api/workflows) and bounded autonomous missions on a `type: harness` task
there (see [capability injection](/one-api/skills-plugins-capabilities)).

Base URL is always `https://api.plungeai.com`. All examples below use `$PLUNGE_API_KEY` for an
`ozk_…` key. Source: `orchestration/api-gateway/routes/agents.ts`.

## 9.1 Agent tiers

An **agent** is a registry card whose id equals its Worker service name. There are ~90 active
agents (`GET /v1/agents?limit=100` returned `count: 92`, live-verified 2026-09-19; the default page is 50 — follow `has_more`/`next_offset`). They fall into these
tiers. Every id below is **status:active** in the registry unless the row says otherwise
(`agents/agents/README.md`, `agents/agents/CORE-AGENTS-DECISION.md`).

| Tier | id(s) | What it is | Use when | Call how |
|---|---|---|---|---|
| **Tool agent** | ~90 ids: `serper-agent`, `exa-agent`, `tavily-agent`, `sec-agent`, `serpapi-agent`, `markitdown`, `weather-agent`, … | a registry agent with a typed invocation contract (parameters/operations) | you need one bounded capability (search, documents, finance, a connector) | `POST /v1/agents/:id/execute` with a `prompt`; or the [Tools plane](/one-api/tools) `POST /v1/tools/:id/execute` with typed params; or an `agent:` task in a workflow |
| **`one-agent`** — the agent runtime | `one-agent` | the one runtime, two modes chosen by the payload. **Call mode** (no loop command): a single LLM call — YAML in → provider RPC → result — with `persona` / `expert(s)` / `skill(s)` injection and `messages` threading. **Loop mode** (`max_iterations` / `maxTurns` or a mission): a bounded ReAct mission — tool fence, iteration cap, long-term memory, capability injection | any generation (pinned model/provider, shaped by a persona or skill) and multi-step autonomous work that calls tools/sub-agents | call mode: agents-plane execute; `model`/`provider`/`system`/`messages`/`persona`. Loop mode: a `type: harness` task with `agent: one-agent` on the [Workflows plane](/one-api/workflows) — the agents plane forwards no mission fields (see 9.3), so it runs call mode only |
| **`concierge`** | `concierge` | interactive Discover→Inspect→Execute front-door over any agent, with a payment/approval gate | "do X" where the platform must find and call the right agent, with a money gate | its own approval contract (9.7) and the [MCP plane](/one-api/mcp) `plungeai_continue`; on the raw agents plane re-send with `approved: true` + `action_token`, or approve by prompt (`approve <token>`, 9.7) |
| **`claude-managed-agent`** | deployed, **not** in the active list | Anthropic hosted Managed Agents runtime for autonomous cloud code execution | autonomous cloud code runs (higher infra overhead) | not exposed on the ozk agents plane today — live-verified 2026-09-18 it is absent from `GET /v1/agents`, so `/v1/agents/claude-managed-agent/execute` returns `404 unknown_agent` |
| **Campaign kind** | Studio `kind: campaign` (no `campaign-agent` Worker) | a scheduled, ledger-driven workflow that works a task list in short runs | recurring bounded batch work | via the scheduler / MCP `plungeai_schedule` / Studio, not a callable agent id (`agents/agents/campaign-agent/README.md`) |

> **Legacy ids.** `llm-agent`, `skill-agent`, `universal-agent` and `agentic-agent` are frozen
> legacy ids: they stay callable until the switch-over, then re-point to `one-agent`. Write
> `agent: one-agent` everywhere — a `type: harness` task with **no** `agent:` line still runs on the
> legacy `harness-agent` loop today. The bare `harness-agent` binding is not a callable registry id
> on the planes (live-verified: `404 unknown_agent`).

## 9.2 List agents & categories

- `GET /v1/agents?limit=<1..100>&offset=<n>` → `{ agents: [...], count, limit, offset, has_more, next_offset }` — `count` is the page size (default limit 50); when `has_more` is true, fetch the next page at `offset=next_offset`. Each card carries `id`,
  a summary and `tags` (including `status:active`).
- `GET /v1/agents/categories` → `{ categories: [{name, count}], count }`, aggregated from the
  active-agent summaries (there is no separate categories store).

```bash
curl -s "https://api.plungeai.com/v1/agents?limit=5" \
  -H "Authorization: Bearer $PLUNGE_API_KEY"
```

## 9.3 Execute — body reference

`POST /v1/agents/:id/execute`. JSON body (default) or an Ocean-format `text/yaml` body with the
same fields. Every accepted field, from `routes/agents.ts`:

| Field | Type | Status | Meaning |
|---|---|---|---|
| `prompt` | string | **supported** — required unless `messages` has a user turn | the per-run input. With no `prompt`/`input`, the last non-blank user message of `messages` is used; neither → `400 missing_prompt` |
| `input` | string | supported | alias for `prompt` |
| `sync` | boolean | supported (default `true`) | `false` → `202` pointer, poll the result route (9.5) |
| `stream` | boolean | supported (default `false`) | `true` → an OpenAI-shaped SSE stream (9.6) |
| `messages` | array | supported | prior chat turns `[{role, content}]`; **`prompt` is optional when `messages` has a user turn** (the last one is used). `one-agent` (call mode) sends the messages — not a separate prompt — to the model, so it answers from the whole conversation; any other agent receives `messages` as an input field and threads it only if it reads it. For a conversation that survives across calls use the [MCP plane](/one-api/mcp) (`session_id`, `plungeai_continue`) |
| `system` | string | supported | system-prompt frame forwarded to the agent |
| `model` | string | supported | pin a model; validated against the catalog → `422 unknown_model` if unknown/inactive |
| `provider` | string | supported | pin a provider; normalised (`google`→`gemini`). Must travel **with** `model` — a pinned model without its provider routes to the agent default |
| `temperature` | number | supported | sampling temperature |
| `top_p` | number | supported | nucleus sampling |
| `max_tokens` / `maxTokens` | number | supported | output-token budget (must be `> 0`) |
| `reasoning_effort` | string | supported | reasoning-effort hint forwarded to the model |
| `thinking_level` | string | supported | thinking-level hint forwarded to the model |
| `persona` | string | supported | the **only** capability field forwarded on this plane |
| `format` | `json`\|`yaml`\|`markdown`\|`text` | supported | response format (also `?format=` or `Accept`). Unknown value → `400 invalid_format` (`lib/format.ts`) |
| `user_context` | object · optional · default: derived from your request's network location (time zone, approximate place) | supported | the caller's time zone, locale and location (`{time_zone, locale, place, latitude, longitude, accuracy_m, source}`, the `UserContext` schema); the agent uses it for local time and as the default schedule time zone |

Fields not in this table (e.g. `goal`, `mission`, `allowed_tools`, `skills`, `experts`, `mcp`)
are **ignored** on the agents plane — run those through a `type: harness` task on the
[Workflows plane](/one-api/workflows). Two more fields are accepted only as a pair, the approve
round-trip of [9.7](#97-the-concierge-approval-flow): `approved: true` with `action_token` (a string
of 1-128 characters), plus an optional `allow_scope` (`once` | `run` | `always`, anything else is
`once`). One without the other, or an `approved` that is not exactly `true` or `false`, is
`400 invalid_params`; `approved: false` alone is a fresh turn.

### Conversation without a prompt

An OpenAI-style body is a valid agent call — the model answers from the whole conversation:

```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"My name is Ada."},{"role":"assistant","content":"Hello Ada."},{"role":"user","content":"Reply with only my name."}],"max_tokens":16}'
```

### Sync execute (default)

```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"Reply with only the word: pong","max_tokens":24}'
```

Success (`200`): `{ "content": "...", "workflow_id": "...", "task_id": "t1", "request_id": "..." }`.
`request_id` equals the trace id (see [Traces](/one-api/traces-observability)).

### Model / provider pinning

Pin a model and provider together; discover models with `GET /v1/models`
([Models plane](/one-api/models)) or `GET /v1/discovery/search?kind=models` ([Discovery](/one-api/discovery)).

```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"Reply with only: pong","provider":"openai","model":"gpt-4o-mini","max_tokens":16}'
```

## 9.4 Async execution (202 + poll)

Send `"sync": false`. The response is `202` with a pointer `{ workflow_id, task_id, request_id }`;
read the result from the result route once it is ready.

```bash
RESP=$(curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"prompt":"Reply with only the word: pong","max_tokens":24,"sync":false}')
echo "$RESP"
WF=$(printf '%s' "$RESP" | sed -n 's/.*"workflow_id":"\([^"]*\)".*/\1/p')
TASK=$(printf '%s' "$RESP" | sed -n 's/.*"task_id":"\([^"]*\)".*/\1/p')
for i in 1 2 3 4 5 6 7 8; do
  R=$(curl -s "https://api.plungeai.com/v1/agents/results/$WF/$TASK" \
    -H "Authorization: Bearer $PLUNGE_API_KEY")
  case "$R" in *'"content"'*) echo "$R"; break;; *) sleep 2;; esac
done
```

## 9.5 Results route

`GET /v1/agents/results/:workflowId/:taskId` → `{ content, content_type, workflow_id, task_id }`.
It reconciles the run's terminal state (`lib/execution-row.ts`):

| Situation | Status | Code |
|---|---|---|
| result stored | `200` | — |
| run still in flight | `404` | `not_ready` |
| run failed | `409` | `execution_failed` |
| run completed but produced no visible content | `422` | `empty_completion` (raise `max_tokens ≥ 64`) |

## 9.6 Streaming

Send `"stream": true` on `POST /v1/agents/:id/execute`. The agents plane emits an
**OpenAI-shaped** chunk stream (the only stream shape on this plane) — `Content-Type:
text/event-stream`, `data:` lines only (no named `event:` lines), each a `chat.completion.chunk`,
terminated by `data: [DONE]`. This mirrors the models plane; see [Streaming](/one-api/streaming).
Relay + encoder: `lib/stream-relay.ts` (`relayEngineStream`, `openaiEncoder`).

Exact request shape:

```bash
curl -sN "https://api.plungeai.com/v1/agents/one-agent/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"Reply with only the word: pong","max_tokens":24,"stream":true}'
```

Observed wire (live-verified 2026-09-18):

```text
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"gemini-3.1-flash-lite","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"pong"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":72,"completion_tokens":1,"total_tokens":73}}

data: [DONE]
```

Notes: `model` is the pinned model, else the provider's default (never the agent id); a final
`usage` is included when the provider reported the token split. On an error **after** the first
byte the stream emits `data: {"error":{…}}` and closes **without** `[DONE]` (OpenRouter behaviour);
a pre-dispatch rejection is a JSON error (see 9.8), not a stream.

## 9.7 The concierge approval flow

`concierge` runs a bounded Discover→Inspect→Execute loop with three tools — `find_agent`
(discover), `agent_card` (inspect), `run_agent` (execute) — and a payment gate
(`agents/agents/concierge-agent/README.md`). Money operations (`buy`, `purchase`, `send`,
`transfer`, `pay`, `withdraw`, `shop`, …) and any card `gated:<op>` are held for approval:

1. A gated action returns `{ "needs_approval": true, "pending_action": {…}, "action_token": "<10-char>", "messages": [...] }`.
2. Re-invoke with the same `messages`, plus `"approved": true` and the `action_token`, to execute
   and continue. The token is held server-side for 1 hour. `ask_user` questions work the same way
   (append the user's answer to `messages`).

On the One API this handshake is surfaced through the [MCP plane](/one-api/mcp): the pause arrives
as a ⏸ `continuation`, and you approve with `plungeai_continue { approve: true }` (never approve on
the user's behalf). The **raw agents plane takes the same pair** (`routes/agents.ts`): send the next
call with the same `messages`, `"approved": true` and the `action_token` (1-128 characters; a half
pair is `400 invalid_params`) and the agent checks the token against your key's pending actions. You
can also approve by prompt: a paused reply ends with
`Running outside the chat? Re-run with input: approve <token>`, so send `{"prompt":"approve <token>"}`
as the next call and the held action runs for your key. An unknown, used or expired token is a
graceful no-op that runs no model and spends nothing — the example uses one:

```bash
curl -s "https://api.plungeai.com/v1/agents/concierge/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"approve 0000000000","max_tokens":64}'
```

(A real approval run can spend money, so it is not run here.)

## 9.8 Errors

Pre-dispatch and post-dispatch errors from `routes/agents.ts` / `routes/_util.ts`. Full envelope
and header reference: [Errors, limits & headers](/one-api/errors-limits).

| Status | Code | When |
|---|---|---|
| `400` | `missing_prompt` | no `prompt`/`input` string |
| `400` | `invalid_format` | `format` is not `json`/`yaml`/`markdown`/`text` |
| `404` | `unknown_agent` | no such agent id (fenced before dispatch) |
| `403` | `agent_not_active` | the card is not `status:active` |
| `422` | `unknown_model` | pinned `model` absent/inactive in the catalog |
| `422` | `empty_completion` | run completed but produced no visible content |
| `422` | `invalid_params` | agent outcome `needs_input` |
| `424` | `connection_required` | outcome `needs_connection` (connect an account — chapter 7) |
| `424` | `credential_required` | outcome `needs_api_key` |
| `503` | `provider_unconfigured` | the provider has no API key configured on the platform |
| `503` | `agent_unavailable` | outcome `unavailable` |
| `502` | `engine_error` / `execution_failed` | the engine/agent failed |
| `502` | `result_unavailable` | run finished but the result could not be retrieved |
| `409` | `duplicate_execution_id` | a replayed `x-trace-id` UUID |
| `413` | `payload_too_large` | body over the cap (default 1 MiB) |

```bash
curl -s "https://api.plungeai.com/v1/agents/no-such-agent-xyz/execute" \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"prompt":"hi","max_tokens":8}'
```
