mirror of
https://github.com/anthropics/skills
synced 2026-09-18 16:28:28 +00:00
Update claude-api skill: Managed Agents August launch wave (#1532)
Mirror the latest bundled-skill content for the August 5 Managed Agents
launch wave:
- Session budgets: budget object at session create (minor-unit cents
string), budget_reached pause semantics, settle-event allowlist,
session.usage event, change/remove-to-resume, multiagent shared cap
- Deployment budgets: same budget object on deployments, copied onto
each fired session, clearable and re-addable
- Inference geo pinning: inference_geo on the agent model object,
per-session override, roster uniformity, never grandfathered
- Skills from a GitHub repository: root .claude/skills discovery at
session start (cloud sandboxes only), trust-boundary warning
- Advisor: {type: "advisor", model} multiagent roster entry, reserved
anthropic.advisor thread, plaintext vs redacted delivery; advisor
tool max_uses/max_tokens/caching options and error result variant
- Multiagent: new when-to-use guidance (start with self, add cheaper
workers, dedicated specialists), delegation tools, docs URL rename
- Corrections: agent versions are sequential integers, Files API
uploads take a purpose param, stop_details refusal categories are an
open set, Sonnet 5 added to the prefill-removed list, vault_ids is
create-only on sessions, deployments gain an update endpoint
No-Verification-Needed: markdown-only skill content sync
This commit is contained in:
@@ -306,7 +306,7 @@ with client.beta.messages.stream(
|
||||
response = stream.get_final_message()
|
||||
```
|
||||
|
||||
`task_budget` fields: `type` (always `"tokens"`), `total`, and optional `remaining` (defaults to `total`). The server injects a countdown marker Claude sees during generation; the budget counts what Claude generates and the tool results it reads this turn — **not** the full history you resend each request.
|
||||
`task_budget` fields: `type` (always `"tokens"`), `total`, and optional `remaining` (defaults to `total`). The server injects a countdown marker Claude sees during generation; the budget counts what Claude generates and the tool results it reads this turn — **not** the full history you resend each request. Not the same thing as **Managed Agents session budgets** — those are hard, dollar-denominated, platform-enforced caps on one CMA session (`shared/managed-agents-core.md` § Session budgets); a task budget is advisory and token-denominated.
|
||||
|
||||
**Observing spend:** accumulate `response.usage.output_tokens` (plus the token count of the tool-result blocks you append) across loop iterations if you want to display progress. Leave `remaining` unset in the normal loop — the server tracks the countdown itself, and passing a client-computed `remaining` while also resending full history under-reports the budget. **Only pass `remaining`** when you compact or rewrite history between requests and the server can no longer derive prior spend.
|
||||
|
||||
@@ -407,6 +407,8 @@ Availability: `shared/platform-availability.md`. For agents on Bedrock / Vertex
|
||||
|
||||
**When the user wants the agent to run on a schedule** (cron, "every night", "weekly report"): read `shared/managed-agents-scheduled-deployments.md` — deployments fire sessions autonomously on a cron cadence, with per-firing run records and lifecycle controls (pause/unpause/archive).
|
||||
|
||||
**When the agent's work fans out** (research across several sources, per-file or per-record work, "look into N things, then summarize") **or one loop would fill its context with reading:** read `shared/managed-agents-multiagent.md` and recommend a multiagent session — start with just `{"type": "self"}` in the roster so the agent can delegate to copies of itself, then move reading-heavy sub-tasks to a cheaper worker agent (e.g. Claude Haiku 4.5) referenced by ID.
|
||||
|
||||
---
|
||||
|
||||
## Server Tools (Quick Reference)
|
||||
@@ -447,7 +449,7 @@ Server-side tools run on Anthropic's infrastructure — no client-side execution
|
||||
|
||||
**Models API (no beta; availability: `shared/platform-availability.md`):** `client.models.list()` (auto-paginates) and `client.models.retrieve("claude-opus-5")`. Each model object has `id`, `display_name`, `created_at`, and — since Mar 2026 — `max_input_tokens` (the context window), `max_tokens` (the output cap), and `capabilities`. There is no `context_window` field.
|
||||
|
||||
**Stop details (GA, Opus 4.7+):** `response.stop_details` is populated **only when `stop_reason == "refusal"`** (fields: `type: "refusal"`, `category: "cyber"|"bio"|null`, `explanation`). It is `null` for every other `stop_reason` (`end_turn`, `max_tokens`, `tool_use`, `pause_turn`, …) — always guard before reading.
|
||||
**Stop details (GA, Opus 4.7+):** `response.stop_details` is populated **only when `stop_reason == "refusal"`** (fields: `type: "refusal"`, `category` — an open set, e.g. `"cyber"`, `"bio"`, `"reasoning_extraction"`, `"frontier_llm"`, or `null`; see the docs for the full list — and `explanation`). It is `null` for every other `stop_reason` (`end_turn`, `max_tokens`, `tool_use`, `pause_turn`, …) — always guard before reading.
|
||||
|
||||
**Client config (no beta):** `timeout` default 10 min; **units differ by SDK** — Python/Ruby: seconds; TypeScript: **milliseconds**; Go `option.WithRequestTimeout(time.Duration)`; Java `Duration`; C# `TimeSpan`. TS scales the default up to 60 min for large `max_tokens` on non-streaming requests; Java does so for streaming requests (Java non-streaming scales 30s–10 min). `max_retries`/`maxRetries` default 2 (retries 408/409/429/5xx + connection errors). `base_url` (or `ANTHROPIC_BASE_URL` env). Per-request override: Python `client.with_options(timeout=5.0).messages.create(...)`; TS `client.messages.create({...}, {timeout: 5_000})`; Ruby `request_options: {timeout: 5}`. Timeouts are retried — wall-clock can reach `timeout × (max_retries+1)`.
|
||||
|
||||
@@ -520,7 +522,7 @@ Live documentation URLs are in `shared/live-sources.md`.
|
||||
## Common Pitfalls
|
||||
|
||||
- Don't truncate inputs when passing files or content to the API. If the content is too long to fit in the context window, notify the user and discuss options (chunking, summarization, etc.) rather than silently truncating.
|
||||
- **Prefill removed (Fable 5, Opus 5, and the 4.6/4.7/4.8 family):** Assistant message prefills (last-assistant-turn prefills) return a 400 error on Fable 5, Opus 5, Opus 4.6, Opus 4.7, Opus 4.8, and Sonnet 4.6. Use structured outputs (`output_config.format`) or system prompt instructions to control response format instead. (One exception: the fallback-credit prefill claim — when redeeming a credit with `fallback_has_prefill_claim: true`, the server accepts the echoed assistant message; see the migration guide's refusal section.)
|
||||
- **Prefill removed (Fable 5, Opus 5, Sonnet 5, and the 4.6/4.7/4.8 family):** Assistant message prefills (last-assistant-turn prefills) return a 400 error on Fable 5, Opus 5, Sonnet 5, Opus 4.6, Opus 4.7, Opus 4.8, and Sonnet 4.6. Use structured outputs (`output_config.format`) or system prompt instructions to control response format instead. (One exception: the fallback-credit prefill claim — when redeeming a credit with `fallback_has_prefill_claim: true`, the server accepts the echoed assistant message; see the migration guide's refusal section.)
|
||||
- **Confirm migration scope before editing:** When a user asks to migrate code to a newer Claude model without naming a specific file, directory, or file list, **ask which scope to apply first** — the entire working directory, a specific subdirectory, or a specific set of files. Do not start editing until the user confirms. Imperative phrasings like "migrate my codebase", "move my project to X", "upgrade to Sonnet 4.6", or bare "migrate to Opus 4.8" are **still ambiguous** — they tell you what to do but not where, so ask. Proceed without asking only when the prompt names an exact file, a specific directory, or an explicit file list ("migrate `app.py`", "migrate everything under `services/`", "update `a.py` and `b.py`"). See `shared/model-migration.md` Step 0.
|
||||
- **`max_tokens` defaults:** Don't lowball `max_tokens` — hitting the cap truncates output mid-thought and requires a retry. For non-streaming requests, default to `~16000` (keeps responses under SDK HTTP timeouts). For streaming requests, default to `~64000` (timeouts aren't a concern, so give the model room). Only go lower when you have a hard reason: classification (`~256`), cost caps, deliberately short outputs, or **`max_tokens: 0`** for cache pre-warming (see `shared/prompt-caching.md` → Pre-warming).
|
||||
- **Disabling thinking on Claude Opus 5 has two failure modes — prefer low/medium effort instead.** Only affects code that explicitly opts out; thinking is on by default, so watch for a disabled-thinking setting carried forward from Opus 4.8. With `thinking: {type: "disabled"}`, the model occasionally writes a tool call into its **visible text** instead of a `tool_use` block: the turn succeeds, the call never runs, no error is raised, and in an agentic loop that text pollutes later turns. It can also leak `<thinking>` tags into the response. Turning thinking on and lowering `effort` fixes both and still cuts cost. If a route must stay thinking-off: **delete** any don't-think/don't-reason rule (it makes tag leakage worse), don't name thinking tags, and add the combined instruction *"When you use a tool, you may say a brief sentence first. If no tool can express what the user asked for, say so instead of guessing. Do not include internal or system XML tags in your response."* Details: `shared/model-migration.md` → Two failure modes when thinking is disabled.
|
||||
@@ -534,11 +536,11 @@ Live documentation URLs are in `shared/live-sources.md`.
|
||||
- **Advisor tool model pairing.** The advisor tool's `model` must be at least as capable as the request's top-level `model` — e.g. executor `claude-sonnet-5` → advisor `claude-opus-4-8` or `claude-opus-4-7`. An invalid pair returns 400. Pairing table in `shared/tool-use-concepts.md` § Advisor. Availability: `shared/platform-availability.md`.
|
||||
- **Agent Skills ≠ Managed Agents.** To have Claude generate a `.pptx`/`.xlsx`/etc. via Agent Skills, call `client.beta.messages.create` with `container={"skills": [...]}`, the `code_execution_20260521` tool, and both `code-execution-2025-08-25` + `skills-2025-10-02` betas. Do not use `client.beta.agents` / `sessions` / `environments` here — those are the Managed Agents surface, not Agent Skills.
|
||||
- **MCP connector needs both halves.** `mcp_servers=[{type:"url", url, name}]` alone is rejected as a validation error — also add `tools=[{type:"mcp_toolset", mcp_server_name:<same name>}]` with beta `mcp-client-2025-11-20`. Availability: `shared/platform-availability.md`.
|
||||
- **`inference_geo` is a direct top-level request parameter** — `client.messages.create(..., inference_geo="us")` / `.inferenceGeo("us")`. Do not put it in `extra_body` / `putAdditionalBodyProperty`. Supported on Opus 4.6 / Sonnet 4.6 and later; availability: `shared/platform-availability.md`. `response.usage.inference_geo` reports where inference ran.
|
||||
- **`inference_geo` is a direct top-level request parameter** — `client.messages.create(..., inference_geo="us")` / `.inferenceGeo("us")`. Do not put it in `extra_body` / `putAdditionalBodyProperty`. (Messages API only — on Managed Agents, `inference_geo` instead nests inside the agent's `model` object, never top-level; see `shared/managed-agents-core.md` § Pinning inference geography.) Supported on Opus 4.6 / Sonnet 4.6 and later; availability: `shared/platform-availability.md`. `response.usage.inference_geo` reports where inference ran.
|
||||
- **Fine-grained tool streaming is not a beta feature.** Set `eager_input_streaming: true` on the tool definition and call the regular `client.messages.stream(...)`. There is no beta header and no `client.beta.*` path.
|
||||
- **Cache diagnostics is beta.** Use `client.beta.messages.*` with beta `cache-diagnosis-2026-04-07`. Pass `diagnostics: {previous_message_id: null}` on the first turn and `diagnostics: {previous_message_id: <previous response id>}` on subsequent turns; the result is on `response.diagnostics`. Availability: `shared/platform-availability.md`.
|
||||
- **Memory tool type is `memory_20250818`.** Declare `{"type": "memory_20250818", "name": "memory"}`. Go uses the beta-namespace type `{OfMemoryTool20250818: &anthropic.BetaMemoryTool20250818Param{}}` on `client.Beta.Messages.New`; Python/TypeScript/Ruby/PHP/C# use the non-beta `client.messages.create`; Java has both a non-beta `MemoryTool20250818` and a beta tool-runner path. Python/TypeScript provide `BetaAbstractMemoryTool` / `betaMemoryTool` helpers for implementing the backend.
|
||||
- **Use a model the feature actually supports.** Some features are restricted to specific model tiers — fast mode is Claude Opus 5 / Opus 4.8 only (and Claude API only), task budgets are Claude Opus 5 / Fable 5 / Sonnet 5 / Opus 4.8 / 4.7 only, and the advisor tool requires a valid executor↔advisor pair. If the user's prompt names a model that the feature doesn't support, use a supported model instead and note the substitution in the output.
|
||||
- **Use a model the feature actually supports.** Some features are restricted to specific model tiers — fast mode is Claude Opus 5 / Opus 4.8 only (and Claude API only), task budgets (Messages API only — Managed Agents session budgets have no model-tier restriction) are Claude Opus 5 / Fable 5 / Sonnet 5 / Opus 4.8 / 4.7 only, and the advisor tool requires a valid executor↔advisor pair. If the user's prompt names a model that the feature doesn't support, use a supported model instead and note the substitution in the output.
|
||||
- **Don't define custom types for SDK data structures:** The SDK exports types for all API objects. Use `Anthropic.MessageParam` for messages, `Anthropic.Tool` for tool definitions, `Anthropic.ToolUseBlock` / `Anthropic.ToolResultBlockParam` for tool results, `Anthropic.Message` for responses. Defining your own `interface ChatMessage { role: string; content: unknown }` duplicates what the SDK already provides and loses type safety.
|
||||
- **Report and document output:** For tasks that produce reports, documents, or visualizations, the code execution sandbox has `python-docx`, `python-pptx`, `matplotlib`, `pillow`, and `pypdf` pre-installed. Claude can generate formatted files (DOCX, PDF, charts) and return them via the Files API — consider this for "report" or "document" type requests instead of plain stdout text.
|
||||
- **Server-tool errors don't raise.** Web search and web fetch errors return HTTP 200 with a `web_search_tool_result` / `web_fetch_tool_result` block whose `content` is a single error object (e.g. `{error_code: "max_uses_exceeded"}`) — not a raised exception. For web search, a success `content` is a *list*; an error `content` is an *object* — branch on that before indexing.
|
||||
|
||||
@@ -74,7 +74,7 @@ curl -X POST https://api.anthropic.com/v1/agents \
|
||||
curl -X POST https://api.anthropic.com/v1/sessions \
|
||||
"${HEADERS[@]}" \
|
||||
-d '{
|
||||
"agent": { "type": "agent", "id": "agent_abc123", "version": "1772585501101368014" },
|
||||
"agent": { "type": "agent", "id": "agent_abc123", "version": 1 },
|
||||
"environment_id": "env_abc123"
|
||||
}'
|
||||
# → { "id": "sesn_abc123", ... }
|
||||
@@ -112,7 +112,7 @@ curl -X POST https://api.anthropic.com/v1/agents \
|
||||
curl -X POST https://api.anthropic.com/v1/sessions \
|
||||
"${HEADERS[@]}" \
|
||||
-d '{
|
||||
"agent": { "type": "agent", "id": "agent_abc123", "version": "1772585501101368014" },
|
||||
"agent": { "type": "agent", "id": "agent_abc123", "version": 1 },
|
||||
"environment_id": "env_abc123",
|
||||
"title": "Code review session",
|
||||
"resources": [
|
||||
@@ -127,6 +127,36 @@ curl -X POST https://api.anthropic.com/v1/sessions \
|
||||
}'
|
||||
```
|
||||
|
||||
### With a session budget
|
||||
|
||||
```bash
|
||||
# Create a session with a hard $25.00 spend cap (list-priced; USD only; create-only).
|
||||
# amount is in minor units (cents) as an integer string: "2500" = $25.00
|
||||
curl -X POST https://api.anthropic.com/v1/sessions \
|
||||
"${HEADERS[@]}" \
|
||||
-d '{
|
||||
"agent": { "type": "agent", "id": "agent_abc123" },
|
||||
"environment_id": "env_abc123",
|
||||
"budget": {
|
||||
"type": "limit",
|
||||
"max_list_cost": { "amount": "2500", "currency": "USD" }
|
||||
}
|
||||
}'
|
||||
|
||||
# Change the cap — higher or lower, but it must exceed the consumed list cost.
|
||||
# An accepted update resumes work paused at budget_reached
|
||||
curl -X POST https://api.anthropic.com/v1/sessions/$SESSION_ID \
|
||||
"${HEADERS[@]}" \
|
||||
-d '{ "budget": { "type": "limit", "max_list_cost": { "amount": "4000", "currency": "USD" } } }'
|
||||
|
||||
# Remove the cap entirely — one-way; a removed budget can never be re-added
|
||||
curl -X POST https://api.anthropic.com/v1/sessions/$SESSION_ID \
|
||||
"${HEADERS[@]}" \
|
||||
-d '{ "budget": null }'
|
||||
```
|
||||
|
||||
See `shared/managed-agents-core.md` § Session budgets for list-cost composition, the settle-event allowlist at the cap, and multiagent semantics.
|
||||
|
||||
---
|
||||
|
||||
## Send a User Message
|
||||
@@ -252,7 +282,8 @@ curl -X POST https://api.anthropic.com/v1/files \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "anthropic-beta: files-api-2025-04-14" \
|
||||
-F "file=@path/to/file.txt"
|
||||
-F "file=@path/to/file.txt" \
|
||||
-F "purpose=agent"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -91,7 +91,7 @@ Both patterns keep the fixed context small and load detail on demand.
|
||||
| Constraint (from `prompt-caching.md`) | Agent-specific workaround |
|
||||
| --- | --- |
|
||||
| Editing the system prompt mid-session invalidates the cache. | Append a `{"role": "system", ...}` message to `messages[]` instead (no beta header; on supporting models — see `prompt-caching.md` § Mid-conversation system messages). The cached prefix stays intact, and the model treats it as an operator-authority instruction rather than user text. On models that don't support it, fall back to a `<system-reminder>` text block in the user turn. |
|
||||
| Switching models mid-session invalidates the cache. | Spawn a **subagent** with the cheaper model for the sub-task; keep the main loop on one model. |
|
||||
| Switching models mid-session invalidates the cache. | Spawn a **subagent** with the cheaper model for the sub-task; keep the main loop on one model. On Managed Agents that is a `multiagent` roster entry — see `managed-agents-multiagent.md`. |
|
||||
| Adding/removing tools mid-session invalidates the cache. | Use **tool search** for dynamic discovery — it appends tool schemas rather than swapping them, so the existing prefix is preserved. |
|
||||
|
||||
For multi-turn breakpoint placement, use top-level auto-caching — see `prompt-caching.md` §Placement patterns.
|
||||
|
||||
@@ -92,7 +92,7 @@ Use these when a managed-agents binding, behavior, or wire-level detail isn't co
|
||||
| Tools | `https://platform.claude.com/docs/en/managed-agents/tools.md` | "Extract built-in toolset, custom tool definitions, and tool result wire format" |
|
||||
| Files | `https://platform.claude.com/docs/en/managed-agents/files.md` | "Extract file upload, mount paths, session resources, and listing/downloading session outputs" |
|
||||
| Permission Policies | `https://platform.claude.com/docs/en/managed-agents/permission-policies.md` | "Extract permission policy types (allow/deny/confirm) and per-tool config" |
|
||||
| Multi-Agent | `https://platform.claude.com/docs/en/managed-agents/multi-agent.md` | "Extract multi-agent composition patterns, sub-agent invocation, and result handoff" |
|
||||
| Multi-Agent | `https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md` | "Extract multi-agent composition patterns, sub-agent invocation, and result handoff" |
|
||||
| Observability | `https://platform.claude.com/docs/en/managed-agents/observability.md` | "Extract logging, tracing, and usage telemetry exposed by managed agents" |
|
||||
| Webhooks | `https://platform.claude.com/docs/en/managed-agents/webhooks.md` | "Extract webhook endpoint registration, HMAC signature verification, supported event types, and delivery semantics" |
|
||||
| GitHub | `https://platform.claude.com/docs/en/managed-agents/github.md` | "Extract github_repository resource shape, multi-repo mounting, and token rotation" |
|
||||
|
||||
@@ -28,7 +28,7 @@ All resources are under the `beta` namespace. Python and TypeScript share identi
|
||||
| Session Events | `sessions.events.list` / `send` / `stream` | `Sessions.Events.List` / `Send` / `StreamEvents` |
|
||||
| Session Threads | `sessions.threads.list` / `retrieve` / `archive`; `sessions.threads.events.list` / `stream` | `Sessions.Threads.List` / `Get` / `Archive`; `Sessions.Threads.Events.List` / `StreamEvents` |
|
||||
| Session Resources | `sessions.resources.add` / `retrieve` / `update` / `list` / `delete` | `Sessions.Resources.Add` / `Get` / `Update` / `List` / `Delete` |
|
||||
| Deployments | `deployments.create` / `pause` / `unpause` / `archive` / `run` | Not yet documented — WebFetch the SDK repo (`shared/live-sources.md`) |
|
||||
| Deployments | `deployments.create` / `update` / `pause` / `unpause` / `archive` / `run` | Not yet documented — WebFetch the SDK repo (`shared/live-sources.md`) |
|
||||
| Deployment Runs | `deployment_runs.list` / `retrieve` (TS: `deploymentRuns.*`) | Not yet documented — WebFetch the SDK repo (`shared/live-sources.md`) |
|
||||
| Vaults | `vaults.create` / `retrieve` / `update` / `list` / `delete` / `archive` | `Vaults.New` / `Get` / `Update` / `List` / `Delete` / `Archive` |
|
||||
| Credentials | `vaults.credentials.create` / `retrieve` / `update` / `list` / `delete` / `archive` / `mcp_oauth_validate` | `Vaults.Credentials.New` / `Get` / `Update` / `List` / `Delete` / `Archive` / `McpOauthValidate` |
|
||||
@@ -44,7 +44,7 @@ All resources are under the `beta` namespace. Python and TypeScript share identi
|
||||
|
||||
**Agent shorthand:** `agent` on session create accepts three forms — a bare string (`agent="agent_abc123"`, latest version), a pinned reference `{type: "agent", id, version}`, or `{type: "agent_with_overrides", id, version?, model?, system?, tools?, mcp_servers?, skills?}` to override those fields for this session only (see `shared/managed-agents-core.md` → Override agent configuration for a session).
|
||||
|
||||
**Model shorthand:** `model` on agent create accepts either a bare string (`model="claude-opus-5"` — uses `standard` speed) or the full config object, which takes `speed` and `effort` alongside `id`: `{id: "claude-opus-5", speed: "fast"}`, `{id: "claude-opus-5", effort: "high"}`. `effort` accepts a level string (`low`/`medium`/`high`/`xhigh`/`max`) or `{type: "<level>"}`, and is **agent-configuration only** — an `effort` inside a per-session `model` override is ignored. See `shared/managed-agents-core.md` → Effort on the agent model. Note: `speed: "fast"` is supported on Claude Opus 5 and Opus 4.8 — on the Claude API only, which includes Managed Agents but not Amazon Bedrock, Google Cloud, or Microsoft Foundry. Opus 4.7 fast mode has been removed; `speed: "fast"` on Opus 4.7 returns an error.
|
||||
**Model shorthand:** `model` on agent create accepts either a bare string (`model="claude-opus-5"` — uses `standard` speed) or the full config object, which takes `speed`, `effort`, and `inference_geo` alongside `id`: `{id: "claude-opus-5", speed: "fast"}`, `{id: "claude-opus-5", effort: "high"}`, `{id: "claude-opus-5", inference_geo: "us"}`. `effort` accepts a level string (`low`/`medium`/`high`/`xhigh`/`max`) or `{type: "<level>"}`, and is **agent-configuration only** — an `effort` inside a per-session `model` override is ignored. `inference_geo` (`"us"` | `"global"`) pins the geography serving the agent's model requests, and unlike `effort` **is** applied in a per-session `model` override. See `shared/managed-agents-core.md` → Effort on the agent model / Pinning inference geography. Note: `speed: "fast"` is supported on Claude Opus 5 and Opus 4.8 — on the Claude API only, which includes Managed Agents but not Amazon Bedrock, Google Cloud, or Microsoft Foundry. Opus 4.7 fast mode has been removed; `speed: "fast"` on Opus 4.7 returns an error.
|
||||
|
||||
---
|
||||
|
||||
@@ -68,7 +68,7 @@ All resources are under the `beta` namespace. Python and TypeScript share identi
|
||||
| `GET` | `/v1/sessions` | ListSessions | List sessions (paginated) |
|
||||
| `POST` | `/v1/sessions` | CreateSession | Create a new session |
|
||||
| `GET` | `/v1/sessions/{session_id}` | GetSession | Get session details |
|
||||
| `POST` | `/v1/sessions/{session_id}` | UpdateSession | Update session `metadata`/`title`, or `agent.tools`/`agent.mcp_servers`/`vault_ids` (session-local override; session must be `idle`). See `shared/managed-agents-core.md` → Updating the agent configuration mid-session. |
|
||||
| `POST` | `/v1/sessions/{session_id}` | UpdateSession | Update session `metadata`/`title`, `agent.tools`/`agent.mcp_servers` (session-local override; session must be `idle`), or `budget` — change the cap (higher or lower; the new value must exceed the consumed list cost) or remove it with `null`; removal is one-way, and a budget can never be added post-create. `vault_ids` is create-only (rejected on update). See `shared/managed-agents-core.md` → Updating the agent configuration mid-session / Session budgets. |
|
||||
| `DELETE` | `/v1/sessions/{session_id}` | DeleteSession | Delete a session |
|
||||
| `POST` | `/v1/sessions/{session_id}/archive` | ArchiveSession | Archive a session |
|
||||
|
||||
@@ -124,6 +124,7 @@ Scheduled deployments (`depl_` IDs) run an agent on a recurring cron schedule
|
||||
| Method | Path | Operation | Description |
|
||||
| -------- | ------------------------------------------------ | ---------------- | ---------------------------------------- |
|
||||
| `POST` | `/v1/deployments` | CreateDeployment | Create a scheduled deployment |
|
||||
| `POST` | `/v1/deployments/{deployment_id}` | UpdateDeployment | Update deployment configuration (see `shared/managed-agents-scheduled-deployments.md`) |
|
||||
| `POST` | `/v1/deployments/{deployment_id}/pause` | PauseDeployment | Suppress scheduled triggers (reversible; manual runs still allowed) |
|
||||
| `POST` | `/v1/deployments/{deployment_id}/unpause` | UnpauseDeployment | Resume from the next occurrence (no backfill) |
|
||||
| `POST` | `/v1/deployments/{deployment_id}/archive` | ArchiveDeployment | **Terminal** — schedule stops, deployment becomes immutable |
|
||||
@@ -234,7 +235,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa
|
||||
```json
|
||||
{
|
||||
"name": "string (required, 1-256 chars)",
|
||||
"model": "claude-opus-5 (required — bare string, or {id, speed?, effort?} object)",
|
||||
"model": "claude-opus-5 (required — bare string, or {id, speed?, effort?, inference_geo?} object)",
|
||||
"description": "string (optional, up to 2048 chars)",
|
||||
"system": "string (optional, up to 100,000 chars)",
|
||||
"tools": [
|
||||
@@ -265,7 +266,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa
|
||||
}
|
||||
```
|
||||
|
||||
> Limits: `tools` max 128, `skills` max 20, `mcp_servers` max 20 (unique names). `multiagent.agents` 1–20 entries (string ID | `{type:"agent",id,version?}` | `{type:"self"}`) — see `shared/managed-agents-multiagent.md`.
|
||||
> Limits: `tools` max 128, `skills` max 20, `mcp_servers` max 20 (unique names). `multiagent.agents` 1–20 entries (string ID | `{type:"agent",id,version?}` | `{type:"self"}` | `{type:"advisor",model}`, at most one advisor) — see `shared/managed-agents-multiagent.md`.
|
||||
|
||||
### CreateSession Request Body
|
||||
|
||||
@@ -287,13 +288,19 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa
|
||||
{ "type": "user.message", "content": [{ "type": "text", "text": "Review the auth module." }] }
|
||||
],
|
||||
"vault_ids": ["vlt_abc123 (optional — vault credentials: MCP auth + environment variables)"],
|
||||
"budget": {
|
||||
"type": "limit",
|
||||
"max_list_cost": { "amount": "2500", "currency": "USD" }
|
||||
},
|
||||
"metadata": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> The `agent` field accepts a string ID, `{type: "agent", id, version}`, or `{type: "agent_with_overrides", id, version?, ...}` for session-local overrides of `model`/`system`/`tools`/`mcp_servers`/`skills`. Outside the overrides form, those fields live on the agent, not here. An `effort` inside a `model` override is ignored — set it on the agent.
|
||||
> The `agent` field accepts a string ID, `{type: "agent", id, version}`, or `{type: "agent_with_overrides", id, version?, ...}` for session-local overrides of `model`/`system`/`tools`/`mcp_servers`/`skills`. Outside the overrides form, those fields live on the agent, not here. An `effort` inside a `model` override is ignored — set it on the agent. An `inference_geo` inside a `model` override **is** applied (omitting it clears the agent's pin for this session).
|
||||
>
|
||||
> **`budget`** (optional, create-only) is a hard dollar cap on the session's list-priced spend; `amount` is an integer string in minor units (cents — `"2500"` = $25.00), `USD` only. It can be changed or removed later via session update, never added. See `shared/managed-agents-core.md` → Session budgets.
|
||||
>
|
||||
> **`initial_events`** (optional, max 50) sends events at creation and starts the agent loop in the same call. Only `user.message` and `user.define_outcome` are accepted — no `system.message`, and none of the tool-result kinds. Validation is all-or-nothing. See `shared/managed-agents-core.md` → Seeding a session with `initial_events`.
|
||||
>
|
||||
@@ -334,7 +341,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa
|
||||
}
|
||||
```
|
||||
|
||||
> Optional session config (`resources`, `vault_ids`, etc.) is supported the same way as on CreateSession. Response includes `status`, `paused_reason`, and `schedule.upcoming_runs_at` (next fire times). See `shared/managed-agents-scheduled-deployments.md`.
|
||||
> Optional session config (`resources`, `vault_ids`, etc.) is supported the same way as on CreateSession, including `budget` — copied onto each fired session; unlike a session's, it can be added where none exists and re-added after clearing (see `shared/managed-agents-scheduled-deployments.md` § Deployment budgets). Response includes `status`, `paused_reason`, and `schedule.upcoming_runs_at` (next fire times). See `shared/managed-agents-scheduled-deployments.md`.
|
||||
|
||||
### SendEvents Request Body
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ for await (const event of stream) {
|
||||
|
||||
## 2. `processed_at` — queued vs processed
|
||||
|
||||
Every event on the stream carries `processed_at` (ISO 8601), set when the event finishes processing. For client-sent events (`user.message`, `user.interrupt`, `user.tool_confirmation`) it's `null` while the event is queued behind earlier ones, and populated once the agent processes it — so the same event appears on the stream twice, once with `null` and once with a timestamp.
|
||||
Every event on the stream carries `processed_at` (ISO 8601), set when the event finishes processing. For client-sent events (`user.message`, `user.interrupt`, `user.tool_confirmation`) it's `null` while the event is queued behind earlier ones, and populated once the agent processes it — so the same event appears on the stream twice, once with `null` and once with a timestamp. (Exception: a `user.interrupt` sent while the session is paused at its budget is accepted and ignored — it never appears at all; see `shared/managed-agents-events.md` § Reaching a session budget.)
|
||||
|
||||
**Three event types skip the queued phase:** `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result` are processed on receipt and echoed back with `processed_at` already populated. A pending → acknowledged UI that assumes "first sighting is always `null`" will never clear for these — treat a populated `processed_at` on first sighting as immediately acknowledged.
|
||||
|
||||
@@ -109,7 +109,7 @@ Reference: `tool-permissions.ts`.
|
||||
|
||||
## 5. Correct idle-break gate
|
||||
|
||||
Do not break on `session.status_idle` alone. The session goes idle transiently — e.g. between parallel tool executions, while waiting for a `user.tool_confirmation`, or while awaiting a `user.custom_tool_result`. Break when idle with a terminal `stop_reason`, or on `session.status_terminated`.
|
||||
Do not break on `session.status_idle` alone. The session goes idle transiently — e.g. between parallel tool executions, while waiting for a `user.tool_confirmation`, or while awaiting a `user.custom_tool_result`. Break when idle with a non-`requires_action` `stop_reason` (terminal, or `budget_reached` — resumable only by a budget update, so break unless you intend to change or remove the budget), or on `session.status_terminated`.
|
||||
|
||||
```ts
|
||||
for await (const event of stream) {
|
||||
@@ -117,7 +117,7 @@ for await (const event of stream) {
|
||||
if (event.type === 'session.status_terminated') break
|
||||
if (event.type === 'session.status_idle') {
|
||||
if (event.stop_reason.type === 'requires_action') continue // waiting on you — handle it
|
||||
break // end_turn or retries_exhausted — both terminal
|
||||
break // end_turn, retries_exhausted, or budget_reached — see list below
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -126,6 +126,7 @@ for await (const event of stream) {
|
||||
- `requires_action` — agent is waiting on a client-side event (tool confirmation, custom tool result). Handle it, don't break.
|
||||
- `retries_exhausted` — terminal failure. Break, then check `sessions.retrieve()` for the error state.
|
||||
- `end_turn` — normal completion.
|
||||
- `budget_reached` — the session hit its spend cap and paused. Not terminal and not resumable by any event: change (typically raise) or remove the session's `budget` to resume, or treat it as done. A `session.usage` event with the final cost immediately precedes this idle. See `shared/managed-agents-core.md` § Session budgets.
|
||||
|
||||
---
|
||||
|
||||
@@ -170,7 +171,7 @@ The `Promise.all([stream, send])` shape works too, but stream-first is simpler a
|
||||
**The mounted resource has a different `file_id` than the file you uploaded.** Session creation makes a session-scoped copy.
|
||||
|
||||
```ts
|
||||
const uploaded = await client.beta.files.upload({ file })
|
||||
const uploaded = await client.beta.files.upload({ file, purpose: 'agent_resource' })
|
||||
// uploaded.id → the original file
|
||||
const session = await client.beta.sessions.create({
|
||||
/* ... */
|
||||
|
||||
@@ -38,12 +38,12 @@ rescheduling → running ↔ idle → terminated
|
||||
|
||||
| Status | Description |
|
||||
| -------------- | ------------------------------------------------------------------ |
|
||||
| `idle` | Agent has finished the current task, and is awaiting input. It's either waiting for input to continue working via a `user.message` or blocked awaiting a `user.custom_tool_result` or `user.tool_confirmation`. The `stop_reason` attached contains more information about why the Agent has stopped working. |
|
||||
| `idle` | Agent has finished the current task, and is awaiting input. It's either waiting for input to continue working via a `user.message`, blocked awaiting a `user.custom_tool_result` or `user.tool_confirmation`, or paused because the session budget cap was reached. The `stop_reason` attached contains more information about why the Agent has stopped working. |
|
||||
| `running` | Session has starting running, and the Agent is actively doing work. |
|
||||
| `rescheduling` | Session is (re)scheduling after a retryable error has occurred, ready to be picked up by the orchestration system. |
|
||||
| `terminated` | Session has ended and is in an irreversible, unusable state — **either on completion or because of an unrecoverable error**. Terminated does not by itself mean failure; fetch the session to tell the two apart. |
|
||||
|
||||
- Events can be sent when the session is `running` or `idle`. Messages are queued and processed in order.
|
||||
- Events can be sent when the session is `running` or `idle`. Messages are queued and processed in order. Exception: a session paused at its budget (`stop_reason: budget_reached`) accepts only **settle events** — events that resolve work already in progress (`user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.interrupt`) rather than starting new work — see § Session budgets.
|
||||
- The agent transitions `idle → running` when it receives a new event, then back to `idle` when done.
|
||||
- Errors surface as `session.error` events in the stream, not as a status value.
|
||||
|
||||
@@ -60,7 +60,7 @@ Every session has a live trace view in the Anthropic Console at `https://platfor
|
||||
| Operation | Notes |
|
||||
|---|---|
|
||||
| List / fetch | Paginated list or single resource by ID |
|
||||
| Update | Only `title` is updatable |
|
||||
| Update | `title`, `metadata`, and the session-local `agent.tools`/`agent.mcp_servers` can be overridden (see § Updating the agent configuration mid-session). `budget` can only be changed or removed (see § Session budgets). `vault_ids` is create-only — update requests setting it are rejected. |
|
||||
| Archive | Session becomes **read-only**. Not reversible. |
|
||||
| Delete | Permanently deletes session, event history, container, and checkpoints. |
|
||||
|
||||
@@ -97,7 +97,9 @@ Key fields returned by the API:
|
||||
| `agent` | object | Agent configuration |
|
||||
| `resources` | array | Attached files, repos, and memory stores |
|
||||
| `metadata` | object | User-provided key-value pairs (max 8 keys) |
|
||||
| `usage` | object | Token usage statistics |
|
||||
| `usage` | object | Cumulative usage: token counts, `server_tool_use` (web search/fetch request counts), `list_cost` (consumption priced at public list rates, as `{amount, currency}` with the amount an integer string in minor units — cents), and `active_seconds` (time with ≥1 thread running; concurrent-thread overlap counted once — unlike `stats.active_seconds`, which sums per-thread time) |
|
||||
| `budget` | object | The session's spend cap, when one was set at creation — see § Session budgets |
|
||||
| `stats` | object | Timing statistics — `stats.active_seconds` sums per-thread time, unlike `usage.active_seconds` |
|
||||
|
||||
### Creating a session
|
||||
|
||||
@@ -136,6 +138,7 @@ const session = await client.beta.sessions.create(
|
||||
| `resources` | array | No | Files, GitHub repos, or memory stores, attached to the container at startup. Memory stores are session-create-only (not addable via `resources.add()`). |
|
||||
| `initial_events`| array | No | Events to send at creation, processed in order — collapses create + first send into one call. See § Seeding a session with `initial_events` below. |
|
||||
| `vault_ids` | array | No | Vault IDs (`vlt_*`) — MCP credentials with auto-refresh + `environment_variable` secrets substituted at egress. See `shared/managed-agents-tools.md` → Vaults. |
|
||||
| `budget` | object | No | Hard dollar cap on the session's spend: `{type: "limit", max_list_cost: {amount, currency}}`. **Create-only** — can be changed or removed later, never added. See § Session budgets. |
|
||||
| `metadata` | object | No | User-provided key-value pairs |
|
||||
|
||||
#### Seeding a session with `initial_events`
|
||||
@@ -165,7 +168,7 @@ An outcome-driven session is therefore a single call — pass one `user.define_o
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | -------- | -------- | ---------------------------------------------- |
|
||||
| `name` | string | **Yes** | Human-readable name (1-256 chars) |
|
||||
| `model` | string or object | **Yes** | Claude model ID (bare string, or an object taking `id`, `speed`, and `effort`). All Claude 4.5+ models supported. See § Effort on the agent model below. |
|
||||
| `model` | string or object | **Yes** | Claude model ID (bare string, or an object taking `id`, `speed`, `effort`, and `inference_geo`). All Claude 4.5+ models supported. See § Effort on the agent model and § Pinning inference geography below. |
|
||||
| `system` | string | No | System prompt — defines the agent's behavior (up to 100K chars) |
|
||||
| `tools` | array | No | Encompasses three kinds: (1) pre-built Claude Agent tools (`agent_toolset_20260401`), (2) MCP tools (`mcp_toolset`), and (3) custom client-side tools. Max 128. |
|
||||
| `mcp_servers` | array | No | MCP server connections — standardized third-party capabilities (e.g. GitHub, Asana). Max 20, unique names. See `shared/managed-agents-tools.md` → MCP Servers. |
|
||||
@@ -174,6 +177,34 @@ An outcome-driven session is therefore a single call — pass one `user.define_o
|
||||
| `multiagent` | object | No | `{type: "coordinator", agents: [...]}` — roster this agent may delegate to. See `shared/managed-agents-multiagent.md`. |
|
||||
| `metadata` | object | No | Arbitrary key-value pairs (max 16, keys ≤64 chars, values ≤512 chars) |
|
||||
|
||||
### Session budgets
|
||||
|
||||
A **session budget** is an optional hard spend ceiling set at session creation. The platform continuously prices everything the session consumes at **public list rates** (the session's **list cost**) and stops issuing new model requests once that total reaches the cap. A session at its budget **pauses and goes `idle` with `stop_reason: budget_reached`** — it is not terminated; history and sandbox are preserved, and changing or removing the budget resumes the paused work automatically.
|
||||
|
||||
```python
|
||||
session = client.beta.sessions.create(
|
||||
agent=AGENT_ID,
|
||||
environment_id=ENVIRONMENT_ID,
|
||||
budget={
|
||||
"type": "limit",
|
||||
"max_list_cost": {"amount": "2500", "currency": "USD"}, # minor units: "2500" = $25.00
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
- `type` is always `"limit"`. `max_list_cost.amount` is the amount in **minor units of the currency (cents), as an integer string** with no leading zeros, > 0 — `"2500"` is $25.00, `"50"` is fifty cents. A string rather than a number so no float rounding is ever applied; decimal forms such as `"25.00"` are rejected. `max_list_cost.currency` is uppercase ISO-4217; **`USD` is the only supported currency.**
|
||||
- **What counts toward list cost:** model tokens at each served model's list price, web searches at $10 per 1,000, and session running time at $0.08/hour. List cost is *not* your contracted price — with negotiated discounts, the session hits the cap when the list-price total does, and billed spend may be lower.
|
||||
- **Enforcement is a pre-request gate:** before every model request the platform checks whether consumed list cost has reached the cap and pauses the thread if it has; the request that crosses the cap completes, so the final figure can exceed the cap by at most one model request per running thread. Treat the budget as a bound on new work, not an exact stop.
|
||||
- The reported `list_cost` is **rounded to the nearest cent** while enforcement compares exact amounts — rounding can move the reported figure up to half a cent in either direction from the exact amount, so a session whose reported `list_cost` equals its cap may not yet be paused. Treat `stop_reason: budget_reached` (or the 400 on `user.message`), not the reported figure, as the signal that the cap was reached.
|
||||
- **Create-only.** Adding a budget to a session created without one is a 400. Updates accept exactly two changes: **change the cap** (the new value can be higher or lower than the old cap, but must be strictly greater than the consumed list cost, else 400: `budget.max_list_cost must be greater than the session's consumed list cost`) or **remove** (`budget: null` — the `session.updated` event carries `budget: null` rather than a separate flag). Because the consumed cost usually sits a fraction past the old cap when the session pauses, base the new value on the session's reported `usage.list_cost`, not the old `max_list_cost`. **Removal is one-way**: a removed budget can never be re-added; to keep a cap, change it instead.
|
||||
- **At the cap, only settle events are accepted** — events that resolve work already in progress rather than starting new work: `user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.interrupt`. A `user.interrupt` sent while the session is paused at its budget (all threads paused at the cap) is accepted and ignored: it does not appear in the event list and changes nothing. Raise or remove the budget to continue. Anything that starts new work (e.g. `user.message`) is a 400 naming that list. No event resumes the session — only a budget change/removal does.
|
||||
- **Multiagent:** one budget shared across all threads, no per-thread caps. Threads pause independently; each thread's consumption is priced at its own served model. A pending tool ask outranks the cap: a session with one thread at `requires_action` and another at `budget_reached` reports `requires_action` at the session level — answer it as usual (settle events aren't blocked).
|
||||
- **Models without a list price can't be budgeted:** a budgeted create whose agent (or any roster agent, including the advisor's model) uses an unpriced model is a 400. If a running budgeted session's usage comes to include one, changing the budget is rejected — remove the budget to resume.
|
||||
- Stream behavior at the cap and the `session.usage` event: `shared/managed-agents-events.md` § Reaching a session budget.
|
||||
- Scheduled deployments can carry a budget too — copied onto each fired session, with different update semantics (clearable and re-addable): `shared/managed-agents-scheduled-deployments.md` § Deployment budgets.
|
||||
|
||||
> **Not the same thing as Messages-API task budgets.** Session budgets are hard, dollar-denominated, platform-enforced caps on one session. `task_budget` on the Messages API is an advisory, token-denominated budget the model uses to pace itself within one agentic loop.
|
||||
|
||||
---
|
||||
|
||||
## Agents
|
||||
@@ -187,7 +218,7 @@ The API is **flat** — `model`, `system`, `tools` etc. are top-level fields, no
|
||||
| Field | Type | Required | Description |
|
||||
| ------------------ | -------- | -------- | -------------------------------------------------- |
|
||||
| `name` | string | Yes | Human-readable name |
|
||||
| `model` | string or object | Yes | Claude model ID — bare string, or `{id, speed?, effort?}` |
|
||||
| `model` | string or object | Yes | Claude model ID — bare string, or `{id, speed?, effort?, inference_geo?}` |
|
||||
| `system` | string | No | System prompt |
|
||||
| `tools` | array | No | Agent toolset / MCP toolset / custom tools |
|
||||
| `mcp_servers` | array | No | MCP server connections |
|
||||
@@ -220,9 +251,19 @@ Pass `model` as an object to set the effort level: `{"id": "claude-opus-5", "eff
|
||||
|
||||
The same object form carries `speed` for fast mode: `{"id": "claude-opus-5", "speed": "fast"}`.
|
||||
|
||||
### Pinning inference geography (`inference_geo`)
|
||||
|
||||
The `model` object also takes `inference_geo` to pin the geography that serves the agent's model requests: `{"id": "claude-opus-5", "inference_geo": "us"}`. Accepts `"us"` or `"global"` — and unlike the Messages API, where `inference_geo` is a top-level request parameter, here it is always nested inside `model`, never top-level. When unset, each model request follows the workspace's default inference geo at the time it's served.
|
||||
|
||||
- **Validated at every stage:** the pin is checked against the workspace's `allowed_inference_geos` when the agent is saved, when a session is created from it, and on every turn the session serves. If the workspace allowlist later narrows so the pin is no longer allowed, new sessions can't be created from the agent and **running sessions refuse further turns** — pins are never grandfathered (workspaces rely on them for compliance).
|
||||
- Setting `inference_geo` on a model that doesn't support geographic inference pinning returns a 400.
|
||||
- **Fixed for a session's lifetime** — the pin can't change mid-session. Set it on the agent, or set/clear it for one session with a `model` override at session create (see § Override agent configuration for a session).
|
||||
- **Multiagent rosters must be geo-uniform:** the coordinator's pin and every roster member's must all be the same value or all be unset — see `shared/managed-agents-multiagent.md`.
|
||||
- Unlike `effort`, an `inference_geo` inside a per-session `model` override **is applied** — and because overrides replace the `model` object in full, an override that *omits* `inference_geo` clears the agent's pin for that session.
|
||||
|
||||
### Versioning
|
||||
|
||||
Each `POST /v1/agents/{id}` (update) creates a new immutable version (numeric timestamp, e.g. `1772585501101368014`). The agent's history is append-only — you can't edit a past version.
|
||||
Each `POST /v1/agents/{id}` (update) creates a new immutable version — a sequential integer, starting at 1 and incrementing on each update. The agent's history is append-only — you can't edit a past version.
|
||||
|
||||
**`version` on update is optional.** Supply it for optimistic concurrency, or omit it to apply the update unconditionally:
|
||||
|
||||
@@ -231,7 +272,7 @@ Each `POST /v1/agents/{id}` (update) creates a new immutable version (numeric ti
|
||||
| Supplied (must be ≥ 1) | 409 if it doesn't match the agent's current version — **even when the fields you send already equal the stored values**. Re-read and retry. | Interactive callers; the recommended default |
|
||||
| Omitted | Applies unconditionally. The most recent update silently replaces any concurrent one, with no error to either caller. | Declarative apply loops — e.g. a CI job syncing checked-in agent definitions, where the loop owns the agent |
|
||||
|
||||
**Update semantics.** Omitted fields are preserved. Scalar fields (`model`, `system`, `name`, `description`) are replaced; `system` and `description` can be cleared with `null`, while `model` and `name` cannot. Array fields (`tools`, `mcp_servers`, `skills`) are replaced wholesale — `null` or `[]` clears them. **`effort` is the exception inside a `model` object you supply:** if the model `id` is unchanged, omitting `effort` leaves the stored level alone; if you change the `id`, an omitted `effort` resets to the new model's default.
|
||||
**Update semantics.** Omitted fields are preserved. Scalar fields (`model`, `system`, `name`, `description`) are replaced; `system` and `description` can be cleared with `null`, while `model` and `name` cannot. Array fields (`tools`, `mcp_servers`, `skills`) are replaced wholesale — `null` or `[]` clears them. **`effort` is the sole exception inside a `model` object you supply:** if the model `id` is unchanged, omitting `effort` leaves the stored level alone; if you change the `id`, an omitted `effort` resets to the new model's default. Other `model` fields are replaced along with the object — **supplying `model` without `inference_geo` clears the agent's inference geo pin.**
|
||||
|
||||
**Why version:**
|
||||
- **Reproducibility** — pin a session to a known-good config: `{type: "agent", id, version: 3}`
|
||||
@@ -293,15 +334,15 @@ session = client.beta.sessions.create(
|
||||
Each overridable field follows tri-state rules:
|
||||
- **Omit** → the session inherits the value from the referenced agent version.
|
||||
- **`null` (or `[]` for list fields)** → the session runs with that field cleared. Applies in full to `system` and `skills`. Three exceptions: `model` is never clearable (`model: null` → 400 `agent_model_required`); clearing `tools` returns 400 when the session's effective `skills` is non-empty (skills require the `read` tool); and clearing `mcp_servers` returns 400 when the effective `tools` still contains an `mcp_toolset` referencing one of the agent's servers — override `tools` in the same request to drop those entries, then clear `mcp_servers`.
|
||||
- **A value** → replaces the agent's value **in full**. Overrides never merge — a `tools` override must list every tool the session should have. One exception: an `effort` level inside a `model` override is **not applied** (set it on the agent instead — see § Effort on the agent model).
|
||||
- **A value** → replaces the agent's value **in full**. Overrides never merge — a `tools` override must list every tool the session should have. One exception: an `effort` level inside a `model` override is **not applied** (set it on the agent instead — see § Effort on the agent model). An `inference_geo` inside a `model` override **is** applied — and because the object is replaced in full, an override that omits it clears the agent's pin, so the session follows the workspace's default inference geo. The overridden value is validated against the workspace's `allowed_inference_geos` at session create.
|
||||
|
||||
Overrides are session-local: they do **not** modify the agent resource or create a new agent version. The response's `agent` object reflects the post-override configuration, while its `id` and `version` still identify the base agent — so you can trace a session back to its base. In multiagent sessions, overrides apply to the coordinator and its `{type: "self"}` copies; roster agents referenced by ID always use their own as-created configuration (see `shared/managed-agents-multiagent.md`).
|
||||
|
||||
### Updating the agent configuration mid-session
|
||||
|
||||
`sessions.update()` can change `agent.tools`, `agent.mcp_servers` (including permission policies), and `vault_ids` on an **existing** session. This is a **session-local override** — it does not create a new agent version and does not propagate back to the agent object. The provided arrays are **full replacements**; to append one tool, `GET` the session, modify, and `POST` back. The session must be `idle` — interrupt first if running.
|
||||
`sessions.update()` can change `agent.tools` and `agent.mcp_servers` (including permission policies) on an **existing** session. This is a **session-local override** — it does not create a new agent version and does not propagate back to the agent object. The provided arrays are **full replacements**; to append one tool, `GET` the session, modify, and `POST` back. The session must be `idle` — interrupt first if running. `vault_ids` is **create-only**: the update param exists in the SDK but is rejected by the API ("Not yet supported") — attach vaults when you create the session.
|
||||
|
||||
Only `tools` and `mcp_servers` can change after a session is created — to run with a `model`, `system`, or `skills` other than the agent's values, use `agent_with_overrides` at create time (above). The agent's configured `system` field is fixed for the session's lifetime; you can still **append system-level context between turns** by sending a `system.message` event (see `shared/managed-agents-events.md` § Adding system context mid-session).
|
||||
Among the agent-configuration fields, only `tools` and `mcp_servers` can change after a session is created — to run with a `model`, `system`, or `skills` other than the agent's values, use `agent_with_overrides` at create time (above). (`title`, `metadata`, and `budget` have their own session-update paths — see § Session operations / § Session budgets.) The agent's model configuration — including its `inference_geo` pin — and its configured `system` field are fixed for the session's lifetime; you can still **append system-level context between turns** by sending a `system.message` event (see `shared/managed-agents-events.md` § Adding system context mid-session).
|
||||
|
||||
```python
|
||||
client.beta.sessions.update(
|
||||
@@ -313,7 +354,6 @@ client.beta.sessions.update(
|
||||
],
|
||||
"mcp_servers": [{"type": "url", "name": "linear", "url": "https://mcp.linear.app/sse"}],
|
||||
},
|
||||
vault_ids=["vlt_..."],
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ Upload a file first via the Files API, then reference by `file_id` + `mount_path
|
||||
// 1. Upload
|
||||
const file = await client.beta.files.upload({
|
||||
file: fs.createReadStream("data.csv"),
|
||||
purpose: "agent",
|
||||
});
|
||||
|
||||
// 2. Attach as a session resource
|
||||
@@ -116,6 +117,8 @@ This gives you a bidirectional file bridge: upload reference data in, download a
|
||||
|
||||
Clones a GitHub repository into the session container during initialization, before the agent begins execution. The agent can read, edit, commit, and push via `bash` (`git`). Multiple repositories per session are supported — add one `resources` entry per repo. Repositories are cached, so future sessions that use the same repository start faster.
|
||||
|
||||
Mounting a repository also loads any skills stored in its root `.claude/skills` directory — discovered once per session, from the repository state checked out at session start (cloud sandboxes only). See `shared/managed-agents-tools.md` → Skills from a GitHub repository.
|
||||
|
||||
Repositories are attached for the lifetime of the session — to change which repositories are mounted, create a new session. You **can** rotate a repository's `authorization_token` on a running session via `client.beta.sessions.resources.update(resource_id, {session_id, authorization_token})`; the resource `id` is returned at session creation and by `resources.list()`.
|
||||
|
||||
**Fields:**
|
||||
|
||||
@@ -67,19 +67,21 @@ Event types use dot notation, grouped by namespace:
|
||||
| `agent.mcp_tool_result` | Result from an MCP tool |
|
||||
| `agent.custom_tool_use` | Agent invoked a custom tool — session goes idle, you respond with `user.custom_tool_result` |
|
||||
| `agent.thread_context_compacted` | Conversation context was compacted |
|
||||
| `session.status_idle` | Agent has finished the current task, and is awaiting input. It's either waiting for input to continue working via a `user.message` or blocked awaiting a `user.custom_tool_result` or `user.tool_confirmation`. The `stop_reason` attached contains more information about why the Agent has stopped working. |
|
||||
| `session.status_idle` | Agent has finished the current task, and is awaiting input. It's either waiting for input to continue working via a `user.message`, blocked awaiting a `user.custom_tool_result` or `user.tool_confirmation`, or paused because the session budget cap was reached. The `stop_reason` attached contains more information about why the Agent has stopped working. |
|
||||
| `session.status_running` | Session has starting running, and the Agent is actively doing work. |
|
||||
| `session.status_rescheduled` | Session is (re)scheduling after a retryable error has occurred, ready to be picked up by the orchestration system. |
|
||||
| `session.status_terminated` | Session ended and is irreversibly unusable — **on completion or on error**, not error-only. |
|
||||
| `session.updated` | A session update changed at least one field — carries only the changed fields (a budget removal carries `budget: null`) |
|
||||
| `session.usage` | Snapshot of the session's cumulative usage and tracked list cost — see § Reaching a session budget below |
|
||||
| `session.error` | Error occurred during processing |
|
||||
| `span.model_request_start` | Model inference started |
|
||||
| `span.model_request_end` | Model inference completed |
|
||||
| `span.outcome_evaluation_start` / `_ongoing` / `_end` | Grader progress for outcome-oriented sessions — see `shared/managed-agents-outcomes.md` |
|
||||
| `session.thread_created` | Subagent thread spawned (multiagent) — see `shared/managed-agents-multiagent.md` |
|
||||
| `session.thread_status_running` / `_idle` / `_rescheduled` / `_terminated` | Subagent thread status transitions (multiagent). `_idle` carries `stop_reason`. |
|
||||
| `session.thread_created` | Subagent thread spawned (multiagent), or an advisor consultation started (thread name `anthropic.advisor`) — see `shared/managed-agents-multiagent.md` |
|
||||
| `session.thread_status_running` / `_idle` / `_rescheduled` / `_terminated` | Thread status transitions — mostly seen in multiagent sessions, but a single-agent session's primary thread also emits `_idle` when pausing at a session budget (§ Reaching a session budget). `_idle` carries `stop_reason`. |
|
||||
| `agent.thread_message_sent` / `_received` | Cross-thread message, carries `to_session_thread_id` / `from_session_thread_id` (multiagent) |
|
||||
|
||||
The stream also echoes back user-sent events (`user.message`, `user.interrupt`, `user.tool_confirmation`, `user.custom_tool_result`, `user.define_outcome`).
|
||||
The stream also echoes back user-sent events (`user.message`, `user.interrupt`, `user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.define_outcome`) — except a `user.interrupt` sent while the session is paused at its budget, which is accepted and ignored and never appears (§ Reaching a session budget).
|
||||
|
||||
Stream-only delta preview events (`event_start`, `event_delta`) are the one exception to the `{domain}.{action}` naming convention — see § Live previews below; they never appear in `GET /v1/sessions/{id}/events`.
|
||||
|
||||
@@ -190,11 +192,11 @@ await sendMessage(sessionId, "And compare the two");
|
||||
// Stream once — agent responds to all three as a coherent turn
|
||||
```
|
||||
|
||||
Events can be sent up to the Session at any time. There is no need to wait on a specific session status to enqueue new events via `client.beta.sessions.events.send()`
|
||||
Events can be sent up to the Session at any time. There is no need to wait on a specific session status to enqueue new events via `client.beta.sessions.events.send()`. One exception: a session paused at its budget (`stop_reason: budget_reached`) accepts only settle events — a `user.message` there is a 400. See § Reaching a session budget.
|
||||
|
||||
### Interrupt
|
||||
|
||||
A `user.interrupt` event **jumps the queue** (ahead of any pending user messages) and forces the session into `idle`. Use this for "stop" / "nevermind" / "cancel" commands:
|
||||
A `user.interrupt` event **jumps the queue** (ahead of any pending user messages) and forces the session into `idle`. Exception: while the session is paused at its budget, an interrupt is accepted and ignored — it is never persisted and changes nothing (§ Reaching a session budget). Use this for "stop" / "nevermind" / "cancel" commands:
|
||||
|
||||
```ts
|
||||
await client.beta.sessions.events.send(sessionId, {
|
||||
@@ -202,13 +204,27 @@ await client.beta.sessions.events.send(sessionId, {
|
||||
});
|
||||
```
|
||||
|
||||
The agent stops mid-task. It does not see the interrupt as a message — it just halts. Send a follow-up `user` event to explain what to do instead. If an outcome is active, the interrupt also marks `span.outcome_evaluation_end.result: "interrupted"` (see `shared/managed-agents-outcomes.md`).
|
||||
The agent stops mid-task. It does not see the interrupt as a message — it just halts. Send a follow-up `user` event to explain what to do instead. If an outcome is active, the interrupt also marks `span.outcome_evaluation_end.result: "interrupted"` (see `shared/managed-agents-outcomes.md`) — though not at a budget pause, where the interrupt is accepted and ignored (see § Reaching a session budget).
|
||||
|
||||
**The interrupted turn ends with `stop_reason: end_turn`** — the same value a turn that finishes on its own carries. There is no interruption-specific stop reason, so a drain loop can't distinguish the two from `stop_reason` alone; track that you sent the interrupt.
|
||||
|
||||
**In a multiagent session, omitting `session_thread_id` interrupts every non-archived thread, including the primary** — it is not primary-only. Pass `session_thread_id` to stop one thread. See `shared/managed-agents-multiagent.md`.
|
||||
|
||||
> **Note**: Interrupt events may have empty IDs in the current implementation. When troubleshooting, use the `processed_at` timestamp along with surrounding event IDs.
|
||||
> **Note**: Interrupt events may have empty IDs in the current implementation. When troubleshooting, use the `processed_at` timestamp along with surrounding event IDs. (Not applicable to an interrupt sent at the budget cap — that event is never persisted, so there is nothing to locate.)
|
||||
|
||||
### Reaching a session budget
|
||||
|
||||
A session created with a budget (see `shared/managed-agents-core.md` § Session budgets) pauses instead of overspending. Before every model request the platform checks whether consumed list cost has reached the cap and pauses the thread if it has, and the session goes idle with `stop_reason: budget_reached` rather than terminating. On the stream, the pause arrives as three events, in order:
|
||||
|
||||
1. `session.thread_status_idle` with `stop_reason: budget_reached`, for each thread as it pauses. When a thread's final request both crosses the cap and finishes its turn, that thread reports `stop_reason: end_turn` while the session still reports `budget_reached` — key on the **session-level** `stop_reason`, not thread-level ones, to detect the pause.
|
||||
2. `session.usage` — a snapshot of the session's cumulative usage and tracked list cost.
|
||||
3. `session.status_idle` with `stop_reason: budget_reached`. The `session.usage` event always immediately precedes this idle.
|
||||
|
||||
While at the cap the session accepts **only settle events** (`user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.interrupt`); anything that starts new work, including `user.message`, is a 400 naming that list. A `user.interrupt` sent while the session is paused at its budget (all threads paused at the cap) is accepted and ignored: it does not appear in the event list and changes nothing. Raise or remove the budget to continue. When one thread waits on a tool ask and another is paused at the cap, the session-level `stop_reason` is `requires_action`, not `budget_reached` — settling the ask doesn't trigger a model request, so respond as usual.
|
||||
|
||||
**No event resumes a session paused at its cap.** Update the session's budget instead: change it to a value above the consumed list cost (higher or lower than the old cap), or remove it with `"budget": null`. An accepted update resumes the paused work automatically.
|
||||
|
||||
**`session.usage`** carries the session's cumulative token totals, `list_cost` (`{amount, currency}`, rounded to the nearest cent), `active_seconds` (concurrent-thread overlap counted once — the figure runtime cost is priced on), `server_tool_use` counts (`web_search_requests`, and `web_fetch_requests` — informational, currently always 0 since web fetch is not metered), and an echo of the session's `budget` when one is set. It appears in the events list and the session stream — a stream reader sees the final cost of the work that hit the cap without an extra fetch; child threads' own streams do not carry it. The same totals live on the session object's `usage` field, and each thread's own `usage` carries per-thread `list_cost` and `active_seconds` — but per-thread costs do **not** sum to the session total: the session figure additionally includes session running time and each figure is rounded independently, so the session figure is the authoritative one. To enforce a spend limit, set a budget rather than polling usage and interrupting the session yourself — the platform's gate runs before each model request.
|
||||
|
||||
### Event payloads
|
||||
|
||||
|
||||
@@ -6,13 +6,97 @@ The SDK sets the `managed-agents-2026-04-01` beta header automatically on all `c
|
||||
|
||||
---
|
||||
|
||||
## When to use it — start with `self`, then add cheaper workers
|
||||
|
||||
**If the agent's work splits into independent pieces** — several sources to research, many files or records to process, anything shaped like "look into N things, then summarize" — or one piece would fill its context with reading, **use a multiagent session instead of one long single-threaded loop.** Each delegated piece runs in its own thread with a fresh context window, threads run in parallel in the same container, and only each subagent's report comes back, so the coordinator's context stays small. There is no orchestration code to write: the coordinator is given delegation tools automatically and decides when to use them, and your client still creates one session and reads one stream.
|
||||
|
||||
**Step 1 — the smallest useful roster is the agent itself.** Add a `multiagent` block whose only entry is `{"type": "self"}`. The coordinator can then hand self-contained sub-tasks to copies of itself — same model, system prompt, and tools, minus the ability to delegate further — and combine what they report. Nothing else changes.
|
||||
|
||||
```python
|
||||
agent = client.beta.agents.create(
|
||||
name="Research assistant",
|
||||
description="Researches a question end to end. A copy can be spawned to own one well-scoped sub-question.",
|
||||
model="claude-opus-5",
|
||||
system="You are a research assistant. When a request splits into independent sub-questions, delegate each to a copy of yourself, one self-contained task per copy, then verify and combine their reports.",
|
||||
tools=[{"type": "agent_toolset_20260401"}],
|
||||
multiagent={"type": "coordinator", "agents": [{"type": "self"}]}, # the only change vs. a single agent
|
||||
)
|
||||
|
||||
session = client.beta.sessions.create(agent=agent.id, environment_id=env.id) # unchanged
|
||||
```
|
||||
|
||||
**Step 2 — move the reading-heavy work to a cheaper model.** Delegated research work is mostly searching, reading, and extracting: many input tokens, little hard reasoning. Create a second agent on a smaller model with a narrow `system` prompt and only the tools it needs, and list it next to `self`. A roster entry is only a reference: the worker runs on its own `model`, `system`, and `tools`, and its tokens are billed at its own model's rates. The large model spends its tokens on planning, checking, and synthesis; the small model does the bulk reading.
|
||||
|
||||
```python
|
||||
worker = client.beta.agents.create(
|
||||
name="Web researcher",
|
||||
description="Fast, low-cost, read-only researcher. Give it one well-scoped question; it searches, reads, and reports findings with sources.",
|
||||
model="claude-haiku-4-5",
|
||||
system="Answer exactly the question you are given. Search and read as much as you need, then report concise findings with a source URL or file path for every claim.",
|
||||
tools=[{
|
||||
"type": "agent_toolset_20260401",
|
||||
"default_config": {"enabled": False},
|
||||
"configs": [{"name": n, "enabled": True} for n in ("read", "glob", "grep", "web_fetch", "web_search")],
|
||||
}],
|
||||
)
|
||||
|
||||
lead = client.beta.agents.create(
|
||||
name="Research lead",
|
||||
description="Plans and synthesizes research. A copy can be spawned to own one large sub-analysis.",
|
||||
model="claude-opus-5",
|
||||
system="Plan the work. Delegate each independent, reading-heavy question to Web researcher, one self-contained task per spawn, several in parallel. Keep verification and the final synthesis for yourself; spawn a copy of yourself only for a sub-analysis that needs your full capability.",
|
||||
tools=[{"type": "agent_toolset_20260401"}],
|
||||
multiagent={"type": "coordinator", "agents": [worker.id, {"type": "self"}]},
|
||||
)
|
||||
```
|
||||
|
||||
**Step 3 — add dedicated specialists.** When the sub-tasks call for different skills, give each its own agent — its own model, a narrow `system` prompt, and only the tools it needs — and roster them by ID next to `self`. Here the lead makes a change itself, sends the same review brief to several read-only reviewer threads for independent passes (one rostered agent can be spawned many times), and hands a test writer a self-contained brief; it then de-duplicates the findings, checks each against the code, and keeps the fix and the summary for itself.
|
||||
|
||||
```python
|
||||
reviewer = client.beta.agents.create(
|
||||
name="Concurrency reviewer",
|
||||
description="Read-only reviewer for race conditions, deadlocks, lost updates, and retry/idempotency bugs. Give it the changed file paths and the invariants that must hold; it reports findings with file:line evidence. Spawn several on the same change for independent reviews.",
|
||||
model="claude-sonnet-5",
|
||||
system="Review only the files you are pointed at. Look for concurrency bugs: unsynchronized shared state, lock ordering, non-atomic read-modify-write, retries without idempotency. Report each finding as file:line, the interleaving that triggers it, and a suggested fix; say plainly if you found none.",
|
||||
tools=[{"type": "agent_toolset_20260401", "default_config": {"enabled": False},
|
||||
"configs": [{"name": n, "enabled": True} for n in ("read", "glob", "grep")]}],
|
||||
)
|
||||
test_writer = client.beta.agents.create(
|
||||
name="Test writer",
|
||||
description="Writes and runs tests. Give it the module path, the behavior to pin down, and the test command; it adds test files, runs them, and reports results with output.",
|
||||
model="claude-sonnet-5",
|
||||
system="Write focused tests for the behavior you are given, run them with the command you are given, and report pass/fail, the relevant output, and the paths of files you added. Do not edit non-test code; if the code under test looks wrong, report that instead.",
|
||||
tools=[{"type": "agent_toolset_20260401", "default_config": {"enabled": True},
|
||||
"configs": [{"name": n, "enabled": False} for n in ("web_fetch", "web_search")]}],
|
||||
)
|
||||
lead = client.beta.agents.create(
|
||||
name="Engineering lead",
|
||||
description="Plans and makes code changes and integrates specialist reports. A copy can be spawned to own one independent change.",
|
||||
model="claude-opus-5",
|
||||
system="Make the change yourself. Then, in parallel, send the changed paths and invariants to three Concurrency reviewers and the module path and test command to Test writer. Merge and de-duplicate the reviewers' findings, check each against the code before acting on it, fix, and have Test writer re-run. Keep design decisions and the final summary for yourself.",
|
||||
tools=[{"type": "agent_toolset_20260401"}],
|
||||
multiagent={"type": "coordinator", "agents": [reviewer.id, test_writer.id, {"type": "self"}]},
|
||||
)
|
||||
```
|
||||
|
||||
The same shape fits a pipeline of different specialists: a fast document extractor (for example on Claude Haiku 4.5) that writes one JSON file per input document, a verifier that checks each file against its source, and a lead that applies the corrections and writes the final table to `/mnt/session/outputs/`. Put the input and output paths in every task: threads share the container's filesystem, not each other's conversation.
|
||||
|
||||
- **Good fits:** parallel research across sources; reading large amounts of material without filling the coordinator's context; specialists with narrow prompts and tool sets rather than one agent carrying every tool. **Poor fit:** a small single-step task — every delegation costs a round-trip and a re-briefing.
|
||||
- **Write `name` and `description` for the coordinator to read.** The coordinator chooses whom to spawn from each roster entry's name and description (the `self` entry is listed under the coordinator's own name), so say what each agent is good at and what to hand it. Names must be unique across the roster; don't name an agent `self`.
|
||||
- **Say how to delegate in the coordinator's `system` prompt** — what to hand off and to whom, how many at once, what to keep for itself, and what is too small to be worth delegating (the *Delegating to subagents* sample prompt in `shared/model-migration.md` is a starting point). Subagents see none of the coordinator's conversation, so each task must carry the paths, constraints, and report format it needs. Spawning returns immediately; the subagent's report arrives in a later coordinator turn.
|
||||
- **Limits:** 1–20 roster entries (at most one `self`; each rostered agent can be spawned many times), one level of delegation (a roster member must not have its own `multiagent`), and at most 25 concurrent threads per session — archive finished threads if a long session needs more (see *Interrupting and archiving threads* below).
|
||||
|
||||
The sections below are the reference for rosters, threads, events, and client-side handling; the platform guide is `https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md`.
|
||||
|
||||
---
|
||||
|
||||
## Declare the roster on the coordinator
|
||||
|
||||
`multiagent` is a **top-level field** on `agents.create()` / `agents.update()` — **not** a `tools[]` entry. `agents` lists 1–20 roster entries. Nothing changes on `sessions.create()` — the roster is resolved from the coordinator's config.
|
||||
|
||||
```python
|
||||
orchestrator = client.beta.agents.create(
|
||||
name="Engineering Lead",
|
||||
name="Engineering lead",
|
||||
model="claude-opus-5",
|
||||
system="You coordinate engineering work. Delegate code review to the reviewer and test writing to the test agent.",
|
||||
tools=[{"type": "agent_toolset_20260401"}],
|
||||
@@ -34,10 +118,13 @@ session = client.beta.sessions.create(agent=orchestrator.id, environment_id=env.
|
||||
| String shorthand | `"agent_abc123"` | References the latest version of a stored agent. |
|
||||
| Agent reference | `{type: "agent", id, version?}` | Omit `version` to pin the latest at coordinator save time. |
|
||||
| Self | `{type: "self"}` | The coordinator can spawn copies of itself. |
|
||||
| Advisor | `{type: "advisor", model}` | A model the session's primary thread can consult mid-turn. At most one per roster. See § Advisor below. |
|
||||
|
||||
If the session was created with `agent_with_overrides` (see `shared/managed-agents-core.md` → Override agent configuration for a session), those overrides apply to the **coordinator and its `self` copies**. Roster agents referenced by ID always use their own as-created configuration — overrides do not propagate to them.
|
||||
|
||||
Up to **20 unique agents** in the roster; the coordinator may spawn **multiple copies** of each. **One level of delegation only** — and it is enforced rather than silently flattened: rostering an agent that itself carries a `multiagent.agents` roster fails the create or update with a validation error.
|
||||
The coordinator's thread receives delegation tools for working the roster: `list_agents` (see the roster) and `send_to_agent` (task or message a member). Up to **20 unique agents** in the roster; the coordinator may spawn **multiple copies** of each. **One level of delegation only** — and it is enforced rather than silently flattened: rostering an agent that itself carries a `multiagent.agents` roster fails the create or update with a validation error.
|
||||
|
||||
**Inference geo pins must be roster-uniform.** When agents pin an inference geography (`model.inference_geo` — see `shared/managed-agents-core.md` § Pinning inference geography), the coordinator's pin and every roster member's must all be the same value or all be unset. A mismatched roster is a 400 validation error, both when the agent is saved and when a session-create `model` override changes any of the pins.
|
||||
|
||||
---
|
||||
|
||||
@@ -53,7 +140,9 @@ The session-level event stream is the **primary thread** — it shows the coordi
|
||||
| List thread events | `GET /v1/sessions/{sid}/threads/{tid}/events` | `.events.list(thread_id, session_id=...)` |
|
||||
| Stream thread events | `GET /v1/sessions/{sid}/threads/{tid}/stream` | `.events.stream(thread_id, session_id=...)` |
|
||||
|
||||
Each `SessionThread` carries `id`, `status` (`running` | `idle` | `rescheduling` | `terminated`), `agent` (a resolved snapshot of the agent config — `id`, `name`, `model`, `system`, `tools`, `skills`, `mcp_servers`, `version`), `parent_thread_id` (null for the primary thread, which is included in the list), `archived_at`, and optional `stats`/`usage`. **Session status aggregates thread statuses** — if any thread is `running`, `session.status` is `running`. Max **25 concurrent threads**. When draining a per-thread stream, break on `session.thread_status_idle` (and check its `stop_reason` as you would for the session-level idle).
|
||||
Each `SessionThread` carries `id`, `status` (`running` | `idle` | `rescheduling` | `terminated`), `agent` (a resolved snapshot of the agent config — `id`, `name`, `model`, `system`, `tools`, `skills`, `mcp_servers`, `version` — except advisor threads, whose `agent` is the two-field advisor form `{"type": "advisor", "model": ...}` — see § Advisor), `parent_thread_id` (null for the primary thread, which is included in the list), `archived_at`, and optional `stats`/`usage`. Per-thread `usage.list_cost` figures do **not** sum to the session total — the session figure additionally includes session running time and each figure is rounded independently; the session-level `usage.list_cost` is authoritative. **Session status aggregates thread statuses** — if any thread is `running`, `session.status` is `running`. Max **25 concurrent threads** (advisor threads are exempt — see § Advisor). When draining a per-thread stream, break on `session.thread_status_idle` (and check its `stop_reason` as you would for the session-level idle).
|
||||
|
||||
**A session budget is one shared cap across all threads** — no per-thread caps. Each thread's consumption is priced at its own served model, and threads pause independently (`stop_reason: budget_reached`) as the shared cap is reached; one thread can pause while another finishes its in-flight request. A thread waiting on `requires_action` outranks the cap at the session level. See `shared/managed-agents-core.md` § Session budgets.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,9 +152,9 @@ Each `SessionThread` carries `id`, `status` (`running` | `idle` | `rescheduling`
|
||||
|---|---|---|
|
||||
| `session.thread_created` | `session_thread_id`, `agent_name` | A new thread was created. |
|
||||
| `session.thread_status_running` | `session_thread_id`, `agent_name` | Thread started activity. |
|
||||
| `session.thread_status_idle` | `session_thread_id`, `agent_name`, **`stop_reason`** | Thread is awaiting input. Inspect `stop_reason` (same shape as `session.status_idle.stop_reason`). |
|
||||
| `session.thread_status_idle` | `session_thread_id`, `agent_name`, **`stop_reason`** | Thread is awaiting input — or paused at the session's shared budget (`stop_reason: budget_reached`). Inspect `stop_reason` (same shape as `session.status_idle.stop_reason`). |
|
||||
| `session.thread_status_rescheduled` | `session_thread_id`, `agent_name` | Thread is rescheduling after a retryable error. |
|
||||
| `session.thread_status_terminated` | `session_thread_id`, `agent_name` | Thread was archived or hit a terminal error. |
|
||||
| `session.thread_status_terminated` | `session_thread_id`, `agent_name` | Thread ended — completed its work and self-terminated (advisor consultation threads — see § Advisor), was archived, or hit a terminal error. |
|
||||
| `agent.thread_message_sent` | `to_session_thread_id`, `to_agent_name`, `content` | *This* thread sent a message to another thread. On the primary stream: the coordinator sent a task or follow-up to an agent. |
|
||||
| `agent.thread_message_received` | `from_session_thread_id`, `from_agent_name`, `content` | A message arrived on *this* thread from another. On the primary stream: an agent sent a report or question to the coordinator. |
|
||||
|
||||
@@ -87,6 +176,49 @@ GET /v1/sessions/{sid}/threads/{tid}/stream?event_deltas%5B%5D=agent.message
|
||||
|
||||
---
|
||||
|
||||
## Advisor
|
||||
|
||||
An `{"type": "advisor", "model": "<model id>"}` roster entry gives the session's **primary thread** an advisor: a model it can consult mid-turn for strategic guidance (planning an approach, getting unstuck, reviewing work before finishing). The entry has exactly two fields — `type` and `model` — and can sit alongside any other roster forms; a roster with no other entries works too. The advisor is also available as a server tool on the Messages API (`advisor_20260301` — see `shared/tool-use-concepts.md` → Advisor); the Managed Agents surface differs in configuration and delivery: the roster entry has **no `max_uses`, `max_tokens`, or `caching` fields**, and advice arrives through thread events rather than `advisor_tool_result` blocks.
|
||||
|
||||
```python
|
||||
agent = client.beta.agents.create(
|
||||
name="Backend engineer",
|
||||
model="claude-sonnet-5",
|
||||
system="You implement backend features end to end.",
|
||||
multiagent={
|
||||
"type": "coordinator",
|
||||
"agents": [{"type": "advisor", "model": "claude-opus-5"}],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
(Claude Opus 5 is the default advisor choice. It is a redacted advisor — the agent reads its advice server-side, but the client sees `[{"type": "redacted"}]`; see *Plaintext vs redacted delivery* below. For client-readable advice, a plaintext advisor such as `claude-opus-4-8` is valid only when the agent's own model is `claude-opus-4-8` or below — agents on Claude Opus 5, Claude Fable 5, or Claude Mythos 5 can only pair with redacted advisors, so client-readable advice is not available for them (pairing table: `shared/tool-use-concepts.md`).)
|
||||
|
||||
**Rules:**
|
||||
- **At most one advisor entry per roster.** The entry occupies the reserved roster name `anthropic.advisor` — a roster that also lists a member literally named `anthropic.advisor` is a 400. In responses, the advisor entry is echoed **last** in the roster regardless of submitted position.
|
||||
- **Pairing is validated at agent save:** the advisor model must meet a minimum capability bar, and the agent's own model must not be more capable than its advisor (equals can pair). Invalid pairing → 400. The valid pairs mirror the Messages advisor tool's executor↔advisor table (`shared/tool-use-concepts.md`) — except Claude Fable 5, which is temporarily unavailable as a Managed Agents advisor; use claude-opus-5 instead. Claude Mythos 5 advisors are unaffected — the unavailability is specific to claude-fable-5, despite the two models' shared capabilities.
|
||||
- **Only the primary thread consults it.** The advisor is not a roster agent: invisible to the coordinator's `list_agents` tool, unreachable via `send_to_agent`, and roster agents cannot consult it.
|
||||
|
||||
**How consultations work.** Each consultation runs as a platform-spawned thread named `anthropic.advisor` that terminates itself when done; the advice is delivered to the primary thread as an `agent.thread_message_received` event. Typical event order (the reserved name rides `agent_name` on lifecycle events and `from_agent_name` on the delivery):
|
||||
|
||||
1. `session.thread_created`
|
||||
2. `session.thread_status_running`
|
||||
3. `agent.thread_message_received` — the advice
|
||||
4. `session.thread_status_idle` (`stop_reason: end_turn`)
|
||||
5. `session.thread_status_terminated`
|
||||
|
||||
No `agent.tool_use` and no `agent.thread_message_sent` are emitted for a consultation, and **the advice delivery is not guaranteed to precede the advisor thread's idle/terminated events** — don't treat those as "advice already delivered."
|
||||
|
||||
**Plaintext vs redacted delivery.** Whether your client can read the advice is the advisor model's policy, mirroring the Messages advisor tool's result variants: models that return plaintext there deliver readable text content here; models that return redacted results deliver `[{"type": "redacted"}]` as the message content on every client surface, while the agent still reads the full advice server-side. Advisor thinking is never surfaced. Clients cannot send `redacted` blocks themselves — an event containing one is a 400.
|
||||
|
||||
**Failure and interruption.** A failed consultation — or one abandoned via a `user.interrupt` carrying the advisor thread's `session_thread_id` — never fails the agent's turn: the agent continues after a generic notice. A session-level `user.interrupt` during a consultation halts the whole session as usual (every thread, primary included), terminating the advisor thread with no advice delivered.
|
||||
|
||||
**Threads, billing, caching.** Advisor threads are **exempt from the 25-concurrent-thread limit**. They appear in the session's thread list with `agent` set to the advisor form as configured (`{"type": "advisor", "model": ...}`) and `parent_thread_id` set to the primary thread. Consultations are billed at the advisor model's rates; their tokens appear in the advisor thread's usage and the session's totals. Advisor-side prompt caching is automatic — nothing to configure.
|
||||
|
||||
**Removing the advisor:** update the agent with a roster that omits the entry; if the advisor is the roster's only entry, clear the roster with `"multiagent": null`.
|
||||
|
||||
---
|
||||
|
||||
## Tool permissions and custom tools from subagent threads
|
||||
|
||||
When a subagent needs your client (an `always_ask` confirmation, or a custom tool result), the request is **cross-posted to the primary thread** with `session_thread_id` identifying the originating thread — so you only need to watch the session stream. Reply with `user.tool_confirmation` (carrying `tool_use_id`) or `user.custom_tool_result` (carrying `custom_tool_use_id`), and **echo the `session_thread_id` from the originating event** (the SDK param type and docstring expect it). The server also routes by the tool-use ID, so the echo is belt-and-suspenders rather than load-bearing — but include it.
|
||||
@@ -122,4 +254,4 @@ The same pattern applies to `user.custom_tool_result`.
|
||||
- **Don't assume shared context.** Threads share the filesystem but not conversation history or tools. If the coordinator needs a subagent to act on something, it must say so in the delegated message (or write it to disk).
|
||||
- **Depth > 1 is a validation error.** Rostering an agent that itself carries a `multiagent.agents` roster fails the create or update — only the session's coordinator delegates.
|
||||
|
||||
For per-language bindings beyond Python, WebFetch `https://platform.claude.com/docs/en/managed-agents/multi-agent.md` (see `shared/live-sources.md`).
|
||||
For per-language bindings beyond Python, WebFetch `https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md` (see `shared/live-sources.md`).
|
||||
|
||||
@@ -49,7 +49,7 @@ Usually zero or one question:
|
||||
- `user.define_outcome` + rubric — when §2 settled on an Outcome; the harness iterates and grades until the rubric passes.
|
||||
- **Scheduled shape?** Skip per-session kickoff entirely — create a **deployment** (`deployments.create()` with `schedule` + `initial_events`); each firing creates the session autonomously. See `shared/managed-agents-scheduled-deployments.md`.
|
||||
|
||||
Mechanics to bake into the runtime code: session creation resolves resources (a bad mount surfaces there, before tokens) but does not itself provision the sandbox; open the event stream *before* sending the kickoff; break on `session.status_terminated`, or `session.status_idle` with a terminal `stop_reason` — anything except `requires_action` (`shared/managed-agents-client-patterns.md` Pattern 5); usage lands on `span.model_request_end`; artifacts land in `/mnt/session/outputs/` (`files.list({scope_id: session.id, ...})`).
|
||||
Mechanics to bake into the runtime code: session creation resolves resources (a bad mount surfaces there, before tokens) but does not itself provision the sandbox; open the event stream *before* sending the kickoff; break on `session.status_terminated`, or `session.status_idle` with any non-`requires_action` `stop_reason` — terminal, or `budget_reached`, which is not terminal (only a budget change/removal resumes it) (`shared/managed-agents-client-patterns.md` Pattern 5); usage lands on `span.model_request_end`; artifacts land in `/mnt/session/outputs/` (`files.list({scope_id: session.id, ...})`).
|
||||
|
||||
## 5. Integrate — emit the code
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ These appear on the standard event stream (`sessions.events.stream` / `.list`) a
|
||||
| `needs_revision` | Agent starts another iteration. |
|
||||
| `max_iterations_reached` | No further grader cycles. Agent may run one final revision, then session → `idle`. |
|
||||
| `failed` | Session → `idle`. Rubric fundamentally doesn't match the task (e.g. description and rubric contradict). |
|
||||
| `interrupted` | Emitted whenever a `user.interrupt` arrives while an outcome is active — **even if evaluation hadn't started**. In that case `outcome_evaluation_start_id` is an empty string rather than an event ID, so don't use it as a lookup key without checking. |
|
||||
| `interrupted` | Emitted whenever a `user.interrupt` arrives while an outcome is active — **even if evaluation hadn't started**. In that case `outcome_evaluation_start_id` is an empty string rather than an event ID, so don't use it as a lookup key without checking. (Except an interrupt sent while paused at the session budget, which is accepted and ignored — see `shared/managed-agents-events.md` § Reaching a session budget.) |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -99,8 +99,8 @@ for ev in session.outcome_evaluations:
|
||||
## Interaction rules & pitfalls
|
||||
|
||||
- **One outcome at a time.** Chain by sending the next `user.define_outcome` only after the previous one's terminal `span.outcome_evaluation_end` (`satisfied` / `max_iterations_reached` / `failed` / `interrupted`). The session retains history across chained outcomes.
|
||||
- **Steering is allowed but optional.** You *may* send `user.message` events mid-outcome to nudge direction, but the agent already knows to keep working until terminal — don't send "keep going" prompts.
|
||||
- **`user.interrupt` pauses the current outcome** — it marks `result: "interrupted"` and leaves the session `idle`, ready for a new outcome or conversational turn.
|
||||
- **Steering is allowed but optional.** You *may* send `user.message` events mid-outcome to nudge direction, but the agent already knows to keep working until terminal — don't send "keep going" prompts. (Exception: a session paused at its budget (`stop_reason: budget_reached`) accepts only settle events — a steering `user.message`, or a chained `user.define_outcome`, is a 400 there; see `shared/managed-agents-events.md` § Reaching a session budget.)
|
||||
- **`user.interrupt` pauses the current outcome** — it marks `result: "interrupted"` and leaves the session `idle`, ready for a new outcome or conversational turn. (Exception: sent while paused at the session budget, the interrupt is accepted and ignored and the outcome stays active — see `shared/managed-agents-events.md` § Reaching a session budget.)
|
||||
- **After terminal, the session is reusable** — continue conversationally or define a new outcome.
|
||||
- **Outcome ≠ session-create field.** Don't put `outcome`, `rubric`, or `description` on `sessions.create()` — outcomes are always sent as a `user.define_outcome` event.
|
||||
- **Idle-break gate is unchanged.** In your drain loop, keep using `event.type === 'session.status_idle' && event.stop_reason?.type !== 'requires_action'` — do **not** gate on `span.outcome_evaluation_end` alone (on `needs_revision` the session keeps running). See `shared/managed-agents-client-patterns.md` Pattern 5.
|
||||
|
||||
@@ -17,7 +17,7 @@ If you're about to write `sessions.create()` with `model`, `system`, or `tools`
|
||||
|
||||
**When generating code, separate setup from runtime.** `agents.create()` belongs in a setup script (or a guarded `if agent_id is None:` block), not at the top of the hot path. If the user's code calls `agents.create()` on every invocation, they're accumulating orphaned agents and paying the create latency for nothing. The correct shape is: define the agent as a version-controlled YAML manifest, apply it once with `ant beta:agents create < agent.yaml` (or a guarded setup script — see `shared/anthropic-cli.md`), persist the returned ID (config file, env var, secrets manager), and have every run load the ID and call `sessions.create()`.
|
||||
|
||||
**To change the agent's behavior, use `POST /v1/agents/{id}` — don't create a new one.** Each update bumps the version; running sessions keep their pinned version, new sessions get the latest (or pin explicitly via `{type: "agent", id, version}`). See `shared/managed-agents-core.md` → Agents → Versioning. To change `tools`/`mcp_servers`/`vault_ids` on **one running session** without touching the agent object, use `sessions.update()` — see `shared/managed-agents-core.md` → Updating the agent configuration mid-session.
|
||||
**To change the agent's behavior, use `POST /v1/agents/{id}` — don't create a new one.** Each update bumps the version; running sessions keep their pinned version, new sessions get the latest (or pin explicitly via `{type: "agent", id, version}`). See `shared/managed-agents-core.md` → Agents → Versioning. To change `tools`/`mcp_servers` on **one running session** without touching the agent object, use `sessions.update()` (`vault_ids` attaches at session create only) — see `shared/managed-agents-core.md` → Updating the agent configuration mid-session.
|
||||
|
||||
## Beta Headers
|
||||
|
||||
@@ -56,6 +56,10 @@ Managed Agents is in beta. The SDK sets required beta headers automatically:
|
||||
| Store credentials (MCP auth, API keys for CLIs/SDKs) | `shared/managed-agents-tools.md` (Vaults section) — `mcp_oauth` / `static_bearer` / `environment_variable` |
|
||||
| Call a non-MCP API / CLI that needs a secret | `shared/managed-agents-tools.md` (Vaults section) — `environment_variable` credential, substituted at egress. If that doesn't fit (e.g. self-hosted sandboxes), `shared/managed-agents-client-patterns.md` Pattern 9 keeps the secret host-side via a custom tool |
|
||||
| Run an agent on a recurring cron schedule | `shared/managed-agents-scheduled-deployments.md` — deployments, deployment runs, pause/auto-pause |
|
||||
| Cap a session's spend with a hard dollar budget | `shared/managed-agents-core.md` (§ Session budgets) — `budget` at session create, `budget_reached` pause, change/remove to resume. Deployments: `shared/managed-agents-scheduled-deployments.md` § Deployment budgets |
|
||||
| Pin where model inference runs (data residency) | `shared/managed-agents-core.md` (§ Pinning inference geography) — `model.inference_geo` on the agent, per-session override, roster uniformity |
|
||||
| Load skills from the codebase instead of uploading | `shared/managed-agents-tools.md` (§ Skills from a GitHub repository) — root `.claude/skills` discovery at session start |
|
||||
| Give the session an advisor to consult mid-turn | `shared/managed-agents-multiagent.md` (§ Advisor) — `{type: "advisor", model}` roster entry, consultation threads, plaintext vs redacted delivery |
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
@@ -66,6 +70,6 @@ Managed Agents is in beta. The SDK sets required beta headers automatically:
|
||||
- **Stream to get events** — `GET /v1/sessions/{id}/events/stream` is the primary way to receive agent output in real-time.
|
||||
- **SSE stream has no replay — reconnect with consolidation** — if the stream drops while a `agent.tool_use`, `agent.mcp_tool_use`, or `agent.custom_tool_use` is pending resolution (`user.tool_confirmation` for the first two, `user.custom_tool_result` for the last one), the session deadlocks (client disconnects → session idles → reconnect happens → no client resolution happens). On every (re)connect: open stream with `GET /v1/sessions/{id}/events/stream` , fetch `GET /v1/sessions/{id}/events`, dedupe by event ID, then proceed. See `shared/managed-agents-events.md` → Reconnecting after a dropped stream.
|
||||
- **Don't trust HTTP-library timeouts as wall-clock caps** — `requests` `timeout=(c, r)` and `httpx.Timeout(n)` are *per-chunk* read timeouts; they reset every byte, so a trickling connection can block indefinitely. For a hard deadline on raw-HTTP polling, track `time.monotonic()` at the loop level and bail explicitly. Prefer the SDK's `sessions.events.stream()` / `sessions.events.list()` over hand-rolled HTTP. See `shared/managed-agents-events.md` → Receiving Events.
|
||||
- **Messages queue** — you can send events while the session is `running` or `idle`; they're processed in order. No need to wait for a response before sending the next message.
|
||||
- **Messages queue** — you can send events while the session is `running` or `idle`; they're processed in order. No need to wait for a response before sending the next message. Exception: a session paused at its budget (`stop_reason: budget_reached`) accepts only settle events — change or remove the budget to resume (`shared/managed-agents-core.md` § Session budgets).
|
||||
- **Environment `config.type` is `"cloud"` or `"self_hosted"`** — `cloud` runs the container on Anthropic's infrastructure; `self_hosted` moves tool execution to your own (see `shared/managed-agents-self-hosted-sandboxes.md`).
|
||||
- **Archive is permanent on every resource** — archiving an agent, environment, session, vault, credential, or memory store makes it read-only with no unarchive. For agents, environments, and memory stores specifically, archived resources cannot be referenced by new sessions (existing sessions continue). Do not call `.archive()` on a production agent, environment, or memory store as cleanup — **always confirm with the user before archiving**.
|
||||
|
||||
@@ -81,6 +81,16 @@ The response is a deployment object (`depl_` ID prefix). Check `schedule.upcomin
|
||||
|
||||
> ⚠️ **DST edge:** wall-clock times that don't exist on a spring-forward day (e.g. 2AM) are **skipped**; times that occur twice on a fall-back day **fire twice**. Schedule outside the 1–3AM local window, or use UTC, when missed or duplicate executions are unacceptable.
|
||||
|
||||
## Deployment budgets
|
||||
|
||||
A deployment accepts the same `budget` object as a session (`{type: "limit", max_list_cost: {amount, currency}}` — minor-unit cents string, `USD` only; see `shared/managed-agents-core.md` § Session budgets). The cap is **copied onto each session at fire time**, and that session then behaves exactly like any budgeted session.
|
||||
|
||||
Deployment budget update semantics differ from a session's:
|
||||
|
||||
- `budget` is accepted on **create and update** — it is not create-only.
|
||||
- `budget: null` on update **clears** it, and a cleared budget **can be re-added later** — there is no one-way door.
|
||||
- A change applies **from the next fired session** — sessions already running keep the cap they were created with (change those via their own session update).
|
||||
|
||||
## Deployment runs
|
||||
|
||||
Every trigger attempt — successful or not — writes a **deployment run** record (`drun_` prefix), so you can audit failures independent of the session lifecycle. A successful run carries the created `session_id`; follow that session via the event stream (`shared/managed-agents-events.md`) or webhooks (`shared/managed-agents-webhooks.md`) as usual. A failed run carries an `error` whose `type` explains why session creation was rejected.
|
||||
|
||||
@@ -182,7 +182,7 @@ This keeps secrets out of reusable agent definitions. Each vault credential is t
|
||||
|
||||
> 💡 **Per-tool enablement (empirical):** `mcp_toolset` has been observed accepting `default_config: {enabled: false}` + `configs: [{name, enabled: true}]` for an allowlist pattern. The API ref shows only the minimal `{type, mcp_server_name}` form.
|
||||
|
||||
> 💡 **Changing tools/MCP servers on a running session:** `sessions.update()` can replace `agent.tools`, `agent.mcp_servers`, and `vault_ids` while the session is `idle` — a session-local override that doesn't touch the agent object. See `shared/managed-agents-core.md` → Updating the agent configuration mid-session.
|
||||
> 💡 **Changing tools/MCP servers on a running session:** `sessions.update()` can replace `agent.tools` and `agent.mcp_servers` while the session is `idle` — a session-local override that doesn't touch the agent object. `vault_ids` is create-only. See `shared/managed-agents-core.md` → Updating the agent configuration mid-session.
|
||||
|
||||
**Large tool outputs.** If a tool returns more than **100,000 characters (roughly 25,000 tokens)**, the output is automatically offloaded to a file in the sandbox — the agent receives a truncated preview plus the file path and can `read` the full content. No configuration required. The threshold is in *characters*, not tokens, and applies to built-in agent tools as well as MCP tools.
|
||||
|
||||
@@ -305,7 +305,7 @@ A credential must have at least one location enabled; a create or update that wo
|
||||
|
||||
Skills are reusable, filesystem-based resources that provide your agent with domain-specific expertise: workflows, context, and best practices that transform general-purpose agents into specialists. Unlike prompts (conversation-level instructions for one-off tasks), skills load on-demand and eliminate the need to repeatedly provide the same guidance across multiple conversations.
|
||||
|
||||
Two types — both work the same way; the agent automatically uses them when relevant to the task at hand:
|
||||
Skills reach the agent two ways: **attached** through the agent's `skills` array, or **loaded from a GitHub repository** mounted on the session (see § Skills from a GitHub repository below). The agent automatically uses them when relevant to the task at hand:
|
||||
|
||||
| Type | What it is |
|
||||
|---|---|
|
||||
@@ -356,6 +356,19 @@ agent = client.beta.agents.create(
|
||||
|
||||
`version` is optional on **both** kinds and defaults to `"latest"` — it is not custom-skill-only.
|
||||
|
||||
### Skills from a GitHub repository
|
||||
|
||||
Skills can also live in your codebase. When a session mounts a repository via the `github_repository` resource (see `shared/managed-agents-environments.md` → GitHub Repositories), the repository's root `.claude/skills` directory is scanned at session start, and each skill found becomes available to the agent: it sees each discovered skill's name, description, and sandbox path, and reads the skill's `SKILL.md` (plus any scripts/resources it ships) when a task matches.
|
||||
|
||||
**The agent can discover any skill in `.claude/skills/<skill-name>/`** — one directory level deep at the repository root. Skills in the following locations are not discoverable: a bare `.claude/skills/SKILL.md` (no skill directory), anything nested deeper (`.claude/skills/tools/code-review/SKILL.md`), a `skills/` directory outside `.claude`, or a `.claude/skills` inside a package subdirectory (though those can still surface when the agent reads files under that subtree). The `SKILL.md` format is the same as uploaded custom skills.
|
||||
|
||||
> ⚠️ **Repository skills are agent instructions — treat them as part of your trust boundary.** Anyone who can commit to a mounted repository (a merged external PR, a compromised dependency, a contributor) can add or edit `.claude/skills/` content, and the platform loads it at session start with no review step — where session tools like `bash` and `web_fetch` give injected instructions real capability. Only mount repositories you trust, and audit `.claude/skills/` before mounting one with external contributors.
|
||||
|
||||
Rules:
|
||||
- **Cloud sandboxes only** — self-hosted sandboxes don't support `github_repository` resources, so they can't load repository skills.
|
||||
- **Scanned once, at session start**, from the repository state checked out then (the resource's `checkout` branch/commit, else the default branch). Commits pushed mid-session are not picked up — start a new session for updated skills. Repositories added to a *running* session are not scanned either.
|
||||
- **Coexists with attached skills.** If a repository skill shares a name with an attached skill (or a skill from another mounted repo), both are available, each announced with its own path.
|
||||
|
||||
### Skills API
|
||||
|
||||
| Operation | Method | Path |
|
||||
|
||||
@@ -86,11 +86,11 @@ The top-level `id` is the same value as the `webhook-id` header, and it is per *
|
||||
|---|---|
|
||||
| `session.status_scheduled` | Session created and ready to accept events |
|
||||
| `session.status_run_started` | Agent execution kicked off (every transition to `running`) |
|
||||
| `session.status_idled` | Agent awaiting input (tool approval, custom tool result, or next message) |
|
||||
| `session.status_idled` | Agent awaiting input (tool approval, custom tool result, or next message) — or paused at its session budget. The webhook payload is thin — list the session's events and check the latest `session.status_idle` event's `stop_reason` (the session object itself has no `stop_reason` field): if it is `budget_reached`, further `user.message` events return a 400 and only a budget change/removal resumes the session (`shared/managed-agents-core.md` § Session budgets) |
|
||||
| `session.status_rescheduled` | A transient error occurred; the session is retrying automatically |
|
||||
| `session.status_terminated` | Session ended — **on completion or on error**, not error-only |
|
||||
| `session.thread_created` | Multiagent: coordinator opened a new subagent thread |
|
||||
| `session.thread_idled` | Multiagent: a subagent thread is waiting for input |
|
||||
| `session.thread_created` | Multiagent: coordinator opened a new subagent thread, or the session's advisor is being consulted (`shared/managed-agents-multiagent.md` → Advisor) |
|
||||
| `session.thread_idled` | Child threads only: a subagent thread is waiting for input — or paused because the session reached its budget cap. When the whole session pauses at the cap, a `session.status_idled` webhook also fires and the stream's `session.status_idle` event carries `stop_reason: budget_reached` — unless another thread is waiting on a tool ask, which outranks the cap at the session level (`shared/managed-agents-core.md` § Session budgets). |
|
||||
| `session.thread_terminated` | A thread ended — child completed its work, or the thread was archived. **Child threads only**; the primary thread's end surfaces as `session.status_terminated` |
|
||||
| `session.outcome_evaluation_ended` | Outcome grader finished one iteration |
|
||||
| `session.updated` | Session properties changed (name, configuration) |
|
||||
@@ -140,4 +140,4 @@ The top-level `id` is the same value as the `webhook-id` header, and it is per *
|
||||
- A `3xx` response. Redirects are never followed; disables immediately, on the first attempt. Reason: `auto-disabled: endpoint URL returned a redirect (3xx)`.
|
||||
- The URL resolves to a non-public IP at connect time. Disables immediately. Reason: `auto-disabled: endpoint URL resolved to an invalid address`.
|
||||
- Continuous failure for a sustained period. Reason: `auto-disabled after sustained delivery failures`. **The trigger is duration, not a delivery count** — a single `2xx` resets the window, so one flaky event can't disable the endpoint.
|
||||
- **Thin payload is intentional.** Don't expect `stop_reason`, `outcome_evaluations`, credential secrets, etc. on the webhook body — fetch the resource.
|
||||
- **Thin payload is intentional.** Don't expect `stop_reason` (list the session's events for that — the session object has no `stop_reason` field), `outcome_evaluations`, credential secrets, etc. on the webhook body — fetch the resource.
|
||||
|
||||
@@ -369,6 +369,12 @@ The advisor tool pairs a faster, lower-cost **executor** model (the top-level `m
|
||||
}
|
||||
```
|
||||
|
||||
Optional fields on the tool definition:
|
||||
|
||||
- `max_uses` — cap on advisor consultations per request. Exceeding it makes the `advisor_tool_result` block's `content` the error object `{"type": "advisor_tool_result_error", "error_code": "max_uses_exceeded"}` — the third member of the content union in the payload-shape table below.
|
||||
- `max_tokens` — bounds the advisor's total output (thinking + text) per call. At the cap the result block carries `stop_reason: "max_tokens"` and a truncation note is appended to the advice the executor sees; the server also emits a remaining-tokens budget block in the advisor's prompt so it self-shapes toward the cap.
|
||||
- `caching` — cache-control for the advisor's own prompt, same shape as a cache breakpoint: `"caching": {"type": "ephemeral", "ttl": "5m"}` (`ttl` is `"5m"` or `"1h"`, default `"5m"`). Each call writes a cache entry at that TTL so later calls in the conversation read the stable prefix. Omitted = advisor prompt not cached.
|
||||
|
||||
**The advisor model must be at least as capable as the executor.** An invalid pairing returns `400 invalid_request_error`. Valid pairs:
|
||||
|
||||
| Executor (request `model`) | Valid advisor (tool `model`) |
|
||||
@@ -385,11 +391,14 @@ The advisor tool pairs a faster, lower-cost **executor** model (the top-level `m
|
||||
> |---|---|---|
|
||||
> | `advisor_result` | `text`, `stop_reason` | Advisor returns plaintext (e.g. Opus 4.8) |
|
||||
> | `advisor_redacted_result` | `encrypted_content`, `stop_reason` | Advisor returns encrypted output — Claude Opus 5, Claude Fable 5, Claude Mythos 5 |
|
||||
> | `advisor_tool_result_error` | `error_code` | Consultation failed — `max_uses_exceeded`, `prompt_too_long`, `too_many_requests`, `overloaded`, `unavailable`, `execution_time_exceeded`, or `model_not_found` |
|
||||
>
|
||||
> So switch on `advisor_tool_result.content` type, not on the block type. Code that reads `.text` unconditionally gets nothing back from an Claude Opus 5 advisor, because the payload is under `encrypted_content` instead — and you cannot read it, only replay it.
|
||||
|
||||
Call via `client.beta.messages.create(...)` with `betas=["advisor-tool-2026-03-01"]` (or the `anthropic-beta: advisor-tool-2026-03-01` header). In multi-turn conversations, append the full `response.content` — including any `advisor_tool_result` blocks — back to `messages` on the next turn. If you remove the advisor tool from `tools` on a later turn while the history still contains `advisor_tool_result` blocks, the API returns a 400.
|
||||
|
||||
> **Advisor on Managed Agents:** CMA sessions support an advisor too, configured as a `{"type": "advisor", "model"}` entry in the agent's multiagent roster rather than as a tool definition — no `max_uses`/`max_tokens`/`caching` options, and advice is delivered as thread events on the session's event stream rather than `advisor_tool_result` blocks. See `shared/managed-agents-multiagent.md` → Advisor.
|
||||
|
||||
---
|
||||
|
||||
## Client-Side Tools: Memory
|
||||
|
||||
@@ -272,6 +272,7 @@ import fs from "fs";
|
||||
|
||||
const file = await client.beta.files.upload({
|
||||
file: fs.createReadStream("data.csv"),
|
||||
purpose: "agent",
|
||||
});
|
||||
|
||||
// Use in a session
|
||||
|
||||
Reference in New Issue
Block a user