Agents
Consumer-facing guide to the agent surface: the library of named, reusable agents your workspace can dispatch, and the run-control verbs (resume / cancel) that drive a live run from the outside. This is one of the three doors of the async surface — the other two are Chat (start an investigation, poll for the answer) and Routines (fire a Chat on a cron), and both can run as an agent from the library documented here.
The model
An agent is a named, reusable prompt — a specialization the platform's Master runtime executes with its own full toolkit. Two kinds live side by side in one roster:
- Amdahl library agents ship in code — curated specializations like
researcher. They arelocked: you can dispatch them, pin them to Chats, and schedule them, but not edit or delete them. - Workspace agents are yours. Create one with a
prompt(and optionally atool_blocklist), get back a stable kebab-caseslug, and dispatch it by that slug anywhere anagentparameter is accepted.
An agent is not a separate execution engine. When a Chat or Routine runs "as" an agent, it is one Master turn whose specialization is the agent's prompt — same toolkit, same data access, same answer envelope. The agent is the reusable instruction set, not a sandbox.
Where an agent plugs in:
| Surface | How |
|---|---|
| A Chat turn | POST /chat with "agent": "<slug or id>" — the turn runs as that agent. |
| A Routine | config.agents on the routine — one ref pins the fire to that agent; several restrict delegation to that roster. |
| Delegation | A Master turn may delegate sub-work to agents from the roster mid-run. |
The library
| Verb | REST | Operation | Scope |
|---|---|---|---|
| List the roster | GET /agents | agents.list_agents | agents:read |
| Read one agent (full prompt) | GET /agents/:id | agents.get_agent | agents:read |
| Create | POST /agents | agents.create_agent | agents:write |
| Update | PATCH /agents/:id | agents.update_agent | agents:write |
| Delete | DELETE /agents/:id | agents.delete_agent | agents:write |
List the roster
curl "https://app.amdahl.co/api/platform/v1/agents" \
-H "X-API-Key: $API_KEY"The listing is a lean projection — everything except the prompt body — with the Amdahl library merged ahead of your workspace's own rows:
{
"agents": [
{
"id": "3c1e…",
"slug": "researcher",
"name": "Researcher",
"description": "Rigorous multi-step research with citations.",
"authored_by": "amdahl",
"locked": true,
"created_at": null
},
{
"id": "9f2a…",
"slug": "pipeline-analyst",
"name": "Pipeline Analyst",
"description": "Weekly pipeline health readout in our house style.",
"authored_by": "tenant",
"locked": false,
"created_at": "2026-07-02T18:11:40.000Z"
}
]
}authored_by is the provenance (amdahl = code-defined library, tenant = yours); locked tells you up front whether an edit will be refused. Read one agent by UUID or slug to get the full prompt:
curl "https://app.amdahl.co/api/platform/v1/agents/pipeline-analyst" \
-H "X-API-Key: $API_KEY"Create an agent
curl -X POST "https://app.amdahl.co/api/platform/v1/agents" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pipeline Analyst",
"description": "Weekly pipeline health readout in our house style.",
"prompt": "You are our pipeline analyst. Always segment by deal size band, lead with the risks, and end with the three deals most worth attention this week."
}'| Field | Required | Notes |
|---|---|---|
name | yes | Display name, ≤120 chars. |
prompt | yes | The instruction set the agent runs with. |
slug | no | Stable kebab-case dispatch key. Omitted: derived from the name and uniquified automatically (-2 / -3 on collision). Supplied: must be well-formed and free — a collision returns a typed slug_conflict instead of guessing. |
description | no | One-line blurb for the roster, ≤2000 chars. |
tool_blocklist | no | Operation ids this agent may NOT call. Each id is validated against the live registry — an unknown id is refused up front (invalid_argument) rather than persisting as a silent no-op. |
{
"success": true,
"agent": {
"id": "9f2a…",
"slug": "pipeline-analyst",
"name": "Pipeline Analyst",
"description": "Weekly pipeline health readout in our house style.",
"authored_by": "tenant",
"locked": false,
"created_at": "2026-07-02T18:11:40.000Z"
}
}A slug that would shadow an Amdahl library agent is rejected before any row is written — library agents ship in code and cannot be replaced.
Update and delete
PATCH /agents/:id revises name / description / prompt / tool_blocklist in place, keeping the slug stable — refine the prompt after seeing output, and every Chat pin and Routine that references the slug picks up the new behavior on its next run. DELETE /agents/:id removes a workspace agent; past runs are untouched. Both refuse a locked library agent with a typed error rather than silently ignoring the edit. A Routine whose roster references a deleted agent skips its next fire loudly instead of running with a partial roster (see Routines: reliability semantics).
Dispatch it
curl -X POST "https://app.amdahl.co/api/platform/v1/chat" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "How healthy is the pipeline this week?",
"agent": "pipeline-analyst"
}'The response is the standard Chat START envelope — handles, never a blocking answer. From here the lifecycle is entirely Chat's: poll read_url, stream stream_url, answer pauses on resume_url.
Run control: resume and cancel
Two verbs drive a live run from the outside. They are the primitives behind the Chat lifecycle — resume_url posts to resume; the MCP respond / cancel_chat actions dispatch the same operations — exposed directly so any client can drive the loop.
| Verb | REST | Operation | Scope |
|---|---|---|---|
| Answer a pause | POST /agents/:session_id/resume | agents.resume | workflows:write |
| Cancel a run | POST /agents/:session_id/cancel | agents.cancel | workflows:write |
:session_id is the run_id from the Chat START envelope.
Resume
A run paused in awaiting_input carries a pending_input object on its READ body: pending_input_type, pending_input_schema (the JSON schema your reply must match), and pending_input_context (the question / reason). POST the reply as input:
curl -X POST "https://app.amdahl.co/api/platform/v1/agents/$RUN_ID/resume" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": { "response": "Focus on enterprise; ignore self-serve." } }'The three pause shapes you will meet:
pending_input_type | The run is asking | Reply shape |
|---|---|---|
question | A free-form question (ask_a_human). | { "response": "…" } — or the pause's structured options when it offered multiple choice. |
approval | Sign-off on a draft or a destructive step. | { "approved": true } or { "approved": false, "feedback": "…" }. |
continue_or_finish | The turn budget ran out. | { "action": "continue", "additional_turns": 10 } or { "action": "finish" }. |
On success:
{
"session_id": "9a8b7c6d-…",
"conversation_id": "3f0e2a1b-…",
"status": "queued",
"message": "Resume job enqueued",
"job_id": "agent-session-9a8b7c6d-…-resume"
}Two failure shapes worth handling: a reply that does not match pending_input_schema returns status: "invalid_input" with a validation_error naming the mismatch — fix the payload and retry; a resume against a run that is not awaiting_input returns status: "invalid_state" with the current status — refetch the run instead of retrying.
Note that pauses are opt-in: a Chat started with the default on_question: "auto" never parks (the agent states its assumption and proceeds), and Routine fires are always headless. You will only ever resume runs you started with on_question: "pause" — see Chat: answering a human question.
Cancel
curl -X POST "https://app.amdahl.co/api/platform/v1/agents/$RUN_ID/cancel" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Scope changed; will re-ask with a new brief" }'Cancellation is safe at any non-terminal state: the run flips to canceled, the worker stops at its next safe checkpoint, and any sub-agents the run dispatched are canceled with it. An already-terminal run returns cannot_cancel with the current status echoed back. reason is optional and recorded in operator logs. A canceled turn stays in the Chat's history — a follow-up turn on the same Chat continues with full context rather than cold-starting.
Over MCP
The whole surface rides the Amdahl MCP server's single agents tool — the library CRUD, the Chat lifecycle, and Routines as actions on one tool:
| Family | Actions |
|---|---|
| Library | list_agents, get_agent, create_agent, update_agent, delete_agent |
| Chat | start_chat, chat_status, respond, cancel_chat |
| Routines | list_routines, get_routine, create_routine, update_routine, delete_routine, run_routine_now |
respond and cancel_chat dispatch the same agents.resume / agents.cancel operations as the REST verbs above. The library reads are also MCP resources: agent://list (lean roster) and agent://<id> (one agent with its full prompt).
{ "action": "create_agent", "name": "Pipeline Analyst", "prompt": "You are our pipeline analyst. …" }{ "action": "start_chat", "input": "How healthy is the pipeline this week?", "agent": "pipeline-analyst" }Scopes
| Scope | Grants | Default role |
|---|---|---|
agents:read | List the roster, read one agent. | viewer |
agents:write | Create / update / delete workspace agents. | editor |
workflows:write | agents.resume + agents.cancel (drive a live run). | editor |
conversations:write / conversations:read | Start / read the Chats agents run in. | editor / viewer |
All are on the customer-agent key bundle. A read-only key can browse the roster and read runs, but cannot author agents or drive a run.
Design notes for agent authors
- Write the prompt as a role, not a script. The agent already has the full toolkit and the platform's grounding rules; your prompt adds the specialization — voice, segmentation habits, what to lead with, what to never do. Short and opinionated beats long and procedural.
- Use
tool_blocklistto subtract, not to sandbox. Blocklisting keeps an agent focused (a pure-analysis agent that must never send email blocklists the action ops). It is not a security boundary — the caller's own scopes are; an agent can never do what the caller could not. - Iterate against real output.
PATCHthe prompt, re-dispatch the same ask, diff the answers. The slug stays stable, so nothing referencing the agent changes while you tune it. - Slugs are the API surface. Everything dispatches by slug; pick names you are happy to see in config files (
pipeline-analyst, nottest-agent-v2-final).
See also
- Chat — the lifecycle every agent run rides on: START, poll, stream, the answer envelope.
- Routines — schedule an agent to run on a cadence.
- Endpoints — the synchronous verbs an agent composes over.
- Tool catalog — the generated per-operation reference.