# Run Header

> Header for one agent run: title, agent, model, branch, status, live elapsed clock, tokens, cost vs budget, step progress and Stop/Pause controls.

- **Slug:** `run-header` · **Category:** agent · **Status:** experimental
- **Import:** `import { RunHeader, RunHeaderProgress, RunHeaderTokens } from "@kura-ui/react/run-header";`
- **Docs:** https://kuro.abstergo.fyi/components/run-header
- **Registry item:** https://kuro.abstergo.fyi/r/run-header.json
- **Tags:** agent, run, session, header, status, cost, budget, tokens, timer, stop

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/run-header.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add run-header
# npm package
npm install @kura-ui/react
```

## When to use

- The top of a page or pane that shows one agent session (Claude Code, Codex, Devin-style runs): what it is doing, how long, how much it costs, and a way to stop it.
- A dense status row above a run's transcript, plan or fleet (variant='bar').
- Showing spend against a per-run budget, with the meter turning amber at 80% and red past 100%.
- Giving the human Stop / Pause / Resume with keyboard shortcuts while an agent works.

## When not to use

- The run's step-by-step plan — use task-list (todo-style) or agent-timeline (steps with connectors).
- Parallel subagents inside a run — use agent-fleet.
- Context-window usage of one conversation — use context-meter.
- A single tool invocation — use tool-call.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `title` | `React.ReactNode` | — | yes | What the run is doing, e.g. "Migrate session API to @acme/auth". Truncates. |
| `agent` | `string` | — | no | Agent name, e.g. "Kura". Panel meta line only. |
| `model` | `string` | — | no | Model id in mono, e.g. "claude-opus-5-5". Panel meta line only. |
| `status` | `"queued" \| "running" \| "waiting" \| "done" \| "failed" \| "skipped" \| "paused"` | — | yes | Run state, drawn with StatusGlyph. running = blue arc + shimmering 'Running'; waiting = amber 'Waiting for you'; done = 'Completed'; failed = red; skipped = 'Stopped'. queued/running/waiting/paused keep the clock ticking. |
| `startedAt` | `Date \| number` | — | no | Run start. Drives the live elapsed clock (m:ss, h:mm:ss). Without it the clock shows –:––. |
| `endedAt` | `Date \| number` | — | no | Run end. Freezes the clock at endedAt − startedAt (use for finished or persisted runs). |
| `tokens` | `{ input: number; output: number }` | — | no | Token usage. Panel shows "412k in · 38.1k out"; bar shows the total. |
| `cost` | `number` | — | no | Spend so far in USD, formatted $1.82. |
| `budget` | `number` | — | no | Budget in USD. Shows '$1.82 of $5.00' with a 4px meter; amber from 80%, red (and red cost) past 100%. |
| `branch` | `string` | — | no | Git branch in mono with a branch icon. In the bar it shows from ~768px container width. |
| `progress` | `{ done: number; total: number }` | — | no | Plan progress: '4 of 7' plus a segmented bar (continuous above 24 steps). The current segment is blue while running, amber while waiting, red when failed. |
| `actions` | `React.ReactNode` | — | no | Extra controls before Pause/Stop, e.g. an 'Open PR' Button once the run is done. |
| `onStop` | `() => void` | — | no | Shows Stop while the run is live (queued, running, waiting, paused). Bound to ⌘/Ctrl+. |
| `onPause` | `() => void` | — | no | Shows Pause while running. Bound to ⌘/Ctrl+Shift+P. |
| `onResume` | `() => void` | — | no | Shows Resume while paused. Bound to ⌘/Ctrl+Shift+P. |
| `variant` | `"bar" \| "panel"` | `"panel"` | no | bar = one 44px row (glyph, title, status, branch, steps, tokens, cost, clock, controls; stats drop out as the container narrows). panel = title + meta line + a stats strip (Elapsed, Steps, Tokens, Spend). |
| `shortcuts` | `boolean` | `true` | no | Bind ⌘/Ctrl+. (Stop) and ⌘/Ctrl+Shift+P (Pause/Resume) on the document. Only one run header on screen should bind them. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **RunHeader** (`data-slot="run-header"`) — Root <section> labelled by the title; `data-variant`, `data-status`. A size container.
- **Title** (`data-slot="run-header-title"`) — <h2>, truncated.
- **Status** (`data-slot="run-header-status"`) — Status word; shimmers while running, amber when waiting, red when failed.
- **Agent** (`data-slot="run-header-agent"`) — Agent name (panel).
- **Model** (`data-slot="run-header-model"`) — Model id in mono (panel).
- **Branch** (`data-slot="run-header-branch"`) — Branch icon + mono name.
- **Controls** (`data-slot="run-header-controls"`) — actions, then run-header-pause and run-header-stop.
- **Stats** (`data-slot="run-header-stats"`) — <dl>. In the panel each cell is run-header-stat with `data-stat` = elapsed | steps | tokens | cost, separated by hairlines.
- **Elapsed** (`data-slot="run-header-elapsed"`) — Mono tabular clock with reserved width so ticking never shifts layout.

## Accessibility

- Role: `region (section labelled by the title)`
- The stats are a <dl>: each value has a term (Elapsed, Steps, Tokens, Spend), visually hidden in the bar.
- A polite status region announces only status changes ('Run waiting for you'); the ticking clock and counters are not announced.
- Stop and Pause have tooltips with their shortcuts and aria-keyshortcuts. Status is never colour-only: the glyph shape and the status word carry it.
- The status glyph is decorative here (label=null) because the status word sits next to it.
- Reduced motion: The running arc, the current step's pulse and the status shimmer stop; meters change width without transition.

| Keys | Action |
| --- | --- |
| Mod+. | Stop the run (when onStop is set and the run is live) |
| Mod+Shift+P | Pause or resume |
| Tab | Actions, Pause, Stop |

## Notes for agents

- Keep one RunHeader mounted for the life of a run and update props; pass startedAt from the server so reloads keep the right clock, and endedAt once it finishes.
- Set shortcuts={false} on every RunHeader except the one for the focused run, otherwise ⌘. stops all of them.
- Map harness states: in-progress → running; needs approval / input → waiting; interrupted by the user → skipped ('Stopped'); error → failed.
- tokens.input should include cache reads if you want the number to match the bill; cost is whatever you computed — the component only formats it.
- Use the bar inside dense layouts (above a fleet or transcript) and the panel at the top of a run page.

## Examples

### Running (minimal)

Title, agent, model, status and a live clock.

```tsx
"use client";

