Skip to content
Star on GitHub
Agent statusAgent phases
Style
Accent
Sound

Built by DesignMe, free to use under the MIT licence.

Expert design and dev support for startups and enterprises, from Astro sites to React apps and AI products.

Book a call

Agent phases

One line for which phase the agent is in, from thinking to done, that stops to ask you, says when it is slow, and folds to how long it took. Free and MIT licensed, as HTML, a typed React component or an Astro component, in two styles with light and dark mode.

$npx designme add agent-phases

    01

    Install

    Add it to your project with the CLI.

    HTML, CSS and JavaScript

    $npx designme add agent-phases

    React

    $npx designme add agent-phases --react

    Astro

    $npx designme add agent-phases --astro

    The CLI copies the source into your project, so you own it and can change anything. The HTML version lands in ./designme with the shared core and a demo.html. The React version lands in src/components/designme and needs React 19; it works in Next.js, Vite and Astro islands. The Astro version lands in src/components/designme too, with the same markup, CSS and script as the HTML version. See Astro.

    shadcn CLI, React

    $npx shadcn@latest add https://designme.dev/r/shadcn/agent-phases.json

    shadcn CLI, Astro

    $npx shadcn@latest add https://designme.dev/r/shadcn/astro/agent-phases.json

    In a project set up for shadcn/ui, its CLI adds the same files to your components folder, under designme/, with the shared core. To give the components your shadcn or Tailwind v4 theme, add the theme bridge.

    02

    About

    What it is for and what it does.

    One line that says which phase the agent is in: Thinking, Searching, Reading, Waiting for you, Writing and Done, each with its own glyph, over a track of six segments that fill phase by phase. When the agent needs a decision it stops the clock and asks, with Approve and Not now; when a phase runs long it says it is still working and offers Stop; when a step fails the line turns red and offers Retry. Once done it reads "Thought for 12s" and opens to the time each phase took.

    • Above an agent's reply, when people only need to know what stage it is at rather than every step.
    • When the agent can stop to ask for approval in the middle of its work.
    • Where the detailed trace (Thinking or Reasoning trail) would be too much, as a lighter companion to them.
    03

    Usage

    Import it in React and drive it from your own state, or place it in an Astro page.

    React

    import { AgentPhases } from '@/components/designme/agent-phases/AgentPhases';

    React example

    The demo above as a React component, 48 lines you can copy.

    'use client';
    // The animated preview from designme.dev, rebuilt from the React component: the search times out and is retried, reading is
    // slow, the agent asks before offering a refund, and the finished line opens to the phases.
    import { useEffect, useRef, useState } from 'react';
    import { AgentPhases, type AgentPhase } from './AgentPhases';
    import { useStory, useWait } from '../lib/useScript';
    
    export default function AgentPhasesDemo() {
      const ref = useRef<HTMLDivElement>(null);
      const wait = useWait();
      const [phase, setPhase] = useState<AgentPhase>('thinking');
      const [label, setLabel] = useState<string | undefined>('Thinking about the question');
      const [failed, setFailed] = useState(false);
      const [slow, setSlow] = useState(false);
      const [open, setOpen] = useState(false);
      const [took, setTook] = useState<Partial<Record<AgentPhase, number>>>({});
      const answer = useRef<((v: boolean) => void) | null>(null);
      const until = (f: (done: (v: boolean) => void) => void) => new Promise<boolean>(res => f(res));
      // the agent's work, which goes on whatever the visitor does; it waits for a person only to retry and to approve
      useEffect(() => { void (async () => {
        await wait(1600); setTook({ thinking: 2 });
        setPhase('searching'); setLabel('Searching the help centre'); await wait(1300);
        setFailed(true); await until(res => { answer.current = res; }); setFailed(false);
        setLabel('Searching the help centre again'); await wait(1200); setTook(t => ({ ...t, searching: 4 }));
        setPhase('reading'); setLabel('Reading 3 articles'); await wait(1800); setSlow(true); await wait(2200); setSlow(false); setTook(t => ({ ...t, reading: 4 }));
        setPhase('waiting'); setLabel(undefined);
        const ok = await until(res => { answer.current = res; });
        setTook(t => ({ ...t, waiting: 2 }));
        setPhase('writing'); setLabel(ok ? 'Writing the reply with the refund' : 'Writing the reply without a refund'); await wait(2000); setTook(t => ({ ...t, writing: 2 }));
        setPhase('done'); setLabel(undefined);
      })(); }, []); // eslint-disable-line react-hooks/exhaustive-deps
      const reply = (v: boolean) => { const f = answer.current; answer.current = null; f?.(v); };
      // the person in the preview: retry, approve, then open the finished line
      useStory(ref, async ({ wait, press }) => {
        await wait(4400); press('.dm-aph-n .dm-pill');
        await wait(9000); press('.dm-aph-n .dm-primary');
        await wait(3500); press('.dm-aph-h');
      }, { hold: false });
      return (
        <div ref={ref} style={{ width: '100%' }}>
          <AgentPhases phase={phase} label={label} took={took} open={open} onOpenChange={setOpen}
            error={failed ? { text: 'The search timed out', detail: "The help centre didn't answer in 10s", onRetry: () => reply(true) } : undefined}
            slow={slow ? { text: 'Still working, the refund policy is a long page' } : undefined}
            ask={{ text: 'Offer Maya a refund of $48?', onApprove: () => reply(true), onDecline: () => reply(false) }} />
        </div>
      );
    }
    

    Astro

    In a page in src/pages. It plays the demo above; change its agent-phases.js to show your own data. Attributes like class go on its root element.

    ---
    import AgentPhases from '../components/designme/agent-phases/AgentPhases.astro';
    ---
    <AgentPhases />
    <AgentPhases data-style="soft" class="my-class" />
    04

    Props

    Everything AgentPhases takes.

    PropTypeWhat it does
    phaserequiredAgentPhase
    labelstringWhat the line says, like "Reading 3 articles". Defaults to the phase's name; once done, "Thought for 12s"
    secondsnumberSeconds so far. Counts by itself while left out (and stops while waiting for you)
    error{ text: string; detail?: string; onRetry: () => void }A step that failed: the line turns red and says so, with Retry
    slow{ text: string; onStop?: () => void }Shown while the phase takes long: "Still working…", with Stop
    ask{ text: ReactNode; approveLabel?: string; onApprove: () => void; onDecline?: () => void }What the agent waits for in the waiting phase, with Approve and Not now
    tookPartial<Record<AgentPhase, number>>Seconds each phase took, by phase, for the list a finished line opens to
    openboolean
    defaultOpenboolean
    onOpenChange(open: boolean) => void
    classNamestring
    05

    Code

    The full source, as the CLI installs it.

    AgentPhases.tsxReact · 70 lines
    'use client';
    import { useEffect, useRef, useState, type ReactNode } from 'react';
    import { cx } from '../lib/cx';
    import { useControllable, useSeconds } from '../lib/hooks';
    import { Icon } from '../lib/icons';
    import { Fold, Radar, Swap, Think } from '../lib/motion';
    import '../designme.css';
    import './agent-phases.css';
    
    export type AgentPhase = 'thinking' | 'searching' | 'reading' | 'waiting' | 'writing' | 'done';
    
    export interface AgentPhasesProps {
      phase: AgentPhase;
      /** What the line says, like "Reading 3 articles". Defaults to the phase's name; once done, "Thought for 12s" */
      label?: string;
      /** Seconds so far. Counts by itself while left out (and stops while waiting for you) */
      seconds?: number;
      /** A step that failed: the line turns red and says so, with Retry */
      error?: { text: string; detail?: string; onRetry: () => void };
      /** Shown while the phase takes long: "Still working…", with Stop */
      slow?: { text: string; onStop?: () => void };
      /** What the agent waits for in the waiting phase, with Approve and Not now */
      ask?: { text: ReactNode; approveLabel?: string; onApprove: () => void; onDecline?: () => void };
      /** Seconds each phase took, by phase, for the list a finished line opens to */
      took?: Partial<Record<AgentPhase, number>>;
      open?: boolean;
      defaultOpen?: boolean;
      onOpenChange?: (open: boolean) => void;
      className?: string;
    }
    
    const ORDER: AgentPhase[] = ['thinking', 'searching', 'reading', 'waiting', 'writing', 'done'];
    const NAME: Record<AgentPhase, string> = { thinking: 'Thinking', searching: 'Searching', reading: 'Reading', waiting: 'Waiting for you', writing: 'Writing', done: 'Done' };
    const KEY: Record<AgentPhase, string> = { thinking: 'think', searching: 'search', reading: 'read', waiting: 'wait', writing: 'write', done: 'done' };
    
    /** One line for which phase the agent is in, from thinking to done: it stops to ask you, says when it is slow or failed, and folds to how long it took. */
    export function AgentPhases({ phase, label, seconds, error, slow, ask, took = {}, open, defaultOpen = false, onOpenChange, className }: AgentPhasesProps) {
      const counted = useSeconds(seconds === undefined && phase !== 'done' && phase !== 'waiting');
      const secs = seconds ?? counted;
      const [isOpen, setOpen] = useControllable(open, defaultOpen, onOpenChange);
      const at = ORDER.indexOf(phase), done = phase === 'done';
      const p = error ? 'err' : KEY[phase];
      const line = error ? error.text : label ?? (done ? `Thought for ${secs}s` : NAME[phase]);
      const glyph = error ? <Icon name="alert" /> : { thinking: <Think />, searching: <Radar />, reading: <Icon name="file-text" />, waiting: <Icon name="hand" />, writing: <Icon name="edit" />, done: <Icon name="ok" /> }[phase];
      // only a change of phase is announced
      const [said, setSaid] = useState('');
      const last = useRef('');
      useEffect(() => { const t = error ? 'Error: ' + error.text : done ? `Done, thought for ${secs}s` : NAME[phase]; if (t !== last.current) { last.current = t; setSaid(t); } }, [phase, !!error]); // eslint-disable-line react-hooks/exhaustive-deps
      return (
        <div data-dm="" data-p={p} className={cx('dm-aph', className)}>
          <button type="button" className="dm-aph-h" aria-expanded={done && isOpen} aria-disabled={!done || undefined} onClick={() => { if (done) setOpen(!isOpen); }}>
            <span className="dm-aph-g" key={p}><span className="dm-enter" style={{ display: 'grid' }}>{glyph}</span></span>
            <Swap className={cx('dm-aph-l', !done && !error && phase !== 'waiting' && 'dm-shim')}>{line}</Swap>
            <span className="dm-aph-t dm-num">{done ? '' : secs + 's'}</span>
            <Icon name="chev-d" size="xs" className="dm-aph-chev" />
          </button>
          <div className="dm-aph-tr" aria-hidden="true">{ORDER.map((ph, j) => <i key={ph} className={cx(j === at && !done && 'dm-now')}>{j <= at && <b />}</i>)}</div>
          <div className="dm-aph-x">
            {error ? <div className="dm-aph-n dm-err dm-enter"><Icon name="alert" size="xs" /><span>{error.detail ?? error.text}</span><button type="button" className="dm-pill dm-line dm-sm" onClick={error.onRetry}><Icon name="refresh" size="xs" />Retry</button></div>
              : phase === 'waiting' && ask ? <div className="dm-aph-n dm-ask dm-enter"><span>{ask.text}</span><button type="button" className="dm-pill dm-primary dm-sm" onClick={ask.onApprove}>{ask.approveLabel ?? 'Approve'}</button>{ask.onDecline && <button type="button" className="dm-pill dm-ghost dm-sm" onClick={ask.onDecline}>Not now</button>}</div>
              : slow && !done ? <div className="dm-aph-n dm-enter"><Icon name="clock" size="xs" /><span>{slow.text}</span>{slow.onStop && <button type="button" className="dm-pill dm-line dm-sm" onClick={slow.onStop}><Icon name="stop" size="xs" />Stop</button>}</div> : null}
          </div>
          <Fold open={done && isOpen}>
            <ol className="dm-aph-list">{ORDER.slice(0, 5).filter(ph => took[ph] !== undefined).map(ph => <li key={ph}><Icon name="check" size="xs" /><span>{NAME[ph]}</span><span className="dm-num">{took[ph]}s</span></li>)}</ol>
          </Fold>
          <p className="dm-aph-sr" role="status">{said}</p>
        </div>
      );
    }
    
    agent-phases.cssReact · 42 lines
    /* Generated from designme.dev by scripts/sync.mjs. Edit the site, then sync. */
    /* ---------- Agent phases ---------- */
    /* which phase the agent is in, in one line: its glyph, its name shimmering while it lasts, and the time; a track of six short
       segments under it fills phase by phase. When it needs you, is slow or fails, a line under the track says so with what to do;
       once done the line reads how long it took and opens to the phases */
    .dm-aph{width:100%;max-width:420px;margin-inline:auto;display:flex;flex-direction:column;gap:var(--dm-s-2);font-size:14px}
    .dm-aph-h{display:flex;align-items:center;gap:10px;width:calc(100% + 16px);margin:0 -8px;padding:6px var(--dm-s-2);border-radius:calc(10px * var(--dm-rk));text-align:left;transition:background var(--dm-t-fast)}
    .dm-aph-h[aria-disabled="true"]{cursor:default}
    @media (hover:hover) and (pointer:fine){.dm-aph-h:not([aria-disabled="true"]):hover{background:var(--dm-row)}}
    .dm-aph-g{width:18px;height:18px;flex:none;display:grid;place-items:center;color:var(--dm-accent-fg,var(--dm-accent))}
    .dm-aph[data-p="wait"] .dm-aph-g{color:var(--dm-warn-mark)}
    .dm-aph[data-p="err"] .dm-aph-g{color:var(--dm-bad)}
    .dm-aph[data-p="done"] .dm-aph-g{color:var(--dm-ok)}
    .dm-aph-l{flex:1;min-width:0;color:var(--dm-fg)}
    .dm-aph-t{font-size:13px;color:var(--dm-muted)}
    .dm-aph-chev{color:var(--dm-muted);visibility:hidden;transition:transform .32s var(--dm-ease)}
    .dm-aph[data-p="done"] .dm-aph-chev{visibility:visible}
    .dm-aph-h[aria-expanded="false"] .dm-aph-chev{transform:rotate(-90deg)}
    .dm-aph-tr{display:flex;gap:3px;height:3px}
    .dm-aph-tr i{flex:1;border-radius:2px;background:var(--dm-line);position:relative;overflow:hidden}
    .dm-aph-tr i b{position:absolute;inset:0;border-radius:inherit;background:var(--dm-accent-mark,var(--dm-accent));transform-origin:left;animation:dm-aphfill .5s var(--dm-ease) backwards}
    .dm-aph-tr i.dm-now b{background:color-mix(in srgb,var(--dm-accent-mark,var(--dm-accent)) 45%,var(--dm-line));animation:dm-aphbreathe 1.6s var(--dm-ease-io) infinite}
    .dm-aph[data-p="wait"] .dm-aph-tr i.dm-now b{background:var(--dm-warn-mark);animation:none}
    .dm-aph[data-p="err"] .dm-aph-tr i.dm-now b{background:var(--dm-bad);animation:none}
    .dm-aph[data-p="done"] .dm-aph-tr i b{background:var(--dm-ok)}
    @keyframes dm-aphfill{from{transform:scaleX(0)}}
    @keyframes dm-aphbreathe{50%{opacity:.45}}
    .dm-aph-x{min-height:40px;display:flex;align-items:center}
    .dm-aph-n{display:flex;align-items:center;gap:var(--dm-s-2);width:100%;font-size:13px;color:var(--dm-fg2)}
    .dm-aph-n>span{flex:1;min-width:0}
    .dm-aph-n.dm-ask{padding:6px 6px 6px var(--dm-s-3);border-radius:var(--dm-r-in);background:var(--dm-row);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dm-line) 50%,transparent);color:var(--dm-fg)}
    .dm-aph-n.dm-err{color:var(--dm-bad)}
    .dm-aph-n .dm-pill{flex:none}
    .dm-aph-list{list-style:none;margin:0;padding:0 0 var(--dm-s-1);display:flex;flex-direction:column;gap:2px}
    .dm-aph-list li{display:flex;align-items:center;gap:10px;padding:4px 0;font-size:13px;color:var(--dm-fg2)}
    .dm-aph-list li .dm-i{color:var(--dm-ok)}
    .dm-aph-list li span:nth-child(2){flex:1}
    .dm-aph-list li .dm-num{color:var(--dm-muted);font-size:12px}
    .dm-aph-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    @media (prefers-reduced-motion:reduce){.dm-aph-tr i.dm-now b{animation:none}.dm-aph .dm-think.dm-on{animation:none}.dm-aph .dm-think.dm-on i{animation:none}}
    :root:not([data-style="soft"]) .dm-aph-tr i:not(:where([data-style="soft"],[data-style="soft"] *)){height:3px}
    
    AgentPhases.astroAstro · 30 lines
    ---
    // Generated from designme.dev by scripts/sync.mjs. Edit the site, then sync.
    // Agent phases: One line for which phase the agent is in, from thinking to done, that stops to ask you, says when it is slow, and folds to how long it took.
    // The markup, CSS and script are designme.dev's own, run by the shared core in ../core, which mounts the component when it scrolls
    // into view and plays its demo. To show your own data, change agent-phases.js. The markup is kept exactly as it is (is:raw).
    import type { HTMLAttributes } from 'astro/types';
    import '../core/designme.css';
    import './agent-phases.css';
    
    export interface Props extends HTMLAttributes<'div'> {
      /** "soft" draws it in the Soft style. Leave it out for Blueprint, or for the style set on <html> (data-style="soft") */
      'data-style'?: 'soft';
    }
    const { class: className, ...rest } = Astro.props;
    ---
    <div class:list={['card', 'bare', className]} data-c="agent-phases" {...rest}>
      <div class="stage-in"><Fragment is:raw>
    <div class="aph">
      <button class="aph-h" aria-expanded="false" aria-disabled="true"><span class="aph-g"></span><span class="aph-l"></span><span class="aph-t num">0s</span><svg class="i xs aph-chev"><use href="#i-chev-d"/></svg></button>
      <div class="aph-tr" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></div>
      <div class="aph-x"></div>
      <div class="fold shut aph-fold"><div><ol class="aph-list"></ol></div></div>
      <p class="aph-sr" role="status"></p>
    </div>
      </Fragment></div>
    </div>
    <script>
      import './agent-phases.js';
    </script>
    
    agent-phases.htmlHTML · 12 lines
    <div class="card bare" data-c="agent-phases">
      <div class="stage-in">
        <div class="aph">
          <button class="aph-h" aria-expanded="false" aria-disabled="true"><span class="aph-g"></span><span class="aph-l"></span><span class="aph-t num">0s</span><svg class="i xs aph-chev"><use href="#i-chev-d"/></svg></button>
          <div class="aph-tr" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></div>
          <div class="aph-x"></div>
          <div class="fold shut aph-fold"><div><ol class="aph-list"></ol></div></div>
          <p class="aph-sr" role="status"></p>
        </div>
      </div>
    </div>
    
    agent-phases.cssHTML · 41 lines
    /* ---------- Agent phases ---------- */
    /* which phase the agent is in, in one line: its glyph, its name shimmering while it lasts, and the time; a track of six short
       segments under it fills phase by phase. When it needs you, is slow or fails, a line under the track says so with what to do;
       once done the line reads how long it took and opens to the phases */
    .aph{width:100%;max-width:420px;margin-inline:auto;display:flex;flex-direction:column;gap:var(--dm-s-2);font-size:14px}
    .aph-h{display:flex;align-items:center;gap:10px;width:calc(100% + 16px);margin:0 -8px;padding:6px var(--dm-s-2);border-radius:calc(10px * var(--dm-rk));text-align:left;transition:background var(--dm-t-fast)}
    .aph-h[aria-disabled="true"]{cursor:default}
    @media (hover:hover) and (pointer:fine){.aph-h:not([aria-disabled="true"]):hover{background:var(--dm-row)}}
    .aph-g{width:18px;height:18px;flex:none;display:grid;place-items:center;color:var(--dm-accent-fg,var(--dm-accent))}
    .aph[data-p="wait"] .aph-g{color:var(--dm-warn-mark)}
    .aph[data-p="err"] .aph-g{color:var(--dm-bad)}
    .aph[data-p="done"] .aph-g{color:var(--dm-ok)}
    .aph-l{flex:1;min-width:0;color:var(--dm-fg)}
    .aph-t{font-size:13px;color:var(--dm-muted)}
    .aph-chev{color:var(--dm-muted);visibility:hidden;transition:transform .32s var(--dm-ease)}
    .aph[data-p="done"] .aph-chev{visibility:visible}
    .aph-h[aria-expanded="false"] .aph-chev{transform:rotate(-90deg)}
    .aph-tr{display:flex;gap:3px;height:3px}
    .aph-tr i{flex:1;border-radius:2px;background:var(--dm-line);position:relative;overflow:hidden}
    .aph-tr i b{position:absolute;inset:0;border-radius:inherit;background:var(--dm-accent-mark,var(--dm-accent));transform-origin:left;animation:aphfill .5s var(--dm-ease) backwards}
    .aph-tr i.now b{background:color-mix(in srgb,var(--dm-accent-mark,var(--dm-accent)) 45%,var(--dm-line));animation:aphbreathe 1.6s var(--dm-ease-io) infinite}
    .aph[data-p="wait"] .aph-tr i.now b{background:var(--dm-warn-mark);animation:none}
    .aph[data-p="err"] .aph-tr i.now b{background:var(--dm-bad);animation:none}
    .aph[data-p="done"] .aph-tr i b{background:var(--dm-ok)}
    @keyframes aphfill{from{transform:scaleX(0)}}
    @keyframes aphbreathe{50%{opacity:.45}}
    .aph-x{min-height:40px;display:flex;align-items:center}
    .aph-n{display:flex;align-items:center;gap:var(--dm-s-2);width:100%;font-size:13px;color:var(--dm-fg2)}
    .aph-n>span{flex:1;min-width:0}
    .aph-n.ask{padding:6px 6px 6px var(--dm-s-3);border-radius:var(--dm-r-in);background:var(--dm-row);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dm-line) 50%,transparent);color:var(--dm-fg)}
    .aph-n.err{color:var(--dm-bad)}
    .aph-n .pill{flex:none}
    .aph-list{list-style:none;margin:0;padding:0 0 var(--dm-s-1);display:flex;flex-direction:column;gap:2px}
    .aph-list li{display:flex;align-items:center;gap:10px;padding:4px 0;font-size:13px;color:var(--dm-fg2)}
    .aph-list li .i{color:var(--dm-ok)}
    .aph-list li span:nth-child(2){flex:1}
    .aph-list li .num{color:var(--dm-muted);font-size:12px}
    .aph-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    @media (prefers-reduced-motion:reduce){.aph-tr i.now b{animation:none}.aph .think.on{animation:none}.aph .think.on i{animation:none}}
    :root:not([data-style="soft"]) .aph-tr i:not(:where([data-style="soft"],[data-style="soft"] *)){height:3px}
    
    agent-phases.jsHTML · 62 lines
    DM.def('agent-phases', (r, c) => {
      const root = $('.aph', r), head = $('.aph-h', r), g = $('.aph-g', r), lab = $('.aph-l', r), tm = $('.aph-t', r), segs = $$('.aph-tr i', r), x = $('.aph-x', r), fold = $('.aph-fold', r), list = $('.aph-list', r), sr = $('.aph-sr', r);
      // the phases in order, each with its glyph and what the line says while it lasts
      const P = [
        ['think', 'Thinking', '<span class="think on"><i></i><i></i><i></i><i></i></span>', 'Thinking about the question'],
        ['search', 'Searching', `<span class="radar"></span>`, 'Searching the help centre'],
        ['read', 'Reading', I('file-text','s'), 'Reading 3 articles'],
        ['wait', 'Waiting for you', I('hand','s'), 'Waiting for you'],
        ['write', 'Writing', I('edit','s'), 'Writing the reply'],
        ['done', 'Done', I('ok','s'), '']
      ];
      const took = {};
      let at = -1, secs = 0, ticking = true, mode = '', resolve = null;
      c.every(1000, () => { if (ticking && mode !== 'wait'){ secs++; tm.textContent = secs + 's'; } });
      const glyph = h => { g.innerHTML = h; enter(g.firstElementChild); };
      // a new phase: its glyph, its words shimmering, the next segment filling; only a change of phase is announced
      function go(i, words){
        at = i; const [k, name, gl, line] = P[i]; root.dataset.p = k; mode = k; glyph(gl);
        lab.classList.toggle('shim', k !== 'done' && k !== 'wait'); swap(lab, words || line);
        segs.forEach((s, j) => { s.classList.toggle('now', j === i && k !== 'done'); if (j <= i && !s.firstChild) s.appendChild(document.createElement('b')); });
        sr.textContent = k === 'done' ? `Done, thought for ${secs}s` : name;
      }
      const note = h => { x.innerHTML = h; if (x.firstElementChild) enter(x.firstElementChild); };
      // wait for a person: resolves with what they chose
      const ask = h => new Promise(res => { resolve = res; note(h); $$('.aph-n button', x).forEach(b => b.onclick = () => { if (resolve){ const f = resolve; resolve = null; f(b.dataset.a); } }); });
      c.run(async () => {
        const t0 = () => secs;
        let s = t0(); go(0); await c.wait(1600); took[0] = secs - s;
        // searching fails once, then works when retried
        s = t0(); go(1); await c.wait(1300);
        mode = 'err'; root.dataset.p = 'err'; glyph(I('alert','s')); lab.classList.remove('shim'); swap(lab, 'The search timed out'); sr.textContent = 'Error: the search timed out';
        await ask(`<div class="aph-n err">${I('alert','xs')}<span>The help centre didn't answer in 10s</span><button class="pill line sm" data-a="retry">${I('refresh','xs')}Retry</button></div>`);
        note(''); go(1, 'Searching the help centre again'); await c.wait(1200); took[1] = secs - s;
        // reading is slow: after a while it says so and offers Stop
        s = t0(); go(2); await c.wait(1800);
        let stopped = false;
        note(`<div class="aph-n">${I('clock','xs')}<span>Still working, the refund policy is a long page</span><button class="pill line sm aph-stop" data-a="stop">${I('stop','xs')}Stop</button></div>`);
        $('.aph-stop', x).onclick = () => { stopped = true; note(''); };
        await c.wait(2200); note(''); took[2] = secs - s;
        if (stopped){ go(5); lab.classList.remove('shim'); swap(lab, `Stopped after ${secs}s`); ticking = false; return; }
        // waiting for you: it stops the clock until someone answers
        s = t0(); go(3);
        const a = await ask(`<div class="aph-n ask"><span>Offer Maya a refund of $48?</span><button class="pill primary sm" data-a="ok">Approve</button><button class="pill ghost sm" data-a="no">Not now</button></div>`);
        note(''); took[3] = secs - s;
        s = t0(); go(4, a === 'ok' ? 'Writing the reply with the refund' : 'Writing the reply without a refund'); await c.wait(2000); took[4] = secs - s;
        // done: the line says how long it took and opens to the phases
        ticking = false; go(5); swap(lab, `Thought for ${secs}s`); tm.textContent = '';
        head.removeAttribute('aria-disabled');
        list.innerHTML = P.slice(0, 5).map(([, name], i) => `<li>${I('check','xs')}<span>${name}</span><span class="num">${took[i] || 0}s</span></li>`).join('');
      });
      head.onclick = () => { if (head.hasAttribute('aria-disabled')) return; const o = head.getAttribute('aria-expanded') !== 'true'; head.setAttribute('aria-expanded', o); fold.classList.toggle('shut', !o); };
      // the preview's story: retry the search, approve the refund, then open the finished line
      if (r === c.st) c.story(async s => {
        await s.until(() => mode === 'err' && $('.aph-n button', x)); await s.beat(3);
        await s.tap($('.aph-n button', x)); s.away();
        await s.until(() => mode === 'wait' && $('.aph-n button', x)); await s.beat(3);
        await s.tap($('.aph-n [data-a="ok"]', x)); s.away();
        await s.until(() => mode === 'done' && !head.hasAttribute('aria-disabled')); await s.beat(3);
        await s.tap(head); s.away(); await s.beat(5);
      });
    });
    
    06

    Questions

    What people ask about it.

    How is this different from Thinking?
    Thinking lists each step the agent takes and what it found. Agent phases says only which of six phases it is in, in one line, and handles the moments it needs you.
    Can I skip a phase?
    Yes. Move phase straight to the next one you need; the track fills up to it. Leave a phase out of took and it is left out of the finished list.
    When should I show the slow line?
    Whenever your agent decides a phase is taking long, for example after twice its usual time. Pass slow then, and clear it when the phase moves on.
    07

    More in Agent status

    Show what the agent is doing, how far along it is, and what it is working with.

    ThinkingThe live trace, written as the agent works, then folded away.
    Reasoning trailThe agent's plan, notes, sources and edits, after it thinks.
    Agent lanesSeveral agents working in parallel, each with its own lane and hand-offs.
    Agent treeAn orchestrator and its sub-agents, with cost rolled up per branch.
    Agent inboxEvery run that needs you, is running or is done, in one list.
    Trace viewOne agent run as a waterfall of spans, with latency and tokens.
    Browser sessionThe agent browsing for you, step by step, with Pause and Take over.
    Web searchThe agent's searches as a trail: what it asked, what came back, what it read or skipped, and the answer with its sources.
    All 20 in Agent status →