# How the Multi-Agent Coordinator Roster Works in Claude Managed Agents

> Understand the multi-agent coordinator roster in Claude Managed Agents. Discover how it maps agent capabilities to endpoints for dynamic request routing. Learn more!

- Repository: [Anthropic/cwc-workshops](https://github.com/anthropics/cwc-workshops)
- Tags: deep-dive
- Published: 2026-07-18

---

**The multi-agent coordinator roster is a declarative array of `RosterEntry` objects that enables dynamic delegation by mapping agent capabilities to endpoints, allowing the coordinator's `sendMessage` function to route requests to the appropriate Claude Managed Agent via a configurable selector callback.**

The `anthropics/cwc-workshops` repository provides a production-ready reference implementation for building scalable multi-agent systems. Central to this architecture is the **multi-agent coordinator roster**, which decouples agent discovery from invocation logic, letting you add or remove sub-agents by modifying a static configuration rather than changing dispatch code.

## Roster Data Structure and Type Definitions

The roster is formally defined in [`production-ready-agent/starter/lib/types.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/types.ts) as a collection of `RosterEntry` interfaces. Each entry acts as a service descriptor containing the metadata required to route requests and describe capabilities to the coordinator.

A `RosterEntry` consists of four required fields:

- **name**: The unique identifier used by the selector logic (e.g., `"research"` or `"legal"`).
- **skill**: A capability tag describing what the agent does (e.g., `"SEC-filing-research"`).
- **endpoint**: The full URL or Claude Managed Agent ID where the sub-agent accepts requests.
- **description**: A human-readable summary consumed by the coordinator's prompting layer or logging system.

```typescript
// production-ready-agent/starter/lib/types.ts
export interface RosterEntry {
  name: string;
  skill: string;
  endpoint: string;
  description: string;
}

```

## Agent Selection and Dispatch Logic

The dispatch mechanism lives in [`production-ready-agent/starter/lib/anthropic.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/anthropic.ts) within the `sendMessage` function. This implementation follows a three-phase pattern: selection, invocation, and normalization.

**Selection Phase**: The function accepts a `selector` callback alongside the `roster` array. The selector receives the raw `userPrompt` and returns the `name` string of the target agent. This design supports arbitrary routing strategies—semantic matching, keyword detection, or load-balancing—without altering core dispatch code.

**Invocation Phase**: Once the selector returns a name, `sendMessage` performs a lookup to find the matching `RosterEntry` in the roster array. It then constructs a request to the Claude Managed Agents API, injecting the entry's `endpoint` and `skill` into the payload.

**Normalization Phase**: After the sub-agent responds, the coordinator wraps the result with bookkeeping metadata (such as `agentUsed` and token consumption) before returning the final output to the caller.

```typescript
// production-ready-agent/starter/lib/anthropic.ts (conceptual flow)
async function sendMessage({
  userPrompt,
  roster,
  selector,
}: {
  userPrompt: string;
  roster: RosterEntry[];
  selector: (prompt: string) => string;
}) {
  const targetName = selector(userPrompt);
  const entry = roster.find(r => r.name === targetName);
  
  // Dispatch to Claude Managed Agent
  const response = await fetch(entry.endpoint, {
    method: "POST",
    body: JSON.stringify({
      prompt: userPrompt,
      skill: entry.skill,
    }),
  });
  
  return {
    content: await response.text(),
    agentUsed: entry.name,
    skillUsed: entry.skill,
  };
}

```

## Practical Implementation Examples

To implement a custom coordinator, define your roster as a static array and pass it to `sendMessage` with an appropriate selector. The following example creates a two-agent system for legal and summarization tasks:

```typescript
// customRoster.ts
import { RosterEntry } from "./production-ready-agent/starter/lib/types";

export const legalRoster: RosterEntry[] = [
  {
    name: "legal",
    skill: "contract-review",
    endpoint: "https://api.anthropic.com/v1/managed_agents/contract-review",
    description: "Review legal contracts for compliance risks"
  },
  {
    name: "summary",
    skill: "document-summarizer",
    endpoint: "https://api.anthropic.com/v1/managed_agents/summarizer",
    description: "Generate executive summaries of long documents"
  },
];

```

```typescript
// coordinator.ts
import { sendMessage } from "./production-ready-agent/starter/lib/anthropic";
import { legalRoster } from "./customRoster";

async function routeUserQuery(question: string) {
  const result = await sendMessage({
    userPrompt: question,
    roster: legalRoster,
    selector: (prompt) => prompt.includes("contract") ? "legal" : "summary",
  });
  
  console.log(`Agent ${result.agentUsed} processed the request.`);
  return result.content;
}

```

For load-balancing scenarios, implement a round-robin selector that maintains state across calls:

```typescript
let lastIndex = 0;

function roundRobinSelector(_prompt: string, roster: RosterEntry[]): string {
  const entry = roster[lastIndex];
  lastIndex = (lastIndex + 1) % roster.length;
  return entry.name;
}

// Usage
await sendMessage({
  userPrompt: "Analyze Q3 earnings",
  roster: myRoster,
  selector: (prompt) => roundRobinSelector(prompt, myRoster),
});

```

## Core Files in the Coordinator Architecture

Understanding the following files is essential for modifying or extending the **multi-agent coordinator roster** behavior:

- **[`production-ready-agent/starter/lib/types.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/types.ts)**: Defines the `RosterEntry` interface and TypeScript types used throughout the coordinator system.

- **[`production-ready-agent/starter/lib/anthropic.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/anthropic.ts)**: Implements the `sendMessage` orchestration layer that consumes the roster and executes the selector-driven dispatch logic.

- **[`production-ready-agent/starter/lib/utils.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/utils.ts)**: Provides helper utilities for logging agent invocations and handling retry logic when sub-agents are unavailable.

- **[`production-ready-agent/starter/lib/chat.ts`](https://github.com/anthropics/cwc-workshops/blob/main/production-ready-agent/starter/lib/chat.ts)**: Contains the UI-facing entry point that initializes the coordinator with a specific roster and handles user input streaming.

## Summary

- The **multi-agent coordinator roster** is a static configuration array that eliminates hardcoded endpoints by describing each sub-agent's `name`, `skill`, `endpoint`, and `description`.

- The `sendMessage` function in [`anthropic.ts`](https://github.com/anthropics/cwc-workshops/blob/main/anthropic.ts) executes a selector callback against the roster to determine which agent should handle a given prompt, enabling flexible routing strategies from simple keyword matching to complex load balancing.

- Because the roster is pure data defined in [`types.ts`](https://github.com/anthropics/cwc-workshops/blob/main/types.ts), you can add new agents or modify capabilities without changing the core dispatch logic in the coordinator.

- The reference implementation supports metadata tracking (such as `agentUsed`) for observability and billing reconciliation across distributed agent calls.

## Frequently Asked Questions

### How does the coordinator handle cases where no roster agent matches the query?

When the selector returns a name that does not exist in the roster array, the `sendMessage` function in [`anthropic.ts`](https://github.com/anthropics/cwc-workshops/blob/main/anthropic.ts) will fail to find a matching `RosterEntry` and typically throws an error or falls back to a default 'generalist' agent if your implementation includes such logic. You should validate selector output against `roster.map(r => r.name)` before dispatching or wrap the lookup in error handling that returns a graceful fallback response.

### Can the roster be updated dynamically without restarting the coordinator?

Yes, because the roster is passed as an argument to `sendMessage` rather than being loaded from a static config file at startup, you can modify the `RosterEntry` array in memory between requests. For persistent changes, update the source module exporting the roster array; in a hot-reloaded development environment or serverless function, this takes effect on the next invocation without requiring a process restart.

### What is the relationship between the `skill` field and actual tool execution?

The `skill` string in a `RosterEntry` serves as a semantic tag consumed by both the coordinator's prompting layer and the downstream Claude Managed Agent to determine which tools to enable. While the coordinator uses `skill` for routing decisions, the actual tool binding happens at the sub-agent's endpoint based on this identifier, ensuring that the `research` skill triggers SEC-filing tools while the `valuation` skill triggers financial modeling tools.

### Is there a performance penalty for maintaining large rosters with many agents?

The roster lookup is an O(n) array search performed once per request in [`anthropic.ts`](https://github.com/anthropics/cwc-workshops/blob/main/anthropic.ts). For rosters under 100 entries, the overhead is negligible; however, if you scale to hundreds of agents, you should refactor the roster into a `Map<string, RosterEntry>` indexed by `name` to achieve O(1) lookups and prevent latency accumulation in high-throughput coordinator deployments.