<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cuelark]]></title><description><![CDATA[Cuelark]]></description><link>https://cuelark.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 03 Nov 2026 03:38:23 GMT</lastBuildDate><atom:link href="https://cuelark.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Think, act, ask: one agent run through Cuelark's 29 agent components]]></title><description><![CDATA[One coding agent run, moment by moment, with Cuelark UI's open-source shadcn agent components: reasoning, tools, approvals, citations, traces.]]></description><link>https://cuelark.hashnode.dev/think-act-ask-one-agent-run-through-cuelark-s-29-agent-components</link><guid isPermaLink="true">https://cuelark.hashnode.dev/think-act-ask-one-agent-run-through-cuelark-s-29-agent-components</guid><category><![CDATA[React]]></category><category><![CDATA[shadcn]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Cuelark]]></dc:creator><pubDate>Sat, 31 Oct 2026 12:33:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae5daa149e77bff2812e116/ef000d63-1a63-4bbc-9b32-94baf39587c4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>The map told you where each piece goes. This one follows a single run, moment by moment, and shows the code that renders each one.</em></p>
<hr />
<p>Two days ago we drew <a href="https://medium.com/@cuelarkofficial/where-everything-goes-in-an-ai-agent-interface-fde7972eb4f1">the map of an agent interface</a>: seven places every agent app shares, and which component belongs in each. That answered <em>where</em>.</p>
<p>This post answers <em>when</em>, and <em>how</em>. An agent run is a sequence of moments: someone <strong>asks</strong>, the agent <strong>thinks</strong>, it <strong>acts</strong>, it stops to get an <strong>approval</strong>, it <strong>answers</strong>, and someone who cares about cost or bugs <strong>observes</strong> the run afterwards. Cuelark UI's agent kit is 29 React components for shadcn/ui, grouped by exactly those moments.</p>
<p>We'll follow one run through all six, with the real props for every component. The run is fictional (file names, counts and timings are made up for the example), but every API is the one in the registry.</p>
<p><strong>The run:</strong> "Invoice totals are a cent off for EU customers. Find out why and fix it."</p>
<h2>Setup</h2>
<p>One line in <code>components.json</code>:</p>
<pre><code class="language-json">{
  "registries": {
    "@cuelark": "https://ui.cuelark.com/r/{name}.json"
  }
}
</code></pre>
<p>Then install by name, for example <code>npx shadcn@latest add @cuelark/reasoning @cuelark/tool @cuelark/plan</code>. Each component lands in <code>components/ui/</code> as source you own. The <a href="https://ui.cuelark.com/docs/installation">installation guide</a> covers the by-URL route and the shadcn MCP server.</p>
<h2>1. Ask</h2>
<p><strong>Prompt Input · Queue · Model Selector</strong> (and Suggestions)</p>
<pre><code class="language-tsx">&lt;PromptInput
  status={running ? "streaming" : "idle"}
  onSubmit={({ text, files }) =&gt; send(text, files)}
  onStop={stop}
  toolbar={&lt;ModelSelector models={models} value={model} onValueChange={setModel} /&gt;}
