AI Learning
intermediate ⏱️ 13 min read · 🎬 ~27 min video

Build a Production-Ready Agent with Claude Managed Agents

A hands-on walkthrough of Claude Managed Agents: defining agents and environments, creating sessions, streaming events, using outcomes for iterative self-verification, and monitoring live runs in the developer console.

This lesson is original educational writing based on this video by Anthropic (published May 21, 2026). All credit for the original content goes to the creators.

#agents #api #production
Video thumbnail: Build a Production-Ready Agent with Claude Managed Agents
Original video β€” all credit to the creators. Watch the original on YouTube β†—

1. What Claude Managed Agents is (and why it exists)

Building a production-quality AI agent from scratch is a surprisingly long engineering checklist: you need an agent loop, remote hosting, context management, state recovery, a sandboxing fleet, durable event storage, MCP server integration, and secure end-user credential injection. That is weeks of infrastructure before you write a single line of product logic.

Claude Managed Agents is a set of API endpoints β€” available today with any Anthropic API key β€” that delivers all of that infrastructure as primitives you can compose. You pick the ones you need, discard the rest, and build your own product experience on top.

The four core primitives are:

PrimitiveWhat it is
AgentA reusable template: system prompt, tools, MCP servers, permission rules
EnvironmentA sandbox template: network access, pre-installed packages, self-hosted infra
SessionA running conversation tied to an agent + environment
EventsThe bidirectional stream of everything happening inside a session
Agentsystem prompttools + MCP serverspermission controlsEnvironmentsandbox confignetwork / packagesself-hosted or AnthropicSessionrunning conversationrepos + files pre-loadedEvents (stream)user messages / interruptstool results / confirmationsoutcome definitionsagent replies / tool callssession lifecycle events
The four Claude Managed Agents primitives and how they compose into a running session.

2. Defining an agent and an environment

An agent definition is a reusable template you create once and reference by ID in every session. Think of it the way you think of a Docker image: you define it, version it, and then run as many instances of it as you need.

The key configuration decisions when defining an agent are:

  • System prompt β€” the persona, rules, and context that govern every session.
  • Tools β€” which built-in tools Claude can call (bash, file read/write, web search, computer use). Omitting a tool is the safest way to prevent a whole category of risk.
  • MCP servers β€” external capabilities, such as a Linear MCP or a Figma MCP.
  • Permission controls β€” per-tool policy: auto_execute for low-risk reads, require_approval for anything that touches a database or runs shell commands. This is how you implement human-in-the-loop without writing custom middleware.
// Create an agent definition (one-time setup)
const agent = await client.beta.agents.create({
  name: 'deal-desk-coordinator',
  model: 'claude-opus-4-7',
  system_prompt: `You are a senior M&A analyst assistant. When asked to evaluate
a company, delegate research tasks to specialist sub-agents and synthesize
their findings into a structured investment memo.`,
  tools: [
    { type: 'web_search' },
    { type: 'file_read', policy: 'auto_execute' },
    { type: 'bash',      policy: 'require_approval' },
  ],
  mcp_servers: [
    { name: 'linear', url: 'https://mcp.linear.app/sse' },
  ],
});

console.log(agent.id); // save this β€” you'll pass it to every session

An environment defines the sandbox template: whether the container has internet access, what packages come pre-installed, and β€” since the launch of self-hosted environments β€” whether the container runs on Anthropic infrastructure or on your own Cloudflare, Modal, or Vercel fleet. Self-hosted environments let sensitive data stay inside your own network perimeter.

const env = await client.beta.environments.create({
  name: 'deal-desk-sandbox',
  network_access: true,
  preinstall: {
    npm: ['xlsx', 'pdf-parse'],
    pip: ['pandas', 'openpyxl'],
  },
});

3. Creating a session and listing sessions

A session is a running conversation bound to a specific agent and environment. Creating one is equivalent to opening a new Claude.ai chat or launching claude in your terminal β€” except it runs remotely, persists durably, and is fully API-driven.

At session-create time you can also attach GitHub repositories or files that should be pre-loaded into the container before Claude starts working.

// Create a session
const session = await client.beta.sessions.create({
  agent_id:       agent.id,
  environment_id: env.id,
  memory_store_ids: [memoryStore.id],   // optional: give Claude persistent memory
  credential_vault_ids: [vault.id],     // optional: inject MCP auth tokens securely
  resources: [
    {
      type: 'github_repository',
      repo: 'acme/deal-data',
      branch: 'main',
    },
  ],
});

console.log(session.id); // pass to your UI

Listing existing sessions lets you build a sidebar or dashboard over your agent’s history:

