Agents
An agent is a named, configured Claude principal — a slug, a system prompt, a model, and an allow-list of tools. Catentio ships with a built-in roster (hachimi, tora, sai, ren, fumi, iro, and a long tail of project-specific names) and you can mint custom, DB-backed agents via this resource.
The control plane is a proxy: these endpoints forward to the runtime and, in public deployments, redact runtime-only fields on the way back out.
POST /v1/agents/{slug}/invokedoes not work today. The route is registered on the control plane, but it forwards to a runtime endpoint that doesn't exist, so the call fails. Start runs withPOST /v1/runs— that is what the portal's own Invoke button uses. Details below.
The agent object
What GET /v1/agents returns, one object per agent.
{
"slug": "hachimi",
"description": "Orchestrator + default agent.",
"model": "claude-opus-4-8",
"module": "app.agents.db_loader",
"class_name": "_BuiltinAgent_hachimi",
"prompt_length": 8214,
"allowed_tools_count": 18,
"allowed_tools": ["bash", "read", "write", "..."],
"is_builtin": true,
"default_model": null,
"selection_mode": "static",
"reasoning": null,
"resolver_enforce": false,
"memory_mode": "both"
}
| Field | Type | Notes |
|---|---|---|
slug |
string | URL-safe handle. Primary key. |
description |
string | One-liner. |
model |
string | The agent's model id. |
module / class_name |
string | Where the agent is defined. Custom (DB-backed) agents report "(db)" / "(custom)". Blanked by redaction — see below. |
prompt_length |
int | Character count of the system prompt. The prompt body itself is not returned in the list. |
allowed_tools_count |
int | |
allowed_tools |
string[] | Tool slugs this agent may load. |
is_builtin |
bool | Built-in (shipped) vs custom (DB-backed). |
default_model / selection_mode / reasoning / resolver_enforce |
Model-resolver sidecar overrides. null where no override is set. |
|
memory_mode |
string | Defaults to "both". |
Custom agents additionally carry owner_customer_id and version.
The list's meta is { "total": N, "builtin": N, "custom": N }.
Redaction. When a deployment sets
CATENTIO_PORTAL_VISIBILITY=public, built-in agents (those whosemodulestarts withapp.) come back withprompt,session_prompt,heartbeat_prompt,module,class_name,allowlist_blockandallowlist_addblanked, plusredacted: true. Custom agents are never redacted. Nothing else is stripped.
Endpoints
List agents
GET /v1/agents
The full roster for your workspace. Powers Dashboard → Agents.
const { data } = await catentio.agents.list();
body = catentio.agents.list()
agents = body["data"]
body, err := client.Agents.List(ctx, token)
Retrieve an agent
GET /v1/agents/{slug}/detail
One agent with the full row plus a recent-run summary. The /detail suffix exists so the path doesn't shadow POST /v1/agents/{slug}/invoke.
Note the SDKs' agents.get(slug) targets GET /v1/agents/{slug} — a route that does not exist on the control plane. Call /detail directly.
Errors: 404 if the slug isn't registered.
Create an agent
POST /v1/agents
Mint a custom, DB-backed agent.
{
"slug": "new-agent",
"model": "claude-sonnet-4-6",
"prompt": "You are …",
"description": "What this agent is for.",
"allowed_tool_slugs": ["bash", "read"]
}
| Field | Required | Notes |
|---|---|---|
slug |
yes | |
model |
yes | |
prompt |
yes | The system prompt. |
description |
no | |
allowed_tool_slugs |
no | |
default_workspace |
no | |
default_model / selection_mode |
no | Model-resolver overrides. |
Your tier caps how many custom agents you may have. Over the cap, the runtime returns 403 with a plan_limit_exceeded detail.
Update an agent
PATCH /v1/agents/{slug}
PATCH /v1/agents/{slug}/model
PATCH /v1/agents/{slug}/voice
PATCH /v1/agents/{slug} is the general partial update. The model is not immutable — PATCH /v1/agents/{slug}/model is a dedicated endpoint for changing it (and the resolver settings that go with it). PATCH /v1/agents/{slug}/voice sets the agent's voice.
Delete an agent
DELETE /v1/agents/{slug}
Existing runs keep their archived agent reference.
Avatars
GET /v1/agents/{slug}/avatar
PUT /v1/agents/{slug}/avatar (multipart/form-data; file=…)
DELETE /v1/agents/{slug}/avatar
The PUT takes a multipart upload; the control plane forwards it to the runtime, which stores and serves the image back via the GET. GET returns 404 when no avatar has been uploaded.
Not available
POST /v1/agents/{slug}/invoke — routed, but broken
POST /v1/agents/{slug}/cancel/{run_id} — routed, but broken
GET /v1/agents/{slug} — does not exist
The first two are registered on the control plane but forward to runtime endpoints that don't exist — the runtime has no /v1/agents/{slug}/invoke and no /v1/agents/{slug}/cancel, so both calls fail upstream. The portal doesn't use them; it posts to /v1/runs and cancels with POST /v1/runs/{run_id}/cancel.
The SDKs expose agents.invoke() (which targets the broken route) but expose no method for POST /v1/runs. Until that's fixed, start runs by calling POST /v1/runs directly.
For the record, the control plane's invoke body is { prompt (required), work_mode: "single_session" | "taskrunner", conversation_context } — there is no message field on it. The CLI's agents invoke --message sends message, which wouldn't validate even if the route worked.
GET /v1/agents/{slug} is not a route; use /{slug}/detail.
Errors
| Status | Shape | When |
|---|---|---|
| 401 | {"detail": "auth_required"} |
No session presented. |
| 403 | {"detail": {"code": "plan_limit_exceeded", …}} |
Custom-agent tier cap hit on create. |
| 404 | {"detail": …} |
Slug doesn't exist — or the route forwards to a runtime endpoint that doesn't exist (invoke / cancel). |
| 422 | FastAPI validation body | Body failed schema validation. |
| 502 | {"detail": "runtime call failed: …"} |
Control plane couldn't reach the runtime. |
Events
Agent runs write run events (run_started, run_completed, run_failed, …) — see Runs → Event vocabulary. Agent CRUD emits no events at all.
Events are rows in an archive you poll. Catentio does not push them to a URL you register — there is no outbound webhook delivery.