/&gt;
</code></pre>
<p><code>status="streaming"</code> is the one prop that matters most: it swaps Send for Stop and binds Esc. The <code>toolbar</code> slot is where Model Selector lives; it's a compact pill that opens a searchable list grouped by provider, with each model's context window.</p>
<p>Thirty seconds in, the user remembers credit notes. The agent is busy, so the follow-up waits:</p>
<pre><code class="language-tsx">&lt;Queue items={queued} onItemsChange={setQueued} /&gt;
</code></pre>
<p>Alt+↑/↓ reorders, × removes, and the agent takes items one at a time.</p>
<p><strong>Which one when:</strong> if your agent answers in two seconds, skip Queue. If you only have one model, skip Model Selector. Prompt Input is never optional.</p>
<h2>2. Think</h2>
<p><strong>Reasoning · Chain of Thought · Task</strong></p>
<p>The model starts thinking. Stream it, then let it get out of the way:</p>
<pre><code class="language-tsx">&lt;Reasoning streaming={isThinking}&gt;{reasoningText}&lt;/Reasoning&gt;
</code></pre>
<p>When <code>streaming</code> flips to false, it folds to "Thought for 6s". Omit <code>duration</code> and it measures itself.</p>
<p>Once there's an approach, named steps read better than raw thought:</p>
<pre><code class="language-tsx">&lt;ChainOfThought title="Tracking down the rounding error"&gt;
  &lt;ChainOfThoughtStep label="Searched for tax calculation" /&gt;
  &lt;ChainOfThoughtStep label="Read src/tax.ts and src/invoice.ts" /&gt;
  &lt;ChainOfThoughtStep status="active" label="Checking EU VAT rounding rules" /&gt;
  &lt;ChainOfThoughtStep status="pending" label="Propose a fix and run tests" /&gt;
&lt;/ChainOfThought&gt;
</code></pre>
<p>Steps default to <code>"complete"</code>, so you only mark the active and pending ones.</p>
<p><strong>Which one when:</strong> three components, three shapes of thinking.</p>
<ul>
<li><strong>Reasoning</strong> for free text the model produced. You don't control its structure.</li>
<li><strong>Chain of Thought</strong> for steps your orchestrator names, where order matters.</li>
<li><strong>Task</strong> for a checklist with a progress bar, where order doesn't: <code>&lt;Task title="Migrate invoices" items={[{ label: "Write helper", status: "done" }]} /&gt;</code>.</li>
</ul>
<p>A common mistake is rendering orchestrator steps inside Reasoning. Then the one thing the user wants to scan is in the faded window they've learned to ignore.</p>
<h2>3. Act</h2>
<p><strong>Tool · Subagent · Terminal · File Diff · Test Results</strong></p>
<p>The agent searches the code and fetches the VAT guidance:</p>
<pre><code class="language-tsx">&lt;Tool name="search_code" status="success" input={{ query: "roundTax" }} output={matches} duration={0.8} /&gt;
&lt;Tool name="web_fetch" status="running" input={{ url: vatGuideUrl }} /&gt;
</code></pre>
<p><code>status</code> runs <code>"pending"</code> → <code>"running"</code> → <code>"success"</code> or <code>"error"</code>. Keep <code>name</code> as the real tool name, not a friendly label. People learn <code>web_fetch</code> in one run, and the real name is what they'll paste into a bug report.</p>
<p>Between calls there's sometimes a beat with nothing to inspect. That's not a Tool; it's a status line:</p>
<pre><code class="language-tsx">&lt;TextShimmer&gt;Comparing rounding modes…&lt;/TextShimmer&gt;
</code></pre>
<p>Text Shimmer is the kit's status line for "doing something right now": CSS-only, and plain muted text under reduced motion.</p>
<p>Then the agent hands the tests to a helper:</p>
<pre><code class="language-tsx">&lt;Subagent name="test-runner" task="Run the invoice suite" status="running" activity="Running vitest…"&gt;
  &lt;Terminal title="vitest" lines={output} streaming={testsRunning} /&gt;
&lt;/Subagent&gt;
</code></pre>
<p>The sub-agent's own transcript sits on an indented rail inside it. When it's finished, set <code>status="done"</code> and pass <code>duration</code>. Once the run completes, swap the console for structure:</p>
<pre><code class="language-tsx">&lt;TestResults
  suites={[{ name: "invoice", tests: [
    { name: "rounds EU totals once", status: "pass" },
    { name: "keeps US line rounding", status: "pass" },
  ] }]}
