Files
anthropics_skills/skills/claude-api/python/managed-agents/README.md
T
Lance Martin 1f630fdf92 Update claude-api skill: Managed Agents July launch wave, partner pricing, tool-runner corrections (#1463)
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.
2026-07-22 14:20:09 -04:00

10 KiB
Raw Blame History

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

pip install anthropic

Client Initialization

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

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

# 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

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

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.


Stream Events (SSE)

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

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

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.


Full Streaming Loop with Custom Tools

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

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.

# 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 (~13s) between session.status_idle and output files appearing in files.list. Retry once or twice if the list is empty.


Session Management

# 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

# 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.