import * as React from "react";
import { RunHeader } from "@kura-ui/react/run-header";

const minutesAgo = (m: number) => Date.now() - m * 60_000;

export function RunHeaderMinimal() {
  const [startedAt] = React.useState(() => minutesAgo(3.2));
  return (
    <RunHeader
      className="w-full max-w-2xl"
      title="Migrate session API to @acme/auth"
      agent="Kura"
      model="claude-opus-5-5"
      status="running"
      startedAt={startedAt}
      shortcuts={false}
    />
  );
}
```

### Live run (full)

Steps advance, tokens and spend climb against a $5 budget, the run waits for approval, then completes and offers Open PR. Pause, Resume and Stop work (⌘. and ⌘⇧P).

```tsx
"use client";

import { GitPullRequestArrow } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import type { AgentStatus } from "@kura-ui/react/status-glyph";
import { RunHeader } from "@kura-ui/react/run-header";

const minutesAgo = (m: number) => Date.now() - m * 60_000;

const SCRIPT: { status: AgentStatus; done: number; input: number; output: number; ms: number }[] = [
  { status: "running", done: 4, input: 268_400, output: 21_900, ms: 1800 },
  { status: "running", done: 4, input: 297_100, output: 24_300, ms: 1600 },
  { status: "waiting", done: 4, input: 301_800, output: 25_000, ms: 2600 },
  { status: "running", done: 5, input: 338_200, output: 29_400, ms: 1800 },
  { status: "running", done: 6, input: 376_900, output: 33_800, ms: 2000 },
  { status: "running", done: 7, input: 412_300, output: 38_100, ms: 1400 },
  { status: "done", done: 7, input: 412_300, output: 38_100, ms: 0 },
];

const priceOf = (input: number, output: number) => (input * 2.5 + output * 20) / 1_000_000 + 0.03;

export function RunHeaderFull() {
  const [startedAt] = React.useState(() => minutesAgo(11.4));
  const [beat, setBeat] = React.useState(0);
  const [override, setOverride] = React.useState<AgentStatus | null>(null);
  const [endedAt, setEndedAt] = React.useState<number | undefined>();
  const step = SCRIPT[Math.min(beat, SCRIPT.length - 1)] as (typeof SCRIPT)[number];
  const status = override ?? step.status;

  // biome-ignore lint/correctness/useExhaustiveDependencies: re-arm the timer on every beat
  React.useEffect(() => {
    if (override != null || step.ms === 0) return;
    const t = setTimeout(() => setBeat((b) => Math.min(b + 1, SCRIPT.length - 1)), step.ms);
    return () => clearTimeout(t);
  }, [beat, override, step.ms]);

  React.useEffect(() => {
    if ((status === "done" || status === "skipped") && endedAt == null) setEndedAt(Date.now());
  }, [status, endedAt]);

  return (
    <RunHeader
      className="w-full max-w-3xl"
      title="Migrate session API to @acme/auth"
      agent="Kura"
      model="claude-opus-5-5"
      branch="kura/auth-client"
      status={status}
      startedAt={startedAt}
      endedAt={endedAt}
      progress={{ done: step.done, total: 7 }}
      tokens={{ input: step.input, output: step.output }}
      cost={priceOf(step.input, step.output)}
      budget={5}
      onPause={() => setOverride("paused")}
      onResume={() => setOverride(null)}
      onStop={() => setOverride("skipped")}
      actions={
        status === "done" ? (
          <Button size="sm" variant="primary" className="animate-kura-fade-in">
            <GitPullRequestArrow />
            Open PR
          </Button>
        ) : null
      }
    />
  );
}
```

### Bar (variant)

One dense row per run: running, waiting for you, failed and completed.

```tsx
"use client";