/&gt;
</code></pre>
<p>A suite with a failure opens itself, failing assertion under the test.</p>
<p>And the change itself:</p>
<pre><code class="language-tsx">&lt;FileDiff
  filename="src/tax.ts"
  lines={[
    { type: "context", content: "export function roundTax(items: LineItem[]) {" },
    { type: "remove", content: "  return items.map((i) =&gt; round(i.tax)).reduce(sum, 0);" },
    { type: "add", content: "  return round(items.map((i) =&gt; i.tax).reduce(sum, 0));" },
    { type: "context", content: "}" },
  ]}
  onAccept={apply}
  onReject={discard}
/&gt;
</code></pre>
<p>Unchanged runs fold after six lines (<code>foldAfter</code>), and the header records Accept or Reject.</p>
<p><strong>Which one when:</strong></p>
<ul>
<li><strong>Terminal</strong> is the live process; <strong>Test Results</strong> is the verdict. Show Terminal while it runs, Test Results when it's done. Most apps show both, stacked.</li>
<li><strong>File Diff</strong> is a decision; a plain Code Block is just code. If you want a yes or no, it's a diff.</li>
<li><strong>Subagent</strong> whenever a different agent did the work, even a short one. Nesting is the only thing that keeps "who did this?" answerable.</li>
</ul>
<h2>4. Approve</h2>
<p><strong>Plan · Confirmation · Checkpoint</strong></p>
<p>Before touching code, the agent proposes a plan:</p>
<pre><code class="language-tsx">&lt;Plan
  steps={[
    { label: "Round the invoice total once in roundTax" },
    { label: "Call it after summing in invoice.ts" },
    { label: "Run the invoice suite" },
    { label: "Open a draft pull request" },
  ]}
  onApprove={run}
  onReject={cancel}
  autoRun={10}
/&gt;
</code></pre>
<p><code>autoRun={10}</code> drains a ring on Run; the plan starts unless someone cancels. We use it for low-stakes plans in trusted workspaces and leave it off everywhere else. <code>visible</code> (default 4) sets how many steps show before folding; <code>shortcuts={false}</code> frees ⌘Enter and Esc if your app needs them.</p>
<p>Later the agent wants to rewrite stored totals on staging. One action, real side effects:</p>
<pre><code class="language-tsx">&lt;Confirmation
  risk="high"
  title="Recalculate 214 staging invoices"
  description="Rewrites stored totals with the new rounding."
  command="pnpm tsx scripts/recalc-invoices.ts --env=staging"
  onApprove={execute}
  onDeny={skip}
/&gt;
</code></pre>
<p><code>risk="high"</code> turns approve into press-and-hold. The command is shown verbatim, so what you approve is what runs.</p>
<p>And before the diff is applied, a way back:</p>
<pre><code class="language-tsx">&lt;Checkpoint label="Before rounding fix" time="14:02" onRestore={restore} /&gt;
</code></pre>
<p><strong>Which one when:</strong> Plan approves <em>intent</em> across steps. Confirmation approves <em>one action</em>. Checkpoint isn't an approval at all; it's the undo you place right before anything you might regret. Approving a plan doesn't remove the need for a Checkpoint; they answer different questions. (For reloads, both Plan and Confirmation render past decisions as receipts. We covered that in the map post, so we won't repeat it here.)</p>
<h2>5. Answer</h2>
<p><strong>Streaming Text · Inline Citation · Sources · Artifact · Branch</strong></p>
<p>The answer streams:</p>
<pre><code class="language-tsx">&lt;StreamingText text={answer} streaming={answering} /&gt;
</code></pre>
<p>New chunks fade up out of a slight blur; text already on screen never moves.</p>
<p>The claim about VAT gets its evidence right next to it:</p>
<pre><code class="language-tsx">&lt;p&gt;
  For this invoice type, VAT is rounded on the total, not per line
  &lt;InlineCitation index={1} source={{ title: guideTitle, url: vatGuideUrl, excerpt: quote }} /&gt;.
