Skip to content

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.

  1. GET /team-templates: pick a template; read GET /team-templates/{name} for its roles
  2. POST /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 without repo_url
  3. PUT /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 project
  4. GET /teams/{name}: read state, home (the room) and role_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.

  1. GET /agents/{name}: admin to write; note current_version and the ETag
  2. POST /agents/{name}/versions: admin, with If-Match of that ETag. {from_version: <current>, system_prompt} changes only what you send; the new version becomes current
  3. GET /teams: then GET /teams/{name} for each: role_settings[].agent names 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.

  1. GET /teams/{name}: note the ETag and the current budget_usd_per_day
  2. PATCH /teams/{name}: operator, with If-Match: {budget_usd_per_day: 5}
  3. GET /teams/{name}/budget: spent_today_usd, state (normal|warning|exceeded), series per UTC day
  4. GET /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.

  1. GET /teams/{name}/runs: newest first; state is running|waiting|succeeded|failed|cancelled
  2. GET /runs/{id}: waiting_for ({role, kind: question|confirmation, since}) for a waiting run, failure_reason for a failed one, chat_thread_url and jaeger_url
  3. GET /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.

  1. PUT /teams/{name}/documents/{doc}: operator. {media_type: "text/markdown", description, content}; 201 on create, 200 with If-Match on replace. Text only, at most 256 KiB each, 20 documents and 384 KiB per owner; it appears under /workspace/context
  2. PUT /agents/{name}/documents/{doc}: admin: the same for every team that runs the agent
  3. PUT /skills/{name}: admin. {body: <SKILL.md text>, resources: {path: text}}, text only; a platform skill any role can name
  4. GET /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.

  1. GET /destinations: the named places (host, methods, paths, inject_secret)
  2. POST /agents/{name}/versions: admin. {from_version, egress: {mode: "allowlist"|"allow_all", destinations: [names], upstream_deny_cidrs}} for every team on that agent
  3. PUT /teams/{name}/roles/{role}/egress: admin, with If-Match of its GET: the same body for one team's role; DELETE returns 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.

  1. PUT /secrets/{name}: admin. {value} once, never returned; never print it
  2. POST /destinations: admin. {name, host, methods, paths, header_allowlist, inject_secret: "secret:<name>"}; a loopback or private host needs dev_only: true
  3. POST /mcp-servers: operator. {team, name, url, destination, auth: "bearer", roles}; the answer's warnings lists roles whose egress lacks the destination
  4. PUT /teams/{name}/roles/{role}/egress: admin, if warnings named a role: add the destination to its egress
  5. POST /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.

Goal: Mattermost, Slack, GitHub, Linear or Jira working, and a team linked to it.

  1. PUT /secrets/{name}: admin. One call per secret of the kind, named integrations/<kind>/<setting>
  2. PUT /integrations/{kind}: admin, with If-Match: {enabled, settings}; enabling needs the required settings and secrets (the 422 names what is missing)
  3. POST /integrations/{kind}/test: operator. state ok|error|unknown with a one-line detail
  4. GET /integrations/{kind}: status.applied: pending means the gateway reads the change when it restarts
  5. PUT /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.

  1. POST /users: admin. {name, email, role}; the answer holds setup_token once. Give the person the link <console URL>/#setup?token=<setup_token>
  2. POST /users/{id}/setup-token: admin: a fresh link when the first expired
  3. PATCH /users/{id}: admin, with If-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.

  1. GET /teams/{name}/runs: nothing running or waiting, or ask the person first
  2. DELETE /teams/{name}: operator, with If-Match; answers when the archive is done
  3. GET /teams/{name}: state is 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.

  1. POST /teams/{name}/tasks: operator. {text}; answers 202 with {run_id, work_item_id, state} and a Location; 409 when the team is archived or lives on a chat surface, 429 when its budget is spent, 503 when no gateway answers
  2. GET /runs/{id}: poll with the run_id: state is running, waiting, succeeded or failed; the lead's task appears under tasks a moment after the 202
  3. GET /runs/{id}/posts: the conversation in order; pass the last seq seen as cursor to read only what is new. The answer and completed posts hold what the team produced; delivery on 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.

  1. GET /runs: state=waiting: each such run has open_question ({seq, role, text}); the same field is on GET /runs/{id}
  2. POST /runs/{id}/replies: operator. {text, in_reply_to: <open_question.seq>}; without in_reply_to the 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)
  3. GET /runs/{id}/posts: read on from the question's seq (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).