// List all sessions for an agent
const { data: sessions } = await client.beta.sessions.list({
  agent_id: agent.id,
  limit: 50,
});

Sessions carry status information (running, idle, error, terminated) so you can show users a live indicator without polling the event stream.

4. Sending events and streaming responses

The event stream is the heartbeat of a session. Your application submits inbound events (user messages, interrupts, tool results, outcome definitions) and receives outbound events (Claude’s replies, tool calls, multi-agent coordination, lifecycle changes).

Sending a user message

await client.beta.sessions.events.create(session.id, {
  type: 'user',
  content: [{ type: 'text', text: 'Give me a quick read on Acme Robotics.' }],
});

Streaming the response

Use the streaming endpoint to forward events to your frontend in real time. The response is a server-sent event (SSE) stream β€” each chunk is one typed event object.

const stream = await client.beta.sessions.events.stream(session.id);

for await (const event of stream) {
  switch (event.type) {
    case 'agent.message':
      // Claude sent text β€” stream to your UI
      process.stdout.write(event.content);
      break;

    case 'agent.tool_call':
      // Claude is calling a tool β€” show a spinner
      console.log(`[tool] ${event.tool_name}`, event.input);
      break;

    case 'agent.subagent_spawned':
      // A sub-agent was launched β€” update your multi-agent view
      console.log(`[subagent] ${event.subagent_id} started`);
      break;

    case 'session.error':
      console.error('Session error:', event.error);
      break;

    case 'session.terminated':
      console.log('Session finished');
      break;
  }
}

Sending an interrupt

If Claude heads in the wrong direction you can cut it off without terminating the session:

await client.beta.sessions.events.create(session.id, {
  type: 'interrupt',
  reason: 'Stop β€” please re-read the brief before continuing.',
});

5. Outcome definitions β€” making Claude self-verify

The most powerful inbound event type is outcome. Instead of just asking Claude a question, you hand it a rubric β€” a structured spec or checklist β€” and Claude enters a loop where it:

  1. Researches and drafts an answer.
  2. Evaluates its own output against the rubric.
  3. Iterates until it is confident the rubric is satisfied.
  4. Emits an outcome.completed event with its findings.

This is especially useful for multi-agent workflows where individual sub-agents surface partial results and a coordinator must synthesize them into something that meets a quality bar.

// Send an outcome definition instead of a plain user message
await client.beta.sessions.events.create(session.id, {
  type: 'outcome',
  content: `
    Evaluate the following three acquisition targets and produce an investment memo.

    ## Companies
    - Bridgewell Dynamics
    - Norwood Automation
    - Acme Robotics

    ## Rubric β€” your response must satisfy ALL of these:
    - [ ] Financial summary for each company (revenue, growth, EBITDA margin)
    - [ ] Competitive positioning relative to each other
    - [ ] Top 3 risks per company, with severity rating
    - [ ] A ranked recommendation with clear reasoning

    Iterate until you can honestly confirm every rubric item is addressed.
  `,
});

When you listen to the event stream after sending an outcome you will see outcome.processing_started followed by several iterations of tool calls and self-critique, and finally outcome.completed once Claude is satisfied.

6. Monitoring and memory in the developer console

The developer console gives you a live visual debugger for every running session. Navigate to Sessions β†’ [your session] and you will see:

  • Agent timeline β€” one horizontal lane per agent thread (coordinator + sub-agents), updated in real time.
  • Event inspector β€” click any tool call to see exact inputs and outputs.
  • Duration indicators β€” flag tool calls that are taking unusually long so you can diagnose slow MCP servers or oversized files.

Beyond real-time monitoring, the console exposes:

  • Memory stores β€” a key/value store Claude reads and writes across sessions. You can edit individual memories directly if Claude recorded something incorrectly, or seed memories before the first session runs.
  • Credential vaults β€” store MCP auth tokens once; Anthropic injects them when needed without the tokens ever entering Claude’s context window.
  • Environments tab β€” shows self-hosted sandbox registrations and their current status.
  • Agent versions β€” roll back to any previous agent definition version if a system prompt change regresses quality.

Check your understanding

5 questions Β· your answers are saved in this browser only

  1. 1. Which Claude Managed Agents primitive defines the sandbox template (network access, pre-installed packages, and hosting provider)?

  2. 2. What is the primary purpose of a per-tool `require_approval` permission policy?

  3. 3. What happens when you send an `outcome` event to a session instead of a plain `user` event?

  4. 4. What is the purpose of `span.*` events in the event stream?

  5. 5. How do credential vaults improve MCP server security compared to passing tokens directly in the session payload?

Build it yourself

Follow these exact steps to reproduce it yourself Β· estimated time: ~30 min

