Skip to main content

Agent Run

A running agent in one card. Its steps, the tools each one calls, the reasoning behind them, and the answer it lands on.

Refactoring the auth module1m 42sDone

The session and token stores now share one CookieStore interface. Both adapters live next to it, the parsing happens once, and every call site compiles unchanged.

Tests pass. Nothing else in the module needed touching.

4 of 4 steps
1'use client';
2
3import { AgentRun, type AgentStep } from '@/components/ui/agent-run';
4
5const STEPS: AgentStep[] = [
6 {
7 id: 'read',
8 title: 'Read the auth module',
9 status: 'complete',
10 detail: 'Scanned 12 files and the three tests that cover them',
11 files: ['auth.service.ts', 'session-store.ts', 'token-store.ts'],
12 },
13 {
14 id: 'plan',
15 title: 'Plan the refactor',
16 status: 'complete',
17 reasoning:
18 'The session store and the token store read the same cookie and expose the same three methods. One interface with two adapters removes the duplicated parsing and keeps both call sites untouched.',
19 },
20 {
21 id: 'write',
22 title: 'Rewrite the session store',
23 status: 'complete',
24 tools: [
25 {
26 id: 'read-file',
27 name: 'read_file',
28 status: 'complete',
29 description: 'Reads a file from the workspace',
30 input: { path: 'src/auth/session-store.ts' },
31 output: { lines: 84, exports: ['SessionStore', 'createSessionStore'] },
32 },
33 {
34 id: 'write-file',
35 name: 'write_file',
36 status: 'complete',
37 description: 'Writes a file to the workspace',
38 input: { path: 'src/auth/session-store.ts', bytes: 2210 },
39 output: 'ok',
40 },
41 ],
42 files: ['session-store.ts'],
43 },
44 {
45 id: 'test',
46 title: 'Run the tests',
47 status: 'complete',
48 detail: '3 files, 14 tests, all green',
49 },
50];
51
52const STARTED = new Date('2026-09-22T10:00:00Z');
53const FINISHED = new Date('2026-09-22T10:01:42Z');
54
55export function Default() {
56 return (
57 <div className="w-full max-w-xl">
58 <AgentRun
59 title="Refactoring the auth module"
60 status="complete"
61 steps={STEPS}
62 startedAt={STARTED}
63 finishedAt={FINISHED}
64 usage={{ used: 38400, max: 200000 }}
65 answer={
66 <>
67 <p>
68 The session and token stores now share one <code>CookieStore</code> interface. Both
69 adapters live next to it, the parsing happens once, and every call site compiles
70 unchanged.
71 </p>
72 <p>Tests pass. Nothing else in the module needed touching.</p>
73 </>
74 }
75 />
76 </div>
77 );
78}

Installation

pnpm dlx nachui add agent-run

Anatomy

1import { AgentRun, type AgentStep } from '@/components/ui/agent-run';
1<AgentRun
2 title="Refactoring the auth module"
3 status="running"
4 steps={steps}
5 startedAt={startedAt}
6 usage={{ used: 38400, max: 200000 }}
7 answer={<p>The session and token stores now share one interface.</p>}
8/>
Hand it the run as data and it lays the whole thing out: a header with the status and the elapsed time, a progress bar, one Task per step, the answer as a Response, and the token usage as a Context meter in the footer. Pass children instead to pick and order the parts yourself.

Composition

AgentRun is a hybrid: it is made of elements you already have, wired in order. Open the file and you will find them in this shape:
AgentRun
├── AgentRun.Header
├── AgentRun.Progress
├── AgentRun.Steps
│ └── Task (one per step)
│ ├── Task.Trigger
│ └── Task.Content
│ ├── Task.Item
│ ├── Task.File
│ ├── Reasoning
│ └── Tool
│ ├── Tool.Header
│ ├── Tool.Input
│ └── Tool.Output
├── AgentRun.Answer
│ └── Response
└── AgentRun.Footer
└── Context

Step shape

Each entry in steps is an AgentStep. Only id, title and status are required; the rest shows up when present.
FieldTypeDescription
idstringStable key
titlestringLabel of the step
status'pending' | 'active' | 'complete' | 'error'Drives the Task icon and which step opens
detailstringOne line under the title
filesstring[]Rendered as Task.File chips
reasoningstringRendered in a Reasoning panel, closed
toolsAgentTool[]Rendered as Tool cards with input and output
An AgentTool is { id, name, status, description?, input?, output?, error? }, with status being the Tool element's own ToolStatus. A tool that failed opens by default so the error is not hidden behind a click.

Live

