#!/usr/bin/env bash # Anthropic Managed Agents (beta) — create agent -> create environment -> create session -> # send user.message -> stream SSE events -> clean up everything that was created. # # STATUS: LIVE_VERIFIED 2026-09-18 — the identical HTTP sequence (env → agent → session with string-cents budget → user.message → SSE stream → cleanup) ran via scripts/live.py (tmp-live/platform-anthropic/agents-probes-2.json); this file itself was not executed. # Model: claude-haiku-4-5-20251001 # Beta header: anthropic-beta: managed-agents-2026-04-01 # Budget: max budget minimal — session budget capped at 5 US cents ({"amount":"5","currency":"USD"}), # prompt "Reply with OK.", no tools enabled (tools omitted => nothing runs in the sandbox). # Names are prefixed "atlas-platform-agent". # # Requires: ANTHROPIC_API_KEY in the environment (source .env; never print it), curl, jq. # Cleanup: DELETE session (after it is idle), POST /archive agent, DELETE environment (falls back to # /archive if the environment is still referenced), all via an EXIT trap. # # Docs: https://platform.claude.com/docs/en/managed-agents/quickstart # https://platform.claude.com/docs/en/managed-agents/events-and-streaming set -eo pipefail : "${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY (source .env)}" BASE="${ANTHROPIC_BASE_URL:-https://api.anthropic.com}" BETA="managed-agents-2026-04-01" MODEL="claude-haiku-4-5-20251001" TS="$(date +%s)" ant() { # ant METHOD PATH [json-body] -> prints response body; fails on non-2xx local method="$1" path="$2" body="${3:-}" if [[ -n "$body" ]]; then curl -sS --fail-with-body -X "$method" "$BASE$path" \ -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: $BETA" -H "content-type: application/json" --data "$body" else curl -sS --fail-with-body -X "$method" "$BASE$path" \ -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: $BETA" fi } AGENT_ID="" ENV_ID="" SESSION_ID="" STREAM_PID="" cleanup() { set +e [[ -n "$STREAM_PID" ]] && kill "$STREAM_PID" 2>/dev/null if [[ -n "$SESSION_ID" ]]; then # A running session cannot be deleted: interrupt first, then wait for idle (max ~60 s). status=$(ant GET "/v1/sessions/$SESSION_ID" | jq -r '.status') if [[ "$status" == "running" ]]; then ant POST "/v1/sessions/$SESSION_ID/events" '{"events":[{"type":"user.interrupt"}]}' >/dev/null for _ in $(seq 1 30); do sleep 2 status=$(ant GET "/v1/sessions/$SESSION_ID" | jq -r '.status') [[ "$status" != "running" ]] && break done fi ant DELETE "/v1/sessions/$SESSION_ID" >/dev/null && echo "deleted session $SESSION_ID" fi [[ -n "$AGENT_ID" ]] && ant POST "/v1/agents/$AGENT_ID/archive" >/dev/null && echo "archived agent $AGENT_ID" if [[ -n "$ENV_ID" ]]; then ant DELETE "/v1/environments/$ENV_ID" >/dev/null 2>&1 && echo "deleted environment $ENV_ID" \ || { ant POST "/v1/environments/$ENV_ID/archive" >/dev/null && echo "archived environment $ENV_ID"; } fi } trap cleanup EXIT # 1) Agent (versioned resource). No tools => minimal, nothing executes in the sandbox. agent=$(ant POST /v1/agents "$(jq -n --arg n "atlas-platform-agent-$TS" --arg m "$MODEL" \ '{name:$n, model:$m, system:"You are a terse test agent. Reply with OK.", metadata:{origin:"api-atlas"}}')") AGENT_ID=$(jq -er '.id' <<<"$agent"); echo "agent: $AGENT_ID v$(jq -r .version <<<"$agent")" # 2) Environment (cloud sandbox, limited egress). Every session gets its own fresh container. env_json=$(ant POST /v1/environments "$(jq -n --arg n "atlas-platform-agent-env-$TS" \ '{name:$n, config:{type:"cloud", networking:{type:"limited", allowed_hosts:["example.com"]}}}')") ENV_ID=$(jq -er '.id' <<<"$env_json"); echo "environment: $ENV_ID" # 3) Session with a hard 5-cent list-cost budget (budget can only be attached at creation). session=$(ant POST /v1/sessions "$(jq -n --arg a "$AGENT_ID" --arg e "$ENV_ID" --arg t "atlas-platform-agent-session-$TS" \ '{agent:$a, environment_id:$e, title:$t, budget:{type:"limit", max_list_cost:{amount:"5", currency:"USD"}}}')") SESSION_ID=$(jq -er '.id' <<<"$session"); echo "session: $SESSION_ID status=$(jq -r .status <<<"$session")" # 4) Open the SSE stream BEFORE sending the message (only events after the stream opens are delivered). STREAM_LOG=$(mktemp) curl -sS -N "$BASE/v1/sessions/$SESSION_ID/events/stream" \ -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: $BETA" -H "accept: text/event-stream" > "$STREAM_LOG" & STREAM_PID=$! sleep 1 # 5) Send the user message (returns as soon as the event is queued). ant POST "/v1/sessions/$SESSION_ID/events" \ '{"events":[{"type":"user.message","content":[{"type":"text","text":"Reply with OK."}]}]}' >/dev/null # 6) Tail the stream until the session goes idle (or 180 s timeout). Print type + text of each event. deadline=$(( $(date +%s) + 180 )) while IFS= read -r line; do [[ $line == data:* ]] || continue ev=${line#data: } type=$(jq -r '.type' <<<"$ev") case "$type" in agent.message) printf '[%s] %s\n' "$type" "$(jq -j '[.content[] | select(.type=="text") | .text] | join("")' <<<"$ev")" ;; session.usage) printf '[%s] list_cost=%s active_seconds=%s\n' "$type" "$(jq -r '.usage.list_cost.amount' <<<"$ev")" "$(jq -r '.usage.active_seconds' <<<"$ev")" ;; session.status_idle) printf '[%s] stop_reason=%s\n' "$type" "$(jq -r '.stop_reason.type' <<<"$ev")"; break ;; session.error) printf '[%s] %s (%s)\n' "$type" "$(jq -r '.error.type' <<<"$ev")" "$(jq -r '.error.retry_status.type // empty' <<<"$ev")"; break ;; *) printf '[%s]\n' "$type" ;; esac (( $(date +%s) > deadline )) && { echo "timeout waiting for idle"; break; } done < <(tail -f -n +1 "$STREAM_LOG") # 7) Final session snapshot (usage priced at list rates). ant GET "/v1/sessions/$SESSION_ID" | jq '{status, usage: {input_tokens: .usage.input_tokens, output_tokens: .usage.output_tokens, list_cost: .usage.list_cost}}' rm -f "$STREAM_LOG" # cleanup() runs on EXIT