# Run console (Claude Agent SDK)

An agent starts and steers runs from the chat; a console lists every run with its log, trace and receipt, and when a step fails the person chooses retry, skip or stop, allowing any retry that changes something.

- Registry name: `run-console-claude` (`@candor/run-console-claude`)
- Tier: Premium. Installing it needs a 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/run-console-claude

## Install

```bash
npx candor-ui add run-console-claude
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/run-console-claude`.
It brings in: button, card-registry, chat, claude-agent-server, composer, entity-chip, log-stream, message, ops, prompt-suggestions, run-log, run-receipt, run-recovery, standard-cards, status, thread, trace-viewer, use-claude-agent, work.
npm packages: @anthropic-ai/claude-agent-sdk@^0.3.318, drizzle-orm@^0.45.4, lucide-react@^1.67.0, postgres@^4.0.0, zod@^4.6.7.

It installs under your app's `app/` folder and runs with a server and an agent: set `ANTHROPIC_API_KEY`, and read the comment at the top of its `store.ts` for storage and auth. Its files:

- `app/run-console/agent.ts`
- `app/run-console/api/approve/route.ts`
- `app/run-console/api/route.ts`
- `app/run-console/api/runs/route.ts`
- `app/run-console/console/page.tsx`
- `app/run-console/db/connect.ts`
- `app/run-console/db/drizzle-repo.ts`
- `app/run-console/db/schema.ts`
- `app/run-console/page.tsx`
- `app/run-console/repo.ts`
- `app/run-console/store.ts`
- `app/run-console/ui.tsx`

## <ConsolePage>

Every run the agent started: the list, and for the one you open its recovery (if it stopped), trace, receipt and log.

No props.

## <Page>

No props.

## <RunConsoleChat>

The chat that drives runs. The console (consoleHref) shows them.

Props:

- `chat: RunChat`: The chat state from useCandorChat (AI SDK) or useClaudeAgent (Claude Agent SDK); the block works with either.
- `runsApi: string`: The runs API route, polled so a stopped-run card knows where its run is now, e.g. "/run-console/api/runs".
- `consoleHref: string`: Where the run console lives, linked from the chat.

## Functions

### connectRunDb

```ts
connectRunDb(url: string)
```

Open the block's database from a Postgres URL with the postgres-js driver. Swap the driver here (node-postgres, Neon's websocket Pool, …) if your app already uses another; it must support interactive transactions.

### drizzleRunRepo

```ts
drizzleRunRepo(db: RunDb)
```

### memoryRunRepo

```ts
memoryRunRepo()
```

In this server process's memory: gone on restart and not shared between instances. Transactions run one at a time, and a transaction that throws is rolled back to the state it started from. There's no demo data: runs appear as the agent starts them.

### setRunRepo

```ts
setRunRepo(repo: RunRepo)
```

Use your own storage (or a database you opened yourself). Call it once, before the routes run.

### runRepo

```ts
runRepo()
```

The storage in use: whatever setRunRepo() was given, else Postgres when DATABASE_URL is set, else memory.

### viewerOf

```ts
viewerOf(request: Request)
```

Who is asking. Replace with your auth; the demo reads a cookie (default Riley).

### startRun

```ts
startRun(template: TemplateId, viewer: string)
```

Start a run and carry it as far as it goes.

### retryStep

```ts
retryStep(runId: string, stepId: string, viewer: string)
```

Retry the failed step, then go on. Two retries at once: one goes on, the other is refused.

### skipStep

```ts
skipStep(runId: string, stepId: string, viewer: string)
```

Skip the failed step, then go on.

### stopRun

```ts
stopRun(runId: string, viewer: string)
```

Stop a failed run. What finished stays done; the answer says what that was and how to undo it.

### needsApproval

```ts
needsApproval(tool: string, input: unknown)
```

Whether a call changes something, so the person must allow it: starting a run with a writing step, or retrying or skipping when a writing step lies ahead. A call the store can't place counts as changing.

### recordUsage

```ts
recordUsage(runIds: Set<string>, usage: Run["usage"])
```

Add what the agent SDK measured for one request to the run it acted on. Usage is only recorded when the request touched exactly one run, because one request's tokens can't honestly be split between runs.

### priced

```ts
priced(model: string, inputTokens: number, outputTokens: number, cachedTokens?: number)
```

Tokens to a cost, only with a price you set in modelPrices.

### listRuns

```ts
listRuns()
```

Every run, newest first, as the console shows it.

### getRun

```ts
getRun(runId: string)
```

### resetStore

```ts
resetStore({ pace = 1 }: { pace?: number | undefined } = {})
```

For tests: start from no runs again, optionally with no waiting in steps. On a database this deletes the block's rows.

### runToolTitle

```ts
runToolTitle(tool: string, input: unknown)
```

Plain-words titles for the block's tools, the same for both agent SDKs.

### useRuns

```ts
useRuns(api: string, busy = false)
```

Every run from the store, checked every second while something runs or the agent replies.

## Types

### RunDb

```ts
PgDatabase<PgQueryResultHKT, any>
```

### AttemptRecord

One attempt at a step, timed by the store (epoch milliseconds).

```ts
{ startedAt: number; endedAt?: number | undefined; error?: string | undefined }
```

### StepRecord

```ts
{ id: string; title: string; kind: SpanKind; writes: boolean; undo?: string | undefined; status: RunStatus; attempts: AttemptRecord[] }
```

### UsageRecord

```ts
{ model: string; inputTokens: number; outputTokens: number; cachedTokens?: number | undefined; cost?: number | undefined }
```

### RunRecord

One run, as stored. Times are epoch milliseconds.

```ts
{ id: string template: string title: string environment: string status: RunStatus startedBy: string startedAt: number /** Set whenever the run stops (done, failed at a step, or stopped); cleared when it goes on. */ endedAt?: number | undefined steps: StepRecord[] /** Time the run spent waiting for the person after a failure. */ waits: { start: number; end?: number | undefined }[] log: LogItem[] /*
```

### NewRunRecord

```ts
Omit<RunRecord, "id" | "rev">
```

### RunPatch

`endedAt: null` clears it.

```ts
Partial<Pick<RunRecord, "status" | "steps" | "waits" | "log" | "usage">> & { endedAt?: number | null | undefined }
```

### TemplateId

```ts
keyof typeof templates
```

### RunView

```ts
{ entry: RunLogEntry status: RunStatus trace: { spans: TraceSpan[]; total: number } /** What the run used. A cost that wasn't measured is marked so on it, with why in costNote. */ receipt: RunReceipt log: LogItem[] recovery?: { title: string; steps: PlanStep[]; failedStepId: string; reason: string; retryWrites: boolean; skipWrites: boolean } | undefined }
```

### RunChat

What either adapter hook returns (useCandorChat or useClaudeAgent): the block works with both.

```ts
{ messages: MessageData[] composer: React.ComponentProps<typeof Composer> approvals: { id: string; tool: string; input?: unknown; title?: string | undefined; reason?: string | undefined }[] respond: (id: string, approved: boolean, reason?: string) => unknown error?: string | undefined }
```

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