import * as React from "react";
import { RunHeader } from "@kura-ui/react/run-header";

export function RunHeaderBar() {
  const [t] = React.useState(() => Date.now());
  return (
    <div className="flex w-full max-w-3xl flex-col gap-2">
      <RunHeader
        variant="bar"
        title="Migrate session API to @acme/auth"
        status="running"
        branch="kura/auth-client"
        startedAt={t - 8 * 60_000 - 42_000}
        progress={{ done: 4, total: 7 }}
        tokens={{ input: 268_400, output: 21_900 }}
        cost={1.12}
        budget={5}
        onPause={() => {}}
        onStop={() => {}}
        shortcuts={false}
      />
      <RunHeader
        variant="bar"
        title="Patch src/middleware.ts"
        status="waiting"
        branch="kura/auth-client"
        startedAt={t - 9 * 60_000 - 5_000}
        progress={{ done: 4, total: 7 }}
        tokens={{ input: 301_800, output: 25_000 }}
        cost={1.31}
        budget={5}
        onStop={() => {}}
        shortcuts={false}
      />
      <RunHeader
        variant="bar"
        title="Fix login.test.ts › refreshes an expired session"
        status="failed"
        branch="kura/auth-client"
        startedAt={t - 14 * 60_000}
        endedAt={t - 2 * 60_000 - 51_000}
        progress={{ done: 5, total: 7 }}
        tokens={{ input: 356_000, output: 31_200 }}
        cost={1.58}
        budget={5}
      />
      <RunHeader
        variant="bar"
        title="Bump @acme/auth to 2.4.0"
        status="done"
        branch="kura/auth-client"
        startedAt={t - 31 * 60_000}
        endedAt={t - 19 * 60_000 - 6_000}
        progress={{ done: 7, total: 7 }}
        tokens={{ input: 412_300, output: 38_100 }}
        cost={1.82}
        budget={5}
      />
    </div>
  );
}
```

### With agent-fleet (composition)

A bar header above the run's subagents.

```tsx
"use client";

import * as React from "react";
import { AgentFleet, type FleetAgent } from "@kura-ui/react/agent-fleet";
import { RunHeader } from "@kura-ui/react/run-header";

const minutesAgo = (m: number) => Date.now() - m * 60_000;

const FLEET: FleetAgent[] = [
  {
    id: "explorer",
    name: "explorer",
    role: "Maps call sites",
    status: "done",
    activity: "Found 23 calls to fetchUser() in 9 files",
    startedAt: minutesAgo(9),
    endedAt: minutesAgo(7.4),
    tokens: 84_200,
    cost: 0.21,
  },
  {
    id: "test-writer",
    name: "test-writer",
    role: "Updates tests",
    status: "running",
    activity: "Rewriting mocks in src/auth/__tests__/login.test.ts",
    progress: 0.62,
    startedAt: minutesAgo(3.1),
    tokens: 51_600,
    cost: 0.14,
  },
  {
    id: "reviewer",
    name: "reviewer",
    role: "Reviews the diff",
    status: "queued",
    activity: "Starts after test-writer",
  },
];

export function RunHeaderComposition() {
  const [startedAt] = React.useState(() => minutesAgo(9.6));
  return (
    <div className="flex w-full max-w-3xl flex-col gap-2">
      <RunHeader
        variant="bar"
        title="Migrate session API to @acme/auth"
        status="running"
        branch="kura/auth-client"
        startedAt={startedAt}
        progress={{ done: 5, total: 7 }}
        tokens={{ input: 338_200, output: 29_400 }}
        cost={1.46}
        budget={5}
        onPause={() => {}}
        onStop={() => {}}
        shortcuts={false}
      />
      <AgentFleet
        agents={FLEET}
        density="compact"
        defaultSelectedId="test-writer"
        onSelect={() => {}}
      />
    </div>
  );
}
```

## Dependencies

- npm: `lucide-react`
- Kura registry items: `kura-theme`, `button`, `icon-button`, `kura-format`, `shimmer-text`, `status-glyph`, `tooltip`, `use-elapsed`, `use-hotkey`, `utils`
- Related: `agent-fleet`, `task-list`, `checkpoints`, `context-meter`, `agent-timeline`