&lt;/p&gt;
&lt;Sources sources={[{ title: guideTitle, url: vatGuideUrl }, { title: "src/tax.ts", url: fileUrl }]} /&gt;
</code></pre>
<p>If the agent also wrote a summary for the finance team, that's too big for a message:</p>
<pre><code class="language-tsx">&lt;Artifact kind="Document" title="Rounding fix: summary" onClose={close} onDownload={download}&gt;
  {summary}
&lt;/Artifact&gt;
</code></pre>
<p>If the user regenerates the answer, keep the old one:</p>
<pre><code class="language-tsx">&lt;Branch versions={[firstAnswer, secondAnswer]} onIndexChange={setIndex} /&gt;
</code></pre>
<p><strong>Which one when:</strong></p>
<ul>
<li><strong>Inline Citation and Sources</strong> are a pair, not alternatives. A Sources list on its own makes people guess which source backs which sentence.</li>
<li><strong>Artifact</strong> for anything people will read full screen or download; the message just points at it.</li>
<li><strong>Branch</strong> only when there really are versions. Don't show "1 / 1".</li>
</ul>
<h2>6. Observe</h2>
<p><strong>Context Meter · Trace Waterfall</strong></p>
<p>This moment isn't for every user. It's for whoever pays the bill or fixes the bug.</p>
<pre><code class="language-tsx">&lt;ContextMeter used={84000} max={200000} cost={0.42} breakdown={{ input, output, cached, reasoning }} /&gt;
</code></pre>
<p>A ring that warms from ink to amber to red as the context fills, with the token breakdown and cost on hover. (Figures above are example values.)</p>
<p>And the whole run on one timeline:</p>
<pre><code class="language-tsx">&lt;TraceWaterfall
  spans={[{
    id: "run", name: "fix-eu-rounding", kind: "agent", start: 0, end: 41,
    children: [
      { id: "m1", name: "plan", kind: "llm", start: 0, end: 6, tokens: 1840 },
      { id: "t1", name: "search_code", kind: "tool", start: 6, end: 6.8 },
      { id: "t2", name: "web_fetch", kind: "tool", start: 7, end: 9.5 },
      { id: "sa", name: "test-runner", kind: "agent", start: 22, end: 39,
        children: [{ id: "t3", name: "vitest", kind: "tool", start: 23, end: 38 }] },
    ],
  }]}
/&gt;
</code></pre>
<p><code>kind</code> is <code>"agent"</code>, <code>"llm"</code> or <code>"tool"</code>. <code>start</code> and <code>end</code> are seconds from the start of the run; leave <code>end</code> off and the span grows live. Set <code>error</code> on a span and it turns red. Hover any bar for duration, tokens and cost. You can see it with real interaction on the <a href="https://ui.cuelark.com/components/trace-waterfall">Trace Waterfall page</a>.</p>
<p><strong>Which one when:</strong> Chain of Thought is the user's view of progress, inside the turn. Trace Waterfall is the developer's view of the whole run, in the run header or a debug panel. If you put a waterfall in a consumer chat thread, you've built a dashboard nobody asked for.</p>
<h2>The whole turn, composed</h2>
<p>Everything above, as one assistant turn. Data loading is yours; every prop is real.</p>
<pre><code class="language-tsx">export function AgentTurn({ run }: { run: Run }) {
  return (
    &lt;Message from="assistant"&gt;
      &lt;MessageContent&gt;
        {/* think */}
        &lt;Reasoning streaming={run.thinking}&gt;{run.reasoning}&lt;/Reasoning&gt;
        &lt;ChainOfThought title={run.title}&gt;
          {run.steps.map((s) =&gt; (
            &lt;ChainOfThoughtStep key={s.label} status={s.status} label={s.label} /&gt;
          ))}
        &lt;/ChainOfThought&gt;

        {/* act */}
        {run.tools.map((t) =&gt; (
          &lt;Tool key={t.id} name={t.name} status={t.status} input={t.input} output={t.output} duration={t.duration} /&gt;
        ))}

        {/* approve */}
        &lt;Plan steps={run.plan} onApprove={run.approve} onReject={run.reject} defaultState={run.planDecision} /&gt;

        {/* act, delegated */}
        &lt;Subagent name="test-runner" task="Run the invoice suite" status={run.tests.status} duration={run.tests.duration}&gt;
          &lt;TestResults suites={run.tests.suites} /&gt;
        &lt;/Subagent&gt;

        {/* approve, then act */}
        &lt;Checkpoint label="Before rounding fix" time={run.checkpointAt} onRestore={run.restore} /&gt;
        &lt;FileDiff filename="src/tax.ts" lines={run.diff} onAccept={run.apply} onReject={run.discard} /&gt;

        {/* answer */}
        &lt;StreamingText text={run.answer} streaming={run.answering} /&gt;
        &lt;Sources sources={run.sources} /&gt;
      &lt;/MessageContent&gt;
    &lt;/Message&gt;
  );
}
</code></pre>
<p>Outside the thread, the observe moment:</p>
<pre><code class="language-tsx">&lt;header className="flex items-center justify-end gap-3"&gt;
  &lt;ContextMeter used={run.tokens.used} max={run.tokens.max} cost={run.cost} /&gt;
