# Agent roster

Every agent on the team: role, model, whether it's on, and two weeks of runs. Agents with recent failures are lifted to the top with the latest failure in a line, so problems aren't buried in alphabetical order.

- Registry name: `agent-roster` (`@candor/agent-roster`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/agent-roster

## Install

```bash
npx candor-ui add agent-roster
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/agent-roster`.
It brings in: agent-avatar, know, locale, status-dot, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { AgentRoster } from "@/components/agent-roster"
import type { AgentConfig, ModelOption } from "@/lib/know"

const models: ModelOption[] = [
  { id: "opus", vendor: "Anthropic", name: "Claude Opus 5.5", cost: 4, zdr: "attested" },
  { id: "sonnet", vendor: "Anthropic", name: "Claude Sonnet 5.5", cost: 2, zdr: "attested" },
  { id: "haiku", vendor: "Anthropic", name: "Claude Haiku 5.5", cost: 1, zdr: "attested" },
]

const runs = (ok: number[], failed: number[] = []) => ok.map((n, i) => ({ ok: n, failed: failed[i] ?? 0 }))

const agents: AgentConfig[] = [
  { agent: { id: "dev", name: "Dev agent", role: "coder" }, enabled: true, status: "running", model: "sonnet", runs: runs([4, 6, 5, 7, 3, 0, 0, 5, 6, 8, 4, 5, 3, 2], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1]), lastFailure: "Add region to customers: row lock on batch 12 during the backfill." },
  { agent: { id: "arch", name: "Architect agent", role: "planner" }, enabled: true, status: "idle", model: "opus", runs: runs([1, 2, 1, 0, 2, 0, 0, 1, 3, 2, 1, 2, 1, 1]) },
  { agent: { id: "qa", name: "QA agent", role: "tester" }, enabled: true, status: "succeeded", model: "haiku", runs: runs([8, 9, 7, 8, 9, 2, 2, 8, 9, 9, 8, 7, 9, 8]) },
  { agent: { id: "rev", name: "Review agent", role: "reviewer" }, enabled: false, status: "idle", model: "sonnet", runs: runs([2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) },
]

export default function Example() {
  const [active, setActive] = React.useState("dev")
  return <AgentRoster className="max-w-2xl" agents={agents} models={models} activeId={active} onSelect={setActive} />
}
```

## <AgentRoster>

Props:

- `agents: AgentConfig[]`: Every agent on the team, with its role, model, state and recent runs.
- `models: ModelOption[]`: The models agents can use, for naming each agent's model.
- `activeId?: string | undefined`: The agent shown as selected.
- `onSelect?: ((id: string) => void) | undefined`: Called with the agent id when a card is clicked.
- `className?: string | undefined`

## Strings (`agentRoster`)

The fixed words, English by default. Override any of them under `agentRoster` in `<LocaleProvider strings={{ agentRoster: { … } }}>`; the table is exported as `agentRosterStrings`.

- `agents`: Agents
- `runs`: { one: "{count} run", other: "{count} runs" }
- `failed`: · {count} failed
- `idle`: Idle
- `off`: Off

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
