mirror of
https://github.com/anthropics/skills.git
synced 2026-08-02 13:05:28 +08:00
1f630fdf92
Syncs the claude-api skill with the current upstream source.
## Managed Agents — five new features
- `effort` on the agent's `model` object (a level string or `{"type": "<level>"}`).
It is agent-configuration only: setting it in a per-session `model` override is
silently ignored.
- Optional `version` on agent update, for optimistic concurrency. Omit it for
last-write-wins.
- `initial_events` on session create, collapsing create plus first send into one
call. Validation is all-or-nothing and only `user.message` and
`user.define_outcome` are accepted.
- Environment and memory-store webhooks: four `environment.*` events and three
`memory_store.*` events.
- Event deltas on per-thread streams. Previews are thread-scoped, so a child
thread's previews never reach the session-level stream.
## Corrections to behavior the skill already documented
- Tool output offload triggers at 100,000 characters (~25k tokens), not 100K
tokens, and covers built-in tools rather than MCP alone.
- `system.message` works on four models, checks only the primary model, appends
system context instead of replacing the prompt, and is accepted during a
`requires_action` idle when it trails a tool result in the same request.
- Vault-to-MCP credential matching is normalized (scheme and host lowercased,
default ports and trailing slashes stripped), not byte-exact.
- Multiagent depth greater than 1 is a validation error, not silently ignored.
- Outcome `interrupted` fires even when evaluation never started, and then
carries an empty-string `outcome_evaluation_start_id`.
- Deployment jitter is 15% of the run interval, floor 5s, cap 9 minutes.
- Webhooks retry three times with jittered 5-120s backoff, then drop silently.
Auto-disable is duration-based with three named triggers.
- `session.status_terminated` means completion or error.
- `agent.thinking` is a progress signal and carries no thinking content.
- `processed_at` is already populated on first sighting for
`user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`.
- Session creation does not provision the sandbox.
- Skill `version` applies to Anthropic-authored skills too.
- Console-created vault credentials are header-injection only, and vault
environment-variable substitution skips secrets in URL paths.
- Console trace URLs need the real workspace ID when the API key is not in the
Default workspace.
## Elsewhere in the skill
- Note partner pricing under the first-party price table: Microsoft Foundry
bills at standard API rates, while Amazon Bedrock and Vertex AI are
partner-operated with separate pricing.
- Correct the tool-runner human-in-the-loop guidance, which previously pointed
readers at the manual loop for approval gates the runner's per-turn hooks
already cover.
- Repoint links away from the retired documentation hosts.
- Dedupe eagerly loaded SKILL.md content that the per-section files already
cover.
336 lines
10 KiB
Markdown
336 lines
10 KiB
Markdown
# 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.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
pip install anthropic
|
||
```
|
||
|
||
## Client Initialization
|
||
|
||
```python
|
||
import anthropic
|
||
|
||
# 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()
|
||
|
||
# Explicit API key (only when you must inject a specific key)
|
||
client = anthropic.Anthropic(api_key="your-api-key")
|
||
```
|
||
|
||
---
|
||
|
||
## Create an Environment
|
||
|
||
```python
|
||
environment = client.beta.environments.create(
|
||
name="my-dev-env",
|
||
config={
|
||
"type": "cloud",
|
||
"networking": {"type": "unrestricted"},
|
||
},
|
||
)
|
||
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}`.
|
||
|
||
### Minimal
|
||
|
||
```python
|
||
# 1. Create the agent (reusable, versioned)
|
||
agent = client.beta.agents.create(
|
||
name="Coding Assistant",
|
||
model="claude-opus-4-8",
|
||
tools=[{"type": "agent_toolset_20260401", "default_config": {"enabled": True}}],
|
||
)
|
||
|
||
# 2. Start a session
|
||
session = client.beta.sessions.create(
|
||
agent={"type": "agent", "id": agent.id, "version": agent.version},
|
||
environment_id=environment.id,
|
||
)
|
||
print(session.id, session.status)
|
||
print(f"Trace: https://platform.claude.com/workspaces/default/sessions/{session.id}") # swap 'default' for your workspace ID if the API key is not in the Default workspace
|
||
```
|
||
|
||
### With system prompt and custom tools
|
||
|
||
```python
|
||
import os
|
||
|
||
agent = client.beta.agents.create(
|
||
name="Code Reviewer",
|
||
model="claude-opus-4-8",
|
||
system="You are a senior code reviewer.",
|
||
tools=[
|
||
{"type": "agent_toolset_20260401"},
|
||
{
|
||
"type": "custom",
|
||
"name": "run_tests",
|
||
"description": "Run the test suite",
|
||
"input_schema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"test_path": {"type": "string", "description": "Path to test file"}
|
||
},
|
||
"required": ["test_path"],
|
||
},
|
||
},
|
||
],
|
||
)
|
||
|
||
session = client.beta.sessions.create(
|
||
agent={"type": "agent", "id": agent.id, "version": agent.version},
|
||
environment_id=environment.id,
|
||
title="Code review session",
|
||
resources=[
|
||
{
|
||
"type": "github_repository",
|
||
"url": "https://github.com/owner/repo",
|
||
"mount_path": "/workspace/repo",
|
||
"authorization_token": os.environ["GITHUB_TOKEN"],
|
||
"branch": "main",
|
||
}
|
||
],
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Send a User Message
|
||
|
||
```python
|
||
client.beta.sessions.events.send(
|
||
session_id=session.id,
|
||
events=[
|
||
{
|
||
"type": "user.message",
|
||
"content": [{"type": "text", "text": "Review the auth module"}],
|
||
}
|
||
],
|
||
)
|
||
```
|
||
|
||
> 💡 **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)
|
||
|
||
```python
|
||
import json
|
||
|
||
# Stream-first: open stream, then send while stream is live
|
||
with client.beta.sessions.events.stream(
|
||
session_id=session.id,
|
||
) as stream:
|
||
client.beta.sessions.events.send(
|
||
session_id=session.id,
|
||
events=[{"type": "user.message", "content": [{"type": "text", "text": "..."}]}],
|
||
)
|
||
for event in stream:
|
||
... # process events
|
||
|
||
# Standalone stream iteration:
|
||
with client.beta.sessions.events.stream(
|
||
session_id=session.id,
|
||
) as stream:
|
||
for event in stream:
|
||
if event.type == "agent.message":
|
||
for block in event.content:
|
||
if block.type == "text":
|
||
print(block.text, end="", flush=True)
|
||
elif event.type == "agent.custom_tool_use":
|
||
# 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)
|
||
elif event.type == "session.status_idle":
|
||
print("\n--- Agent idle ---")
|
||
elif event.type == "session.status_terminated":
|
||
print("\n--- Session terminated ---")
|
||
break
|
||
```
|
||
|
||
---
|
||
|
||
## Provide Custom Tool Result
|
||
|
||
```python
|
||
client.beta.sessions.events.send(
|
||
session_id=session.id,
|
||
events=[
|
||
{
|
||
"type": "user.custom_tool_result",
|
||
"custom_tool_use_id": "sevt_abc123",
|
||
"content": [{"type": "text", "text": "All 42 tests passed."}],
|
||
}
|
||
],
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Poll Events
|
||
|
||
```python
|
||
events = client.beta.sessions.events.list(
|
||
session_id=session.id,
|
||
)
|
||
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).
|
||
|
||
---
|
||
|
||
## Full Streaming Loop with Custom Tools
|
||
|
||
```python
|
||
import json
|
||
|
||
|
||
def run_custom_tool(tool_name: str, tool_input: dict) -> str:
|
||
"""Execute a custom tool and return the result."""
|
||
if tool_name == "run_tests":
|
||
# Your tool implementation here
|
||
return "All tests passed."
|
||
return f"Unknown tool: {tool_name}"
|
||
|
||
|
||
def run_session(client, session_id: str):
|
||
"""Stream events and handle custom tool calls."""
|
||
while True:
|
||
with client.beta.sessions.events.stream(
|
||
session_id=session_id,
|
||
) as stream:
|
||
tool_calls = []
|
||
for event in stream:
|
||
if event.type == "agent.message":
|
||
for block in event.content:
|
||
if block.type == "text":
|
||
print(block.text, end="", flush=True)
|
||
elif event.type == "agent.custom_tool_use":
|
||
tool_calls.append(event)
|
||
elif event.type == "session.status_idle":
|
||
break
|
||
elif event.type == "session.status_terminated":
|
||
return
|
||
|
||
if not tool_calls:
|
||
break
|
||
|
||
# Process custom tool calls
|
||
results = []
|
||
for call in tool_calls:
|
||
result = run_custom_tool(call.name, call.input)
|
||
results.append({
|
||
"type": "user.custom_tool_result",
|
||
"custom_tool_use_id": call.id,
|
||
"content": [{"type": "text", "text": result}],
|
||
})
|
||
|
||
client.beta.sessions.events.send(
|
||
session_id=session_id,
|
||
events=results,
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Upload a File
|
||
|
||
```python
|
||
with open("data.csv", "rb") as f:
|
||
file = client.beta.files.upload(
|
||
file=f,
|
||
)
|
||
|
||
# Use in a session
|
||
session = client.beta.sessions.create(
|
||
agent={"type": "agent", "id": agent.id, "version": agent.version},
|
||
environment_id=environment.id,
|
||
resources=[{"type": "file", "file_id": file.id, "mount_path": "/workspace/data.csv"}],
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## List and Download Session Files
|
||
|
||
List files the agent wrote to `/mnt/session/outputs/` during a session, then download them.
|
||
|
||
```python
|
||
# List files associated with a session
|
||
files = client.beta.files.list(
|
||
scope_id=session.id,
|
||
betas=["managed-agents-2026-04-01"],
|
||
)
|
||
for f in files.data:
|
||
print(f.filename, f.size_bytes)
|
||
# Download each file and save to disk
|
||
file_content = client.beta.files.download(f.id)
|
||
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.
|
||
|
||
---
|
||
|
||
## Session Management
|
||
|
||
```python
|
||
# Get session details
|
||
session = client.beta.sessions.retrieve(session_id="sesn_011CZxAbc123Def456")
|
||
print(session.status, session.usage)
|
||
|
||
# List sessions
|
||
sessions = client.beta.sessions.list()
|
||
|
||
# Delete a session
|
||
client.beta.sessions.delete(session_id="sesn_011CZxAbc123Def456")
|
||
|
||
# Archive a session
|
||
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 = client.beta.agents.create(
|
||
name="MCP Agent",
|
||
model="claude-opus-4-8",
|
||
mcp_servers=[
|
||
{"type": "url", "name": "my-tools", "url": "https://my-mcp-server.example.com/sse"},
|
||
],
|
||
tools=[
|
||
{"type": "agent_toolset_20260401", "default_config": {"enabled": True}},
|
||
{"type": "mcp_toolset", "mcp_server_name": "my-tools"},
|
||
],
|
||
)
|
||
|
||
# Session attaches vault(s) containing credentials for those MCP server URLs
|
||
session = client.beta.sessions.create(
|
||
agent=agent.id,
|
||
environment_id=environment.id,
|
||
vault_ids=[vault.id],
|
||
)
|
||
```
|
||
|
||
See `shared/managed-agents-tools.md` §Vaults for creating vaults and adding credentials.
|