Prerequisites

  • Anthropic API key with Managed Agents access
  • Node.js 18+ or Bun
  • A Linear account (optional, for the MCP demo)

Step 1 β€” Install the SDK and scaffold a project

mkdir deal-desk-agent && cd deal-desk-agent
bun init -y          # or: npm init -y
bun add @anthropic-ai/sdk

Create a .env file:

ANTHROPIC_API_KEY=sk-ant-...

Step 2 β€” Create an agent definition

// src/setup.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

async function setup() {
  // Create the agent
  const agent = await client.beta.agents.create({
    name: 'deal-desk-coordinator',
    model: 'claude-opus-4-7',
    system_prompt: `You are a senior M&A analyst. When asked to evaluate
acquisition targets, delegate to specialist sub-agents and synthesize their
findings into a structured investment memo.`,
    tools: [
      { type: 'web_search' },
      { type: 'file_read',  policy: 'auto_execute' },
      { type: 'bash',       policy: 'require_approval' },
    ],
  });

  // Create the environment
  const env = await client.beta.environments.create({
    name: 'deal-desk-sandbox',
    network_access: true,
  });

  console.log('Agent ID:', agent.id);
  console.log('Environment ID:', env.id);
  // Save these IDs β€” paste into your .env file
}

setup();

Run it once:

bun run src/setup.ts

Add the printed IDs to .env:

AGENT_ID=agt_...
ENVIRONMENT_ID=env_...

Step 3 β€” Create a session

// src/session.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

export async function createSession() {
  const session = await client.beta.sessions.create({
    agent_id:       process.env.AGENT_ID!,
    environment_id: process.env.ENVIRONMENT_ID!,
  });
  console.log('Session created:', session.id);
  return session;
}

Step 4 β€” Send a message and stream the response

// src/chat.ts
import Anthropic from '@anthropic-ai/sdk';
import { createSession } from './session';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

async function chat(sessionId: string, text: string) {
  // Send the user message
  await client.beta.sessions.events.create(sessionId, {
    type: 'user',
    content: [{ type: 'text', text }],
  });

  // Stream the response
  const stream = await client.beta.sessions.events.stream(sessionId);

  for await (const event of stream) {
    if (event.type === 'agent.message') {
      process.stdout.write(event.content);
    } else if (event.type === 'agent.tool_call') {
      console.log(`\n[tool] ${event.tool_name}`);
    } else if (event.type === 'session.terminated') {
      break;
    } else if (event.type === 'session.error') {
      console.error('\nError:', event.error);
      break;
    }
  }
}

(async () => {
  const session = await createSession();
  await chat(session.id, 'Give me a quick overview of the robotics industry.');
})();
bun run src/chat.ts

Step 5 β€” Upgrade to an outcome for self-verified research

Replace the plain user message with an outcome definition so Claude iterates until it satisfies a quality rubric:

await client.beta.sessions.events.create(session.id, {
  type: 'outcome',
  content: `
Research Acme Robotics and produce an investment memo.

## Rubric
- [ ] Revenue and growth rate (last 2 years)
- [ ] Top 3 competitors and differentiation
- [ ] Key risks with severity (High / Medium / Low)
- [ ] Go / No-go recommendation with one-paragraph rationale

Iterate until every rubric item is fully addressed.
  `,
});

Then stream as before β€” watch for outcome.processing_started and outcome.completed events in addition to the regular agent messages.

Step 6 β€” Monitor in the developer console

  1. Go to console.anthropic.com β†’ Managed Agents β†’ Sessions.
  2. Find your session (it shows up by ID and creation time).
  3. Click into it to see the live agent timeline.
  4. Expand any tool call to inspect its exact input and output.
  5. If Claude wrote a memory, navigate to Memory Stores to review or edit it.

Related lessons

intermediate 🎬 Anthropic · ~24 min

Building with Claude on Google Cloud: Zero to Deployed in 30 Minutes

Watch a complete feedback app go from empty directory to live URL in a single session β€” using Claude with Google Cloud, subagents, MCP servers, and skills to handle the full software lifecycle across five distinct roles.

#agentic-workflows #agents #google-cloud #mcp #production
beginner 🎬 Anthropic · ~1 min

Introducing Claude Managed Agents

Claude Managed Agents is a suite of composable APIs for building and deploying agents at scale. Out of the box: native MCP, tool integrations, memory, and all infrastructure needed to run production agents.

#agents #managed-agents #api
intermediate 🎬 Anthropic · ~29 min

Get to Production Faster with Claude Managed Agents

Why building a production-ready agent is harder than it looks, and how Claude Managed Agents handles the infrastructure layer so you can focus on what actually makes your agent valuable.

#agents #managed-agents #production #agentic-workflows