The active step opens on its own and finished ones fold, the bar advances with every completed step, the answer streams with a caret once it starts, and the elapsed time stops when the run does.
Adding a composer to the docsWaiting
1'use client';
2
3import { useEffect, useRef, useState } from 'react';
4import { AgentRun, type AgentRunStatus, type AgentStep } from '@/components/ui/agent-run';
5
6const PLAN: AgentStep[] = [
7 { id: 'read', title: 'Read the docs page', status: 'pending', detail: 'Fetching llms.txt' },
8 {
9 id: 'find',
10 title: 'Find the component',
11 status: 'pending',
12 reasoning: 'The request mentions a composer with attachments, which matches prompt-input.',
13 tools: [
14 {
15 id: 'search',
16 name: 'search_registry',
17 status: 'complete',
18 input: { query: 'composer attachments' },
19 output: { slug: 'ai/prompt-input', score: 0.93 },
20 },
21 ],
22 },
23 {
24 id: 'install',
25 title: 'Install it',
26 status: 'pending',
27 files: ['prompt-input.tsx', 'attachments.tsx'],
28 },
29 { id: 'verify', title: 'Type check', status: 'pending', detail: 'tsc --noEmit' },
30];
31
32const ANSWER =
33 'Installed prompt-input and attachments into components/ui. The type check passes and the demo is on the page.';
34
35const STEP_MS = 1400;
36const CHAR_MS = 18;
37
38export function Live() {
39 const [status, setStatus] = useState<AgentRunStatus>('idle');
40 const [steps, setSteps] = useState<AgentStep[]>(PLAN);
41 const [answer, setAnswer] = useState('');
42 const [startedAt, setStartedAt] = useState<number | undefined>();
43 const [finishedAt, setFinishedAt] = useState<number | undefined>();
44 const timers = useRef<number[]>([]);
45
46 const clear = () => {
47 timers.current.forEach((timer) => window.clearTimeout(timer));
48 timers.current = [];
49 };
50
51 useEffect(() => clear, []);
52
53 const start = () => {
54 clear();
55 setStatus('running');
56 setSteps(PLAN.map((step, index) => ({ ...step, status: index === 0 ? 'active' : 'pending' })));
57 setAnswer('');
58 setStartedAt(Date.now());
59 setFinishedAt(undefined);
60
61 PLAN.forEach((_, index) => {
62 timers.current.push(
63 window.setTimeout(
64 () => {
65 setSteps((current) =>
66 current.map((step, position) => {
67 if (position <= index) return { ...step, status: 'complete' };
68 if (position === index + 1) return { ...step, status: 'active' };
69 return step;
70 }),
71 );
72 },
73 STEP_MS * (index + 1),
74 ),
75 );
76 });
77
78 const answerStart = STEP_MS * (PLAN.length + 1);
79 for (let index = 1; index <= ANSWER.length; index += 1) {
80 timers.current.push(
81 window.setTimeout(() => setAnswer(ANSWER.slice(0, index)), answerStart + CHAR_MS * index),
82 );
83 }
84 timers.current.push(
85 window.setTimeout(
86 () => {
87 setStatus('complete');
88 setFinishedAt(Date.now());
89 },
90 answerStart + CHAR_MS * ANSWER.length + 200,
91 ),
92 );
93 };
94
95 return (
96 <div className="flex w-full max-w-xl flex-col gap-4">
97 <button
98 type="button"
99 onClick={start}
100 disabled={status === 'running'}
101 className="border-border hover:bg-muted w-fit rounded-full border px-3 py-1.5 text-xs transition-colors disabled:opacity-50"
102 >
103 {status === 'running' ? 'Running' : status === 'complete' ? 'Run again' : 'Start the run'}
104 </button>
105 <AgentRun
106 title="Adding a composer to the docs"
107 status={status}
108 steps={steps}
109 startedAt={startedAt}
110 finishedAt={finishedAt}
111 answer={answer ? <p>{answer}</p> : undefined}
112 />
113 </div>
114 );
115}

API Reference

AgentRun

PropTypeDefaultDescription
stepsAgentStep[]-The run, one entry per step
status'idle' | 'running' | 'complete' | 'error''idle'State of the whole run
titlestring-Printed in the header
answerReactNode-The final response, rendered inside Response
usage{ used: number; max: number }-Token usage for the footer meter
startedAtDate | number-Start of the elapsed clock
finishedAtDate | number-Stops the clock
labelsPartial<AgentRunLabels>EnglishStatus names, input and output labels, progress text
defaultOpenboolean-Forces every step open or closed
childrenReactNode-Replaces the default layout with your own parts
classNamestring-Additional CSS classes

AgentRun.Header

PropTypeDefaultDescription
childrenReactNode-Replaces the title
classNamestring-Additional CSS classes

AgentRun.Progress

PropTypeDefaultDescription
classNamestring-Additional CSS classes

AgentRun.Steps

PropTypeDefaultDescription
classNamestring-Additional CSS classes

AgentRun.Answer

PropTypeDefaultDescription
childrenReactNode-Replaces the answer prop
classNamestring-Additional CSS classes

AgentRun.Footer

PropTypeDefaultDescription
childrenReactNode-Replaces the usage meter on the right
classNamestring-Additional CSS classes

Helpers

agentRunProgress(steps) returns { done, total, ratio }, the same numbers the bar and the footer print. useAgentRun() reads the run from context inside custom parts.
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue