diff --git a/skills/claude-api/SKILL.md b/skills/claude-api/SKILL.md index 2d4272034..1346a5c08 100644 --- a/skills/claude-api/SKILL.md +++ b/skills/claude-api/SKILL.md @@ -13,37 +13,38 @@ This skill helps you build LLM-powered applications with Claude. Choose the righ ## Before You Start -Scan the target file (or, if no target file, the prompt and project) for non-Anthropic provider markers — `import openai`, `from openai`, `langchain_openai`, `OpenAI(`, `gpt-4`, `gpt-5`, file names like `agent-openai.py` or `*-generic.py`, or any explicit instruction to keep the code provider-neutral. If you find any, stop and tell the user that this skill produces Claude/Anthropic SDK code; ask whether they want to switch the file to Claude or want a non-Claude implementation. Do not edit a non-Anthropic file with Anthropic SDK calls. +Scan the target file (or, if no target file, the prompt and project) for non-Anthropic provider markers - `import openai`, `from openai`, `langchain_openai`, `OpenAI(`, `gpt-4`, `gpt-5`, file names like `agent-openai.py` or `*-generic.py`, or any explicit instruction to keep the code provider-neutral. If you find any, stop and tell the user that this skill produces Claude/Anthropic SDK code; ask whether they want to switch the file to Claude or want a non-Claude implementation. Do not edit a non-Anthropic file with Anthropic SDK calls. (Exception: the `prompt-audit` subcommand is non-interactive and does not stop here - it records non-Anthropic provider markers in its report's stated assumptions and never proposes switching a non-Anthropic file to the Anthropic SDK.) ## Output Requirement When the user asks you to add, modify, or implement a Claude feature, your code must call Claude through one of: 1. **The official Anthropic SDK** for the project's language (`anthropic`, `@anthropic-ai/sdk`, `com.anthropic.*`, etc.). This is the default whenever a supported SDK exists for the project. -2. **Raw HTTP** (`curl`, `requests`, `fetch`, `httpx`, etc.) — only when the user explicitly asks for cURL/REST/raw HTTP, the project is a shell/cURL project, or the language has no official SDK. +2. **Raw HTTP** (`curl`, `requests`, `fetch`, `httpx`, etc.) - only when the user explicitly asks for cURL/REST/raw HTTP, the project is a shell/cURL project, or the language has no official SDK. -Never mix the two — don't reach for `requests`/`fetch` in a Python or TypeScript project just because it feels lighter. Never fall back to OpenAI-compatible shims. +Never mix the two - don't reach for `requests`/`fetch` in a Python or TypeScript project just because it feels lighter. Never fall back to OpenAI-compatible shims. -**Never guess SDK usage.** Function names, class names, namespaces, method signatures, and import paths must come from explicit documentation — either the `{lang}/` files in this skill or the official SDK repositories or documentation links listed in `shared/live-sources.md`. If the binding you need is not explicitly documented in the skill files, WebFetch the relevant SDK repo from `shared/live-sources.md` before writing code. Do not infer Ruby/Java/Go/PHP/C# APIs from cURL shapes or from another language's SDK. +**Never guess SDK usage.** Function names, class names, namespaces, method signatures, and import paths must come from explicit documentation - either the `{lang}/` files in this skill or the official SDK repositories or documentation links listed in `shared/live-sources.md`. If the binding you need is not explicitly documented in the skill files, WebFetch the relevant SDK repo from `shared/live-sources.md` before writing code. Do not infer Ruby/Java/Go/PHP/C# APIs from cURL shapes or from another language's SDK. -**If WebFetch or repository access fails** (network restricted, timeouts, clone blocked): do not keep retrying — write code from the patterns and namespace/package tables in the `{lang}/` file, run the compiler or interpreter on it, and iterate on the error output. For statically-typed SDKs (C#, Java, Go) a compile-fix loop against local errors reaches working code faster than blocked network research. +**If WebFetch or repository access fails** (network restricted, timeouts, clone blocked): do not keep retrying - write code from the patterns and namespace/package tables in the `{lang}/` file, run the compiler or interpreter on it, and iterate on the error output. For statically-typed SDKs (C#, Java, Go) a compile-fix loop against local errors reaches working code faster than blocked network research. ## Defaults Unless the user requests otherwise: -For the Claude model version, please use Claude Opus 5, which you can access via the exact model string `claude-opus-5`. Please default to using adaptive thinking (`thinking: {type: "adaptive"}`) for anything remotely complicated. And finally, please default to streaming for any request that may involve long input, long output, or high `max_tokens` — it prevents hitting request timeouts. Use the SDK's `.get_final_message()` / `.finalMessage()` helper to get the complete response if you don't need to handle individual stream events +For the Claude model version, please use Claude Opus 5, which you can access via the exact model string `claude-opus-5`. Please default to using adaptive thinking (`thinking: {type: "adaptive"}`) for anything remotely complicated. And finally, please default to streaming for any request that may involve long input, long output, or high `max_tokens` - it prevents hitting request timeouts. Use the SDK's `.get_final_message()` / `.finalMessage()` helper to get the complete response if you don't need to handle individual stream events -## ⚠️ API Drift — Your Training Prior May Be Stale +## Warning: API Drift - Your Training Prior May Be Stale -Several common Claude API shapes changed in 2025–2026. If you recall a pattern from training, verify it against the `{lang}/` files in this skill before writing — the rows below are the most frequent drift points: +Several common Claude API shapes changed in 2025-2026. If you recall a pattern from training, verify it against the `{lang}/` files in this skill before writing - the rows below are the most frequent drift points: | Area | Stale prior | Current API | |---|---|---| -| Extended thinking | `thinking: {type: "enabled", budget_tokens: N}` | On Claude 4.6+ models: `thinking: {type: "adaptive"}`. `budget_tokens` is deprecated on Opus 4.6 / Sonnet 4.6 and **rejected with a 400** on Fable 5 / Sonnet 5 / Opus 5 / 4.8 / 4.7. Pre-4.6 models still use `budget_tokens`. | -| Web search / web fetch tool type | `web_search_20250305`, `web_fetch_20250910` | `web_search_20260209`, `web_fetch_20260209` (dynamic filtering) on Opus 5/4.8/4.7/4.6, Sonnet 5, and Sonnet 4.6. Older models keep the basic variants; on Vertex AI only basic `web_search_20250305` is available (web fetch is not on Vertex) — see the Server Tools QR below. | -| PHP parameter names | snake_case wire names as named args (`max_tokens`) | Top-level named args are camelCase (`maxTokens`). Nested array keys vary by feature (e.g. `'taskBudget'`, `'skillID'`, `'mcp_server_name'`) — copy the exact key from the documented example; do not bulk-convert. | -| Managed Agents credentials | Keep secrets host-side via custom tools (the only option before vaults shipped) | Vault `environment_variable` credentials — stored by Anthropic, substituted at egress, never visible in the sandbox (`shared/managed-agents-tools.md` → Vaults). Host-side custom tools remain the fallback for self-hosted sandboxes. | +| Extended thinking | `thinking: {type: "enabled", budget_tokens: N}` | On Claude 4.6+ models: `thinking: {type: "adaptive"}`. `budget_tokens` is deprecated on Opus 4.6 / Sonnet 4.6 and **rejected with a 400** on Fable 5/5.1 / Sonnet 5 / Opus 5 / 4.8 / 4.7. Pre-4.6 models still use `budget_tokens`. | +| Web search / web fetch tool type | `web_search_20250305`, `web_fetch_20250910` | `web_search_20260209`, `web_fetch_20260209` (dynamic filtering) on Opus 5/4.8/4.7/4.6, Sonnet 5, and Sonnet 4.6. Older models keep the basic variants; on Vertex AI only basic `web_search_20250305` is available (web fetch is not on Vertex) - see the Server Tools QR below. | +| PHP parameter names | snake_case wire names as named args (`max_tokens`) | Top-level named args are camelCase (`maxTokens`). Nested array keys vary by feature (e.g. `'taskBudget'`, `'skillID'`, `'mcp_server_name'`) - copy the exact key from the documented example; do not bulk-convert. | +| Managed Agents credentials | Keep secrets host-side via custom tools (the only option before vaults shipped) | Vault `environment_variable` credentials - stored by Anthropic, substituted at egress, never visible in the sandbox (`shared/managed-agents-tools.md` -> Vaults). Host-side custom tools remain the fallback for self-hosted sandboxes. | +| Files API / Skills | `client.beta.files.*` / `client.beta.skills.*` with beta `files-api-2025-04-14` / `skills-2025-10-02` | Out of beta: `client.files.*` / `client.skills.*`, no beta header. In current SDKs `client.beta.files` / `client.beta.skills` have breaking shape changes from previous versions, matching the stable namespaces - migrate per `shared/live-sources.md` -> Files API / Skills Guide. | The `{lang}/` files in this skill are authoritative over recalled patterns. @@ -51,34 +52,33 @@ The `{lang}/` files in this skill are authoritative over recalled patterns. ## Subcommands -If the User Request at the bottom of this prompt is a bare subcommand string (no prose), search every **Subcommands** table in this document — including any in sections appended below — and follow the matching Action column directly. This lets users invoke specific flows via `/claude-api `. If no table in the document matches, treat the request as normal prose. +If the User Request at the bottom of this prompt is a bare subcommand string (no prose), search every **Subcommands** table in this document - including any in sections appended below - and follow the matching Action column directly. This lets users invoke specific flows via `/claude-api `. If no table in the document matches, treat the request as normal prose. | Subcommand | Action | |---|---| -| `migrate` | Migrate existing Claude API code to a newer model. **Read `shared/model-migration.md` immediately** and follow it in order: Step 0 (confirm scope — ask which files/directories before any edit), Step 1 (classify each file), then the per-target breaking-changes section. Do not summarize the guide — execute it. If the user did not name a target model, ask which model to migrate to in the same turn as the scope question. After the per-target changes are applied, audit the in-scope prompt text, tool descriptions, and request code against `shared/prompt-audit.md` — prompting written for the source model is part of every migration, and it does not announce itself. | -| `prompt-audit` | Audit existing prompts, skills, and tool descriptions for dated patterns ("cruft") written for older models. **Read `shared/prompt-audit.md` immediately** and follow it in order: Step 0 (establish scope and target model from the request and the repository — state the assumptions in the report, do not stop to ask), inventory, provenance, then the pattern scan. Produce both deliverables in full — the audit report (findings with `file:line`, pattern, why it's obsolete for the target model, confidence) and a proposed diff — without pausing for confirmation; apply edits only if the request explicitly asked for them. Do not summarize the guide — execute it. | -| `upgrade` | Upgrade the project's Anthropic SDK dependency across a major version — currently the Python SDK, `anthropic` 0.x → 1.x. Trailing words may name the language and/or a scope (`upgrade python`, `upgrade python sdk src/`). **Read `python/claude-api/sdk-upgrade.md` immediately** and follow it in order: Step 0 (confirm scope, then establish the current and target versions — a published 1.x must exist before you write a pin), the Step 1 inventory, each numbered section, then verification and the report. Do not summarize the guide — execute it. If the detected or named language has no `sdk-upgrade.md` in this skill, say that no major-version upgrade guide is bundled for that SDK yet and point the user at that SDK's CHANGELOG (repositories in `shared/live-sources.md`); do not improvise one from the Python guide. This is not model migration — to move code to a newer Claude model, use `migrate`. | +| `migrate` | Migrate existing Claude API code to a newer model. **Read `shared/model-migration.md` immediately** and follow it in order: Step 0 (confirm scope - ask which files/directories before any edit), Step 1 (classify each file), then the per-target breaking-changes section. Do not summarize the guide - execute it. If the user did not name a target model, ask which model to migrate to in the same turn as the scope question. After the per-target changes are applied, audit the in-scope prompt text, tool descriptions, and request code against `shared/prompt-audit.md` - prompting written for the source model is part of every migration, and it does not announce itself. | +| `prompt-audit` | Audit existing prompts, skills, and tool descriptions for dated patterns ("cruft") written for older models. **Read `shared/prompt-audit.md` immediately** and follow it in order: Step 0 (establish scope and target model from the request and the repository - state the assumptions in the report, do not stop to ask), inventory, provenance, then the pattern scan. Produce both deliverables in full - the audit report (findings with `file:line`, pattern, why it's obsolete for the target model, confidence) and a proposed diff - without pausing for confirmation; apply edits only if the request explicitly asked for them. Do not summarize the guide - execute it. | +| `upgrade` | Upgrade the project's Anthropic SDK dependency across a major version - currently the Python SDK, `anthropic` 0.x -> 1.x. Trailing words may name the language and/or a scope (`upgrade python`, `upgrade python sdk src/`). **Read `python/claude-api/sdk-upgrade.md` immediately** and follow it in order: Step 0 (confirm scope, then establish the current and target versions - a published 1.x must exist before you write a pin), the Step 1 inventory, each numbered section, then verification and the report. Do not summarize the guide - execute it. If the detected or named language has no `sdk-upgrade.md` in this skill, say that no major-version upgrade guide is bundled for that SDK yet and point the user at that SDK's CHANGELOG (repositories in `shared/live-sources.md`); do not improvise one from the Python guide. This is not model migration - to move code to a newer Claude model, use `migrate`. | +| `cost-optimize` | Reduce what existing Claude API code costs to run, without sacrificing output quality. **Read `shared/cost-optimization.md` immediately** and follow it in order: Step 0 (establish scope, quality bar, and baseline), the token profile - measured through the Usage and Cost Admin API when the user has an Admin API key, from the app's own `response.usage` logs when it has those (ask), or estimated from the code otherwise - then a savings-ranked shortlist of levers (quoted in dollars, % of bill, or relative buckets depending on which of those data sources you have), free wins (caching, input-token hygiene, loop hygiene, output-token hygiene, batch) before tradeoffs (budgets, effort, model choice, multi-model); any lever that earns a place becomes its own diff - proposed by default, applied and measured against the eval covering the traffic it touches when the user asks and approves - and "no changes recommended" is a valid outcome. Two standing rules: every run that exercises the model spends real money, so get the user's approval first; and when context for a lever is missing, work through it interactively with the user - this workflow is not expected to one-shot the audit. Do not summarize the guide - execute it; presenting the profile and the ranked plan to the user is part of executing it. | --- ## Language Detection -First decide whether the request involves a specific SDK language at all. Some tasks don't: auditing prompt text (`prompt-audit`), choosing a model, pricing and limits questions, and conceptual API questions are language-agnostic. For those, skip this section and don't ask the user for a language. - -When the task does involve reading or writing SDK code, determine which language the user is working in before reading code examples: +Before reading code examples, determine which language the user is working in (exception: for the `prompt-audit` subcommand, skip this section's ask steps - the audit is non-interactive and its inventory is language-agnostic; when no language is inferable, proceed without asking and state the assumption in the report): 1. **Look at project files** to infer the language: - - `*.py`, `requirements.txt`, `pyproject.toml`, `setup.py`, `Pipfile` → **Python** — read from `python/` - - `*.ts`, `*.tsx`, `package.json`, `tsconfig.json` → **TypeScript** — read from `typescript/` - - `*.js`, `*.jsx` (no `.ts` files present) → **TypeScript** — JS uses the same SDK, read from `typescript/` - - `*.java`, `pom.xml`, `build.gradle` → **Java** — read from `java/` - - `*.kt`, `*.kts`, `build.gradle.kts` → **Java** — Kotlin uses the Java SDK, read from `java/` - - `*.scala`, `build.sbt` → **Java** — Scala uses the Java SDK, read from `java/` - - `*.go`, `go.mod` → **Go** — read from `go/` - - `*.rb`, `Gemfile` → **Ruby** — read from `ruby/` - - `*.cs`, `*.csproj` → **C#** — read from `csharp/` - - `*.php`, `composer.json` → **PHP** — read from `php/` + - `*.py`, `requirements.txt`, `pyproject.toml`, `setup.py`, `Pipfile` -> **Python** - read from `python/` + - `*.ts`, `*.tsx`, `package.json`, `tsconfig.json` -> **TypeScript** - read from `typescript/` + - `*.js`, `*.jsx` (no `.ts` files present) -> **TypeScript** - JS uses the same SDK, read from `typescript/` + - `*.java`, `pom.xml`, `build.gradle` -> **Java** - read from `java/` + - `*.kt`, `*.kts`, `build.gradle.kts` -> **Java** - Kotlin uses the Java SDK, read from `java/` + - `*.scala`, `build.sbt` -> **Java** - Scala uses the Java SDK, read from `java/` + - `*.go`, `go.mod` -> **Go** - read from `go/` + - `*.rb`, `Gemfile` -> **Ruby** - read from `ruby/` + - `*.cs`, `*.csproj` -> **C#** - read from `csharp/` + - `*.php`, `composer.json` -> **PHP** - read from `php/` 2. **If multiple languages detected** (e.g., both Python and TypeScript files): @@ -99,7 +99,7 @@ When the task does involve reading or writing SDK code, determine which language ### Language-Specific Feature Support -Every SDK language above supports both the beta Tool Runner and Managed Agents (beta) — Python (`@beta_tool` decorator), TypeScript (`betaZodTool` + Zod), Java (annotated classes), Go (`BetaToolRunner` in the `toolrunner` pkg), Ruby (`BaseTool` + `tool_runner`), C# (`BetaToolRunner` + raw JSON schema), PHP (`BetaRunnableTool` + `toolRunner()`); code entry points are in the Tool Use Patterns quick reference below. cURL is raw HTTP (no SDK features) and supports Managed Agents. +Every SDK language above supports both the beta Tool Runner and Managed Agents (beta) - Python (`@beta_tool` decorator), TypeScript (`betaZodTool` + Zod), Java (annotated classes), Go (`BetaToolRunner` in the `toolrunner` pkg), Ruby (`BaseTool` + `tool_runner`), C# (`BetaToolRunner` + raw JSON schema), PHP (`BetaRunnableTool` + `toolRunner()`); code entry points are in the Tool Use Patterns quick reference below. cURL is raw HTTP (no SDK features) and supports Managed Agents. > **Managed Agents code examples**: see the reading guide in the `## Managed Agents (Beta)` section below. @@ -107,7 +107,7 @@ Every SDK language above supports both the beta Tool Runner and Managed Agents ( ## Which Surface Should I Use? -> **Start simple.** Default to the simplest tier that meets your needs. Single API calls and workflows handle most use cases — only reach for agents when the task genuinely requires open-ended, model-driven exploration. "Simplest" means the least code you own: for a hosted, scheduled, or memory-backed agent, Managed Agents is usually the simplest option (no loop code, no state files, no scheduler), even though it's a bigger platform. +> **Start simple.** Default to the simplest tier that meets your needs. Single API calls and workflows handle most use cases - only reach for agents when the task genuinely requires open-ended, model-driven exploration. "Simplest" means the least code you own: for a hosted, scheduled, or memory-backed agent, Managed Agents is usually the simplest option (no loop code, no state files, no scheduler), even though it's a bigger platform. | Use Case | Tier | Recommended Surface | Why | | ----------------------------------------------- | --------------- | ------------------------- | ------------------------------------------------------------ | @@ -118,41 +118,41 @@ Every SDK language above supports both the beta Tool Runner and Managed Agents ( | Server-managed stateful agent with workspace | Agent | **Managed Agents** | Anthropic runs the loop and hosts the tool-execution sandbox | | Persisted, versioned agent configs | Agent | **Managed Agents** | Agents are stored objects; sessions pin to a version | | Long-running multi-turn agent with file mounts | Agent | **Managed Agents** | Per-session containers, SSE event stream, Skills + MCP | -| Agent that runs on a schedule (cron, "every night") | Agent | **Managed Agents** — scheduled deployments | Deployments fire sessions autonomously; no client-side scheduler | +| Agent that runs on a schedule (cron, "every night") | Agent | **Managed Agents** - scheduled deployments | Deployments fire sessions autonomously; no client-side scheduler | -> **Note:** Managed Agents is the right choice when you want Anthropic to run the agent loop *and* host the container where tools execute — file ops, bash, code execution all run in the per-session workspace. If you want to host the compute yourself or run your own custom tool runtime, Claude API + tool use is the right choice — use the tool runner for the agentic loop — its per-turn hooks still give you approval gates, logging, error interception, and conditional execution (see `shared/tool-use-concepts.md`) — or the manual loop when you want to own the entire loop yourself. +> **Note:** Managed Agents is the right choice when you want Anthropic to run the agent loop *and* host the container where tools execute - file ops, bash, code execution all run in the per-session workspace. If you want to host the compute yourself or run your own custom tool runtime, Claude API + tool use is the right choice - use the tool runner for the agentic loop - its per-turn hooks still give you approval gates, logging, error interception, and conditional execution (see `shared/tool-use-concepts.md`) - or the manual loop when you want to own the entire loop yourself. -> **Cloud-provider access.** **Claude Platform on AWS** is Anthropic-operated with same-day API parity — see `shared/claude-platform-on-aws.md` for client setup. For per-feature availability on **Claude Platform on AWS**, **Amazon Bedrock**, **Google Vertex AI**, and **Microsoft Foundry**, see `shared/platform-availability.md` — that table is the single source of truth in this skill; do not infer availability from anywhere else. +> **Cloud-provider access.** **Claude Platform on AWS** is Anthropic-operated with same-day API parity - see `shared/claude-platform-on-aws.md` for client setup. For per-feature availability on **Claude Platform on AWS**, **Amazon Bedrock**, **Google Vertex AI**, and **Microsoft Foundry**, see `shared/platform-availability.md` - that table is the single source of truth in this skill; do not infer availability from anywhere else. ### Building an Agent: Four Approaches -Once you've decided you actually need an agent (open-ended, model-driven tool use), there are four distinct ways to build one. Two independent questions separate them: **who supplies the harness** (the agent loop + context management) and **who supplies the deployment** (the infra the agent runs on). The Tool Runner and the Claude Agent SDK both supply a *harness only* — you still host and deploy them yourself — which is why they're easy to conflate. Managed Agents (CMA) is the only option that supplies **both** the harness *and* managed deployment; the manual loop supplies neither. +Once you've decided you actually need an agent (open-ended, model-driven tool use), there are four distinct ways to build one. Two independent questions separate them: **who supplies the harness** (the agent loop + context management) and **who supplies the deployment** (the infra the agent runs on). The Tool Runner and the Claude Agent SDK both supply a *harness only* - you still host and deploy them yourself - which is why they're easy to conflate. Managed Agents (CMA) is the only option that supplies **both** the harness *and* managed deployment; the manual loop supplies neither. | # | Approach | You write | Harness & deployment | Tools available | Use when | |---|----------|-----------|----------------------|-----------------|----------| -| 1 | **Claude API — manual loop** | The `while stop_reason == "tool_use"` loop yourself | You build the harness; you host | Only tools you define | You want to own the *entire* loop — no beta dependency, or a control flow the Tool Runner's per-turn hooks don't fit | -| 2 | **Claude API — Tool Runner** (`client.beta.messages.tool_runner` + `@beta_tool` / `betaZodTool`) | Just the tool functions | SDK supplies the loop (**harness only**); you host | Only tools you define | A custom-tool agent without hand-writing the loop (most cases). Per-turn hooks still give you approval gates, error interception, result modification (e.g. `cache_control`), retries, streaming, and compaction | +| 1 | **Claude API - manual loop** | The `while stop_reason == "tool_use"` loop yourself | You build the harness; you host | Only tools you define | You want to own the *entire* loop - no beta dependency, or a control flow the Tool Runner's per-turn hooks don't fit | +| 2 | **Claude API - Tool Runner** (`client.beta.messages.tool_runner` + `@beta_tool` / `betaZodTool`) | Just the tool functions | SDK supplies the loop (**harness only**); you host | Only tools you define | A custom-tool agent without hand-writing the loop (most cases). Per-turn hooks still give you approval gates, error interception, result modification (e.g. `cache_control`), retries, streaming, and compaction | | 3 | **Managed Agents** (REST, beta) | Agent config + your tool results | Anthropic supplies the harness **and** hosts a per-session sandbox (**harness + deployment**) | Anthropic-hosted sandbox (bash, files, code exec) + Skills/MCP + your tools | You want Anthropic to run the loop *and* host the per-session workspace; persisted/versioned configs; long-running sessions | -| 4 | **Claude Agent SDK** — *separate product* (`claude-agent-sdk` / `@anthropic-ai/claude-agent-sdk`) | A prompt + options | SDK supplies the Claude Code harness + built-in tools (**harness only**); you host | Built-in Read/Write/Edit/Bash/Glob/Grep/WebSearch/WebFetch + MCP + subagents | You want a batteries-included coding/filesystem agent running on your own infra | +| 4 | **Claude Agent SDK** - *separate product* (`claude-agent-sdk` / `@anthropic-ai/claude-agent-sdk`) | A prompt + options | SDK supplies the Claude Code harness + built-in tools (**harness only**); you host | Built-in Read/Write/Edit/Bash/Glob/Grep/WebSearch/WebFetch + MCP + subagents | You want a batteries-included coding/filesystem agent running on your own infra | -The harness/deployment split is the key mental model: options 1, 2, and 4 all **leave deployment to you**; only option 3 (CMA) adds managed deployment. Options 1–3 are what this skill generates; option 4 is a different library with its own docs — see the disambiguation below. +The harness/deployment split is the key mental model: options 1, 2, and 4 all **leave deployment to you**; only option 3 (CMA) adds managed deployment. Options 1-3 are what this skill generates; option 4 is a different library with its own docs - see the disambiguation below. -> **Tool Runner ≠ Claude Agent SDK.** These sound alike but are different packages: -> - **Tool Runner** is part of the regular Anthropic API SDK (`anthropic` / `@anthropic-ai/sdk`), reached via `client.beta.messages.tool_runner`. It automates the request → execute → loop cycle *for tools you define*. No built-in tools, no filesystem access, no sandbox — you supply every tool and host the compute. It is option 2 above, a thin helper over `POST /v1/messages`. +> **Tool Runner != Claude Agent SDK.** These sound alike but are different packages: +> - **Tool Runner** is part of the regular Anthropic API SDK (`anthropic` / `@anthropic-ai/sdk`), reached via `client.beta.messages.tool_runner`. It automates the request -> execute -> loop cycle *for tools you define*. No built-in tools, no filesystem access, no sandbox - you supply every tool and host the compute. It is option 2 above, a thin helper over `POST /v1/messages`. > - **Claude Agent SDK** (`claude-agent-sdk` / `@anthropic-ai/claude-agent-sdk`) is Claude Code packaged as a library. It ships built-in tools (file read/write/edit, bash, grep, web search), the full agent loop, context management, hooks, subagents, permissions, and sessions. You call `query(prompt, options)` and it drives everything. > -> Both are **harness-only — you host and deploy them.** The difference is scope of harness: the Tool Runner loops over tools *you* define (with per-turn hooks for approval, interception, result modification, and retries — but no built-in tools); the Agent SDK is the full Claude Code harness with built-in tools. Neither provides managed deployment — that's what **Managed Agents (CMA)** adds (Anthropic hosts the loop and a per-session sandbox). +> Both are **harness-only - you host and deploy them.** The difference is scope of harness: the Tool Runner loops over tools *you* define (with per-turn hooks for approval, interception, result modification, and retries - but no built-in tools); the Agent SDK is the full Claude Code harness with built-in tools. Neither provides managed deployment - that's what **Managed Agents (CMA)** adds (Anthropic hosts the loop and a per-session sandbox). > -> **This skill covers the Claude API and Managed Agents (options 1–3); it does not generate Claude Agent SDK code.** If the user actually wants the Claude Agent SDK, point them to its docs (`code.claude.com/docs/en/agent-sdk`) — don't substitute the API Tool Runner for it, or vice-versa. +> **This skill covers the Claude API and Managed Agents (options 1-3); it does not generate Claude Agent SDK code.** If the user actually wants the Claude Agent SDK, point them to its docs (`code.claude.com/docs/en/agent-sdk`) - don't substitute the API Tool Runner for it, or vice-versa. ### Should I Build an Agent? Before choosing the agent tier, check all four criteria: -- **Complexity** — Is the task multi-step and hard to fully specify in advance? (e.g., "turn this design doc into a PR" vs. "extract the title from this PDF") -- **Value** — Does the outcome justify higher cost and latency? -- **Viability** — Is Claude capable at this task type? -- **Cost of error** — Can errors be caught and recovered from? (tests, review, rollback) +- **Complexity** - Is the task multi-step and hard to fully specify in advance? (e.g., "turn this design doc into a PR" vs. "extract the title from this PDF") +- **Value** - Does the outcome justify higher cost and latency? +- **Viability** - Is Claude capable at this task type? +- **Cost of error** - Can errors be caught and recovered from? (tests, review, rollback) If the answer is "no" to any of these, stay at a simpler tier (single call or workflow). @@ -160,15 +160,15 @@ If the answer is "no" to any of these, stay at a simpler tier (single call or wo ## Architecture -Everything goes through `POST /v1/messages`. Tools and output constraints are features of this single endpoint — not separate APIs. +Everything goes through `POST /v1/messages`. Tools and output constraints are features of this single endpoint - not separate APIs. -**User-defined tools** — You define tools (via decorators, Zod schemas, or raw JSON), and the SDK's tool runner handles calling the API, executing your functions, and looping until Claude is done. For full control, you can write the loop manually. +**User-defined tools** - You define tools (via decorators, Zod schemas, or raw JSON), and the SDK's tool runner handles calling the API, executing your functions, and looping until Claude is done. For full control, you can write the loop manually. -**Server-side tools** — Anthropic-hosted tools that run on Anthropic's infrastructure. Code execution is fully server-side (declare it in `tools`, Claude runs code automatically). Computer use can be server-hosted or self-hosted. +**Server-side tools** - Anthropic-hosted tools that run on Anthropic's infrastructure. Code execution is fully server-side (declare it in `tools`, Claude runs code automatically). Computer use can be server-hosted or self-hosted. -**Structured outputs** — Constrains the Messages API response format (`output_config.format`) and/or tool parameter validation (`strict: true`). The recommended approach is `client.messages.parse()` which validates responses against your schema automatically. Note: the old `output_format` parameter is deprecated; use `output_config: {format: {...}}` on `messages.create()`. +**Structured outputs** - Constrains the Messages API response format (`output_config.format`) and/or tool parameter validation (`strict: true`). The recommended approach is `client.messages.parse()` which validates responses against your schema automatically. Note: the old `output_format` parameter is deprecated; use `output_config: {format: {...}}` on `messages.create()`. -**Supporting endpoints** — Batches (`POST /v1/messages/batches`), Files (`POST /v1/files`), Token Counting (`POST /v1/messages/count_tokens` — see `shared/token-counting.md`), and Models (`GET /v1/models`, `GET /v1/models/{id}` — live capability/context-window discovery) feed into or support Messages API requests. +**Supporting endpoints** - Batches (`POST /v1/messages/batches`), Files (`POST /v1/files`), Token Counting (`POST /v1/messages/count_tokens` - see `shared/token-counting.md`), and Models (`GET /v1/models`, `GET /v1/models/{id}` - live capability/context-window discovery) feed into or support Messages API requests. --- @@ -176,48 +176,50 @@ Everything goes through `POST /v1/messages`. Tools and output constraints are fe | Model | Model ID | Context | Input $/1M | Output $/1M | | ----------------- | ------------------- | -------------- | ---------- | ----------- | -| Claude Fable 5 | `claude-fable-5` | 1M | $10.00 | $50.00 | -| Claude Mythos 5 (Project Glasswing only) | `claude-mythos-5` | 1M | $10.00 | $50.00 | +| Claude Fable 5.1 | `claude-fable-5-1` | 1M | $10.00 | $50.00 | +| Claude Mythos 5.1 (Project Glasswing only) | `claude-mythos-5-1` | 1M | $10.00 | $50.00 | +| Claude Fable 5 | `claude-fable-5` | 1M | $10.00 | $50.00 | | Claude Opus 5 | `claude-opus-5` | 1M | $5.00 | $25.00 | | Claude Opus 4.8 | `claude-opus-4-8` | 1M | $5.00 | $25.00 | | Claude Opus 4.7 | `claude-opus-4-7` | 1M | $5.00 | $25.00 | | Claude Opus 4.6 | `claude-opus-4-6` | 1M | $5.00 | $25.00 | -| Claude Sonnet 5 | `claude-sonnet-5` | 1M | $3.00 ($2.00 intro through 2026-08-31) | $15.00 ($10.00 intro) | +| Claude Sonnet 5 | `claude-sonnet-5` | 1M | $2.00 | $10.00 | | Claude Sonnet 4.6 | `claude-sonnet-4-6` | 1M | $3.00 | $15.00 | | Claude Haiku 4.5 | `claude-haiku-4-5` | 200K | $1.00 | $5.00 | -**Partner pricing:** The prices above are Anthropic first-party API rates — they also apply to Claude on Microsoft Foundry, which is billed through the Microsoft Marketplace at standard API rates. Claude on Amazon Bedrock and Vertex AI is partner-operated with separate pricing — see [Bedrock](https://aws.amazon.com/bedrock/pricing/) or [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/pricing#claude-models). For WebFetch, use the Pricing row in `shared/live-sources.md`. +**Partner pricing:** The prices above are Anthropic first-party API rates - they also apply to Claude on Microsoft Foundry, which is billed through the Microsoft Marketplace at standard API rates. Claude on Amazon Bedrock and Vertex AI is partner-operated with separate pricing - see [Bedrock](https://aws.amazon.com/bedrock/pricing/) or [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/pricing#claude-models). For WebFetch, use the Pricing row in `shared/live-sources.md`. -**ALWAYS use `claude-opus-5` unless the user explicitly names a different model.** This is non-negotiable. Do not use `claude-sonnet-5`, `claude-sonnet-4-6`, or any other model unless the user literally says "use sonnet" or "use haiku". Never downgrade for cost — that's the user's decision, not yours. Use `claude-fable-5` only when the user explicitly asks for Claude Fable 5, "fable", or Anthropic's most capable model — it has different API behavior than the Opus family (see below) and pricing that exceeds Opus-tier. **Use only the exact model ID strings from the table — they are complete as-is; never append date suffixes** (`claude-sonnet-4-6`, never `claude-sonnet-4-6-20251114` or any other date-suffixed variant you might recall from training data). If the user requests an older model not in the table (e.g., "opus 4.5", "sonnet 3.7"), read `shared/models.md` for the exact ID — do not construct one yourself. +**ALWAYS use `claude-opus-5` unless the user explicitly names a different model.** This is non-negotiable. Do not use `claude-sonnet-5`, `claude-sonnet-4-6`, or any other model unless the user literally says "use sonnet" or "use haiku". Never downgrade for cost - that's the user's decision, not yours. Use `claude-fable-5-1` only when the user explicitly asks for Claude Fable 5.1, "fable", or Anthropic's most capable model - it has different API behavior than the Opus family (see below) and pricing that exceeds Opus-tier. **Use only the exact model ID strings from the table - they are complete as-is; never append date suffixes** (`claude-sonnet-4-6`, never `claude-sonnet-4-6-20251114` or any other date-suffixed variant you might recall from training data). If the user requests an older model not in the table (e.g., "opus 4.5", "sonnet 3.7"), read `shared/models.md` for the exact ID - do not construct one yourself. -### Claude Fable 5 (`claude-fable-5`) — most capable widely released model +### Claude Fable 5.1 (`claude-fable-5-1`) - most capable widely released model -Claude Fable 5 is Anthropic's most capable widely released model, for the most demanding reasoning and long-horizon agentic work; everything below also applies to **Claude Mythos 5** (`claude-mythos-5`, Project Glasswing — same capabilities, pricing, and API surface; successor to the invitation-only `claude-mythos-preview`). 1M context window (the maximum is also the default), 128K max output. Key API differences from Opus-tier — see `shared/model-migration.md` → Migrating to Claude Fable 5 for details: +Claude Fable 5.1 is Anthropic's most capable widely released model, for the most demanding reasoning and long-horizon agentic work; everything below also applies to **Claude Mythos 5.1** (`claude-mythos-5-1`, Project Glasswing - same capabilities, pricing, and API surface; it runs safeguards that depend on the access program, so the `refusal` handling below applies there too; successor to Claude Mythos 5, which ran no safety classifiers). 1M context window (the maximum is also the default), 128K max output. Key API differences from Opus-tier - see `shared/model-migration.md` -> Migrating to Claude Fable 5.1 for details: -- **Thinking is always on** — omit the `thinking` parameter entirely (or send `{type: "adaptive"}`). Any other explicit configuration is rejected: `{type: "disabled"}` and `{type: "enabled", budget_tokens: N}` both return a 400. Control depth with `output_config.effort` (supports `low` through `xhigh` and `max`). -- **The raw chain of thought is never returned** — responses carry regular `thinking` blocks (not `redacted_thinking`): `display: "summarized"` returns a readable summary, `"omitted"` (the default) leaves the `thinking` field as an empty string. Replay rules: pass thinking blocks back unchanged on the same model; other models drop them silently (unbilled — nothing to strip); details in `shared/model-migration.md`. -- **Tokenizer** — same tokenizer as Opus 4.8 (introduced with Opus 4.7). Token counts are roughly unchanged when migrating from Opus 4.7/4.8; per-token pricing differs. Coming from Opus 4.6, Sonnet, Haiku, or older, re-baseline with `count_tokens` (the Opus 4.7 tokenizer uses ~1×–1.35× as many tokens). -- **`refusal` stop reason — handle it, and opt into fallbacks by default** — safety classifiers may decline a request (HTTP 200, `stop_reason: "refusal"`, with a `stop_details` category); always check `stop_reason` before reading `content`. **When you write `claude-fable-5` or `claude-opus-5` code, include the server-side `fallbacks` parameter by default.** Simplest form: `betas: ["server-side-fallback-2026-07-01"]` + `fallbacks: "default"`, which routes by refusal category so you never maintain a model list. (The older array form — `betas: ["server-side-fallback-2026-06-01"]` + `fallbacks: [{"model": "claude-opus-4-8"}]` — still works; Claude API and Claude Platform on AWS — on Bedrock, Vertex and Foundry, use the SDKs' client-side `BetaRefusalFallbackMiddleware` + `BetaFallbackState` instead). Tell the user you've enabled it; drop it only if they decline. Full semantics (billing, mid-stream refusals, credit repricing) in `shared/model-migration.md` → refusal section. **Per-language code examples in `{lang}/claude-api/README.md` § Refusal Fallbacks cover the array form only** — for the `"default"` mode, follow the raw-HTTP shape in `shared/model-migration.md` → Migrating to Claude Opus 5 → New API features and swap `fallbacks: [{...}]` for `fallbacks: "default"` plus the `-2026-07-01` header; the rest of the request is unchanged. -- **No assistant prefill** — same as the rest of the 4.6+ family. -- **30-day data retention required** — Claude Fable 5 is not available under zero data retention; requests from an org whose retention configuration doesn't meet the requirement return `400 invalid_request_error`. -- **Longer turns, different prompting** — single requests on hard tasks can run many minutes (plan timeouts/streaming/progress UX); effort sweeps should include low/medium for routine work; prompts written for prior models are often too prescriptive and reduce output quality. See `shared/model-migration.md` → Migrating to Claude Fable 5 → Behavioral shifts (prompt-tunable) for the recommended prompt snippets. +- **Thinking is always on** - omit the `thinking` parameter entirely (or send `{type: "adaptive"}`). Any other explicit configuration is rejected: `{type: "disabled"}` and `{type: "enabled", budget_tokens: N}` both return a 400. Control depth with `output_config.effort` (supports `low` through `xhigh` and `max`). +- **The raw chain of thought is never returned** - responses carry regular `thinking` blocks (not `redacted_thinking`): `display: "summarized"` returns a readable summary, `"omitted"` (the default) leaves the `thinking` field as an empty string. Replay rules: pass thinking blocks back unchanged on the same model; other models drop them silently (unbilled - nothing to strip; Claude Mythos 5.1 instead reads them); details in `shared/model-migration.md`. +- **Tokenizer** - same tokenizer as Opus 4.8 (introduced with Opus 4.7). Token counts are roughly unchanged when migrating from Opus 4.7/4.8; per-token pricing differs. Coming from Opus 4.6, Sonnet, Haiku, or older, re-baseline with `count_tokens` (the Opus 4.7 tokenizer uses ~1×-1.35× as many tokens). +- **`refusal` stop reason - handle it, and opt into fallbacks by default** - safety classifiers may decline a request (HTTP 200, `stop_reason: "refusal"`, with a `stop_details` category); always check `stop_reason` before reading `content`. **When you write `claude-fable-5-1` or `claude-opus-5` code, include the server-side `fallbacks` parameter by default.** Simplest form: `betas: ["server-side-fallback-2026-07-01"]` + `fallbacks: "default"`, which routes by refusal category so you never maintain a model list. (The older array form - `betas: ["server-side-fallback-2026-06-01"]` + `fallbacks: [{"model": "claude-opus-4-8"}]` - still works; Claude API and Claude Platform on AWS - on Bedrock, Vertex and Foundry, use the SDKs' client-side `BetaRefusalFallbackMiddleware` + `BetaFallbackState`). Tell the user you've enabled it; drop it only if they decline. Full semantics (billing, mid-stream refusals, credit repricing) in `shared/model-migration.md` -> refusal section. **Per-language code examples in `{lang}/claude-api/README.md` § Refusal Fallbacks cover the array form only** - for the `"default"` mode, follow the raw-HTTP shape in `shared/model-migration.md` -> Migrating to Claude Opus 5 -> New API features and swap `fallbacks: [{...}]` for `fallbacks: "default"` plus the `-2026-07-01` header; the rest of the request is unchanged. +- **No assistant prefill** - same as the rest of the 4.6+ family. +- **30-day data retention required** - Claude Fable 5.1 is not available under zero data retention unless expressly authorized by Anthropic; requests from an org whose retention configuration doesn't meet the requirement return `400 invalid_request_error`. +- **Longer turns, different prompting** - single requests on hard tasks can run many minutes (plan timeouts/streaming/progress UX); effort sweeps should include low/medium for routine work; prompts written for prior models are often too prescriptive and reduce output quality. See `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> Behavioral shifts (prompt-tunable) for the recommended prompt snippets. +- **Successor to Claude Fable 5 (`claude-fable-5`, still served) in the same tier at the same per-token price.** Same surface as Claude Fable 5 with three breaking changes - forced tool use (`tool_choice` `any` / `tool`) returns a 400 (use `auto` + a prompt instruction, `strict: true` for schema-valid arguments, or structured outputs); thinking blocks are bound to the producing model (other models drop them, unbilled); and editing earlier turns invalidates thinking blocks ("preserved thinking"; new accounts created on/after 2026-08-31 get a 400 on edited history; later models enforce it for everyone - make every harness append-only and run the three-step check; the opt-in controls are per-platform, see `shared/platform-availability.md`) - plus per-message `effort` (beta `mid-conversation-output-config-2026-07-01`, also on Claude Opus 5), turn-scoped `clear_at: "next_user_message"` system messages (beta), `thinking.display: "updates"` progress notes (beta, all platforms), cache reads at $0.25/MTok (whether Claude Mythos 5.1 shares that rate is open at launch), and content provenance. Covered Model - ZDR orgs get `400 invalid_request_error` as on Claude Fable 5 (ZDR only if expressly authorized by Anthropic); no Priority Tier. Same tokenizer as Claude Fable 5. See `shared/model-migration.md` -> Migrating to Claude Fable 5.1 from Claude Fable 5. -If any model strings above look unfamiliar, that just means they were released after your training data cutoff — they are real models. +If any model strings above look unfamiliar, that just means they were released after your training data cutoff - they are real models. -**Live capability lookup:** The table above is cached. When the user asks "what's the context window for X", "does X support vision/thinking/effort", or "which models support Y", query the Models API (`client.models.retrieve(id)` / `client.models.list()`) — see `shared/models.md` for the field reference and capability-filter examples. +**Live capability lookup:** The table above is cached. When the user asks "what's the context window for X", "does X support vision/thinking/effort", or "which models support Y", query the Models API (`client.models.retrieve(id)` / `client.models.list()`) - see `shared/models.md` for the field reference and capability-filter examples. --- ## Authentication (Quick Reference) -**An unset `ANTHROPIC_API_KEY` does NOT mean there are no credentials.** The SDKs and the `ant` CLI resolve credentials in this order (first match wins): `ANTHROPIC_API_KEY` → `ANTHROPIC_AUTH_TOKEN` → the `ANTHROPIC_PROFILE`-selected or active OAuth profile from `ant auth login` → Workload Identity Federation env vars → the default profile on disk. A bare `Anthropic()` / `new Anthropic()` / `anthropic.NewClient()` works after `ant auth login` with no env var set. +**An unset `ANTHROPIC_API_KEY` does NOT mean there are no credentials.** The SDKs and the `ant` CLI resolve credentials in this order (first match wins): `ANTHROPIC_API_KEY` -> `ANTHROPIC_AUTH_TOKEN` -> the `ANTHROPIC_PROFILE`-selected or active OAuth profile from `ant auth login` -> Workload Identity Federation env vars -> the default profile on disk. A bare `Anthropic()` / `new Anthropic()` / `anthropic.NewClient()` works after `ant auth login` with no env var set. -**When you need to call the API and `ANTHROPIC_API_KEY` is unset, don't ask the user for a key.** First run `ant auth status` — it shows which credential source and profile is active. If it reports an active profile: +**When you need to call the API and `ANTHROPIC_API_KEY` is unset, don't ask the user for a key.** First run `ant auth status` - it shows which credential source and profile is active. If it reports an active profile: -- **SDK code or `ant` CLI:** just run it. The zero-arg client constructor and every `ant …` subcommand pick up the profile automatically — no env var needed. -- **Raw `curl` / HTTP:** get a short-lived token with `ant auth print-credentials --access-token` and send it as `Authorization: Bearer ` **plus** the header `anthropic-beta: oauth-2025-04-20` (OAuth tokens go on `Authorization: Bearer`, not `x-api-key:` — converting a curl from an API key is a header change, not a key swap). Always pass `--access-token`; the no-flag form prints JSON, not a bare token. +- **SDK code or `ant` CLI:** just run it. The zero-arg client constructor and every `ant ...` subcommand pick up the profile automatically - no env var needed. +- **Raw `curl` / HTTP:** get a short-lived token with `ant auth print-credentials --access-token` and send it as `Authorization: Bearer ` **plus** the header `anthropic-beta: oauth-2025-04-20` (OAuth tokens go on `Authorization: Bearer`, not `x-api-key:` - converting a curl from an API key is a header change, not a key swap). Always pass `--access-token`; the no-flag form prints JSON, not a bare token. -Only ask the user for a key if `ant auth status` reports no active credential source (or `ant` itself isn't installed). Suggest `ant auth login` as the first option — it stores a profile under `~/.config/anthropic/` that the SDKs read automatically — and an exported `ANTHROPIC_API_KEY` as the alternative. +Only ask the user for a key if `ant auth status` reports no active credential source (or `ant` itself isn't installed). Suggest `ant auth login` as the first option - it stores a profile under `~/.config/anthropic/` that the SDKs read automatically - and an exported `ANTHROPIC_API_KEY` as the alternative. Full auth details (named profiles, scopes, the API-key-shadows-profile trap, refresh-token expiry): `shared/anthropic-cli.md`. @@ -225,30 +227,31 @@ Full auth details (named profiles, scopes, the API-key-shadows-profile trap, ref ## Thinking & Effort (Quick Reference) -Use adaptive thinking (`thinking: {type: "adaptive"}`) on every current model — Claude dynamically decides when and how much to think. Per-model rules: +Use adaptive thinking (`thinking: {type: "adaptive"}`) on every current model - Claude dynamically decides when and how much to think. Per-model rules: | Model | Thinking config | Omitting `thinking` | `budget_tokens` | Sampling (`temperature`/`top_p`/`top_k`) | Effort levels | |---|---|---|---|---|---| -| Fable 5 | `{type: "adaptive"}` or omit; explicit `{type: "disabled"}` returns 400 — omit the param instead | Runs adaptive (thinking is always on) | Removed — `{type: "enabled", budget_tokens: N}` returns 400 | Removed — 400 | `low`/`medium`/`high`/`xhigh`/`max` | -| Claude Opus 5 | `{type: "adaptive"}` or omit; `{type: "disabled"}` accepted **only at effort `high` or below** — 400 at `xhigh`/`max`, and see the disabled-thinking pitfall below | Runs **adaptive** (thinking is on by default — unlike Opus 4.8/4.7) | Removed — 400 | Removed — 400 | `low`–`max` (all five) | -| Opus 4.8 / 4.7 | `{type: "adaptive"}` is the only on-mode; `{type: "disabled"}` accepted | Runs **without** thinking — set `{type: "adaptive"}` explicitly | Removed — 400 | Removed — 400 | `low`/`medium`/`high`/`xhigh`/`max` | -| Sonnet 5 | `{type: "adaptive"}` is the only on-mode; `{type: "disabled"}` accepted | Runs adaptive | Removed — 400 | Removed — 400 | `low`/`medium`/`high`/`xhigh`/`max` | -| Opus 4.6 / Sonnet 4.6 | `{type: "adaptive"}` (recommended; auto-enables interleaved thinking, no beta header) | Set `{type: "adaptive"}` explicitly | Deprecated — do not use in new code; transitional escape hatch only (see below) | Allowed | `low`/`medium`/`high`/`max` (`xhigh` arrived with Opus 4.7) | -| Older (Sonnet 4.5, Haiku 4.5, …) — only if explicitly requested | `{type: "enabled", budget_tokens: N}` | No thinking | Required for thinking; must be less than `max_tokens`, minimum 1024 — errors otherwise | Allowed | `effort` works on Opus 4.5 (`low`/`medium`/`high` only — no `xhigh`/`max`); errors on Sonnet 4.5 / Haiku 4.5 | +| Fable 5 / Claude Fable 5.1 (and the Mythos counterparts) | `{type: "adaptive"}` or omit; explicit `{type: "disabled"}` returns 400 - omit the param instead (Claude Fable 5.1 / Claude Mythos 5.1 also 400 on forced `tool_choice` `any`/`tool`, and run preserved thinking's history-editing check on replayed thinking blocks) | Runs adaptive (thinking is always on) | Removed - `{type: "enabled", budget_tokens: N}` returns 400 | Removed - 400 | `low`/`medium`/`high`/`xhigh`/`max` | +| Claude Opus 5 | `{type: "adaptive"}` or omit; `{type: "disabled"}` accepted **only at effort `high` or below** - 400 at `xhigh`/`max`, and see the disabled-thinking pitfall below | Runs **adaptive** (thinking is on by default - unlike Opus 4.8/4.7) | Removed - 400 | Removed - 400 | `low`-`max` (all five) | +| Opus 4.8 / 4.7 | `{type: "adaptive"}` is the only on-mode; `{type: "disabled"}` accepted | Runs **without** thinking - set `{type: "adaptive"}` explicitly | Removed - 400 | Removed - 400 | `low`/`medium`/`high`/`xhigh`/`max` | +| Sonnet 5 | `{type: "adaptive"}` is the only on-mode; `{type: "disabled"}` accepted | Runs adaptive | Removed - 400 | Removed - 400 | `low`/`medium`/`high`/`xhigh`/`max` | +| Opus 4.6 / Sonnet 4.6 | `{type: "adaptive"}` (recommended; auto-enables interleaved thinking, no beta header) | Set `{type: "adaptive"}` explicitly | Deprecated - do not use in new code; transitional escape hatch only (see below) | Allowed | `low`/`medium`/`high`/`max` (`xhigh` arrived with Opus 4.7) | +| Older (Sonnet 4.5, Haiku 4.5, ...) - only if explicitly requested | `{type: "enabled", budget_tokens: N}` | No thinking | Required for thinking; must be less than `max_tokens`, minimum 1024 - errors otherwise | Allowed | `effort` works on Opus 4.5 (`low`/`medium`/`high` only - no `xhigh`/`max`); errors on Sonnet 4.5 / Haiku 4.5 | -Opus 4.8 keeps the same request surface as 4.7 (no new breaking changes) — see `shared/model-migration.md` → Migrating to Opus 4.8 for the behavioral re-tuning, and → Migrating to Opus 4.7 for the full breaking-change list when coming from 4.6 or earlier. With `thinking` disabled, Opus 4.8 may write longer reasoning into the visible response — leave adaptive thinking on, or add a final-answer-only instruction (see the migration guide). +Opus 4.8 keeps the same request surface as 4.7 (no new breaking changes) - see `shared/model-migration.md` -> Migrating to Opus 4.8 for the behavioral re-tuning, and -> Migrating to Opus 4.7 for the full breaking-change list when coming from 4.6 or earlier. With `thinking` disabled, Opus 4.8 may write longer reasoning into the visible response - leave adaptive thinking on, or add a final-answer-only instruction (see the migration guide). -- **Effort (GA, no beta header):** `output_config: {effort: "low"|"medium"|"high"|"xhigh"|"max"}` — inside `output_config`, not top-level; default `high` (equivalent to omitting it). Controls thinking depth and overall token spend; combine with adaptive thinking for the best cost-quality tradeoffs. `xhigh` (added on Opus 4.7, between `high` and `max`) is the best setting for most coding and agentic use cases on Fable 5 / Opus 4.7/4.8 / Sonnet 5, and the default in Claude Code; effort matters more on those models than on any prior model in their tier — re-tune it when migrating, and run long-horizon/agentic tasks at `high`/`xhigh` with the full task spec given up front. Use a minimum of `high` for intelligence-sensitive work, `max` when correctness matters more than cost, and `low` for subagents or simple tasks — lower effort means fewer and more-consolidated tool calls, less preamble, and terser confirmations (`high` is often the sweet spot balancing quality and token efficiency). -- **Thinking display — `"omitted"` by default on Fable 5 / Mythos 5 / Opus 5 / 4.8 / 4.7 / Sonnet 5:** `display: "summarized"` returns a readable summary of the reasoning; `"omitted"` (the default on all six — a silent change from Opus 4.6 and Sonnet 4.6, where it was `"summarized"`) streams `thinking` blocks with empty text. `display` controls visibility only — thinking happens and is billed the same under every setting; the raw chain of thought is never exposed on any model. If you stream reasoning to users, the default looks like a long pause before output — set `thinking: {type: "adaptive", display: "summarized"}` explicitly. (Independent of display, echo thinking blocks back unchanged when continuing on the same model; other models silently ignore them — see the migration guide.) -- **When the user asks for "extended thinking", a "thinking budget", or `budget_tokens`:** always use Fable 5, Opus 5, 4.8, 4.7, or 4.6 with `thinking: {type: "adaptive"}` — the fixed thinking-token-budget concept is deprecated and adaptive thinking replaces it. Do NOT use `budget_tokens` for new 4.6/4.7/4.8 code and do NOT switch to an older model just because the user mentions it. *Gradual-migration carve-out:* `budget_tokens` is still functional on Opus 4.6 and Sonnet 4.6 only, as a transitional escape hatch for existing code that needs a hard token ceiling before you've tuned `effort` — see `shared/model-migration.md` → Transitional escape hatch. It is fully removed on Fable 5, Opus 5/4.7/4.8, and Sonnet 5. +- **Effort (GA, no beta header):** `output_config: {effort: "low"|"medium"|"high"|"xhigh"|"max"}` - inside `output_config`, not top-level; default `high` (equivalent to omitting it). Controls thinking depth and overall token spend; combine with adaptive thinking for the best cost-quality tradeoffs. `xhigh` (added on Opus 4.7, between `high` and `max`) is the best setting for most coding and agentic use cases on Fable 5 / Opus 4.7/4.8 / Sonnet 5, and the default in Claude Code; effort matters more on those models than on any prior model in their tier - re-tune it when migrating, and run long-horizon/agentic tasks at `high`/`xhigh` with the full task spec given up front. Use a minimum of `high` for intelligence-sensitive work, `max` when correctness matters more than cost, and `low` for subagents or simple tasks - lower effort means fewer and more-consolidated tool calls, less preamble, and terser confirmations (`high` is often the sweet spot balancing quality and token efficiency). +- **Choosing an effort level (cost tuning):** Effort is the first quality-trading lever, after the free wins (caching first) - it trades thoroughness against token spend within one model, and the top of the range earns its cost only on hard problems (raise to `max` only when measurement shows headroom at the level below). Which workloads repay higher effort is a property of the workload: coding and long-horizon agentic work respond strongly; chat, classification, and high-volume or latency-sensitive routes often don't and do well at `low`, with `medium` as the cost-saving step-down where quality holds (the per-level defaults above cover the rest). Measure on a sample of real requests before raising a default, and tune per route rather than globally. Before building a multi-model cost cascade, measure the simpler alternative first - the most capable model at lower effort on the same tasks: lower effort on the newest models often matches or exceeds prior-generation performance at high effort (on Fable 5, lower effort often exceeds `xhigh` on prior models), and one model means one cache namespace (caches are model-scoped, so a cascade forfeits cache reuse across its models; a mid-conversation top-level `effort` change still invalidates the messages cache, though the per-message effort system message avoids that on Claude Fable 5.1 / Claude Mythos 5.1 / Claude Opus 5 - `shared/prompt-caching.md` § Invalidation hierarchy). Judge cost per completed task, not per request - a cheaper request that needs more turns or retries to finish the job isn't cheaper. For the measured effort/cost tradeoffs by workload and the full lever order, `shared/cost-optimization.md` § 2.6. +- **Thinking display - `"omitted"` by default on Fable 5 / Claude Fable 5.1 / Mythos 5 / Claude Mythos 5.1 / Opus 5 / 4.8 / 4.7 / Sonnet 5:** `display: "summarized"` returns a readable summary of the reasoning; `"omitted"` (the default on all eight - a silent change from Opus 4.6 and Sonnet 4.6, where it was `"summarized"`) streams `thinking` blocks with empty text. `display` controls visibility only - thinking happens and is billed the same under every setting; the raw chain of thought is never exposed on any model. If you stream reasoning to users, the default looks like a long pause before output - set `thinking: {type: "adaptive", display: "summarized"}` explicitly. (Independent of display, echo thinking blocks back unchanged when continuing on the same model; other models silently ignore them (Claude Fable 5.1 / Claude Mythos 5.1 read them) - see the migration guide.) On Claude Fable 5.1 / Claude Mythos 5.1 / Claude Fable 5, `display: "updates"` (beta `thinking-display-updates-2026-08-18`, every platform) hides reasoning like `"omitted"` but returns the model's between-tool-call progress notes as short `thinking` block summaries - see `shared/model-migration.md` -> Migrating to Claude Fable 5.1 from Claude Fable 5 -> New API features. +- **When the user asks for "extended thinking", a "thinking budget", or `budget_tokens`:** always use Fable 5/5.1, Opus 5, 4.8, 4.7, or 4.6 with `thinking: {type: "adaptive"}` - the fixed thinking-token-budget concept is deprecated and adaptive thinking replaces it. Do NOT use `budget_tokens` for new 4.6/4.7/4.8 code and do NOT switch to an older model just because the user mentions it. *Gradual-migration carve-out:* `budget_tokens` is still functional on Opus 4.6 and Sonnet 4.6 only, as a transitional escape hatch for existing code that needs a hard token ceiling before you've tuned `effort` - see `shared/model-migration.md` -> Transitional escape hatch. It is fully removed on Fable 5/5.1, Opus 5/4.7/4.8, and Sonnet 5. --- ## Compaction (Quick Reference) -**Beta, Fable 5, Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 5, and Sonnet 4.6.** For long-running conversations that may exceed the 1M context window, enable server-side compaction. The API automatically summarizes earlier context when it approaches the trigger threshold (default: 150K tokens). Requires beta header `compact-2026-01-12`. +**Beta, Fable 5/5.1, Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 5, and Sonnet 4.6.** For long-running conversations that may exceed the 1M context window, enable server-side compaction. The API automatically summarizes earlier context when it approaches the trigger threshold (default: 150K tokens). Requires beta header `compact-2026-01-12`. -**Critical:** Append `response.content` (not just the text) back to your messages on every turn. Compaction blocks in the response must be preserved — the API uses them to replace the compacted history on the next request. Extracting only the text string and appending that will silently lose the compaction state. +**Critical:** Append `response.content` (not just the text) back to your messages on every turn. Compaction blocks in the response must be preserved - the API uses them to replace the compacted history on the next request. Extracting only the text string and appending that will silently lose the compaction state. See `{lang}/claude-api/README.md` (Compaction section) for code examples. Full docs via WebFetch in `shared/live-sources.md`. @@ -256,13 +259,13 @@ See `{lang}/claude-api/README.md` (Compaction section) for code examples. Full d ## Prompt Caching (Quick Reference) -**Prefix match.** Any byte change anywhere in the prefix invalidates everything after it. Render order is `tools` → `system` → `messages`. Keep stable content first (frozen system prompt, deterministic tool list), put volatile content (timestamps, per-request IDs, varying questions) after the last `cache_control` breakpoint. +**Prefix match.** Any byte change anywhere in the prefix invalidates everything after it. Render order is `tools` -> `system` -> `messages`. Keep stable content first (frozen system prompt, deterministic tool list), put volatile content (timestamps, per-request IDs, varying questions) after the last `cache_control` breakpoint. -**Mid-conversation operator instructions** (Claude Opus 5, Claude Opus 4.8, Claude Fable 5, Claude Mythos 5; not Claude Sonnet 5; no beta header): append `{"role": "system", ...}` to `messages[]` instead of editing top-level `system`. Preserves the cached history prefix and is the prompt-injection-safe operator channel. See `shared/prompt-caching.md` § Mid-conversation system messages. +**Mid-conversation operator instructions** (Claude Opus 5, Claude Opus 4.8, Claude Fable 5, Claude Fable 5.1, Claude Mythos 5, Claude Mythos 5.1; not Claude Sonnet 5; no beta header): append `{"role": "system", ...}` to `messages[]` instead of editing top-level `system`. Preserves the cached history prefix and is the prompt-injection-safe operator channel. See `shared/prompt-caching.md` § Mid-conversation system messages. -**Top-level auto-caching** (`cache_control: {type: "ephemeral"}` on `messages.create()`) is the simplest option when you don't need fine-grained placement. Max 4 breakpoints per request. Minimum cacheable prefix is ~1024 tokens — shorter prefixes silently won't cache. +**Top-level auto-caching** (`cache_control: {type: "ephemeral"}` on `messages.create()`) is the simplest option when you don't need fine-grained placement. Max 4 breakpoints per request. Minimum cacheable prefix is model-dependent (512-4096 tokens - see `shared/prompt-caching.md` § API reference) - shorter prefixes silently won't cache. -**Verify with `usage.cache_read_input_tokens`** — if it's zero across repeated requests, a silent invalidator is at work (`datetime.now()` in system prompt, unsorted JSON, varying tool set). +**Verify with `usage.cache_read_input_tokens`** - if it's zero across repeated requests, a silent invalidator is at work (`datetime.now()` in system prompt, unsorted JSON, varying tool set). For placement patterns, architectural guidance, and the silent-invalidator audit checklist: read `shared/prompt-caching.md`. Language-specific syntax: `{lang}/claude-api/README.md` (Prompt Caching section). @@ -270,7 +273,7 @@ For placement patterns, architectural guidance, and the silent-invalidator audit ## Fast Mode (Quick Reference) -**Research preview, Claude Opus 5 / Opus 4.8 only** — Claude API and Managed Agents, not Bedrock / Google Cloud / Foundry. Opus 4.7 fast mode has been removed: `speed: "fast"` on 4.7 returns an error. Fast mode on Claude Opus 5 is priced at $10 / $50 per MTok. Fast mode runs the same model at up to 2.5x higher output tokens per second, at premium pricing. Three things are required on every request: use the **beta** messages endpoint (`client.beta.messages.…`), pass the beta flag `fast-mode-2026-02-01`, and set `speed: "fast"` as a top-level request parameter (not a header, not in `extra_body`). +**Research preview, Claude Opus 5 / Opus 4.8 only** - Claude API and Managed Agents, not Bedrock / Google Cloud / Foundry. Opus 4.7 fast mode has been removed: `speed: "fast"` on 4.7 returns an error. Fast mode on Claude Opus 5 is priced at $10 / $50 per MTok. Fast mode runs the same model at up to 2.5x higher output tokens per second, at premium pricing. Three things are required on every request: use the **beta** messages endpoint (`client.beta.messages....`), pass the beta flag `fast-mode-2026-02-01`, and set `speed: "fast"` as a top-level request parameter (not a header, not in `extra_body`). ```python client.beta.messages.create( @@ -292,13 +295,13 @@ client.beta.messages.create( `response.usage.speed` reports which speed was used. Fast mode has its own rate limit separate from standard Opus; on 429, either retry after the `retry-after` delay or drop `speed` and fall back to standard (note: switching speed invalidates prompt cache). Not available with Batch API, Priority Tier, Claude Platform on AWS, or third-party platforms. -**Priority Tier does not cover Claude Opus 5.** It is supported on every other current model, including Claude Fable 5 and Opus 4.8, but Claude Opus 5, Claude Sonnet 5, Claude Mythos 5, and Mythos Preview are excluded — a Priority Tier request naming one of them fails validation. +**Priority Tier is not supported on every current model.** It is supported on Claude Fable 5, Opus 4.8, and the older current models, but Claude Opus 5, Claude Sonnet 5, Claude Fable 5.1, Claude Mythos 5.1, Claude Mythos 5, and Mythos Preview are excluded - a Priority Tier request naming one of them fails validation. --- ## Task Budgets (Quick Reference) -**Beta, Claude Opus 5 / Fable 5 / Sonnet 5 / Opus 4.8 / 4.7.** A task budget gives Claude a token ceiling for an agentic loop so it paces itself and finishes gracefully instead of being cut off — distinct from `max_tokens`, which is an enforced per-response ceiling the model is not aware of. Minimum `total`: 20,000. Set `task_budget` inside `output_config` on `client.beta.messages.stream(...)` with beta flag `task-budgets-2026-03-13` — use streaming so the large `max_tokens` doesn't hit HTTP timeouts (full details: `shared/model-migration.md` → Task Budgets): +**Beta, Claude Opus 5 / Fable 5 / Claude Fable 5.1 (confirm at launch) / Sonnet 5 / Opus 4.8 / 4.7.** A task budget gives Claude a token ceiling for an agentic loop so it paces itself and finishes gracefully instead of being cut off - distinct from `max_tokens`, which is an enforced per-response ceiling the model is not aware of. Minimum `total`: 20,000. Set `task_budget` inside `output_config` on `client.beta.messages.stream(...)` with beta flag `task-budgets-2026-03-13` - use streaming so the large `max_tokens` doesn't hit HTTP timeouts (full details: `shared/model-migration.md` -> Task Budgets): ```python with client.beta.messages.stream( @@ -310,15 +313,15 @@ 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. 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. +`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. +**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. --- ## Provider Clients (Quick Reference) -When targeting Claude on a third-party platform, use that platform's dedicated client class — not the first-party `Anthropic()` client with a `base_url` override. After construction the client exposes the same `messages.create` / `.stream` surface as the first-party SDK. +When targeting Claude on a third-party platform, use that platform's dedicated client class - not the first-party `Anthropic()` client with a `base_url` override. After construction the client exposes the same `messages.create` / `.stream` surface as the first-party SDK. ### Amazon Bedrock @@ -326,41 +329,41 @@ Use the **Mantle** client (Messages-API Bedrock endpoint). Bedrock model IDs tak | Language | Client | |---|---| -| Python | `from anthropic import AnthropicBedrockMantle` → `AnthropicBedrockMantle(aws_region="…")` | -| TypeScript | `import { AnthropicBedrockMantle } from "@anthropic-ai/bedrock-sdk"` → `new AnthropicBedrockMantle({ awsRegion: "…" })` | -| Go | `bedrock.NewMantleClient(ctx, bedrock.MantleClientConfig{ AWSRegion: "…" })` | +| Python | `from anthropic import AnthropicBedrockMantle` -> `AnthropicBedrockMantle(aws_region="...")` | +| TypeScript | `import { AnthropicBedrockMantle } from "@anthropic-ai/bedrock-sdk"` -> `new AnthropicBedrockMantle({ awsRegion: "..." })` | +| Go | `bedrock.NewMantleClient(ctx, bedrock.MantleClientConfig{ AWSRegion: "..." })` | | Java | `AnthropicOkHttpClient.builder().backend(BedrockMantleBackend.fromEnv()).build()` (from `com.anthropic.bedrock.backends`) | -| C# | `new AnthropicBedrockMantleClient(new() { AwsRegion = "…" })` (package `Anthropic.Bedrock`) | -| PHP | `use Anthropic\Bedrock\MantleClient;` → `new MantleClient(awsRegion: '…')` | -| Ruby | `Anthropic::BedrockMantleClient.new(aws_region: "…")` | +| C# | `new AnthropicBedrockMantleClient(new() { AwsRegion = "..." })` (package `Anthropic.Bedrock`) | +| PHP | `use Anthropic\Bedrock\MantleClient;` -> `new MantleClient(awsRegion: '...')` | +| Ruby | `Anthropic::BedrockMantleClient.new(aws_region: "...")` | -`AnthropicBedrock` / `BedrockClient` / `BedrockBackend` (without `Mantle`) are the legacy `bedrock-runtime` InvokeModel path — prefer the Mantle client for new code. +`AnthropicBedrock` / `BedrockClient` / `BedrockBackend` (without `Mantle`) are the legacy `bedrock-runtime` InvokeModel path - prefer the Mantle client for new code. ### Microsoft Foundry | Language | Client | |---|---| -| Python | `from anthropic import AnthropicFoundry` → `AnthropicFoundry(api_key=…, resource="…")` | -| TypeScript | `import AnthropicFoundry from "@anthropic-ai/foundry-sdk"` → `new AnthropicFoundry({ … })` | +| Python | `from anthropic import AnthropicFoundry` -> `AnthropicFoundry(api_key=..., resource="...")` | +| TypeScript | `import AnthropicFoundry from "@anthropic-ai/foundry-sdk"` -> `new AnthropicFoundry({ ... })` | | Java | `AnthropicOkHttpClient.builder().backend(FoundryBackend.fromEnv()).build()` (from `com.anthropic.foundry.backends`) | -| C# | `new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(…))` (package `Anthropic.Foundry`) | -| PHP | `Foundry\Client::withCredentials(…)` | +| C# | `new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(...))` (package `Anthropic.Foundry`) | +| PHP | `Foundry\Client::withCredentials(...)` | The Go and Ruby SDKs do not currently support Foundry. For Ruby, use the standard `Anthropic::Client.new(base_url: "")` as a fallback (Entra ID auth is not built in). For Claude Platform on AWS, see `shared/claude-platform-on-aws.md`. ### Google Cloud Vertex AI -Two required constructor args: GCP `project_id` and `region`. Vertex model IDs take **no prefix** — current-generation models (Opus 4.8/4.7/4.6, Sonnet 5, Sonnet 4.6) use the bare first-party ID (e.g. `"claude-opus-5"`); dated-snapshot models use an `@` version separator (e.g. `claude-opus-4-5@20251101`, **not** `claude-opus-4-5-20251101`). Auth is GCP ADC (`gcloud auth application-default login`); no Anthropic API key. `region` can be `"global"` (recommended), a multi-region (`"us"`/`"eu"`), or a specific region. After construction, use the same `messages.create` / `.stream` surface. +Two required constructor args: GCP `project_id` and `region`. Vertex model IDs take **no prefix** - current-generation models (Opus 4.8/4.7/4.6, Sonnet 5, Sonnet 4.6) use the bare first-party ID (e.g. `"claude-opus-5"`); dated-snapshot models use an `@` version separator (e.g. `claude-opus-4-5@20251101`, **not** `claude-opus-4-5-20251101`). Auth is GCP ADC (`gcloud auth application-default login`); no Anthropic API key. `region` can be `"global"` (recommended), a multi-region (`"us"`/`"eu"`), or a specific region. After construction, use the same `messages.create` / `.stream` surface. | Language | Client | |---|---| -| Python | `from anthropic import AnthropicVertex` → `AnthropicVertex(project_id="…", region="…")` (install `"anthropic[vertex]"`) | -| TypeScript | `import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"` → `new AnthropicVertex({ projectId, region })` | -| Go | `import "github.com/anthropics/anthropic-sdk-go/vertex"` → `anthropic.NewClient(vertex.WithGoogleAuth(ctx, region, projectID))` | -| Java | `AnthropicOkHttpClient.builder().backend(VertexBackend.builder().region("…").project("…").build()).build()` (from `com.anthropic.vertex.backends`) | +| Python | `from anthropic import AnthropicVertex` -> `AnthropicVertex(project_id="...", region="...")` (install `"anthropic[vertex]"`) | +| TypeScript | `import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"` -> `new AnthropicVertex({ projectId, region })` | +| Go | `import "github.com/anthropics/anthropic-sdk-go/vertex"` -> `anthropic.NewClient(vertex.WithGoogleAuth(ctx, region, projectID))` | +| Java | `AnthropicOkHttpClient.builder().backend(VertexBackend.builder().region("...").project("...").build()).build()` (from `com.anthropic.vertex.backends`) | | C# | `new AnthropicClient { Backend = new VertexBackend(projectId, region) }` (package `Anthropic.Vertex`) | -| PHP | `use Anthropic\Vertex;` → `Vertex\Client::fromEnvironment(location: '…', projectId: '…')` — note `location`, not `region` | -| Ruby | `Anthropic::VertexClient.new(region: "…", project_id: "…")` | +| PHP | `use Anthropic\Vertex;` -> `Vertex\Client::fromEnvironment(location: '...', projectId: '...')` - note `location`, not `region` | +| Ruby | `Anthropic::VertexClient.new(region: "...", project_id: "...")` | --- @@ -377,63 +380,63 @@ client.beta.messages.create( ) ``` -Strategy types: `clear_tool_uses_20250919` (clears old tool results; optional `clear_tool_inputs: true` also clears the tool_use params) and `clear_thinking_20251015` (clears thinking blocks). Do **not** use `compact_20260112` or beta `compact-2026-01-12` — those are the separate compaction feature. +Strategy types: `clear_tool_uses_20250919` (clears old tool results; optional `clear_tool_inputs: true` also clears the tool_use params) and `clear_thinking_20251015` (clears thinking blocks). Do **not** use `compact_20260112` or beta `compact-2026-01-12` - those are the separate compaction feature. --- ## Mid-Conversation System Messages (Quick Reference) -**Claude Opus 5, Claude Opus 4.8, Claude Fable 5, and Claude Mythos 5; not Claude Sonnet 5; no beta header.** Append `{"role": "system", "content": "…"}` to the `messages` array (not the top-level `system` field) to add an operator instruction mid-conversation without invalidating the cached prefix. Use the regular `client.messages.create` — there is no beta. A mid-conversation system message must follow a `user` message (or an `assistant` message ending in server-tool use), and must be either the last entry in `messages` or be followed by an `assistant` turn — it cannot be `messages[0]`. Availability: `shared/platform-availability.md`. See `shared/prompt-caching.md` § Mid-conversation system messages. +**Claude Opus 5, Claude Opus 4.8, Claude Fable 5, Claude Fable 5.1, Claude Mythos 5, and Claude Mythos 5.1; not Claude Sonnet 5; no beta header.** Append `{"role": "system", "content": "..."}` to the `messages` array (not the top-level `system` field) to add an operator instruction mid-conversation without invalidating the cached prefix. Use the regular `client.messages.create` - there is no beta. A mid-conversation system message must follow a `user` message (or an `assistant` message ending in server-tool use), and must be either the last entry in `messages` or be followed by an `assistant` turn - it cannot be `messages[0]`. Availability: `shared/platform-availability.md`. See `shared/prompt-caching.md` § Mid-conversation system messages. A beta extension shipped with Claude Fable 5.1: `output_config: {effort: ...}` with `content: []` changes effort from that point on without a cache reset (beta `mid-conversation-output-config-2026-07-01`; Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5; Claude API). An effort-only message (empty `content`) is exempt from the placement rules above - it can sit anywhere in `messages`, including first or between an assistant turn and the next user turn; the rules apply to text and `clear_at` messages. For a per-turn reminder, give the message `clear_at: "next_user_message"` (beta `mid-conversation-system-clear-at-2026-08-21`): it renders for one turn, then stays in the transcript cleared - never delete earlier copies (on Claude Fable 5.1 deleting one invalidates later thinking blocks); without the beta, a text block after the tool results, earlier copies kept. See `shared/model-migration.md` -> Migrating to Claude Fable 5.1 from Claude Fable 5 -> New API features. --- ## Managed Agents (Beta) -**Managed Agents** is a third surface: server-managed stateful agents with Anthropic-hosted tool execution. You create a persisted, versioned Agent config (`POST /v1/agents`), then start Sessions that reference it. Each session provisions a container as the agent's workspace — bash, file ops, and code execution run there; the agent loop itself runs on Anthropic's orchestration layer and acts on the container via tools. The session streams events; you send messages and tool results back. +**Managed Agents** is a third surface: server-managed stateful agents with Anthropic-hosted tool execution. You create a persisted, versioned Agent config (`POST /v1/agents`), then start Sessions that reference it. Each session provisions a container as the agent's workspace - bash, file ops, and code execution run there; the agent loop itself runs on Anthropic's orchestration layer and acts on the container via tools. The session streams events; you send messages and tool results back. Availability: `shared/platform-availability.md`. For agents on Bedrock / Vertex / Foundry (where Managed Agents is unsupported), use Claude API + tool use. -**Mandatory flow:** Agent (once) → Session (every run). `model`/`system`/`tools` live on the agent, never the session. See `shared/managed-agents-overview.md` for the full reading guide, beta headers, and pitfalls. +**Mandatory flow:** Agent (once) -> Session (every run). `model`/`system`/`tools` live on the agent, never the session. See `shared/managed-agents-overview.md` for the full reading guide, beta headers, and pitfalls. -**Beta headers:** `managed-agents-2026-04-01` — the SDK sets this automatically for all `client.beta.{agents,environments,sessions,vaults,memory_stores,deployments,deployment_runs}.*` calls. Skills API uses `skills-2025-10-02` and Files API uses `files-api-2025-04-14`, but you don't need to explicitly pass those in for endpoints other than `/v1/skills` and `/v1/files`. +**Beta headers:** `managed-agents-2026-04-01` - the SDK sets this automatically for all `client.beta.{agents,environments,sessions,vaults,memory_stores,deployments,deployment_runs}.*` calls. Files API and Skills API are out of beta - no beta header needed (see the API Drift table above for the migration guides). -**Subcommands** — invoke directly with `/claude-api `: +**Subcommands** - invoke directly with `/claude-api `: | Subcommand | Action | |---|---| -| `managed-agents-onboard` | Walk the user through setting up a Managed Agent from scratch. **Read `shared/managed-agents-onboarding.md` immediately** and follow its interview script: **describe → configure the agent (propose, don't interrogate) → environment → session** (same arc as the Console quickstart, auth deferred to the session step) — defaults and inline suggestions do the work, with a silent viability gate (job vs tools/credentials/data) before any code is emitted. Do not summarize — run the interview. | +| `managed-agents-onboard` | Walk the user through setting up a Managed Agent from scratch. **Read `shared/managed-agents-onboarding.md` immediately** and follow its interview script: **describe -> configure the agent (propose, don't interrogate) -> environment -> session** (same arc as the Console quickstart, auth deferred to the session step) - defaults and inline suggestions do the work, with a silent viability gate (job vs tools/credentials/data) before any code is emitted. Do not summarize - run the interview. | -**Reading guide:** Start with `shared/managed-agents-overview.md`, then the topical `shared/managed-agents-*.md` files (core, environments, tools, events, outcomes, multiagent, webhooks, memory, scheduled-deployments, client-patterns, onboarding, api-reference). For Python, TypeScript, Go, Ruby, PHP, and Java, read `{lang}/managed-agents/README.md` for code examples. For cURL, read `curl/managed-agents.md`. **Agents are persistent — create once, reference by ID.** Define agents and environments as version-controlled YAML applied with the `ant` CLI — this is the recommended flow (see `shared/anthropic-cli.md`): the CLI owns the control plane (creating and updating agents), your code owns the data plane (`sessions.create` with the stored agent ID). Call `agents.create()` in code only when you must provision programmatically; either way, store the returned agent ID and pass it to every subsequent `sessions.create`; never call `agents.create()` in the request path. If a binding you need isn't shown in the language README, WebFetch the relevant entry from `shared/live-sources.md` rather than guess. C# has beta Managed Agents support via `client.Beta.Agents` and related namespaces — see `csharp/claude-api/README.md` for details, or `curl/managed-agents.md` for raw HTTP reference. +**Reading guide:** Start with `shared/managed-agents-overview.md`, then the topical `shared/managed-agents-*.md` files (core, environments, tools, events, outcomes, multiagent, webhooks, memory, scheduled-deployments, client-patterns, onboarding, api-reference). For Python, TypeScript, Go, Ruby, PHP, and Java, read `{lang}/managed-agents/README.md` for code examples. For cURL, read `curl/managed-agents.md`. **Agents are persistent - create once, reference by ID.** Define agents and environments as version-controlled YAML applied with the `ant` CLI - this is the recommended flow (see `shared/anthropic-cli.md`): the CLI owns the control plane (creating and updating agents), your code owns the data plane (`sessions.create` with the stored agent ID). Call `agents.create()` in code only when you must provision programmatically; either way, store the returned agent ID and pass it to every subsequent `sessions.create`; never call `agents.create()` in the request path. If a binding you need isn't shown in the language README, WebFetch the relevant entry from `shared/live-sources.md` rather than guess. C# has beta Managed Agents support via `client.Beta.Agents` and related namespaces - see `csharp/claude-api/README.md` for details, or `curl/managed-agents.md` for raw HTTP reference. -**When the user wants to set up a Managed Agent from scratch** (e.g. "how do I get started", "walk me through creating one", "set up a new agent"): read `shared/managed-agents-onboarding.md` and run its interview — same flow as the `managed-agents-onboard` subcommand. +**When the user wants to set up a Managed Agent from scratch** (e.g. "how do I get started", "walk me through creating one", "set up a new agent"): read `shared/managed-agents-onboarding.md` and run its interview - same flow as the `managed-agents-onboard` subcommand. -**When the user asks "how do I write the client code for X":** reach for `shared/managed-agents-client-patterns.md` — covers lossless stream reconnect, `processed_at` queued/processed gate, interrupt, `tool_confirmation` round-trip, the correct idle/terminated break gate, post-idle status race, stream-first ordering, file-mount gotchas, etc. For credentials, lead with vault `environment_variable` credentials — the first-class mechanism; secrets are substituted at egress and never enter the sandbox (`shared/managed-agents-tools.md` → Vaults). Keeping credentials host-side via custom tools is the fallback where vault credentials don't fit (e.g. self-hosted sandboxes). +**When the user asks "how do I write the client code for X":** reach for `shared/managed-agents-client-patterns.md` - covers lossless stream reconnect, `processed_at` queued/processed gate, interrupt, `tool_confirmation` round-trip, the correct idle/terminated break gate, post-idle status race, stream-first ordering, file-mount gotchas, etc. For credentials, lead with vault `environment_variable` credentials - the first-class mechanism; secrets are substituted at egress and never enter the sandbox (`shared/managed-agents-tools.md` -> Vaults). Keeping credentials host-side via custom tools is the fallback where vault credentials don't fit (e.g. self-hosted sandboxes). -**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 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. +**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) -Server-side tools run on Anthropic's infrastructure — no client-side execution loop. Declare in `tools`; results arrive as content blocks in the same response. **No beta header** unless noted. **Prefer the latest type variant your model supports.** The `_20260209` web search / web fetch variants below (dynamic filtering) require Opus 5/4.8/4.7/4.6, Sonnet 5, or Sonnet 4.6; the basic variants for older models are listed after the table. +Server-side tools run on Anthropic's infrastructure - no client-side execution loop. Declare in `tools`; results arrive as content blocks in the same response. **No beta header** unless noted. **Prefer the latest type variant your model supports.** The `_20260209` web search / web fetch variants below (dynamic filtering) require Opus 5/4.8/4.7/4.6, Sonnet 5, or Sonnet 4.6; the basic variants for older models are listed after the table. | Tool | `type` | `name` | Key optional params | Result block type | |---|---|---|---|---| -| Web search | `web_search_20260209` | `web_search` | `max_uses`, `allowed_domains`/`blocked_domains`, `user_location` | `web_search_tool_result` → `.content` is a list of `web_search_result` | -| Web fetch | `web_fetch_20260209` | `web_fetch` | `max_uses`, `allowed_domains`/`blocked_domains`, `citations`, `max_content_tokens` | `web_fetch_tool_result` → `.content` is a `web_fetch_result` with a `document` block | -| Code execution | `code_execution_20260521` | `code_execution` | none | `bash_code_execution_tool_result` → `.content.stdout` / `.stderr` / `.return_code` | +| Web search | `web_search_20260209` | `web_search` | `max_uses`, `allowed_domains`/`blocked_domains`, `user_location` | `web_search_tool_result` -> `.content` is a list of `web_search_result` | +| Web fetch | `web_fetch_20260209` | `web_fetch` | `max_uses`, `allowed_domains`/`blocked_domains`, `citations`, `max_content_tokens` | `web_fetch_tool_result` -> `.content` is a `web_fetch_result` with a `document` block | +| Code execution | `code_execution_20260521` | `code_execution` | none | `bash_code_execution_tool_result` -> `.content.stdout` / `.stderr` / `.return_code` | | Tool search (regex) | `tool_search_tool_regex_20251119` | `tool_search_tool_regex` | mark other tools `defer_loading: true` | `tool_search_tool_result` | | Tool search (BM25) | `tool_search_tool_bm25_20251119` | `tool_search_tool_bm25` | mark other tools `defer_loading: true` | `tool_search_tool_result` | -`web_search_20260209` / `web_fetch_20260209` have built-in dynamic filtering — code execution runs under the hood, so do **not** separately declare `code_execution` in `tools` (a second execution environment confuses the model). For models older than Opus 4.6 / Sonnet 4.6, use the basic variants `web_search_20250305` / `web_fetch_20250910` instead; on Vertex AI only basic `web_search_20250305` is available. `code_execution_20260120` (REPL persistence + programmatic tool calling) runs on Opus 4.5+ / Sonnet 4.5+. **Go SDK only**: `code_execution_20260521` lives under `client.Beta.Messages.New` with `Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25"}` (other languages use plain `client.messages.create`); `code_execution_20260120` uses the non-beta `client.Messages.New` in Go like everywhere else. Web fetch only fetches URLs already present in the conversation. Provider availability varies by tool — see `shared/platform-availability.md`. See `shared/tool-use-concepts.md` for `pause_turn` handling. +`web_search_20260209` / `web_fetch_20260209` have built-in dynamic filtering - code execution runs under the hood, so do **not** separately declare `code_execution` in `tools` (a second execution environment confuses the model). For models older than Opus 4.6 / Sonnet 4.6, use the basic variants `web_search_20250305` / `web_fetch_20250910` instead; on Vertex AI only basic `web_search_20250305` is available. `code_execution_20260120` (REPL persistence + programmatic tool calling) runs on Opus 4.5+ / Sonnet 4.5+. **Go SDK only**: `code_execution_20260521` lives under `client.Beta.Messages.New` with `Betas: []anthropic.AnthropicBeta{"code-execution-2025-08-25"}` (other languages use plain `client.messages.create`); `code_execution_20260120` uses the non-beta `client.Messages.New` in Go like everywhere else. Web fetch only fetches URLs already present in the conversation. Provider availability varies by tool - see `shared/platform-availability.md`. See `shared/tool-use-concepts.md` for `pause_turn` handling. ## Document & File Input (Quick Reference) **PDF (base64, no beta):** `{"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": }}` in user content, placed before the text block. Base64 string must have no newlines. Limits: 32 MB request, 600 pages (100 for 200k-context models). Java: `ContentBlockParam.ofDocument(DocumentBlockParam... Base64PdfSource.builder().data(...))`. -**Files API (beta `files-api-2025-04-14`):** upload via `client.beta.files.upload(...)` → response `id` is the `file_id`. Reference it as `{"type": "document", "source": {"type": "file", "file_id": "..."}}` for PDF/text, or `{"type": "image", ...}` for images — the content-block type must match the file's MIME type. The beta header is required on **both** the upload and the `messages.create` that references the file. Availability: `shared/platform-availability.md`. +**Files API (no beta):** upload via `client.files.upload(...)` -> response `id` is the `file_id`. Reference it as `{"type": "document", "source": {"type": "file", "file_id": "..."}}` for PDF/text, or `{"type": "image", ...}` for images - the content-block type must match the file's MIME type. To migrate code off `files-api-2025-04-14`, WebFetch the Files API row in `shared/live-sources.md`. Availability: `shared/platform-availability.md`. **Citations (no beta):** set `citations: {enabled: true}` on each `document` content block (all or none). Response splits into multiple `text` blocks; cited blocks carry a `citations` array. Each citation has `cited_text`, `document_index`, `document_title`, and a location by `type`: `char_location` (`start_char_index`/`end_char_index`) for plain text, `page_location` (`start_page_number`/`end_page_number`, 1-indexed) for PDF, `content_block_location` for custom content. Incompatible with `output_config.format` (returns a 400). @@ -441,79 +444,88 @@ Server-side tools run on Anthropic's infrastructure — no client-side execution **Strict tool use (no beta):** set `strict: true` as a top-level field on the tool definition (alongside `name`/`description`/`input_schema`), **not** on `tool_choice`. Schema must have `additionalProperties: false` + `required`. Guarantees `tool_use.input` validates exactly. Go: `Strict: anthropic.Bool(true)` + `additionalProperties` via `InputSchema.ExtraFields`; Java: `.strict(true)` + `.putAdditionalProperty("additionalProperties", JsonValue.from(false))`. -**Parallel tool use (default on):** one assistant message may contain multiple `tool_use` blocks. Execute them concurrently, then return **all** `tool_result` blocks in a **single** user message — splitting them across multiple messages silently trains Claude to stop making parallel calls. For a failed tool, return `tool_result` with `is_error: true` — don't drop it. +**Parallel tool use (default on):** one assistant message may contain multiple `tool_use` blocks. Execute them concurrently, then return **all** `tool_result` blocks in a **single** user message - splitting them across multiple messages silently trains Claude to stop making parallel calls. For a failed tool, return `tool_result` with `is_error: true` - don't drop it. -**Tool Runner (SDK beta helper):** drives the tool-call loop for you via `client.beta.messages.*`. Python: `@beta_tool` decorator + `client.beta.messages.tool_runner(...)` → `runner.until_done()`. TypeScript: `betaZodTool({...})` from `@anthropic-ai/sdk/helpers/beta/zod` + `client.beta.messages.toolRunner(...)` → `await runner`. Go: `toolrunner.NewBetaToolFromJSONSchema(...)` + `client.Beta.Messages.NewToolRunner(...)` → `.RunToCompletion(ctx)`. Java requires `.addBeta("structured-outputs-2025-11-13")`. Ruby: `Anthropic::BaseTool` subclass + `client.beta.messages.tool_runner(...)`. PHP: `BetaRunnableTool` + `->toolRunner(...)`. C#: raw JSON-schema tools + `BetaToolRunner` via `client.Beta.Messages.ToolRunner(...)`. +**Tool Runner (SDK beta helper):** drives the tool-call loop for you via `client.beta.messages.*`. Python: `@beta_tool` decorator + `client.beta.messages.tool_runner(...)` -> `runner.until_done()`. TypeScript: `betaZodTool({...})` from `@anthropic-ai/sdk/helpers/beta/zod` + `client.beta.messages.toolRunner(...)` -> `await runner`. Go: `toolrunner.NewBetaToolFromJSONSchema(...)` + `client.Beta.Messages.NewToolRunner(...)` -> `.RunToCompletion(ctx)`. Java requires `.addBeta("structured-outputs-2025-11-13")`. Ruby: `Anthropic::BaseTool` subclass + `client.beta.messages.tool_runner(...)`. PHP: `BetaRunnableTool` + `->toolRunner(...)`. C#: raw JSON-schema tools + `BetaToolRunner` via `client.Beta.Messages.ToolRunner(...)`. **Programmatic tool calling (no beta header):** Claude calls your custom tool from inside code execution. Add `{"type": "code_execution_20260120", "name": "code_execution"}` **and** set `"allowed_callers": ["code_execution_20260120"]` on your custom tool. Opus 4.5+ / Sonnet 4.5+ (availability: `shared/platform-availability.md`). When responding to a pending programmatic call, the user message must contain **only** `tool_result` blocks (no text). Not compatible with `strict: true`, `disable_parallel_tool_use`, forced `tool_choice`, or MCP tools. ## Other API Surfaces (Quick Reference) -**Message Batches (no beta; availability: `shared/platform-availability.md`):** `client.messages.batches.create(requests=[{custom_id, params}, ...])` → poll `client.messages.batches.retrieve(id).processing_status` until `"ended"` → stream `client.messages.batches.results(id)`. Each result has `.custom_id` + `.result.type` (`succeeded`/`errored`/`canceled`/`expired`); on success read `.result.message.content`. Python wraps requests as `Request(custom_id=..., params=MessageCreateParamsNonStreaming(...))`. Results arrive in **any order** — key by `custom_id`, never by position. +**Message Batches (no beta; availability: `shared/platform-availability.md`):** `client.messages.batches.create(requests=[{custom_id, params}, ...])` -> poll `client.messages.batches.retrieve(id).processing_status` until `"ended"` -> stream `client.messages.batches.results(id)`. Each result has `.custom_id` + `.result.type` (`succeeded`/`errored`/`canceled`/`expired`); on success read `.result.message.content`. Python wraps requests as `Request(custom_id=..., params=MessageCreateParamsNonStreaming(...))`. Results arrive in **any order** - key by `custom_id`, never by position. -**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. +**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` — 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. +**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)`. +**Admin API (beta, since 2026-08-26):** organization management - members, invites, workspaces and workspace members, API keys, rate limit reports, service accounts, federation issuers/rules, CMEK external keys - under `client.beta.organization` in all seven SDKs and `ant beta:organization` in the CLI. Requires an admin credential: an Admin API key (`sk-ant-admin...`, read from `ANTHROPIC_API_KEY`) or an `org:admin` OAuth token (`ANTHROPIC_AUTH_TOKEN`); regular API keys are rejected. Usage and cost reports and the Claude Enterprise user-management/analytics endpoints are **not** in the SDKs - raw HTTP only. See `shared/admin-api.md`. + +**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)`. ## Workload Identity Federation (Quick Reference) -**GA, no beta header.** Construct the normal zero-arg client (`Anthropic()` / `new Anthropic()` / `anthropic.NewClient()` / `AnthropicOkHttpClient.fromEnv()`); the SDK auto-detects WIF when **all** of `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and `ANTHROPIC_IDENTITY_TOKEN_FILE` (or `ANTHROPIC_IDENTITY_TOKEN`) are set, exchanges the JWT at `/v1/oauth/token`, and auto-refreshes. `ANTHROPIC_WORKSPACE_ID` does not gate activation — required only when the federation rule spans multiple workspaces (else 400 `workspace_id_required`), optional for single-workspace rules. `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` (even empty) outrank WIF, and a set `ANTHROPIC_PROFILE` also wins over the federation env vars (a missing named profile is an error, not a fall-through) — unset all three. +**GA, no beta header.** Construct the normal zero-arg client (`Anthropic()` / `new Anthropic()` / `anthropic.NewClient()` / `AnthropicOkHttpClient.fromEnv()`); the SDK auto-detects WIF when **all** of `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID`, and `ANTHROPIC_IDENTITY_TOKEN_FILE` (or `ANTHROPIC_IDENTITY_TOKEN`) are set, exchanges the JWT at `/v1/oauth/token`, and auto-refreshes. `ANTHROPIC_WORKSPACE_ID` does not gate activation - required only when the federation rule spans multiple workspaces (else 400 `workspace_id_required`), optional for single-workspace rules. `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` (even empty) outrank WIF, and a set `ANTHROPIC_PROFILE` also wins over the federation env vars (a missing named profile is an error, not a fall-through) - unset all three. --- ## Reading Guide -After detecting the language, read the relevant files based on what the user needs. +After detecting the language, read the relevant files based on what the user needs. Every `{lang}/...`, `shared/...`, and `curl/...` path cited in this document is relative to this skill's base directory, and none of those files' content is included above - Read each one on demand before relying on what it covers. -**All SDK languages use the same multi-file layout** — directory `{lang}/claude-api/` containing `README.md` (install, client init, basic request, thinking, caching, stop details, misc), `tool-use.md` (tool definitions, agentic loop, Anthropic-defined tools, structured outputs), `streaming.md`, `batches.md`, `files-api.md`. Not every language has every file (e.g., Ruby has no `batches.md`); if a file is absent, that feature's example is not yet documented for that language — fall back to the cURL shape or WebFetch the SDK repo from `shared/live-sources.md`. **cURL** → `curl/examples.md`. +**All SDK languages use the same multi-file layout** - directory `{lang}/claude-api/` containing `README.md` (install, client init, basic request, thinking, caching, stop details, misc), `tool-use.md` (tool definitions, agentic loop, Anthropic-defined tools, structured outputs), `streaming.md`, `batches.md`, `files-api.md`. Not every language has every file (e.g., Ruby has no `batches.md`); if a file is absent, that feature's example is not yet documented for that language - fall back to the cURL shape or WebFetch the SDK repo from `shared/live-sources.md`. **cURL** -> `curl/examples.md`. The Quick Task Reference below uses the `{lang}/claude-api/FILE.md` path notation for all languages. ### Quick Task Reference **Single text classification/summarization/extraction/Q&A:** -→ Read only `{lang}/claude-api/README.md` — **always read the README first** for any task (installation, quick start, common patterns, error handling) +-> Read only `{lang}/claude-api/README.md` - **always read the README first** for any task (installation, quick start, common patterns, error handling) **Chat UI or real-time response display:** -→ Read `{lang}/claude-api/README.md` + `{lang}/claude-api/streaming.md` +-> Read `{lang}/claude-api/README.md` + `{lang}/claude-api/streaming.md` **Long-running conversations (may exceed context window):** -→ Read `{lang}/claude-api/README.md` — see Compaction section -**Migrating to a newer model (Fable 5 / Opus 5 / Opus 4.8 / Opus 4.7 / Opus 4.6 / Sonnet 5 / Sonnet 4.6), replacing a retired model, or translating `budget_tokens` / prefill patterns to the current API:** -→ Read `shared/model-migration.md` -**Upgrading the Anthropic SDK package itself across a major version (`anthropic` 0.x → 1.x: `httpx2`, awaited async `.with_raw_response`, removed deprecated parameters / aliases / Text Completions, Python ≥ 3.10) — or writing new code against a project already on 1.x:** -→ Read `{lang}/claude-api/sdk-upgrade.md` (currently Python only; other SDKs have no bundled major-version guide yet — use that SDK's CHANGELOG via `shared/live-sources.md`) -**Prompting or tuning Fable 5 (long turns, effort, verbosity, autonomous runs, sub-agents):** -→ Read `shared/model-migration.md` → Migrating to Fable 5 → Behavioral shifts (prompt-tunable) + Long-running agent recommendations +-> Read `{lang}/claude-api/README.md` - see Compaction section +**Migrating to a newer model (Fable 5.1 / Fable 5 / Opus 5 / Opus 4.8 / Opus 4.7 / Opus 4.6 / Sonnet 5 / Sonnet 4.6), replacing a retired model, or translating `budget_tokens` / prefill patterns to the current API:** +-> Read `shared/model-migration.md` +**Upgrading the Anthropic SDK package itself across a major version (`anthropic` 0.x -> 1.x: `httpx2`, awaited async `.with_raw_response`, removed deprecated parameters / aliases / Text Completions, Python >= 3.10) - or writing new code against a project already on 1.x:** +-> Read `{lang}/claude-api/sdk-upgrade.md` (currently Python only; other SDKs have no bundled major-version guide yet - use that SDK's CHANGELOG via `shared/live-sources.md`) +**Prompting or tuning Fable 5/5.1 (long turns, effort, verbosity, autonomous runs, sub-agents):** +-> Read `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> Behavioral shifts (prompt-tunable) + Long-running agent recommendations +**Prompting or tuning Claude Fable 5.1 (progress updates, parallel tool calls, writing density / formatting, autonomy, test sprawl, whole-file rewrites) or making a harness compatible with preserved thinking's history-editing check (history edits, compaction, per-turn reminders):** +-> Read `shared/model-migration.md` -> Migrating to Claude Fable 5.1 from Claude Fable 5 -> New API features + Behavioral shifts (prompt-tunable); for the history-editing check itself (the three-step check, the append-only edit table, compaction shapes), Breaking change 3 in the same section **Prompt caching / optimize caching / "why is my cache hit rate low":** -→ Read `shared/prompt-caching.md` (prefix-stability design, breakpoint placement, anti-patterns that silently invalidate cache) + `{lang}/claude-api/README.md` (Prompt Caching section) +-> Read `shared/prompt-caching.md` (prefix-stability design, breakpoint placement, anti-patterns that silently invalidate cache) + `{lang}/claude-api/README.md` (Prompt Caching section) **Auditing or cleaning up prompts, skills, or tool descriptions ("is this prompt outdated", "remove the cruft", "this was written for an older model"):** -→ Read `shared/prompt-audit.md` — dated-pattern tables with greppable signals, the keep list (what NOT to delete), and the report + proposed-diff output contract +-> Read `shared/prompt-audit.md` - dated-pattern tables with greppable signals, the keep list (what NOT to delete), and the report + proposed-diff output contract **Count tokens in a file / prompt / diff ("how many tokens is X"):** -→ Read `shared/token-counting.md` — use `messages.count_tokens`, never `tiktoken` +-> Read `shared/token-counting.md` - use `messages.count_tokens`, never `tiktoken` +**Reducing or reviewing API spend ("the bill is too high", "make this cheaper", "am I overspending", cost per completed task, cheapest model or effort that holds quality):** +-> Read `shared/cost-optimization.md` - baseline and token profile first, then the levers in order (free wins before tradeoffs) with measured expectations, and a workload-shape -> lever mapping table **Function calling / tool use / agents:** -→ Read `{lang}/claude-api/README.md` + `shared/tool-use-concepts.md` (conceptual foundations: function calling, code execution, memory, structured outputs) + `{lang}/claude-api/tool-use.md` (language-specific code examples: tool runner, manual loop, code execution, memory, structured outputs) +-> Read `{lang}/claude-api/README.md` + `shared/tool-use-concepts.md` (conceptual foundations: function calling, code execution, memory, structured outputs) + `{lang}/claude-api/tool-use.md` (language-specific code examples: tool runner, manual loop, code execution, memory, structured outputs) **Agent design (tool surface, context management, caching strategy):** -→ Read `shared/agent-design.md` (bash vs. dedicated tools, programmatic tool calling, tool search/skills, context editing vs. compaction vs. memory, caching principles) +-> Read `shared/agent-design.md` (bash vs. dedicated tools, programmatic tool calling, tool search/skills, context editing vs. compaction vs. memory, caching principles) **Batch processing (non-latency-sensitive; runs asynchronously at 50% cost):** -→ Read `{lang}/claude-api/README.md` + `{lang}/claude-api/batches.md` +-> Read `{lang}/claude-api/README.md` + `{lang}/claude-api/batches.md` **File uploads across multiple requests (same file without re-uploading):** -→ Read `{lang}/claude-api/README.md` + `{lang}/claude-api/files-api.md` +-> Read `{lang}/claude-api/README.md` + `{lang}/claude-api/files-api.md` + +**Organization administration (members, invites, workspaces, API keys, rate limit reports, service accounts, WIF resources, CMEK):** +-> Read `shared/admin-api.md` - `client.beta.organization` endpoint/method table, admin credentials, per-language naming and pagination, what stays curl-only **Debugging HTTP errors or implementing error handling:** -→ Read `shared/error-codes.md` — per-SDK typed exception class table and the Go `errors.As` pattern +-> Read `shared/error-codes.md` - per-SDK typed exception class table and the Go `errors.As` pattern **Latest official documentation:** -→ WebFetch the URLs in `shared/live-sources.md` +-> WebFetch the URLs in `shared/live-sources.md` **Managed Agents (server-managed stateful agents with workspace):** -→ See the reading guide in the `## Managed Agents (Beta)` section above — it lists every `shared/managed-agents-*.md` file and the language-specific READMEs (`{lang}/managed-agents/README.md`, `curl/managed-agents.md`). +-> See the reading guide in the `## Managed Agents (Beta)` section above - it lists every `shared/managed-agents-*.md` file and the language-specific READMEs (`{lang}/managed-agents/README.md`, `curl/managed-agents.md`). --- @@ -530,27 +542,29 @@ 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, 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 `` 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. -- **128K output tokens:** Fable 5, Opus 5, Opus 4.6, Opus 4.7, Opus 4.8, Sonnet 5, and Sonnet 4.6 support up to 128K `max_tokens`, but the SDKs require streaming for values that large to avoid HTTP timeouts. Use `.stream()` with `.get_final_message()` / `.finalMessage()`. -- **Tool call JSON parsing (Fable 5, Opus 5, and the 4.6/4.7/4.8 family):** Fable 5, Opus 5, Opus 4.6, Opus 4.7, Opus 4.8, and Sonnet 4.6 may produce different JSON string escaping in tool call `input` fields (e.g., Unicode or forward-slash escaping). Always parse tool inputs with `json.loads()` / `JSON.parse()` — never do raw string matching on the serialized input. +- **Prefill removed (Fable 5, Claude Fable 5.1, 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, Claude Fable 5.1, 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 `` 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. +- **128K output tokens:** Fable 5, Claude Fable 5.1, Opus 5, Opus 4.6, Opus 4.7, Opus 4.8, Sonnet 5, and Sonnet 4.6 support up to 128K `max_tokens`, but the SDKs require streaming for values that large to avoid HTTP timeouts. Use `.stream()` with `.get_final_message()` / `.finalMessage()`. +- **Forced tool use removed (Claude Fable 5.1 / Claude Mythos 5.1, as on Mythos Preview):** `tool_choice: {type: "any"}` and `{type: "tool", name: ...}` return a 400 (`tool_choice: type "tool" and "any" are not supported for this model.`), on `count_tokens` and Batches too. Use `{type: "auto"}` plus an explicit instruction naming the tool, `strict: true` on the tool to keep schema-valid arguments, or structured outputs (`output_config.format`) when the forced call only existed to get JSON back. `{type: "none"}` is unaffected; `disable_parallel_tool_use` still works with `auto` (at most one call). +- **Tool call JSON parsing (Fable 5, Claude Fable 5.1, Opus 5, and the 4.6/4.7/4.8 family):** Fable 5, Claude Fable 5.1, Opus 5, Opus 4.6, Opus 4.7, Opus 4.8, and Sonnet 4.6 may produce different JSON string escaping in tool call `input` fields (e.g., Unicode or forward-slash escaping). Always parse tool inputs with `json.loads()` / `JSON.parse()` - never do raw string matching on the serialized input. - **Structured outputs (all models):** Use `output_config: {format: {...}}` instead of the deprecated `output_format` parameter on `messages.create()`. This is a general API change, not 4.6-specific. -- **Don't reimplement SDK functionality:** The SDK provides high-level helpers — use them instead of building from scratch. Specifically: use `stream.finalMessage()` instead of wrapping `.on()` events in `new Promise()`; use typed exception classes (`Anthropic.RateLimitError`, etc.) instead of string-matching error messages; use SDK types (`Anthropic.MessageParam`, `Anthropic.Tool`, `Anthropic.Message`, etc.) instead of redefining equivalent interfaces. -- **Error handling — catch a chain, not one broad class.** A single `except APIStatusError` / `catch (AnthropicServiceException)` / `rescue APIError` loses the distinction between retryable (429, ≥500, network) and non-retryable (400/404) failures. Write a most-specific-first chain — e.g. `NotFoundError` → `RateLimitError` → `APIStatusError` → `APIConnectionError` (or the Go equivalent: `errors.As` into `*anthropic.Error` then `switch apierr.StatusCode { case 404: …; case 429: …; default: … }`). Per-language class names and namespaces are in `shared/error-codes.md`. -- **Don't research SDK types — write first.** If a type name isn't shown in the documentation included in this skill, write the code file from the namespace/package tables in the language-specific doc and let the compiler's error point you to the right name. Do not spend turns on WebFetch, SDK-repo clones, or compiling-and-running a separate reflection program to discover type names before writing — produce the source file first, then fix what the compiler reports. A quick `strings` / `jar tf` / `javap` against the installed SDK is acceptable for locating names (it returns in seconds), but don't escalate beyond that. A file with a wrong type name is recoverable; a session spent on discovery with no file written is not. -- **Bash and text editor tools are Anthropic-defined, schema-less.** Declare `{"type": "bash_20250124", "name": "bash"}` / `{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}` — no `input_schema`. A custom tool with your own schema named `"bash"` is a different tool. Handler paths and security checks are in `shared/tool-use-concepts.md` § Client-Side Tools. -- **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:}]` 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`. (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. +- **Don't reimplement SDK functionality:** The SDK provides high-level helpers - use them instead of building from scratch. Specifically: use `stream.finalMessage()` instead of wrapping `.on()` events in `new Promise()`; use typed exception classes (`Anthropic.RateLimitError`, etc.) instead of string-matching error messages; use SDK types (`Anthropic.MessageParam`, `Anthropic.Tool`, `Anthropic.Message`, etc.) instead of redefining equivalent interfaces. +- **Error handling - catch a chain, not one broad class.** A single `except APIStatusError` / `catch (AnthropicServiceException)` / `rescue APIError` loses the distinction between retryable (429, >=500, network) and non-retryable (400/404) failures. Write a most-specific-first chain - e.g. `NotFoundError` -> `RateLimitError` -> `APIStatusError` -> `APIConnectionError` (or the Go equivalent: `errors.As` into `*anthropic.Error` then `switch apierr.StatusCode { case 404: ...; case 429: ...; default: ... }`). Per-language class names and namespaces are in `shared/error-codes.md`. +- **Don't research SDK types - write first.** If a type name isn't shown in the documentation included in this skill, write the code file from the namespace/package tables in the language-specific doc and let the compiler's error point you to the right name. Do not spend turns on WebFetch, SDK-repo clones, or compiling-and-running a separate reflection program to discover type names before writing - produce the source file first, then fix what the compiler reports. A quick `strings` / `jar tf` / `javap` against the installed SDK is acceptable for locating names (it returns in seconds), but don't escalate beyond that. A file with a wrong type name is recoverable; a session spent on discovery with no file written is not. +- **Bash and text editor tools are Anthropic-defined, schema-less.** Declare `{"type": "bash_20250124", "name": "bash"}` / `{"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}` - no `input_schema`. A custom tool with your own schema named `"bash"` is a different tool. Handler paths and security checks are in `shared/tool-use-concepts.md` § Client-Side Tools. +- **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 the `code-execution-2025-08-25` beta (Skills is out of beta - no `skills-2025-10-02` header needed). 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:}]` 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`. (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: }` 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 (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. +- **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 / Claude Fable 5.1 (confirm at launch) / 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. +- **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. +- **Managed Agents web tools ignore the environment's `networking`.** `web_search` / `web_fetch` run on Anthropic's servers in cloud *and* self-hosted environments, and Console org-level web settings apply to the Messages API only. Restrict them per tool with `allowed_domains` **or** `blocked_domains` (never both; 1-64 plain hostnames per list, subdomains covered; IPs, bare TLDs, single-label and `localhost`-style names rejected on both tools; a path suffix is allowed only on `web_search`) on the toolset `configs` entry - `shared/managed-agents-tools.md` § Web search & web fetch settings. - **Code execution output block type:** `code_execution_20260521` returns `bash_code_execution_tool_result` (with `.content.stdout`), **not** the legacy bare `code_execution_tool_result`. Iterate `response.content` and match on the correct type. - **Tool search: never defer everything.** The search tool itself must not have `defer_loading: true`, and at least one tool in `tools` must be non-deferred, or the API returns 400 `All tools have defer_loading set`. diff --git a/skills/claude-api/csharp/claude-api/README.md b/skills/claude-api/csharp/claude-api/README.md index 9891518ce..4b04b5089 100644 --- a/skills/claude-api/csharp/claude-api/README.md +++ b/skills/claude-api/csharp/claude-api/README.md @@ -1,24 +1,24 @@ -# Claude API — C# +# Claude API - C# > **Note:** The C# SDK is the official Anthropic SDK for C#. Tool use is supported via the Messages API with a beta `BetaToolRunner` for automatic tool execution loops. The SDK also supports Microsoft.Extensions.AI IChatClient integration with function invocation and Managed Agents (beta). ## Namespace Reference -Types are organized by namespace. If a type you need isn't shown in an example below, locate it via this table first — don't block on fetching SDK source over the network. +Types are organized by namespace. If a type you need isn't shown in an example below, locate it via this table first - don't block on fetching SDK source over the network. | `using` | Contains | |---|---| | `Anthropic` | `AnthropicClient`, top-level options | -| `Anthropic.Models.Messages` | non-beta request/response types — `MessageCreateParams`, `Model`, `Role`, `ContentBlock`, `TextBlock`, `ToolUseBlock`, `ToolResultBlockParam`, `Tool*` (tool definition classes) | -| `Anthropic.Models.Beta.Messages` | beta-endpoint equivalents — `MessageCreateParams`, `BetaMessage`, `BetaTool*`, `Speed`, `BetaRequestMcpServerUrlDefinition`, context-editing/compaction configs | +| `Anthropic.Models.Messages` | non-beta request/response types - `MessageCreateParams`, `Model`, `Role`, `ContentBlock`, `TextBlock`, `ToolUseBlock`, `ToolResultBlockParam`, `Tool*` (tool definition classes) | +| `Anthropic.Models.Beta.Messages` | beta-endpoint equivalents - `MessageCreateParams`, `BetaMessage`, `BetaTool*`, `Speed`, `BetaRequestMcpServerUrlDefinition`, context-editing/compaction configs | | `Anthropic.Models.Beta` | shared beta constants | | `Anthropic.Models.Beta.Files` | Files API types | | `Anthropic.Models.Messages.Batches` | Batch API types | | `Anthropic.Helpers.Beta` | `BetaToolRunner`, beta helper utilities | -| `Anthropic.Exceptions` | `AnthropicApiException`, `AnthropicRateLimitException`, `Anthropic5xxException`, etc. — see `shared/error-codes.md` | +| `Anthropic.Exceptions` | `AnthropicApiException`, `AnthropicRateLimitException`, `Anthropic5xxException`, etc. - see `shared/error-codes.md` | | `Anthropic.Bedrock` / `Anthropic.Vertex` / `Anthropic.Foundry` / `Anthropic.Aws` | platform clients (separate NuGet packages): `AnthropicBedrockMantleClient`, `AnthropicFoundryClient`, `AnthropicAwsClient` | -`client.Messages.*` uses non-beta types; `client.Beta.Messages.*` uses the `Anthropic.Models.Beta.Messages` types. Both namespaces define a `MessageCreateParams` — pick the one matching the client path you call. +`client.Messages.*` uses non-beta types; `client.Beta.Messages.*` uses the `Anthropic.Models.Beta.Messages` types. Both namespaces define a `MessageCreateParams` - pick the one matching the client path you call. ### Key types per feature @@ -26,28 +26,28 @@ Write from this table instead of reflecting the SDK assembly. Endpoint column te | Feature | Endpoint | Key C# types (namespace per table above) | |---|---|---| -| User profiles | beta | `client.Beta.UserProfiles.Create(...)` / `.Retrieve(id)` / `.List()`. Pass the returned profile id on the beta messages call. Requires a beta header — check the SDK's beta-headers reference for the current flag. | -| Agent Skills | beta | `BetaContainerParams` (with `Skills = [new BetaSkillParams { ... }]`), `BetaCodeExecutionTool20250825`. `Betas = ["code-execution-2025-08-25", "skills-2025-10-02"]`. Download the output via `client.Beta.Files.Download(fileId)`. | -| Advisor tool | beta | `BetaAdvisorTool20260301` — may not be in all SDK releases yet | -| Cache diagnostics | beta | `Diagnostics = new() { PreviousMessageID = … }`, `BetaCacheControlEphemeral`, `BetaContentBlockParam` | -| Context editing | beta | `ContextManagement = new BetaContextManagementConfig { Edits = [new BetaClearToolUses20250919Edit()] }`. `Betas = ["context-management-2025-06-27"]` (not `compact-2026-01-12` — that's for `BetaCompact20260112Edit`). | +| User profiles | beta | `client.Beta.UserProfiles.Create(...)` / `.Retrieve(id)` / `.List()`. Pass the returned profile id on the beta messages call. Requires a beta header - check the SDK's beta-headers reference for the current flag. | +| Agent Skills | beta | `BetaContainerParams` (with `Skills = [new BetaSkillParams { ... }]`), `BetaCodeExecutionTool20250825`. `Betas = ["code-execution-2025-08-25"]` (Skills is out of beta - no `skills-2025-10-02`). Download the output via `client.Beta.Files.Download(fileId)`. | +| Advisor tool | beta | `BetaAdvisorTool20260301` - may not be in all SDK releases yet | +| Cache diagnostics | beta | `Diagnostics = new() { PreviousMessageID = ... }`, `BetaCacheControlEphemeral`, `BetaContentBlockParam` | +| Context editing | beta | `ContextManagement = new BetaContextManagementConfig { Edits = [new BetaClearToolUses20250919Edit()] }`. `Betas = ["context-management-2025-06-27"]` (not `compact-2026-01-12` - that's for `BetaCompact20260112Edit`). | | Memory tool | non-beta | `Tools = [new ToolUnion(new MemoryTool20250818())]` | | Programmatic tool calling | non-beta | `CodeExecutionTool20260120`, `ToolResultBlockParam`, `ContentBlockParam` | | Task budgets | beta | `BetaOutputConfig` with `TaskBudget = new BetaTokenTaskBudget { ... }` | -| Tool search | non-beta | `new ToolUnion(new ToolSearchToolRegex20251119 { Type = ToolSearchToolRegex20251119Type.ToolSearchToolRegex20251119 })` — `Type` must be set explicitly. | -| Web search | non-beta | `new ToolUnion(new WebSearchTool20260209())` — the latest variant with dynamic filtering (Claude Fable 5 + Claude Opus 5 + Opus 4.8/4.7/4.6 + Claude Sonnet 5 + Sonnet 4.6). For older models or Vertex, use `WebSearchTool20250305()` | +| Tool search | non-beta | `new ToolUnion(new ToolSearchToolRegex20251119 { Type = ToolSearchToolRegex20251119Type.ToolSearchToolRegex20251119 })` - `Type` must be set explicitly. | +| Web search | non-beta | `new ToolUnion(new WebSearchTool20260209())` - the latest variant with dynamic filtering (Claude Fable 5.1 + Claude Opus 5 + Opus 4.8/4.7/4.6 + Claude Sonnet 5 + Sonnet 4.6). For older models or Vertex, use `WebSearchTool20250305()` | ### Discovering type and member names -If a type or member you need isn't in the tables above, `strings ~/.nuget/packages/anthropic/*/lib/*/Anthropic.dll | grep -i ` is fast and sufficient for locating class and property names. **Do not escalate to a `dotnet run` reflection probe** to dump members precisely — the first compile is slow enough to be backgrounded in many environments, trapping you in a polling loop. Instead, write `Program.cs` using the names `strings | grep` found; if a member name is wrong the compiler error (`error CS1061: 'X' does not contain a definition for 'Y'`) points at it in a few seconds, faster than any reflection probe. +If a type or member you need isn't in the tables above, `strings ~/.nuget/packages/anthropic/*/lib/*/Anthropic.dll | grep -i ` is fast and sufficient for locating class and property names. **Do not escalate to a `dotnet run` reflection probe** to dump members precisely - the first compile is slow enough to be backgrounded in many environments, trapping you in a polling loop. Instead, write `Program.cs` using the names `strings | grep` found; if a member name is wrong the compiler error (`error CS1061: 'X' does not contain a definition for 'Y'`) points at it in a few seconds, faster than any reflection probe. -Note that `strings` will not surface wire-format snake_case field names (`output_tokens`, `stop_reason`) — those are stored in the DLL differently. **C# properties are the PascalCase equivalent of the wire field** (`response.Usage.OutputTokens`, `response.StopReason`). If you know the wire field name from the docs, write the PascalCase property and compile; do not probe for the snake_case string. +Note that `strings` will not surface wire-format snake_case field names (`output_tokens`, `stop_reason`) - those are stored in the DLL differently. **C# properties are the PascalCase equivalent of the wire field** (`response.Usage.OutputTokens`, `response.StopReason`). If you know the wire field name from the docs, write the PascalCase property and compile; do not probe for the snake_case string. ### Minimal working skeleton -**Write a plain `Program.cs` body** — `using` statements followed by top-level statements, as below. Do **not** add a `#!/usr/bin/env dotnet` shebang or `#:package Anthropic@*` directive: those are .NET file-based-app syntax and fail with `CS1024: Preprocessor directive expected` when the file is compiled via an existing `.csproj`. The standard project setup (per the [C# quickstart](https://platform.claude.com/docs/en/get-started): `dotnet new console` → `dotnet add package Anthropic` → edit `Program.cs` → `dotnet run`) provides the `.csproj` and package reference. +**Write a plain `Program.cs` body** - `using` statements followed by top-level statements, as below. Do **not** add a `#!/usr/bin/env dotnet` shebang or `#:package Anthropic@*` directive: those are .NET file-based-app syntax and fail with `CS1024: Preprocessor directive expected` when the file is compiled via an existing `.csproj`. The standard project setup (per the [C# quickstart](https://platform.claude.com/docs/en/get-started): `dotnet new console` -> `dotnet add package Anthropic` -> edit `Program.cs` -> `dotnet run`) provides the `.csproj` and package reference. -Start from this — it compiles as-is. Fill in the feature-specific fields; do not spend turns running reflection or XML-doc inspection to discover type names first. +Start from this - it compiles as-is. Fill in the feature-specific fields; do not spend turns running reflection or XML-doc inspection to discover type names first. ```csharp using System; @@ -66,7 +66,7 @@ var message = await client.Messages.Create(new MessageCreateParams Console.WriteLine(message); ``` -For beta features (anything behind an `anthropic-beta` header), use the beta client path and namespace — same overall shape: +For beta features (anything behind an `anthropic-beta` header), use the beta client path and namespace - same overall shape: ```csharp using System; @@ -80,19 +80,19 @@ var response = await client.Beta.Messages.Create(new MessageCreateParams Model = "claude-opus-5", MaxTokens = 4096, Betas = [""], - Messages = [ new() { Role = Role.User, Content = "…" } ], - // Tools = new BetaToolUnion[] { new BetaSomeTool { … } }, // for tool features + Messages = [ new() { Role = Role.User, Content = "..." } ], + // Tools = new BetaToolUnion[] { new BetaSomeTool { ... } }, // for tool features }); Console.WriteLine(response); ``` -If a type name the feature needs isn't in this file, write it following the naming pattern in the Namespace Reference above and fix from compiler output — producing a `Program.cs` and iterating beats researching. +If a type name the feature needs isn't in this file, write it following the naming pattern in the Namespace Reference above and fix from compiler output - producing a `Program.cs` and iterating beats researching. ### Common C# compile errors - **CS8803 (top-level statements must precede type declarations):** put any `record`/`class`/`struct` definitions **after** the last top-level statement, at the end of the file. A record defined above `var client = new AnthropicClient()` will not compile. -- **`await foreach` on a `Task<…Page>`:** `client.Models.List()` returns a `Task`, which is not directly async-enumerable. Await it first, then iterate: `var page = await client.Models.List(); foreach (var m in page.Items) {…}`. For auto-pagination, check whether the page type exposes `AutoPagingEachAsync()` or similar before reaching for `await foreach`. +- **`await foreach` on a `Task<...Page>`:** `client.Models.List()` returns a `Task`, which is not directly async-enumerable. Await it first, then iterate: `var page = await client.Models.List(); foreach (var m in page.Items) {...}`. For auto-pagination, check whether the page type exposes `AutoPagingEachAsync()` or similar before reaching for `await foreach`. ## Installation @@ -108,7 +108,7 @@ using Anthropic; // Default (uses ANTHROPIC_API_KEY env var) AnthropicClient client = new(); -// Explicit API key (use environment variables — never hardcode keys) +// Explicit API key (use environment variables - never hardcode keys) AnthropicClient client = new() { ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") }; @@ -145,7 +145,7 @@ foreach (var text in response.Content.Select(b => b.Value).OfType()) **Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think. > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking (below). `new ThinkingConfigEnabled { BudgetTokens = N }` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `Thinking` runs adaptive (`ThinkingConfigAdaptive` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `ThinkingConfigDisabled` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `Thinking` runs adaptive (`ThinkingConfigAdaptive` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `ThinkingConfigDisabled` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. > **Older models:** Use `new ThinkingConfigEnabled { BudgetTokens = N }` (budget must be < `MaxTokens`, min 1024). ```csharp @@ -155,7 +155,7 @@ var response = await client.Messages.Create(new MessageCreateParams { Model = "claude-opus-5", MaxTokens = 16000, - // ThinkingConfigParam? implicitly converts from the concrete variant classes — + // ThinkingConfigParam? implicitly converts from the concrete variant classes - // no wrapper needed. // display opt-in: default is omitted (empty thinking text) on Fable 5 / Mythos 5 / Claude Opus 5 / Opus 4.8 / 4.7 Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized }, @@ -194,12 +194,12 @@ using NonBeta = Anthropic.Models.Messages; // only if you also need non-beta ty ``` -`BetaMessage.Content` is `IReadOnlyList` — a 15-variant discriminated union. Narrow with `TryPick*`. **Response `BetaContentBlock` is NOT assignable to param `BetaContentBlockParam`** — there's no `.ToParam()` in C#. Round-trip by converting each block: +`BetaMessage.Content` is `IReadOnlyList` - a 15-variant discriminated union. Narrow with `TryPick*`. **Response `BetaContentBlock` is NOT assignable to param `BetaContentBlockParam`** - there's no `.ToParam()` in C#. Round-trip by converting each block: ```csharp using Anthropic.Models.Beta.Messages; -var betaParams = new MessageCreateParams // no Beta prefix — see unprefixed list above +var betaParams = new MessageCreateParams // no Beta prefix - see unprefixed list above { Model = "claude-opus-5", MaxTokens = 16000, @@ -216,7 +216,7 @@ foreach (BetaContentBlock block in resp.Content) { if (block.TryPickCompaction(out BetaCompactionBlock? compaction)) { - // Content is nullable — compaction can fail server-side + // Content is nullable - compaction can fail server-side Console.WriteLine($"compaction summary: {compaction.Content}"); } } @@ -243,7 +243,7 @@ messages.Add(new BetaMessageParam { Role = Role.Assistant, Content = paramBlocks All 15 `BetaContentBlock.TryPick*` variants: `Text`, `Thinking`, `RedactedThinking`, `ToolUse`, `ServerToolUse`, `WebSearchToolResult`, `WebFetchToolResult`, `CodeExecutionToolResult`, `BashCodeExecutionToolResult`, `TextEditorCodeExecutionToolResult`, `ToolSearchToolResult`, `McpToolUse`, `McpToolResult`, `ContainerUpload`, `Compaction`. -**`BetaToolUseBlock.Input` is `IReadOnlyDictionary`** — index by key then call the `JsonElement` extractor: +**`BetaToolUseBlock.Input` is `IReadOnlyDictionary`** - index by key then call the `JsonElement` extractor: ```csharp if (block.TryPickToolUse(out BetaToolUseBlock? tu)) @@ -269,7 +269,7 @@ Values: `Effort.Low`, `Effort.Medium`, `Effort.High`, `Effort.Max`. Combine with ## Prompt Caching -`System` takes `MessageCreateParamsSystem?` — a union of `string` or `List`. There is no `SystemTextBlockParam`; use plain `TextBlockParam`. The implicit conversion needs the concrete `List` type (array literals won't convert). For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. +`System` takes `MessageCreateParamsSystem?` - a union of `string` or `List`. There is no `SystemTextBlockParam`; use plain `TextBlockParam`. The implicit conversion needs the concrete `List` type (array literals won't convert). For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. ```csharp System = new List { @@ -296,7 +296,7 @@ MessageTokensCount result = await client.Messages.CountTokens(new MessageCountTo long tokens = result.InputTokens; ``` -`MessageCountTokensParams.Tools` uses a different union type (`MessageCountTokensTool`) than `MessageCreateParams.Tools` (`ToolUnion`) — if you're passing tools, the compiler will tell you when it matters. +`MessageCountTokensParams.Tools` uses a different union type (`MessageCountTokensTool`) than `MessageCreateParams.Tools` (`ToolUnion`) - if you're passing tools, the compiler will tell you when it matters. --- @@ -337,9 +337,9 @@ var one = await client.Models.Retrieve("claude-opus-5"); ## Long Output (128k) + Prefill -Set `MaxTokens = 128000` on `client.Messages` and use the streaming path (see `streaming.md`). On Claude 4+ models, 128k output is native — no `output-128k-*` beta header or beta namespace is needed. +Set `MaxTokens = 128000` on `client.Messages` and use the streaming path (see `streaming.md`). On Claude 4+ models, 128k output is native - no `output-128k-*` beta header or beta namespace is needed. -**Prefilling the assistant message** (putting a trailing `Role.Assistant` message in the input) is **not supported** on Claude Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 — requests return a 400. Use `OutputConfig.Format` (structured outputs) instead. +**Prefilling the assistant message** (putting a trailing `Role.Assistant` message in the input) is **not supported** on Claude Fable 5.1, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 - requests return a 400. Use `OutputConfig.Format` (structured outputs) instead. ## Stop Details @@ -353,7 +353,7 @@ if (response.StopReason == "refusal" && response.StopDetails is { } details) } ``` -**Refusal fallbacks (Claude Fable 5) — opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. In `claude-fable-5` code, include `Fallbacks = [new(Model.ClaudeOpus4_8)]` plus the `AnthropicBeta.ServerSideFallback2026_06_01` beta on the beta messages call by default — a policy decline is re-served by the fallback model inside the same call. Not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry — use the client-side handler there: `new AnthropicClient { Handlers = [new BetaRefusalFallbackHandler { Fallbacks = [new(Model.ClaudeOpus4_8)] }] }` (namespace `Anthropic.Helpers`), with per-conversation state via `BetaFallbackState.Create()` scoped with `using (fallbackState.Use()) { ... }`. Full semantics (billing, sticky routing, streaming) and a runnable example: `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason, and the C# SDK repo's `examples/` (WebFetch via `shared/live-sources.md`). +**Refusal fallbacks (Claude Fable 5.1) - opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. In `claude-fable-5-1` code, include `Fallbacks = [new(Model.ClaudeOpus4_8)]` plus the `AnthropicBeta.ServerSideFallback2026_06_01` beta on the beta messages call by default - a policy decline is re-served by the fallback model inside the same call. Not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry - use the client-side handler there: `new AnthropicClient { Handlers = [new BetaRefusalFallbackHandler { Fallbacks = [new(Model.ClaudeOpus4_8)] }] }` (namespace `Anthropic.Helpers`), with per-conversation state via `BetaFallbackState.Create()` scoped with `using (fallbackState.Use()) { ... }`. Full semantics (billing, sticky routing, streaming) and a runnable example: `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason, and the C# SDK repo's `examples/` (WebFetch via `shared/live-sources.md`). --- diff --git a/skills/claude-api/csharp/claude-api/batches.md b/skills/claude-api/csharp/claude-api/batches.md index c066bef6c..3b4003ce6 100644 --- a/skills/claude-api/csharp/claude-api/batches.md +++ b/skills/claude-api/csharp/claude-api/batches.md @@ -1,4 +1,4 @@ -# Message Batches — C# +# Message Batches - C# ## Message Batches API diff --git a/skills/claude-api/csharp/claude-api/files-api.md b/skills/claude-api/csharp/claude-api/files-api.md index a4232bd76..46f85ca24 100644 --- a/skills/claude-api/csharp/claude-api/files-api.md +++ b/skills/claude-api/csharp/claude-api/files-api.md @@ -1,6 +1,8 @@ -# Files API — C# +# Files API - C# -## Files API (Beta) +## Files API + +> **Out of beta.** In current SDKs `client.Beta.Files` has breaking shape changes from previous versions, matching the stable `client.Files` - migrate per the Files API row in `shared/live-sources.md`. Examples below predate this. Files live under `client.Beta.Files` (namespace `Anthropic.Models.Beta.Files`). `BinaryContent` implicit-converts from `Stream` and `byte[]`. @@ -17,7 +19,7 @@ new BetaRequestDocumentBlock { } ``` -The non-beta `DocumentBlockParamSource` union has no file-ID variant — file references need `client.Beta.Messages.Create()`. +The non-beta `DocumentBlockParamSource` union has no file-ID variant - file references need `client.Beta.Messages.Create()`. --- diff --git a/skills/claude-api/csharp/claude-api/streaming.md b/skills/claude-api/csharp/claude-api/streaming.md index d06c9d32f..be16108e5 100644 --- a/skills/claude-api/csharp/claude-api/streaming.md +++ b/skills/claude-api/csharp/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — C# +# Streaming - C# ## Streaming @@ -22,7 +22,7 @@ await foreach (RawMessageStreamEvent streamEvent in client.Messages.CreateStream } ``` -**`RawMessageStreamEvent` TryPick methods** (naming drops the `Message`/`Raw` prefix): `TryPickStart`, `TryPickDelta`, `TryPickStop`, `TryPickContentBlockStart`, `TryPickContentBlockDelta`, `TryPickContentBlockStop`. There is no `TryPickMessageStop` — use `TryPickStop`. +**`RawMessageStreamEvent` TryPick methods** (naming drops the `Message`/`Raw` prefix): `TryPickStart`, `TryPickDelta`, `TryPickStop`, `TryPickContentBlockStart`, `TryPickContentBlockDelta`, `TryPickContentBlockStop`. There is no `TryPickMessageStop` - use `TryPickStop`. --- diff --git a/skills/claude-api/csharp/claude-api/tool-use.md b/skills/claude-api/csharp/claude-api/tool-use.md index 4ce612430..e6cc63751 100644 --- a/skills/claude-api/csharp/claude-api/tool-use.md +++ b/skills/claude-api/csharp/claude-api/tool-use.md @@ -1,4 +1,4 @@ -# Tool Use — C# +# Tool Use - C# For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). @@ -6,7 +6,7 @@ For conceptual overview (tool definitions, tool choice, tips), see [shared/tool- ### Defining a tool -`Tool` (NOT `ToolParam`) with an `InputSchema` record. `InputSchema.Type` is auto-set to `"object"` by the constructor — don't set it. `ToolUnion` has an implicit conversion from `Tool`, triggered by the collection expression `[...]`. +`Tool` (NOT `ToolParam`) with an `InputSchema` record. `InputSchema.Type` is auto-set to `"object"` by the constructor - don't set it. `ToolUnion` has an implicit conversion from `Tool`, triggered by the collection expression `[...]`. ```csharp using System.Text.Json; @@ -38,14 +38,14 @@ Derived from `anthropic-sdk-csharp/src/Anthropic/Models/Messages/Tool.cs` and `T See [shared tool use concepts](../../shared/tool-use-concepts.md) for the loop pattern. ### Converting response content to the follow-up assistant message -When echoing Claude's response back in the assistant turn, **there is no `.ToParam()` helper** — manually reconstruct each `ContentBlock` variant as its `*Param` counterpart. Do NOT use `new ContentBlockParam(block.Json)`: it compiles and serializes, but `.Value` stays `null` so `TryPick*`/`Validate()` fail (degraded JSON pass-through, not the typed path). +When echoing Claude's response back in the assistant turn, **there is no `.ToParam()` helper** - manually reconstruct each `ContentBlock` variant as its `*Param` counterpart. Do NOT use `new ContentBlockParam(block.Json)`: it compiles and serializes, but `.Value` stays `null` so `TryPick*`/`Validate()` fail (degraded JSON pass-through, not the typed path). ```csharp using Anthropic.Models.Messages; Message response = await client.Messages.Create(parameters); -// No .ToParam() — reconstruct per variant. Implicit conversions from each +// No .ToParam() - reconstruct per variant. Implicit conversions from each // *Param type to ContentBlockParam mean no explicit wrapper. List assistantContent = []; List toolResults = []; @@ -57,7 +57,7 @@ foreach (ContentBlock block in response.Content) } else if (block.TryPickThinking(out ThinkingBlock? thinking)) { - // Signature MUST be preserved — the API rejects tampering + // Signature MUST be preserved - the API rejects tampering assistantContent.Add(new ThinkingBlockParam { Thinking = thinking.Thinking, @@ -70,14 +70,14 @@ foreach (ContentBlock block in response.Content) } else if (block.TryPickToolUse(out ToolUseBlock? toolUse)) { - // ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional — don't copy it + // ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional - don't copy it assistantContent.Add(new ToolUseBlockParam { ID = toolUse.ID, Name = toolUse.Name, Input = toolUse.Input, }); - // Execute the tool; collect ONE result per tool_use block — the API + // Execute the tool; collect ONE result per tool_use block - the API // rejects the follow-up if any tool_use ID lacks a matching tool_result. string result = ExecuteYourTool(toolUse.Name, toolUse.Input); toolResults.Add(new ToolResultBlockParam @@ -97,7 +97,7 @@ List followUpMessages = ]; ``` -`ToolResultBlockParam` has no tuple constructor — use the object initializer. `Content` is a string-or-list union; a plain `string` implicitly converts. +`ToolResultBlockParam` has no tuple constructor - use the object initializer. `Content` is a string-or-list union; a plain `string` implicitly converts. --- @@ -122,7 +122,7 @@ OutputConfig = new OutputConfig { ## Anthropic-Defined Tools -Web search, bash, text editor, and code execution are Anthropic-defined tools with built-in schemas. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally — see `shared/tool-use-concepts.md`). Type names are version-suffixed; constructors auto-set `name`/`type`. **Wrap each in `new ToolUnion(...)` explicitly.** +Web search, bash, text editor, and code execution are Anthropic-defined tools with built-in schemas. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally - see `shared/tool-use-concepts.md`). Type names are version-suffixed; constructors auto-set `name`/`type`. **Wrap each in `new ToolUnion(...)` explicitly.** ```csharp Tools = [ @@ -139,7 +139,7 @@ Also available: `new ToolUnion(new WebFetchTool20260209())`, `new ToolUnion(new ## Tool Runner (Beta) -The C# SDK provides a `BetaToolRunner` for automatic tool execution loops. Define tools with raw JSON schemas, and the runner handles the API call → tool execution → result feedback loop. +The C# SDK provides a `BetaToolRunner` for automatic tool execution loops. Define tools with raw JSON schemas, and the runner handles the API call -> tool execution -> result feedback loop. ```csharp using Anthropic.Models.Beta.Messages; diff --git a/skills/claude-api/curl/examples.md b/skills/claude-api/curl/examples.md index ba93ea217..f38b88e5b 100644 --- a/skills/claude-api/curl/examples.md +++ b/skills/claude-api/curl/examples.md @@ -1,4 +1,4 @@ -# Claude API — cURL / Raw HTTP +# Claude API - cURL / Raw HTTP Use these examples when the user needs raw HTTP requests or is working in a language without an official SDK. @@ -28,7 +28,7 @@ curl https://api.anthropic.com/v1/messages \ ### Parsing the response -Use `jq` to extract fields from the JSON response. Do not use `grep`/`sed` — +Use `jq` to extract fields from the JSON response. Do not use `grep`/`sed` - JSON strings can contain any character and regex parsing will break on quotes, escapes, or multi-line content. @@ -183,7 +183,7 @@ For 1-hour TTL: `"cache_control": {"type": "ephemeral", "ttl": "1h"}`. Top-level ## Extended Thinking > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking. `budget_tokens` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `"thinking"` runs adaptive (`{"type": "adaptive"}` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{"type": "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `"thinking"` runs adaptive (`{"type": "adaptive"}` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{"type": "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. > **Older models:** Use `"type": "enabled"` with `"budget_tokens": N` (must be < `max_tokens`, min 1024). ```bash @@ -208,9 +208,9 @@ curl https://api.anthropic.com/v1/messages \ --- -## Refusal Fallbacks (Claude Fable 5) — opt in by default +## Refusal Fallbacks (Claude Fable 5.1) - opt in by default -On `claude-fable-5`, safety classifiers may decline a request (HTTP 200 with `stop_reason: "refusal"`). Fallbacks are **opt-in**: without them the request simply stops. Include the `fallbacks` parameter and its beta header by default — on a policy decline the API re-runs the same request on the fallback model inside the same call. A decline before any output isn't billed (a mid-stream decline bills the streamed partial); the rescue bills at the fallback model's own rates. +On `claude-fable-5-1`, safety classifiers may decline a request (HTTP 200 with `stop_reason: "refusal"`). Fallbacks are **opt-in**: without them the request simply stops. Include the `fallbacks` parameter and its beta header by default - on a policy decline the API re-runs the same request on the fallback model inside the same call. A decline before any output isn't billed (a mid-stream decline bills the streamed partial); the rescue bills at the fallback model's own rates. ```bash response=$(curl -s https://api.anthropic.com/v1/messages \ @@ -219,7 +219,7 @@ response=$(curl -s https://api.anthropic.com/v1/messages \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: server-side-fallback-2026-06-01" \ -d '{ - "model": "claude-fable-5", + "model": "claude-fable-5-1", "max_tokens": 16000, "fallbacks": [{"model": "claude-opus-4-8"}], "messages": [{"role": "user", "content": "Hello"}] @@ -234,7 +234,7 @@ echo "$response" | jq -r '.stop_reason' # Switch points: one fallback block per model that ran and declined this turn echo "$response" | jq -r '.content[] | select(.type == "fallback") | "\(.from.model) declined; \(.to.model) continued"' -# Served-by signal — covers sticky turns, which carry no fallback block. +# Served-by signal - covers sticky turns, which carry no fallback block. # Pair with stop_reason: the fallback model can itself refuse. if [ "$(echo "$response" | jq -r '.stop_reason')" != "refusal" ] && \ echo "$response" | jq -e '[.usage.iterations[]? | select(.type == "fallback_message")] | length > 0' > /dev/null; then @@ -242,7 +242,7 @@ if [ "$(echo "$response" | jq -r '.stop_reason')" != "refusal" ] && \ fi ``` -The header must be exactly `server-side-fallback-2026-06-01` **for this array form**; the newer `fallbacks: "default"` scalar form uses `server-side-fallback-2026-07-01` instead (see `shared/model-migration.md` → Migrating to Claude Opus 5 → New API features), and pairing either header with the other form returns a 400. The parameter is rejected on the Batches API and unavailable on Amazon Bedrock, Vertex AI, and Microsoft Foundry. Full semantics (sticky routing, billing, streaming, echoing fallback turns back): `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason. +The header must be exactly `server-side-fallback-2026-06-01` **for this array form**; the newer `fallbacks: "default"` scalar form uses `server-side-fallback-2026-07-01` instead (see `shared/model-migration.md` -> Migrating to Claude Opus 5 -> New API features), and pairing either header with the other form returns a 400. The parameter is rejected on the Batches API and unavailable on Amazon Bedrock, Vertex AI, and Microsoft Foundry. Full semantics (sticky routing, billing, streaming, echoing fallback turns back): `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason. --- diff --git a/skills/claude-api/curl/managed-agents.md b/skills/claude-api/curl/managed-agents.md index aead72d74..8f52314b6 100644 --- a/skills/claude-api/curl/managed-agents.md +++ b/skills/claude-api/curl/managed-agents.md @@ -1,4 +1,4 @@ -# Managed Agents — cURL / Raw HTTP +# Managed Agents - cURL / Raw HTTP Use these examples when the user needs raw HTTP requests or is working without an SDK. @@ -55,7 +55,7 @@ curl -X POST https://api.anthropic.com/v1/environments \ ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** Under `managed-agents-2026-04-01`, `model`/`system`/`tools` are top-level fields on `POST /v1/agents`, not on the session. Always create the agent first — the session only takes `"agent": {"type": "agent", "id": "..."}`. +> Warning: **There is no inline agent config.** Under `managed-agents-2026-04-01`, `model`/`system`/`tools` are top-level fields on `POST /v1/agents`, not on the session. Always create the agent first - the session only takes `"agent": {"type": "agent", "id": "..."}`. ### Minimal @@ -68,7 +68,7 @@ curl -X POST https://api.anthropic.com/v1/agents \ "model": "claude-opus-5", "tools": [{ "type": "agent_toolset_20260401" }] }' -# → { "id": "agent_abc123", ... } +# -> { "id": "agent_abc123", ... } # 2. Start a session curl -X POST https://api.anthropic.com/v1/sessions \ @@ -77,7 +77,7 @@ curl -X POST https://api.anthropic.com/v1/sessions \ "agent": { "type": "agent", "id": "agent_abc123", "version": 1 }, "environment_id": "env_abc123" }' -# → { "id": "sesn_abc123", ... } +# -> { "id": "sesn_abc123", ... } # Trace: https://platform.claude.com/workspaces/default/sessions/sesn_abc123 (swap 'default' for your workspace ID if the API key is not in the Default workspace) ``` @@ -143,13 +143,13 @@ curl -X POST https://api.anthropic.com/v1/sessions \ } }' -# Change the cap — higher or lower, but it must exceed the consumed list cost. +# 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 +# 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 }' @@ -205,7 +205,7 @@ data: {"type":"session.status_idle","id":"sevt_...","processed_at":"..."} curl https://api.anthropic.com/v1/sessions/$SESSION_ID/events \ "${HEADERS[@]}" -# Paginated — get next page of events +# Paginated - get next page of events curl "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?page=page_abc123" \ "${HEADERS[@]}" ``` @@ -321,7 +321,7 @@ curl https://api.anthropic.com/v1/agents \ ## MCP Server Integration ```bash -# 1. Agent declares MCP server (no auth here — auth goes in a vault) +# 1. Agent declares MCP server (no auth here - auth goes in a vault) curl -X POST https://api.anthropic.com/v1/agents \ "${HEADERS[@]}" \ -d '{ diff --git a/skills/claude-api/go/claude-api/README.md b/skills/claude-api/go/claude-api/README.md index 958582c33..2267a7d17 100644 --- a/skills/claude-api/go/claude-api/README.md +++ b/skills/claude-api/go/claude-api/README.md @@ -1,4 +1,4 @@ -# Claude API — Go +# Claude API - Go > **Note:** The Go SDK supports the Claude API and beta tool use with `BetaToolRunner`. Agent SDK is not yet available for Go. @@ -31,7 +31,7 @@ client := anthropic.NewClient( The Go SDK provides typed model constants: `anthropic.ModelClaudeFable5`, `anthropic.ModelClaudeOpus4_8`, `anthropic.ModelClaudeOpus4_7`, `anthropic.ModelClaudeSonnet4_6`, `anthropic.ModelClaudeHaiku4_5_20251001`. Default to Claude Opus 5 unless the user specifies otherwise; if they ask for Fable or the most powerful model, use `anthropic.ModelClaudeFable5` (see `shared/models.md` for the full resolution table). -`anthropic.Model` is an alias for `string`, so a model with no typed constant yet — including Claude Opus 5 — is passed as the plain id: `Model: "claude-opus-5"`. Check the SDK release notes for a typed `Claude Opus 5` constant before assuming one exists. +`anthropic.Model` is an alias for `string`, so a model with no typed constant yet - including Claude Opus 5 - is passed as the plain id: `Model: "claude-opus-5"`. Check the SDK release notes for a typed `Claude Opus 5` constant before assuming one exists. --- @@ -67,7 +67,7 @@ Enable Claude's internal reasoning by setting `Thinking` in `MessageNewParams`. Derived from `anthropic-sdk-go/message.go` (`ThinkingConfigParamUnion`, `ThinkingConfigAdaptiveParam`). ```go -// There is no ThinkingConfigParamOfAdaptive helper — construct the union +// There is no ThinkingConfigParamOfAdaptive helper - construct the union // struct-literal directly and take the address of the variant. adaptive := anthropic.ThinkingConfigAdaptiveParam{} params := anthropic.MessageNewParams{ @@ -96,10 +96,10 @@ for _, block := range resp.Content { ``` > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking (above). `ThinkingConfigParamOfEnabled(budgetTokens)` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — leaving `Thinking` unset runs adaptive (the adaptive union is equivalent), unlike Opus 4.8/4.7 where leaving it unset meant no thinking. +> **Claude Opus 5:** thinking is on by default - leaving `Thinking` unset runs adaptive (the adaptive union is equivalent), unlike Opus 4.8/4.7 where leaving it unset meant no thinking. > **Older models:** Use `anthropic.ThinkingConfigParamOfEnabled(N)` (budget must be < `MaxTokens`, min 1024). -To disable: `anthropic.ThinkingConfigParamUnion{OfDisabled: &anthropic.ThinkingConfigDisabledParam{}}`. On Claude Opus 5 that is accepted only at effort `high` or lower — pairing it with `xhigh`/`max` returns a 400. +To disable: `anthropic.ThinkingConfigParamUnion{OfDisabled: &anthropic.ThinkingConfigDisabledParam{}}`. On Claude Opus 5 that is accepted only at effort `high` or lower - pairing it with `xhigh`/`max` returns a 400. --- @@ -126,12 +126,12 @@ When `StopReason` is `anthropic.StopReasonRefusal`, the response includes struct ```go if resp.StopReason == anthropic.StopReasonRefusal { - fmt.Println("Category:", resp.StopDetails.Category) // e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or "" — see docs for the full set + fmt.Println("Category:", resp.StopDetails.Category) // e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or "" - see docs for the full set fmt.Println("Explanation:", resp.StopDetails.Explanation) } ``` -**Refusal fallbacks (Claude Fable 5) — opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. In `claude-fable-5` code, include `Fallbacks: []anthropic.BetaFallbackParam{{Model: "claude-opus-4-8"}}` plus the `anthropic.AnthropicBetaServerSideFallback2026_06_01` beta on `client.Beta.Messages.New` by default — a policy decline is re-served by the fallback model inside the same call. Not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry — register the client-side middleware there: `option.WithMiddleware(betafallback.BetaRefusalFallbackMiddleware(...))` from `lib/betafallback`, with per-conversation state via `betafallback.WithBetaFallbackState(&betafallback.BetaFallbackState{})`. Full semantics (billing, sticky routing, streaming) and a runnable example: `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason, and the Go SDK repo's `examples/` (WebFetch via `shared/live-sources.md`). +**Refusal fallbacks (Claude Fable 5.1) - opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. In `claude-fable-5-1` code, include `Fallbacks: []anthropic.BetaFallbackParam{{Model: "claude-opus-4-8"}}` plus the `anthropic.AnthropicBetaServerSideFallback2026_06_01` beta on `client.Beta.Messages.New` by default - a policy decline is re-served by the fallback model inside the same call. Not available on Amazon Bedrock, Vertex AI, or Microsoft Foundry - register the client-side middleware there: `option.WithMiddleware(betafallback.BetaRefusalFallbackMiddleware(...))` from `lib/betafallback`, with per-conversation state via `betafallback.WithBetaFallbackState(&betafallback.BetaFallbackState{})`. Full semantics (billing, sticky routing, streaming) and a runnable example: `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason, and the Go SDK repo's `examples/` (WebFetch via `shared/live-sources.md`). --- @@ -154,7 +154,7 @@ Other sources: `URLPDFSourceParam{URL: "https://..."}`, `PlainTextSourceParam{Da ## Context Editing / Compaction (Beta) -Use `Beta.Messages.New` with `ContextManagement` on `BetaMessageNewParams`. There is no `NewBetaAssistantMessage` — use `.ToParam()` for the round-trip. +Use `Beta.Messages.New` with `ContextManagement` on `BetaMessageNewParams`. There is no `NewBetaAssistantMessage` - use `.ToParam()` for the round-trip. ```go params := anthropic.BetaMessageNewParams{ @@ -185,4 +185,4 @@ for _, block := range resp.Content { } ``` -Other edit types: `BetaClearToolUses20250919EditParam`, `BetaClearThinking20251015EditParam` — these need `Betas: []anthropic.AnthropicBeta{"context-management-2025-06-27"}`, not `compact-2026-01-12`. +Other edit types: `BetaClearToolUses20250919EditParam`, `BetaClearThinking20251015EditParam` - these need `Betas: []anthropic.AnthropicBeta{"context-management-2025-06-27"}`, not `compact-2026-01-12`. diff --git a/skills/claude-api/go/claude-api/files-api.md b/skills/claude-api/go/claude-api/files-api.md index edf3c0131..b62f163fc 100644 --- a/skills/claude-api/go/claude-api/files-api.md +++ b/skills/claude-api/go/claude-api/files-api.md @@ -1,6 +1,8 @@ -# Files API — Go +# Files API - Go -## Files API (Beta) +## Files API + +> **Out of beta.** In current SDKs `client.Beta.Files` has breaking shape changes from previous versions, matching the stable `client.Files` - migrate per the Files API row in `shared/live-sources.md`. Examples below predate this. Under `client.Beta.Files`. Method is **`Upload`** (NOT `New`/`Create`), params struct is `BetaFileUploadParams`. The `File` field takes an `io.Reader`; use `anthropic.File()` to attach a filename + content-type for the multipart encoding. diff --git a/skills/claude-api/go/claude-api/streaming.md b/skills/claude-api/go/claude-api/streaming.md index 61ec32f62..72e44cb07 100644 --- a/skills/claude-api/go/claude-api/streaming.md +++ b/skills/claude-api/go/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — Go +# Streaming - Go ## Streaming diff --git a/skills/claude-api/go/claude-api/tool-use.md b/skills/claude-api/go/claude-api/tool-use.md index 45fff7d94..e9a301cff 100644 --- a/skills/claude-api/go/claude-api/tool-use.md +++ b/skills/claude-api/go/claude-api/tool-use.md @@ -1,10 +1,10 @@ -# Tool Use — Go +# Tool Use - Go For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). ## Tool Use -### Tool Runner (Beta — Recommended) +### Tool Runner (Beta - Recommended) **Beta:** The Go SDK provides `BetaToolRunner` for automatic tool use loops via the `toolrunner` package. @@ -61,7 +61,7 @@ if err != nil { } // RunToCompletion returns *BetaMessage; content is []BetaContentBlockUnion. -// Narrow via AsAny() switch — note the Beta-namespace types (BetaTextBlock, +// Narrow via AsAny() switch - note the Beta-namespace types (BetaTextBlock, // not TextBlock): for _, block := range message.Content { switch block := block.AsAny().(type) { @@ -81,7 +81,7 @@ for _, block := range message.Content { ### Manual Loop -Prefer the tool runner above. For interception, validation, logging, or human-in-the-loop approval, gate inside the tool's run function or step the runner with `NextMessage()`/`All()` and inspect each message (the runner's public `Params` field lets you adjust the next request) — a manual loop is not required. Drop to a manual loop only when you need control the runner does not expose: define tools with `ToolParam`, check `StopReason`, execute tools yourself, and feed `tool_result` blocks back. +Prefer the tool runner above. For interception, validation, logging, or human-in-the-loop approval, gate inside the tool's run function or step the runner with `NextMessage()`/`All()` and inspect each message (the runner's public `Params` field lets you adjust the next request) - a manual loop is not required. Drop to a manual loop only when you need control the runner does not expose: define tools with `ToolParam`, check `StopReason`, execute tools yourself, and feed `tool_result` blocks back. Derived from `anthropic-sdk-go/examples/tools/main.go`. @@ -130,7 +130,7 @@ func main() { } // 2. Append the assistant response to history BEFORE processing tool calls. - // resp.ToParam() converts Message → MessageParam in one call. + // resp.ToParam() converts Message -> MessageParam in one call. messages = append(messages, resp.ToParam()) // 3. Walk content blocks. ContentBlockUnion is a flattened struct; @@ -142,7 +142,7 @@ func main() { fmt.Println(variant.Text) case anthropic.ToolUseBlock: // 4. Parse the tool input. Use variant.JSON.Input.Raw() to get the - // raw JSON — block.Input is json.RawMessage, not the parsed value. + // raw JSON - block.Input is json.RawMessage, not the parsed value. var in struct { A int `json:"a"` B int `json:"b"` @@ -173,7 +173,7 @@ func main() { | Symbol | Purpose | |---|---| -| `resp.ToParam()` | Convert `Message` response → `MessageParam` for history | +| `resp.ToParam()` | Convert `Message` response -> `MessageParam` for history | | `block.AsAny().(type)` | Type-switch on `ContentBlockUnion` variants | | `variant.JSON.Input.Raw()` | Raw JSON string of tool input (for `json.Unmarshal`) | | `anthropic.NewToolResultBlock(id, content, isError)` | Build `tool_result` block | @@ -185,7 +185,7 @@ func main() { ## Anthropic-Defined Tools -Version-suffixed struct names with `Param` suffix. `Name`/`Type` are `constant.*` types — zero value marshals correctly, so `{}` works. Wrap in `ToolUnionParam` with the matching `Of*` field. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally — see `shared/tool-use-concepts.md`). +Version-suffixed struct names with `Param` suffix. `Name`/`Type` are `constant.*` types - zero value marshals correctly, so `{}` works. Wrap in `ToolUnionParam` with the matching `Of*` field. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally - see `shared/tool-use-concepts.md`). ```go Tools: []anthropic.ToolUnionParam{ @@ -200,7 +200,7 @@ Also available: `WebFetchTool20260209Param`, `ToolSearchToolBm25_20251119Param`, ### Advisor tool (beta) -Server-side — no tool_result round-trip. The advisor model must be ≥ the executor (top-level) model; invalid pairs return 400. +Server-side - no tool_result round-trip. The advisor model must be >= the executor (top-level) model; invalid pairs return 400. ```go response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{ diff --git a/skills/claude-api/go/managed-agents/README.md b/skills/claude-api/go/managed-agents/README.md index d5cdc5e3e..a8e2c9658 100644 --- a/skills/claude-api/go/managed-agents/README.md +++ b/skills/claude-api/go/managed-agents/README.md @@ -1,8 +1,8 @@ -# Managed Agents — Go +# Managed Agents - Go > **Bindings not shown here:** This README covers the most common managed-agents flows for Go. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the Go SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK. -> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `agents.New` and pass it to every subsequent `sessions.New`; do not call `agents.New` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. +> **Agents are persistent - create once, reference by ID.** Store the agent ID returned by `agents.New` and pass it to every subsequent `sessions.New`; do not call `agents.New` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. ## Installation @@ -56,7 +56,7 @@ fmt.Println(environment.ID) // env_... ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** `Model`/`System`/`Tools` live on the agent object, not the session. Always start with `Beta.Agents.New()` — the session only takes `Agent: anthropic.BetaSessionNewParamsAgentUnion{OfString: anthropic.String(agent.ID)}` (or the typed `OfBetaManagedAgentsAgents` variant when you need a specific version). +> Warning: **There is no inline agent config.** `Model`/`System`/`Tools` live on the agent object, not the session. Always start with `Beta.Agents.New()` - the session only takes `Agent: anthropic.BetaSessionNewParamsAgentUnion{OfString: anthropic.String(agent.ID)}` (or the typed `OfBetaManagedAgentsAgents` variant when you need a specific version). ### Minimal @@ -152,7 +152,7 @@ if err != nil { } ``` -> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). +> Tip: **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens - stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). --- @@ -244,7 +244,7 @@ if err := stream.Err(); err != nil { ## Provide Custom Tool Result -> ℹ️ The Go managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `github.com/anthropics/anthropic-sdk-go` repository for the corresponding Go params types. +> Note: The Go managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `github.com/anthropics/anthropic-sdk-go` repository for the corresponding Go params types. --- @@ -336,7 +336,7 @@ if _, err := client.Beta.Sessions.Resources.Delete(ctx, resource.ID, anthropic.B ## List and Download Session Files -> ℹ️ Listing and downloading files an agent wrote during a session is not yet documented for Go in this skill or in the apps source examples. See `shared/managed-agents-events.md` and the `github.com/anthropics/anthropic-sdk-go` repository for the `Beta.Files.List` and `Beta.Files.Download` Go params types. +> Note: Listing and downloading files an agent wrote during a session is not yet documented for Go in this skill or in the apps source examples. See `shared/managed-agents-events.md` and the `github.com/anthropics/anthropic-sdk-go` repository for the `Beta.Files.List` and `Beta.Files.Download` Go params types. --- @@ -379,7 +379,7 @@ if err != nil { ## MCP Server Integration ```go -// Agent declares MCP server (no auth here — auth goes in a vault) +// Agent declares MCP server (no auth here - auth goes in a vault) agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{ Name: "GitHub Assistant", Model: anthropic.BetaManagedAgentsModelConfigParams{ diff --git a/skills/claude-api/java/claude-api/README.md b/skills/claude-api/java/claude-api/README.md index 57aeb67c6..87a900865 100644 --- a/skills/claude-api/java/claude-api/README.md +++ b/skills/claude-api/java/claude-api/README.md @@ -1,22 +1,22 @@ -# Claude API — Java +# Claude API - Java > **Note:** The Java SDK supports the Claude API and beta tool use with annotated classes. Agent SDK is not yet available for Java. ## Package Reference -Types are organized by package. If a class you need isn't shown in an example below, locate it via this table first — don't block on fetching SDK source over the network. +Types are organized by package. If a class you need isn't shown in an example below, locate it via this table first - don't block on fetching SDK source over the network. | `import` prefix | Contains | |---|---| | `com.anthropic.client` / `com.anthropic.client.okhttp` | `AnthropicClient`, `AnthropicOkHttpClient` | -| `com.anthropic.models.messages` | non-beta request/response types — `MessageCreateParams`, `Model`, `Message`, `TextBlockParam`, `ContentBlockParam`, `ToolUseBlockParam`, `ToolResultBlockParam`, `CacheControlEphemeral`, `Tool*` (e.g. `ToolBash20250124`, `ToolTextEditor20250728`), `StopReason`, `StructuredMessage*` | -| `com.anthropic.models.messages.batches` | Batch API — `BatchResultsParams`, `MessageBatchIndividualResponse` | +| `com.anthropic.models.messages` | non-beta request/response types - `MessageCreateParams`, `Model`, `Message`, `TextBlockParam`, `ContentBlockParam`, `ToolUseBlockParam`, `ToolResultBlockParam`, `CacheControlEphemeral`, `Tool*` (e.g. `ToolBash20250124`, `ToolTextEditor20250728`), `StopReason`, `StructuredMessage*` | +| `com.anthropic.models.messages.batches` | Batch API - `BatchResultsParams`, `MessageBatchIndividualResponse` | | `com.anthropic.models.beta` | `AnthropicBeta` (beta-flag constants) | -| `com.anthropic.models.beta.messages` | beta-endpoint types — `MessageCreateParams`, `BetaMessage`, `BetaStopReason`, `BetaContextManagementConfig`, `BetaMcpToolset`, `BetaRequestMcpServerUrlDefinition`, `BetaTool*` | +| `com.anthropic.models.beta.messages` | beta-endpoint types - `MessageCreateParams`, `BetaMessage`, `BetaStopReason`, `BetaContextManagementConfig`, `BetaMcpToolset`, `BetaRequestMcpServerUrlDefinition`, `BetaTool*` | | `com.anthropic.core` | `JsonValue`, `JsonField`, `JsonSchemaLocalValidation`, `com.anthropic.core.http.StreamResponse` | -| `com.anthropic.errors` | typed exceptions — `AnthropicServiceException`, `RateLimitException`, `NotFoundException`, etc. (see `shared/error-codes.md`) | +| `com.anthropic.errors` | typed exceptions - `AnthropicServiceException`, `RateLimitException`, `NotFoundException`, etc. (see `shared/error-codes.md`) | -`client.messages()` uses `com.anthropic.models.messages.*`; `client.beta().messages()` uses `com.anthropic.models.beta.messages.*`. Both packages define a `MessageCreateParams` — import the one matching the client path you call. +`client.messages()` uses `com.anthropic.models.messages.*`; `client.beta().messages()` uses `com.anthropic.models.beta.messages.*`. Both packages define a `MessageCreateParams` - import the one matching the client path you call. ### Key types per feature @@ -24,20 +24,20 @@ Write from this table instead of `javap`/jar inspection. Endpoint column tells y | Feature | Endpoint | Key Java types / builder calls | |---|---|---| -| User profiles | beta | `client.beta().userProfiles().create(...)` / `.retrieve(id)` / `.list()`. Pass the returned profile id on the beta `MessageCreateParams`. Requires a beta header — check the SDK's beta-headers reference for the current flag. | -| Agent Skills | beta | `BetaContainerParams`, `BetaSkillParams`, `BetaCodeExecutionTool20250825`. `.addBeta("code-execution-2025-08-25").addBeta("skills-2025-10-02")`. Download the output via `client.beta().files().download(fileId)`. | +| User profiles | beta | `client.beta().userProfiles().create(...)` / `.retrieve(id)` / `.list()`. Pass the returned profile id on the beta `MessageCreateParams`. Requires a beta header - check the SDK's beta-headers reference for the current flag. | +| Agent Skills | beta | `BetaContainerParams`, `BetaSkillParams`, `BetaCodeExecutionTool20250825`. `.addBeta("code-execution-2025-08-25")` (Skills is out of beta - no `skills-2025-10-02`). Download the output via `client.beta().files().download(fileId)`. | | Cache diagnostics | beta | `BetaDiagnosticsParam`, `BetaCacheControlEphemeral` | -| Context editing | beta | `.contextManagement(BetaContextManagementConfig.builder()…)`. The edit strategy is a `BetaClearToolUses20250919Edit` (or `BetaClearThinking20251015Edit`); its trigger is a `BetaInputTokensTrigger` built separately and passed to the edit's builder — there is no direct `.inputTokensTrigger(N)` shortcut on the edit builder. `javap` the edit and trigger classes for the exact setter names. | +| Context editing | beta | `.contextManagement(BetaContextManagementConfig.builder()...)`. The edit strategy is a `BetaClearToolUses20250919Edit` (or `BetaClearThinking20251015Edit`); its trigger is a `BetaInputTokensTrigger` built separately and passed to the edit's builder - there is no direct `.inputTokensTrigger(N)` shortcut on the edit builder. `javap` the edit and trigger classes for the exact setter names. | | Memory tool | non-beta | `.addTool(MemoryTool20250818.builder().build())` from `com.anthropic.models.messages` | | Programmatic tool calling | non-beta | `CodeExecutionTool20260120`, `Tool`, `ContentBlockParam` | | Strict tool use | non-beta | `Tool`, `Tool.InputSchema` | | Task budgets | beta | `.outputConfig(BetaOutputConfig.builder().taskBudget(BetaTokenTaskBudget.builder()...))` | | Tool search | non-beta | `.addTool(ToolSearchToolRegex20251119.builder()...)` from `com.anthropic.models.messages` | -| Web search | non-beta | `WebSearchTool20260209` from `com.anthropic.models.messages` — the latest variant with dynamic filtering (Claude Fable 5 + Claude Opus 5 + Opus 4.8/4.7/4.6 + Claude Sonnet 5 + Sonnet 4.6). For older models or Vertex, use `WebSearchTool20250305` | +| Web search | non-beta | `WebSearchTool20260209` from `com.anthropic.models.messages` - the latest variant with dynamic filtering (Claude Fable 5.1 + Claude Opus 5 + Opus 4.8/4.7/4.6 + Claude Sonnet 5 + Sonnet 4.6). For older models or Vertex, use `WebSearchTool20250305` | ### Discovering type and member names -If a class or builder method you need isn't in the tables above, `jar tf | grep -i ` or `javap -classpath com.anthropic.models.…` is fast enough to locate names. **Do not compile and run a separate reflection program** to enumerate members — the first build is slow enough to be backgrounded in many environments, trapping you in a polling loop. Write the script with the names you found and let the compiler error (`cannot find symbol`) point at any wrong member. +If a class or builder method you need isn't in the tables above, `jar tf | grep -i ` or `javap -classpath com.anthropic.models....` is fast enough to locate names. **Do not compile and run a separate reflection program** to enumerate members - the first build is slow enough to be backgrounded in many environments, trapping you in a polling loop. Write the script with the names you found and let the compiler error (`cannot find symbol`) point at any wrong member. ## Installation @@ -81,7 +81,7 @@ import com.anthropic.models.messages.MessageCreateParams; import com.anthropic.models.messages.Message; MessageCreateParams params = MessageCreateParams.builder() - .model("claude-opus-5") // .model(String) overload — use it for ids with no typed Model constant yet + .model("claude-opus-5") // .model(String) overload - use it for ids with no typed Model constant yet .maxTokens(16000L) .addUserMessage("What is the capital of France?") .build(); @@ -96,10 +96,10 @@ response.content().stream() ## Thinking -**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think. The builder has a direct `.thinking(ThinkingConfigAdaptive)` overload — no manual union wrapping. +**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think. The builder has a direct `.thinking(ThinkingConfigAdaptive)` overload - no manual union wrapping. > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking (below). `ThinkingConfigEnabled.builder().budgetTokens(N)` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `.thinking(...)` runs adaptive (`ThinkingConfigAdaptive` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `ThinkingConfigDisabled` is accepted only at effort `HIGH` or lower; pairing it with `XHIGH`/`MAX` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `.thinking(...)` runs adaptive (`ThinkingConfigAdaptive` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `ThinkingConfigDisabled` is accepted only at effort `HIGH` or lower; pairing it with `XHIGH`/`MAX` returns a 400. > **Older models:** Use `.thinking(ThinkingConfigEnabled.builder().budgetTokens(N).build())` (budget must be < `maxTokens`, min 1024). ```java @@ -121,13 +121,13 @@ for (ContentBlock block : client.messages().create(params).content()) { } ``` -`ContentBlock` narrowing: `.thinking()` / `.text()` return `Optional` — use `.ifPresent(...)` or `.stream().flatMap(...)`. Alternative: `isThinking()` / `asThinking()` boolean+unwrap pairs (throws on wrong variant). +`ContentBlock` narrowing: `.thinking()` / `.text()` return `Optional` - use `.ifPresent(...)` or `.stream().flatMap(...)`. Alternative: `isThinking()` / `asThinking()` boolean+unwrap pairs (throws on wrong variant). --- ## Effort Parameter -Effort is nested inside `OutputConfig` — there is NO `.effort()` directly on `MessageCreateParams.Builder`. +Effort is nested inside `OutputConfig` - there is NO `.effort()` directly on `MessageCreateParams.Builder`. ```java import com.anthropic.models.messages.OutputConfig; @@ -143,7 +143,7 @@ Combine with `Thinking = ThinkingConfigAdaptive` for cost-quality control. ## Prompt Caching -System message as a list of `TextBlockParam` with `CacheControlEphemeral`. Use `.systemOfTextBlockParams(...)` — the plain `.system(String)` overload can't carry cache control. For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. +System message as a list of `TextBlockParam` with `CacheControlEphemeral`. Use `.systemOfTextBlockParams(...)` - the plain `.system(String)` overload can't carry cache control. For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. ```java import com.anthropic.models.messages.TextBlockParam; @@ -195,7 +195,7 @@ DocumentBlockParam doc = DocumentBlockParam.builder() .build(); ``` -For **Files API** document references, use the beta path and beta types — see `files-api.md`: `BetaRequestDocumentBlock.builder().source(BetaFileDocumentSource.builder().fileId(id).build())`. +For **Files API** document references, use the beta path and beta types - see `files-api.md`: `BetaRequestDocumentBlock.builder().source(BetaFileDocumentSource.builder().fileId(id).build())`. ```java .addUserMessageOfBlockParams(List.of( @@ -216,7 +216,7 @@ response.stopDetails().ifPresent(details -> { }); ``` -**Refusal fallbacks (Claude Fable 5) — opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, fallback model `claude-opus-4-8`, on the beta messages call) by default. The exact Java builder methods (and the client-side middleware for providers without server-side support) are not documented here — WebFetch the Java SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason. +**Refusal fallbacks (Claude Fable 5.1) - opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5-1` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, fallback model `claude-opus-4-8`, on the beta messages call) by default. The exact Java builder methods (and the client-side middleware for providers without server-side support) are not documented here - WebFetch the Java SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason. --- diff --git a/skills/claude-api/java/claude-api/files-api.md b/skills/claude-api/java/claude-api/files-api.md index 84cf74cc4..e824fc3c8 100644 --- a/skills/claude-api/java/claude-api/files-api.md +++ b/skills/claude-api/java/claude-api/files-api.md @@ -1,6 +1,8 @@ -# Files API — Java +# Files API - Java -## Files API (Beta) +## Files API + +> **Out of beta.** In current SDKs `client.beta().files()` has breaking shape changes from previous versions, matching the stable `client.files()` - migrate per the Files API row in `shared/live-sources.md`. Examples below predate this. Under `client.beta().files()`. File references in messages need the beta message types (non-beta `DocumentBlockParam.Source` has no file-ID variant). diff --git a/skills/claude-api/java/claude-api/streaming.md b/skills/claude-api/java/claude-api/streaming.md index 921e9475f..fb09ab981 100644 --- a/skills/claude-api/java/claude-api/streaming.md +++ b/skills/claude-api/java/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — Java +# Streaming - Java ## Streaming diff --git a/skills/claude-api/java/claude-api/tool-use.md b/skills/claude-api/java/claude-api/tool-use.md index caf45b58f..d5df6a16c 100644 --- a/skills/claude-api/java/claude-api/tool-use.md +++ b/skills/claude-api/java/claude-api/tool-use.md @@ -1,4 +1,4 @@ -# Tool Use — Java +# Tool Use - Java For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). @@ -78,7 +78,7 @@ See the [shared memory tool concepts](../../shared/tool-use-concepts.md) for mor ### Non-Beta Tool Declaration (manual JSON schema) -`Tool.InputSchema.Properties` is a freeform `Map` wrapper — build property schemas via `putAdditionalProperty`. `type: "object"` is the default. The builder has a direct `.addTool(Tool)` overload that wraps in `ToolUnion` automatically. +`Tool.InputSchema.Properties` is a freeform `Map` wrapper - build property schemas via `putAdditionalProperty`. `type: "object"` is the default. The builder has a direct `.addTool(Tool)` overload that wraps in `ToolUnion` automatically. ```java import com.anthropic.core.JsonValue; @@ -107,7 +107,7 @@ For manual tool loops, handle `tool_use` blocks in the response, send `tool_resu ### Building `MessageParam` with Content Blocks (Tool Result Round-Trip) -`MessageParam.Content` is an inner union class (string | list). Use the builder's `.contentOfBlockParams(List)` alias — there is NO separate `MessageParamContent` class with a static `ofBlockParams`: +`MessageParam.Content` is an inner union class (string | list). Use the builder's `.contentOfBlockParams(List)` alias - there is NO separate `MessageParamContent` class with a static `ofBlockParams`: ```java import com.anthropic.models.messages.MessageParam; @@ -131,7 +131,7 @@ MessageParam toolResultMsg = MessageParam.builder() ## Structured Output -The class-based overload auto-derives the JSON schema from your POJO and gives you a typed `.text()` return — no manual schema, no manual parsing. +The class-based overload auto-derives the JSON schema from your POJO and gives you a typed `.text()` return - no manual schema, no manual parsing. ```java import com.anthropic.models.messages.StructuredMessageCreateParams; @@ -160,7 +160,7 @@ Supports Jackson annotations: `@JsonPropertyDescription`, `@JsonIgnore`, `@Array ## Anthropic-Defined Tools -Version-suffixed types; `name`/`type` auto-set by builder. Direct `.addTool()` overloads exist for most tool types; where one is missing (newer or less-common tools — see the advisor note below), wrap via the union type's static factory: `.addTool(BetaToolUnion.of(builder…build()))`. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally — see `shared/tool-use-concepts.md`). +Version-suffixed types; `name`/`type` auto-set by builder. Direct `.addTool()` overloads exist for most tool types; where one is missing (newer or less-common tools - see the advisor note below), wrap via the union type's static factory: `.addTool(BetaToolUnion.of(builder...build()))`. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally - see `shared/tool-use-concepts.md`). ```java import com.anthropic.models.messages.WebSearchTool20260209; @@ -177,11 +177,11 @@ import com.anthropic.models.messages.CodeExecutionTool20260120; .addTool(CodeExecutionTool20260120.builder().build()) ``` -Also available: `WebFetchTool20260209`, `MemoryTool20250818`, `ToolSearchToolBm25_20251119`. For the advisor tool, use `BetaAdvisorTool20260301` in the beta namespace with `.addBeta("advisor-tool-2026-03-01")` (server-side; advisor model ≥ executor model). There is no direct `.addTool(BetaAdvisorTool20260301)` overload on the beta builder — wrap it via the `BetaToolUnion` static factory for the advisor type; if `javac` rejects the specific factory method name, `javap com.anthropic.models.beta.messages.BetaToolUnion | grep -i advisor` shows the exact one. +Also available: `WebFetchTool20260209`, `MemoryTool20250818`, `ToolSearchToolBm25_20251119`. For the advisor tool, use `BetaAdvisorTool20260301` in the beta namespace with `.addBeta("advisor-tool-2026-03-01")` (server-side; advisor model >= executor model). There is no direct `.addTool(BetaAdvisorTool20260301)` overload on the beta builder - wrap it via the `BetaToolUnion` static factory for the advisor type; if `javac` rejects the specific factory method name, `javap com.anthropic.models.beta.messages.BetaToolUnion | grep -i advisor` shows the exact one. ### Beta namespace (MCP, compaction) -For beta-only features use `com.anthropic.models.beta.messages.*` — class names have a `Beta` prefix AND live in the beta package. The beta `MessageCreateParams.Builder` has direct `.addTool(BetaToolBash20250124)` overloads AND `.addMcpServer()`: +For beta-only features use `com.anthropic.models.beta.messages.*` - class names have a `Beta` prefix AND live in the beta package. The beta `MessageCreateParams.Builder` has direct `.addTool(BetaToolBash20250124)` overloads AND `.addMcpServer()`: ```java import com.anthropic.models.beta.messages.MessageCreateParams; @@ -205,9 +205,9 @@ MessageCreateParams params = MessageCreateParams.builder() client.beta().messages().create(params); ``` -`BetaTool*` types are NOT interchangeable with non-beta `Tool*` — pick one namespace per request. +`BetaTool*` types are NOT interchangeable with non-beta `Tool*` - pick one namespace per request. -**Reading server-tool blocks in the response:** `ServerToolUseBlock` has `.id()`, `.name()` (enum), and `._input()` returning raw `JsonValue` — there is NO typed `.input()`. For code execution results, unwrap two levels: +**Reading server-tool blocks in the response:** `ServerToolUseBlock` has `.id()`, `.name()` (enum), and `._input()` returning raw `JsonValue` - there is NO typed `.input()`. For code execution results, unwrap two levels: ```java for (ContentBlock block : response.content()) { diff --git a/skills/claude-api/java/managed-agents/README.md b/skills/claude-api/java/managed-agents/README.md index 56814c8cf..ff1f11598 100644 --- a/skills/claude-api/java/managed-agents/README.md +++ b/skills/claude-api/java/managed-agents/README.md @@ -1,8 +1,8 @@ -# Managed Agents — Java +# Managed Agents - Java > **Bindings not shown here:** This README covers the most common managed-agents flows for Java. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the Java SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK. -> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `client.beta().agents().create` and pass it to every subsequent `client.beta().sessions().create`; do not call `agents().create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. +> **Agents are persistent - create once, reference by ID.** Store the agent ID returned by `client.beta().agents().create` and pass it to every subsequent `client.beta().sessions().create`; do not call `agents().create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. ## Installation @@ -44,7 +44,7 @@ System.out.println("Environment ID: " + environment.id()); // env_... ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** Model, system, and tools live on the agent object, not the session. Always start with `client.beta().agents().create()` — the session takes either `.agent(agent.id())` or the typed `BetaManagedAgentsAgentParams.builder()...build()`. +> Warning: **There is no inline agent config.** Model, system, and tools live on the agent object, not the session. Always start with `client.beta().agents().create()` - the session takes either `.agent(agent.id())` or the typed `BetaManagedAgentsAgentParams.builder()...build()`. ### Minimal @@ -117,7 +117,7 @@ client.beta().sessions().events().send(session.id(), EventSendParams.builder() .build()); ``` -> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). +> Tip: **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens - stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). --- @@ -185,7 +185,7 @@ try (var stream = client.beta().sessions().events().streamStreaming(session.id() ## Provide Custom Tool Result -> ℹ️ The Java managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic-java` repository for the corresponding params types. +> Note: The Java managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic-java` repository for the corresponding params types. --- @@ -240,7 +240,7 @@ var resource = client.beta().sessions().resources().add(session.id(), ResourceAd .build()); System.out.println(resource.id()); // "sesrsc_01ABC..." -// List resources on the session — entries are a discriminated union +// List resources on the session - entries are a discriminated union var listed = client.beta().sessions().resources().list(session.id()); for (var entry : listed.data()) { if (entry.isFile()) { @@ -262,7 +262,7 @@ client.beta().sessions().resources().delete(resource.id(), ResourceDeleteParams. ## List and Download Session Files -> ℹ️ Listing and downloading files an agent wrote during a session is not yet documented for Java in this skill or in the apps source examples. See `shared/managed-agents-events.md` and the `anthropic-java` repository for the file list/download bindings. +> Note: Listing and downloading files an agent wrote during a session is not yet documented for Java in this skill or in the apps source examples. See `shared/managed-agents-events.md` and the `anthropic-java` repository for the file list/download bindings. --- @@ -293,7 +293,7 @@ client.beta().sessions().delete(session.id()); import com.anthropic.models.beta.agents.BetaManagedAgentsMcpToolsetParams; import com.anthropic.models.beta.agents.BetaManagedAgentsUrlMcpServerParams; -// Agent declares MCP server (no auth here — auth goes in a vault) +// Agent declares MCP server (no auth here - auth goes in a vault) var agent = client.beta().agents().create(AgentCreateParams.builder() .name("GitHub Assistant") .model("claude-opus-5") diff --git a/skills/claude-api/php/claude-api/README.md b/skills/claude-api/php/claude-api/README.md index 41b3714b4..8d8f7d5ca 100644 --- a/skills/claude-api/php/claude-api/README.md +++ b/skills/claude-api/php/claude-api/README.md @@ -1,4 +1,4 @@ -# Claude API — PHP +# Claude API - PHP > **Note:** The PHP SDK is the official Anthropic SDK for PHP. A beta tool runner is available via `$client->beta->messages->toolRunner()`. Structured output helpers are supported via `StructuredOutputModel` classes. Agent SDK is not available. Bedrock, Vertex AI, and Foundry clients are supported. @@ -26,7 +26,7 @@ use Anthropic\Bedrock\MantleClient; $client = new MantleClient(awsRegion: 'us-east-1'); ``` -Model IDs on Bedrock take an `anthropic.` prefix — e.g. `model: 'anthropic.claude-opus-5'`. +Model IDs on Bedrock take an `anthropic.` prefix - e.g. `model: 'anthropic.claude-opus-5'`. ### Google Vertex AI @@ -109,7 +109,7 @@ $message = $client->messages->create( foreach ($message->content as $block) { if ($block instanceof ThinkingBlock) { echo "Thinking:\n{$block->thinking}\n\n"; - // $block->signature is an opaque string — preserve verbatim if + // $block->signature is an opaque string - preserve verbatim if // passing thinking blocks back in multi-turn conversations } elseif ($block->type === 'text') { echo "Answer: {$block->text}\n"; @@ -118,7 +118,7 @@ foreach ($message->content as $block) { ``` > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking (above). `['type' => 'enabled', 'budgetTokens' => N]` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `thinking:` runs adaptive (`['type' => 'adaptive']` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `['type' => 'disabled']` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `thinking:` runs adaptive (`['type' => 'adaptive']` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `['type' => 'disabled']` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. > **Older models:** Use `thinking: ['type' => 'enabled', 'budgetTokens' => N]` (budget must be < `maxTokens`, min 1024). `$block->type === 'thinking'` also works for the check; `instanceof` narrows for PHPStan. @@ -152,12 +152,12 @@ When `stopReason` is `'refusal'`, the response includes structured `stopDetails` ```php if ($message->stopReason === 'refusal' && $message->stopDetails !== null) { - echo "Category: " . $message->stopDetails->category . "\n"; // e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or null — see docs for the full set + echo "Category: " . $message->stopDetails->category . "\n"; // e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or null - see docs for the full set echo "Explanation: " . $message->stopDetails->explanation . "\n"; } ``` -**Refusal fallbacks (Claude Fable 5) — opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, fallback model `claude-opus-4-8`, on the beta messages call) by default. The exact PHP binding (and the client-side middleware for providers without server-side support) is not documented here — WebFetch the PHP SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason. +**Refusal fallbacks (Claude Fable 5.1) - opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5-1` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, fallback model `claude-opus-4-8`, on the beta messages call) by default. The exact PHP binding (and the client-side middleware for providers without server-side support) is not documented here - WebFetch the PHP SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason. --- diff --git a/skills/claude-api/php/claude-api/batches.md b/skills/claude-api/php/claude-api/batches.md index 7c0db9c0f..d42350d25 100644 --- a/skills/claude-api/php/claude-api/batches.md +++ b/skills/claude-api/php/claude-api/batches.md @@ -1,4 +1,4 @@ -# Message Batches — PHP +# Message Batches - PHP ## Message Batches API diff --git a/skills/claude-api/php/claude-api/files-api.md b/skills/claude-api/php/claude-api/files-api.md index 3fb617975..26bd142dc 100644 --- a/skills/claude-api/php/claude-api/files-api.md +++ b/skills/claude-api/php/claude-api/files-api.md @@ -1,7 +1,9 @@ -# Files API — PHP +# Files API - PHP ## Files API +> **Out of beta.** In current SDKs `$client->beta->files` has breaking shape changes from previous versions, matching the stable `$client->files` - migrate per the Files API row in `shared/live-sources.md`. Example below predates this. + ```php $file = $client->beta->files->upload( file: fopen('upload_me.txt', 'r'), diff --git a/skills/claude-api/php/claude-api/streaming.md b/skills/claude-api/php/claude-api/streaming.md index 0a90596b6..86577eae4 100644 --- a/skills/claude-api/php/claude-api/streaming.md +++ b/skills/claude-api/php/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — PHP +# Streaming - PHP ## Streaming diff --git a/skills/claude-api/php/claude-api/tool-use.md b/skills/claude-api/php/claude-api/tool-use.md index ffd66ca51..55c5f3ebc 100644 --- a/skills/claude-api/php/claude-api/tool-use.md +++ b/skills/claude-api/php/claude-api/tool-use.md @@ -1,4 +1,4 @@ -# Tool Use — PHP +# Tool Use - PHP For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). @@ -6,7 +6,7 @@ For conceptual overview (tool definitions, tool choice, tips), see [shared/tool- ### Tool Runner (Beta) -**Beta:** The PHP SDK provides a tool runner via `$client->beta->messages->toolRunner()`. Define tools with `BetaRunnableTool` — a definition array plus a `run` closure: +**Beta:** The PHP SDK provides a tool runner via `$client->beta->messages->toolRunner()`. Define tools with `BetaRunnableTool` - a definition array plus a `run` closure: ```php use Anthropic\Lib\Tools\BetaRunnableTool; @@ -46,7 +46,7 @@ foreach ($runner as $message) { ### Manual Loop -Tools are passed as arrays. **The SDK uses camelCase keys** (`inputSchema`, `toolUseID`, `stopReason`) and auto-maps to the API's snake_case on the wire — since v0.5.0. See [shared tool use concepts](../../shared/tool-use-concepts.md) for the loop pattern. +Tools are passed as arrays. **The SDK uses camelCase keys** (`inputSchema`, `toolUseID`, `stopReason`) and auto-maps to the API's snake_case on the wire - since v0.5.0. See [shared tool use concepts](../../shared/tool-use-concepts.md) for the loop pattern. ```php use Anthropic\Messages\ToolUseBlock; @@ -78,9 +78,9 @@ while ($response->stopReason === 'tool_use') { // camelCase property $toolResults = []; foreach ($response->content as $block) { if ($block instanceof ToolUseBlock) { - // $block->name : string — tool name to dispatch on - // $block->input : array — parsed JSON input - // $block->id : string — pass back as toolUseID + // $block->name : string - tool name to dispatch on + // $block->input : array - parsed JSON input + // $block->id : string - pass back as toolUseID $result = executeYourTool($block->name, $block->input); $toolResults[] = [ 'type' => 'tool_result', @@ -188,7 +188,7 @@ foreach ($message->content as $block) { ## Beta Features & Anthropic-Defined Tools -**`betas:` is NOT a param on `$client->messages->create()`** — it only exists on the beta namespace. Use it for features that need an explicit opt-in header: +**`betas:` is NOT a param on `$client->messages->create()`** - it only exists on the beta namespace. Use it for features that need an explicit opt-in header: ```php use Anthropic\Beta\Messages\BetaRequestMCPServerURLDefinition; @@ -233,7 +233,7 @@ $r2 = $client->beta->messages->create( ); ``` -**Anthropic-defined tools** (bash, web_search, text_editor, code_execution) are GA and work on both paths. Of these, web_search and code_execution are server-executed; bash and text_editor are client-executed (you handle the `tool_use` locally) — `Anthropic\Messages\ToolBash20250124` / `WebSearchTool20260209` / `ToolTextEditor20250728` / `CodeExecutionTool20260120` for non-beta, `Anthropic\Beta\Messages\BetaToolBash20250124` / `BetaWebSearchTool20260209` / `BetaToolTextEditor20250728` / `BetaCodeExecutionTool20260120` for beta. No `betas:` header needed for these. +**Anthropic-defined tools** (bash, web_search, text_editor, code_execution) are GA and work on both paths. Of these, web_search and code_execution are server-executed; bash and text_editor are client-executed (you handle the `tool_use` locally) - `Anthropic\Messages\ToolBash20250124` / `WebSearchTool20260209` / `ToolTextEditor20250728` / `CodeExecutionTool20260120` for non-beta, `Anthropic\Beta\Messages\BetaToolBash20250124` / `BetaWebSearchTool20260209` / `BetaToolTextEditor20250728` / `BetaCodeExecutionTool20260120` for beta. No `betas:` header needed for these. ### Tool search (non-beta, server-side) @@ -247,7 +247,7 @@ tools: [ ### Memory tool (non-beta, client-executed) -Declare `['type' => 'memory_20250818', 'name' => 'memory']`. Handle the `tool_use` by reading/writing files under a fixed `/memories` directory. **Validate every model-supplied path**: resolve to its canonical form and verify it remains within the memory directory; reject traversal (`..`, symlinks) — see `shared/tool-use-concepts.md` § Client-Side Tools. +Declare `['type' => 'memory_20250818', 'name' => 'memory']`. Handle the `tool_use` by reading/writing files under a fixed `/memories` directory. **Validate every model-supplied path**: resolve to its canonical form and verify it remains within the memory directory; reject traversal (`..`, symlinks) - see `shared/tool-use-concepts.md` § Client-Side Tools. --- diff --git a/skills/claude-api/php/managed-agents/README.md b/skills/claude-api/php/managed-agents/README.md index fc63d9888..13e8969cf 100644 --- a/skills/claude-api/php/managed-agents/README.md +++ b/skills/claude-api/php/managed-agents/README.md @@ -1,8 +1,8 @@ -# Managed Agents — PHP +# Managed Agents - PHP > **Bindings not shown here:** This README covers the most common managed-agents flows for PHP. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the PHP SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK. -> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `$client->beta->agents->create` and pass it to every subsequent `->sessions->create`; do not call `agents->create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. +> **Agents are persistent - create once, reference by ID.** Store the agent ID returned by `$client->beta->agents->create` and pass it to every subsequent `->sessions->create`; do not call `agents->create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. ## Installation @@ -38,7 +38,7 @@ echo "Environment ID: {$environment->id}\n"; // env_... ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `$client->beta->agents->create()` — the session takes either `agent: $agent->id` or the typed `BetaManagedAgentsAgentParams::with(type: 'agent', id: $agent->id, version: $agent->version)`. +> Warning: **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `$client->beta->agents->create()` - the session takes either `agent: $agent->id` or the typed `BetaManagedAgentsAgentParams::with(type: 'agent', id: $agent->id, version: $agent->version)`. ### Minimal @@ -105,13 +105,13 @@ $client->beta->sessions->events->send( ); ``` -> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). +> Tip: **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens - stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). --- ## Stream Events (SSE) -> ℹ️ **Streaming transporter:** PHP's default buffered PSR-18 client never returns for the open-ended session event stream. Use a streaming Guzzle transporter for `streamStream()` calls — other calls keep the default client. +> Note: **Streaming transporter:** PHP's default buffered PSR-18 client never returns for the open-ended session event stream. Use a streaming Guzzle transporter for `streamStream()` calls - other calls keep the default client. ```php $streamingClient = new GuzzleHttp\Client(['stream' => true]); @@ -188,7 +188,7 @@ $stream->close(); ## Provide Custom Tool Result -> ℹ️ The PHP managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic-ai/sdk` PHP repository for the corresponding params. +> Note: The PHP managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic-ai/sdk` PHP repository for the corresponding params. --- @@ -204,7 +204,7 @@ foreach ($client->beta->sessions->events->list($session->id)->pagingEachItem() a ## Upload a File -> ℹ️ **PHP file upload:** The PHP SDK's beta managed-agents file upload binding is not shown in the apps source examples; the canonical PHP example uses raw cURL against `POST /v1/files`. If your codebase prefers the SDK, WebFetch the `anthropic-ai/sdk` PHP repository for the latest binding before writing code. +> Note: **PHP file upload:** The PHP SDK's beta managed-agents file upload binding is not shown in the apps source examples; the canonical PHP example uses raw cURL against `POST /v1/files`. If your codebase prefers the SDK, WebFetch the `anthropic-ai/sdk` PHP repository for the latest binding before writing code. ```php use Anthropic\Beta\Sessions\BetaManagedAgentsFileResourceParams; @@ -304,7 +304,7 @@ use Anthropic\Beta\Agents\BetaManagedAgentsMCPToolsetParams; use Anthropic\Beta\Agents\BetaManagedAgentsURLMCPServerParams; use Anthropic\Beta\Sessions\BetaManagedAgentsAgentParams; -// Agent declares MCP server (no auth here — auth goes in a vault) +// Agent declares MCP server (no auth here - auth goes in a vault) $agent = $client->beta->agents->create( name: 'GitHub Assistant', model: 'claude-opus-5', diff --git a/skills/claude-api/python/claude-api/README.md b/skills/claude-api/python/claude-api/README.md index 62c0f5086..c65289f5b 100644 --- a/skills/claude-api/python/claude-api/README.md +++ b/skills/claude-api/python/claude-api/README.md @@ -1,4 +1,4 @@ -# Claude API — Python +# Claude API - Python ## Installation @@ -11,7 +11,7 @@ pip install anthropic ```python import anthropic -# Default — resolves credentials from the environment: +# Default - resolves credentials from the environment: # ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN, or an `ant auth login` profile. # Prefer this for local dev; don't hardcode a key. client = anthropic.Anthropic() @@ -50,11 +50,11 @@ client = anthropic.Anthropic( ) ``` -`anthropic` 1.x is built on [`httpx2`](https://pypi.org/project/httpx2/), not `httpx`. `anthropic.Timeout` is `httpx2.Timeout`; if you import the HTTP library yourself, write `import httpx2 as httpx` — an object from the `httpx` package (`httpx.Timeout`, `httpx.Client`, transports, limits) is rejected or fails at request time. Existing `httpx`-era code is covered by the [v1 migration guide](https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md) and `/claude-api upgrade python`. +`anthropic` 1.x is built on [`httpx2`](https://pypi.org/project/httpx2/), not `httpx`. `anthropic.Timeout` is `httpx2.Timeout`; if you import the HTTP library yourself, write `import httpx2 as httpx` - an object from the `httpx` package (`httpx.Timeout`, `httpx.Client`, transports, limits) is rejected or fails at request time. Existing `httpx`-era code is covered by the [v1 migration guide](https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md) and `/claude-api upgrade python`. ### Retries -The SDK auto-retries connection errors, 408, 409, 429, and ≥500 with exponential backoff (default 2 retries). Set `max_retries` on the client or via `with_options()`; `max_retries=0` disables. +The SDK auto-retries connection errors, 408, 409, 429, and >=500 with exponential backoff (default 2 retries). Set `max_retries` on the client or via `with_options()`; `max_retries=0` disables. ### Async performance (aiohttp backend) @@ -69,7 +69,7 @@ async with AsyncAnthropic(http_client=DefaultAioHttpClient()) as client: ### Custom HTTP client (proxy, base URL) -Use `DefaultHttpxClient` / `DefaultAsyncHttpxClient` — not a raw `httpx2.Client` (and never a client from the `httpx` package) — so the SDK's default timeouts and connection limits are preserved: +Use `DefaultHttpxClient` / `DefaultAsyncHttpxClient` - not a raw `httpx2.Client` (and never a client from the `httpx` package) - so the SDK's default timeouts and connection limits are preserved: ```python from anthropic import Anthropic, DefaultHttpxClient @@ -118,7 +118,7 @@ response = client.messages.create( ### Mid-conversation system messages (model-gated) -For operator instructions that arrive mid-conversation (mode switches, injected state), append `{"role": "system", ...}` to `messages` instead of editing top-level `system` — this preserves the cached prefix and carries operator authority. Must follow a user message (or an `assistant` message ending in server-tool use), and must be either the last entry in `messages` or be followed by an `assistant` turn; cannot be `messages[0]`. Unsupported models return a 400 (`role 'system' is not supported on this model`). See `shared/prompt-caching.md` for when to use this vs. top-level `system`. +For operator instructions that arrive mid-conversation (mode switches, injected state), append `{"role": "system", ...}` to `messages` instead of editing top-level `system` - this preserves the cached prefix and carries operator authority. Must follow a user message (or an `assistant` message ending in server-tool use), and must be either the last entry in `messages` or be followed by an `assistant` turn; cannot be `messages[0]`. Unsupported models return a 400 (`role 'system' is not supported on this model`). See `shared/prompt-caching.md` for when to use this vs. top-level `system`. ```python response = client.messages.create( @@ -127,9 +127,9 @@ response = client.messages.create( system=[{"type": "text", "text": STABLE_SYSTEM, "cache_control": {"type": "ephemeral"}}], messages=history + [ {"role": "user", "content": user_message}, - {"role": "system", "content": "Terse mode enabled — keep responses under 40 words."}, + {"role": "system", "content": "Terse mode enabled - keep responses under 40 words."}, ], -) # No beta header needed — use regular client.messages.create +) # No beta header needed - use regular client.messages.create ``` --- @@ -190,11 +190,11 @@ response = client.messages.create( ## Prompt Caching -Cache large context to reduce costs (up to 90% savings). **Caching is a prefix match** — any byte change anywhere in the prefix invalidates everything after it. For placement patterns, architectural guidance (frozen system prompt, deterministic tool order, where to put volatile content), and the silent-invalidator audit checklist, read `shared/prompt-caching.md`. +Cache large context to reduce costs (up to 90% savings). **Caching is a prefix match** - any byte change anywhere in the prefix invalidates everything after it. For placement patterns, architectural guidance (frozen system prompt, deterministic tool order, where to put volatile content), and the silent-invalidator audit checklist, read `shared/prompt-caching.md`. ### Automatic Caching (Recommended) -Use top-level `cache_control` to automatically cache the last cacheable block in the request — no need to annotate individual content blocks: +Use top-level `cache_control` to automatically cache the last cacheable block in the request - no need to annotate individual content blocks: ```python response = client.messages.create( @@ -243,14 +243,14 @@ print(response.usage.cache_read_input_tokens) # tokens served from cache (~ print(response.usage.input_tokens) # uncached tokens (full cost) ``` -If `cache_read_input_tokens` is zero across repeated identical-prefix requests, a silent invalidator is at work — `datetime.now()` or a UUID in the system prompt, unsorted `json.dumps()`, or a varying tool set. See `shared/prompt-caching.md` for the full audit table. +If `cache_read_input_tokens` is zero across repeated identical-prefix requests, a silent invalidator is at work - `datetime.now()` or a UUID in the system prompt, unsorted `json.dumps()`, or a varying tool set. See `shared/prompt-caching.md` for the full audit table. --- ## Extended Thinking > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking. `budget_tokens` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `thinking` runs adaptive (`{"type": "adaptive"}` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{"type": "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `thinking` runs adaptive (`{"type": "adaptive"}` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{"type": "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. > **Older models:** Use `thinking: {type: "enabled", budget_tokens: N}` (must be < `max_tokens`, min 1024). ```python @@ -304,7 +304,7 @@ except anthropic.APIConnectionError: ## Response Helpers -Every response object exposes `_request_id` (populated from the `request-id` header) — log it when reporting failures to Anthropic. Despite the underscore prefix, this property is public. +Every response object exposes `_request_id` (populated from the `request-id` header) - log it when reporting failures to Anthropic. Despite the underscore prefix, this property is public. ```python message = client.messages.create(...) @@ -329,7 +329,7 @@ message = raw.parse() # the Message object messages.create() would have returne ## Multi-Turn Conversations -The API is stateless — send the full conversation history each time. +The API is stateless - send the full conversation history each time. ```python class ConversationManager: @@ -373,15 +373,15 @@ response2 = conversation.send("What's my name?") # Claude remembers "Alice" **Rules:** -- Consecutive same-role messages are allowed — the API combines them into a single turn +- Consecutive same-role messages are allowed - the API combines them into a single turn - First message must be `user` -- `role: "system"` messages are allowed mid-conversation on supporting models (no beta header needed) — see § Mid-conversation system messages above +- `role: "system"` messages are allowed mid-conversation on supporting models (no beta header needed) - see § Mid-conversation system messages above --- ### Compaction (long conversations) -> **Beta, Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text. +> **Beta, Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests - append `response.content`, not just the text. ```python import anthropic @@ -402,7 +402,7 @@ def chat(user_message: str) -> str: } ) - # Append full content — compaction blocks must be preserved + # Append full content - compaction blocks must be preserved messages.append({"role": "assistant", "content": response.content}) return next(block.text for block in response.content if block.type == "text") @@ -422,11 +422,11 @@ The `stop_reason` field in the response indicates why the model stopped generati | Value | Meaning | |-------|---------| | `end_turn` | Claude finished its response naturally | -| `max_tokens` | Hit the `max_tokens` limit — increase it or use streaming | +| `max_tokens` | Hit the `max_tokens` limit - increase it or use streaming | | `stop_sequence` | Hit a custom stop sequence | -| `tool_use` | Claude wants to call a tool — execute it and continue | +| `tool_use` | Claude wants to call a tool - execute it and continue | | `pause_turn` | Model paused and can be resumed (agentic flows) | -| `refusal` | Claude refused for safety reasons — check `stop_details` | +| `refusal` | Claude refused for safety reasons - check `stop_details` | ### Structured Stop Details @@ -434,17 +434,17 @@ When `stop_reason` is `"refusal"`, the response includes a `stop_details` object ```python if response.stop_reason == "refusal" and response.stop_details: - print(f"Category: {response.stop_details.category}") # e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or None — see docs for the full set + print(f"Category: {response.stop_details.category}") # e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or None - see docs for the full set print(f"Explanation: {response.stop_details.explanation}") ``` -### Refusal Fallbacks (Claude Fable 5) — opt in by default +### Refusal Fallbacks (Claude Fable 5.1) - opt in by default -Fallbacks are **opt-in**: without them a refused request simply stops. Include the server-side `fallbacks` parameter in `claude-fable-5` code by default — on a policy decline the API re-runs the same request on the fallback model inside the same call. A decline before any output isn't billed (a mid-stream decline bills the streamed partial); the rescue bills at the fallback model's own rates, with cache repricing applied automatically. +Fallbacks are **opt-in**: without them a refused request simply stops. Include the server-side `fallbacks` parameter in `claude-fable-5-1` code by default - on a policy decline the API re-runs the same request on the fallback model inside the same call. A decline before any output isn't billed (a mid-stream decline bills the streamed partial); the rescue bills at the fallback model's own rates, with cache repricing applied automatically. ```python response = client.beta.messages.create( - model="claude-fable-5", + model="claude-fable-5-1", max_tokens=16000, betas=["server-side-fallback-2026-06-01"], fallbacks=[{"model": "claude-opus-4-8"}], @@ -456,7 +456,7 @@ for block in response.content: if block.type == "fallback": print(f"{block.from_.model} declined; {block.to.model} continued") -# Served-by signal — covers sticky turns, which carry no fallback block. +# Served-by signal - covers sticky turns, which carry no fallback block. # Pair with stop_reason: the fallback model can itself refuse. fallback_ran = any( entry.type == "fallback_message" for entry in response.usage.iterations or [] @@ -465,7 +465,7 @@ if fallback_ran and response.stop_reason != "refusal": print(f"Served by {response.model}") ``` -A `stop_reason: "refusal"` on the final response means the whole chain refused. The header must be exactly `server-side-fallback-2026-06-01` **for this array form**; the newer `fallbacks: "default"` scalar form uses `server-side-fallback-2026-07-01` instead (see `shared/model-migration.md` → Migrating to Claude Opus 5 → New API features), and pairing either header with the other form returns a 400. The parameter is rejected on the Batches API and unavailable on Amazon Bedrock, Vertex AI, and Microsoft Foundry — register the client-side `BetaRefusalFallbackMiddleware` on the client there instead. Full semantics (sticky routing, billing, streaming, echoing fallback turns back): `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason. +A `stop_reason: "refusal"` on the final response means the whole chain refused. The header must be exactly `server-side-fallback-2026-06-01` **for this array form**; the newer `fallbacks: "default"` scalar form uses `server-side-fallback-2026-07-01` instead (see `shared/model-migration.md` -> Migrating to Claude Opus 5 -> New API features), and pairing either header with the other form returns a 400. The parameter is rejected on the Batches API and unavailable on Amazon Bedrock, Vertex AI, and Microsoft Foundry - register the client-side `BetaRefusalFallbackMiddleware` on the client there instead. Full semantics (sticky routing, billing, streaming, echoing fallback turns back): `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason. --- @@ -474,7 +474,7 @@ A `stop_reason: "refusal"` on the final response means the whole chain refused. ### 1. Use Prompt Caching for Repeated Context ```python -# Automatic caching (simplest — caches the last cacheable block) +# Automatic caching (simplest - caches the last cacheable block) response = client.messages.create( model="claude-opus-5", max_tokens=16000, @@ -499,7 +499,7 @@ response = client.messages.create( # Use Sonnet for high-volume production workloads standard_response = client.messages.create( - model="claude-sonnet-5", # $3.00/$15.00 per 1M tokens + model="claude-sonnet-5", # $2.00/$10.00 per 1M tokens max_tokens=16000, messages=[{"role": "user", "content": "Summarize this document"}] ) diff --git a/skills/claude-api/python/claude-api/batches.md b/skills/claude-api/python/claude-api/batches.md index e917f57a4..313eec069 100644 --- a/skills/claude-api/python/claude-api/batches.md +++ b/skills/claude-api/python/claude-api/batches.md @@ -1,4 +1,4 @@ -# Message Batches API — Python +# Message Batches API - Python The Batches API (`POST /v1/messages/batches`) processes Messages API requests asynchronously at 50% of standard prices. @@ -102,7 +102,7 @@ print(f"Status: {cancelled.processing_status}") # "canceling" ## List Batches (auto-pagination) -Iterating the return value of any `list()` call auto-paginates across all pages — do not index into `.data` if you want the full set: +Iterating the return value of any `list()` call auto-paginates across all pages - do not index into `.data` if you want the full set: ```python for batch in client.messages.batches.list(limit=20): diff --git a/skills/claude-api/python/claude-api/files-api.md b/skills/claude-api/python/claude-api/files-api.md index 05778ddb0..1ec884d4c 100644 --- a/skills/claude-api/python/claude-api/files-api.md +++ b/skills/claude-api/python/claude-api/files-api.md @@ -1,8 +1,8 @@ -# Files API — Python +# Files API - Python The Files API uploads files for use in Messages API requests. Reference files via `file_id` in content blocks, avoiding re-uploads across multiple API calls. -**Beta:** Pass `betas=["files-api-2025-04-14"]` in your API calls (the SDK sets the required header automatically). +The Files API is out of beta. In current SDKs `client.beta.files` has breaking shape changes from previous versions, matching the stable `client.files` - migrate per the Files API row in `shared/live-sources.md`. Examples below predate this. ## Key Facts @@ -16,7 +16,7 @@ The Files API uploads files for use in Messages API requests. Reference files vi ## Upload a File -The `file` argument accepts a `(filename, content, content_type)` tuple, a `pathlib.Path` (or any `PathLike` — read for you, async-safe with `AsyncAnthropic`), or an open binary file object. +The `file` argument accepts a `(filename, content, content_type)` tuple, a `pathlib.Path` (or any `PathLike` - read for you, async-safe with `AsyncAnthropic`), or an open binary file object. ```python import anthropic @@ -91,7 +91,7 @@ response = client.beta.messages.create( ### List Files -Iterate the list result directly — the SDK auto-paginates across all pages. Only use `.data` if you want the first page only. +Iterate the list result directly - the SDK auto-paginates across all pages. Only use `.data` if you want the first page only. ```python for f in client.beta.files.list(): diff --git a/skills/claude-api/python/claude-api/sdk-upgrade.md b/skills/claude-api/python/claude-api/sdk-upgrade.md index 3070f16d6..94acc244a 100644 --- a/skills/claude-api/python/claude-api/sdk-upgrade.md +++ b/skills/claude-api/python/claude-api/sdk-upgrade.md @@ -1,27 +1,27 @@ -# Upgrading the `anthropic` Python SDK: 0.x → 1.x +# Upgrading the `anthropic` Python SDK: 0.x -> 1.x -> **If you arrived via `/claude-api upgrade`:** this is the right file. Execute the steps below in order — do not summarize them back to the user. Start with Step 0 before touching any file. +> **If you arrived via `/claude-api upgrade`:** this is the right file. Execute the steps below in order - do not summarize them back to the user. Start with Step 0 before touching any file. -`anthropic` 1.x is deliberately a small step from the last 0.x release: no method was restructured and no new pattern is required. Long-deprecated surface was removed, the HTTP layer moved from `httpx` to its maintained fork `httpx2`, and the minimum Python version is now 3.10. Almost every required edit is mechanical, and a type checker flags nearly all of them once 1.x is installed — which makes `pyright` / `mypy` output a good cross-check for the inventory below. +`anthropic` 1.x is deliberately a small step from the last 0.x release: no method was restructured and no new pattern is required. Long-deprecated surface was removed, the HTTP layer moved from `httpx` to its maintained fork `httpx2`, and the minimum Python version is now 3.10. Almost every required edit is mechanical, and a type checker flags nearly all of them once 1.x is installed - which makes `pyright` / `mypy` output a good cross-check for the inventory below. -The SDK repository's `MIGRATION.md` is the authoritative change list — WebFetch it (URL in `shared/live-sources.md` → SDK major-version upgrade guides) when you can, and if it disagrees with this file, follow `MIGRATION.md` and say so in your report. The other Python files in this skill may still show 0.x-era details; for a project on 1.x, this file takes precedence. +The SDK repository's `MIGRATION.md` is the authoritative change list - WebFetch it (URL in `shared/live-sources.md` -> SDK major-version upgrade guides) when you can, and if it disagrees with this file, follow `MIGRATION.md` and say so in your report. The other Python files in this skill may still show 0.x-era details; for a project on 1.x, this file takes precedence. --- ## Step 0: Confirm scope, current version, and target -**Scope — ask before editing unless it is already unambiguous.** Same rule as model migration: if the request does not name an exact file, a specific directory, or an explicit file list, ask one question offering (1) the whole working directory, (2) a specific subdirectory, (3) specific files — and wait. `upgrade`, `upgrade python`, "move my project to anthropic v1" are all scope-ambiguous. A trailing path in the subcommand (`upgrade python src/`) is a scope. Dependency manifests and lockfiles at the project root (`pyproject.toml`, `requirements*.txt`, `setup.py`/`setup.cfg`, `Pipfile`, `uv.lock`, `poetry.lock`) count as in scope whenever any code under them is — say so when you confirm the scope. +**Scope - ask before editing unless it is already unambiguous.** Same rule as model migration: if the request does not name an exact file, a specific directory, or an explicit file list, ask one question offering (1) the whole working directory, (2) a specific subdirectory, (3) specific files - and wait. `upgrade`, `upgrade python`, "move my project to anthropic v1" are all scope-ambiguous. A trailing path in the subcommand (`upgrade python src/`) is a scope. Dependency manifests and lockfiles at the project root (`pyproject.toml`, `requirements*.txt`, `setup.py`/`setup.cfg`, `Pipfile`, `uv.lock`, `poetry.lock`) count as in scope whenever any code under them is - say so when you confirm the scope. -**Current version.** Read the declared requirement (`anthropic...` in the manifests above) and, if a project environment is available, the installed one (`python -c "import anthropic; print(anthropic.__version__)"`). If the project is already on 1.x, skip the dependency bump and treat this as a call-site cleanup. If nothing in scope declares the dependency (a bare scripts directory, or `anthropic` arrives transitively), don't invent a manifest — upgrade the code and put the install command in the report. +**Current version.** Read the declared requirement (`anthropic...` in the manifests above) and, if a project environment is available, the installed one (`python -c "import anthropic; print(anthropic.__version__)"`). If the project is already on 1.x, skip the dependency bump and treat this as a call-site cleanup. If nothing in scope declares the dependency (a bare scripts directory, or `anthropic` arrives transitively), don't invent a manifest - upgrade the code and put the install command in the report. -**Target version.** Before writing any pin, confirm a 1.x release is actually published: `pip index versions anthropic` (or `curl -s https://pypi.org/pypi/anthropic/json` and read `info.version`). Use the newest 1.x you find. If no 1.x release exists yet, stop and tell the user — do not write an uninstallable requirement. If you cannot check (no network), proceed with `>=1,<2` and list the unverified pin in your report. +**Target version.** Before writing any pin, confirm a 1.x release is actually published: `pip index versions anthropic` (or `curl -s https://pypi.org/pypi/anthropic/json` and read `info.version`). Use the newest 1.x you find. If no 1.x release exists yet, stop and tell the user - do not write an uninstallable requirement. If you cannot check (no network), proceed with `>=1,<2` and list the unverified pin in your report. -If the scope is under git, check `git status` before editing — unexpected modifications mean a concurrent process; stop and investigate before proceeding. +If the scope is under git, check `git status` before editing - unexpected modifications mean a concurrent process; stop and investigate before proceeding. ## Step 1: Inventory the call sites -Search the scope for each signal below (`rg -n -F` for the literal strings; exclude virtualenvs, `.git`, build output and vendored code) and keep the hit list — it is your checklist and, re-run at the end, your verification. +Search the scope for each signal below (`rg -n -F` for the literal strings; exclude virtualenvs, `.git`, build output and vendored code) and keep the hit list - it is your checklist and, re-run at the end, your verification. | Signal | What it finds | Section | |---|---|---| @@ -32,7 +32,7 @@ Search the scope for each signal below (`rg -n -F` for the literal strings; excl | `with_raw_response` | raw-response call sites | Step 4 | | `LegacyAPIResponse`, `_legacy_response` | annotations / imports of the removed class | Step 4 | | `completions.create`, `HUMAN_PROMPT`, `AI_PROMPT`, `max_tokens_to_sample` | the removed Text Completions API | Step 5 | -| `temperature`, `top_p`, `top_k` (keyword arguments and quoted dict keys) | removed sampling parameters — only hits that feed Anthropic SDK calls count | Step 6 | +| `temperature`, `top_p`, `top_k` (keyword arguments and quoted dict keys) | removed sampling parameters - only hits that feed Anthropic SDK calls count | Step 6 | | `output_format` | raw `output_format={...}` dicts vs the unchanged `output_format=Model` helper argument | Step 6 | | `BetaBase64PDFBlockParam`, `READ_MAX_BYTES`, `ProxiesTypes` / `Transport` imported from `anthropic`, `AsyncTransport` / `ProxiesDict` imported from `anthropic._types` | renamed / removed exports | Step 7 | | `.parse(` calls that pass `stream=` | `messages.parse(stream=...)` | Step 8 | @@ -42,22 +42,22 @@ Search the scope for each signal below (`rg -n -F` for the literal strings; excl | `default_headers`, `extra_headers`, `ANTHROPIC_CUSTOM_HEADERS` | header maps to check for duplicate casings / `bytes` values | Step 9 | | `AnthropicBedrock(`, `AsyncAnthropicBedrock(` | Bedrock clients that may rely on the old region fallback | Step 10 | -Classify each hit before editing: **SDK call site** (edit), **unrelated use of the same name** (leave — e.g. `httpx` calls to other services, `urllib.parse`, a pydantic `.parse_obj`, a `temperature` variable for a thermostat), **test** (edit, and keep the test meaningful), **docs / README snippet or notebook inside the scope** (edit — for `.ipynb`, the greps match inside the JSON cell sources; edit the source strings, `%pip install` lines included, and keep the JSON valid). Never touch installed packages or vendored third-party code. +Classify each hit before editing: **SDK call site** (edit), **unrelated use of the same name** (leave - e.g. `httpx` calls to other services, `urllib.parse`, a pydantic `.parse_obj`, a `temperature` variable for a thermostat), **test** (edit, and keep the test meaningful), **docs / README snippet or notebook inside the scope** (edit - for `.ipynb`, the greps match inside the JSON cell sources; edit the source strings, `%pip install` lines included, and keep the JSON valid). Never touch installed packages or vendored third-party code. -## Step 2: Environment — Python ≥ 3.10 and the dependency pins +## Step 2: Environment - Python >= 3.10 and the dependency pins - **[DECIDE] Python floor.** 1.x requires Python 3.10+. If the project still declares or tests 3.9 (`requires-python = ">=3.9"`, trove classifiers, a `3.9` CI matrix entry, tox/nox envs, a `python:3.9` base image), that is the user's decision, not a silent edit: propose the floor bump and the CI-matrix change as their own hunk and call it out in the report. On 3.9, `pip` simply keeps resolving the last 0.x release, so nothing breaks until they move. -- **[BREAKS] The `anthropic` requirement.** Rewrite it in the file's existing style — `anthropic>=1,<2` for a range, `anthropic~=1.0` / Poetry `^1.0` for compatible-release styles, `anthropic==` where the project pins exactly. Extras (`anthropic[bedrock]`, `[vertex]`, `[aiohttp]`) are unchanged. Regenerate the lockfile with the project's own tool (`uv lock`, `poetry lock`, `pip-compile`, `pipenv lock`) if you can run it; otherwise give the user the exact command. -- **`httpx-aiohttp`.** If it is pinned only so `DefaultAioHttpClient()` works, remove it — the aiohttp transport now ships inside the SDK and the `aiohttp` extra installs only `aiohttp`. -- **`httpx2` / `httpx`.** After Step 3, if any project module imports `httpx2` directly, add `httpx2` to the declared dependencies (it arrives transitively with `anthropic`, but direct imports should be declared). `httpx2` has its own version line starting at 2.0 — write `httpx2>=2.0` (or match what `anthropic` resolved: `pip index versions httpx2`), never a specifier copied from the old `httpx` pin such as `>=0.27`. Keep `httpx` declared only if the project still uses it for something other than the SDK. +- **[BREAKS] The `anthropic` requirement.** Rewrite it in the file's existing style - `anthropic>=1,<2` for a range, `anthropic~=1.0` / Poetry `^1.0` for compatible-release styles, `anthropic==` where the project pins exactly. Extras (`anthropic[bedrock]`, `[vertex]`, `[aiohttp]`) are unchanged. Regenerate the lockfile with the project's own tool (`uv lock`, `poetry lock`, `pip-compile`, `pipenv lock`) if you can run it; otherwise give the user the exact command. +- **`httpx-aiohttp`.** If it is pinned only so `DefaultAioHttpClient()` works, remove it - the aiohttp transport now ships inside the SDK and the `aiohttp` extra installs only `aiohttp`. +- **`httpx2` / `httpx`.** After Step 3, if any project module imports `httpx2` directly, add `httpx2` to the declared dependencies (it arrives transitively with `anthropic`, but direct imports should be declared). `httpx2` has its own version line starting at 2.0 - write `httpx2>=2.0` (or match what `anthropic` resolved: `pip index versions httpx2`), never a specifier copied from the old `httpx` pin such as `>=0.27`. Keep `httpx` declared only if the project still uses it for something other than the SDK. Pydantic v1 and v2 both remain supported; nothing else about the environment changes. -## Step 3: `httpx` → `httpx2`, only where objects cross the SDK boundary +## Step 3: `httpx` -> `httpx2`, only where objects cross the SDK boundary `httpx2` is the API-compatible, maintained fork of `httpx` (same classes, same behaviour). The change only matters for `httpx` objects handed **to** the SDK or received **from** it; plain values (`timeout=30.0`, `max_retries=3`) need nothing. -- **[BREAKS] Objects passed in.** `httpx.Timeout`, `httpx.Limits`, transports (`httpx.HTTPTransport(...)`, `AsyncHTTPTransport`, `MockTransport`), and whole clients (`httpx.Client` / `AsyncClient` as `http_client=`) must come from `httpx2`. An old-`httpx` client passed as `http_client=` raises `TypeError` at construction. This includes the project's own middleware, not just the outermost object handed to `Anthropic(...)`: a `class TracingTransport(httpx.BaseTransport)` subclass, the inner `httpx.HTTPTransport()` a wrapper delegates to, an `httpx.Auth` flow, and the annotations on `event_hooks` callables all re-base onto `httpx2` — a wrapper left delegating to an old-`httpx` transport hands the SDK `httpx.Response` objects. If the module uses `httpx` only for the SDK, alias the import (`import httpx2 as httpx`) and nothing else changes; if it also talks to other services with `httpx`, import both and switch only the SDK-bound objects to `httpx2`. Prefer the SDK's own re-exports where they let you drop the import entirely: `anthropic.Timeout`, `anthropic.DefaultHttpxClient`, `anthropic.DefaultAsyncHttpxClient`, `anthropic.DefaultAioHttpClient` (all already `httpx2`-based, all unchanged). +- **[BREAKS] Objects passed in.** `httpx.Timeout`, `httpx.Limits`, transports (`httpx.HTTPTransport(...)`, `AsyncHTTPTransport`, `MockTransport`), and whole clients (`httpx.Client` / `AsyncClient` as `http_client=`) must come from `httpx2`. An old-`httpx` client passed as `http_client=` raises `TypeError` at construction. This includes the project's own middleware, not just the outermost object handed to `Anthropic(...)`: a `class TracingTransport(httpx.BaseTransport)` subclass, the inner `httpx.HTTPTransport()` a wrapper delegates to, an `httpx.Auth` flow, and the annotations on `event_hooks` callables all re-base onto `httpx2` - a wrapper left delegating to an old-`httpx` transport hands the SDK `httpx.Response` objects. If the module uses `httpx` only for the SDK, alias the import (`import httpx2 as httpx`) and nothing else changes; if it also talks to other services with `httpx`, import both and switch only the SDK-bound objects to `httpx2`. Prefer the SDK's own re-exports where they let you drop the import entirely: `anthropic.Timeout`, `anthropic.DefaultHttpxClient`, `anthropic.DefaultAsyncHttpxClient`, `anthropic.DefaultAioHttpClient` (all already `httpx2`-based, all unchanged). ```python # Before @@ -79,7 +79,7 @@ Pydantic v1 and v2 both remain supported; nothing else about the environment cha ) ``` -- **[DECIDE] Or alias process-wide, for applications.** `httpx2.alias_httpx()` makes `import httpx` / `import httpcore` resolve to `httpx2` / `httpcore2` for the whole process, so nothing else needs editing. Reach for it instead of the import edits when the scope is an **application** that shares clients, transports or exception types between the SDK and other `httpx` code, or that relies on tooling which patches `httpx` itself (tracing / APM instrumentation, HTTP mocking — see **Instrumentation and tests** below). Two hard rules: it must run before anything imports `httpx` or `httpcore` (otherwise it raises `RuntimeError`; calling it twice is a no-op), so it goes at the very top of the entry point; and it is for applications only — never add it to a **library's** import path on behalf of that library's users (edit the imports there instead). Say which you chose and why in the report. +- **[DECIDE] Or alias process-wide, for applications.** `httpx2.alias_httpx()` makes `import httpx` / `import httpcore` resolve to `httpx2` / `httpcore2` for the whole process, so nothing else needs editing. Reach for it instead of the import edits when the scope is an **application** that shares clients, transports or exception types between the SDK and other `httpx` code, or that relies on tooling which patches `httpx` itself (tracing / APM instrumentation, HTTP mocking - see **Instrumentation and tests** below). Two hard rules: it must run before anything imports `httpx` or `httpcore` (otherwise it raises `RuntimeError`; calling it twice is a no-op), so it goes at the very top of the entry point; and it is for applications only - never add it to a **library's** import path on behalf of that library's users (edit the imports there instead). Say which you chose and why in the report. ```python # the very first lines of the application's entry point @@ -90,9 +90,9 @@ Pydantic v1 and v2 both remain supported; nothing else about the environment cha import httpx # now the httpx2 module: httpx.Client is httpx2.Client ``` -- **[BREAKS] Objects coming out.** `APIStatusError.response`, `APIConnectionError.request`, `.http_response` / `.headers` / `.url` on raw and streaming responses, the `request` / `response` arguments your `http_client` event hooks receive, and `cast_to=httpx.Response` on the low-level `client.get/post/...` methods are now `httpx2` types with identical attributes. Only `isinstance` checks and annotations naming `httpx.Response` / `httpx.Request` / `httpx.Headers` / `httpx.URL` change (`httpx2.Response`, …). +- **[BREAKS] Objects coming out.** `APIStatusError.response`, `APIConnectionError.request`, `.http_response` / `.headers` / `.url` on raw and streaming responses, the `request` / `response` arguments your `http_client` event hooks receive, and `cast_to=httpx.Response` on the low-level `client.get/post/...` methods are now `httpx2` types with identical attributes. Only `isinstance` checks and annotations naming `httpx.Response` / `httpx.Request` / `httpx.Headers` / `httpx.URL` change (`httpx2.Response`, ...). - **Removed re-exports.** `anthropic.Transport` and `anthropic.ProxiesTypes` (and `AsyncTransport` / `ProxiesDict` from `anthropic._types`) are gone; use `httpx2.BaseTransport`, `httpx2.AsyncBaseTransport`, `httpx2.Proxy` (or a proxy URL string). -- **Instrumentation and tests.** Libraries that observe or stub HTTP by patching `httpx` — OpenTelemetry's `HTTPXClientInstrumentor`, Sentry's `httpx` integration, `respx`, `pytest-httpx`, `vcrpy` — keep importing fine but silently stop seeing the SDK's requests, so nothing fails loudly. The fix is the same `httpx2.alias_httpx()` call — not swapping in some `*-httpx2` instrumentation package (verify any such name is a real, populated release before depending on it) — made before any of them (or `httpx`) is imported: at the top of the application entry point for instrumentation, and under pytest as an early plugin so it runs before `respx` / `pytest-httpx` and the test modules load: +- **Instrumentation and tests.** Libraries that observe or stub HTTP by patching `httpx` - OpenTelemetry's `HTTPXClientInstrumentor`, Sentry's `httpx` integration, `respx`, `pytest-httpx`, `vcrpy` - keep importing fine but silently stop seeing the SDK's requests, so nothing fails loudly. The fix is the same `httpx2.alias_httpx()` call - not swapping in some `*-httpx2` instrumentation package (verify any such name is a real, populated release before depending on it) - made before any of them (or `httpx`) is imported: at the top of the application entry point for instrumentation, and under pytest as an early plugin so it runs before `respx` / `pytest-httpx` and the test modules load: ```python # tests/_alias_httpx.py @@ -114,17 +114,17 @@ Pydantic v1 and v2 both remain supported; nothing else about the environment cha `.with_raw_response` used to return `LegacyAPIResponse` on both clients; it now returns the same classes `.with_streaming_response` already used. Two consequences: -- **[BREAKS] On async clients, reading the body is awaited** — `parse()`, `json()`, `text()`, `read()` are coroutines. Decide sync vs async from the client the accessor hangs off (`AsyncAnthropic` and the other `Async*` platform clients) or an `await` on the `.with_raw_response...(...)` call itself — not from the enclosing function alone. -- **[BREAKS] `.text` and `.content` are methods now, on the sync client too:** `.text` → `.text()`, `.content` → `.read()`. The new classes also expose `json()` and the `iter_bytes()` / `iter_text()` / `iter_lines()` iterators directly; 0.x code reached those through `r.http_response`, which still works and need not be rewritten. +- **[BREAKS] On async clients, reading the body is awaited** - `parse()`, `json()`, `text()`, `read()` are coroutines. Decide sync vs async from the client the accessor hangs off (`AsyncAnthropic` and the other `Async*` platform clients) or an `await` on the `.with_raw_response...(...)` call itself - not from the enclosing function alone. +- **[BREAKS] `.text` and `.content` are methods now, on the sync client too:** `.text` -> `.text()`, `.content` -> `.read()`. The new classes also expose `json()` and the `iter_bytes()` / `iter_text()` / `iter_lines()` iterators directly; 0.x code reached those through `r.http_response`, which still works and need not be rewritten. | 0.x (`LegacyAPIResponse`) | 1.x sync (`APIResponse`) | 1.x async (`AsyncAPIResponse`) | |---|---|---| | `r.parse()` | `r.parse()` | `await r.parse()` | | `r.text` | `r.text()` | `await r.text()` | | `r.content` | `r.read()` | `await r.read()` | -| — (only `r.http_response.json()`) | `r.json()` | `await r.json()` | -| — (only `r.http_response.iter_bytes()` …) | `r.iter_bytes()` / `.iter_text()` / `.iter_lines()` | `async for chunk in r.iter_bytes():` … | -| `.headers`, `.status_code`, `.url`, `.request_id`, `.retries_taken`, `.http_response`, `.elapsed` | unchanged | unchanged (plain attributes — never awaited) | +| - (only `r.http_response.json()`) | `r.json()` | `await r.json()` | +| - (only `r.http_response.iter_bytes()` ...) | `r.iter_bytes()` / `.iter_text()` / `.iter_lines()` | `async for chunk in r.iter_bytes():` ... | +| `.headers`, `.status_code`, `.url`, `.request_id`, `.retries_taken`, `.http_response`, `.elapsed` | unchanged | unchanged (plain attributes - never awaited) | ```python # Before (async client) @@ -140,14 +140,14 @@ message = await raw.parse() Anchor every edit on a value that demonstrably comes from a `.with_raw_response.` call (follow it through variables, return values and fixtures); do not touch `.parse()` / `.text` on unrelated objects, and do not double-await. Annotations and imports of `anthropic._legacy_response.LegacyAPIResponse` become `anthropic.APIResponse` / `anthropic.AsyncAPIResponse`. `.with_streaming_response` code is unchanged. -## Step 5: Text Completions → Messages (the one non-mechanical change) +## Step 5: Text Completions -> Messages (the one non-mechanical change) **[BREAKS]** `client.completions.create()` (`/v1/complete`), the `Completion` types, and the `anthropic.HUMAN_PROMPT` / `anthropic.AI_PROMPT` constants are removed (also from `AnthropicBedrock`). Port each call to `client.messages.create()`: -- the `f"{HUMAN_PROMPT} …{AI_PROMPT}"` prompt string becomes `messages=[{"role": "user", "content": "…"}]`; text that preceded the first `HUMAN_PROMPT` as instructions becomes `system=`; alternating `HUMAN_PROMPT`/`AI_PROMPT` turns become alternating `user`/`assistant` messages; -- `max_tokens_to_sample=` → `max_tokens=`; `stop_sequences=` carries over; drop `temperature`/`top_p`/`top_k` (Step 6); -- `completion.completion` → the text blocks of `message.content` (`"".join(b.text for b in message.content if b.type == "text")`); `stop_reason` values carry over (`"stop_sequence"`, `"max_tokens"`), with `"end_turn"` as the new normal-completion value; -- `stream=True` completions → `client.messages.stream(...)` and its `text_stream`. +- the `f"{HUMAN_PROMPT} ...{AI_PROMPT}"` prompt string becomes `messages=[{"role": "user", "content": "..."}]`; text that preceded the first `HUMAN_PROMPT` as instructions becomes `system=`; alternating `HUMAN_PROMPT`/`AI_PROMPT` turns become alternating `user`/`assistant` messages; +- `max_tokens_to_sample=` -> `max_tokens=`; `stop_sequences=` carries over; drop `temperature`/`top_p`/`top_k` (Step 6); +- `completion.completion` -> the text blocks of `message.content` (`"".join(b.text for b in message.content if b.type == "text")`); `stop_reason` values carry over (`"stop_sequence"`, `"max_tokens"`), with `"end_turn"` as the new normal-completion value; +- `stream=True` completions -> `client.messages.stream(...)` and its `text_stream`. ```python # Before @@ -169,11 +169,11 @@ message = client.messages.create( print("".join(block.text for block in message.content if block.type == "text")) ``` -**[DECIDE] The model.** Code still on Text Completions usually pins a retired model (`claude-2.x`, `claude-instant-*`), which 404s regardless of SDK version. Keep a model that is still served; otherwise switch to `claude-opus-5` so the code runs, say so prominently in the report, and point the user at `/claude-api migrate` for validating prompts against the new model — a completions-era prompt is exactly what `shared/prompt-audit.md` exists for. +**[DECIDE] The model.** Code still on Text Completions usually pins a retired model (`claude-2.x`, `claude-instant-*`), which 404s regardless of SDK version. Keep a model that is still served; otherwise switch to `claude-opus-5` so the code runs, say so prominently in the report, and point the user at `/claude-api migrate` for validating prompts against the new model - a completions-era prompt is exactly what `shared/prompt-audit.md` exists for. ## Step 6: Removed request parameters -- **[BREAKS] `temperature`, `top_p`, `top_k`** are no longer accepted by `messages.create()` / `.stream()` / `.parse()`, their `beta.messages` counterparts, or `beta.messages.tool_runner()` (passing them is a `TypeError`), and are gone from the per-request `params` TypedDict of `messages.batches.create()` (a type checker flags the key; at runtime the SDK still forwards it). Delete them — they are gone from the 1.x signatures, not from the API, and whether a model still honours them is a model question (`shared/model-migration.md`): Opus 4.7 and later return a 400 for any request that carries one (the default value included), Claude Sonnet 5 rejects non-default values, and every still-served model before those accepts them — the Claude 4.6 / 4.5 line (Opus 4.6, Sonnet 4.6, Opus 4.5, Sonnet 4.5, Haiku 4.5) and the deprecated-but-still-served Claude 4 models (`shared/models.md` → Deprecated Models). So **[DECIDE]** when the call pins one of those accepting models and visibly depends on the setting (a documented determinism requirement, an A/B on temperature), move it into `extra_body` instead of deleting it — `extra_body={"temperature": 0.2}` is merged into the request JSON as-is — and for a `messages.batches.create()` request leave the key in that request's `params` dict (it is forwarded, see above). A call that pins a retired model (`shared/models.md` → Retired Models) is the `migrate` flow's problem first: it needs a replacement model, and the replacement decides whether the setting survives. Say which calls kept a setting this way in the report. When a test existed only to assert that these parameters pass through, keep it meaningful by asserting on parameters that still exist (`stop_sequences`, `metadata`, `service_tier`, `max_tokens`) rather than deleting it. +- **[BREAKS] `temperature`, `top_p`, `top_k`** are no longer accepted by `messages.create()` / `.stream()` / `.parse()`, their `beta.messages` counterparts, or `beta.messages.tool_runner()` (passing them is a `TypeError`), and are gone from the per-request `params` TypedDict of `messages.batches.create()` (a type checker flags the key; at runtime the SDK still forwards it). Delete them - they are gone from the 1.x signatures, not from the API, and whether a model still honours them is a model question (`shared/model-migration.md`): Opus 4.7 and later return a 400 for any request that carries one (the default value included), Claude Sonnet 5 rejects non-default values, and every still-served model before those accepts them - the Claude 4.6 / 4.5 line (Opus 4.6, Sonnet 4.6, Opus 4.5, Sonnet 4.5, Haiku 4.5) and the deprecated-but-still-served Claude 4 models (`shared/models.md` -> Deprecated Models). So **[DECIDE]** when the call pins one of those accepting models and visibly depends on the setting (a documented determinism requirement, an A/B on temperature), move it into `extra_body` instead of deleting it - `extra_body={"temperature": 0.2}` is merged into the request JSON as-is - and for a `messages.batches.create()` request leave the key in that request's `params` dict (it is forwarded, see above). A call that pins a retired model (`shared/models.md` -> Retired Models) is the `migrate` flow's problem first: it needs a replacement model, and the replacement decides whether the setting survives. Say which calls kept a setting this way in the report. When a test existed only to assert that these parameters pass through, keep it meaningful by asserting on parameters that still exist (`stop_sequences`, `metadata`, `service_tier`, `max_tokens`) rather than deleting it. ```python # Before @@ -183,7 +183,7 @@ print("".join(block.text for block in message.content if block.type == "text")) client.messages.create(..., model="claude-sonnet-4-6", extra_body={"temperature": 0.2}) ``` -- **[BREAKS] `output_format={...}` as a raw dict/TypedDict** — on `beta.messages.create()`, `beta.messages.count_tokens()` and batch params (where the parameter is gone) and on the `messages.stream()` / `messages.count_tokens()` / `beta.messages.stream()` helpers (which used to accept a dict as well and now raise `TypeError` for one) → `output_config={"format": {...}}` (merge into an existing `output_config` if one is already passed, e.g. alongside `effort`). **Leave `output_format=SomeModel` alone** when the value is a *type* (a Pydantic model / class passed to the `parse()`, `stream()` or `tool_runner()` helpers, or to the non-beta `messages.count_tokens()`) — that is the one form the helpers still take (`beta.messages.count_tokens()` only ever took the dict form, and has no `output_format` at all now). Tell them apart by the value: dict literal / `{"type": "json_schema", ...}` → migrate; a class name → keep. +- **[BREAKS] `output_format={...}` as a raw dict/TypedDict** - on `beta.messages.create()`, `beta.messages.count_tokens()` and batch params (where the parameter is gone) and on the `messages.stream()` / `messages.count_tokens()` / `beta.messages.stream()` helpers (which used to accept a dict as well and now raise `TypeError` for one) -> `output_config={"format": {...}}` (merge into an existing `output_config` if one is already passed, e.g. alongside `effort`). **Leave `output_format=SomeModel` alone** when the value is a *type* (a Pydantic model / class passed to the `parse()`, `stream()` or `tool_runner()` helpers, or to the non-beta `messages.count_tokens()`) - that is the one form the helpers still take (`beta.messages.count_tokens()` only ever took the dict form, and has no `output_format` at all now). Tell them apart by the value: dict literal / `{"type": "json_schema", ...}` -> migrate; a class name -> keep. ```python # Before @@ -202,7 +202,7 @@ print("".join(block.text for block in message.content if block.type == "text")) |---|---| | `anthropic.types.beta.BetaBase64PDFBlockParam` | `anthropic.types.beta.BetaRequestDocumentBlockParam` | | `anthropic.Transport` / `anthropic.ProxiesTypes` (and `anthropic._types.AsyncTransport` / `ProxiesDict`) | `httpx2.BaseTransport` / `httpx2.Proxy` (`httpx2.AsyncBaseTransport`) | -| `anthropic.HUMAN_PROMPT` / `anthropic.AI_PROMPT` | none — Step 5 | +| `anthropic.HUMAN_PROMPT` / `anthropic.AI_PROMPT` | none - Step 5 | | `anthropic.lib.tools.agent_toolset.READ_MAX_BYTES` | `anthropic.lib.tools.agent_toolset.DEFAULT_MAX_FILE_BYTES` | ## Step 8: Removed helper arguments and behaviour @@ -219,7 +219,7 @@ print("".join(block.text for block in message.content if block.type == "text")) ``` A `parse(..., stream=False)` just loses the argument. -- **[BREAKS] `tool_runner(compaction_control=...)`** — client-side compaction is removed in favour of server-side compaction. Carry the old `context_token_threshold` over as the trigger value (the API minimum is 50,000; raise smaller values to that and mention it): +- **[BREAKS] `tool_runner(compaction_control=...)`** - client-side compaction is removed in favour of server-side compaction. Carry the old `context_token_threshold` over as the trigger value (the API minimum is 50,000; raise smaller values to that and mention it): ```python # Before @@ -233,7 +233,7 @@ print("".join(block.text for block in message.content if block.type == "text")) ) ``` - If the loop around the runner rebuilds `messages` itself, make sure it appends the full `message.content` (compaction blocks included) — see the Compaction section of `python/claude-api/README.md`. + If the loop around the runner rebuilds `messages` itself, make sure it appends the full `message.content` (compaction blocks included) - see the Compaction section of `python/claude-api/README.md`. - **[BREAKS] Raw `bytes` as `body=`** on `client.get/post/put/patch/delete`: `body=` is always JSON-serialised now; raw payloads (and iterators, for streaming uploads) go through `content=`: ```python @@ -248,18 +248,18 @@ print("".join(block.text for block in message.content if block.type == "text")) ## Step 9: Header names are matched case-insensitively -Usually nothing to edit. The SDK now merges `default_headers`, `extra_headers`, `with_options(default_headers=...)` and `ANTHROPIC_CUSTOM_HEADERS` case-insensitively: a later entry replaces an earlier header of the same name whatever its casing (including headers the SDK sets itself), and `omit` removes one the same way. Scan the Step 1 hits for two things and fix only those: **[DECIDE]** the same header name spelled with two casings where the code relied on both lines being sent (send one comma-joined value instead), and **[BREAKS]** `bytes` header values, which now raise — `.decode()` them. +Usually nothing to edit. The SDK now merges `default_headers`, `extra_headers`, `with_options(default_headers=...)` and `ANTHROPIC_CUSTOM_HEADERS` case-insensitively: a later entry replaces an earlier header of the same name whatever its casing (including headers the SDK sets itself), and `omit` removes one the same way. Scan the Step 1 hits for two things and fix only those: **[DECIDE]** the same header name spelled with two casings where the code relied on both lines being sent (send one comma-joined value instead), and **[BREAKS]** `bytes` header values, which now raise - `.decode()` them. -## Step 10: Bedrock — a region is required +## Step 10: Bedrock - a region is required -**[DECIDE]** `AnthropicBedrock()` / `AsyncAnthropicBedrock()` used to warn and fall back to `us-east-1` when no region was configured; they now raise `ValueError` at construction. Resolution order: `aws_region=` → `AWS_REGION` / `AWS_DEFAULT_REGION` → the region configured for the boto3 session / `aws_profile` (the profile is now honoured for region lookup). For each construction without `aws_region=`, check whether the deployment provides a region (env files, Dockerfiles, deployment manifests, AWS profile config in the repo). If it demonstrably does, nothing to do; if you cannot tell, do **not** invent a region — list the call site in the report as needing `aws_region=` or `AWS_REGION`, and only hardcode `"us-east-1"` if the user confirms that the old implicit default is what they were actually using. +**[DECIDE]** `AnthropicBedrock()` / `AsyncAnthropicBedrock()` used to warn and fall back to `us-east-1` when no region was configured; they now raise `ValueError` at construction. Resolution order: `aws_region=` -> `AWS_REGION` / `AWS_DEFAULT_REGION` -> the region configured for the boto3 session / `aws_profile` (the profile is now honoured for region lookup). For each construction without `aws_region=`, check whether the deployment provides a region (env files, Dockerfiles, deployment manifests, AWS profile config in the repo). If it demonstrably does, nothing to do; if you cannot tell, do **not** invent a region - list the call site in the report as needing `aws_region=` or `AWS_REGION`, and only hardcode `"us-east-1"` if the user confirms that the old implicit default is what they were actually using. -Streaming from Bedrock also changes: event types the SDK does not know are now skipped instead of yielded — the only known case is the `amazon-bedrock-invocationMetrics` frame. Code that filtered those frames out can be deleted; code that *consumed* invocation metrics loses them on 1.x — **[DECIDE]** list it in the report (the SDK asks such users to open an issue). +Streaming from Bedrock also changes: event types the SDK does not know are now skipped instead of yielded - the only known case is the `amazon-bedrock-invocationMetrics` frame. Code that filtered those frames out can be deleted; code that *consumed* invocation metrics loses them on 1.x - **[DECIDE]** list it in the report (the SDK asks such users to open an issue). ## Step 11: Verify -1. Re-run the Step 1 greps over the scope. Every remaining hit needs a reason (unrelated `httpx` use, `Raw*` names, helper `output_format=Model`, …) — put the reasons in the report. -2. `python -m compileall -q ` must pass. If the project has a type checker configured, run it — nearly every missed call site is a type error on 1.x. Run the test suite if it is runnable without credentials. +1. Re-run the Step 1 greps over the scope. Every remaining hit needs a reason (unrelated `httpx` use, `Raw*` names, helper `output_format=Model`, ...) - put the reasons in the report. +2. `python -m compileall -q ` must pass. If the project has a type checker configured, run it - nearly every missed call site is a type error on 1.x. Run the test suite if it is runnable without credentials. 3. If 1.x is installed in the environment: `python -c "import anthropic, httpx2; print(anthropic.__version__)"`. ## Step 12: Report @@ -267,20 +267,20 @@ Streaming from Bedrock also changes: event types the SDK does not know are now s Lead with the outcome, then: - what changed, grouped by the steps above, with file counts and the notable files; -- **decisions the user owns** — Python floor / CI matrix (Step 2), import edits vs `alias_httpx()` (Step 3), sampling-parameter reliance (Step 6), the model chosen for ported completions calls (Step 5), duplicate-casing headers (Step 9), Bedrock regions and invocation metrics (Step 10); +- **decisions the user owns** - Python floor / CI matrix (Step 2), import edits vs `alias_httpx()` (Step 3), sampling-parameter reliance (Step 6), the model chosen for ported completions calls (Step 5), duplicate-casing headers (Step 9), Bedrock regions and invocation metrics (Step 10); - if you introduced `httpx2` anywhere, one provenance line, because reviewers and supply-chain scanners flag unfamiliar package names as possible typosquats: it is the SDK's own HTTP dependency, the maintained fork of `httpx` by its original author, published by Pydantic (`github.com/pydantic/httpx2`), version line 2.x; - what you could not verify (offline PyPI check, no type checker, tests not runnable, pre-commit hooks that need the new packages installed) and the exact commands to finish: the install / lock command and, if relevant, `pip uninstall httpx-aiohttp`. ## Checklist - [ ] **[BREAKS]** `anthropic` requirement moved to 1.x in the project's pin style; lockfile regenerated or command given -- [ ] **[DECIDE]** Python ≥ 3.10 floor and CI matrix proposed as a separate hunk -- [ ] **[BREAKS]** `httpx` objects passed to / received from the SDK (custom transports, auth flows and event hooks included) come from `httpx2` — or **[DECIDE]** `httpx2.alias_httpx()` at the top of an application entry point; `httpx`-patching instrumentation / mocking (`respx`, `pytest-httpx`, `vcrpy`, OpenTelemetry, Sentry) covered by the alias; `httpx-aiohttp` dropped; `httpx2>=2.0` declared if imported -- [ ] **[BREAKS]** async `.with_raw_response`: `await` on `parse()/json()/text()/read()`; `.text` → `.text()`, `.content` → `.read()` everywhere; `LegacyAPIResponse` annotations replaced +- [ ] **[DECIDE]** Python >= 3.10 floor and CI matrix proposed as a separate hunk +- [ ] **[BREAKS]** `httpx` objects passed to / received from the SDK (custom transports, auth flows and event hooks included) come from `httpx2` - or **[DECIDE]** `httpx2.alias_httpx()` at the top of an application entry point; `httpx`-patching instrumentation / mocking (`respx`, `pytest-httpx`, `vcrpy`, OpenTelemetry, Sentry) covered by the alias; `httpx-aiohttp` dropped; `httpx2>=2.0` declared if imported +- [ ] **[BREAKS]** async `.with_raw_response`: `await` on `parse()/json()/text()/read()`; `.text` -> `.text()`, `.content` -> `.read()` everywhere; `LegacyAPIResponse` annotations replaced - [ ] **[BREAKS]** `completions.create` / `HUMAN_PROMPT` / `AI_PROMPT` ported to Messages; **[DECIDE]** model choice surfaced -- [ ] **[BREAKS]** `temperature` / `top_p` / `top_k` removed from SDK calls — or, **[DECIDE]**, moved to `extra_body` only where the call pins an older model *and* visibly depends on the setting; raw `output_format={...}` → `output_config={"format": ...}` everywhere (helpers included); helper `output_format=Model` untouched -- [ ] **[BREAKS]** `BetaBase64PDFBlockParam` → `BetaRequestDocumentBlockParam`; `Transport`/`AsyncTransport`/`ProxiesTypes` → `httpx2` names; `READ_MAX_BYTES` → `DEFAULT_MAX_FILE_BYTES` -- [ ] **[BREAKS]** `parse(stream=)` → `messages.stream()`; `compaction_control` → server-side compaction; `body=bytes` → `content=`; `Stream` isinstance checks retargeted +- [ ] **[BREAKS]** `temperature` / `top_p` / `top_k` removed from SDK calls - or, **[DECIDE]**, moved to `extra_body` only where the call pins an older model *and* visibly depends on the setting; raw `output_format={...}` -> `output_config={"format": ...}` everywhere (helpers included); helper `output_format=Model` untouched +- [ ] **[BREAKS]** `BetaBase64PDFBlockParam` -> `BetaRequestDocumentBlockParam`; `Transport`/`AsyncTransport`/`ProxiesTypes` -> `httpx2` names; `READ_MAX_BYTES` -> `DEFAULT_MAX_FILE_BYTES` +- [ ] **[BREAKS]** `parse(stream=)` -> `messages.stream()`; `compaction_control` -> server-side compaction; `body=bytes` -> `content=`; `Stream` isinstance checks retargeted - [ ] **[DECIDE]** duplicate-casing headers joined; **[BREAKS]** `bytes` header values decoded - [ ] **[DECIDE]** Bedrock constructions without a discoverable region listed, not guessed; invocation-metrics consumers flagged - [ ] Step 11 verification run and Step 12 report written diff --git a/skills/claude-api/python/claude-api/streaming.md b/skills/claude-api/python/claude-api/streaming.md index e0a88fd4c..2f2070ff1 100644 --- a/skills/claude-api/python/claude-api/streaming.md +++ b/skills/claude-api/python/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — Python +# Streaming - Python ## Quick Start @@ -26,7 +26,7 @@ async with async_client.messages.stream( ### Low-level: `stream=True` -`messages.stream()` (above) is the recommended helper — it accumulates state and exposes `text_stream` / `get_final_message()`. If you only need the raw event iterator and want lower memory use, pass `stream=True` to `messages.create()` instead: +`messages.stream()` (above) is the recommended helper - it accumulates state and exposes `text_stream` / `get_final_message()`. If you only need the raw event iterator and want lower memory use, pass `stream=True` to `messages.create()` instead: ```python for event in client.messages.create( @@ -73,7 +73,7 @@ with client.messages.stream( ## Streaming with Tool Use -The Python tool runner supports streaming: pass `stream=True` to `client.beta.messages.tool_runner(...)` and each iteration yields a stream you consume event-by-event, with `get_final_message()` for the accumulated message per turn (see `shared/tool-use-concepts.md` → Tool Runner vs Manual Loop). Use the manual-loop pattern below only when you're not using the tool runner and need per-token streaming with tools: +The Python tool runner supports streaming: pass `stream=True` to `client.beta.messages.tool_runner(...)` and each iteration yields a stream you consume event-by-event, with `get_final_message()` for the accumulated message per turn (see `shared/tool-use-concepts.md` -> Tool Runner vs Manual Loop). Use the manual-loop pattern below only when you're not using the tool runner and need per-token streaming with tools: ```python with client.messages.stream( @@ -171,9 +171,9 @@ except anthropic.APIStatusError as e: ## Best Practices -1. **Always flush output** — Use `flush=True` to show tokens immediately -2. **Handle partial responses** — If the stream is interrupted, you may have incomplete content -3. **Track token usage** — The `message_delta` event contains usage information -4. **Use timeouts** — Set appropriate timeouts for your application -5. **Default to streaming** — Use `.get_final_message()` to get the complete response even when streaming, giving you timeout protection without needing to handle individual events -6. **Large `max_tokens` without streaming raises `ValueError`** — The SDK refuses non-streaming requests it estimates will exceed ~10 minutes (idle connections drop). Pass `stream=True` / use `messages.stream()`, or explicitly override `timeout`, to suppress the guard. +1. **Always flush output** - Use `flush=True` to show tokens immediately +2. **Handle partial responses** - If the stream is interrupted, you may have incomplete content +3. **Track token usage** - The `message_delta` event contains usage information +4. **Use timeouts** - Set appropriate timeouts for your application +5. **Default to streaming** - Use `.get_final_message()` to get the complete response even when streaming, giving you timeout protection without needing to handle individual events +6. **Large `max_tokens` without streaming raises `ValueError`** - The SDK refuses non-streaming requests it estimates will exceed ~10 minutes (idle connections drop). Pass `stream=True` / use `messages.stream()`, or explicitly override `timeout`, to suppress the guard. diff --git a/skills/claude-api/python/claude-api/tool-use.md b/skills/claude-api/python/claude-api/tool-use.md index ac467bfbd..9aac895d1 100644 --- a/skills/claude-api/python/claude-api/tool-use.md +++ b/skills/claude-api/python/claude-api/tool-use.md @@ -1,4 +1,4 @@ -# Tool Use — Python +# Tool Use - Python For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). @@ -42,16 +42,16 @@ For async usage, use `@beta_async_tool` with `async def` functions. **Key benefits of the tool runner:** -- No manual loop — the SDK handles calling tools and feeding results back +- No manual loop - the SDK handles calling tools and feeding results back - Type-safe tool inputs via decorators - Tool schemas are generated automatically from function signatures - Iteration stops automatically when Claude has no more tool calls ### Server tools with the tool runner -The runner's `tools` list accepts raw server-tool definitions (`web_search_20260209`, `web_fetch_20260209`, code execution) alongside decorated tools — pass the literal tool dict; server tools run on Anthropic's servers, so there is no function to implement. +The runner's `tools` list accepts raw server-tool definitions (`web_search_20260209`, `web_fetch_20260209`, code execution) alongside decorated tools - pass the literal tool dict; server tools run on Anthropic's servers, so there is no function to implement. -**Caution — the runner does not auto-resume `pause_turn` (as of `anthropic` 0.116.0).** A long-running server-tool turn can stop with `stop_reason: "pause_turn"`. The runner only continues after a client tool produces a result, so a paused turn ends the loop and is returned as the final message — no error, no warning, just a silently truncated answer. Unlike the TypeScript runner, the Python runner cannot be resumed mid-loop: it exits unconditionally when no client tool ran, and `runner.append_messages(...)` does not prevent the exit. To handle `pause_turn`, mirror the conversation history as you iterate, then restart the runner with the paused turn appended: +**Caution - the runner does not auto-resume `pause_turn` (as of `anthropic` 0.116.0).** A long-running server-tool turn can stop with `stop_reason: "pause_turn"`. The runner only continues after a client tool produces a result, so a paused turn ends the loop and is returned as the final message - no error, no warning, just a silently truncated answer. Unlike the TypeScript runner, the Python runner cannot be resumed mid-loop: it exits unconditionally when no client tool ran, and `runner.append_messages(...)` does not prevent the exit. To handle `pause_turn`, mirror the conversation history as you iterate, then restart the runner with the paused turn appended: ```python messages = [{"role": "user", "content": user_input}] @@ -68,7 +68,7 @@ while True: last = None for message in runner: last = message - # Mirror the history — the runner keeps its own copy and does not expose it + # Mirror the history - the runner keeps its own copy and does not expose it messages.append({"role": "assistant", "content": message.content}) tool_response = runner.generate_tool_call_response() # cached; tools still run once if tool_response is not None: @@ -107,7 +107,7 @@ async with stdio_client(StdioServerParameters(command="mcp-server")) as (read, w await mcp_client.initialize() tools_result = await mcp_client.list_tools() - # tool_runner is sync — returns the runner, not a coroutine + # tool_runner is sync - returns the runner, not a coroutine runner = client.beta.messages.tool_runner( model="claude-opus-5", max_tokens=16000, @@ -167,7 +167,7 @@ Conversion functions raise `UnsupportedMCPValueError` if an MCP value cannot be ## Manual Agentic Loop -Prefer the tool runner above. Drop to a manual loop only when you need control the runner does not expose (e.g., a custom transport, request shapes the SDK cannot build, or avoiding a beta dependency — the runner is beta). Human-in-the-loop approval does *not* require a manual loop — gate inside the tool function (return a "user declined" result) or inspect pending `tool_use` blocks in the `for message in runner:` body and call `runner.set_messages_params()`. +Prefer the tool runner above. Drop to a manual loop only when you need control the runner does not expose (e.g., a custom transport, request shapes the SDK cannot build, or avoiding a beta dependency - the runner is beta). Human-in-the-loop approval does *not* require a manual loop - gate inside the tool function (return a "user declined" result) or inspect pending `tool_use` blocks in the `for message in runner:` body and call `runner.set_messages_params()`. If you do need a manual loop: @@ -356,11 +356,9 @@ for block in response.content: uploaded = client.beta.files.upload(file=open("sales_data.csv", "rb")) # 2. Pass to code execution via container_upload block -# Code execution is GA; Files API is still beta (pass via extra_headers) response = client.messages.create( model="claude-opus-5", max_tokens=16000, - extra_headers={"anthropic-beta": "files-api-2025-04-14"}, messages=[{ "role": "user", "content": [ @@ -499,7 +497,7 @@ For full implementation examples, use WebFetch: ## Structured Outputs -### JSON Outputs (Pydantic — Recommended) +### JSON Outputs (Pydantic - Recommended) ```python from pydantic import BaseModel diff --git a/skills/claude-api/python/managed-agents/README.md b/skills/claude-api/python/managed-agents/README.md index cb97bfa47..825dd5fc1 100644 --- a/skills/claude-api/python/managed-agents/README.md +++ b/skills/claude-api/python/managed-agents/README.md @@ -1,8 +1,8 @@ -# Managed Agents — Python +# Managed Agents - Python > **Bindings not shown here:** This README covers the most common managed-agents flows for Python. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the Python SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK. -> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `agents.create` and pass it to every subsequent `sessions.create`; do not call `agents.create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. +> **Agents are persistent - create once, reference by ID.** Store the agent ID returned by `agents.create` and pass it to every subsequent `sessions.create`; do not call `agents.create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. ## Installation @@ -15,7 +15,7 @@ pip install anthropic ```python import anthropic -# Default — resolves credentials from the environment: +# Default - resolves credentials from the environment: # ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN, or an `ant auth login` profile. # Prefer this for local dev; don't hardcode a key. client = anthropic.Anthropic() @@ -43,7 +43,7 @@ print(environment.id) # env_... ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `agents.create()` — the session only takes `agent={"type": "agent", "id": agent.id}`. +> Warning: **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `agents.create()` - the session only takes `agent={"type": "agent", "id": agent.id}`. ### Minimal @@ -122,7 +122,7 @@ client.beta.sessions.events.send( ) ``` -> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). +> Tip: **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens - stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). --- @@ -152,7 +152,7 @@ with client.beta.sessions.events.stream( if block.type == "text": print(block.text, end="", flush=True) elif event.type == "agent.custom_tool_use": - # Custom tool invocation — session is now idle + # Custom tool invocation - session is now idle print(f"\nCustom tool call: {event.name}") print(f"Input: {json.dumps(event.input)}") # Send result back (see below) @@ -192,7 +192,7 @@ for event in events.data: print(f"{event.type}: {event.id}") ``` -> ⚠️ **Prefer the SDK over raw `requests`/`httpx`.** If you hand-roll a poll loop, don't assume `timeout=(5, 60)` or `httpx.Timeout(120)` caps total call duration — both are **per-chunk** read timeouts (reset on every byte), so a trickling response can block forever. For a hard wall-clock deadline, track `time.monotonic()` at the loop level and bail explicitly, or wrap with `asyncio.wait_for()`. See [Receiving Events](../../shared/managed-agents-events.md#receiving-events). +> Warning: **Prefer the SDK over raw `requests`/`httpx`.** If you hand-roll a poll loop, don't assume `timeout=(5, 60)` or `httpx.Timeout(120)` caps total call duration - both are **per-chunk** read timeouts (reset on every byte), so a trickling response can block forever. For a hard wall-clock deadline, track `time.monotonic()` at the loop level and bail explicitly, or wrap with `asyncio.wait_for()`. See [Receiving Events](../../shared/managed-agents-events.md#receiving-events). --- @@ -285,7 +285,7 @@ for f in files.data: file_content.write_to_file(f.filename) ``` -> 💡 There's a brief indexing lag (~1–3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if the list is empty. +> Tip: There's a brief indexing lag (~1-3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if the list is empty. --- @@ -311,7 +311,7 @@ client.beta.sessions.archive(session_id="sesn_011CZxAbc123Def456") ## MCP Server Integration ```python -# Agent declares MCP server (no auth here — auth goes in a vault) +# Agent declares MCP server (no auth here - auth goes in a vault) agent = client.beta.agents.create( name="MCP Agent", model="claude-opus-5", diff --git a/skills/claude-api/ruby/claude-api/README.md b/skills/claude-api/ruby/claude-api/README.md index 2a0466854..0349b0ca0 100644 --- a/skills/claude-api/ruby/claude-api/README.md +++ b/skills/claude-api/ruby/claude-api/README.md @@ -1,4 +1,4 @@ -# Claude API — Ruby +# Claude API - Ruby > **Note:** The Ruby SDK supports the Claude API. A tool runner is available in beta via `client.beta.messages.tool_runner()`. Agent SDK is not yet available for Ruby. @@ -33,7 +33,7 @@ message = client.messages.create( ] ) # content is an array of polymorphic block objects (TextBlock, ThinkingBlock, -# ToolUseBlock, ...). .type is a Symbol — compare with :text, not "text". +# ToolUseBlock, ...). .type is a Symbol - compare with :text, not "text". # .text raises NoMethodError on non-TextBlock entries. message.content.each do |block| puts block.text if block.type == :text @@ -45,7 +45,7 @@ end ## Extended Thinking > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking. `budget_tokens` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. -> **Claude Opus 5:** thinking is on by default — omitting `thinking:` runs adaptive (`{ type: "adaptive" }` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{ type: "disabled" }` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. +> **Claude Opus 5:** thinking is on by default - omitting `thinking:` runs adaptive (`{ type: "adaptive" }` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{ type: "disabled" }` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400. > **Older models:** Use `thinking: { type: "enabled", budget_tokens: N }` (must be < `max_tokens`, min 1024). ```ruby @@ -68,7 +68,7 @@ end ## Prompt Caching -`system_:` (trailing underscore — avoids shadowing `Kernel#system`) takes an array of text blocks; set `cache_control` on the last block. Plain hashes work via the `OrHash` type alias. For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. +`system_:` (trailing underscore - avoids shadowing `Kernel#system`) takes an array of text blocks; set `cache_control` on the last block. Plain hashes work via the `OrHash` type alias. For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`. ```ruby message = client.messages.create( @@ -93,12 +93,12 @@ When `stop_reason` is `:refusal`, the response includes structured `stop_details ```ruby if message.stop_reason == :refusal && message.stop_details - puts "Category: #{message.stop_details.category}" # e.g. :cyber, :bio, :reasoning_extraction, :frontier_llm, or nil — see docs for the full set + puts "Category: #{message.stop_details.category}" # e.g. :cyber, :bio, :reasoning_extraction, :frontier_llm, or nil - see docs for the full set puts "Explanation: #{message.stop_details.explanation}" end ``` -**Refusal fallbacks (Claude Fable 5) — opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, `fallbacks: [{model: "claude-opus-4-8"}]` on the beta messages call) by default. The exact Ruby binding (and the client-side middleware for providers without server-side support) is not documented here — WebFetch the Ruby SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` → Migrating to Claude Fable 5 → `refusal` stop reason. +**Refusal fallbacks (Claude Fable 5.1) - opt in by default.** Fallbacks are opt-in: without them a refused request simply stops. New `claude-fable-5-1` code should include the server-side `fallbacks` parameter (beta header `server-side-fallback-2026-06-01`, `fallbacks: [{model: "claude-opus-4-8"}]` on the beta messages call) by default. The exact Ruby binding (and the client-side middleware for providers without server-side support) is not documented here - WebFetch the Ruby SDK repo's `examples/` from `shared/live-sources.md`; full semantics in `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason. --- diff --git a/skills/claude-api/ruby/claude-api/streaming.md b/skills/claude-api/ruby/claude-api/streaming.md index 2de9260aa..fa23fe384 100644 --- a/skills/claude-api/ruby/claude-api/streaming.md +++ b/skills/claude-api/ruby/claude-api/streaming.md @@ -1,4 +1,4 @@ -# Streaming — Ruby +# Streaming - Ruby ## Streaming diff --git a/skills/claude-api/ruby/claude-api/tool-use.md b/skills/claude-api/ruby/claude-api/tool-use.md index 70914b9ee..d23af793c 100644 --- a/skills/claude-api/ruby/claude-api/tool-use.md +++ b/skills/claude-api/ruby/claude-api/tool-use.md @@ -1,4 +1,4 @@ -# Tool Use — Ruby +# Tool Use - Ruby For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). diff --git a/skills/claude-api/ruby/managed-agents/README.md b/skills/claude-api/ruby/managed-agents/README.md index 4b58dc96d..e21d77f1f 100644 --- a/skills/claude-api/ruby/managed-agents/README.md +++ b/skills/claude-api/ruby/managed-agents/README.md @@ -1,8 +1,8 @@ -# Managed Agents — Ruby +# Managed Agents - Ruby > **Bindings not shown here:** This README covers the most common managed-agents flows for Ruby. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the Ruby SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK. -> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `client.beta.agents.create` and pass it to every subsequent `client.beta.sessions.create`; do not call `agents.create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. +> **Agents are persistent - create once, reference by ID.** Store the agent ID returned by `client.beta.agents.create` and pass it to every subsequent `client.beta.sessions.create`; do not call `agents.create` in the request path. **Recommended:** define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md` (its live-docs URL is in `shared/live-sources.md`). The CLI owns the control plane (create/update); your code owns the data plane (sessions with the stored ID). The examples below show in-code creation for when you must provision programmatically; in production the create call belongs in setup, not in the request path. ## Installation @@ -22,7 +22,7 @@ client = Anthropic::Client.new client = Anthropic::Client.new(api_key: "your-api-key") ``` -> ⚠️ **Trailing underscores:** The Ruby SDK uses `system_:` and `send_(` (trailing underscore) to avoid shadowing `Kernel#system` and `Kernel#send`. Use these forms throughout managed-agents code. +> Warning: **Trailing underscores:** The Ruby SDK uses `system_:` and `send_(` (trailing underscore) to avoid shadowing `Kernel#system` and `Kernel#send`. Use these forms throughout managed-agents code. --- @@ -43,7 +43,7 @@ puts "Environment ID: #{environment.id}" # env_... ## Create an Agent (required first step) -> ⚠️ **There is no inline agent config.** `model`/`system_`/`tools` live on the agent object, not the session. Always start with `client.beta.agents.create()` — the session takes either `agent: agent.id` or the typed hash form `agent: {type: "agent", id: agent.id, version: agent.version}`. +> Warning: **There is no inline agent config.** `model`/`system_`/`tools` live on the agent object, not the session. Always start with `client.beta.agents.create()` - the session takes either `agent: agent.id` or the typed hash form `agent: {type: "agent", id: agent.id, version: agent.version}`. ### Minimal @@ -102,7 +102,7 @@ client.beta.sessions.events.send_( ) ``` -> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). +> Tip: **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens - stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns). --- @@ -137,7 +137,7 @@ stream.each do |event| end ``` -> ℹ️ Event `.type` is a Symbol (compare with `:"agent.message"`, not `"agent.message"`). +> Note: Event `.type` is a Symbol (compare with `:"agent.message"`, not `"agent.message"`). ### Reconnecting and Tailing @@ -171,7 +171,7 @@ end ## Provide Custom Tool Result -> ℹ️ The Ruby managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic` Ruby gem repository for the corresponding params. +> Note: The Ruby managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic` Ruby gem repository for the corresponding params. --- @@ -262,7 +262,7 @@ client.beta.sessions.delete(session.id) ## MCP Server Integration ```ruby -# Agent declares MCP server (no auth here — auth goes in a vault) +# Agent declares MCP server (no auth here - auth goes in a vault) agent = client.beta.agents.create( name: "GitHub Assistant", model: :"claude-opus-5", diff --git a/skills/claude-api/shared/admin-api.md b/skills/claude-api/shared/admin-api.md new file mode 100644 index 000000000..66a15a74b --- /dev/null +++ b/skills/claude-api/shared/admin-api.md @@ -0,0 +1,179 @@ +# Admin API (Organization Management) + +Read this file when the user wants to manage their Anthropic organization programmatically: members and roles, invites, workspaces and workspace members, API keys, rate limit reports, service accounts, workload identity federation (WIF), or customer-managed encryption keys (CMEK). + +The Admin API lives under `https://api.anthropic.com/v1/organizations/*`. It manages the organization itself - it does not send messages. As of **August 26, 2026** it is available in all seven SDKs (Python, TypeScript, C#, Go, Java, PHP, Ruby) under `client.beta.organization`, and in the `ant` CLI under `ant beta:organization`. Usage reports, cost reports, and the Claude Enterprise user-management and analytics endpoints are **not** in the SDKs - call those with raw HTTP. + +## Authentication + +Two credential types, both read automatically by the default SDK client and the CLI: + +| Credential | Env var | HTTP header | Covers | +| --- | --- | --- | --- | +| Admin API key (`sk-ant-admin...`) | `ANTHROPIC_API_KEY` | `x-api-key` | Most endpoints | +| `org:admin` OAuth token | `ANTHROPIC_AUTH_TOKEN` | `authorization: Bearer` | Everything, including the OAuth-only endpoints | + +- **OAuth-only endpoints:** service accounts, federation issuers, and federation rules reject API keys - they require an `org:admin` OAuth token. +- **Precedence gotcha:** when both env vars are set, some clients prefer the API key. When using a bearer token, leave `ANTHROPIC_API_KEY` unset in that shell. +- Admin API keys are created in the Claude Console by organization admins. +- Regular (non-admin) API keys do not work on any of these endpoints, and admin credentials do not work on the Messages API. +- An `org:admin` token grants access to the whole organization regardless of any workspace binding. + +**Interactive OAuth token** - log in with the `ant` CLI under a dedicated profile (keeps routine commands from running with elevated access), then export the token. Tokens are short-lived; on 401, re-run the export. Profile and scope mechanics (why `org:admin` needs an explicit `--scope`, switching profiles): `shared/anthropic-cli.md`. + +```bash +ant auth login --profile admin --scope "org:admin" +export ANTHROPIC_AUTH_TOKEN=$(ant auth print-credentials --profile admin --access-token) +# When done: unset ANTHROPIC_AUTH_TOKEN && ant profile activate default +``` + +**Automated workloads (CI)** - don't log in interactively. Create a federation rule with `oauth_scope: org:admin` targeting a service account whose `organization_role` is `admin` (this one rule must be created by a human in the Claude Console), then point the client at it with the federation env vars and construct it with no arguments - the SDK/CLI performs the token exchange automatically and refreshes before expiry: + +```bash +export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... # the org:admin rule +export ANTHROPIC_ORGANIZATION_ID= +export ANTHROPIC_SERVICE_ACCOUNT_ID=svac_... # the rule's target service account +export ANTHROPIC_IDENTITY_TOKEN_FILE=/path/to/jwt # or ANTHROPIC_IDENTITY_TOKEN +``` + +**curl** also needs `anthropic-version: 2023-06-01` on every request. + +## Endpoint Coverage + +SDK accessor shown in Python spelling; see the per-language table below for naming conventions. + +| Resource | REST path | SDK accessor (`client.beta.organization` +) | CLI (`ant beta:organization` +) | +| --- | --- | --- | --- | +| Organization info | `GET /v1/organizations/me` | `.retrieve()` | `retrieve` | +| Members | `/v1/organizations/users` | `.users` - `list`, `update`, `remove` | `:users list\|update\|remove` | +| Invites | `/v1/organizations/invites` | `.invites` - `create`, `list`, `delete` | `:invites create\|list\|delete` | +| Workspaces | `/v1/organizations/workspaces` | `.workspaces` - `create`, `retrieve`, `list`, `update`, `archive` | `:workspaces create\|list\|update\|archive` | +| Workspace members | `/v1/organizations/workspaces/{id}/members` | `.workspaces.members` - `add`, `list`, `update`, `remove` | `:workspaces:members add\|list\|update\|remove` | +| API keys | `/v1/organizations/api_keys` | `.api_keys` - `list`, `update` | `:api-keys list\|update` | +| Org rate limits | `GET /v1/organizations/rate_limits` | `.rate_limits.list(model=..., group_type=...)` | `:rate-limits list` | +| Workspace rate limits | `GET /v1/organizations/workspaces/{id}/rate_limits` | `.workspaces.rate_limits.list(workspace_id)` | `:workspaces:rate-limits list` | +| Service accounts (*) | `/v1/organizations/service_accounts` | `.service_accounts` - `create`, `list`, `archive` | `:service-accounts create\|list\|archive` | +| Federation issuers (*) | `/v1/organizations/federation_issuers` | `.federation.issuers` - `create`, `list`, `archive` | `:federation:issuers create\|list\|archive` | +| Federation rules (*) | `/v1/organizations/federation_rules` | `.federation.rules` - `create`, `list`, `archive` | `:federation:rules create\|list\|archive` | +| CMEK external keys | `/v1/organizations/external_keys` | `.external_keys` - `create`, `validate` | - | + +(*) OAuth-only: requires an `org:admin` bearer token, not an API key. + +Attaching a CMEK external key to a workspace is a workspace update: `client.beta.organization.workspaces.update("", external_key_id="ekey_...")`. + +## Per-Language Naming & Pagination + +| Language | Accessor style (list members example) | List behavior | +| --- | --- | --- | +| Python | `client.beta.organization.users.list(limit=10)` | Iterator auto-fetches more pages; `limit` = page size, not total | +| TypeScript | `client.beta.organization.users.list({ limit: 10 })` - camelCase sub-resources: `apiKeys`, `rateLimits`, `serviceAccounts`, `externalKeys` | `for await` auto-pages | +| C# | `client.Beta.Organization.Users.List(new() { Limit = 10 })` | `await foreach (var u in page.Paginate())` auto-pages | +| Go | `client.Beta.Organization.Users.ListAutoPaging(ctx, params)`; org info is `Organization.Get(ctx)` | `.Next()` / `.Current()` auto-pages | +| Java | `client.beta().organization().users().list(params)` with builder params (`UserListParams.builder().limit(10).build()`) | `.autoPager()` auto-pages | +| PHP | `$client->beta->organization->users->list(limit: 10)` | Raw single-page data call - iterate `->getItems()`; the SDK's auto-pagination helpers aren't wired up for these endpoints yet | +| Ruby | `client.beta.organization.users.list(limit: 10)` | Raw single-page data call - iterate `.data`; the SDK's auto-pagination helpers aren't wired up for these endpoints yet | +| CLI | `ant beta:organization:users list --limit 10` | On the member, invite, workspace, workspace-member, and API-key lists, `--limit` caps the results (unlike most `ant` list commands, where `--limit` sets the page size and `--max-items` caps - see `shared/anthropic-cli.md`) | +| curl | `GET /v1/organizations/users?limit=10` | One page per request; cursor pagination per the Admin API reference | + +The rate-limit lists (`rate_limits`, `workspaces.rate_limits`) also support pagination as of launch - page them like the other list endpoints rather than assuming a single response. + +Go param types follow the pattern `anthropic.BetaOrganizationUserListParams` (with `anthropic.Int(10)` for `Limit`); Java params use builders from `com.anthropic.models.beta.organization.*` (e.g. `UserListParams.builder().limit(10).build()`). The Go and Java pagination loops: + +```go +users := client.Beta.Organization.Users.ListAutoPaging(ctx, anthropic.BetaOrganizationUserListParams{Limit: anthropic.Int(10)}) +for users.Next() { + user := users.Current() // ... +} +if err := users.Err(); err != nil { /* handle */ } +``` + +```java +for (var user : client.beta().organization().users().list(params).autoPager()) { /* ... */ } +``` + +## Examples + +Common operations (Python spelling; map to other languages with the table above - every operation follows the same shape in each language): + +```python +# Organization info +org = client.beta.organization.retrieve() + +# List members (iterator auto-fetches more pages; limit = page size) +for user in client.beta.organization.users.list(limit=10): + print(f"{user.id}: {user.email} ({user.role})") + +# Change a member's role / remove a member +client.beta.organization.users.update("user_...", role="developer") +client.beta.organization.users.remove("user_...") + +# Invite someone +client.beta.organization.invites.create(email="user@example.com", role="developer") + +# Create a workspace and add a member to it +ws = client.beta.organization.workspaces.create(name="Production") +client.beta.organization.workspaces.members.add( + ws.id, user_id="user_...", workspace_role="workspace_developer" +) + +# Deactivate / rename an API key +client.beta.organization.api_keys.update("apikey_...", status="inactive", name="New Key Name") + +# Rate limit reports (optional filters: model=..., group_type=...) +client.beta.organization.rate_limits.list(model="claude-opus-5") +client.beta.organization.workspaces.rate_limits.list("wrkspc_...") + +# Service accounts + WIF (org:admin OAuth token required) +sa = client.beta.organization.service_accounts.create(name="inference-worker", organization_role="developer") +issuer = client.beta.organization.federation.issuers.create( + name="github-actions", + issuer_url="https://token.actions.githubusercontent.com", + jwks={"type": "discovery"}, +) +client.beta.organization.federation.rules.create( + name="gha-deploy", + issuer_id=issuer.id, + match={"subject_prefix": "repo:my-org/my-repo:ref:refs/heads/main", + "claims": {"repository_owner": "my-org"}}, + target={"type": "service_account", "service_account_id": sa.id}, + workspace_id="wrkspc_...", + oauth_scope="workspace:developer", + token_lifetime_seconds=600, +) + +# CMEK: register, validate, then attach an external key to a workspace +key = client.beta.organization.external_keys.create( + display_name="prod-key", geo="us", + provider_config={"type": "aws", "kms_arn": "arn:aws:kms:..."}, +) +client.beta.organization.external_keys.validate(key.id) +client.beta.organization.workspaces.update("wrkspc_...", external_key_id=key.id) +``` + +## Organization Roles + +| Role | Permissions | +| --- | --- | +| `user` | Playground | +| `claude_code_user` | Playground + Claude Code | +| `developer` | Playground + manage API keys | +| `billing` | Playground + manage billing | +| `admin` | All of the above + manage users | + +Owners and primary owners have all admin permissions and can also manage admins. Workspace roles are `workspace_user`, `workspace_developer`, `workspace_admin`, and `workspace_billing`. + +## Platform Restrictions + +- **Claude Platform on AWS:** only the workspace endpoints work. Members, workspace members, invites, API keys, and usage/cost/rate-limit reports are unavailable. CMEK external-key endpoints are not yet available there - register and attach keys in the Claude Console. +- **Claude Enterprise (claude.ai orgs):** only members and invites from this surface, plus Enterprise-only endpoints (group and custom-role reads, spend limits) that are not in the SDKs. + +## Live Docs + +| Topic | URL | +| --- | --- | +| Admin API guide | `https://platform.claude.com/docs/en/manage-claude/admin-api.md` | +| Admin API reference | `https://platform.claude.com/docs/en/api/admin.md` | +| Workspaces | `https://platform.claude.com/docs/en/manage-claude/workspaces.md` | +| Rate limits API | `https://platform.claude.com/docs/en/manage-claude/rate-limits-api.md` | +| WIF admin | `https://platform.claude.com/docs/en/manage-claude/wif-admin-api.md` | +| Usage & cost reports (curl-only) | `https://platform.claude.com/docs/en/manage-claude/usage-cost-api.md` | diff --git a/skills/claude-api/shared/agent-design.md b/skills/claude-api/shared/agent-design.md index cf21113e5..517cec6b2 100644 --- a/skills/claude-api/shared/agent-design.md +++ b/skills/claude-api/shared/agent-design.md @@ -9,7 +9,7 @@ This file covers decision heuristics for building agents on the Claude API: whic | Parameter | When to use it | What to expect | | --- | --- | --- | | **Adaptive thinking** (`thinking: {type: "adaptive"}`) | When you want Claude to control when and how much to think. | Claude determines thinking depth per request and automatically interleaves thinking between tool calls. No token budget to tune. | -| **Effort** (`output_config: {effort: ...}`) | When adjusting the tradeoff between thoroughness and token efficiency. | Lower effort → fewer and more-consolidated tool calls, less preamble, terser confirmations. `medium` is often a favorable balance. Use `max` when correctness matters more than cost. | +| **Effort** (`output_config: {effort: ...}`) | When adjusting the tradeoff between thoroughness and token efficiency. | Lower effort -> fewer and more-consolidated tool calls, less preamble, terser confirmations. `medium` is often a favorable balance. Use `max` when correctness matters more than cost. | See `SKILL.md` §Thinking & Effort for model support and parameter details. @@ -21,7 +21,7 @@ See `SKILL.md` §Thinking & Effort for model support and parameter details. Claude doesn't know your application's security boundary, approval policy, or UX surface. Claude emits tool calls; your harness handles them. The shape of those tool calls determines what the harness can do. -A **bash tool** gives Claude broad programmatic leverage — it can perform almost any action. But it gives the harness only an opaque command string, the same shape for every action. Promoting an action to a **dedicated tool** gives the harness an action-specific hook with typed arguments it can intercept, gate, render, or audit. +A **bash tool** gives Claude broad programmatic leverage - it can perform almost any action. But it gives the harness only an opaque command string, the same shape for every action. Promoting an action to a **dedicated tool** gives the harness an action-specific hook with typed arguments it can intercept, gate, render, or audit. **When to promote an action to a dedicated tool:** @@ -45,15 +45,15 @@ A **bash tool** gives Claude broad programmatic leverage — it can perform almo | **Web search / fetch** | Server | Claude needs information past its training cutoff (news, current events, recent docs) or the content of a specific URL. | Claude issues a query or URL; Anthropic executes it and returns results with citations. | | **Memory** | Client | Claude needs to save context across sessions. | Claude reads/writes a `/memories` directory. You implement the storage backend. | -**Client-side** tools are defined by Anthropic (name, schema, Claude's usage pattern) but executed by your harness. Anthropic provides reference implementations. **Server-side** tools run entirely on Anthropic infrastructure — declare them in `tools` and Claude handles the rest. +**Client-side** tools are defined by Anthropic (name, schema, Claude's usage pattern) but executed by your harness. Anthropic provides reference implementations. **Server-side** tools run entirely on Anthropic infrastructure - declare them in `tools` and Claude handles the rest. --- ## Composing Tool Calls: Programmatic Tool Calling -With standard tool use, each tool call is a round trip: Claude calls the tool, the result lands in Claude's context, Claude reasons about it, then calls the next tool. Three sequential actions (read profile → look up orders → check inventory) means three round trips. Each adds latency and tokens, and most of the intermediate data is never needed again. +With standard tool use, each tool call is a round trip: Claude calls the tool, the result lands in Claude's context, Claude reasons about it, then calls the next tool. Three sequential actions (read profile -> look up orders -> check inventory) means three round trips. Each adds latency and tokens, and most of the intermediate data is never needed again. -**Programmatic tool calling (PTC)** lets Claude compose those calls into a script instead. The script runs in the code execution container. When the script calls a tool, the container pauses, the call is executed (client-side or server-side), and the result returns to the running code — not to Claude's context. The script processes it with normal control flow (loops, filters, branches). Only the script's final output returns to Claude. +**Programmatic tool calling (PTC)** lets Claude compose those calls into a script instead. The script runs in the code execution container. When the script calls a tool, the container pauses, the call is executed (client-side or server-side), and the result returns to the running code - not to Claude's context. The script processes it with normal control flow (loops, filters, branches). Only the script's final output returns to Claude. | When to use it | What to expect | | --- | --- | @@ -65,7 +65,7 @@ With standard tool use, each tool call is a round trip: Claude calls the tool, t | Feature | When to use it | What to expect | | --- | --- | --- | -| **Tool search** | Many tools available, but only a few relevant per request. Don't want all schemas in context upfront. | Claude searches the tool set and loads only relevant schemas. Tool definitions are appended, not swapped — preserves cache (see Caching below). | +| **Tool search** | Many tools available, but only a few relevant per request. Don't want all schemas in context upfront. | Claude searches the tool set and loads only relevant schemas. Tool definitions are appended, not swapped - preserves cache (see Caching below). | | **Skills** | Task-specific instructions Claude should load only when relevant. | Each skill is a folder with a `SKILL.md`. The skill's description sits in context by default; Claude reads the full file when the task calls for it. | Both patterns keep the fixed context small and load detail on demand. @@ -80,7 +80,7 @@ Both patterns keep the fixed context small and load detail on demand. | **Compaction** | Conversation likely to reach or exceed the context window limit. | Earlier context is summarized into a compaction block server-side. See `SKILL.md` §Compaction for the critical `response.content` handling. | | **Memory** | State must persist across sessions (not just within one conversation). | Claude reads/writes files in a memory directory. Survives process restarts. | -**Choosing between them:** Context editing and compaction operate within a session — editing prunes stale turns, compaction summarizes when you're near the limit. Memory is for cross-session persistence. Many long-running agents use all three. +**Choosing between them:** Context editing and compaction operate within a session - editing prunes stale turns, compaction summarizes when you're near the limit. Memory is for cross-session persistence. Many long-running agents use all three. --- @@ -90,11 +90,11 @@ 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 `` 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. 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. | +| 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 `` 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. 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. +For multi-turn breakpoint placement, use the combination in `prompt-caching.md` § Automatic vs explicit breakpoints: one explicit breakpoint on the static system prefix plus top-level automatic caching for the conversation tail (where automatic caching is available). --- diff --git a/skills/claude-api/shared/anthropic-cli.md b/skills/claude-api/shared/anthropic-cli.md index 7a47885f1..1f2fa3638 100644 --- a/skills/claude-api/shared/anthropic-cli.md +++ b/skills/claude-api/shared/anthropic-cli.md @@ -4,9 +4,9 @@ The `ant` CLI exposes every Claude API resource as a shell subcommand. Compared ## When to use the CLI vs the SDK -**CLI for the control plane, SDK for the data plane.** Agents and environments are relatively static resources you define, configure, and debug with `ant` — check the YAML into your repo, apply from CI, inspect from a terminal. Sessions are dynamic and driven by your application through the SDK — create per task, stream events, react to tool calls, integrate into your product. Both hit the same API; the split is about where the call lives, not what's possible. +**CLI for the control plane, SDK for the data plane.** Agents and environments are relatively static resources you define, configure, and debug with `ant` - check the YAML into your repo, apply from CI, inspect from a terminal. Sessions are dynamic and driven by your application through the SDK - create per task, stream events, react to tool calls, integrate into your product. Both hit the same API; the split is about where the call lives, not what's possible. -| | Control plane → `ant` | Data plane → SDK | +| | Control plane -> `ant` | Data plane -> SDK | |---|---|---| | Resources | agents, environments, skills, vaults, files | sessions, events | | Cadence | Once per deploy / ad-hoc | Every task / every turn | @@ -20,7 +20,7 @@ The `ant` CLI exposes every Claude API resource as a shell subcommand. Compared brew install anthropics/tap/ant xattr -d com.apple.quarantine "$(brew --prefix)/bin/ant" -# Linux / WSL — pick the release from github.com/anthropics/anthropic-cli/releases +# Linux / WSL - pick the release from github.com/anthropics/anthropic-cli/releases curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${VERSION}/ant_${VERSION}_$(uname -s | tr A-Z a-z)_$(uname -m | sed -e s/x86_64/amd64/ -e s/aarch64/arm64/).tar.gz" \ | sudo tar -xz -C /usr/local/bin ant @@ -28,15 +28,15 @@ curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${VER go install github.com/anthropics/anthropic-cli/cmd/ant@latest ``` -**Auth** — the CLI resolves credentials the same way the SDKs do (first match wins): explicit flags, then `ANTHROPIC_API_KEY`, then `ANTHROPIC_AUTH_TOKEN`, then the `ANTHROPIC_PROFILE`-selected or active profile, then Workload Identity Federation env vars, then the default profile on disk. Override the host with `ANTHROPIC_BASE_URL` or `--base-url`. +**Auth** - the CLI resolves credentials the same way the SDKs do (first match wins): explicit flags, then `ANTHROPIC_API_KEY`, then `ANTHROPIC_AUTH_TOKEN`, then the `ANTHROPIC_PROFILE`-selected or active profile, then Workload Identity Federation env vars, then the default profile on disk. Override the host with `ANTHROPIC_BASE_URL` or `--base-url`. - **API key**: set `ANTHROPIC_API_KEY` in the environment. -- **OAuth profile** (no static key to manage): `ant auth login` opens a browser, exchanges for a short-lived token, and stores a profile under `$ANTHROPIC_CONFIG_DIR` (default `~/.config/anthropic/` on Linux/macOS, `%APPDATA%\Anthropic` on Windows — `configs/.json` for settings, `credentials/.json` for tokens). Subsequent `ant` (and SDK) calls pick it up automatically — a bare `Anthropic()` client works after login, but scripts that read `ANTHROPIC_API_KEY` directly do not. Claude Code and the Claude Agent SDK honor the same profile resolution. `ant auth status` shows which credential source and profile won (it reports status only — don't script against its exit code as a health check); `ant auth logout` clears the active profile (`--all` for every profile). On a remote host without a browser, `ant auth login --no-browser` prints the authorize URL and accepts the code back in the terminal. -- **Non-interactive workloads** (CI, servers, containers): interactive login is for development on your own machine — use Workload Identity Federation instead (see the authentication docs via `shared/live-sources.md`). +- **OAuth profile** (no static key to manage): `ant auth login` opens a browser, exchanges for a short-lived token, and stores a profile under `$ANTHROPIC_CONFIG_DIR` (default `~/.config/anthropic/` on Linux/macOS, `%APPDATA%\Anthropic` on Windows - `configs/.json` for settings, `credentials/.json` for tokens). Subsequent `ant` (and SDK) calls pick it up automatically - a bare `Anthropic()` client works after login, but scripts that read `ANTHROPIC_API_KEY` directly do not. Claude Code and the Claude Agent SDK honor the same profile resolution. `ant auth status` shows which credential source and profile won (it reports status only - don't script against its exit code as a health check); `ant auth logout` clears the active profile (`--all` for every profile). On a remote host without a browser, `ant auth login --no-browser` prints the authorize URL and accepts the code back in the terminal. +- **Non-interactive workloads** (CI, servers, containers): interactive login is for development on your own machine - use Workload Identity Federation instead (see the authentication docs via `shared/live-sources.md`). -> **The #1 auth trap:** profiles are only consulted when no API key is set. A stale exported `ANTHROPIC_API_KEY` silently overrides every profile — requests hit whatever org/workspace that key is scoped to. `ant auth status` shows which source won; unset the key (or per-command: `env -u ANTHROPIC_API_KEY ant …`) before relying on a profile. Truly **unset** it — an empty `ANTHROPIC_API_KEY=""` still wins its precedence slot and authenticates with an empty key. The same shadowing applies in reverse to Claude Code: after `ant auth login`, Claude Code may warn about an auth conflict between the profile and its own `/login` credential — keep one (use the profile and `/logout` in Claude Code, or `ant auth logout` to keep Claude Code's own login). +> **The #1 auth trap:** profiles are only consulted when no API key is set. A stale exported `ANTHROPIC_API_KEY` silently overrides every profile - requests hit whatever org/workspace that key is scoped to. `ant auth status` shows which source won; unset the key (or per-command: `env -u ANTHROPIC_API_KEY ant ...`) before relying on a profile. Truly **unset** it - an empty `ANTHROPIC_API_KEY=""` still wins its precedence slot and authenticates with an empty key. The same shadowing applies in reverse to Claude Code: after `ant auth login`, Claude Code may warn about an auth conflict between the profile and its own `/login` credential - keep one (use the profile and `/logout` in Claude Code, or `ant auth logout` to keep Claude Code's own login). -**Named profiles** — an interactive-login token is bound to a single org+workspace, and the API only shows resources belonging to that workspace. If an agent, session, or file you created "disappears", the usual cause is a token scoped to a different workspace than the one that created it (`ant auth status` shows the active workspace). Multi-workspace work means one profile per workspace: +**Named profiles** - an interactive-login token is bound to a single org+workspace, and the API only shows resources belonging to that workspace. If an agent, session, or file you created "disappears", the usual cause is a token scoped to a different workspace than the one that created it (`ant auth status` shows the active workspace). Multi-workspace work means one profile per workspace: ```sh ant auth login --profile # creates the profile if it doesn't exist; org/workspace picker in browser @@ -44,17 +44,17 @@ ant auth login --profile --workspace-id wrkspc_01... # bind directly, s ant profile activate # switch the default profile ant --profile models list # one-off; equivalent: ANTHROPIC_PROFILE= ant models list ant profile list # inspect -ant profile set workspace_id wrkspc_01... --profile # edit config keys (workspace_id, base_url, organization_id, …) +ant profile set workspace_id wrkspc_01... --profile # edit config keys (workspace_id, base_url, organization_id, ...) ``` -`ant profile set` edits an existing profile's config — it never creates one, and it does **not** rebind already-issued credentials; run `ant auth login` again under that profile to mint a token for the new target. Pointing `ANTHROPIC_PROFILE` at a profile that doesn't exist is an error, not a fall-through. Refresh tokens eventually hard-expire (they don't slide with use) — when a previously working profile starts failing auth, re-run `ant auth login` before debugging anything else. +`ant profile set` edits an existing profile's config - it never creates one, and it does **not** rebind already-issued credentials; run `ant auth login` again under that profile to mint a token for the new target. Pointing `ANTHROPIC_PROFILE` at a profile that doesn't exist is an error, not a fall-through. Refresh tokens eventually hard-expire (they don't slide with use) - when a previously working profile starts failing auth, re-run `ant auth login` before debugging anything else. -**Scopes** — a profile's OAuth scope set is requested at login (`--scope`) and persists on the profile (`scope` is also a `profile set` config key; like other config edits, changing it requires a fresh `ant auth login` to take effect). Privileged scopes — e.g. `org:admin` for organization-administration endpoints — are **not** in the default scope set: pass the full set you want explicitly (`ant auth login --profile admin --scope "... org:admin"`), and the server grants a privileged scope only if your role actually has it. Because the scope set rides on every token the profile mints, keep privileged work on a dedicated profile (`admin` vs `default`) and do day-to-day inference on the unprivileged one, switching with `--profile`/`ANTHROPIC_PROFILE`. Check `ant auth login --help` for the current scope list, and `ant auth status` to see what the active token carries. +**Scopes** - a profile's OAuth scope set is requested at login (`--scope`) and persists on the profile (`scope` is also a `profile set` config key; like other config edits, changing it requires a fresh `ant auth login` to take effect). Privileged scopes - e.g. `org:admin` for organization-administration endpoints - are **not** in the default scope set: pass the full set you want explicitly (`ant auth login --profile admin --scope "... org:admin"`), and the server grants a privileged scope only if your role actually has it. Because the scope set rides on every token the profile mints, keep privileged work on a dedicated profile (`admin` vs `default`) and do day-to-day inference on the unprivileged one, switching with `--profile`/`ANTHROPIC_PROFILE`. Check `ant auth login --help` for the current scope list, and `ant auth status` to see what the active token carries. To hand the active credential to a subprocess or raw-HTTP script: ```sh -# Bare access token — for curl's Authorization header +# Bare access token - for curl's Authorization header curl https://api.anthropic.com/v1/messages \ -H "Authorization: Bearer $(ant auth print-credentials --access-token)" \ -H "anthropic-version: 2023-06-01" \ @@ -62,15 +62,15 @@ curl https://api.anthropic.com/v1/messages \ -H "content-type: application/json" \ -d '{"model": "claude-opus-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]}' -# .env format — sets ANTHROPIC_AUTH_TOKEN (and ANTHROPIC_BASE_URL if the profile has one). +# .env format - sets ANTHROPIC_AUTH_TOKEN (and ANTHROPIC_BASE_URL if the profile has one). # Output is bare KEY=value (no `export`), so use `set -a` to auto-export for child processes: set -a; eval "$(ant auth print-credentials --env)"; set +a python my_script.py # SDK picks up ANTHROPIC_AUTH_TOKEN ``` -OAuth tokens go on `Authorization: Bearer` (not `x-api-key:`) **plus the `anthropic-beta: oauth-2025-04-20` header** — converting a raw curl/httpx script from an API key is a header change, not a key swap. The beta header requirement is endpoint-dependent (some endpoints happen to work without it; `/v1/messages` does not) — always send it so requests don't break when you switch endpoints. The token is short-lived and not auto-refreshed when passed via env var, so re-run `print-credentials` before it expires for long-running scripts (`print-credentials` itself refreshes the token if needed). If both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` are set, the SDKs send both and the API rejects the request — unset `ANTHROPIC_API_KEY` before `eval`ing the `--env` output. +OAuth tokens go on `Authorization: Bearer` (not `x-api-key:`) **plus the `anthropic-beta: oauth-2025-04-20` header** - converting a raw curl/httpx script from an API key is a header change, not a key swap. The beta header requirement is endpoint-dependent (some endpoints happen to work without it; `/v1/messages` does not) - always send it so requests don't break when you switch endpoints. The token is short-lived and not auto-refreshed when passed via env var, so re-run `print-credentials` before it expires for long-running scripts (`print-credentials` itself refreshes the token if needed). If both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` are set, the SDKs send both and the API rejects the request - unset `ANTHROPIC_API_KEY` before `eval`ing the `--env` output. -**Foot-gun:** `ant auth print-credentials` with **no flags** prints the entire credentials JSON, not the bare token — putting that in an `Authorization` header yields an empty response or HTTP/2 protocol error. Always use `--access-token` for headers (it always reads the named/active profile; a set `ANTHROPIC_API_KEY` doesn't override credential printing). +**Foot-gun:** `ant auth print-credentials` with **no flags** prints the entire credentials JSON, not the bare token - putting that in an `Authorization` header yields an empty response or HTTP/2 protocol error. Always use `--access-token` for headers (it always reads the named/active profile; a set `ANTHROPIC_API_KEY` doesn't override credential printing). ## Command structure @@ -78,7 +78,7 @@ OAuth tokens go on `Authorization: Bearer` (not `x-api-key:`) **plus the `anthro ant [:] [flags] ``` -Beta resources (agents, sessions, environments, deployments, skills, vaults, memory stores) live under `beta:` — the CLI auto-sends the right `anthropic-beta` header, so don't pass it yourself unless overriding with `--beta
`. For self-hosted environments, `ant beta:worker poll/run` and `ant beta:environments:work stats/stop` drive and monitor the work queue — see `shared/managed-agents-self-hosted-sandboxes.md`. +Beta resources (agents, sessions, environments, deployments, skills, vaults, memory stores) live under `beta:` - the CLI auto-sends the right `anthropic-beta` header, so don't pass it yourself unless overriding with `--beta
`. For self-hosted environments, `ant beta:worker poll/run` and `ant beta:environments:work stats/stop` drive and monitor the work queue - see `shared/managed-agents-self-hosted-sandboxes.md`. ```sh ant models list @@ -97,11 +97,11 @@ ant beta:sessions:events list --session-id session_01... | `--transform` | GJSON path applied to the response (per-item on list endpoints). Not applied when `--format raw`. | | `-r`, `--raw-output` | If the transformed result is a string, print it without quotes (jq semantics). Pair with `--transform` for scalar capture. | | `--max-items` | Cap total results returned from auto-paginating list endpoints (distinct from `--limit`, which is the server page size). | -| `--format-error` / `--transform-error` | Same as `--format`/`--transform`, applied to error responses. `-r` does not apply to the error path — use `--format-error yaml` for unquoted error scalars. | +| `--format-error` / `--transform-error` | Same as `--format`/`--transform`, applied to error responses. `-r` does not apply to the error path - use `--format-error yaml` for unquoted error scalars. | | `--base-url` | Override API host | | `--debug` | Print full HTTP request + response to stderr (API key redacted) | -## Output — `--transform` + `--format` +## Output - `--transform` + `--format` `--transform` takes a [GJSON path](https://github.com/tidwall/gjson/blob/master/SYNTAX.md). On list endpoints it runs **per item**, not on the envelope. @@ -109,16 +109,16 @@ ant beta:sessions:events list --session-id session_01... ant beta:agents list --transform '{id,name,model}' --format jsonl ``` -**Extract a scalar for shell use:** pair `--transform` with `-r` (`--raw-output` — prints strings unquoted, jq-style): +**Extract a scalar for shell use:** pair `--transform` with `-r` (`--raw-output` - prints strings unquoted, jq-style): ```sh AGENT_ID=$(ant beta:agents create --name "My Agent" --model '{id: claude-sonnet-5}' \ --transform id -r) ``` -## Input — flags, stdin, `@file` +## Input - flags, stdin, `@file` -**Flags** — scalar fields map directly. Structured fields accept relaxed-YAML syntax (unquoted keys) or strict JSON. Repeatable flags build arrays (each `--tool`, `--event`, `--message` appends one element): +**Flags** - scalar fields map directly. Structured fields accept relaxed-YAML syntax (unquoted keys) or strict JSON. Repeatable flags build arrays (each `--tool`, `--event`, `--message` appends one element): ```sh ant beta:agents create \ @@ -128,7 +128,7 @@ ant beta:agents create \ --tool '{type: custom, name: search_docs, input_schema: {type: object, properties: {query: {type: string}}}}' ``` -**Stdin** — pipe a full JSON or YAML body. Merged with flags; flags win on conflict (for array fields, any flag **replaces** the stdin array entirely — it does not append). Quote the heredoc delimiter (`<<'YAML'`) to disable shell expansion inside the body: +**Stdin** - pipe a full JSON or YAML body. Merged with flags; flags win on conflict (for array fields, any flag **replaces** the stdin array entirely - it does not append). Quote the heredoc delimiter (`<<'YAML'`) to disable shell expansion inside the body: ```sh ant beta:agents create <<'YAML' @@ -141,7 +141,7 @@ tools: YAML ``` -**`@file` references** — inline a file's contents into any string-valued field. Inside structured flag values, quote the path. Binary files are auto-base64'd; force with `@file://` (text) or `@data://` (base64). Escape a literal leading `@` as `\@`. +**`@file` references** - inline a file's contents into any string-valued field. Inside structured flag values, quote the path. Binary files are auto-base64'd; force with `@file://` (text) or `@data://` (base64). Escape a literal leading `@` as `\@`. ```sh ant beta:agents create --name "Researcher" --model '{id: claude-sonnet-5}' --system @./prompts/researcher.txt @@ -158,7 +158,7 @@ Flags that natively take a file path (e.g. `--file` on `beta:files upload`) acce ## Version-controlled Managed Agents resources -This is the recommended flow for defining agents and environments — check the YAML into your repo and sync via `create` (first time) / `update` (thereafter). See `shared/managed-agents-core.md` for the field reference. +This is the recommended flow for defining agents and environments - check the YAML into your repo and sync via `create` (first time) / `update` (thereafter). See `shared/managed-agents-core.md` for the field reference. ```yaml # summarizer.agent.yaml @@ -171,10 +171,10 @@ tools: ``` ```sh -# Create (once) — capture the ID +# Create (once) - capture the ID AGENT_ID=$(ant beta:agents create < summarizer.agent.yaml --transform id -r) -# Update (CI) — needs ID + current version (optimistic lock) +# Update (CI) - needs ID + current version (optimistic lock) ant beta:agents update --agent-id "$AGENT_ID" --version 1 < summarizer.agent.yaml ``` @@ -190,7 +190,7 @@ ant beta:sessions:events stream --session-id "$SID" # live event stream ### Interactive session loop (stream-before-send) -`ant beta:sessions:events stream` only delivers events emitted *after* the stream opens — so open it **before** sending the kickoff to avoid missing early events. Use process substitution to hold the stream on a file descriptor, send, then read: +`ant beta:sessions:events stream` only delivers events emitted *after* the stream opens - so open it **before** sending the kickoff to avoid missing early events. Use process substitution to hold the stream on a file descriptor, send, then read: ```sh exec {stream}< <(ant beta:sessions:events stream --session-id "$SID" \ @@ -224,18 +224,18 @@ done exec {stream}<&- ``` -This works for interactive exploration and demos. For application code that needs to react to `agent.tool_use` / `agent.custom_tool_use` events, reconnect after drops, or dedup against `events.list`, use the SDK — see `shared/managed-agents-client-patterns.md`. +This works for interactive exploration and demos. For application code that needs to react to `agent.tool_use` / `agent.custom_tool_use` events, reconnect after drops, or dedup against `events.list`, use the SDK - see `shared/managed-agents-client-patterns.md`. ## Scripting patterns -`--transform id -r` on a list endpoint emits one bare ID per line — compose with `xargs`, or use `--max-items N` to bound the result set without piping through `head`: +`--transform id -r` on a list endpoint emits one bare ID per line - compose with `xargs`, or use `--max-items N` to bound the result set without piping through `head`: ```sh FIRST=$(ant beta:agents list --transform id -r --max-items 1) ant beta:agents:versions list --agent-id "$FIRST" --transform '{version,created_at}' --format jsonl ``` -Error shaping mirrors the success path (note: `-r` does not apply to error output — use `--format-error yaml` for an unquoted scalar here): +Error shaping mirrors the success path (note: `-r` does not apply to error output - use `--format-error yaml` for an unquoted scalar here): ```sh ant beta:agents retrieve --agent-id bogus --transform-error error.message --format-error yaml 2>&1 diff --git a/skills/claude-api/shared/claude-platform-on-aws.md b/skills/claude-api/shared/claude-platform-on-aws.md index 098db27ab..124b22d04 100644 --- a/skills/claude-api/shared/claude-platform-on-aws.md +++ b/skills/claude-api/shared/claude-platform-on-aws.md @@ -1,6 +1,6 @@ # Claude Platform on AWS -**Anthropic-operated** access to the Claude Developer Platform through AWS infrastructure — SigV4 authentication, AWS IAM access control, and AWS Marketplace billing. Because Anthropic operates it, **the API surface matches first-party with same-day parity** — for per-feature exceptions, see `shared/platform-availability.md` (the single source of truth; do not rely on an inline exception list here). Model IDs are the bare first-party strings (`claude-opus-5`, `claude-sonnet-5`) — **no provider prefix**. +**Anthropic-operated** access to the Claude Developer Platform through AWS infrastructure - SigV4 authentication, AWS IAM access control, and AWS Marketplace billing. Because Anthropic operates it, **the API surface matches first-party with same-day parity** - for per-feature exceptions, see `shared/platform-availability.md` (the single source of truth; do not rely on an inline exception list here). Model IDs are the bare first-party strings (`claude-opus-5`, `claude-sonnet-5`) - **no provider prefix**. > **Not the same as Amazon Bedrock.** Bedrock is partner-operated (AWS runs the service; release schedules vary, feature subset, `anthropic.`-prefixed model IDs). Claude Platform on AWS and Bedrock coexist; pick by whether you need AWS-native IAM/billing with full Anthropic API parity (this page) vs. Bedrock's own ecosystem. @@ -10,15 +10,15 @@ | Language | Install | Client | |---|---|---| -| Python | `pip install -U "anthropic[aws]"` | `from anthropic import AnthropicAWS` → `AnthropicAWS()` | -| TypeScript | `npm install @anthropic-ai/aws-sdk` | `import AnthropicAws from "@anthropic-ai/aws-sdk"` → `new AnthropicAws()` | -| Go | `go get github.com/anthropics/anthropic-sdk-go` | `import anthropicaws "github.com/anthropics/anthropic-sdk-go/aws"` → `anthropicaws.NewClient(ctx, anthropicaws.ClientConfig{})` | +| Python | `pip install -U "anthropic[aws]"` | `from anthropic import AnthropicAWS` -> `AnthropicAWS()` | +| TypeScript | `npm install @anthropic-ai/aws-sdk` | `import AnthropicAws from "@anthropic-ai/aws-sdk"` -> `new AnthropicAws()` | +| Go | `go get github.com/anthropics/anthropic-sdk-go` | `import anthropicaws "github.com/anthropics/anthropic-sdk-go/aws"` -> `anthropicaws.NewClient(ctx, anthropicaws.ClientConfig{})` | | C# | `dotnet add package Anthropic.Aws` | `new AnthropicAwsClient()` | | Java | See SDK repo in `shared/live-sources.md` | See SDK repo in `shared/live-sources.md` | | Ruby | `gem install anthropic aws-sdk-core` | See SDK repo in `shared/live-sources.md` | | PHP | `composer require anthropic-ai/sdk aws/aws-sdk-php` | See SDK repo in `shared/live-sources.md` | -After construction, **use the client exactly as you would `Anthropic()`** — `client.messages.create(...)`, `client.beta.sessions.*`, etc., with bare model IDs. +After construction, **use the client exactly as you would `Anthropic()`** - `client.messages.create(...)`, `client.beta.sessions.*`, etc., with bare model IDs. ```python from anthropic import AnthropicAWS @@ -35,7 +35,7 @@ client.messages.create( ## Required configuration -Two values must be available (constructor args or environment) — **there is no default fallback** for either: +Two values must be available (constructor args or environment) - **there is no default fallback** for either: | Value | Env var | Notes | |---|---|---| @@ -46,7 +46,7 @@ Endpoint pattern: `https://aws-external-anthropic.{region}.api.aws/v1/...`. Requ ## Authentication -The client resolves AWS credentials via the standard precedence chain: explicit constructor args → environment (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN`) → shared profile → assumed role / instance metadata. +The client resolves AWS credentials via the standard precedence chain: explicit constructor args -> environment (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN`) -> shared profile -> assumed role / instance metadata. **Short-term API keys** are also supported for cases where SigV4 isn't practical (e.g., browser, simple scripts). Mint one with the per-language token-generator package; pass it as `api_key` on the client. Lifetime is the **lesser of** the requested duration, the underlying credential's expiry, and **12 hours**. For package names and IAM details, WebFetch the Claude Platform on AWS page in `shared/live-sources.md`. @@ -54,6 +54,6 @@ The client resolves AWS credentials via the standard precedence chain: explicit ## What to tell users -- Treat it as first-party: every section of this skill applies unchanged. Do **not** apply Bedrock's feature-availability mask. +- Treat it as first-party: every section of this skill applies unchanged. Do **not** apply Bedrock's feature-availability mask. Three Managed Agents differences only: (1) a session can run autonomously (no user events) for at most **6 hours** before it needs reauthentication - send any user-role event to continue; (2) sessions on **self-hosted** environments **cannot attach memory stores** (rejected at session create) - cloud environments attach them as usual; (3) self-hosted workers authenticate with IAM/SigV4 or an AWS-Console API key plus the `AnthropicSelfHostedEnvironmentAccess` managed policy - Console-generated environment keys don't work against the AWS endpoint. - Model IDs are bare (`claude-opus-5`). Do **not** add an `anthropic.` prefix. -- A missing region or `workspace_id` throws at client-construction time (no request is sent). A **403** means the request reached the server — check for a **wrong** `workspace_id` or a missing IAM action on the principal. See the IAM actions reference in `shared/live-sources.md`. +- A missing region or `workspace_id` throws at client-construction time (no request is sent). A **403** means the request reached the server - check for a **wrong** `workspace_id` or a missing IAM action on the principal. See the IAM actions reference in `shared/live-sources.md`. diff --git a/skills/claude-api/shared/cost-optimization.md b/skills/claude-api/shared/cost-optimization.md new file mode 100644 index 000000000..01d3a8324 --- /dev/null +++ b/skills/claude-api/shared/cost-optimization.md @@ -0,0 +1,233 @@ +# Cost Optimization - Cutting Spend per Completed Task + +> **If you arrived via `/claude-api cost-optimize`:** this is the right file. Execute the steps below in order rather than summarizing the guide back to the user - presenting the profile, the ranked plan, and the findings IS part of the execution. Start with Step 0 (establish scope, quality bar, and baseline), and finish with Step 4's two deliverables: the cost profile and the changes. + +API spend is optimized in units of **cost per completed task, not cost per token**. A model with a higher sticker price can be the cheaper option if it finishes the job in fewer turns, and a cheaper model that fails still bills its tokens, then the retry, then whatever the failure costs downstream. Every judgment below reads cost and quality together. + +The levers divide into two kinds, and the order of the steps is load-bearing: + +- **Free wins** - prompt caching, input-token hygiene (including a prompt audit), loop hygiene, output-token hygiene, batch processing - lower what you pay without lowering output quality. They go first, and caching stays on permanently. +- **Tradeoffs** - budgets, effort, model choice, multi-model architectures - exchange cost for intelligence. They go last, because each one changes what the model can do, and overshooting costs quality that the free wins never touch. + +**Where this workflow sits**: the `prompt-audit` subcommand (`shared/prompt-audit.md`) audits the prompt surface (prompts, skills, tool descriptions) alone; this workflow is the holistic cost pass - request shape, caching, loop structure, output, batching, effort, model - and runs that audit as one sub-lever of input hygiene (§ 2.2) rather than restating its patterns; and once the project has an eval, the levers become a hillclimb - one change at a time against the eval, keep or revert (Step 3). + +Measured expectations quoted below are snapshots of Anthropic's published runs (sources at the end). They are directional, not guarantees - the validation loop in Step 3 is what makes a number true for this project - and both sources are fetched live - the platform guide through `shared/live-sources.md`, the cookbook at its URL in the Sources section below: wherever a fetched page differs from this snapshot, the page wins. + +--- + +## Step 0: Establish scope, quality bar, and baseline + +**First, establish three things - from the request and the repository where they answer it, and from the user where they don't.** Unlike the prompt audit, this workflow is interactive by design: when context for a lever is missing, or a step would spend real money, work through it with the user rather than assuming. It is not expected to one-shot the audit. State all three at the top of the report (the baseline value itself may read "pending Step 1" at first). + +1. **Scope.** If the request names files or directories, that is the scope. Otherwise it is every place the project calls the Claude API - request builders, agent loops, batch jobs. Note distinct traffic classes (an interactive path and a nightly job are different workloads even on one key): the profile, the ranking, and every validation later run per class, and "cost per task" means nothing blended across classes. **Also establish which platform** the code targets (first-party Anthropic API, Claude Platform on AWS, Bedrock, Vertex, or Foundry) - feature availability varies, and it filters which levers are even on the table. +2. **Quality bar.** Find the project's eval, test suite, or outcome checks for its LLM calls. If none exists, say so prominently in the report: without one, savings cannot be told apart from regressions. Do not stop - free wins are safe to propose regardless - but mark every tradeoff lever "needs an eval before applying", and ask the user what outcome check they can provide. An eval only validates the traffic class it covers: mark levers on uncovered paths the same way. If the only check is the user's own manual review, it gates free wins - it never clears a tradeoff. The full no-eval endgame - including a minimal eval recipe that unblocks tradeoffs - is in Step 3. +3. **Baseline cost per task.** The baseline is whatever honest number is cheapest to obtain, in this order: + - **From history, free**: with Admin API access, pull Step 1's usage and cost reports forward and compute the baseline from them - the reports supply the dollars, but the per-task denominator must come from the user or the application's own logs; or roll up the application's own logged `usage` objects per task, not per request - four token counts, each at its own rate: regular input, cache writes (1.25x input for the 5-minute duration, 2x for 1-hour), cache reads (0.1x input), and output - multiplier structure as published on the pricing page; confirm it when you fetch the rates. + - **From a baseline run, paid**: run the project's eval (or, with no eval, replay a representative sample of real requests) and roll up the same way. This spends real API money: state the expected cost - from Step 1's token estimates and live pricing, and "estimated - pending Step 1" is an acceptable first answer - **and get the user's approval before running it.** If the user declines the spend, estimate the baseline from the code and any bill figure they can read off the Console, label it an estimate, and continue. + + For current per-model rates, WebFetch the **Pricing** URL from `shared/live-sources.md` - prices change; do not quote remembered ones (if the pricing fetch fails, effective realized rates come from dividing cost-report amounts by the usage report's matching token counts - same model, same token type). For counting tokens in prompts and files, see `shared/token-counting.md` (`count_tokens` returns the count without running inference). Sanity-check an estimated baseline against any known monthly bill: divergence usually means multi-turn history growth the single-turn estimate missed. + +## Step 1: Profile where the tokens go + +The profile can be measured or estimated. Measure when the organization's access allows it; fall back to reading the code. Either way, the levers that pay are decided by the workload's shape, not by the list of what exists. + +### Measure it - the Usage and Cost Admin API (preferred) + +If the user has an **Admin API key** (`sk-ant-admin01-...` - a different key type from the standard API key; not available for individual accounts - creation and scopes are covered in the Admin API docs, reachable from the **Usage and Cost Admin API** URL in `shared/live-sources.md`), pull the real numbers instead of estimating. These are report reads, not model calls - they consume no tokens. Full parameters and response schemas: the **Usage and Cost Admin API** URL in `shared/live-sources.md`. + +- **Token profile**: `GET /v1/organizations/usage_report/messages` with `group_by[]=model` and `bucket_width=1d` (the default page is 7 daily buckets - raise `limit`, up to 31; the `group_by` dimensions also include `api_key_id`, `workspace_id`, `service_tier`, and `context_window`, among others). Each result splits into exactly the quantities the levers below act on: `uncached_input_tokens`, `cache_read_input_tokens`, `cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`, and `output_tokens`. +- **Dollar profile**: `GET /v1/organizations/cost_report` (daily granularity, USD as decimal strings in cents) with `group_by[]=description`; description-grouped results carry structured `model`, `cost_type`, `token_type`, and `service_tier` fields - `token_type` makes the cache split readable directly in dollars. Code execution appears under a `Code Execution Usage` description; Priority Tier costs are not included in this endpoint - track those through the usage endpoint's `service_tier` dimension. +- Data appears within about 5 minutes of a request completing; poll at most once per minute for sustained use. +- Caveats by platform: Claude Enterprise (claude.ai) organizations use the Analytics API instead, and the endpoints are not currently available on Claude Platform on AWS - there, ask the user to read the totals off the Console's Usage and Cost pages and relay them. + +The measured profile answers directly: the real cache hit rate (`cache_read_input_tokens` against uncached input), how much traffic already rides the batch tier, the input/output balance, and where spend concentrates by model, key, and workspace. **Check that the measured footprint plausibly matches the audited code** (same models, a believable order of magnitude): the report covers the whole organization, and a key shared across projects blends their traffic - making per-project reads, including Step 3's post-cutover confirmation, unattributable. On a mismatch, reconcile against the code estimate, scope usage-report queries by `api_key_ids[]` / `workspace_ids[]` where the separation exists (the cost report takes neither filter - it segments only by workspace, via `group_by`), and recommend per-project keys or workspaces as a measurement prerequisite where it doesn't. Optimization effort follows the audited scope's spend, not the org blend. + +### Estimate it from the code + +Without Admin API access (no Admin key, a Claude Enterprise organization, or Claude Platform on AWS - whose feature availability `shared/claude-platform-on-aws.md` covers) - and even with it, for the structural facts no usage report can show - read the request-building code: + +> **Per-model defaults, parameter support, and per-platform feature availability change across releases.** For any "what happens when `thinking`/`effort` is omitted", "does this model accept `effort`", "what levels does it support", or "is this feature available on Bedrock/Vertex/Foundry" question, read the answer from SKILL.md -> Thinking & Effort, `shared/models.md`, or `shared/platform-availability.md` (or the live Models API) - never assume, and never encode the answer in this guide. + +- **Prefix**: how large are the system prompt and tool schemas, and is anything dynamic (timestamps, request IDs) interpolated into them? +- **Reference material**: is documentation or a manual inlined into every request? +- **Tools**: how many schema tokens, and does every request need every tool? +- **Loop**: how many turns deep, and do bulky tool results accumulate across them? +- **Media**: are images, PDFs, or large files entering the context at full size? +- **Output**: how long are visible responses, and what is `max_tokens` set to? +- **Model and effort**: which model, which effort, and was either ever swept against an eval? Look up what the model does when both are omitted (SKILL.md -> Thinking & Effort) - an unset default that runs thinking is a hidden output-token line item. +- **Caching**: are there `cache_control` breakpoints already, and what do `cache_read_input_tokens` / `cache_creation_input_tokens` show in practice? +- **Latency tolerance**: is a user waiting on every response, or can some work batch? + +### Ask for the app's own usage logs first + +Before ranking on estimates, **ask the user whether the application already logs `response.usage` per request** - and if so, to paste a representative day's worth. That turns cache hit rate, the input/output split, and thinking-token spend from guesses into measurements at zero API cost, and it decides which tier of the ranking table below applies. If the app doesn't log usage yet, note that adding it is itself a free-win diff (Step 3) and proceed on the code estimate. + +**Estimating cache hit rate without usage data.** If the app logs request timestamps, simulate the TTL walk: sort timestamps, count a hit whenever the gap to the previous request is <= TTL (reads refresh the entry), and run it for each cache TTL the platform offers (see `shared/prompt-caching.md`) - the difference between durations is the longer-TTL lever's ceiling on the user's real traffic. If only aggregate volume is known, approximate with Poisson arrivals: hit rate ~ `1 - e^(-lambda·TTL)` where lambda is requests per second. Either beats comparing average gap to TTL, which ignores burstiness. + +### Rank the levers + +Before touching code, size each lever the profile makes applicable so the shortlist can be ordered. **How you quote the size depends on what data you have** - an estimate and a measurement must not look the same in the report: + +| Data available | Quote each ceiling as | +|---|---| +| Admin API usage/cost report | **Dollar range**, labeled `measured` | +| App-side `usage` logs, or a user-reported bill total only | **% of current bill**, with dollars only as a parenthetical "(~ $Y at your reported $X/mo)" - the % is the claim; the $ is the user's own arithmetic | +| Neither (pure code read) | **Relative buckets** - "largest / medium / small", or an order-of-magnitude band - no specific figures | + +**Before sizing, drop any lever the target platform doesn't support** (`shared/platform-availability.md` is the single source of truth - do not assume 1P availability carries to Bedrock, Vertex, Foundry, or Claude Platform on AWS). A lever that can't ship on the user's platform isn't worth ranking; list it under "skipped" with the availability reason instead. + +Within whichever unit applies, size each lever from the measured (or estimated) spend components and the measured expectations quoted in Step 2 - for example: + +- **Caching ceiling**: the spend on input that is shared and byte-stable across requests - the would-be prefix - re-billed at 0.1x. (0.025x on Claude Fable 5.1 - whether Claude Mythos 5.1 shares that rate is open at launch - so its cost per task sits at or under the Claude Fable 5 figures quoted below.) Blend the measured `uncached_input_tokens` with the code profile here: unique per-request payload can never cache, so on a workload that is mostly payload (or already well cached) this ceiling is honestly small. Sanity-bound the result against the published agent-loop range (a factor of 2.5 to 3.7 off at 81% to 90% hit rates). +- **Batch ceiling**: 50% of the spend on standard-tier traffic that no one is waiting on. The model-grouped profile cannot see that split - segment first: group by `service_tier` to find what already batches, use a finer `bucket_width` to spot scheduled spikes, and ask the user which traffic can wait. +- **Input-hygiene ceiling**: the share of input spend going to reference material, tool schemas, or oversized media that the § 2.2 levers would remove or defer. +- **Effort/model ceiling**: the published tradeoff curves applied to the biggest spend concentrations - carried as a range, since the quality cost is unknown until the eval runs. + +Ceilings that claim the same tokens (caching an inlined document versus deleting it) are mutually exclusive: compute each ceiling unconditionally, rank, then deflate each for its overlap with the levers above it, so the shortlist can never sum past the bill. + +Present the ranked shortlist with the profile evidence behind each number - labeled as ranked by savings ceiling, not application order (Step 2's § 2.x numbering decides the sequence) - and say where the list stops: a lever whose ceiling is a small fraction of the bill - or would not repay the approved runs and effort needed to validate it - does not earn an eval cycle, and most levers will not earn a place on any given workload (the "Workload shape -> lever" table near the end of this file is the map for matching profile to levers). On a small bill the honest shortlist may be empty: "nothing here is worth changing" is a successful finding, not a failure - report it plainly. Expected savings are planning numbers, not results - Step 3's measurements are the results. + +## Step 2: Work the levers in order + +Free wins may be applied directly when the request asked for edits (a bare subcommand invocation has not asked - propose). Tradeoff levers (2.6 onward) are always presented with their measured quality cost and applied only on the user's explicit acceptance - never trade accuracy for cost silently. And every run that exercises the model - the baseline, each lever's validation pass - spends real API money: get explicit approval before each one, with the expected cost, or once as a Step 3 measurement budget that covers them. + +Pricing multipliers quoted below (cache read/write rates, batch discount) are current as of writing - confirm against the Pricing URL in `shared/live-sources.md` before computing any ceiling. + +### 2.1 Prompt caching - first, and it stays on + +Every turn of an agentic task resends the entire growing conversation - system prompt, tool definitions, every prior turn - so a 40-turn task sends its first turn 40 times and task cost grows with roughly the square of turn count. Caching does not stop the resending; it reprices it to 0.1x for everything already cached. + +For design and placement - the prefix-match invariant, classifying inputs by stability, breakpoint patterns, the anti-pattern table - **read `shared/prompt-caching.md` and follow its workflow**; do not improvise `cache_control` markers. Points that matter specifically for cost: + +- **Measured expectation**: the largest single lever on every model and benchmark Anthropic measured - it cut agent-loop cost by a factor of 2.5 to 3.7, at 81% to 90% hit rates; a small issue-triage agent's bill fell 83% from caching alone. +- **Explicit breakpoints when many independent conversations share a static prefix** (or prefix layers change at different rates). Automatic caching only amortizes within one conversation; in the cookbook's worked example, one explicit breakpoint on the static system prefix roughly halved cost per task across a queue of independent tasks. The robust shape for agent loops - one explicit breakpoint on the static prefix plus top-level automatic caching for the tail - and the cases where automatic alone is a pure surcharge are in `shared/prompt-caching.md` § Automatic vs explicit breakpoints. +- **Use the 1-hour cache duration when the loop waits on humans between turns.** It writes at 2x instead of 1.25x and pays for itself on the first prevented miss - a miss resends the whole prefix at full price and writes it again. Decide from the start-to-start gap between requests (generation time counts against the TTL) - the table in `shared/prompt-caching.md` § Choosing the TTL. +- **Audit for mid-task cache-breakers**: dynamic content above a breakpoint; changing `thinking` or `effort` between requests (always invalidates the messages cache, and on some models the tools+system cache too - `shared/prompt-caching.md` § Invalidation hierarchy); changing a task budget mid-task; every context-editing pass; switching models mid-conversation (caches are per-model). +- **Verify from usage, not from code review - and re-verify after every prompt-assembly change**: on a warmed-up loop, `cache_read_input_tokens` should dominate regular `input_tokens`, and `cache_creation_input_tokens` should be roughly one turn's worth, not the whole conversation. If it isn't, hunt for a cache-breaker with the healthy-loop signature and payload-diff method in `shared/prompt-caching.md` § Verifying cache hits - unless the workload's input is mostly unique per-request payload (which can never cache), or the misses are concurrent-batch artifacts (§ 2.5); neither is a breaker, and neither has a fix. +- **The cache probe, when there is no usage history to read**: a scratch script for the project's own stack that sends one representative request twice, byte-identical; prints all four usage meters (`input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `output_tokens`) for both; and exits non-zero if the second request's `cache_read_input_tokens` is zero. Ship it alongside the caching diff so the user can run the before/after themselves. It spends real tokens and may execute the project's tools - run it only under the standing approval rule, and point it at a scratch environment if the request's tools mutate state. + +### 2.2 Input tokens - progressive disclosure + +Send the model what the task needs, let it fetch the rest. Each sub-lever has a skip-when; the caveat at the end of this section governs all of them. + +- **Large reference document in every prompt** -> move it behind a tool or skill so the model retrieves sections on demand. Skip when most calls consult most of it anyway - a document in the cached prefix is cheap - or when the eval shows misses on cases that hinge on rules the model now has to go looking for. +- **Tool recaps in the system prompt** -> delete them. Tool schemas already render into the request; prose restating them only inflates the prefix. +- **Many or heavy tool schemas** -> tool search with `defer_loading` on rarely-used tools, so definitions load only when needed. Pays once schemas run past roughly 10K tokens (MCP servers reach that fast); below that the search step is overhead. Measurement gotcha: the token-counting endpoint rejects server tools - read billed input off a `max_tokens: 1` request instead (a paid, if tiny, model call: it sits under the standing approval rule). +- **Images and PDFs at full resolution** -> pre-downscale to what the task needs. Vision inputs are tokenized by pixel area at roughly one token per 28×28 patch, so cost scales with resolution, not information content; 1280×720 is a safe default that caps an image near 1,200 tokens (current formula - verify via the Vision docs in `shared/live-sources.md`). +- **Large tables and artifacts inlined** -> Files API plus code execution: mount the file, let the model compute in the sandbox, and only the answer enters context. Skip when there is nothing to extract or compute - the sandbox round-trip only adds tokens (and sandbox container time bills hourly beyond a free allowance). +- **Fetched web pages** -> dynamic filtering in the web fetch tool keeps boilerplate out of the context. +- **Chained tool calls whose intermediates don't matter** -> programmatic tool calling runs the calls from code so only the filtered result enters context; its documentation reports 24% fewer input tokens on agentic search benchmarks, with a higher score. +- **Broad data-dump tools** -> prefer narrow accessors (`get_policy(claim_id)` over `get_all_policies()`), and give list tools `limit`/`fields`/`date_range` parameters. +- **Unbounded user-supplied input** -> the token-counting endpoint as an ingestion gate (`shared/token-counting.md`): count first, then truncate, summarize, or route oversize payloads to the Files API. +- **The prompt text itself** -> run the `prompt-audit` subcommand (`shared/prompt-audit.md`) as part of this step; its pattern tables are the reference for dated prompt text (this guide deliberately does not restate them), and its report and proposed diff fold into this workflow's deliverables. Skip when the prompt surface is small and recently audited. Prompts written for an older model make the current one over-work: on a support-desk evaluation, prompts written for Claude Opus 4.8 cost 36% more per ticket on Claude Opus 5 for no change in accuracy; audited, the same prompts were 14% cheaper than unaudited and more accurate (97% of tickets, up from 92%). On the Claude Sonnet 4.6 to Claude Sonnet 5 migration the audit took 14% off at the same accuracy. + +**Caveat for the whole section**: a smaller prefix is not automatically a cheaper task. Deferring context means the model may spend discovery turns fetching what it previously read inline. Validate against the eval - on the cookbook's workload, wrapping the manual in a tool matched the explicit-breakpoint config on cost and gave back accuracy. + +### 2.3 Agent-loop hygiene - keep long loops from compounding + +Only relevant when the profile shows deep loops with bulky accumulating results; short loops never trigger these and the added machinery is pure overhead. + +- **Context editing** (clearing old tool uses or thinking) **is a context-window tool, not a savings lever.** Every clearing pass rewrites the cached conversation, which works against prompt caching - in the run measured for the platform docs, context editing cost more than it saved. Use it to make room in the window; set the trigger high enough that clears stay infrequent, and clear in a few large batches rather than every turn. +- **Compaction** (the server-side summarize-and-continue edit) needs sessions long enough to reach its trigger; where it fired once on a long triage run it cut the bill a further 38%. Steer it with its `instructions` string so task-critical state survives the summary. +- **Client-side pruning at natural boundaries**: collapse bulky tool results to one-line extracts when a work phase completes, keeping the message array byte-identical between prunes so each prune is one cold cache miss rather than a new miss every turn. +- **Subagents for self-contained bulky steps**: a nested loop absorbs its own heavy tool results and hands back one line, optionally on a cheaper model. Skip when the deciding model needs the intermediate context to judge well - and note the subagent starts a fresh prefix with no cache shared with the parent. + +### 2.4 Output tokens + +- **`max_tokens` is a backstop, not a tuning knob.** The model never sees it; hitting it cuts the response off mid-thought with `stop_reason: "max_tokens"`. In Anthropic's coding runs a 16,384-token cap ended 15% of Claude Opus 5's attempts and a third of Claude Fable 5's, none of them solved - capped runs spent less per attempt and bought proportionally fewer solves, so cost per solved task didn't improve. Set it to 64,000 for agentic work (128,000 at `xhigh` or `max` effort), stream responses that large, and treat `stop_reason: max_tokens` as a failed attempt rather than retrying at the same cap. +- **To shorten visible responses**, specify the exact output shape in the prompt, ideally with an example. To shorten reasoning, that is the effort parameter (§ 2.6) - not `max_tokens`. +- **Stop sequences as content-aware early exits**: register a sentinel the model emits when it cannot proceed (for example ``), so it stops instead of spending tokens explaining. + +### 2.5 Batch processing + +50% off **every token in the request, including cache reads and writes** - the discounts stack. The second-largest free lever after caching for unattended agent work - evaluation runs, backfills, scheduled jobs. + +- Results arrive asynchronously within 24 hours; that window is an expiry, not an SLA. Keep user-facing work synchronous. +- Batch requests are single-shot - no mid-batch tool loop. A tool loop can sometimes be flattened into one batchable request by pre-fetching its inputs up front; in the cookbook's worked example that ran at roughly half the interactive config's cost, but it is an architecture decision, not a parameter - it changes how the model reasons (the flattened run held its pass rate less firmly), and cache hits inside a concurrent batch are best-effort. +- Not available for Managed Agents sessions (current mechanics and availability: the **Batch Processing** URL in `shared/live-sources.md`). + +### 2.6 Effort and budgets - the first tradeoffs + +From here down, every lever trades capability for cost. Sweep on the eval, one change at a time. + +- **Sweep effort before touching the model** (on models that expose an effort parameter - check `shared/models.md` or the **Effort Parameter** URL in `shared/live-sources.md`). Effort scales thinking and tool-call depth without changing the model. Test each level in a separate session - changing effort mid-session invalidates the cache and distorts the comparison. Sweep mechanics that keep the comparison honest: + - Cells are byte-identical except `output_config.effort`; same model throughout. Complete every sample request at one setting before starting the next, in a stable order, so cache reads are comparable across settings - and if the cache meters still differ materially between settings, say so and weight the read toward output-side cost. + - Include a hard case the user knows about: curves are flattest on easy tasks, and the hard tail is where higher effort earns its cost. + - **Side-effect gate**: if replaying a sample request executes tools that mutate real state, point the replay at a scratch environment or stub those tools first; a sweep is never worth a production mutation. If that isn't possible, sweep only the requests that are safe to replay and say so. + - Read the curve as flat (the lower setting does this workload's work), steep (the higher setting is earning its cost - now a measured number rather than a fear), or mixed (name which tasks flipped - those are the candidates for the re-run-failures policy below). Differences of a task or two of pass rate, or cents of mean cost, are within noise on single runs; the remedy is repeat trials at the settings in contention, offered with their cost. + - The curve is per-workload *and* per-model. Keep the sample and the outcome check where the report says they live, and re-sweep after a model migration, a major prompt change, or a workload shift. + + What to expect by workload shape: + - Research and knowledge work: nearly flat curves - in Anthropic's runs (all with Claude Fable 5), `low` gave up 1 to 3 points for a third to a half off cost per task; `medium` matched the default's accuracy at 70% to 85% of its cost; the default bought nothing measurable over `medium` on any of the four benchmarks measured. Lower effort is also faster (4.5 versus 7.9 minutes per problem on one research benchmark). + - Long-horizon coding: a real tradeoff - Claude Opus 5 gave up about 2 points at `medium` for half the cost, and about 8 points at `low` for a quarter of it. + - Reasoning-ceiling work (deep multi-subtopic research): every effort step bought about 2.4 rubric points - no free cut on that curve. +- **Re-run failures at higher effort** - when the workload has a usable failure signal (tests, a checker, a validator). Run everything at `low` and re-run failures at the default: in Anthropic's coding runs, about 93% passed for about $0.70 per task, against 91.7% for $1.39 running everything at the default - the same pass rate for half the cost, counting the failed cheap attempts. Starting at `medium` solved about 94% for about $0.95. Use this for the saving, not the lift, and price in the checker and the doubled wall-clock on failures. +- **Task budgets** (the model sees the budget and paces itself - this is the budget control that saves money): set from the loop's 90th-percentile token usage, then tighten. The budget is advisory - it steers the model rather than stopping it - so verify adherence on the workload. Measured on coding: a generous budget gave up about 2.7 points of pass rate for an 18% saving; the tightest allowed budget gave up 4.4 points for 47%. Budgets below the 20,000-token floor are rejected; very tight budgets can produce refusal-like behavior; set the budget once on the first request - a mid-task change invalidates the cache. Check model availability before wiring it in (beta, and not available on every current model) - parameter shape, the streaming requirement, and supported models are in this skill's SKILL.md -> Task Budgets (Quick Reference) and `shared/model-migration.md` -> Task Budgets. +- **Backstops that don't save per-task money but cap the damage**: a Managed Agents session budget is a hard dollar stop; a workspace spend limit is the final backstop on the whole workspace. + +### 2.7 Model selection - last, deliberately + +Model choice constrains the intelligence ceiling, which is why it comes after every lever that doesn't. + +- **Price candidates in cost per completed task on your own traffic**, including the larger model at reduced effort - per-token price lists do not predict the ranking. In Anthropic's runs, Claude Fable 5 at `low` effort beat Claude Sonnet 5 on a deep-research benchmark while costing about 10% less per task; on a coding subset both models largely saturate, Claude Opus 5 matched Claude Fable 5 (91.7% versus 91.3%) at about 60% of its cost. For most agent workloads, start with Claude Opus 5. At the other end, Claude Haiku 4.5 answered knowledge questions at about a tenth of Claude Opus 5's cost per question at 63% accuracy versus 92% - it fits high-volume work with checkable outputs, not long agentic loops. +- **Price the tail, not the median.** Compare models on the hardest tenth of the workload: on the typical task every model looks similar and the cheapest looks best, but the bill is decided by the tasks the cheap model fails - and the tail is where the money goes even when nothing fails (on one 20-problem research run, two problems carried 43% of the spend). +- **The stepping-down method**: sweep effort on the current model first; if `low` passes the eval, drop one model tier, **confirm which parameters and effort levels the target tier supports** (SKILL.md -> Thinking & Effort), reset effort to that tier's default - not a hardcoded level; the default and the supported range vary by model - and re-sweep down from there (on a tier without `effort` support, evaluate at its single default only). One notch at a time, against the eval - and when there is no cheaper tier, the lever is exhausted; say so rather than inventing a step. Current model lineup and discovery: `shared/models.md`; for model-swap mechanics and per-target breaking changes, the `migrate` subcommand (`shared/model-migration.md`). +- **Two models can beat one, in exactly two measured shapes** - both are architecture changes; validate like one: + - **Advisor** (a cheaper executor runs the loop and consults a frontier model on hard decisions): pays when the capability gap between the two models is wide and the executor actually consults. The consult rate is the fragile variable - lowering effort can drop a pairing from consulting on most tasks to almost none, and then it scores below the executor alone - and gating the consult well requires a cheap signal; asking the executor to recognize the hard cases itself demands the very judgment it's missing. Benchmark first: on Anthropic's coding benchmark the flagship pairing was the most accurate configuration measured but sat within noise of the frontier model alone at `medium` effort, at about the same cost - sweep effort and price the stronger model alone before adding the advisor. + - **Orchestrator** (a frontier model plans and delegates bulk work to cheaper workers): buys something only when there is bulk to hand off - many independent pieces, ideally too many for one context window. On work larger than any context window it cost 55% less than the frontier model solo at every effort setting (3 to 7 points below its best score); on routine search work it paid as tail insurance (about half the average cost, a third at the 90th percentile) but reversed on the harder full set. When the work is one dependent chain, or fits in a single context, the orchestrator pays for a plan, a handoff, and a merge that a single model gets for free - in every such case measured, the coordinator's model alone at lower effort came out ahead. + +## Step 3: Apply, measure, keep or revert - one lever at a time + +- Work down the ranked shortlist to decide which levers earn a diff - but **apply shortlisted levers in the § 2 order** (free wins -> effort/budgets -> model), not in savings-rank order: the ranking decides inclusion and where the eval budget goes; the § 2.x numbering decides sequence. Each lever that earns a place becomes **its own diff** (one lever per diff, so a revert is clean and effects attribute), applied and then measured: re-run the eval covering that lever's traffic class, and read pass rate and cost per task together against the previous kept configuration (the baseline for the first lever only). A lever that saves money and gives back accuracy is not an optimization - revert it and record why. A lever touching a path no eval covers cannot be validated by the eval you have: a free win there is measured on cost only, and said so; a tradeoff there stays an unapplied proposal (Step 0.2's marking rule). +- **Ask for the measurement budget once, not per run.** Present the validation plan with its total expected runs and cost - an effort sweep is several configurations at several trials each - and get it approved as a budget; within an approved budget, individual runs need no fresh approval. A shadow-run on live traffic roughly doubles production spend while it runs: it is its own approval. +- **Never keep or revert on a one-case swing.** Repeat trials within the approved budget until the decision clears the noise. The published bar - around fifty cases and at least five trials per configuration - is the standard for the production cutover; a smaller project eval is acceptable for per-lever decisions when trials are repeated. And validating a caching diff needs a warm cache: run the sample sequentially and measure from the second request on, or the 1.25x writes dominate and the free win reads as a regression. +- **When the user can provide no outcome check at all**: free wins become cost-only-measured diffs (or proposals, if no spend is approved), tradeoffs stay unapplied proposals carrying the published expectations, and offer a manual before/after spot-check of a handful of real answers - the user's review gates free wins, never a tradeoff. For an effort sweep specifically, a cost-only run is still worth offering: the same matrix with no pass-rate column, reporting per task the outputs at each setting laid side by side - exactly what the user needs in front of them to judge quality themselves. State plainly in the report which mode ran, and do not invent a grader to fill the gap. If the application doesn't log usage, adding `response.usage` logging is itself a free-win diff, and it is the measurement channel for everything after it when there is no Admin API key. +- **Minimal eval recipe** - the cheapest thing that clears a tradeoff lever, so "needs an eval" is a next step rather than a dead end. Offer to build it with the user: + - **Inputs**: a fixed set of ~20-30 real requests pulled from production logs or written by the user - enough for per-lever keep/revert decisions (the ~50-case bar above is for the final production cutover). Freeze them; every config runs the identical set. + - **Judgment per output**: whichever is cheapest for the workload - golden answers to diff against, a short rubric the user scores each output on, or an automated checker (tests pass, JSON validates, required fields present). A model-graded judge is acceptable when nothing cheaper exists, but it is itself an approved API spend. + - **Runner**: a script that runs the frozen inputs through one config, records each output plus `response.usage`, and reports pass rate and cost per task. Each config is one invocation; the sweep is a loop over configs. + - **Cost and approval**: estimate it (inputs × configs × baseline cost per task) and get the user's go-ahead before running - this is real API spend under the standing approval rule. +- Keep-or-revert is decided locally, on the eval evidence. Shadow-run the winning configuration on live traffic before cutover, keep the eval running after it, and confirm the savings in the usage and cost reports **after** cutover - only where the traffic is attributable (Step 1's shared-key caveat applies to the confirmation read too). +- Expect most levers not to fit any given workload. On the cookbook's worked example, most didn't earn a place - tool schemas too small for tool search, loops too short for editing or compaction, no numeric work for code execution - and the levers that came closest on cost each gave back a correct answer. The profile from Step 1 exists so optimization isn't blind. +- Plot configurations as score versus cost per task and take the Pareto frontier - that is what the cutover decision reads from. + +## Workload shape -> lever + +Adapted from the cookbook's takeaways table, for mapping a profile to levers (row 1's watch-out is extended): + +| Where the cost is | Reach for | Skip it or watch out when | +|---|---|---| +| Same system prompt and tools re-billed on every call | Prompt caching with auto first, then an explicit breakpoint on the static prefix when many independent conversations share it or prefix layers change at different rates, and 1-hour TTL if calls are more than five minutes apart | Anything dynamic sits above the breakpoint - move that content into the user turn. And a cache that already reads well needs nothing: concurrent-batch misses (§ 2.5) aren't breakers, and a 1-hour TTL doesn't reach calls that are hours apart | +| Large reference document in every prompt | Move it behind a tool or skill | Each call needs most of the document rather than a section, or the eval shows misses on cases that hinge on rules the model has to go looking for | +| Many or heavy tool schemas | Tool search with `defer_loading` | Under roughly 10K schema tokens, where the search step is overhead | +| Images, PDFs, or large files in context | Downscale images to what the task needs, and use the Files API plus code execution for tables and PDFs | There is nothing to extract or compute so the sandbox only adds tokens | +| Unbounded user-supplied input | Token counting as an ingestion gate | | +| Bulky results piling up across a long loop | Context editing or compaction server-side, or a client-side prune at natural boundaries | Loops are short or the cleared content is still needed, and note that every edit breaks the cache from that point | +| One self-contained step with bulky intermediates | Subagent, optionally on a cheaper model | The deciding model needs that intermediate context to judge well | +| Long visible responses | Specify the output shape with an example, with `max_tokens` as a backstop and a stop-sequence sentinel for early exits | | +| Thinking and tool calls dominate, and the eval has headroom | Lower `effort` first, then drop a model tier and re-sweep effort | Always a direct capability trade, so step down one notch at a time against the eval | +| Mostly routine cases with a few hard ones | Advisor tool on a cheaper driver | There is no cheap signal to gate the consult, leaving the driver to spot hard cases itself | +| No one is waiting on the response | Batch API, flattening a tool loop into one request by pre-fetching its inputs if you have to | A user is waiting, or when flattening changes how the model reasons | + +## Step 4: Deliverables + +1. **The cost profile and plan**: the Step 0 assumptions (scope, quality bar, baseline), the Step 1 token profile, and the levers chosen with the measured expectation each one carries - plus the levers deliberately skipped and why, so the next person doesn't re-litigate them. Label the shortlist table as ranked by savings ceiling, not application order, so it can't be misread as the diff sequence. +2. **The changes**: one diff per lever so effects attribute - applied and measured (expected versus measured cost per task, pass rate held or not) where the user approved the runs; left as proposals carrying their expected savings and published quality cost where they didn't, or where a tradeoff lever still needs an eval. When nothing cleared the ranking floor, this deliverable is "no changes recommended" - a successful outcome; say it plainly rather than manufacturing a lever. + +**Report skeleton** (section order and required columns - keep the rest flexible): + +- **Scope / quality bar / baseline / platform** (Step 0 assumptions) +- **Token profile** (Step 1) +- **Ranked shortlist** - table columns: `Lever | Type (free win / tradeoff) | Savings ceiling | Data source (measured / usage logs / code estimate)`. Ceiling is in the unit tier the data supports (Step 1 -> Rank the levers). Caption the table "ranked by savings ceiling, not application order." +- **Proposed changes** - one diff per lever, numbered in § 2 application order (free wins -> effort/budgets -> model), each tagged *applied and measured* / *proposed* / *needs an eval* +- **Levers skipped** and why (including any dropped for platform availability) +- **Next step / approvals needed** - measurement budget ask, eval prerequisite, or "no changes recommended" + +## Sources and live references + +The measured results above come from two published Anthropic sources (and the Admin API facts in Step 1 from a third); fetch them when the user needs the full write-ups, charts, or current numbers: + +- The platform guide **Optimizing for cost and intelligence** - WebFetch the Cost Optimization URL in `shared/live-sources.md`. +- The cookbook **Cost optimization on the Claude API** (`https://platform.claude.com/cookbook/cost-optimization-cost-optimization`) - a runnable end-to-end worked example of this workflow. +- The **Usage and Cost Admin API** docs - the URL in `shared/live-sources.md`; the endpoint reference pages linked from that page carry the full parameter and response schemas. +- Per-model prices: always the **Pricing** URL in `shared/live-sources.md`, never remembered rates. diff --git a/skills/claude-api/shared/error-codes.md b/skills/claude-api/shared/error-codes.md index 56059ec37..2b594358f 100644 --- a/skills/claude-api/shared/error-codes.md +++ b/skills/claude-api/shared/error-codes.md @@ -56,7 +56,7 @@ This file documents HTTP error codes returned by the Claude API, their common ca - Invalid API key format - Revoked or deleted API key - OAuth bearer token sent via `x-api-key` instead of `Authorization: Bearer` -- Both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` set — the SDK sends both headers and the API rejects the request +- Both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` set - the SDK sends both headers and the API rejects the request **Fix:** Set `ANTHROPIC_API_KEY`, or run `ant auth login` and leave the client constructor empty. For raw HTTP with an OAuth token, use `Authorization: Bearer ` (not `x-api-key:`). @@ -94,7 +94,7 @@ This file documents HTTP error codes returned by the Claude API, their common ca - Too many tokens in input - Image data too large -**Fix:** Reduce input size — truncate conversation history, compress/resize images, or split large documents into chunks. +**Fix:** Reduce input size - truncate conversation history, compress/resize images, or split large documents into chunks. --- @@ -107,19 +107,21 @@ Some 400 errors are specifically related to parameter validation: - `budget_tokens` >= `max_tokens` in extended thinking - Invalid tool definition schema -**Model-specific 400s on Claude Opus 5 / Fable 5 / Opus 4.8 / 4.7:** +**Model-specific 400s on Claude Opus 5 / Fable 5/5.1 / Opus 4.8 / 4.7:** -- `temperature`, `top_p`, `top_k` are removed — sending any of them returns 400. Delete the parameter; see `shared/model-migration.md` → Per-SDK Syntax Reference. -- `thinking: {type: "enabled", budget_tokens: N}` is removed — sending it returns 400. Use `thinking: {type: "adaptive"}` instead. -- **Claude Opus 5:** `thinking: {type: "disabled"}` returns 400 when `effort` is `xhigh` or `max` — it is accepted at `high` or below. Thinking is on by default, so omitting the param runs adaptive rather than disabling it. -- **Fable 5 only:** an explicit `thinking: {type: "disabled"}` returns 400 at any effort (it is accepted on Opus 4.8/4.7). Omit the `thinking` param entirely instead. -- **Fable 5 only:** if the organization is set to zero data retention (ZDR) — or any retention below the required 30 days — then **all** Fable 5 requests return `400 invalid_request_error`, even with a perfectly valid payload. Check the org's retention configuration before debugging the request body. +- `temperature`, `top_p`, `top_k` are removed - sending any of them returns 400. Delete the parameter; see `shared/model-migration.md` -> Per-SDK Syntax Reference. +- `thinking: {type: "enabled", budget_tokens: N}` is removed - sending it returns 400. Use `thinking: {type: "adaptive"}` instead. +- **Claude Opus 5:** `thinking: {type: "disabled"}` returns 400 when `effort` is `xhigh` or `max` - it is accepted at `high` or below. Thinking is on by default, so omitting the param runs adaptive rather than disabling it. +- **Fable 5/5.1 only:** an explicit `thinking: {type: "disabled"}` returns 400 at any effort (it is accepted on Opus 4.8/4.7). Omit the `thinking` param entirely instead. +- **Fable 5/5.1, Mythos 5/5.1:** if the organization or workspace is set to zero data retention (ZDR) - or any retention below the required 30 days - then **all** requests to these models return `400 invalid_request_error` ("In order to access this model, your organization or workspace must have data retention enabled."), even with a perfectly valid payload; ZDR only if expressly authorized by Anthropic. Check the retention configuration before debugging the request body. +- **Claude Fable 5.1 / Claude Mythos 5.1 (and Mythos Preview):** `tool_choice: {type: "any"}` or `{type: "tool", name: ...}` returns 400 `tool_choice: type "tool" and "any" are not supported for this model.` - also on `count_tokens` and Batches. Use `{type: "auto"}` plus a prompt instruction (`strict: true` for schema-valid arguments), or structured outputs. +- **Claude Fable 5.1 / Claude Mythos 5.1 - preserved thinking / history-editing check (new accounts created on/after 2026-08-31, or any request that sets `prefix_mismatch_behavior`):** ``messages.N.content.M: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".`` (plus a sentence naming the beta header when it wasn't sent, and optionally one naming the first message that changed) means the system prompt, tool list, or an earlier message changed since that thinking block was produced. Retrying the same body never clears it; `count_tokens` returns the same 400. (In the Message Batches API the *unset* default drops the failing blocks instead of failing the item - a Batches item fails as `errored` only with `prefix_mismatch_behavior: "error"` set.) Strip the named block and every thinking block after it and retry once, or resend with `thinking.block_binding.prefix_mismatch_behavior: "drop_block"` under beta `thinking-binding-controls-2026-08-01` (where the controls beta is offered - Claude API / Claude Platform on AWS at launch, per model on Bedrock and Google Cloud, not on Foundry: `shared/platform-availability.md`; elsewhere use the strip-and-retry path; without the header that field is a 400 ending `block_binding: Extra inputs are not permitted`); then fix the harness so it stops editing history (see `shared/model-migration.md` -> Migrating to Claude Fable 5.1 from Claude Fable 5). The same leading clause with *no* "bound to a different conversation" sentence is a tampered signature - always a 400, regardless of the setting. **Common mistake with extended thinking on older models (Opus 4.6 and earlier):** ``` # Wrong: budget_tokens must be < max_tokens -thinking: budget_tokens=10000, max_tokens=1000 → Error! +thinking: budget_tokens=10000, max_tokens=1000 -> Error! # Correct thinking: budget_tokens=10000, max_tokens=16000 @@ -171,10 +173,13 @@ thinking: budget_tokens=10000, max_tokens=16000 | Mistake | Error | Fix | | ------------------------------- | ---------------- | ------------------------------------------------------- | -| `temperature`/`top_p`/`top_k` on Claude Opus 5 / Fable 5 / Opus 4.8 / 4.7 | 400 | Remove the parameter (see `shared/model-migration.md`) | -| `budget_tokens` on Claude Opus 5 / Fable 5 / Opus 4.8 / 4.7 | 400 | Use `thinking: {type: "adaptive"}` | -| `thinking: {type: "disabled"}` on Fable 5 | 400 | Omit the `thinking` param entirely (accepted on Opus 4.8/4.7) | -| Org set to ZDR / retention below 30 days (Fable 5) | 400 on every request | Fix the org's data-retention configuration — the payload isn't the problem | +| `temperature`/`top_p`/`top_k` on Claude Opus 5 / Fable 5/5.1 / Opus 4.8 / 4.7 | 400 | Remove the parameter (see `shared/model-migration.md`) | +| `budget_tokens` on Claude Opus 5 / Fable 5/5.1 / Opus 4.8 / 4.7 | 400 | Use `thinking: {type: "adaptive"}` | +| `thinking: {type: "disabled"}` on Fable 5/5.1 | 400 | Omit the `thinking` param entirely (accepted on Opus 4.8/4.7) | +| Org set to ZDR / retention below 30 days (Fable 5/5.1, Mythos 5/5.1) | 400 on every request | Fix the org's data-retention configuration - the payload isn't the problem | +| `tool_choice` `any` / `tool` on Claude Fable 5.1 / Claude Mythos 5.1 / Mythos Preview | 400 | `{type: "auto"}` + name the tool in the prompt (`strict: true` for schema-valid args), or structured outputs | +| Edited history replayed with thinking blocks (Claude Fable 5.1 / Claude Mythos 5.1, preserved thinking) | 400 `Invalid signature in thinking block ... bound to a different conversation` | Stop editing history - keep the transcript append-only, using mid-conversation `role: "system"` / tool-change messages, turn-scoped `clear_at` reminders that are never deleted, server-side context editing, and summary-only compaction instead of edits; recover once by stripping the named block and every thinking block after it (text and tool calls stay), or `prefix_mismatch_behavior: "drop_block"` | +| `thinking.block_binding` without `thinking-binding-controls-2026-08-01` | 400 `block_binding: Extra inputs are not permitted` | Send the beta header where the controls beta is offered (`shared/platform-availability.md`); elsewhere remove `block_binding` and use strip-and-retry | | `budget_tokens` >= `max_tokens` (older models) | 400 | Ensure `budget_tokens` < `max_tokens` | | Typo in model ID | 404 | Use valid model ID like `claude-opus-5` | | First message is `assistant` | 400 | First message must be `user` | @@ -196,22 +201,22 @@ thinking: budget_tokens=10000, max_tokens=16000 | 404 | `NotFoundError` | `NotFoundError` | `NotFoundException` | `AnthropicNotFoundException` | `NotFoundException` | | 422 | `UnprocessableEntityError` | `UnprocessableEntityError` | `UnprocessableEntityException` | `AnthropicUnprocessableEntityException` | `UnprocessableEntityException` | | 429 | `RateLimitError` | `RateLimitError` | `RateLimitException` | `AnthropicRateLimitException` | `RateLimitException` | -| ≥500 | `InternalServerError` | `InternalServerError` | `InternalServerException` | `Anthropic5xxException` | `InternalServerException` | +| >=500 | `InternalServerError` | `InternalServerError` | `InternalServerException` | `Anthropic5xxException` | `InternalServerException` | | net | `APIConnectionError` | `APIConnectionError` | `AnthropicIoException` | `AnthropicIOException` | `APIConnectionException` | | base | `APIError` (both); `APIStatusError` (Python only) | `APIStatusError` / `APIError` | `AnthropicServiceException` | `AnthropicApiException` | `APIStatusException` / `APIException` | -The Ruby and PHP classes live in a dedicated errors namespace — write `Anthropic::Errors::RateLimitError` and `Anthropic\Core\Exceptions\RateLimitException` (not bare `Anthropic::RateLimitError`). All 4xx C# exceptions also inherit from `Anthropic4xxException`. +The Ruby and PHP classes live in a dedicated errors namespace - write `Anthropic::Errors::RateLimitError` and `Anthropic\Core\Exceptions\RateLimitException` (not bare `Anthropic::RateLimitError`). All 4xx C# exceptions also inherit from `Anthropic4xxException`. ### Catch most-specific first, in a chain -Order `catch`/`except`/`rescue` clauses from the most specific subclass to the base class, with a separate clause for each category you handle differently — retryable (429, ≥500, network) vs. non-retryable (4xx). The SDK defines a distinct class per status for exactly this reason; a single broad catch-all discards that information. +Order `catch`/`except`/`rescue` clauses from the most specific subclass to the base class, with a separate clause for each category you handle differently - retryable (429, >=500, network) vs. non-retryable (4xx). The SDK defines a distinct class per status for exactly this reason; a single broad catch-all discards that information. ```python try: msg = client.messages.create(...) -except anthropic.NotFoundError as e: # 404 — e.g. bad model ID +except anthropic.NotFoundError as e: # 404 - e.g. bad model ID ... -except anthropic.RateLimitError as e: # 429 — back off and retry +except anthropic.RateLimitError as e: # 429 - back off and retry ... except anthropic.APIStatusError as e: # any other non-2xx HTTP response print(e.status_code, e.message) @@ -219,9 +224,9 @@ except anthropic.APIConnectionError as e: # network failure before a respons ... ``` -The same chain shape applies in every SDK: TypeScript `instanceof Anthropic.NotFoundError` → `RateLimitError` → `APIConnectionError` → `APIError` (check `APIConnectionError` before `APIError` — in the TypeScript SDK it's a subclass of `APIError`, unlike Python where it's a sibling); Ruby `rescue Anthropic::Errors::NotFoundError` → `…::RateLimitError` → `…::APIStatusError`; Java `catch (NotFoundException) … catch (RateLimitException) … catch (AnthropicServiceException)`; C# `catch (AnthropicNotFoundException) … catch (AnthropicRateLimitException) … catch (AnthropicApiException)`; PHP `catch (NotFoundException) … catch (RateLimitException) … catch (APIStatusException)`. +The same chain shape applies in every SDK: TypeScript `instanceof Anthropic.NotFoundError` -> `RateLimitError` -> `APIConnectionError` -> `APIError` (check `APIConnectionError` before `APIError` - in the TypeScript SDK it's a subclass of `APIError`, unlike Python where it's a sibling); Ruby `rescue Anthropic::Errors::NotFoundError` -> `...::RateLimitError` -> `...::APIStatusError`; Java `catch (NotFoundException) ... catch (RateLimitException) ... catch (AnthropicServiceException)`; C# `catch (AnthropicNotFoundException) ... catch (AnthropicRateLimitException) ... catch (AnthropicApiException)`; PHP `catch (NotFoundException) ... catch (RateLimitException) ... catch (APIStatusException)`. -### Go — `errors.As` then branch on status +### Go - `errors.As` then branch on status The Go SDK returns a single `*anthropic.Error` for all non-2xx responses. Unwrap it with `errors.As`, then branch on `StatusCode`: @@ -236,7 +241,7 @@ if err != nil { case 429: // back off and retry default: - // other API error — apierr.StatusCode, apierr.RequestID + // other API error - apierr.StatusCode, apierr.RequestID } } else { // transport-level error (*url.Error wrapping *net.OpError, etc.) @@ -246,7 +251,7 @@ if err != nil { ### Error `.type` Field -All `APIStatusError` subclasses now expose a `.type` property (Python: `.type`, TypeScript: `.type`, Java: `.errorType()`, Go: `.Type()`, Ruby: `.type`, PHP: `.type`) that returns the API error type string (e.g., `"invalid_request_error"`, `"authentication_error"`, `"rate_limit_error"`, `"overloaded_error"`). Use this for programmatic error classification when you need finer granularity than the HTTP status code — for example, distinguishing `"billing_error"` from `"permission_error"` (both map to 403). +All `APIStatusError` subclasses now expose a `.type` property (Python: `.type`, TypeScript: `.type`, Java: `.errorType()`, Go: `.Type()`, Ruby: `.type`, PHP: `.type`) that returns the API error type string (e.g., `"invalid_request_error"`, `"authentication_error"`, `"rate_limit_error"`, `"overloaded_error"`). Use this for programmatic error classification when you need finer granularity than the HTTP status code - for example, distinguishing `"billing_error"` from `"permission_error"` (both map to 403). ```python except anthropic.APIStatusError as e: diff --git a/skills/claude-api/shared/live-sources.md b/skills/claude-api/shared/live-sources.md index 649a837e0..918dec589 100644 --- a/skills/claude-api/shared/live-sources.md +++ b/skills/claude-api/shared/live-sources.md @@ -19,6 +19,7 @@ This file contains WebFetch URLs for fetching current information from platform. | Migration Guide | `https://platform.claude.com/docs/en/about-claude/models/migration-guide.md` | "Extract breaking changes, deprecated parameters, and per-model migration steps when moving to a newer Claude model" | | Introducing Claude Fable 5 | `https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5.md` | "Extract capabilities, API changes, and availability stages for Claude Fable 5 and Claude Mythos 5" | | Pricing | `https://platform.claude.com/docs/en/pricing.md` | "Extract current pricing per million tokens for input and output" | +| Cost Optimization | `https://platform.claude.com/docs/en/about-claude/models/optimizing-for-cost-and-intelligence.md` | "Extract measured cost levers, cache and batch savings, effort and model cost-per-task comparisons, budget controls, and multi-model guidance" | ### Core Features @@ -43,13 +44,25 @@ This file contains WebFetch URLs for fetching current information from platform. | Topic | URL | Extraction Prompt | | ---------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Batch Processing | `https://platform.claude.com/docs/en/build-with-claude/batch-processing.md` | "Extract batch API endpoints, request format, and polling for results" | -| Files API | `https://platform.claude.com/docs/en/build-with-claude/files.md` | "Extract file upload, download, and referencing in messages, including supported types and beta header" | +| Files API | `https://platform.claude.com/docs/en/build-with-claude/files.md` | "Extract file upload, download, referencing in messages, supported types, and the migration steps from files-api-2025-04-14" | | Token Counting | `https://platform.claude.com/docs/en/build-with-claude/token-counting.md` | "Extract token counting API usage and examples" | | Rate Limits | `https://platform.claude.com/docs/en/api/rate-limits.md` | "Extract current rate limits by tier and model" | +| Usage and Cost Admin API | `https://platform.claude.com/docs/en/manage-claude/usage-cost-api.md` | "Extract the usage_report and cost_report endpoints, Admin API key requirements, filter and group_by dimensions, token fields, and granularity limits" | | Errors | `https://platform.claude.com/docs/en/api/errors.md` | "Extract HTTP error codes, meanings, and retry guidance" | | Amazon Bedrock | `https://platform.claude.com/docs/en/build-with-claude/claude-on-amazon-bedrock.md` | "Extract the AnthropicBedrockMantle client per language, `anthropic.`-prefixed model IDs, auth paths, feature availability, and regions" | | Claude Platform on AWS | `https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws.md` | "Extract the AnthropicAWS client per language, SigV4 auth, credential precedence, short-term API keys, workspace_id, and region requirements" | -| Claude Platform on AWS — IAM actions | `https://platform.claude.com/docs/en/api/claude-platform-on-aws-iam-actions.md` | "Extract the IAM action names, resource ARNs, and policy examples required for each API capability" | +| Claude Platform on AWS - IAM actions | `https://platform.claude.com/docs/en/api/claude-platform-on-aws-iam-actions.md` | "Extract the IAM action names, resource ARNs, and policy examples required for each API capability" | + +### Admin API (Organization Management) + +| Topic | URL | Extraction Prompt | +| -------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| Admin API Guide | `https://platform.claude.com/docs/en/manage-claude/admin-api.md` | "Extract Admin API authentication, SDK/CLI usage, and member/invite/key management" | +| Admin API Reference | `https://platform.claude.com/docs/en/api/admin.md` | "Extract endpoint parameters, responses, and pagination for the Admin API" | +| Workspaces | `https://platform.claude.com/docs/en/manage-claude/workspaces.md` | "Extract workspace create/list/archive and member management via API" | +| Rate Limits API | `https://platform.claude.com/docs/en/manage-claude/rate-limits-api.md` | "Extract org and workspace rate limit report endpoints and filters" | +| WIF Admin | `https://platform.claude.com/docs/en/manage-claude/wif-admin-api.md` | "Extract service account, federation issuer, and federation rule management" | +| Usage & Cost Reports | `https://platform.claude.com/docs/en/manage-claude/usage-cost-api.md` | "Extract usage and cost report endpoints (curl-only, not in the SDKs)" | ### Tools @@ -63,6 +76,7 @@ This file contains WebFetch URLs for fetching current information from platform. | Tool Search | `https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool.md` | "Extract tool search setup, when to use, and cache interaction" | | Programmatic Tool Calling | `https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling.md` | "Extract PTC setup, script execution model, and tool invocation from code" | | Skills | `https://platform.claude.com/docs/en/agents-and-tools/skills.md` | "Extract skill folder structure, SKILL.md format, and loading behavior" | +| Skills Guide | `https://platform.claude.com/docs/en/build-with-claude/skills-guide.md` | "Extract the Skills API (/v1/skills) usage and the migration steps from skills-2025-10-02" | ### Advanced Features @@ -81,13 +95,13 @@ Use these when a managed-agents binding, behavior, or wire-level detail isn't co | Topic | URL | Extraction Prompt | | --------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Overview | `https://platform.claude.com/docs/en/managed-agents/overview.md` | "Extract the high-level architecture and how agents/sessions/environments/vaults fit together" | -| Quickstart | `https://platform.claude.com/docs/en/managed-agents/quickstart.md` | "Extract the minimal end-to-end agent → environment → session → stream code path" | +| Quickstart | `https://platform.claude.com/docs/en/managed-agents/quickstart.md` | "Extract the minimal end-to-end agent -> environment -> session -> stream code path" | | Agent Setup | `https://platform.claude.com/docs/en/managed-agents/agent-setup.md` | "Extract agent create/update/list-versions/archive lifecycle and parameters" | | Define Outcomes | `https://platform.claude.com/docs/en/managed-agents/define-outcomes.md` | "Extract outcome definitions, evaluation hooks, and success criteria configuration" | | Sessions | `https://platform.claude.com/docs/en/managed-agents/sessions.md` | "Extract session lifecycle, status transitions, idle/terminated semantics, and resume rules" | | Environments | `https://platform.claude.com/docs/en/managed-agents/environments.md` | "Extract environment config (cloud/networking), management endpoints, and reuse model" | -| Self-Hosted Sandboxes | `https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes.md` | "Extract config:{type:self_hosted}, ANTHROPIC_ENVIRONMENT_KEY, EnvironmentWorker.run/run_one, beta_agent_toolset, ant beta:worker poll/run, webhook-driven wake" | -| Self-Hosted Sandboxes — Security | `https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-security.md` | "Extract what the customer owns (hardening, egress, key custody, trust boundaries) vs what Anthropic cannot do" | +| Self-Hosted Sandboxes | `https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes.md` | "Extract config:{type:self_hosted}, ANTHROPIC_ENVIRONMENT_KEY, EnvironmentWorker.run/handle_item, environments.work.poller(drain), beta_agent_toolset, ant beta:worker poll/run, webhook-driven wake, memory stores (ANTHROPIC_WORK_SECRET, memory_sync_interval/memory_sync_deletes)" | +| Self-Hosted Sandboxes - Security | `https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-security.md` | "Extract what the customer owns (hardening, egress, key custody, trust boundaries) vs what Anthropic cannot do" | | Events and Streaming | `https://platform.claude.com/docs/en/managed-agents/events-and-streaming.md` | "Extract event stream types, stream-first ordering, reconnect/dedupe, and steering patterns" | | 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" | @@ -106,7 +120,7 @@ Use these when a managed-agents binding, behavior, or wire-level detail isn't co ### Anthropic CLI -The `ant` CLI provides terminal access to the Claude API. Every API resource is exposed as a subcommand. It is the recommended way to create agents and environments from version-controlled YAML (`ant beta:agents create < agent.yaml` — see `shared/anthropic-cli.md`), and also exposes sessions and every other API resource for scripting and interactive inspection. +The `ant` CLI provides terminal access to the Claude API. Every API resource is exposed as a subcommand. It is the recommended way to create agents and environments from version-controlled YAML (`ant beta:agents create < agent.yaml` - see `shared/anthropic-cli.md`), and also exposes sessions and every other API resource for scripting and interactive inspection. | Topic | URL | Extraction Prompt | | ------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | @@ -118,7 +132,7 @@ The `ant` CLI provides terminal access to the Claude API. Every API resource is ## Claude API SDK Repositories -WebFetch these when a binding (class, method, namespace, field) isn't covered in the cached `{lang}/` skill files or in the managed-agents docs above. The SDKs include beta managed-agents support for `/v1/agents`, `/v1/sessions`, `/v1/environments`, and related resources — search the repo for `BetaManagedAgents`, `beta.agents`, `beta.sessions`, or the equivalent namespace for that language. +WebFetch these when a binding (class, method, namespace, field) isn't covered in the cached `{lang}/` skill files or in the managed-agents docs above. The SDKs include beta managed-agents support for `/v1/agents`, `/v1/sessions`, `/v1/environments`, and related resources - search the repo for `BetaManagedAgents`, `beta.agents`, `beta.sessions`, or the equivalent namespace for that language. | SDK | URL | Extraction Prompt | | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | @@ -130,7 +144,7 @@ WebFetch these when a binding (class, method, namespace, field) isn't covered in | C# | `https://github.com/anthropics/anthropic-sdk-csharp` | "Extract beta managed-agents classes and method signatures (NuGet package, `BetaManagedAgents*` types)" | | PHP | `https://github.com/anthropics/anthropic-sdk-php` | "Extract beta managed-agents classes and method signatures (`$client->beta->agents`, `BetaManagedAgents*` params)" | -Each SDK repo also ships runnable programs under `examples/` — including the refusal-fallback / `fallbacks` examples (client-side middleware registration, fallback state, server-side `fallbacks` param). Fetch those for exact per-language syntax instead of translating another language's example. +Each SDK repo also ships runnable programs under `examples/` - including the refusal-fallback / `fallbacks` examples (client-side middleware registration, fallback state, server-side `fallbacks` param). Fetch those for exact per-language syntax instead of translating another language's example. ### SDK major-version upgrade guides @@ -138,7 +152,7 @@ Authoritative change lists for upgrading the SDK package itself across a major v | SDK | URL | Extraction Prompt | | ------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Python (0.x → 1.x) | `https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md` | "Extract every breaking change with its before/after code, the new minimum Python version, and the upgrade command" | +| Python (0.x -> 1.x) | `https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md` | "Extract every breaking change with its before/after code, the new minimum Python version, and the upgrade command" | --- diff --git a/skills/claude-api/shared/managed-agents-api-reference.md b/skills/claude-api/shared/managed-agents-api-reference.md index d757497b0..6f7a5e95b 100644 --- a/skills/claude-api/shared/managed-agents-api-reference.md +++ b/skills/claude-api/shared/managed-agents-api-reference.md @@ -1,8 +1,8 @@ -# Managed Agents — Endpoint Reference +# Managed Agents - Endpoint Reference All endpoints require `x-api-key` and `anthropic-version: 2023-06-01` headers. Managed Agents endpoints additionally require the `anthropic-beta` header. -> Most users should define agents and environments as version-controlled YAML applied with the `ant` CLI — see `shared/anthropic-cli.md`. The endpoints below are the underlying API that the CLI and SDKs drive. +> Most users should define agents and environments as version-controlled YAML applied with the `ant` CLI - see `shared/anthropic-cli.md`. The endpoints below are the underlying API that the CLI and SDKs drive. ## Beta Headers @@ -28,8 +28,8 @@ 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` / `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`) | +| 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` | | Memory Stores | `memory_stores.create` / `retrieve` / `update` / `list` / `delete` / `archive` | `MemoryStores.New` / `Get` / `Update` / `List` / `Delete` / `Archive` | @@ -37,28 +37,28 @@ All resources are under the `beta` namespace. Python and TypeScript share identi | Memory Versions | `memory_stores.memory_versions.list` / `retrieve` / `redact` | `MemoryStores.MemoryVersions.List` / `Get` / `Redact` | **Naming quirks to watch for:** -- Agents and Session Threads have **no delete** — only `archive`. Archive is **permanent**: the agent becomes read-only, new sessions cannot reference it, and there is no unarchive. Confirm with the user before archiving a production agent. Environments, Sessions, Vaults, Credentials, and Memory Stores have both `delete` and `archive`; Session Resources, Files, Skills, and Memories are `delete`-only; Memory Versions have neither — only `redact`. +- Agents and Session Threads have **no delete** - only `archive`. Archive is **permanent**: the agent becomes read-only, new sessions cannot reference it, and there is no unarchive. Confirm with the user before archiving a production agent. Environments, Sessions, Vaults, Credentials, and Memory Stores have both `delete` and `archive`; Session Resources, Files, Skills, and Memories are `delete`-only; Memory Versions have neither - only `redact`. - Session resources use `add` (not `create`). - Go's event stream is `StreamEvents` (not `Stream`). -- The self-hosted worker is **not** under `client.beta.*` — it's `EnvironmentWorker` from `anthropic.lib.environments` / `@anthropic-ai/sdk/helpers/beta/environments`; only `environments.work.poller/stats/stop` are client methods. +- The self-hosted worker class is `EnvironmentWorker` from `anthropic.lib.environments` / `@anthropic-ai/sdk/helpers/beta/environments` / `anthropic-sdk-go/lib/environments`; `client.beta.environments.work.worker(...)` is a factory that returns the same class, alongside the `environments.work.poller/stats/stop` client methods. -**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). +**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`, `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: ""}`, 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. +**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: ""}`, 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. --- ## Agents -**Step one of every flow.** Sessions require a pre-created agent — there is no inline agent config under `managed-agents-2026-04-01`. +**Step one of every flow.** Sessions require a pre-created agent - there is no inline agent config under `managed-agents-2026-04-01`. | Method | Path | Operation | Description | | -------- | ------------------------------------------------ | ---------------- | ---------------------------------------- | | `GET` | `/v1/agents` | ListAgents | List agents | | `POST` | `/v1/agents` | CreateAgent | Create a saved agent configuration | | `GET` | `/v1/agents/{agent_id}` | GetAgent | Get agent details | -| `POST` | `/v1/agents/{agent_id}` | UpdateAgent | Update agent configuration. `version` is **optional**: supply it (≥ 1) for optimistic concurrency — a mismatch returns 409 — or omit it for an unconditional last-write-wins update. | -| `POST` | `/v1/agents/{agent_id}/archive` | ArchiveAgent | Archive an agent. Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive — this is the terminal state. | +| `POST` | `/v1/agents/{agent_id}` | UpdateAgent | Update agent configuration. `version` is **optional**: supply it (>= 1) for optimistic concurrency - a mismatch returns 409 - or omit it for an unconditional last-write-wins update. | +| `POST` | `/v1/agents/{agent_id}/archive` | ArchiveAgent | Archive an agent. Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive - this is the terminal state. | | `GET` | `/v1/agents/{agent_id}/versions` | ListAgentVersions | List agent versions | ## Sessions @@ -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`, `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. | +| `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 | @@ -78,7 +78,7 @@ All resources are under the `beta` namespace. Python and TypeScript share identi | -------- | ------------------------------------------------ | ---------------- | ---------------------------------------- | | `GET` | `/v1/sessions/{session_id}/events` | ListEvents | List events (polling, paginated) | | `POST` | `/v1/sessions/{session_id}/events` | SendEvents | Send events (user message, tool result) | -| `GET` | `/v1/sessions/{session_id}/events/stream` | StreamEvents | Stream events via SSE. Optional `event_deltas[]=agent.message` / `agent.thinking` opts in to live-preview `event_start`/`event_delta` events — see `shared/managed-agents-events.md` § Live previews. | +| `GET` | `/v1/sessions/{session_id}/events/stream` | StreamEvents | Stream events via SSE. Optional `event_deltas[]=agent.message` / `agent.thinking` opts in to live-preview `event_start`/`event_delta` events - see `shared/managed-agents-events.md` § Live previews. | ## Session Threads @@ -97,7 +97,7 @@ Per-subagent event streams in multiagent sessions. See `shared/managed-agents-mu | Method | Path | Operation | Description | | -------- | ------------------------------------------------------- | ---------------- | ---------------------------------------- | | `GET` | `/v1/sessions/{session_id}/resources` | ListResources | List resources attached to session | -| `POST` | `/v1/sessions/{session_id}/resources` | AddResource | Attach `file` or `github_repository` resource (SDK method: `add`, not `create`). `memory_store` resources attach at session-create time only. | +| `POST` | `/v1/sessions/{session_id}/resources` | AddResource | Attach `file` or `github_repository` resource (SDK method: `add`, not `create`). `memory_store` resources attach at session-create time only. Self-hosted environments accept **only** `memory_store` (at create); `file` / `github_repository` are rejected there. | | `GET` | `/v1/sessions/{session_id}/resources/{resource_id}` | GetResource | Get a single resource | | `POST` | `/v1/sessions/{session_id}/resources/{resource_id}` | UpdateResource | Update resource | | `DELETE` | `/v1/sessions/{session_id}/resources/{resource_id}` | DeleteResource | Remove resource from session | @@ -111,15 +111,15 @@ Per-subagent event streams in multiagent sessions. See `shared/managed-agents-mu | `GET` | `/v1/environments/{environment_id}` | GetEnvironment | Get environment details | | `POST` | `/v1/environments/{environment_id}` | UpdateEnvironment | Update environment | | `DELETE` | `/v1/environments/{environment_id}` | DeleteEnvironment | Delete environment. Returns 204. | -| `POST` | `/v1/environments/{environment_id}/archive` | ArchiveEnvironment | Archive environment. Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive — this is the terminal state. | +| `POST` | `/v1/environments/{environment_id}/archive` | ArchiveEnvironment | Archive environment. Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive - this is the terminal state. | | `GET` | `/v1/environments/{environment_id}/work/stats` | WorkQueueStats | Self-hosted work-queue depth/pending/workers. `x-api-key` auth. See `shared/managed-agents-self-hosted-sandboxes.md`. | | `POST` | `/v1/environments/{environment_id}/work/{work_id}/stop` | StopWork | Self-hosted: stop a claimed work item. `x-api-key` auth. | -For `type: "self_hosted"`, `config` is the bare `{"type": "self_hosted"}` — `networking` and `packages` do not apply. +For `type: "self_hosted"`, `config` is the bare `{"type": "self_hosted"}` - `networking` and `packages` do not apply. (`networking` never governs `web_search` / `web_fetch` in either type - those are restricted per-tool with `allowed_domains` / `blocked_domains` in the agent toolset; see `shared/managed-agents-tools.md`.) ## Deployments -Scheduled deployments (`depl_` IDs) run an agent on a recurring cron schedule — each firing creates a session. See `shared/managed-agents-scheduled-deployments.md` for the conceptual guide (cron/DST semantics, failure behavior, lifecycle). +Scheduled deployments (`depl_` IDs) run an agent on a recurring cron schedule - each firing creates a session. See `shared/managed-agents-scheduled-deployments.md` for the conceptual guide (cron/DST semantics, failure behavior, lifecycle). | Method | Path | Operation | Description | | -------- | ------------------------------------------------ | ---------------- | ---------------------------------------- | @@ -127,7 +127,7 @@ Scheduled deployments (`depl_` IDs) run an agent on a recurring cron schedule | `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 | +| `POST` | `/v1/deployments/{deployment_id}/archive` | ArchiveDeployment | **Terminal** - schedule stops, deployment becomes immutable | | `POST` | `/v1/deployments/{deployment_id}/run` | RunDeployment | Trigger a manual run immediately (`trigger_context.type: "manual"`); works while paused | ## Deployment Runs @@ -141,7 +141,7 @@ Each trigger attempt (scheduled or manual) writes a `deployment_run` record (`dr ## Vaults -Vaults store credentials that Anthropic manages on your behalf — MCP credentials (OAuth with auto-refresh, or static bearer tokens) and `environment_variable` credentials substituted into outbound requests at egress. Attach to sessions via `vault_ids`. See `managed-agents-tools.md` §Vaults for the conceptual guide and credential shapes. +Vaults store credentials that Anthropic manages on your behalf - MCP credentials (OAuth with auto-refresh, or static bearer tokens) and `environment_variable` credentials substituted into outbound requests at egress. Attach to sessions via `vault_ids`. See `managed-agents-tools.md` §Vaults for the conceptual guide and credential shapes. | Method | Path | Operation | Description | | -------- | ------------------------------------------------ | ---------------- | ---------------------------------------- | @@ -181,7 +181,7 @@ Workspace-scoped persistent memory that survives across sessions. Attach to a se ## Memories -Individual text documents inside a store (≤ 100KB each). `create` creates at a `path` and returns `409` (`memory_path_conflict_error`, with `conflicting_memory_id`) if the path is occupied; `update` mutates by `mem_...` ID (rename and/or content). Only `update` accepts a `precondition` (`{"type": "content_sha256", "content_sha256": ...}`) — on mismatch returns `409` (`memory_precondition_failed_error`). List endpoints accept `view: "basic"|"full"` (controls whether `content` is populated; `retrieve` defaults to `full`). +Individual text documents inside a store (<= 100KB each). `create` creates at a `path` and returns `409` (`memory_path_conflict_error`, with `conflicting_memory_id`) if the path is occupied; `update` mutates by `mem_...` ID (rename and/or content). Only `update` accepts a `precondition` (`{"type": "content_sha256", "content_sha256": ...}`) - on mismatch returns `409` (`memory_precondition_failed_error`). List endpoints accept `view: "basic"|"full"` (controls whether `content` is populated; `retrieve` defaults to `full`). | Method | Path | Operation | Description | | -------- | ----------------------------------------------------------------- | -------------- | ---------------------------------------- | @@ -193,7 +193,7 @@ Individual text documents inside a store (≤ 100KB each). `create` creates at a ## Memory Versions -Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surface. `operation` ∈ `created` / `modified` / `deleted`. +Immutable per-mutation snapshots (`memver_...`) - the audit and rollback surface. `operation` in `created` / `modified` / `deleted`. | Method | Path | Operation | Description | | -------- | ----------------------------------------------------------------------------- | --------------------- | ---------------------------------------- | @@ -230,12 +230,12 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa ### CreateAgent Request Body -**Always start here.** `model`, `system`, `tools`, `mcp_servers`, `skills` are top-level fields on this object — they do NOT go on the session. +**Always start here.** `model`, `system`, `tools`, `mcp_servers`, `skills` are top-level fields on this object - they do NOT go on the session. ```json { "name": "string (required, 1-256 chars)", - "model": "claude-opus-5 (required — bare string, or {id, speed?, effort?, inference_geo?} 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": [ @@ -261,18 +261,18 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa ] }, "metadata": { - "key": "value (max 16 pairs, keys ≤64 chars, values ≤512 chars)" + "key": "value (max 16 pairs, keys <=64 chars, values <=512 chars)" } } ``` -> 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`. +> 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 ```json { - "agent": "agent_abc123 (required — string shorthand for latest version, or {type: \"agent\", id, version} object)", + "agent": "agent_abc123 (required - string shorthand for latest version, or {type: \"agent\", id, version} object)", "environment_id": "env_abc123 (required)", "title": "string (optional)", "resources": [ @@ -280,14 +280,14 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa "type": "github_repository", "url": "https://github.com/owner/repo (required)", "authorization_token": "ghp_... (required)", - "mount_path": "/workspace/repo (optional — defaults to /workspace/)", + "mount_path": "/workspace/repo (optional - defaults to /workspace/)", "checkout": { "type": "branch", "name": "main" } } ], "initial_events": [ { "type": "user.message", "content": [{ "type": "text", "text": "Review the auth module." }] } ], - "vault_ids": ["vlt_abc123 (optional — vault credentials: MCP auth + environment variables)"], + "vault_ids": ["vlt_abc123 (optional - vault credentials: MCP auth + environment variables)"], "budget": { "type": "limit", "max_list_cost": { "amount": "2500", "currency": "USD" } @@ -298,11 +298,11 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa } ``` -> 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). +> 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. +> **`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`. +> **`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`. > > **`checkout`** accepts `{type: "branch", name: "..."}` or `{type: "commit", sha: "..."}`. Omit for the repo's default branch. @@ -315,7 +315,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa "config": { "type": "cloud | self_hosted", "networking": { - "type": "unrestricted | limited (union — see SDK types)" + "type": "unrestricted | limited (union - see SDK types)" }, "packages": { } }, @@ -328,7 +328,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa ```json { "name": "Weekly compliance scan", - "agent": "agent_abc123 (required — same shapes as CreateSession)", + "agent": "agent_abc123 (required - same shapes as CreateSession)", "environment_id": "env_abc123 (required)", "initial_events": [ { "type": "user.message", "content": [{ "type": "text", "text": "Run the weekly compliance scan." }] } @@ -341,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, 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`. +> 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 @@ -361,7 +361,7 @@ Immutable per-mutation snapshots (`memver_...`) — the audit and rollback surfa } ``` -> `system.message` events (append system-level context for this turn and later ones) use the same envelope with `type: "system.message"` — supported on Claude Opus 5, Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5, and Claude Mythos 5, checked against the agent's *primary* model only; see `shared/managed-agents-events.md` § Adding system context mid-session. +> `system.message` events (append system-level context for this turn and later ones) use the same envelope with `type: "system.message"` - supported on Claude Opus 5, Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5.1, and Claude Mythos 5.1, checked against the agent's *primary* model only; see `shared/managed-agents-events.md` § Adding system context mid-session. ### Define Outcome Event @@ -404,7 +404,7 @@ Managed Agents endpoints use the standard Anthropic API error format. Errors are } ``` -Include the `request_id` when reporting issues to Anthropic — it lets us trace the request end-to-end. The inner `error.type` is one of the following: +Include the `request_id` when reporting issues to Anthropic - it lets us trace the request end-to-end. The inner `error.type` is one of the following: | Status | Error type | Description | |---|---|---| @@ -414,9 +414,9 @@ Include the `request_id` when reporting issues to Anthropic — it lets us trace | 404 | `not_found_error` | The requested resource doesn't exist | | 409 | `invalid_request_error` | The request conflicts with the resource's current state (e.g., sending to an archived session) | | 413 | `request_too_large` | The request body exceeds the maximum allowed size | -| 429 | `rate_limit_error` | Too many requests — check rate limit headers for retry timing | +| 429 | `rate_limit_error` | Too many requests - check rate limit headers for retry timing | | 500 | `api_error` | An internal server error occurred | -| 529 | `overloaded_error` | The service is temporarily overloaded — retry with backoff | +| 529 | `overloaded_error` | The service is temporarily overloaded - retry with backoff | Note that `409 Conflict` carries `error.type: "invalid_request_error"` (there is no separate `conflict_error` type); inspect both the HTTP status and the `message` to distinguish conflicts from other invalid requests. @@ -429,14 +429,14 @@ Most Managed Agents list endpoints use the `page` / `next_page` cursor scheme: | Field | Where | Notes | |---|---|---| | `limit` | query | Max items per page | -| `page` | query | Opaque cursor from a previous response — pass a `next_page` or `prev_page` value here | -| `order` | query | `asc` / `desc` on endpoints that support sorting. A cursor encodes the `order` of the request that produced it — reusing it with a different `order` returns 400. Other params (filters, `limit`) can change between paginated requests. | +| `page` | query | Opaque cursor from a previous response - pass a `next_page` or `prev_page` value here | +| `order` | query | `asc` / `desc` on endpoints that support sorting. A cursor encodes the `order` of the request that produced it - reusing it with a different `order` returns 400. Other params (filters, `limit`) can change between paginated requests. | | `next_page` | response | Cursor for the next page; `null` when there are no more results | -| `prev_page` | response | Cursor for the previous page on endpoints that support backward pagination — currently **only `GET /v1/sessions`**. `null` on the first page. On endpoints that don't support it, the field is **absent** (not `null`). | +| `prev_page` | response | Cursor for the previous page on endpoints that support backward pagination - currently **only `GET /v1/sessions`**. `null` on the first page. On endpoints that don't support it, the field is **absent** (not `null`). | -Every SDK exposes an auto-paginating iterator that follows `next_page`. In Python and TypeScript, iterate the list result directly; the other SDKs expose the iterator via a separate method (iterating the plain list result returns one page). SDK auto-pagination is **forward-only** — to go back a page, read `prev_page` from the response and pass it back as the `page` parameter yourself. +Every SDK exposes an auto-paginating iterator that follows `next_page`. In Python and TypeScript, iterate the list result directly; the other SDKs expose the iterator via a separate method (iterating the plain list result returns one page). SDK auto-pagination is **forward-only** - to go back a page, read `prev_page` from the response and pass it back as the `page` parameter yourself. -> ⚠️ Some endpoints use a **different** cursor scheme: Message Batches, Files, Models, and several Admin API endpoints take `after_id`/`before_id` and return `has_more`/`first_id`/`last_id` instead of `page`/`next_page`. Some `page`-scheme endpoints (e.g. `GET /v1/skills`) also return a `has_more` boolean alongside `next_page`. Check the endpoint's reference page for its exact pagination fields. +> Warning: Some endpoints use a **different** cursor scheme: Message Batches, Files, Models, and several Admin API endpoints take `after_id`/`before_id` and return `has_more`/`first_id`/`last_id` instead of `page`/`next_page`. Some `page`-scheme endpoints (e.g. `GET /v1/skills`) also return a `has_more` boolean alongside `next_page`. Check the endpoint's reference page for its exact pagination fields. --- @@ -446,8 +446,8 @@ Managed Agents endpoints have per-organization request-per-minute (RPM) limits, | Endpoint group | Scope | RPM | Max concurrent | |---|---|---|---| -| Create operations (Agents, Sessions, Vaults) | organization | 300 | — | -| All other operations (Agents, Sessions, Vaults) | organization | 600 | — | +| Create operations (Agents, Sessions, Vaults) | organization | 300 | - | +| All other operations (Agents, Sessions, Vaults) | organization | 600 | - | | All operations (Environments) | organization | 60 | 5 | Files and Skills endpoints use the standard tier-based [rate limits](https://platform.claude.com/docs/en/api/rate-limits). diff --git a/skills/claude-api/shared/managed-agents-client-patterns.md b/skills/claude-api/shared/managed-agents-client-patterns.md index 512b58526..96fd91624 100644 --- a/skills/claude-api/shared/managed-agents-client-patterns.md +++ b/skills/claude-api/shared/managed-agents-client-patterns.md @@ -1,8 +1,8 @@ -# Managed Agents — Common Client Patterns +# Managed Agents - Common Client Patterns Patterns you'll write on the client side when driving a Managed Agent session, grounded in working SDK examples. -Code samples are TypeScript — other languages follow the same shape; see `{lang}/managed-agents/README.md` (cURL and C#: `curl/managed-agents.md`) for equivalents. +Code samples are TypeScript - other languages follow the same shape; see `{lang}/managed-agents/README.md` (cURL and C#: `curl/managed-agents.md`) for equivalents. --- @@ -22,7 +22,7 @@ for await (const event of client.beta.sessions.events.list(session.id)) { handle(event) } -// Tail the live stream. Dedupe only gates handle() — terminal checks must run +// Tail the live stream. Dedupe only gates handle() - terminal checks must run // even for already-seen events, or a terminal event that was in the history // response gets skipped by `continue` and the loop never exits. for await (const event of stream) { @@ -37,11 +37,11 @@ for await (const event of stream) { --- -## 2. `processed_at` — queued vs processed +## 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. (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.) +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. +**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. ```ts for await (const event of stream) { @@ -52,7 +52,7 @@ for await (const event of stream) { } ``` -Use this to drive pending → acknowledged UI state for anything you send. How you map a locally-rendered optimistic message to the server-assigned `event.id` is application-specific (typically via the return value of `events.send()` or FIFO ordering). +Use this to drive pending -> acknowledged UI state for anything you send. How you map a locally-rendered optimistic message to the server-assigned `event.id` is application-specific (typically via the return value of `events.send()` or FIFO ordering). --- @@ -65,7 +65,7 @@ await client.beta.sessions.events.send(session.id, { events: [{ type: 'user.interrupt' }], }) -// Drain until the session is truly done — see Pattern 5 for the full gate. +// Drain until the session is truly done - see Pattern 5 for the full gate. for await (const event of stream) { if (event.type === 'session.status_terminated') break if ( @@ -75,7 +75,7 @@ for await (const event of stream) { } ``` -Reference: `interrupt.ts` — sends the interrupt the moment it sees `span.model_request_start`, drains to idle, then verifies via `sessions.retrieve()`. +Reference: `interrupt.ts` - sends the interrupt the moment it sees `span.model_request_start`, drains to idle, then verifies via `sessions.retrieve()`. --- @@ -89,7 +89,7 @@ for await (const event of stream) { await client.beta.sessions.events.send(session.id, { events: [{ type: 'user.tool_confirmation', - tool_use_id: event.id, // not a toolu_ id — use event.id + tool_use_id: event.id, // not a toolu_ id - use event.id result: 'allow', // or 'deny' // deny_message: '...', // optional, only with result: 'deny' }], @@ -100,7 +100,7 @@ for await (const event of stream) { Key points: - `tool_use_id` is `event.id` (typically `sevt_...`), **not** a `toolu_...` ID. -- `result` is `'allow' | 'deny'`. Use `deny_message` to tell the model *why* you denied — it gets surfaced back to the agent. +- `result` is `'allow' | 'deny'`. Use `deny_message` to tell the model *why* you denied - it gets surfaced back to the agent. - Multiple pending tools: respond once per `agent.tool_use` event with `evaluated_permission === 'ask'`. Reference: `tool-permissions.ts`. @@ -109,24 +109,24 @@ 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 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`. +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) { handle(event) 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, retries_exhausted, or budget_reached — see list below + if (event.stop_reason.type === 'requires_action') continue // waiting on you - handle it + break // end_turn, retries_exhausted, or budget_reached - see list below } } ``` `stop_reason.type` values on `session.status_idle`: -- `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. +- `requires_action` - agent is waiting on a client-side event (tool confirmation, custom tool result). Handle it, don't break. **Self-hosted exception:** if the session went `requires_action`-idle with no pending `agent.tool_use` (always_ask) or `agent.custom_tool_use` to answer, the worker failed the claimed work item (typically a memory-store mount error, logged only on the worker host). Don't `continue` forever on that - surface it, fix the host, and send `user.interrupt` to re-queue the work (`shared/managed-agents-self-hosted-sandboxes.md` § Memory stores -> Troubleshooting). +- `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. --- @@ -145,7 +145,7 @@ for (let i = 0; i < 10; i++) { } if (s?.status !== 'running') { await client.beta.sessions.archive(session.id) -} // else: still running after 2s — don't archive, let it settle or escalate +} // else: still running after 2s - don't archive, let it settle or escalate ``` --- @@ -162,7 +162,7 @@ await client.beta.sessions.events.send(session.id, { for await (const event of stream) { /* ... */ } ``` -The `Promise.all([stream, send])` shape works too, but stream-first is simpler and has the same effect — the stream starts buffering the moment it's opened. +The `Promise.all([stream, send])` shape works too, but stream-first is simpler and has the same effect - the stream starts buffering the moment it's opened. --- @@ -172,23 +172,23 @@ The `Promise.all([stream, send])` shape works too, but stream-first is simpler a ```ts const uploaded = await client.beta.files.upload({ file, purpose: 'agent_resource' }) -// uploaded.id → the original file +// uploaded.id -> the original file const session = await client.beta.sessions.create({ /* ... */ resources: [{ type: 'file', file_id: uploaded.id, mount_path: '/workspace/data.csv' }], }) -// session.resources[0].file_id !== uploaded.id ← different IDs +// session.resources[0].file_id !== uploaded.id <- different IDs ``` -Delete the original via `files.delete(uploaded.id)`; the session-scoped copy is garbage-collected with the session. `mount_path` must be absolute — see `shared/managed-agents-environments.md`. +Delete the original via `files.delete(uploaded.id)`; the session-scoped copy is garbage-collected with the session. `mount_path` must be absolute - see `shared/managed-agents-environments.md`. --- -## 9. Secrets for non-MCP APIs and CLIs — keep them host-side via custom tools +## 9. Secrets for non-MCP APIs and CLIs - keep them host-side via custom tools **Problem:** you want the agent to call a third-party API or run a CLI that needs a secret (API key, token, service-account credential), but you can't or don't want to hand the secret to a vault. -**First check:** for cloud environments, the first-class answer is now a vault `environment_variable` credential — the agent's shell sees an opaque placeholder and the real secret is substituted at egress. See `shared/managed-agents-tools.md` → Vaults. Use this pattern instead when that doesn't fit: **self-hosted sandboxes** (env-var credentials not yet supported there), clients that reject the placeholder via local format validation, secrets that must never leave your infrastructure, or calls that need host-side binaries. +**First check:** for cloud environments, the first-class answer is now a vault `environment_variable` credential - the agent's shell sees an opaque placeholder and the real secret is substituted at egress. See `shared/managed-agents-tools.md` -> Vaults. Use this pattern instead when that doesn't fit: **self-hosted sandboxes** (env-var credentials not yet supported there), clients that reject the placeholder via local format validation, secrets that must never leave your infrastructure, or calls that need host-side binaries. **Solution:** move the authenticated call to your side. Declare a custom tool on the agent; when the agent emits `agent.custom_tool_use`, your orchestrator (the process reading the SSE stream) executes the call with its own credentials and responds with `user.custom_tool_result`. The container never sees the key. @@ -213,6 +213,6 @@ for await (const event of stream) { Same shape works for `gh` CLI, local eval scripts, or anything else that needs host-side auth or binaries. -**Security note:** this does not expose a public endpoint. `agent.custom_tool_use` arrives on the SSE stream your orchestrator already holds open with your Anthropic API key, and `user.custom_tool_result` goes back via `events.send()` under the same key. Your orchestrator is a client, not a server — nothing unauthenticated is listening. +**Security note:** this does not expose a public endpoint. `agent.custom_tool_use` arrives on the SSE stream your orchestrator already holds open with your Anthropic API key, and `user.custom_tool_result` goes back via `events.send()` under the same key. Your orchestrator is a client, not a server - nothing unauthenticated is listening. -**Do not embed API keys in the system prompt or user messages as a workaround.** Prompts and messages are stored in the session's event history, returned by `events.list()`, and included in compaction summaries — a secret placed there is durably persisted and readable via the API for the life of the session. +**Do not embed API keys in the system prompt or user messages as a workaround.** Prompts and messages are stored in the session's event history, returned by `events.list()`, and included in compaction summaries - a secret placed there is durably persisted and readable via the API for the life of the session. diff --git a/skills/claude-api/shared/managed-agents-core.md b/skills/claude-api/shared/managed-agents-core.md index e317f39ec..d352371f8 100644 --- a/skills/claude-api/shared/managed-agents-core.md +++ b/skills/claude-api/shared/managed-agents-core.md @@ -1,4 +1,4 @@ -# Managed Agents — Core Concepts +# Managed Agents - Core Concepts ## Architecture @@ -9,31 +9,31 @@ Managed Agents is built around four core concepts: | **Agent** | `/v1/agents` | A persisted, versioned object defining the agent's capabilities and persona: model, system prompt, tools, MCP servers, skills. **Must be created before starting a session.** See the Agents section below. | | **Session** | `/v1/sessions` | A stateful interaction with an agent. References a pre-created agent by ID + an environment + initial instructions. Produces an event stream. | | **Environment** | `/v1/environments` | A template defining the configuration for container provisioning. | -| **Container** | N/A | An isolated compute instance where the agent's **tools** execute (bash, file ops, code). The agent loop does not run here — it runs on Anthropic's orchestration layer and acts on the container via tool calls. | +| **Container** | N/A | An isolated compute instance where the agent's **tools** execute (bash, file ops, code). The agent loop does not run here - it runs on Anthropic's orchestration layer and acts on the container via tool calls. | ``` - ┌─────────────────────────────────────┐ - │ Anthropic orchestration layer │ -Agent (config) ───────▶│ (agent loop: Claude + tool calls) │ - └──────────────┬──────────────────────┘ - │ tool calls - ▼ -Environment (template) ──▶ Container (tool execution workspace) - │ - Session ─┤ - ├── Resources (files, repos, memory stores — attached at startup) - ├── Vault IDs (MCP credential references) - └── Conversation (event stream in/out) + +-------------------------------------+ + | Anthropic orchestration layer | +Agent (config) ------->| (agent loop: Claude + tool calls) | + +--------------+----------------------+ + | tool calls + v +Environment (template) --> Container (tool execution workspace) + | + Session -+ + +-- Resources (files, repos, memory stores - attached at startup) + +-- Vault IDs (MCP credential references) + +-- Conversation (event stream in/out) ``` -> **Agent creation is a prerequisite.** Sessions reference a pre-created agent by ID — `model`/`system`/`tools` live on the agent object, never on the session. Every flow starts with `POST /v1/agents`. +> **Agent creation is a prerequisite.** Sessions reference a pre-created agent by ID - `model`/`system`/`tools` live on the agent object, never on the session. Every flow starts with `POST /v1/agents`. --- ## Session Lifecycle ``` -rescheduling → running ↔ idle → terminated +rescheduling -> running <-> idle -> terminated ``` | Status | Description | @@ -41,30 +41,30 @@ rescheduling → running ↔ idle → terminated | `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. | +| `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. 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. +- 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. -Every session has a live trace view in the Anthropic Console at `https://platform.claude.com/workspaces/{workspace}/sessions/{session_id}`. Print this URL immediately after creating a session so the user can watch tool calls and messages stream in real time. **`{workspace}` is the workspace the API key belongs to** — use `default` only when that's the org's Default workspace. The session response does **not** include a workspace field and the Console has no workspace-agnostic session route, so for non-default workspaces substitute the workspace's ID (visible in the Console URL bar, or expose it as a config value alongside the API key). A `default` link to a session that lives in another workspace lands on a **"Session not found"** page — the **Search workspaces** button there will locate it, but it is not an automatic redirect. +Every session has a live trace view in the Anthropic Console at `https://platform.claude.com/workspaces/{workspace}/sessions/{session_id}`. Print this URL immediately after creating a session so the user can watch tool calls and messages stream in real time. **`{workspace}` is the workspace the API key belongs to** - use `default` only when that's the org's Default workspace. The session response does **not** include a workspace field and the Console has no workspace-agnostic session route, so for non-default workspaces substitute the workspace's ID (visible in the Console URL bar, or expose it as a config value alongside the API key). A `default` link to a session that lives in another workspace lands on a **"Session not found"** page - the **Search workspaces** button there will locate it, but it is not an automatic redirect. ### Built-in session features -- **Context compaction** — if you approach max context, the API automatically condenses session history to keep the interaction going -- **Prompt caching** — historical repeated tokens are cached, reducing processing time and cost -- **Extended thinking** — on by default; `agent.thinking` events signal thinking progress and carry no thinking content +- **Context compaction** - if you approach max context, the API automatically condenses session history to keep the interaction going +- **Prompt caching** - historical repeated tokens are cached, reducing processing time and cost +- **Extended thinking** - on by default; `agent.thinking` events signal thinking progress and carry no thinking content ### Session operations | Operation | Notes | |---|---| | List / fetch | Paginated list or single resource by ID | -| 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. | +| 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. | -These are ops/inspection calls — typically made from a terminal, not application code. From the shell (see `shared/anthropic-cli.md`): +These are ops/inspection calls - typically made from a terminal, not application code. From the shell (see `shared/anthropic-cli.md`): ```sh ant beta:sessions list --transform '{id,title,status,created_at}' --format jsonl @@ -97,9 +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 | 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` | +| `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 @@ -119,31 +119,31 @@ const agent = await client.beta.agents.create( // 2. Start a session that references it const session = await client.beta.sessions.create( { - agent: agent.id, // string shorthand → latest version. Or: { type: "agent", id: agent.id, version: agent.version } + agent: agent.id, // string shorthand -> latest version. Or: { type: "agent", id: agent.id, version: agent.version } environment_id: environmentId, title: "Hello World Session", }, ); ``` -> 💡 **Watch it live in Console.** While developing, print a link so you can click through to the session UI instead of parsing the event stream: `https://platform.claude.com/workspaces/{workspace}/sessions/{session.id}`. Use `default` for `{workspace}` only when the API key belongs to the org's Default workspace; otherwise substitute the workspace's ID (the session response does not carry it — read it from the Console URL bar or make it a config value). Include this `print`/`console.log` in code you generate for users who are iterating locally. +> Tip: **Watch it live in Console.** While developing, print a link so you can click through to the session UI instead of parsing the event stream: `https://platform.claude.com/workspaces/{workspace}/sessions/{session.id}`. Use `default` for `{workspace}` only when the API key belongs to the org's Default workspace; otherwise substitute the workspace's ID (the session response does not carry it - read it from the Console URL bar or make it a config value). Include this `print`/`console.log` in code you generate for users who are iterating locally. **Session creation parameters:** | Field | Type | Required | Description | | --------------- | -------- | -------- | ---------------------------------------------- | -| `agent` | string or object | **Yes** | Three forms: string shorthand `"agent_abc123"` (latest version); pinned `{type: "agent", id, version}`; or `{type: "agent_with_overrides", id, version?, ...}` to override `model`/`system`/`tools`/`mcp_servers`/`skills` for this session only — see § Override agent configuration for a session | +| `agent` | string or object | **Yes** | Three forms: string shorthand `"agent_abc123"` (latest version); pinned `{type: "agent", id, version}`; or `{type: "agent_with_overrides", id, version?, ...}` to override `model`/`system`/`tools`/`mcp_servers`/`skills` for this session only - see § Override agent configuration for a session | | `environment_id`| string | **Yes** | Environment ID | | `title` | string | No | Human-readable name (appears in logs/dashboards) | | `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. | +| `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` -Creating a session without `initial_events` registers the session in `idle` and starts no work; the sandbox is provisioned when the session first needs it. Passing a **non-empty** `initial_events` array starts the agent loop in the same call — the session is **created directly in `running`**, never passing through `idle`. A client that waits for an `idle → running` transition to know work began will wait forever; check `status` on the create response instead. +Creating a session without `initial_events` registers the session in `idle` and starts no work; the sandbox is provisioned when the session first needs it. Passing a **non-empty** `initial_events` array starts the agent loop in the same call - the session is **created directly in `running`**, never passing through `idle`. A client that waits for an `idle -> running` transition to know work began will wait forever; check `status` on the create response instead. ```python session = client.beta.sessions.create( @@ -156,12 +156,12 @@ session = client.beta.sessions.create( ``` - **Only `user.message` and `user.define_outcome` are accepted**, max **50** events. The tool-result kinds (`user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`) are rejected because no agent turn exists yet, and `user.interrupt` because there is no turn to stop. Unlike a scheduled deployment's `initial_events`, a session's does **not** accept `system.message`. -- Each event is validated and persisted before the create response returns, in list order, with a server-assigned ID — exactly as if you had posted it to the send-events endpoint immediately after creation. Per-event content rules are the same as on that endpoint. +- Each event is validated and persisted before the create response returns, in list order, with a server-assigned ID - exactly as if you had posted it to the send-events endpoint immediately after creation. Per-event content rules are the same as on that endpoint. - **The events are not echoed on the create response.** Read them back with `sessions.events.list(session.id)` if you need their server-assigned IDs. - **Validation is all-or-nothing:** if any event fails, the whole request is rejected and no session is created. An empty list is equivalent to omitting the field. -- Rejections: more than one `user.define_outcome` → 400; a `user.define_outcome` without a `rubric` → 400; more than 100 file-sourced `document` content blocks across the whole list → 400; a request body over 32 MB → 413. +- Rejections: more than one `user.define_outcome` -> 400; a `user.define_outcome` without a `rubric` -> 400; more than 100 file-sourced `document` content blocks across the whole list -> 400; a request body over 32 MB -> 413. -An outcome-driven session is therefore a single call — pass one `user.define_outcome` in `initial_events` instead of creating the session and then sending the event (see `shared/managed-agents-outcomes.md`). +An outcome-driven session is therefore a single call - pass one `user.define_outcome` in `initial_events` instead of creating the session and then sending the event (see `shared/managed-agents-outcomes.md`). **Agent configuration fields** (passed to `agents.create()`, not `sessions.create()`): @@ -169,17 +169,17 @@ An outcome-driven session is therefore a single call — pass one `user.define_o | ------------- | -------- | -------- | ---------------------------------------------- | | `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`, `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) | +| `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. | -| `skills` | array | No | Customized "best-practices" context with progressive disclosure. Max 20. See `shared/managed-agents-tools.md` → Skills. | +| `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. | +| `skills` | array | No | Customized "best-practices" context with progressive disclosure. Max 20. See `shared/managed-agents-tools.md` -> Skills. | | `description` | string | No | Description of the agent (up to 2048 chars) | -| `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) | +| `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. +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( @@ -192,16 +192,16 @@ session = client.beta.sessions.create( ) ``` -- `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. +- `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. +- 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. +- 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. @@ -209,22 +209,22 @@ session = client.beta.sessions.create( ## Agents -**This is where every Managed Agents flow begins.** The agent object is a persisted, versioned configuration — you create it once, then reference it by ID every time you start a session. No agent → no session. +**This is where every Managed Agents flow begins.** The agent object is a persisted, versioned configuration - you create it once, then reference it by ID every time you start a session. No agent -> no session. ### Agent Object -The API is **flat** — `model`, `system`, `tools` etc. are top-level fields, not wrapped in an `agent:{}` sub-object. +The API is **flat** - `model`, `system`, `tools` etc. are top-level fields, not wrapped in an `agent:{}` sub-object. | Field | Type | Required | Description | | ------------------ | -------- | -------- | -------------------------------------------------- | | `name` | string | Yes | Human-readable name | -| `model` | string or object | Yes | Claude model ID — bare string, or `{id, speed?, effort?, inference_geo?}` | +| `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 | | `skills` | array | No | Skill references (max 20) | | `description` | string | No | Description of the agent | -| `multiagent` | object | No | Coordinator roster — see `shared/managed-agents-multiagent.md` | +| `multiagent` | object | No | Coordinator roster - see `shared/managed-agents-multiagent.md` | | `metadata` | object | No | Arbitrary key-value pairs | ### Lifecycle: create once, run many, update in place @@ -232,56 +232,56 @@ The API is **flat** — `model`, `system`, `tools` etc. are top-level fields, no The agent is a **persistent resource**, not a per-run parameter. The intended pattern: ``` -┌─ setup (once) ─────────┐ ┌─ runtime (every invocation) ─┐ -│ agents.create() │ │ sessions.create( │ -│ → store agent_id │ ──→ │ agent={type:..., id: ID} │ -│ in config/env/db │ │ ) │ -└────────────────────────┘ └──────────────────────────────┘ ++- setup (once) ---------+ +- runtime (every invocation) -+ +| agents.create() | | sessions.create( | +| -> store agent_id | ---> | agent={type:..., id: ID} | +| in config/env/db | | ) | ++------------------------+ +------------------------------+ ``` -**Anti-pattern:** calling `agents.create()` at the top of every script run. This accumulates orphaned agent objects, pays create latency on every invocation, and defeats the versioning model. If you see `agents.create()` in a function that's called per-request or per-cron-tick, that's wrong — hoist it to one-time setup and persist the ID. +**Anti-pattern:** calling `agents.create()` at the top of every script run. This accumulates orphaned agent objects, pays create latency on every invocation, and defeats the versioning model. If you see `agents.create()` in a function that's called per-request or per-cron-tick, that's wrong - hoist it to one-time setup and persist the ID. -> **Recommended — define agents and environments as YAML + apply via the `ant` CLI.** The split is **CLI for the control plane, SDK for the data plane**: agents and environments are relatively static resources you manage with `ant` (version-controlled YAML, applied from CI); sessions are dynamic and driven by your application through the SDK. See `shared/anthropic-cli.md` → *Version-controlled Managed Agents resources* for the `ant beta:agents create < agent.yaml` / `update --version N` flow. The SDK `agents.create()` call shown elsewhere in this doc is the in-code equivalent — use it when you need to provision programmatically, but prefer the YAML flow for anything a human maintains. +> **Recommended - define agents and environments as YAML + apply via the `ant` CLI.** The split is **CLI for the control plane, SDK for the data plane**: agents and environments are relatively static resources you manage with `ant` (version-controlled YAML, applied from CI); sessions are dynamic and driven by your application through the SDK. See `shared/anthropic-cli.md` -> *Version-controlled Managed Agents resources* for the `ant beta:agents create < agent.yaml` / `update --version N` flow. The SDK `agents.create()` call shown elsewhere in this doc is the in-code equivalent - use it when you need to provision programmatically, but prefer the YAML flow for anything a human maintains. ### Effort on the agent model Pass `model` as an object to set the effort level: `{"id": "claude-opus-5", "effort": "high"}`. `effort` accepts a level string (`low`, `medium`, `high`, `xhigh`, `max`) or an object such as `{"type": "high"}`. The create/update response echoes it in object form and fills in omitted `model` fields with their defaults. -> ⚠️ **Effort is agent configuration only.** An `effort` set inside a per-session `model` override is **not applied** — the session runs at the agent's effort. To change effort you must update the agent (or point the session at a different agent). This is the one field where the override form silently does nothing rather than erroring. +> Warning: **Effort is agent configuration only.** An `effort` set inside a per-session `model` override is **not applied** - the session runs at the agent's effort. To change effort you must update the agent (or point the session at a different agent). This is the one field where the override form silently does nothing rather than erroring. 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. +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). +- **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. +- **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 — 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. +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: | `version` | Behavior | Fits | |---|---|---| -| 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 | +| 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 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.** +**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}` -- **Safe iteration** — update the agent without breaking sessions already running on the old version -- **Rollback** — if a new system prompt regresses, pin new sessions back to the prior version while you debug +- **Reproducibility** - pin a session to a known-good config: `{type: "agent", id, version: 3}` +- **Safe iteration** - update the agent without breaking sessions already running on the old version +- **Rollback** - if a new system prompt regresses, pin new sessions back to the prior version while you debug **`version` is optional.** Omit it (or use the string shorthand `agent="agent_abc123"`) to get the latest version at session-creation time. Pass it explicitly (`{type: "agent", id, version: N}`) to pin for reproducibility. -**Getting the version to pin:** `agents.create()` and `agents.update()` both return `version` in the response. Store it alongside `agent_id`. To fetch the current latest for an existing agent: `GET /v1/agents/{id}` → `.version`. +**Getting the version to pin:** `agents.create()` and `agents.update()` both return `version` in the response. Store it alongside `agent_id`. To fetch the current latest for an existing agent: `GET /v1/agents/{id}` -> `.version`. **When to update vs create new:** Update (`POST /v1/agents/{id}`) when it's conceptually the same agent with tweaked behavior (better prompt, extra tool). Create a new agent when it's a different persona/purpose. Rule of thumb: if you'd give it the same `name`, update. @@ -295,14 +295,14 @@ Each `POST /v1/agents/{id}` (update) creates a new immutable version — a seque | Update | `POST` | `/v1/agents/{id}` | | Archive | `POST` | `/v1/agents/{id}/archive` | -> ⚠️ **Archive is permanent.** Archiving makes the agent read-only: existing sessions continue to run, but **new sessions cannot reference it**, and there is no unarchive. Since agents have no `delete`, this is the terminal lifecycle state. Never archive a production agent as routine cleanup — confirm with the user first. +> Warning: **Archive is permanent.** Archiving makes the agent read-only: existing sessions continue to run, but **new sessions cannot reference it**, and there is no unarchive. Since agents have no `delete`, this is the terminal lifecycle state. Never archive a production agent as routine cleanup - confirm with the user first. ### Using an Agent in a Session Reference the agent by string ID (latest version) or by object with an explicit version: ```python -# String shorthand — uses the agent's latest version +# String shorthand - uses the agent's latest version session = client.beta.sessions.create( agent=agent.id, environment_id=environment_id, @@ -317,7 +317,7 @@ session = client.beta.sessions.create( ### Override agent configuration for a session -The third `agent` form, `agent_with_overrides`, replaces parts of the agent's configuration for **a single session** — try a different model or grant an extra tool without versioning the agent. Pass `id` (and optionally `version`; omitted = latest, same default as the other two forms) plus any of `model`, `system`, `tools`, `mcp_servers`, `skills`: +The third `agent` form, `agent_with_overrides`, replaces parts of the agent's configuration for **a single session** - try a different model or grant an extra tool without versioning the agent. Pass `id` (and optionally `version`; omitted = latest, same default as the other two forms) plus any of `model`, `system`, `tools`, `mcp_servers`, `skills`: ```python session = client.beta.sessions.create( @@ -332,17 +332,17 @@ 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). 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. +- **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). 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`). +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` 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. +`sessions.update()` can change `agent.tools` and `agent.mcp_servers` (including permission policies and the per-tool web settings - `allowed_domains` / `blocked_domains` etc., see `shared/managed-agents-tools.md` § Web search & web fetch settings) on an **existing** session. Updated domain lists apply to the rest of the 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. -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). +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( diff --git a/skills/claude-api/shared/managed-agents-environments.md b/skills/claude-api/shared/managed-agents-environments.md index 4b02922f4..1c86e8e95 100644 --- a/skills/claude-api/shared/managed-agents-environments.md +++ b/skills/claude-api/shared/managed-agents-environments.md @@ -1,8 +1,8 @@ -# Managed Agents — Environments & Resources +# Managed Agents - Environments & Resources ## Environments -Creating a session requires an `environment_id`. Environments are **reusable configuration templates** for spinning up containers in Anthropic's infrastructure — you might create different environments for different use cases (e.g. data visualization vs web development, with different package sets). Anthropic handles scaling, container lifecycle, and work orchestration. +Creating a session requires an `environment_id`. Environments are **reusable configuration templates** for spinning up containers in Anthropic's infrastructure - you might create different environments for different use cases (e.g. data visualization vs web development, with different package sets). Anthropic handles scaling, container lifecycle, and work orchestration. **Environment names must be unique.** Creating an environment with an existing name returns 409. @@ -28,6 +28,10 @@ All three `limited` fields are optional. `allow_package_managers` (default `fals **MCP caveat:** Under `limited` networking, either set `allow_mcp_servers: true` or add each MCP server domain to `allowed_hosts`. Otherwise the container can't reach them and tools silently fail. +**Packages caveat:** Under `limited` networking, `packages` requires `allow_package_managers: true`; otherwise the request fails with a 400. Listing the registry in `allowed_hosts` is not enough. + +**`networking` does not govern `web_search` / `web_fetch`.** Those tools run on Anthropic's servers (in cloud *and* self-hosted environments), so `limited` egress and `allowed_hosts` don't restrict them. To restrict the sites they can reach, set `allowed_domains` / `blocked_domains` on the tool's `configs` entry in the agent toolset - see `shared/managed-agents-tools.md` § Web search & web fetch settings. + ### Creating an environment The SDK adds `managed-agents-2026-04-01` automatically. TypeScript: @@ -44,7 +48,7 @@ const env = await client.beta.environments.create({ ### Self-hosted sandboxes -To run tool execution in **your own infrastructure** instead of Anthropic's, set `config: {type: "self_hosted"}` — the agent loop stays on Anthropic's side, but `bash` / file ops / code execute in a container you control via an outbound-polling worker. The `networking` block does not apply (you control egress). Resource mounting (`file`, `github_repository`) and memory stores behave differently — see `shared/managed-agents-self-hosted-sandboxes.md` for the worker, credentials, and cloud-vs-self-hosted comparison. +To run tool execution in **your own infrastructure** instead of Anthropic's, set `config: {type: "self_hosted"}` - the agent loop stays on Anthropic's side, but `bash` / file ops / code execute in a container you control via an outbound-polling worker. The `networking` block does not apply (you control egress). Resource mounting (`file`, `github_repository`) and memory stores behave differently - see `shared/managed-agents-self-hosted-sandboxes.md` for the worker, credentials, and cloud-vs-self-hosted comparison. ### Environment CRUD @@ -55,15 +59,15 @@ To run tool execution in **your own infrastructure** instead of Anthropic's, set | Get | `GET` | `/v1/environments/{id}` | | | Update | `POST` | `/v1/environments/{id}` | Changes apply only to **new** containers; existing sessions keep their original config | | Delete | `DELETE` | `/v1/environments/{id}` | Returns 204. | -| Archive | `POST` | `/v1/environments/{id}/archive` | Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive — terminal state. | +| Archive | `POST` | `/v1/environments/{id}/archive` | Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive - terminal state. | --- ## Resources -Attach files, GitHub repositories, and memory stores to a session. Resources are resolved during session creation, so a bad `file_id` or an unreachable repo surfaces on the create call rather than mid-run. Creating a session does **not** by itself start work or provision the sandbox — without `initial_events` the session is only registered, and the sandbox comes up when the session first needs it (see `shared/managed-agents-core.md` → Seeding a session with `initial_events`). Max **999 file resources** per session. Multiple GitHub repositories per session are supported. For `type: "memory_store"` resources (persistent cross-session memory — max 8 per session), see `shared/managed-agents-memory.md`. +Attach files, GitHub repositories, and memory stores to a session. Resources are resolved during session creation, so a bad `file_id` or an unreachable repo surfaces on the create call rather than mid-run. Creating a session does **not** by itself start work or provision the sandbox - without `initial_events` the session is only registered, and the sandbox comes up when the session first needs it (see `shared/managed-agents-core.md` -> Seeding a session with `initial_events`). Max **999 file resources** per session. Multiple GitHub repositories per session are supported. For `type: "memory_store"` resources (persistent cross-session memory - max 8 per session), see `shared/managed-agents-memory.md`. -### File Uploads (input — host → agent) +### File Uploads (input - host -> agent) Upload a file first via the Files API, then reference by `file_id` + `mount_path`: @@ -84,9 +88,9 @@ const session = await client.beta.sessions.create({ }); ``` -**`mount_path` is required** and must be absolute. Parent directories are created automatically. Agent working directory defaults to `/workspace`. Files are mounted read-only — the agent writes modified versions to new paths. +**`mount_path` is required** and must be absolute. Parent directories are created automatically. Agent working directory defaults to `/workspace`. Files are mounted read-only - the agent writes modified versions to new paths. -### Session outputs (output — agent → host) +### Session outputs (output - agent -> host) The agent can write files to `/mnt/session/outputs/` during a session. These are automatically captured by the Files API and can be listed and downloaded afterwards: @@ -105,44 +109,44 @@ for await (const f of client.beta.files.list({ **Requirements:** - The `write` tool (or `bash`) must be enabled for the agent to create output files. - Session-scoped `files.list` / `files.download` captures outputs written to `/mnt/session/outputs/`. -- The filter parameter is **`scope_id`** (REST query param `?scope_id=`). The SDK's files resource auto-adds only the `files-api-2025-04-14` header, so pass `betas: ["managed-agents-2026-04-01"]` explicitly (or both headers on raw HTTP) — without it the API may reject `scope_id` as an unknown field. Requires `@anthropic-ai/sdk` ≥ 0.88.0 / `anthropic` (Python) ≥ 0.92.0 — older versions don't type `scope_id`. The `ant` CLI does **not** expose this flag yet; use the SDK or curl. -- Pass the session ID returned by `sessions.create()` verbatim (e.g. `sesn_011CZx...`) — the API validates the prefix. -- There's a brief indexing lag (~1–3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if empty. +- The filter parameter is **`scope_id`** (REST query param `?scope_id=`). The SDK's files resource auto-adds only the `files-api-2025-04-14` header, so pass `betas: ["managed-agents-2026-04-01"]` explicitly (or both headers on raw HTTP) - without it the API may reject `scope_id` as an unknown field. Requires `@anthropic-ai/sdk` >= 0.88.0 / `anthropic` (Python) >= 0.92.0 - older versions don't type `scope_id`. The `ant` CLI does **not** expose this flag yet; use the SDK or curl. +- Pass the session ID returned by `sessions.create()` verbatim (e.g. `sesn_011CZx...`) - the API validates the prefix. +- There's a brief indexing lag (~1-3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if empty. -> **Fallback when `scope_id` filtering is unavailable** (older SDK, or endpoint returns an error): send a follow-up `user.message` asking the agent to `read` each file under `/mnt/session/outputs/` and return the contents. The agent streams the file bodies back as `agent.message` text. This works for text files only and costs output tokens — use it to unblock, not as the primary path. +> **Fallback when `scope_id` filtering is unavailable** (older SDK, or endpoint returns an error): send a follow-up `user.message` asking the agent to `read` each file under `/mnt/session/outputs/` and return the contents. The agent streams the file bodies back as `agent.message` text. This works for text files only and costs output tokens - use it to unblock, not as the primary path. This gives you a bidirectional file bridge: upload reference data in, download agent artifacts out. ### GitHub Repositories -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. +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. +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()`. +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:** | Field | Required | Notes | |---|---|---| -| `type` | ✅ | `"github_repository"` | -| `url` | ✅ | The GitHub repository URL | -| `authorization_token` | ✅ | GitHub Personal Access Token with repository access. **Never echoed in API responses.** | -| `mount_path` | ❌ | Path where the repository will be cloned. Defaults to `/workspace/`. | -| `checkout` | ❌ | `{type: "branch", name: "..."}` or `{type: "commit", sha: "..."}`. Defaults to the repo's default branch. | +| `type` | Yes | `"github_repository"` | +| `url` | Yes | The GitHub repository URL | +| `authorization_token` | Yes | GitHub Personal Access Token with repository access. **Never echoed in API responses.** | +| `mount_path` | No | Path where the repository will be cloned. Defaults to `/workspace/`. | +| `checkout` | No | `{type: "branch", name: "..."}` or `{type: "commit", sha: "..."}`. Defaults to the repo's default branch. | **Token permission levels** (fine-grained PATs): -- `Contents: Read` — clone only -- `Contents: Read and write` — push changes and create pull requests +- `Contents: Read` - clone only +- `Contents: Read and write` - push changes and create pull requests -**How auth works:** `authorization_token` is never placed inside the container. `git pull` / `git push` and GitHub REST calls against the attached repository are routed through an Anthropic-side git proxy that injects the token after the request leaves the sandbox. Code running in the container — including anything the agent writes — cannot read or exfiltrate it. +**How auth works:** `authorization_token` is never placed inside the container. `git pull` / `git push` and GitHub REST calls against the attached repository are routed through an Anthropic-side git proxy that injects the token after the request leaves the sandbox. Code running in the container - including anything the agent writes - cannot read or exfiltrate it. -> ‼️ **To generate pull requests** you also need GitHub **MCP server** access — the `github_repository` resource gives filesystem + git access only. See `shared/managed-agents-tools.md` → MCP Servers. The PR workflow is: edit files in the mounted repo → push branch via `bash` (authenticated via the git proxy using `authorization_token`) → create PR via the MCP `create_pull_request` tool (authenticated via the vault). +> Important: **To generate pull requests** you also need GitHub **MCP server** access - the `github_repository` resource gives filesystem + git access only. See `shared/managed-agents-tools.md` -> MCP Servers. The PR workflow is: edit files in the mounted repo -> push branch via `bash` (authenticated via the git proxy using `authorization_token`) -> create PR via the MCP `create_pull_request` tool (authenticated via the vault). **TypeScript:** ```ts -// 1. Create the agent — declare GitHub MCP (no auth here) +// 1. Create the agent - declare GitHub MCP (no auth here) const agent = await client.beta.agents.create( { name: 'GitHub Agent', @@ -157,7 +161,7 @@ const agent = await client.beta.agents.create( }, ); -// 2. Start a session — attach vault for MCP auth + mount the repo +// 2. Start a session - attach vault for MCP auth + mount the repo const session = await client.beta.sessions.create({ agent: agent.id, environment_id: envId, @@ -166,7 +170,7 @@ const session = await client.beta.sessions.create({ { type: 'github_repository', url: 'https://github.com/owner/repo', - authorization_token: process.env.GITHUB_TOKEN, // repo clone token (≠ MCP auth) + authorization_token: process.env.GITHUB_TOKEN, // repo clone token (!= MCP auth) checkout: { type: 'branch', name: 'main' }, }, ], @@ -199,7 +203,7 @@ session = client.beta.sessions.create( resources=[{ "type": "github_repository", "url": "https://github.com/owner/repo", - "authorization_token": os.environ["GITHUB_TOKEN"], # repo clone token (≠ MCP auth) + "authorization_token": os.environ["GITHUB_TOKEN"], # repo clone token (!= MCP auth) "checkout": {"type": "branch", "name": "main"}, }], ) @@ -216,7 +220,7 @@ Upload and manage files for use as session resources, and download files the age | Upload | `POST` | `/v1/files` | `client.beta.files.upload({ file })` | | List | `GET` | `/v1/files?scope_id=...` | `client.beta.files.list({ scope_id, betas: ["managed-agents-2026-04-01"] })` | | Get Metadata | `GET` | `/v1/files/{id}` | `client.beta.files.retrieveMetadata(id)` | -| Download | `GET` | `/v1/files/{id}/content` | `client.beta.files.download(id)` → `Response` | +| Download | `GET` | `/v1/files/{id}/content` | `client.beta.files.download(id)` -> `Response` | | Delete | `DELETE` | `/v1/files/{id}` | `client.beta.files.delete(id)` | The `scope_id` filter on List scopes the results to files written to `/mnt/session/outputs/` by that session. Without the filter, you get all files uploaded to your account. diff --git a/skills/claude-api/shared/managed-agents-events.md b/skills/claude-api/shared/managed-agents-events.md index 3fbe3c3a4..7465288a6 100644 --- a/skills/claude-api/shared/managed-agents-events.md +++ b/skills/claude-api/shared/managed-agents-events.md @@ -1,4 +1,4 @@ -# Managed Agents — Events & Steering +# Managed Agents - Events & Steering ## Events @@ -12,12 +12,12 @@ Send events to a session via `POST /v1/sessions/{id}/events`. | `user.interrupt` | Interrupt the agent while it's running | | `user.tool_confirmation` | Approve/deny a tool call (when `always_ask` policy) | | `user.custom_tool_result` | Provide result for a custom tool call | -| `user.define_outcome` | Start a rubric-graded iterate loop — see `shared/managed-agents-outcomes.md` | +| `user.define_outcome` | Start a rubric-graded iterate loop - see `shared/managed-agents-outcomes.md` | | `system.message` | Append privileged system-level context for this turn and every turn after it; see § Adding system context mid-session | #### Adding system context mid-session (`system.message`) -The `system` field on the agent definition sets the top-level system prompt and is fixed for the session's lifetime. A `system.message` event **appends** to the session's system context as a `role: "system"` turn — it does not replace that prompt. The content applies to the accompanying turn and all subsequent turns. Use it for a different persona, revised constraints, or runtime-fetched context that should shape behavior going forward: +The `system` field on the agent definition sets the top-level system prompt and is fixed for the session's lifetime. A `system.message` event **appends** to the session's system context as a `role: "system"` turn - it does not replace that prompt. The content applies to the accompanying turn and all subsequent turns. Use it for a different persona, revised constraints, or runtime-fetched context that should shape behavior going forward: ```python client.beta.sessions.events.send( @@ -35,23 +35,25 @@ client.beta.sessions.events.send( Constraints: -- **Model-gated: Claude Opus 5, Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5, and Claude Mythos 5.** Only the agent's **primary** model is checked — `system.message` lands on the primary thread only, so subagent models are not considered. On an unsupported primary model the event is rejected with a `model_does_not_support_mid_conversation_system` validation error. -- **While the session is idle with `stop_reason: requires_action`** (blocked on `user.custom_tool_result` / `user.tool_confirmation`), a `system.message` is accepted **only when it trails a tool result event in the same request**. Sent on its own — or alongside a `user.message` — it is rejected until the pending tool events are resolved. -- `content` accepts 1–1000 text items. +- **Model-gated: Claude Opus 5, Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5.1, and Claude Mythos 5.1.** Only the agent's **primary** model is checked - `system.message` lands on the primary thread only, so subagent models are not considered. On an unsupported primary model the event is rejected with a `model_does_not_support_mid_conversation_system` validation error. +- **While the session is idle with `stop_reason: requires_action`** (blocked on `user.custom_tool_result` / `user.tool_confirmation`), a `system.message` is accepted **only when it trails a tool result event in the same request**. Sent on its own - or alongside a `user.message` - it is rejected until the pending tool events are resolved. +- `content` accepts 1-1000 text items. ### Receiving Events Three methods: -1. **Streaming (SSE)**: `GET /v1/sessions/{id}/events/stream` — real-time Server-Sent Events. **Long-lived** — the server sends periodic heartbeats to keep the connection alive. -2. **Polling**: `GET /v1/sessions/{id}/events` — paginated event list (query params: `limit` default 1000, `page`). **Returns immediately** — this is a plain paginated GET, not a long-poll. -3. **Webhooks**: Anthropic POSTs session state transitions to your HTTPS endpoint — thin payloads (IDs only), HMAC-signed, Console-registered. See `shared/managed-agents-webhooks.md`. +1. **Streaming (SSE)**: `GET /v1/sessions/{id}/events/stream` - real-time Server-Sent Events. **Long-lived** - the server sends periodic heartbeats to keep the connection alive. +2. **Polling**: `GET /v1/sessions/{id}/events` - paginated event list (query params: `limit` default 1000, `page`). **Returns immediately** - this is a plain paginated GET, not a long-poll. +3. **Webhooks**: Anthropic POSTs session state transitions to your HTTPS endpoint - thin payloads (IDs only), HMAC-signed, Console-registered. See `shared/managed-agents-webhooks.md`. -All **persisted** events carry `id`, `type`, and `processed_at` (ISO 8601), set when the event finishes processing. On events you send, `processed_at` is `null` while the event is still queued behind earlier ones — **except** `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`, which are processed on receipt and echoed back with `processed_at` already populated. The stream-only `event_start` / `event_delta` preview events (see § Live previews) carry only the `id` of the event they preview. +**No-code inspection - the Console session viewer** (Console sidebar -> **Managed Agents** -> **Sessions**; Developers and Admins only). Point users here for debugging before they parse the stream themselves: a session list (ID, name, status, agent, tokens in/out, cost; filter by status/created, search by ID); a **timeline minimap** with one lane per thread in multiagent sessions; the **transcript** grouped by model request (thinking, tool calls with inputs/results, streaming text) with a **Filter events** box (matches ID, type, tool name, or text; Enter steps between matches) and copy/download-as-JSON (filtered export when a filter is active); and an **Inspector** side panel (toggle with `d`) with five tabs - **Session** (details, metadata, cumulative-cost chart vs. budget), **Events** (raw events in server order, JSON per event, plus a **Deltas** view for messages that streamed while the page was open), **Tools** (every configured tool with call counts, failures, median duration; jump to any call), **Resources** (mounted files, repos, memory stores with per-session memory changes, `/mnt/session/outputs` files, skills under `/workspace/skills`), **Threads** (status, context size, cost per thread; context-size chart for the current thread; switch threads). Deep-link with `?event={event_id}` on the session URL - handy to include in error reports alongside the Console link from `shared/managed-agents-core.md`. -> ⚠️ **Robust polling (raw HTTP).** If you bypass the SDK and roll your own poll loop, don't rely on `requests` or `httpx` timeouts as wall-clock caps — they're **per-chunk** read timeouts, reset every time a byte arrives. A trickling response (heartbeats, a wedged chunked-encoding body, a misbehaving proxy) can keep the call blocked indefinitely even with `timeout=(5, 60)` or `httpx.Timeout(120)`. Neither library has a "total wall-clock" timeout built in. For a hard deadline: track `time.monotonic()` at the loop level and break/cancel if a single request exceeds your budget (e.g. via a watchdog thread, or `asyncio.wait_for()` around async httpx). **Prefer the SDK** — `client.beta.sessions.events.stream()` and `client.beta.sessions.events.list()` handle timeout + retry sanely. +All **persisted** events carry `id`, `type`, and `processed_at` (ISO 8601), set when the event finishes processing. On events you send, `processed_at` is `null` while the event is still queued behind earlier ones - **except** `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`, which are processed on receipt and echoed back with `processed_at` already populated. The stream-only `event_start` / `event_delta` preview events (see § Live previews) carry only the `id` of the event they preview. + +> Warning: **Robust polling (raw HTTP).** If you bypass the SDK and roll your own poll loop, don't rely on `requests` or `httpx` timeouts as wall-clock caps - they're **per-chunk** read timeouts, reset every time a byte arrives. A trickling response (heartbeats, a wedged chunked-encoding body, a misbehaving proxy) can keep the call blocked indefinitely even with `timeout=(5, 60)` or `httpx.Timeout(120)`. Neither library has a "total wall-clock" timeout built in. For a hard deadline: track `time.monotonic()` at the loop level and break/cancel if a single request exceeds your budget (e.g. via a watchdog thread, or `asyncio.wait_for()` around async httpx). **Prefer the SDK** - `client.beta.sessions.events.stream()` and `client.beta.sessions.events.list()` handle timeout + retry sanely. > -> If `GET /v1/sessions/{id}/events` (paginated) ever hangs after headers, you've likely hit `GET /v1/sessions/{id}/events/stream` by mistake or a server-side stall — report it; don't treat it as a client-config problem. +> If `GET /v1/sessions/{id}/events` (paginated) ever hangs after headers, you've likely hit `GET /v1/sessions/{id}/events/stream` by mistake or a server-side stall - report it; don't treat it as a client-config problem. ### Event Types (Received) @@ -60,40 +62,40 @@ Event types use dot notation, grouped by namespace: | Event Type | Description | | --- | --- | | `agent.message` | Agent text output | -| `agent.thinking` | Progress signal that the agent is thinking — it does **not** carry the thinking content | +| `agent.thinking` | Progress signal that the agent is thinking - it does **not** carry the thinking content | | `agent.tool_use` | Agent used a built-in tool (`agent_toolset_20260401`) | | `agent.tool_result` | Result from a built-in tool | | `agent.mcp_tool_use` | Agent used an MCP tool | | `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.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`, 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.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), 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`. | +| `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), 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.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). +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`. +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`. --- ## Live previews -By default, assistant text reaches the stream as buffered `agent.message` events — emitted only after the model request that produced them finishes. **Live previews** let you render that text incrementally while the model is still generating. The buffered `agent.message` is always the authoritative record; a client that ignores previews still receives a complete, correct stream. The wire format is **not** Messages-API streaming: the delta type is `content_delta`, not `content_block_delta`, so Messages-API accumulator code does not carry over unchanged. +By default, assistant text reaches the stream as buffered `agent.message` events - emitted only after the model request that produced them finishes. **Live previews** let you render that text incrementally while the model is still generating. The buffered `agent.message` is always the authoritative record; a client that ignores previews still receives a complete, correct stream. The wire format is **not** Messages-API streaming: the delta type is `content_delta`, not `content_block_delta`, so Messages-API accumulator code does not carry over unchanged. -**Opt in per stream connection** by adding the `event_deltas[]` query parameter, repeated once per event type to preview. Accepted values: `agent.message`, `agent.thinking` — any other value returns a 400, as does a request with more than 100 values. **Both stream endpoints accept it:** the session-level stream (`GET /v1/sessions/{id}/events/stream`) and each session thread's own stream (`GET /v1/sessions/{sid}/threads/{tid}/stream`). In a shell, quote the URL or percent-encode the brackets as `%5B%5D` — bare `[]` is a glob pattern. +**Opt in per stream connection** by adding the `event_deltas[]` query parameter, repeated once per event type to preview. Accepted values: `agent.message`, `agent.thinking` - any other value returns a 400, as does a request with more than 100 values. **Both stream endpoints accept it:** the session-level stream (`GET /v1/sessions/{id}/events/stream`) and each session thread's own stream (`GET /v1/sessions/{sid}/threads/{tid}/stream`). In a shell, quote the URL or percent-encode the brackets as `%5B%5D` - bare `[]` is a glob pattern. -**Previews are thread-scoped.** A connection previews only the thread it is reading. A child thread's previews are delivered on that child's stream and are *never* cross-posted to the session-level stream, whose previews stay scoped to the primary thread. To watch a subagent's text as the model generates it, open that subagent's thread stream — see `shared/managed-agents-multiagent.md`. Run one accumulator instance per connection. +**Previews are thread-scoped.** A connection previews only the thread it is reading. A child thread's previews are delivered on that child's stream and are *never* cross-posted to the session-level stream, whose previews stay scoped to the primary thread. To watch a subagent's text as the model generates it, open that subagent's thread stream - see `shared/managed-agents-multiagent.md`. Run one accumulator instance per connection. ```python stream = client.beta.sessions.events.stream( @@ -109,24 +111,24 @@ When a previewed event begins, the stream emits an `event_start` carrying the up {"type": "event_delta", "event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "Here is the summary"}}} ``` -`event_start` and `event_delta` have no `id` or `processed_at` of their own — the only identifier they carry is the `id` of the event they preview. For `agent.thinking`, **only** the `event_start` is emitted (a "thinking has started" signal) — no deltas follow, and the buffered `agent.thinking` that concludes the preview carries no thinking content either. It is a progress signal, not a content carrier; there is nothing to read out of it. +`event_start` and `event_delta` have no `id` or `processed_at` of their own - the only identifier they carry is the `id` of the event they preview. For `agent.thinking`, **only** the `event_start` is emitted (a "thinking has started" signal) - no deltas follow, and the buffered `agent.thinking` that concludes the preview carries no thinking content either. It is a progress signal, not a content carrier; there is nothing to read out of it. -**Accumulate-and-reconcile pattern.** Treat the preview as a scratch buffer keyed by `(event_id, index)`. On `event_start`, create an empty entry for the announced `id`. On each `event_delta`, append `delta.content.text` to `(event_id, delta.index)` and render the running text. When the buffered `agent.message` arrives, match it by `id`, **discard the accumulated preview**, and render the message's content instead. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered event's `id` are the same value. On a normal turn the order is fixed: `session.status_running` → `span.model_request_start` → `event_start` → `event_delta`* → buffered `agent.message` → `span.model_request_end`. If the turn errors or is interrupted the buffered event may never arrive, but `span.model_request_end` still does — close any unreconciled preview when you see it. Python/TypeScript/Go SDKs ship an accumulator helper that implements this; in other SDKs apply the manual pattern to the generated event types. +**Accumulate-and-reconcile pattern.** Treat the preview as a scratch buffer keyed by `(event_id, index)`. On `event_start`, create an empty entry for the announced `id`. On each `event_delta`, append `delta.content.text` to `(event_id, delta.index)` and render the running text. When the buffered `agent.message` arrives, match it by `id`, **discard the accumulated preview**, and render the message's content instead. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered event's `id` are the same value. On a normal turn the order is fixed: `session.status_running` -> `span.model_request_start` -> `event_start` -> `event_delta`* -> buffered `agent.message` -> `span.model_request_end`. If the turn errors or is interrupted the buffered event may never arrive, but `span.model_request_end` still does - close any unreconciled preview when you see it. Python/TypeScript/Go SDKs ship an accumulator helper that implements this; in other SDKs apply the manual pattern to the generated event types. -**Two guarantees the pattern relies on:** concatenating a preview's deltas in arrival order, keyed by `(event_id, index)`, yields a *prefix* of `content[index].text` in the buffered event (a prefix, not necessarily the whole text — deltas may be shed under load); and a connection emits at most one `event_start` per `event_id`, with the buffered event as the last thing that connection delivers for that `id`. +**Two guarantees the pattern relies on:** concatenating a preview's deltas in arrival order, keyed by `(event_id, index)`, yields a *prefix* of `content[index].text` in the buffered event (a prefix, not necessarily the whole text - deltas may be shed under load); and a connection emits at most one `event_start` per `event_id`, with the buffered event as the last thing that connection delivers for that `id`. **Limitations:** -- **Best effort** — under load the server may shed deltas for an event; you receive a contiguous prefix and then no further deltas for that event. The buffered `agent.message` still arrives complete. Never treat an accumulated preview as final. -- **No replay on reconnect** — deltas are delivered only to the connection that opted in, while it's open; this holds for the session-level stream and each thread stream alike. A connection opened after a model request started receives no deltas for that in-flight event. After a drop, follow the consolidation pattern in § Reconnecting after a dropped stream — the history fetch returns any buffered events emitted during the gap; missed deltas cannot be re-requested. -- **One thread, text only** — previews cover assistant text on the thread the connection is reading. Tool use, tool results, MCP results, and activity on any *other* thread are never previewed on that connection. -- **Never persisted** — `event_start` / `event_delta` exist only on the live SSE stream, never in `GET /v1/sessions/{id}/events` or any thread's event history. +- **Best effort** - under load the server may shed deltas for an event; you receive a contiguous prefix and then no further deltas for that event. The buffered `agent.message` still arrives complete. Never treat an accumulated preview as final. +- **No replay on reconnect** - deltas are delivered only to the connection that opted in, while it's open; this holds for the session-level stream and each thread stream alike. A connection opened after a model request started receives no deltas for that in-flight event. After a drop, follow the consolidation pattern in § Reconnecting after a dropped stream - the history fetch returns any buffered events emitted during the gap; missed deltas cannot be re-requested. +- **One thread, text only** - previews cover assistant text on the thread the connection is reading. Tool use, tool results, MCP results, and activity on any *other* thread are never previewed on that connection. +- **Never persisted** - `event_start` / `event_delta` exist only on the live SSE stream, never in `GET /v1/sessions/{id}/events` or any thread's event history. **Troubleshooting:** | You see | What it means | | --- | --- | | Buffered events but no `event_start` / `event_delta` | This connection didn't opt in (`event_deltas[]` is per connection, not per session), or the turn ran on a different thread. List `GET /v1/sessions/{sid}/threads` to find which one ran. | -| 404 on the stream URL | Wrong path or ID, or the request carries no managed-agents beta header — the thread endpoints are beta-gated, so without it they don't exist. The thread path is `/threads/{tid}/stream`, **not** `/threads/{tid}/events/stream` (which doesn't exist) and not `/events/stream` (session level only). | +| 404 on the stream URL | Wrong path or ID, or the request carries no managed-agents beta header - the thread endpoints are beta-gated, so without it they don't exist. The thread path is `/threads/{tid}/stream`, **not** `/threads/{tid}/events/stream` (which doesn't exist) and not `/events/stream` (session level only). | | 400 naming `event_deltas` | Only `agent.message` and `agent.thinking` are accepted, max 100 values. | --- @@ -137,21 +139,21 @@ Practical patterns for driving a session via the events surface. ### Stream-first ordering -**Open the stream before sending events.** The stream only delivers events that occur *after* it's opened — it does not replay current state or historical events. If you send a message first and open the stream second, early events (including fast status transitions) arrive buffered in a single batch and you lose the ability to react to them in real time. +**Open the stream before sending events.** The stream only delivers events that occur *after* it's opened - it does not replay current state or historical events. If you send a message first and open the stream second, early events (including fast status transitions) arrive buffered in a single batch and you lose the ability to react to them in real time. ```ts -// ✅ Correct — stream and send concurrently +// Correct - stream and send concurrently const [response] = await Promise.all([ streamEvents(sessionId), // opens SSE connection sendMessage(sessionId, text), ]); -// ❌ Wrong — events before stream opens arrive as a single buffered batch +// Wrong - events before stream opens arrive as a single buffered batch await sendMessage(sessionId, text); const response = await streamEvents(sessionId); ``` -**For full history,** use `GET /v1/sessions/{id}/events` (paginated list) — the stream only gives you live events from connection onward. +**For full history,** use `GET /v1/sessions/{id}/events` (paginated list) - the stream only gives you live events from connection onward. ### Reconnecting after a dropped stream @@ -169,7 +171,7 @@ def connect_with_consolidation(client, session_id): session_id=session_id, ) - # 3. Yield history first, then stream — dedupe by event.id + # 3. Yield history first, then stream - dedupe by event.id seen = set() for ev in history.data: seen.add(ev.id) @@ -189,14 +191,14 @@ def connect_with_consolidation(client, session_id): await sendMessage(sessionId, "Summarize the README"); await sendMessage(sessionId, "Actually also check the CONTRIBUTING guide"); await sendMessage(sessionId, "And compare the two"); -// Stream once — agent responds to all three as a coherent turn +// 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()`. 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. +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`. 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: +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, { @@ -204,33 +206,35 @@ 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`) — though not at a budget pause, where the interrupt is accepted and ignored (see § Reaching a session budget). +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. +**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`. +**Against an already-`idle` session an interrupt is normally a no-op.** The exception is a session on a self-hosted environment whose worker failed the claimed work item (a memory-store mount error, for instance): it sits `idle` with `stop_reason: requires_action` and no error event, and `user.interrupt` re-queues the work for the next worker claim (`shared/managed-agents-self-hosted-sandboxes.md` § Memory stores -> Troubleshooting). -> **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.) +**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. (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. +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. +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. +**`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 some events carry useful metadata beyond the status change itself: -`session.status_idle` — includes a `stop_reason` field which elaborates on why the session stopped and what type of further action is required by the user. +`session.status_idle` - includes a `stop_reason` field which elaborates on why the session stopped and what type of further action is required by the user. ```json { "id": "sevt_456", @@ -263,7 +267,7 @@ some events carry useful metadata beyond the status change itself: } ``` -**`agent.thread_context_compacted`** — emitted when the conversation history was summarized to fit context. Includes `pre_compaction_tokens` so you know how much was squeezed: +**`agent.thread_context_compacted`** - emitted when the conversation history was summarized to fit context. Includes `pre_compaction_tokens` so you know how much was squeezed: ```json { @@ -281,6 +285,6 @@ When done with a session, archive it to free resources: await client.beta.sessions.archive(sessionId); ``` -> Archiving a **session** is routine cleanup — sessions are per-run and disposable. **Do not generalize this to agents or environments**: those are persistent, reusable resources, and archiving them is permanent (no unarchive; new sessions cannot reference them). See `shared/managed-agents-overview.md` → Common Pitfalls. +> Archiving a **session** is routine cleanup - sessions are per-run and disposable. **Do not generalize this to agents or environments**: those are persistent, reusable resources, and archiving them is permanent (no unarchive; new sessions cannot reference them). See `shared/managed-agents-overview.md` -> Common Pitfalls. diff --git a/skills/claude-api/shared/managed-agents-memory.md b/skills/claude-api/shared/managed-agents-memory.md index 1b59052ec..28e119b6f 100644 --- a/skills/claude-api/shared/managed-agents-memory.md +++ b/skills/claude-api/shared/managed-agents-memory.md @@ -1,24 +1,24 @@ -# Managed Agents — Memory Stores +# Managed Agents - Memory Stores > **Public beta.** Memory stores ship under the `managed-agents-2026-04-01` beta header; the SDK sets it automatically on all `client.beta.memory_stores.*` calls. If `client.beta.memory_stores` is missing, upgrade to the latest SDK release. -Sessions are ephemeral by default — when one ends, anything the agent learned is gone. A **memory store** is a workspace-scoped collection of small text documents that persists across sessions. When a store is attached to a session (via `resources[]`), it is mounted into the container as a filesystem directory; the agent reads and writes it with the ordinary file tools, and a system-prompt note tells it the mount is there. +Sessions are ephemeral by default - when one ends, anything the agent learned is gone. A **memory store** is a workspace-scoped collection of small text documents that persists across sessions. When a store is attached to a session (via `resources[]`), it is mounted into the container as a filesystem directory; the agent reads and writes it with the ordinary file tools, and a system-prompt note tells it the mount is there. Every mutation to a memory produces an immutable **memory version** (`memver_...`), giving you an audit trail and point-in-time rollback/redact. -> ⚠️ **Never store credentials, API keys, or tokens in memory stores.** Memories persist across sessions and are returned verbatim into future contexts — a key written once is replayed into every later session that mounts the store. Use vault `environment_variable` credentials instead (`shared/managed-agents-tools.md` → Vaults). If a secret has already been written, delete the memory and redact the affected versions (see "Redact a version" below). +> Warning: **Never store credentials, API keys, or tokens in memory stores.** Memories persist across sessions and are returned verbatim into future contexts - a key written once is replayed into every later session that mounts the store. Use vault `environment_variable` credentials instead (`shared/managed-agents-tools.md` -> Vaults). If a secret has already been written, delete the memory and redact the affected versions (see "Redact a version" below). ## Object model | Object | ID prefix | Scope | Notes | | --- | --- | --- | --- | | Memory store | `memstore_...` | Workspace | Attach to sessions via `resources[]` | -| Memory | `mem_...` | Store | One text file, addressed by `path` (≤ 100KB each — prefer many small files) | -| Memory version | `memver_...` | Memory | Immutable snapshot per mutation; `operation` ∈ `created` / `modified` / `deleted` | +| Memory | `mem_...` | Store | One text file, addressed by `path` (<= 100KB each - prefer many small files) | +| Memory version | `memver_...` | Memory | Immutable snapshot per mutation; `operation` in `created` / `modified` / `deleted` | ## Create a store -`description` is passed to the agent so it knows what the store contains — write it for the model, not for humans. +`description` is passed to the agent so it knows what the store contains - write it for the model, not for humans. ```python store = client.beta.memory_stores.create( @@ -28,9 +28,9 @@ store = client.beta.memory_stores.create( print(store.id) # memstore_01Hx... ``` -Other SDKs: TypeScript `client.beta.memoryStores.create({...})`; Go `client.Beta.MemoryStores.New(ctx, ...)`. See `shared/managed-agents-api-reference.md` → SDK Method Reference for the full per-language table. +Other SDKs: TypeScript `client.beta.memoryStores.create({...})`; Go `client.Beta.MemoryStores.New(ctx, ...)`. See `shared/managed-agents-api-reference.md` -> SDK Method Reference for the full per-language table. -Stores support `retrieve` / `update` / `list` (with `include_archived`, `created_at_{gte,lte}` filters) / `delete` / **`archive`**. Archive makes the store read-only — existing session attachments continue, new sessions cannot reference it; no unarchive. +Stores support `retrieve` / `update` / `list` (with `include_archived`, `created_at_{gte,lte}` filters) / `delete` / **`archive`**. Archive makes the store read-only - existing session attachments continue, new sessions cannot reference it; no unarchive. ### Seed with content (optional) @@ -46,7 +46,7 @@ client.beta.memory_stores.memories.create( ## Attach to a session -Memory stores go in the session's `resources[]` array alongside `file` and `github_repository` resources (see `shared/managed-agents-environments.md` → Resources). Memory stores attach at **session create time only** — `sessions.resources.add()` does not accept `memory_store`. +Memory stores go in the session's `resources[]` array alongside `file` and `github_repository` resources (see `shared/managed-agents-environments.md` -> Resources). Memory stores attach at **session create time only** - `sessions.resources.add()` does not accept `memory_store`. Sessions on **self-hosted** environments attach them the same way (and `memory_store` is the *only* resource type those environments accept) - see the self-hosted note below. ```python session = client.beta.sessions.create( @@ -65,26 +65,28 @@ session = client.beta.sessions.create( | Field | Required | Notes | | --- | --- | --- | -| `type` | ✅ | `"memory_store"` | -| `memory_store_id` | ✅ | `memstore_...` | -| `access` | — | `"read_write"` (default) or `"read_only"` — enforced at the filesystem level on the mount | -| `instructions` | — | Session-specific guidance for this store, in addition to the store's `name`/`description`. ≤ 4,096 chars. | +| `type` | Yes | `"memory_store"` | +| `memory_store_id` | Yes | `memstore_...` | +| `access` | - | `"read_write"` (default) or `"read_only"` - enforced at the filesystem level on the cloud mount; on self-hosted sandboxes enforced by the worker's `write`/`edit` tools and by the upload path (see below) | +| `instructions` | - | Session-specific guidance for this store, in addition to the store's `name`/`description`. <= 4,096 chars. | -**Max 8 memory stores per session.** Attach multiple when different slices of memory have different owners or lifecycles — e.g. one read-only shared-reference store plus one read-write per-user store, or one store per end-user/team/project sharing a single agent config. +**Max 8 memory stores per session.** Attach multiple when different slices of memory have different owners or lifecycles - e.g. one read-only shared-reference store plus one read-write per-user store, or one store per end-user/team/project sharing a single agent config. ### How the agent sees it (FUSE mount) -Each attached store is mounted in the session container at `/mnt/memory//`. The agent interacts with it using the standard file tools (`bash`, `read`, `write`, `edit`, `glob`, `grep`) — there are no dedicated memory tools. `access: "read_only"` makes the mount read-only at the filesystem level; `"read_write"` allows the agent to create, edit, and delete files under it. A short description of each mount (name, path, `instructions`, access) is automatically injected into the system prompt so the agent knows the store exists without you having to mention it. +Each attached store is mounted in the session container at `/mnt/memory//`. The agent interacts with it using the standard file tools (`bash`, `read`, `write`, `edit`, `glob`, `grep`) - there are no dedicated memory tools. On cloud sandboxes `access: "read_only"` makes the mount read-only at the filesystem level (on self-hosted sandboxes it is enforced by the worker's `write`/`edit` tools and the upload path - see below); `"read_write"` allows the agent to create, edit, and delete files under it. A short description of each mount (name, path, `instructions`, access) is automatically injected into the system prompt so the agent knows the store exists without you having to mention it. Writes the agent makes under the mount are persisted back to the store and produce memory versions just like host-side `memories.update` calls. +**Self-hosted sandboxes: a synced local copy, not a live mount.** On a `self_hosted` environment the SDK worker (`EnvironmentWorker` - Python, TypeScript, Go; the `ant` CLI worker does not mount stores) downloads each attached store to the same `/mnt/memory//` path and reconciles it with the store on an interval, so writes are visible to other sessions only after sync, conflicts resolve in favor of the store, and `read_only` is enforced by the worker's tools rather than the filesystem (`bash` can still alter the local copy). Everything else - sync interval, per-session `secret`, host prep, troubleshooting - lives in `shared/managed-agents-self-hosted-sandboxes.md` § Memory stores. Not available on self-hosted environments on Claude Platform on AWS. + ## Manage memories directly (host-side) Use these for review workflows, correcting bad memories, or seeding stores out-of-band. ### List -Returns `Memory | MemoryPrefix` entries — a `MemoryPrefix` (`type: "memory_prefix"`, just a `path`) is a directory-like node when listing hierarchically. Use `path_prefix` to scope (include a trailing slash: `"/notes/"` matches `/notes/a.md` but not `/notes_backup/old.md`) and `depth` to bound the tree walk. Pass `view="full"` to include `content` in each item; the default `"basic"` returns metadata only. +Returns `Memory | MemoryPrefix` entries - a `MemoryPrefix` (`type: "memory_prefix"`, just a `path`) is a directory-like node when listing hierarchically. Use `path_prefix` to scope (include a trailing slash: `"/notes/"` matches `/notes/a.md` but not `/notes_backup/old.md`) and `depth` to bound the tree walk. Pass `view="full"` to include `content` in each item; the default `"basic"` returns metadata only. ```python for m in client.beta.memory_stores.memories.list(store.id, path_prefix="/"): @@ -126,7 +128,7 @@ client.beta.memory_stores.memories.update( ### Optimistic concurrency (precondition on `update`) -`memories.update` accepts a `precondition` so you can read → modify → write back without clobbering a concurrent writer. The only supported type is `content_sha256`. On mismatch the API returns `409` (`memory_precondition_failed_error`) — re-read and retry against fresh state. +`memories.update` accepts a `precondition` so you can read -> modify -> write back without clobbering a concurrent writer. The only supported type is `content_sha256`. On mismatch the API returns `409` (`memory_precondition_failed_error`) - re-read and retry against fresh state. ```python client.beta.memory_stores.memories.update( @@ -145,7 +147,7 @@ client.beta.memory_stores.memories.delete(mem.id, memory_store_id=store.id) Pass `expected_content_sha256` for a conditional delete. -## Audit and rollback — memory versions +## Audit and rollback - memory versions Every mutation creates an immutable `memver_...` snapshot. Versions accumulate for the lifetime of the parent memory; `memories.retrieve` always returns the current head, the version endpoints give you history. @@ -155,7 +157,7 @@ Every mutation creates an immutable `memver_...` snapshot. Versions accumulate f | `memories.update` changing `content`, `path`, or both (or an agent-side write to the mount) | `"modified"` | | `memories.delete` | `"deleted"` | -Each version also records `created_by` — an actor object with `type` ∈ `session_actor` / `api_actor` / `user_actor` — and, after redaction, `redacted_at` + `redacted_by`. +Each version also records `created_by` - an actor object with `type` in `session_actor` / `api_actor` / `user_actor` - and, after redaction, `redacted_at` + `redacted_by`. ### List versions @@ -185,7 +187,7 @@ client.beta.memory_stores.memory_versions.redact(version_id, memory_store_id=sto ## Endpoint reference -See `shared/managed-agents-api-reference.md` → Memory Stores / Memories / Memory Versions for the full HTTP method/path tables. Raw HTTP base path: +See `shared/managed-agents-api-reference.md` -> Memory Stores / Memories / Memory Versions for the full HTTP method/path tables. Raw HTTP base path: ``` POST /v1/memory_stores @@ -196,4 +198,4 @@ GET /v1/memory_stores/{memory_store_id}/memory_versions POST /v1/memory_stores/{memory_store_id}/memory_versions/{version_id}/redact ``` -For cURL examples and the CLI (`ant beta:memory-stores ...`), WebFetch the Memory URL in `shared/live-sources.md` → Managed Agents. +For cURL examples and the CLI (`ant beta:memory-stores ...`), WebFetch the Memory URL in `shared/live-sources.md` -> Managed Agents. diff --git a/skills/claude-api/shared/managed-agents-multiagent.md b/skills/claude-api/shared/managed-agents-multiagent.md index b4d322918..2c0d8fbc0 100644 --- a/skills/claude-api/shared/managed-agents-multiagent.md +++ b/skills/claude-api/shared/managed-agents-multiagent.md @@ -1,16 +1,16 @@ -# Managed Agents — Multiagent Sessions +# Managed Agents - Multiagent Sessions -A coordinator agent can delegate to other agents within one session. All agents **share the container and filesystem**; each runs in its own **thread** — a context-isolated event stream with its own conversation history, model, system prompt, tools, MCP servers, and skills (from that agent's own config). Threads are persistent: the coordinator can send a follow-up to a subagent it called earlier and that subagent retains its prior turns. +A coordinator agent can delegate to other agents within one session. All agents **share the container and filesystem**; each runs in its own **thread** - a context-isolated event stream with its own conversation history, model, system prompt, tools, MCP servers, and skills (from that agent's own config). Threads are persistent: the coordinator can send a follow-up to a subagent it called earlier and that subagent retains its prior turns. The SDK sets the `managed-agents-2026-04-01` beta header automatically on all `client.beta.{agents,sessions}.*` calls; no additional header is required for multiagent. --- -## When to use it — start with `self`, then add cheaper workers +## 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. +**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. +**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( @@ -25,7 +25,7 @@ agent = client.beta.agents.create( 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. +**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( @@ -50,7 +50,7 @@ lead = client.beta.agents.create( ) ``` -**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. +**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( @@ -81,10 +81,11 @@ lead = client.beta.agents.create( 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. +- **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). +- **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. +- **Web tool domain lists layer, never widen.** A roster agent's `web_search` / `web_fetch` calls are bound by its own `allowed_domains` / `blocked_domains`, by those of every agent that called it, and by the coordinator's current lists (allow-lists intersect, block-lists union). Keep each roster agent's allow-list inside the coordinator's - disjoint lists leave the tool present but every call fails `url_not_allowed`. See `shared/managed-agents-tools.md` § Web search & web fetch settings. +- **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`. @@ -92,7 +93,7 @@ The sections below are the reference for rosters, threads, events, and client-si ## 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. +`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( @@ -103,7 +104,7 @@ orchestrator = client.beta.agents.create( multiagent={ "type": "coordinator", "agents": [ - reviewer.id, # bare string — latest version + reviewer.id, # bare string - latest version {"type": "agent", "id": test_writer.id, "version": 4}, # pinned version {"type": "self"}, # the coordinator itself ], @@ -120,17 +121,17 @@ session = client.beta.sessions.create(agent=orchestrator.id, environment_id=env. | 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. +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. -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. +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. +**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. --- ## Threads -The session-level event stream is the **primary thread** — it shows the coordinator's trace plus a condensed view of subagent activity (thread status transitions and cross-thread messages, not every subagent tool call). Drill into a specific subagent via the per-thread endpoints: +The session-level event stream is the **primary thread** - it shows the coordinator's trace plus a condensed view of subagent activity (thread status transitions and cross-thread messages, not every subagent tool call). Drill into a specific subagent via the per-thread endpoints: | Operation | HTTP | SDK (`client.beta.sessions.threads.*`) | |---|---|---| @@ -140,9 +141,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` — 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). +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. +**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. --- @@ -152,9 +153,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 — 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_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 ended — completed its work and self-terminated (advisor consultation threads — see § Advisor), 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. | @@ -170,15 +171,15 @@ Each thread's stream accepts the same `event_deltas[]` parameter as the session- GET /v1/sessions/{sid}/threads/{tid}/stream?event_deltas%5B%5D=agent.message ``` -**Previews are thread-scoped.** A child's previews are delivered only on that child's stream and never cross-posted to the session-level stream, whose previews stay scoped to the primary thread. So watching a subagent live means opening its thread stream — the session stream will not show it, no matter what you pass. +**Previews are thread-scoped.** A child's previews are delivered only on that child's stream and never cross-posted to the session-level stream, whose previews stay scoped to the primary thread. So watching a subagent live means opening its thread stream - the session stream will not show it, no matter what you pass. -> ⚠️ **Only plain assistant text previews.** A subagent's *reply to its coordinator* rides `agent.thread_message_sent` and is never previewed. A worker that does nothing but report back therefore streams no deltas at all, even with a correct opt-in on the right thread. To get a live preview out of a subagent, its prompt has to make it write the answer as a plain assistant message in its own thread first, and only then report to the coordinator. Run one accumulator per connection, and exit the read loop on `session.thread_status_idle`. Opt-in, accumulate, and reconcile details: `shared/managed-agents-events.md` → Live previews. +> Warning: **Only plain assistant text previews.** A subagent's *reply to its coordinator* rides `agent.thread_message_sent` and is never previewed. A worker that does nothing but report back therefore streams no deltas at all, even with a correct opt-in on the right thread. To get a live preview out of a subagent, its prompt has to make it write the answer as a plain assistant message in its own thread first, and only then report to the coordinator. Run one accumulator per connection, and exit the read loop on `session.thread_status_idle`. Opt-in, accumulate, and reconcile details: `shared/managed-agents-events.md` -> Live previews. --- ## Advisor -An `{"type": "advisor", "model": ""}` 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. +An `{"type": "advisor", "model": ""}` 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( @@ -192,28 +193,28 @@ agent = client.beta.agents.create( ) ``` -(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`).) +(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.1, or Claude Mythos 5.1 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. +- **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`). - **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 +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." +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. +**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. +**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. +**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`. @@ -221,7 +222,7 @@ No `agent.tool_use` and no `agent.thread_message_sent` are emitted for a consult ## 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. +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. ```python for event_id in stop.event_ids: @@ -242,9 +243,9 @@ The same pattern applies to `user.custom_tool_result`. ## Interrupting and archiving threads -- **`user.interrupt` without `session_thread_id` interrupts every non-archived thread in the session, including the primary** — it is not a primary-only stop. Pass `session_thread_id` to target one thread. -- **Against a child thread blocked on `requires_action`**, the interrupt closes each pending tool call with an *error* tool result (`"Tool execution was interrupted before completion. Please retry."`) and re-emits `session.thread_status_idle` with `stop_reason: end_turn` directly — the model is not sampled. Against a thread already `idle`, the interrupt is a no-op. -- **Archive requires the thread to be idle, and `requires_action` counts as idle** — a thread parked on a pending tool call can be archived directly. Only a *running* thread must be interrupted first. +- **`user.interrupt` without `session_thread_id` interrupts every non-archived thread in the session, including the primary** - it is not a primary-only stop. Pass `session_thread_id` to target one thread. +- **Against a child thread blocked on `requires_action`**, the interrupt closes each pending tool call with an *error* tool result (`"Tool execution was interrupted before completion. Please retry."`) and re-emits `session.thread_status_idle` with `stop_reason: end_turn` directly - the model is not sampled. Against a thread already `idle`, the interrupt is a no-op - with one exception: a session on a self-hosted environment whose worker failed the claimed work item (e.g. a memory-store mount error) sits `idle`, and a `user.interrupt` re-queues that work so the next worker claim retries (`shared/managed-agents-self-hosted-sandboxes.md` § Memory stores -> Troubleshooting). +- **Archive requires the thread to be idle, and `requires_action` counts as idle** - a thread parked on a pending tool call can be archived directly. Only a *running* thread must be interrupted first. --- @@ -252,6 +253,6 @@ The same pattern applies to `user.custom_tool_result`. - **Don't put the roster on `sessions.create()` or in `tools[]`.** `multiagent` is a top-level agent field; update the coordinator, then start a session that references it. - **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. +- **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/multiagent-orchestration.md` (see `shared/live-sources.md`). diff --git a/skills/claude-api/shared/managed-agents-onboarding.md b/skills/claude-api/shared/managed-agents-onboarding.md index f4769a133..9b5f99464 100644 --- a/skills/claude-api/shared/managed-agents-onboarding.md +++ b/skills/claude-api/shared/managed-agents-onboarding.md @@ -1,32 +1,32 @@ -# Managed Agents — Onboarding Flow +# Managed Agents - Onboarding Flow -> **Invoked via `/claude-api managed-agents-onboard`?** You're in the right place. Run the interview below — don't summarize it back to the user, ask the questions. +> **Invoked via `/claude-api managed-agents-onboard`?** You're in the right place. Run the interview below - don't summarize it back to the user, ask the questions. -Claude Managed Agents is a hosted agent: Anthropic runs the agent loop and provisions a sandboxed container per session where the agent's tools execute (or your own worker, with a `self_hosted` environment — see `shared/managed-agents-self-hosted-sandboxes.md`). You supply an **agent config** (tools, skills, model, system prompt — reusable, versioned) and an **environment config** (the sandbox — reusable across agents). Each run is a **session**. +Claude Managed Agents is a hosted agent: Anthropic runs the agent loop and provisions a sandboxed container per session where the agent's tools execute (or your own worker, with a `self_hosted` environment - see `shared/managed-agents-self-hosted-sandboxes.md`). You supply an **agent config** (tools, skills, model, system prompt - reusable, versioned) and an **environment config** (the sandbox - reusable across agents). Each run is a **session**. -The flow is four beats — **describe → agent → environment → session** — the same arc as the Console quickstart, and the same philosophy: **value before credentials**. The user goes from idea to a runnable session before any auth ask; each credential is *flagged* at the moment the design makes it relevant (§2) and *collected* once, at session setup (§4), where it binds (`sessions.create()`) and gets exercised (smoke-test). Read `shared/managed-agents-core.md` alongside this — it has full detail for each knob; this doc is the interview script. +The flow is four beats - **describe -> agent -> environment -> session** - the same arc as the Console quickstart, and the same philosophy: **value before credentials**. The user goes from idea to a runnable session before any auth ask; each credential is *flagged* at the moment the design makes it relevant (§2) and *collected* once, at session setup (§4), where it binds (`sessions.create()`) and gets exercised (smoke-test). Read `shared/managed-agents-core.md` alongside this - it has full detail for each knob; this doc is the interview script. --- ## 1. Describe the task -**Open with a one-breath signpost and a single open prompt — don't guess, don't questionnaire.** In your own words: +**Open with a one-breath signpost and a single open prompt - don't guess, don't questionnaire.** In your own words: -> Managed Agents is hosted — Anthropic runs the agent loop, the sandbox, and the infrastructure; you just define the agent. We'll do this in three moves: the agent, the environment it runs in, then a live test session. So: describe the agent you want — what should it do, and what kicks it off (a person, an event, a schedule)? +> Managed Agents is hosted - Anthropic runs the agent loop, the sandbox, and the infrastructure; you just define the agent. We'll do this in three moves: the agent, the environment it runs in, then a live test session. So: describe the agent you want - what should it do, and what kicks it off (a person, an event, a schedule)? Let them answer in full before configuring anything. -## 2. Configure the agent — propose, don't interrogate +## 2. Configure the agent - propose, don't interrogate -Their description does the interview's work. Draft the agent config from it and **present it as a proposal with your suggestions inline** — the user reacts to a concrete config instead of answering a question list. At most one batched follow-up for true gaps. Suggest where the description gives you an opening: +Their description does the interview's work. Draft the agent config from it and **present it as a proposal with your suggestions inline** - the user reacts to a concrete config instead of answering a question list. At most one batched follow-up for true gaps. Suggest where the description gives you an opening: -- **Tools** — enable the full prebuilt toolset by default (`agent_toolset_20260401`: `bash`, `read`, `write`, `edit`, `glob`, `grep`, `web_fetch`, `web_search`). **Suggest MCP servers** for any third-party service the job names (GitHub, Linear, Slack, …) — and flag the credential each one implies as you suggest it ("Linear MCP → you'll need a Linear API token at kickoff"), so §4's auth step is a formality, not a surprise. Collection itself waits for §4. Custom tools only if the user's own app must answer calls (name, description, input schema — their handler code is theirs; don't generate it). -- **Skills** — **suggest** prebuilt `xlsx`/`docx`/`pptx`/`pdf` when the job produces those artifacts; custom by `skill_id` (max 20 total per agent, prebuilt + custom combined). -- **Outcome** — if the description implies checkable "done" criteria (or you can elicit them in the follow-up: not "a good report" but "a CSV with a numeric `price` column per SKU"), **suggest an Outcome kickoff** — the harness grades and iterates against a rubric (`shared/managed-agents-outcomes.md`). -- **On-hand resources** — repos on disk (`github_repository`: URL, optional `mount_path`/`checkout`; token comes in §4), files to seed (Files API upload → `{type: "file", file_id, mount_path}`; read-only), if the job references them. -- **Model** — default `claude-opus-5`; `claude-fable-5` for the hardest long-horizon work (`shared/model-migration.md` → Migrating to Claude Fable 5). +- **Tools** - enable the full prebuilt toolset by default (`agent_toolset_20260401`: `bash`, `read`, `write`, `edit`, `glob`, `grep`, `web_fetch`, `web_search`). **Suggest MCP servers** for any third-party service the job names (GitHub, Linear, Slack, ...) - and flag the credential each one implies as you suggest it ("Linear MCP -> you'll need a Linear API token at kickoff"), so §4's auth step is a formality, not a surprise. Collection itself waits for §4. Custom tools only if the user's own app must answer calls (name, description, input schema - their handler code is theirs; don't generate it). +- **Skills** - **suggest** prebuilt `xlsx`/`docx`/`pptx`/`pdf` when the job produces those artifacts; custom by `skill_id` (max 20 total per agent, prebuilt + custom combined). +- **Outcome** - if the description implies checkable "done" criteria (or you can elicit them in the follow-up: not "a good report" but "a CSV with a numeric `price` column per SKU"), **suggest an Outcome kickoff** - the harness grades and iterates against a rubric (`shared/managed-agents-outcomes.md`). +- **On-hand resources** - repos on disk (`github_repository`: URL, optional `mount_path`/`checkout`; token comes in §4), files to seed (Files API upload -> `{type: "file", file_id, mount_path}`; read-only), if the job references them. +- **Model** - default `claude-opus-5`; `claude-fable-5-1` for the hardest long-horizon work (`shared/model-migration.md` -> Migrating to Claude Fable 5.1). -> ‼️ **PR creation needs the GitHub MCP server too** — a `github_repository` mount is filesystem-only. Edit in the mount → push branch via `bash` → open the PR via the MCP `create_pull_request` tool. +> Important: **PR creation needs the GitHub MCP server too** - a `github_repository` mount is filesystem-only. Edit in the mount -> push branch via `bash` -> open the PR via the MCP `create_pull_request` tool. Full detail per knob: `shared/managed-agents-tools.md` (toolset, MCP, custom tools, skills), `shared/managed-agents-environments.md` (repos, files). @@ -34,28 +34,28 @@ Full detail per knob: `shared/managed-agents-tools.md` (toolset, MCP, custom too Usually zero or one question: -- **Reuse or create?** Environments are shared across agents — check for an existing one first. -- **Networking** — default unrestricted egress. Switch to `limited` only if the user wants egress control — then set `allow_mcp_servers: true` or list every MCP server domain in `allowed_hosts`, or those tools fail silently. -- **Suggest `self_hosted`** when the signals are there: tools must run on their own infra, secrets can't leave it, or they need binaries/data the cloud container won't have (`shared/managed-agents-self-hosted-sandboxes.md`; not available on Claude Platform on AWS). Otherwise `cloud` — don't raise it unprompted for simple jobs. +- **Reuse or create?** Environments are shared across agents - check for an existing one first. +- **Networking** - default unrestricted egress. Switch to `limited` only if the user wants egress control - then set `allow_mcp_servers: true` or list every MCP server domain in `allowed_hosts`, or those tools fail silently. +- **Suggest `self_hosted`** when the signals are there: tools must run on their own infra, secrets can't leave it, or they need binaries/data the cloud container won't have (`shared/managed-agents-self-hosted-sandboxes.md`; on Claude Platform on AWS the worker authenticates with IAM instead of an environment key and sessions there can't attach memory stores). Otherwise `cloud` - don't raise it unprompted for simple jobs. -## 4. Session — auth, then test run +## 4. Session - auth, then test run -**Auth happens here — collect the credentials flagged in §2, now that the config is settled:** a vault (existing or `vaults.create()`) + `vaults.credentials.create()` for each MCP server declared in §2, `environment_variable` credentials for API keys the job uses (substituted at egress; the sandbox sees a placeholder), and the `authorization_token` for each repo mount. Credentials are write-only; MCP credentials match servers by URL and auto-refresh. See `shared/managed-agents-tools.md` → Vaults. +**Auth happens here - collect the credentials flagged in §2, now that the config is settled:** a vault (existing or `vaults.create()`) + `vaults.credentials.create()` for each MCP server declared in §2, `environment_variable` credentials for API keys the job uses (substituted at egress; the sandbox sees a placeholder), and the `authorization_token` for each repo mount. Credentials are write-only; MCP credentials match servers by URL and auto-refresh. See `shared/managed-agents-tools.md` -> Vaults. -**Silent viability gate — run this yourself before emitting anything; surface only the gaps.** Walk the job clause by clause: every verb maps to an enabled tool or MCP server ("open a PR" → GitHub MCP, not just the mount); every MCP server and repo mount has its credential from the auth step; every external host is reachable under the networking choice; every file/repo/dataset the job references is mounted; "done" is checkable. If something's missing, say so and resolve it — don't emit a config you already know is under-resourced. +**Silent viability gate - run this yourself before emitting anything; surface only the gaps.** Walk the job clause by clause: every verb maps to an enabled tool or MCP server ("open a PR" -> GitHub MCP, not just the mount); every MCP server and repo mount has its credential from the auth step; every external host is reachable under the networking choice; every file/repo/dataset the job references is mounted; "done" is checkable. If something's missing, say so and resolve it - don't emit a config you already know is under-resourced. -**Kickoff — pick one, never both:** -- `user.message` — conversational. -- `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`. +**Kickoff - pick one, never both:** +- `user.message` - conversational. +- `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 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, ...})`). +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 +## 5. Integrate - emit the code -Go straight from the last answer to the code — no preamble, no lecture about setup-vs-runtime; the two-block structure shows it. Generate **two clearly-separated blocks**: +Go straight from the last answer to the code - no preamble, no lecture about setup-vs-runtime; the two-block structure shows it. Generate **two clearly-separated blocks**: -**Block 1 — Setup (run once, store the IDs).** Prefer **YAML files + `ant` CLI** — agents and environments are version-controlled definitions users should check in and apply from CI: +**Block 1 - Setup (run once, store the IDs).** Prefer **YAML files + `ant` CLI** - agents and environments are version-controlled definitions users should check in and apply from CI: 1. `.agent.yaml` (flat: `name`, `model`, `system`, `tools`, `mcp_servers`, `skills`) and `.environment.yaml` 2. ```sh @@ -64,19 +64,19 @@ Go straight from the last answer to the code — no preamble, no lecture about s # CI sync: ant beta:agents update --agent-id "$AGENT_ID" --version N < .agent.yaml ``` -SDK fallback if the user asks — and **required on Claude Platform on AWS**, where auth is SigV4 and the `ant` CLI has no SigV4 mode (use the platform client from `shared/claude-platform-on-aws.md`): label it `# ONE-TIME SETUP — run once, save the IDs` and call `environments.create()` → `agents.create()`. +SDK fallback if the user asks - and **required on Claude Platform on AWS**, where auth is SigV4 and the `ant` CLI has no SigV4 mode (use the platform client from `shared/claude-platform-on-aws.md`): label it `# ONE-TIME SETUP - run once, save the IDs` and call `environments.create()` -> `agents.create()`. -> ⚠️ **Deployments are newer than the rest of the MA surface.** Before emitting `ant beta:deployments …` or `client.beta.deployments` / `client.beta.deployment_runs` calls, verify the user's installed CLI/SDK exposes them (`ant beta:deployments --help`; `hasattr(client.beta, "deployments")`). If not, emit raw HTTP against `POST /v1/deployments` with the `managed-agents-2026-04-01` beta header (plus `oauth-2025-04-20` when authenticating with a Bearer token from `ant auth print-credentials`), and leave an upgrade note marking what simplifies to SDK calls. +> Warning: **Deployments are newer than the rest of the MA surface.** Before emitting `ant beta:deployments ...` or `client.beta.deployments` / `client.beta.deployment_runs` calls, verify the user's installed CLI/SDK exposes them (`ant beta:deployments --help`; `hasattr(client.beta, "deployments")`). If not, emit raw HTTP against `POST /v1/deployments` with the `managed-agents-2026-04-01` beta header (plus `oauth-2025-04-20` when authenticating with a Bearer token from `ant auth print-credentials`), and leave an upgrade note marking what simplifies to SDK calls. -**Scheduled shape? The deployment is setup, not runtime.** Create it in Block 1, after the agent/environment IDs exist (`deployments.create()` with `schedule` + `initial_events`). Block 2 is then **not** a session loop — there is no per-run kickoff to send. Emit instead: a manual-run trigger (`POST /v1/deployments/{id}/run`) so the user can test now rather than wait for the first firing — the manual run doubles as the smoke test — plus a fetch helper (latest `deployment_runs` entry → `session_id` → Console URL + `files.list(scope_id=session_id)` for the artifacts). +**Scheduled shape? The deployment is setup, not runtime.** Create it in Block 1, after the agent/environment IDs exist (`deployments.create()` with `schedule` + `initial_events`). Block 2 is then **not** a session loop - there is no per-run kickoff to send. Emit instead: a manual-run trigger (`POST /v1/deployments/{id}/run`) so the user can test now rather than wait for the first firing - the manual run doubles as the smoke test - plus a fetch helper (latest `deployment_runs` entry -> `session_id` -> Console URL + `files.list(scope_id=session_id)` for the artifacts). -**Block 2 — Runtime (every invocation; conversational and Outcome shapes).** SDK code in the detected language (Python/TS/cURL — SKILL.md → Language Detection); don't emit shell loops here: +**Block 2 - Runtime (every invocation; conversational and Outcome shapes).** SDK code in the detected language (Python/TS/cURL - SKILL.md -> Language Detection); don't emit shell loops here: 1. Load `agent_id` + `env_id` from config/env 2. `sessions.create(agent=AGENT_ID, environment_id=ENV_ID, resources=[...], vault_ids=[...])`, then print the Console URL so the user can watch live: `https://platform.claude.com/workspaces/default/sessions/{session.id}` (swap `default` for their workspace slug) -3. **Smoke-test when the job depends on MCP servers, credentials, or locked-down hosts** — those failures don't surface at `sessions.create()`, only on first use. One cheap probe turn ("Confirm you can reach and list 1–2 items; don't start the task"), verify, then send the real kickoff. Skip when there are no external dependencies. -4. Open stream → send the §4 kickoff → loop with the terminal gate from §4. +3. **Smoke-test when the job depends on MCP servers, credentials, or locked-down hosts** - those failures don't surface at `sessions.create()`, only on first use. One cheap probe turn ("Confirm you can reach and list 1-2 items; don't start the task"), verify, then send the real kickoff. Skip when there are no external dependencies. +4. Open stream -> send the §4 kickoff -> loop with the terminal gate from §4. -> ⚠️ **Never emit `agents.create()` and `sessions.create()` in the same unguarded block** — that teaches creating a new agent per run, the #1 anti-pattern. Single-script requests: wrap creation in `if not os.getenv("AGENT_ID"):`. +> Warning: **Never emit `agents.create()` and `sessions.create()` in the same unguarded block** - that teaches creating a new agent per run, the #1 anti-pattern. Single-script requests: wrap creation in `if not os.getenv("AGENT_ID"):`. Pull exact syntax from `{lang}/managed-agents/README.md` for your detected language (cURL and C#: use `curl/managed-agents.md` as the wire-level reference). Don't invent field names. diff --git a/skills/claude-api/shared/managed-agents-outcomes.md b/skills/claude-api/shared/managed-agents-outcomes.md index dd81a948f..e06b4fd43 100644 --- a/skills/claude-api/shared/managed-agents-outcomes.md +++ b/skills/claude-api/shared/managed-agents-outcomes.md @@ -1,6 +1,6 @@ -# Managed Agents — Outcomes +# Managed Agents - Outcomes -An **outcome** elevates a session from *conversation* to *work*: you state what "done" looks like, and the harness runs an iterate → grade → revise loop until the artifact meets the rubric, hits `max_iterations`, or is interrupted. A separate **grader** (independent context window) scores each iteration against your rubric and feeds per-criterion gaps back to the agent. +An **outcome** elevates a session from *conversation* to *work*: you state what "done" looks like, and the harness runs an iterate -> grade -> revise loop until the artifact meets the rubric, hits `max_iterations`, or is interrupted. A separate **grader** (independent context window) scores each iteration against your rubric and feeds per-criterion gaps back to the agent. The SDK sets the `managed-agents-2026-04-01` beta header automatically on all `client.beta.sessions.*` calls; no additional header is required for outcomes. @@ -8,9 +8,9 @@ The SDK sets the `managed-agents-2026-04-01` beta header automatically on all `c ## The `user.define_outcome` event -Outcomes are not a field on `sessions.create()`. You create a normal session, then send a `user.define_outcome` event. The agent starts working on receipt — **do not also send a `user.message`** to kick it off. +Outcomes are not a field on `sessions.create()`. You create a normal session, then send a `user.define_outcome` event. The agent starts working on receipt - **do not also send a `user.message`** to kick it off. -You can collapse both calls into one by passing a single `user.define_outcome` in the session's `initial_events` array — same event, same rules, one round trip (see `shared/managed-agents-core.md` → Seeding a session with `initial_events`). More than one `user.define_outcome` in that array, or one without a `rubric`, rejects the whole create with a 400. +You can collapse both calls into one by passing a single `user.define_outcome` in the session's `initial_events` array - same event, same rules, one round trip (see `shared/managed-agents-core.md` -> Seeding a session with `initial_events`). More than one `user.define_outcome` in that array, or one without a `rubric`, rejects the whole create with a 400. ```python session = client.beta.sessions.create( @@ -36,13 +36,13 @@ client.beta.sessions.events.send( | Field | Type | Notes | |---|---|---| | `type` | `"user.define_outcome"` | | -| `description` | string | The task. This is what the agent works toward — no separate `user.message` needed. | +| `description` | string | The task. This is what the agent works toward - no separate `user.message` needed. | | `rubric` | `{type: "text", content}` \| `{type: "file", file_id}` | **Required.** Markdown with explicit, independently gradeable criteria. Upload once via `client.beta.files.upload(...)` (beta `files-api-2025-04-14`) to reuse across sessions. | | `max_iterations` | int | Optional. Default **3**, max **20**. | The event is echoed back on the stream with a server-assigned `outcome_id` and `processed_at`. -> **Writing rubrics.** Use explicit, gradeable criteria ("CSV has a numeric `price` column"), not vibes ("data looks good") — the grader scores each criterion independently, so vague criteria produce noisy loops. If you don't have a rubric, have Claude analyze a known-good artifact and turn that analysis into one. +> **Writing rubrics.** Use explicit, gradeable criteria ("CSV has a numeric `price` column"), not vibes ("data looks good") - the grader scores each criterion independently, so vague criteria produce noisy loops. If you don't have a rubric, have Claude analyze a known-good artifact and turn that analysis into one. --- @@ -53,18 +53,18 @@ These appear on the standard event stream (`sessions.events.stream` / `.list`) a | Event | Payload highlights | Meaning | |---|---|---| | `span.outcome_evaluation_start` | `outcome_id`, `iteration` (0-indexed) | Grader began scoring iteration *N*. | -| `span.outcome_evaluation_ongoing` | `outcome_id` | Heartbeat while the grader runs. Grader reasoning is opaque — you see *that* it's working, not *what* it's thinking. | +| `span.outcome_evaluation_ongoing` | `outcome_id` | Heartbeat while the grader runs. Grader reasoning is opaque - you see *that* it's working, not *what* it's thinking. | | `span.outcome_evaluation_end` | `outcome_evaluation_start_id`, `outcome_id`, `iteration`, `result`, `explanation`, `usage` | Grader finished one iteration. `result` drives what happens next (table below). | ### `span.outcome_evaluation_end.result` | `result` | Next | |---|---| -| `satisfied` | Session → `idle`. Terminal for this outcome. | +| `satisfied` | Session -> `idle`. Terminal for this outcome. | | `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. (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.) | +| `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. (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 { @@ -84,7 +84,7 @@ These appear on the standard event stream (`sessions.events.stream` / `.list`) a ## Checking status & retrieving deliverables -**Status** — either watch the stream for `span.outcome_evaluation_end`, or poll the session and read `outcome_evaluations`: +**Status** - either watch the stream for `span.outcome_evaluation_end`, or poll the session and read `outcome_evaluations`: ```python session = client.beta.sessions.retrieve(session.id) @@ -92,17 +92,17 @@ for ev in session.outcome_evaluations: print(f"{ev.outcome_id}: {ev.result}") # outc_01a...: satisfied ``` -**Deliverables** — the agent writes to `/mnt/session/outputs/`. Once idle, fetch via the Files API with `scope_id=session.id`. This is the same session-outputs mechanism documented in `shared/managed-agents-environments.md` → Session outputs (including the dual-beta-header requirement on `files.list`). +**Deliverables** - the agent writes to `/mnt/session/outputs/`. Once idle, fetch via the Files API with `scope_id=session.id`. This is the same session-outputs mechanism documented in `shared/managed-agents-environments.md` -> Session outputs (including the dual-beta-header requirement on `files.list`). --- ## 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. (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. +- **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. For the raw HTTP shapes and per-language SDK bindings beyond Python, WebFetch `https://platform.claude.com/docs/en/managed-agents/define-outcomes.md` (see `shared/live-sources.md`). diff --git a/skills/claude-api/shared/managed-agents-overview.md b/skills/claude-api/shared/managed-agents-overview.md index 3fad97947..7df784412 100644 --- a/skills/claude-api/shared/managed-agents-overview.md +++ b/skills/claude-api/shared/managed-agents-overview.md @@ -1,23 +1,23 @@ -# Managed Agents — Overview +# Managed Agents - Overview -Managed Agents provisions a container per session as the agent's workspace. The agent loop runs on Anthropic's orchestration layer; the container is where the agent's *tools* execute — bash commands, file operations, code. You create a persisted **Agent** config (model, system prompt, tools, MCP servers, skills), then start **Sessions** that reference it. The session streams events back to you; you send user messages and tool results in. +Managed Agents provisions a container per session as the agent's workspace. The agent loop runs on Anthropic's orchestration layer; the container is where the agent's *tools* execute - bash commands, file operations, code. You create a persisted **Agent** config (model, system prompt, tools, MCP servers, skills), then start **Sessions** that reference it. The session streams events back to you; you send user messages and tool results in. -## ⚠️ THE MANDATORY FLOW: Agent (once) → Session (every run) +## Warning: THE MANDATORY FLOW: Agent (once) -> Session (every run) -**Why agents are separate objects: versioning.** An agent is a persisted, versioned config — every update creates a new immutable version, and sessions pin to a version at creation time. This lets you iterate on the agent (tweak the prompt, add a tool) without breaking sessions already running, roll back if a change regresses, and A/B test versions side-by-side. None of that works if you `agents.create()` fresh on every run. +**Why agents are separate objects: versioning.** An agent is a persisted, versioned config - every update creates a new immutable version, and sessions pin to a version at creation time. This lets you iterate on the agent (tweak the prompt, add a tool) without breaking sessions already running, roll back if a change regresses, and A/B test versions side-by-side. None of that works if you `agents.create()` fresh on every run. Every session references a pre-created `/v1/agents` object. Create the agent once, store the ID, and reuse it across runs. | Step | Call | Frequency | |---|---|---| -| 1 | `POST /v1/agents` — `model`, `system`, `tools`, `mcp_servers`, `skills` live here | **ONCE.** Store `agent.id` **and** `agent.version`. | -| 2 | `POST /v1/sessions` — `agent: "agent_abc123"` or `{type: "agent", id, version}` | **Every run.** String shorthand uses latest version. | +| 1 | `POST /v1/agents` - `model`, `system`, `tools`, `mcp_servers`, `skills` live here | **ONCE.** Store `agent.id` **and** `agent.version`. | +| 2 | `POST /v1/sessions` - `agent: "agent_abc123"` or `{type: "agent", id, version}` | **Every run.** String shorthand uses latest version. | -If you're about to write `sessions.create()` with `model`, `system`, or `tools` on the session body — **stop**. Those fields live on `agents.create()`. The session takes a *pointer* only. +If you're about to write `sessions.create()` with `model`, `system`, or `tools` on the session body - **stop**. Those fields live on `agents.create()`. The session takes a *pointer* only. -**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()`. +**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` 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. +**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 @@ -29,47 +29,49 @@ Managed Agents is in beta. The SDK sets required beta headers automatically: | `skills-2025-10-02` | Skills API (for managing custom skill definitions) | | `files-api-2025-04-14` | Files API for file uploads | -**Which beta header goes where:** The SDK sets `managed-agents-2026-04-01` automatically on `client.beta.{agents,environments,sessions,vaults,memory_stores,deployments,deployment_runs}.*` calls, and `files-api-2025-04-14` / `skills-2025-10-02` automatically on `client.beta.files.*` / `client.beta.skills.*` calls. You do NOT need to add the Skills or Files beta header when calling Managed Agents endpoints. On raw HTTP the Managed Agents header **grants Files API access on its own**, so uploading a file for use as a session resource does not need `files-api-2025-04-14` alongside it. (Direct Skills API calls over cURL do still need `skills-2025-10-02`; the `ant` CLI and the SDKs send it for you.) **Exception — session-scoped file listing:** `client.beta.files.list({scope_id: session.id})` is a Files endpoint that takes a Managed Agents parameter, so it needs **both** headers. Pass `betas: ["managed-agents-2026-04-01"]` explicitly on that call (the SDK adds the Files header; you add the Managed Agents one). See `shared/managed-agents-environments.md` → Session outputs. +**Which beta header goes where:** The SDK sets `managed-agents-2026-04-01` automatically on `client.beta.{agents,environments,sessions,vaults,memory_stores,deployments,deployment_runs}.*` calls, and `files-api-2025-04-14` / `skills-2025-10-02` automatically on `client.beta.files.*` / `client.beta.skills.*` calls. You do NOT need to add the Skills or Files beta header when calling Managed Agents endpoints. On raw HTTP the Managed Agents header **grants Files API access on its own**, so uploading a file for use as a session resource does not need `files-api-2025-04-14` alongside it. (Direct Skills API calls over cURL do still need `skills-2025-10-02`; the `ant` CLI and the SDKs send it for you.) **Exception - session-scoped file listing:** `client.beta.files.list({scope_id: session.id})` is a Files endpoint that takes a Managed Agents parameter, so it needs **both** headers. Pass `betas: ["managed-agents-2026-04-01"]` explicitly on that call (the SDK adds the Files header; you add the Managed Agents one). See `shared/managed-agents-environments.md` -> Session outputs. ## Reading Guide | User wants to... | Read these files | | -------------------------------------- | ------------------------------------------------------- | -| **Get started from scratch / "help me set up an agent"** | `shared/managed-agents-onboarding.md` — guided interview (WHERE→WHO→WHAT→WATCH), then emit code | +| **Get started from scratch / "help me set up an agent"** | `shared/managed-agents-onboarding.md` - guided interview (WHERE->WHO->WHAT->WATCH), then emit code | | Understand how the API works | `shared/managed-agents-core.md` | | See the full endpoint reference | `shared/managed-agents-api-reference.md` | | **Create an agent** (required first step) | `shared/managed-agents-core.md` (Agents section) + language file | -| Update/version an agent | `shared/managed-agents-core.md` (Agents → Versioning) — update, don't re-create | +| Update/version an agent | `shared/managed-agents-core.md` (Agents -> Versioning) - update, don't re-create | | Create a session | `shared/managed-agents-core.md` + `{lang}/managed-agents/README.md` (cURL/C#: `curl/managed-agents.md`) | | Configure tools and permissions | `shared/managed-agents-tools.md` | +| Restrict which sites `web_search` / `web_fetch` can reach; localize search; cap fetched content | `shared/managed-agents-tools.md` (§ Web search & web fetch settings) - `allowed_domains` / `blocked_domains` / `user_location` / `max_content_tokens` on the toolset `configs` entry; **not** the environment's `networking` | | Set up MCP servers | `shared/managed-agents-tools.md` (MCP Servers section) | | Stream events / handle tool_use | `shared/managed-agents-events.md` + language file | -| Get notified of session state changes via webhook (no polling) | `shared/managed-agents-webhooks.md` — Console-registered endpoint, HMAC verify, thin payload + fetch | -| Define an outcome / rubric-graded iterate loop | `shared/managed-agents-outcomes.md` — `user.define_outcome` event, grader, `span.outcome_evaluation_*` events | -| Coordinate multiple agents / subagents / threads | `shared/managed-agents-multiagent.md` — `multiagent: {type: "coordinator", agents: [...]}` on the agent, session threads, cross-posted tool confirmations | +| Get notified of session state changes via webhook (no polling) | `shared/managed-agents-webhooks.md` - Console-registered endpoint, HMAC verify, thin payload + fetch | +| Define an outcome / rubric-graded iterate loop | `shared/managed-agents-outcomes.md` - `user.define_outcome` event, grader, `span.outcome_evaluation_*` events | +| Coordinate multiple agents / subagents / threads | `shared/managed-agents-multiagent.md` - `multiagent: {type: "coordinator", agents: [...]}` on the agent, session threads, cross-posted tool confirmations | | Set up environments | `shared/managed-agents-environments.md` + language file | -| Run tool execution in your own infra / VPC (self-hosted sandbox) | `shared/managed-agents-self-hosted-sandboxes.md` — `config:{type:"self_hosted"}`, `ANTHROPIC_ENVIRONMENT_KEY`, `EnvironmentWorker.run()` / `ant beta:worker poll` | +| Run tool execution in your own infra / VPC (self-hosted sandbox) | `shared/managed-agents-self-hosted-sandboxes.md` - `config:{type:"self_hosted"}`, `ANTHROPIC_ENVIRONMENT_KEY`, `EnvironmentWorker.run()` / `ant beta:worker poll` | | Upload files / attach repos | `shared/managed-agents-environments.md` (Resources) | -| Give agents persistent memory across sessions | `shared/managed-agents-memory.md` — memory stores, `memory_store` session resource, preconditions, versions/redact | -| Define agents/environments as version-controlled YAML; drive the API from the shell | `shared/anthropic-cli.md` — `ant beta:agents create < agent.yaml`, `--transform`, `@file` inlining | -| 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 | +| Give agents persistent memory across sessions | `shared/managed-agents-memory.md` - memory stores, `memory_store` session resource, preconditions, versions/redact. On self-hosted sandboxes: `shared/managed-agents-self-hosted-sandboxes.md` § Memory stores (SDK worker syncs a local copy) | +| Inspect a session without code (transcript, per-tool stats, cost, threads) | `shared/managed-agents-events.md` - Console session viewer note; deep link `?event={event_id}` | +| Define agents/environments as version-controlled YAML; drive the API from the shell | `shared/anthropic-cli.md` - `ant beta:agents create < agent.yaml`, `--transform`, `@file` inlining | +| 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 -- **Agent FIRST, then session — NO EXCEPTIONS** — the session's `agent` field accepts **only** a string ID or `{type: "agent", id, version}`. `model`, `system`, `tools`, `mcp_servers`, `skills` are **top-level fields on `POST /v1/agents`**, never on `sessions.create()`. If the user hasn't created an agent, that is step zero of every example. -- **Agent ONCE, not every run** — `agents.create()` is a setup step. Store the returned `agent_id` and reuse it; don't call `agents.create()` at the top of your hot path. If the agent's config needs to change, `POST /v1/agents/{id}` — each update creates a new version, and sessions can pin to a specific version for reproducibility. -- **MCP auth goes through vaults** — the agent's `mcp_servers` array declares `{type, name, url}` only (no auth). Credentials live in vaults (`client.beta.vaults.credentials.create`) and attach to sessions via `vault_ids`. Anthropic auto-refreshes OAuth tokens using the stored refresh token. Vaults also hold `environment_variable` credentials for non-MCP services (CLIs, SDKs, direct API calls) — substituted at egress, never visible in the sandbox. -- **Reconcile resources before the first run** — a session with a clear ask but a missing tool, credential, data mount, or context will discover the gap mid-run, then flail and give up. Before creating the session, check that every action in the task maps to a configured tool/MCP server, every MCP server has a vault credential, and every referenced file/host is mounted/reachable. When helping a user set one up, run the reconciliation in `shared/managed-agents-onboarding.md` → §3 Pre-flight viability check. -- **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. 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**. +- **Agent FIRST, then session - NO EXCEPTIONS** - the session's `agent` field accepts **only** a string ID or `{type: "agent", id, version}`. `model`, `system`, `tools`, `mcp_servers`, `skills` are **top-level fields on `POST /v1/agents`**, never on `sessions.create()`. If the user hasn't created an agent, that is step zero of every example. +- **Agent ONCE, not every run** - `agents.create()` is a setup step. Store the returned `agent_id` and reuse it; don't call `agents.create()` at the top of your hot path. If the agent's config needs to change, `POST /v1/agents/{id}` - each update creates a new version, and sessions can pin to a specific version for reproducibility. +- **MCP auth goes through vaults** - the agent's `mcp_servers` array declares `{type, name, url}` only (no auth). Credentials live in vaults (`client.beta.vaults.credentials.create`) and attach to sessions via `vault_ids`. Anthropic auto-refreshes OAuth tokens using the stored refresh token. Vaults also hold `environment_variable` credentials for non-MCP services (CLIs, SDKs, direct API calls) - substituted at egress, never visible in the sandbox. +- **Reconcile resources before the first run** - a session with a clear ask but a missing tool, credential, data mount, or context will discover the gap mid-run, then flail and give up. Before creating the session, check that every action in the task maps to a configured tool/MCP server, every MCP server has a vault credential, and every referenced file/host is mounted/reachable. When helping a user set one up, run the reconciliation in `shared/managed-agents-onboarding.md` -> §3 Pre-flight viability check. +- **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. 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**. diff --git a/skills/claude-api/shared/managed-agents-scheduled-deployments.md b/skills/claude-api/shared/managed-agents-scheduled-deployments.md index 8256546c7..9b42736fe 100644 --- a/skills/claude-api/shared/managed-agents-scheduled-deployments.md +++ b/skills/claude-api/shared/managed-agents-scheduled-deployments.md @@ -1,6 +1,6 @@ -# Managed Agents — Scheduled Deployments +# Managed Agents - Scheduled Deployments -A **scheduled deployment** runs an agent on a recurring cron schedule — each firing creates a session autonomously. Use it for predictable-cadence work: nightly triage, weekly compliance scans, hourly monitors. +A **scheduled deployment** runs an agent on a recurring cron schedule - each firing creates a session autonomously. Use it for predictable-cadence work: nightly triage, weekly compliance scans, hourly monitors. Requires the `managed-agents-2026-04-01` beta header (the SDK sets it automatically for `client.beta.deployments.*` / `client.beta.deployment_runs.*` calls). @@ -8,8 +8,8 @@ Requires the `managed-agents-2026-04-01` beta header (the SDK sets it automatica A deployment bundles everything a session needs (agent, environment, optional files / GitHub / memory stores / vaults) plus a `schedule` and the `initial_events` that kick off each run: -- `agent` and `environment_id` are required — same shapes as `sessions.create` (see `shared/managed-agents-core.md`). -- `initial_events` must contain at least one starting event — a `user.message` **or** a `user.define_outcome`. (A deployment's `initial_events` also accepts `system.message`, which a session's does not.) +- `agent` and `environment_id` are required - same shapes as `sessions.create` (see `shared/managed-agents-core.md`). A deployment targeting a **self-hosted** environment can attach `memory_store` resources (SDK worker required - `shared/managed-agents-self-hosted-sandboxes.md` § Memory stores); `file` and `github_repository` resources need a cloud environment. The Console deployment form doesn't offer memory stores for self-hosted environments - attach them via the API/SDK. +- `initial_events` must contain at least one starting event - a `user.message` **or** a `user.define_outcome`. (A deployment's `initial_events` also accepts `system.message`, which a session's does not.) - `schedule` takes a cron `expression` and an IANA `timezone`. Minute-level granularity is the maximum. ```bash @@ -54,7 +54,7 @@ deployment = client.beta.deployments.create( ) ``` -The response is a deployment object (`depl_` ID prefix). Check `schedule.upcoming_runs_at` — the next fire times — to confirm the schedule parses the way you intended: +The response is a deployment object (`depl_` ID prefix). Check `schedule.upcoming_runs_at` - the next fire times - to confirm the schedule parses the way you intended: ```json { @@ -77,23 +77,23 @@ The response is a deployment object (`depl_` ID prefix). Check `schedule.upcomin - **Expression:** standard POSIX cron (`minute hour day-of-month month day-of-week`). - **Timezone:** IANA identifier (e.g. `"America/Los_Angeles"`). -- **DST:** literal wall-clock matching — `"0 20 * * *"` in `America/New_York` fires at 8:00 PM local regardless of EST/EDT. +- **DST:** literal wall-clock matching - `"0 20 * * *"` in `America/New_York` fires at 8:00 PM local regardless of EST/EDT. -> ⚠️ **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. +> Warning: **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. +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). +- `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. +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. ```python # All runs for a deployment @@ -114,7 +114,7 @@ for await (const run of client.beta.deploymentRuns.list({ } ``` -Raw HTTP: `GET /v1/deployment_runs?deployment_id=...&has_error=true`. To retrieve a single run by ID, `GET /v1/deployment_runs/{deployment_run_id}` (SDK: `client.beta.deployment_runs.retrieve(run_id)`) — a `deployment_run.*` webhook event carries the run ID as its `data.id`. +Raw HTTP: `GET /v1/deployment_runs?deployment_id=...&has_error=true`. To retrieve a single run by ID, `GET /v1/deployment_runs/{deployment_run_id}` (SDK: `client.beta.deployment_runs.retrieve(run_id)`) - a `deployment_run.*` webhook event carries the run ID as its `data.id`. A failed run looks like: @@ -133,7 +133,7 @@ A failed run looks like: Error types include `environment_archived`, `agent_archived`, `vault_not_found`, `session_rate_limited`, and `service_unavailable`. -The outcome of each **scheduled** run (started/succeeded/failed) and each deployment lifecycle change (created/updated/paused/unpaused/archived/deleted) is also delivered as a webhook event — see `shared/managed-agents-webhooks.md` for the `deployment.*` and `deployment_run.*` event types — so you can react without polling. Manual runs do **not** emit `deployment_run.*` webhook events. +The outcome of each **scheduled** run (started/succeeded/failed) and each deployment lifecycle change (created/updated/paused/unpaused/archived/deleted) is also delivered as a webhook event - see `shared/managed-agents-webhooks.md` for the `deployment.*` and `deployment_run.*` event types - so you can react without polling. Manual runs do **not** emit `deployment_run.*` webhook events. ## Lifecycle: pause / unpause / archive @@ -141,16 +141,16 @@ The outcome of each **scheduled** run (started/succeeded/failed) and each deploy |---|---|---| | Pause | `client.beta.deployments.pause(id)` | Suppresses scheduled triggers go-forward. Sessions already running continue. **Manual runs are still permitted while paused.** Sets `paused_reason: {"type": "manual"}`. | | Unpause | `client.beta.deployments.unpause(id)` | Resumes from the next scheduled occurrence. **Missed triggers are not backfilled.** Clears `paused_reason`. | -| Archive | `client.beta.deployments.archive(id)` | **Terminal** — the schedule stops and the deployment can no longer be modified. Use pause for anything reversible. | +| Archive | `client.beta.deployments.archive(id)` | **Terminal** - the schedule stops and the deployment can no longer be modified. Use pause for anything reversible. | Raw HTTP: `POST /v1/deployments/{deployment_id}/pause` (likewise `/unpause`, `/archive`). ### Failure behavior -- **Rate-limited:** recorded immediately as a `session_rate_limited` run, **no retry** — the schedule simply tries again at the next occurrence. (Rate limits on API calls *inside* a session are handled by the session itself.) -- **Other failed runs** (e.g. `environment_archived`, `vault_not_found`, `service_unavailable`): the run records the `error.type` — monitor runs and fix the referenced resource, or pause the deployment. +- **Rate-limited:** recorded immediately as a `session_rate_limited` run, **no retry** - the schedule simply tries again at the next occurrence. (Rate limits on API calls *inside* a session are handled by the session itself.) +- **Other failed runs** (e.g. `environment_archived`, `vault_not_found`, `service_unavailable`): the run records the `error.type` - monitor runs and fix the referenced resource, or pause the deployment. - **Agent archived:** the deployment is automatically **archived** (terminal) in the same operation. **Agent deleted:** the next scheduled trigger detects the missing agent and archives the deployment then. Either way no deployment run is recorded, and no further sessions are created. ## Manual runs -`POST /v1/deployments/{deployment_id}/run` (SDK: `client.beta.deployments.run(id)`) creates a session immediately and writes a run with `trigger_context.type: "manual"`. Use it to **test a deployment before committing to the schedule** — and remember it works even while the deployment is paused. +`POST /v1/deployments/{deployment_id}/run` (SDK: `client.beta.deployments.run(id)`) creates a session immediately and writes a run with `trigger_context.type: "manual"`. Use it to **test a deployment before committing to the schedule** - and remember it works even while the deployment is paused. diff --git a/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md b/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md index 497b103b0..a4f280f26 100644 --- a/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +++ b/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md @@ -1,12 +1,12 @@ -# Managed Agents — Self-Hosted Sandboxes +# Managed Agents - Self-Hosted Sandboxes -With `config.type: "self_hosted"`, the **agent loop stays on Anthropic's orchestration layer** but **tool execution moves to infrastructure you control** — bash, file ops, and code run inside your container, so filesystem contents and network egress never leave your environment. Contrast with `config.type: "cloud"`, where Anthropic runs the container. Connectivity is **outbound-only**: your worker long-polls Anthropic's work queue; Anthropic never dials into your network. +With `config.type: "self_hosted"`, the **agent loop stays on Anthropic's orchestration layer** but **tool execution moves to infrastructure you control** - bash, file ops, and code run inside your container, so filesystem contents and the sandbox's network egress never leave your environment. (`web_search` / `web_fetch` are the exception: they run on Anthropic's servers in both environment types - restrict them with `allowed_domains` / `blocked_domains` in the agent toolset, `shared/managed-agents-tools.md` § Web search & web fetch settings.) Tool inputs/outputs still flow to Anthropic's control plane so the model can see results; the agent's skills and the contents of any attached memory stores are stored by Anthropic and copied into your sandbox for the session (memory changes sync back - see § Memory stores). Contrast with `config.type: "cloud"`, where Anthropic runs the container. Connectivity is **outbound-only**: your worker long-polls Anthropic's work queue; Anthropic never dials into your network. ## Flow ``` -1. Create environment: config: {type: "self_hosted"} → env_... -2. Generate environment key (Console, on the environment page) → sk-ant-oat01-... as ANTHROPIC_ENVIRONMENT_KEY +1. Create environment: config: {type: "self_hosted"} -> env_... +2. Generate environment key (Console, on the environment page) -> sk-ant-oat01-... as ANTHROPIC_ENVIRONMENT_KEY 3. Run a worker: EnvironmentWorker.run() or ant beta:worker poll 4. Sessions reference environment_id=env_... exactly as for cloud ``` @@ -21,17 +21,19 @@ environment = client.beta.environments.create( ) ``` -`{"type": "self_hosted"}` is the entire config — there are no pool, capacity, or networking sub-fields; you control those on your side. +`{"type": "self_hosted"}` is the entire config - there are no pool, capacity, or networking sub-fields; you control those on your side. -## Run a worker — SDK (primary path) +## Run a worker - SDK (primary path) -`EnvironmentWorker` wraps the poll → dispatch → tool-execute loop. `.run()` is the always-on loop; `.run_one()` / `.runOne()` handles one work item (for webhook-driven wake). +`EnvironmentWorker` wraps the poll -> dispatch -> tool-execute loop. `.run()` is the always-on loop (loops until cancelled). `.handle_item()` / `.handleItem()` / `.HandleItem()` services **one already-claimed** work item without polling - IDs fall back to `ANTHROPIC_WORK_ID` / `ANTHROPIC_ENVIRONMENT_ID` / `ANTHROPIC_SESSION_ID`, the key to the worker's own `environment_key` and then `ANTHROPIC_ENVIRONMENT_KEY`, and the per-session secret to `ANTHROPIC_WORK_SECRET`, so inside an `ant beta:worker poll --on-work` container it needs no arguments. It ignores (and force-stops) non-session work items itself. There is no `run_one()`; claiming is done by `.run()` or by the mid-level poller (below). -**Python — always-on:** +**Python - always-on:** ```python import asyncio +import contextlib import os +import signal from anthropic import AsyncAnthropic from anthropic.lib.environments import EnvironmentWorker @@ -40,18 +42,26 @@ async def main() -> None: environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"] environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"] async with AsyncAnthropic(auth_token=environment_key) as client: - await EnvironmentWorker( + worker = EnvironmentWorker( client, environment_id=environment_id, environment_key=environment_key, workdir="/workspace", - ).run() + ) + task = asyncio.create_task(worker.run()) + # Cancel the task (don't kill the process): the worker stops its in-flight + # work item and uploads changed memory files before exiting. + loop = asyncio.get_running_loop() + for signum in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(signum, task.cancel) + with contextlib.suppress(asyncio.CancelledError): + await task asyncio.run(main()) ``` -**TypeScript — always-on:** +**TypeScript - always-on:** ```typescript import Anthropic from "@anthropic-ai/sdk"; @@ -62,6 +72,7 @@ const environmentId = process.env.ANTHROPIC_ENVIRONMENT_ID!; const client = new Anthropic({ authToken: environmentKey }); const ctrl = new AbortController(); process.once("SIGTERM", () => ctrl.abort()); +process.once("SIGINT", () => ctrl.abort()); await new EnvironmentWorker({ client, @@ -74,9 +85,11 @@ await new EnvironmentWorker({ **Customizing tools.** `EnvironmentWorker` runs the built-in toolset by default. To add or replace tools, use `AgentToolContext(workdir=, client=, session_id=)` with `beta_agent_toolset(env)` / `betaAgentToolset(env)` and pass the resulting tools to the lower-level `tool_runner()`. Skills attached to the agent are downloaded into `{workdir}/skills//` before tool calls begin (`AgentToolContext` handles this when given `client` and `session_id`). Downloaded skill files are marked executable automatically by the CLI and SDK; if you implement skills download yourself, you set permissions. -> **Runtime deps:** the SDK helpers require `/bin/bash` at that exact path. The TypeScript SDK additionally requires `unzip`, `tar`, and Node.js 22+. These are resolved at fixed paths and do **not** respect `PATH` overrides. +> **Runtime deps:** the SDK helpers require `/bin/bash` at that exact path (not consulted via `PATH`). The TypeScript SDK additionally requires `unzip` and `tar` on `PATH` and Node.js 22+; Python and Go use their standard libraries for archive extraction. Memory stores additionally need a POSIX host (Linux or macOS - not Windows, the worker opens memory files with `O_NOFOLLOW`) with a writable `/mnt/memory` - see § Memory stores. -## Run a worker — `ant` CLI (fixed tools) +**File-tool confinement.** `AgentToolContext` confines `read`/`write`/`edit`/`glob`/`grep` to the working directory plus `allowed_roots` (`allowedRoots` / `AllowedRoots`); `write` and `edit` also refuse paths under `read_only_roots` (`readOnlyRoots` / `ReadOnlyRoots`). `EnvironmentWorker` adds the session's memory store directories to these lists itself. This is a guardrail for the file tools only - it does **not** constrain `bash`. The old `unrestricted_paths` option is no longer accepted (passing it raises); add directories to `allowed_roots` instead. + +## Run a worker - `ant` CLI (fixed tools) The `ant` CLI ships a worker with the fixed built-in toolset (`bash`, `read`, `write`, `edit`, `glob`, `grep`). Install per `shared/anthropic-cli.md`, then: @@ -87,22 +100,23 @@ ant beta:worker poll --environment-id env_... --workdir /workspace - `--workdir` is the directory tools operate in (default `.`); tool calls are sandboxed to it. - `--environment-key` overrides the env var. -- `--on-work