Playbooks¶
Twelve things a person asks an agent to do, as the routes to call in order (paths relative to /api/v1; every write of an existing resource needs If-Match with the ETag of a fresh GET). Generated from console/playbooks.py; the agents page carries the same text.
1. Create a team from a template with a repository and a chat room¶
Goal: A new team of agents that works on the person's repository and talks in a chat room.
GET /team-templates: pick a template; readGET /team-templates/{name}for its rolesPOST /teams: operator.{name, template: {name}, repo_url, home: {surface: "mattermost"|"slack"|"none"}, budget_usd_per_day}; answers when the team is ready (up to a minute); a template that needs a repository is a 422 withoutrepo_urlPUT /teams/{name}/links: operator.{surface: "github", external_id: "owner/repo"}links the repository (422 when the GitHub App is not installed on it); the same route links a Linear team or a Jira projectGET /teams/{name}: readstate,home(the room) androle_settings
Check: the team's links list the repository as verified and the answer's home names the room: report the team's name and the room to the person.
2. Change an agent's system prompt and see which teams pick it up¶
Goal: A new prompt for one agent; the old version stays and can be made current again.
GET /agents/{name}: admin to write; notecurrent_versionand the ETagPOST /agents/{name}/versions: admin, withIf-Matchof that ETag.{from_version: <current>, system_prompt}changes only what you send; the new version becomes currentGET /teams: thenGET /teams/{name}for each:role_settings[].agentnames the agent
Check: every team whose roles run that agent starts its next run on the new version (a run that is already going keeps the old one). To undo, post a version from_version the old one.
3. Set a daily budget and read today's spend¶
Goal: A team stops overspending and the person sees what it costs.
GET /teams/{name}: note the ETag and the currentbudget_usd_per_dayPATCH /teams/{name}: operator, withIf-Match:{budget_usd_per_day: 5}GET /teams/{name}/budget:spent_today_usd,state(normal|warning|exceeded),seriesper UTC dayGET /spend: all active teams per day, with each team's series
Check: state is normal and today's spend is below the limit; ask the person before raising a budget above what they named.
4. Read what a team is doing now and answer a waiting question¶
Goal: Find out whether a team is working, stuck or failed, and why.
GET /teams/{name}/runs: newest first;stateis running|waiting|succeeded|failed|cancelledGET /runs/{id}:waiting_for({role, kind: question|confirmation, since}) for a waiting run,failure_reasonfor a failed one,chat_thread_urlandjaeger_urlGET /runs/{id}/trace: the spans: which role did what, the cost of each
Check: no route answers a waiting run: tell the person who is waiting and for what, and give them chat_thread_url, where they answer in the room.
5. Give an agent a document or a skill¶
Goal: Context the agent reads at its next run start, or a skill it can use.
PUT /teams/{name}/documents/{doc}: operator.{media_type: "text/markdown", description, content}; 201 on create, 200 withIf-Matchon replace. Text only, at most 256 KiB each, 20 documents and 384 KiB per owner; it appears under /workspace/contextPUT /agents/{name}/documents/{doc}: admin: the same for every team that runs the agentPUT /skills/{name}: admin.{body: <SKILL.md text>, resources: {path: text}}, text only; a platform skill any role can nameGET /teams/{name}/skills: a team's own skills are proposed by its agents and curated in chat; read-only
Check: read the document or skill back with its GET route; an agent sees it from its next run.
6. Restrict or open an agent's egress¶
Goal: Choose which hosts an agent may reach through the credential gateway.
GET /destinations: the named places (host, methods, paths,inject_secret)POST /agents/{name}/versions: admin.{from_version, egress: {mode: "allowlist"|"allow_all", destinations: [names], upstream_deny_cidrs}}for every team on that agentPUT /teams/{name}/roles/{role}/egress: admin, withIf-Matchof its GET: the same body for one team's role;DELETEreturns to the agent's own
Check: tell the person what allow_all costs before you set it: any public host is reachable through the gateway; the deny CIDRs still apply; named destinations' credentials are injected only on their own hosts and no secret is ever in the guest; CONNECT to any port is allowed. llm, mcp and otel are always included.
7. Connect a destination that needs a secret and use it from an MCP server¶
Goal: An agent calls a tool of a server that needs a credential, without the guest ever holding it.
PUT /secrets/{name}: admin.{value}once, never returned; never print itPOST /destinations: admin.{name, host, methods, paths, header_allowlist, inject_secret: "secret:<name>"}; a loopback or private host needsdev_only: truePOST /mcp-servers: operator.{team, name, url, destination, auth: "bearer", roles}; the answer'swarningslists roles whose egress lacks the destinationPUT /teams/{name}/roles/{role}/egress: admin, ifwarningsnamed a role: add the destination to its egressPOST /agents/{name}/versions: admin. A PLATFORM server is attached with{from_version, mcp_servers: [names]}
Check: GET /mcp-servers/{id} shows secret.status set and no warnings; a team that needs its own credential gets its own destination.
8. Connect an integration and link a team to a repository or a tracker¶
Goal: Mattermost, Slack, GitHub, Linear or Jira working, and a team linked to it.
PUT /secrets/{name}: admin. One call per secret of the kind, namedintegrations/<kind>/<setting>PUT /integrations/{kind}: admin, withIf-Match:{enabled, settings}; enabling needs the required settings and secrets (the 422 names what is missing)POST /integrations/{kind}/test: operator.stateok|error|unknown with a one-linedetailGET /integrations/{kind}:status.applied:pendingmeans the gateway reads the change when it restartsPUT /teams/{name}/links: operator: link the team to the repository, Linear team or Jira project
Check: the test says ok and status.applied is current: an agent cannot restart the gateway, so when it is pending tell the person that it must be restarted.
9. Invite a colleague and change their role¶
Goal: A new person can log in to the Console with the role they need.
POST /users: admin.{name, email, role}; the answer holdssetup_tokenonce. Give the person the link<console URL>/#setup?token=<setup_token>POST /users/{id}/setup-token: admin: a fresh link when the first expiredPATCH /users/{id}: admin, withIf-Match:{role}; never leaves the Console without an admin
Check: GET /users/{id} shows the role; confirm with the person before disabling anyone.
10. Archive a team and clean up¶
Goal: A team that is no longer needed stops and releases what it holds.
GET /teams/{name}/runs: nothing running or waiting, or ask the person firstDELETE /teams/{name}: operator, withIf-Match; answers when the archive is doneGET /teams/{name}:stateis archived
Check: archiving cancels open work, destroys the team's sandboxes, revokes its LiteLLM keys and removes its roles, links and members, which frees the name; the runs, the spend history and the team's memory stay.
11. Give a team a task and read the answer¶
Goal: A task for a team from the Console, with no chat surface in between.
POST /teams/{name}/tasks: operator.{text}; answers 202 with{run_id, work_item_id, state}and aLocation; 409 when the team is archived or lives on a chat surface, 429 when its budget is spent, 503 when no gateway answersGET /runs/{id}: poll with therun_id:stateis running, waiting, succeeded or failed; the lead's task appears undertasksa moment after the 202GET /runs/{id}/posts: the conversation in order; pass the lastseqseen ascursorto read only what is new. Theanswerandcompletedposts hold what the team produced;deliveryon the run names a branch and pull request
Check: the run is succeeded and the last completed or answer post is what you report; on failed, report failure_reason. To stop early, POST /runs/{id}/cancel.
12. Answer a question a run is waiting on¶
Goal: A run that paused for a person goes on with the person's answer.
GET /runs:state=waiting: each such run hasopen_question({seq, role, text}); the same field is onGET /runs/{id}POST /runs/{id}/replies: operator.{text, in_reply_to: <open_question.seq>}; withoutin_reply_tothe text is a follow-up task in the same run. 202 on success; 409 for a run that lives on a chat surface (answer it there)GET /runs/{id}/posts: read on from the question'sseq(cursor) to see the run continue
Check: the run's open_question is null and its state is running again; ask the person before answering anything that commits them (a confirmation, a merge).