Agents
The library of named, reusable agents your workspace can dispatch, and the run-control verbs that drive a live run from the outside.
Write a prompt once, name it, and dispatch it anywhere. That is 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
Amdahl library agents
Ship in code - curated specializations like gtm-strategist. Locked: dispatch,
pin and schedule them, but you cannot edit or delete them.
Workspace agents
Yours. Create one with a prompt and an optional tool blocklist, get back a stable
kebab-case slug, and dispatch it wherever an agent parameter is accepted.
An agent is a named, reusable prompt — a specialization the platform's Master runtime (the general-purpose agent behind every Chat turn) executes with its own full toolkit. Two kinds live side by side in one roster:
- Amdahl library agents ship in code — curated specializations like
gtm-strategist. They arelocked: you can dispatch them, pin them to Chats, and schedule them, but not edit or delete them. When one is retired, its slug stays reserved: a call that names it returnsnot_foundwith a message that says what replaced it, and no workspace agent can take the slug. - 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.ai/api/platform/v1/agents" \
-H "X-API-Key: $AMDAHL_KEY"The listing is a lean projection — everything except the prompt body — with the Amdahl library merged ahead of your workspace's own rows:
{
"data": {
"agents": [
{
"id": "4705…",
"slug": "gtm-strategist",
"name": "GTM Strategist",
"description": "Turns this workspace's own customer conversations into a go-to-market recommendation: who to sell to, how to position, where pipeline is leaking and, when the run has external search, what the market says that your buyers do not. Every call names its evidence and what would disprove it.",
"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.ai/api/platform/v1/agents/pipeline-analyst" \
-H "X-API-Key: $AMDAHL_KEY"Create an agent
curl -X POST "https://app.amdahl.ai/api/platform/v1/agents" \
-H "X-API-Key: $AMDAHL_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. |
{
"data": {
"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.ai/api/platform/v1/chat" \
-H "X-API-Key: $AMDAHL_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 answer_chat_question and cancel_chat tools 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.ai/api/platform/v1/agents/$RUN_ID/resume" \
-H "X-API-Key: $AMDAHL_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:
{
"data": {
"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.ai/api/platform/v1/agents/$RUN_ID/cancel" \
-H "X-API-Key: $AMDAHL_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
Each operation is its own tool on the Amdahl MCP server. The read tools only read and change nothing. The write tools start runs and make changes, so a client may ask the user to approve those calls.
| Family | Read tools | Write tools |
|---|---|---|
| Library | list_agents, get_agent | create_agent, update_agent, delete_agent |
| Chat | get_chat_run | start_chat, answer_chat_question, cancel_chat |
| Routines | list_routines, get_routine | create_routine, update_routine, delete_routine, run_routine_now |
answer_chat_question 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).
The first example below is a create_agent call and the second a start_chat call:
{
"slug": "pipeline-analyst",
"name": "Pipeline Analyst",
"prompt": "You are our pipeline analyst. …"
}{
"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.