&lt;/header&gt;
&lt;TraceWaterfall spans={run.trace} /&gt;
</code></pre>
<p>Read the comments top to bottom and you get the run: think, act, approve, act, answer. The order of components <em>is</em> the story of the run. If your JSX reads in an order that doesn't match what happened, your users will be confused in the same place.</p>
<h2>What this run taught us</h2>
<ul>
<li><strong>One moment, one component.</strong> The kit has three ways to show thinking because there are three shapes of thinking. Pick by shape, not by looks.</li>
<li><strong>Defaults are compact, errors aren't.</strong> A failed Tool and a failing test suite open themselves. Everything else waits for a click.</li>
<li><strong>Approvals come in three kinds.</strong> Intent (Plan), action (Confirmation), and the way back (Checkpoint). Most apps ship only the middle one.</li>
<li><strong>Observe lives outside the thread.</strong> Context Meter and Trace Waterfall are for the people who pay for or debug the run.</li>
</ul>
<h2>Try it</h2>
<p>The fastest way to learn the kit is to take one apart. On the <a href="https://ui.cuelark.com/agents">live agent demos</a>, hover any part of the coding, research or data agent and it names itself; click the tag to open its page and copy the code.</p>
<p>Cuelark UI is free and open source (MIT): <a href="https://github.com/cuelark/ui">github.com/cuelark/ui</a>.</p>
<h2>FAQ</h2>
<p><strong>What are AI agent UI components?</strong> Components for the moments of an agent run: asking, thinking, acting, approving, answering and observing. A chat kit covers asking and answering; an agent kit covers the four in between.</p>
<p><strong>How many agent components does Cuelark UI have?</strong> 29. By moment: ask (Prompt Input, Queue, Model Selector, Suggestions), think (Reasoning, Chain of Thought, Task, Text Shimmer), act (Tool, Subagent, Terminal, File Diff, Test Results, Stack Trace, Code Block, File Tree, Web Preview), approve (Plan, Confirmation, Checkpoint), answer (Conversation, Message, Streaming Text, Inline Citation, Sources, Artifact, Branch) and observe (Context Meter, Trace Waterfall).</p>
<p><strong>Do they work with the Vercel AI SDK?</strong> Yes, or with any stream. They take plain props such as <code>status</code>, <code>input</code>, <code>output</code>, <code>steps</code> and <code>spans</code>.</p>
<p><strong>Are they shadcn/ui components?</strong> Yes. They install as source with the shadcn CLI and use your Tailwind CSS v4 theme tokens.</p>
]]></content:encoded></item></channel></rss>