AgenticGENERALSPEC-EVOLVING

Showing What the System Is Doing

Observable states — searching, retrieving, calling, processing — reported honestly. Not a dramatisation of thinking the system cannot actually show you.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

What do I show during the thirty seconds when the model is not producing text?

The user intent

Someone asked a question that requires the system to go and look things up. They want to know it is working, roughly what it is doing, and whether it is stuck.

The obvious build

Show a spinner. If it feels too blank, add a rotating series of messages like "Thinking…", "Pondering…", "Consulting the archives…".

Why it breaks

A spinner conveys "busy" and nothing else, so a run that takes thirty seconds is indistinguishable from one that has hung.

How it breaks in a real browser
  • A spinner conveys "busy" and nothing else, so a run that takes thirty seconds is indistinguishable from one that has hung.
  • Invented status messages are not reporting anything. They are a loading animation with extra words, and once a user notices they are decorative, every status message you show loses credibility — including the true ones.
  • A rotating message that says "Consulting the archives" while the system is actually waiting on a failed tool is actively misleading.
  • None of it survives a screen reader: a spinner with no accessible name and no announced state is silence.
  • When something does go wrong, there is nowhere to put the information, because the UI has no vocabulary for tool states.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • The agent backend genuinely knows a sequence of observable facts: which tool was invoked, with what broad intent, whether it returned, and whether it succeeded. Those are reportable (What the Frontend Owns in an Agent Product).
  • It does not know, and cannot faithfully show, why the model chose that tool. Presenting a narrative of reasoning is presenting something the system does not have.
  • Tool events arrive interleaved with tokens on the same stream, so progress is part of the same state machine rather than a separate loading flag.
  • A run can call several tools, sequentially or in parallel, and any of them can fail while the run continues (Citations and Answers With Holes In).

What this makes the browser do

And which of it is avoidable.

  • Rendering a small, frequently-changing status region — cheap, unless it is inside the same reactive scope as the transcript (What a Component Costs to Render).
  • Animating a progress indicator, which should be compositor-driven so it keeps moving even when the main thread is busy (Cheap and Expensive Animation).
  • Retaining tool results for the transcript, which is where an agent UI's memory growth usually comes from.

Report the observable, not the imagined

The line to hold is simple: show states the system emitted. A tool started; it returned; it failed; the run finished. These are facts, they are useful, and they degrade gracefully — if you cannot report one, you show less rather than showing something invented.

The alternative — a narration of thought — is tempting because it fills the silence attractively. But it describes something the system does not expose, so it can be wrong at exactly the moments the user most needs it to be right, and it teaches users to discount every status message you show.

The states worth reporting
  1. 1
    Queued

    The run has been accepted and has not started.

    fails by Being skipped, so a busy backend looks like a dead one.

  2. 2
    Searching / Retrieving

    Names the tool in the user's terms and shows it is outstanding.

    fails by Naming the internal service instead, which tells the user nothing.

  3. 3
    Tool returned

    States the outcome, including a count where one exists.

    fails by Reporting success before the backend confirmed it.

  4. 4
    Tool failed

    Says so in place, and what it costs the answer.

    fails by Swallowing the failure, leaving a silently degraded answer.

  5. 5
    Processing result

    Covers the gap between a tool returning and tokens resuming.

    fails by Leaving the gap blank, which reads as a hang (Streaming a Response Without Melting the Device).

  6. 6
    Completed / Cancelled / Truncated

    Gives the run a terminal state that can be announced.

    fails by Only ever having one ending, so an interrupted run looks finished.

Show thisNot thisWhy
Searching your documentsThinking about your question…The first names an event that occurred; the second describes an internal process the system cannot observe
Retrieved 12 resultsFound some interesting things!A count is checkable and sets expectations; enthusiasm is not information
Calendar unavailable — answering without itsilenceThe gap in the answer has a cause; hiding it makes the answer look more complete than it is
Calling calendar (14s)Almost there…Elapsed time distinguishes slow from stuck; reassurance does not
Completedthe spinner just disappearingA terminal state is announceable and tells a screen-reader user the run has ended

The announcement contract

Progress is a live-region problem, and live regions are easy to get wrong in both directions — too quiet and the run is invisible, too eager and it is unusable. The spec below is the shape that works for a stepped, long-running operation.

accessibility specAgent run progressA long operation with observable steps

semantics A role="status" region (implicitly aria-live="polite", aria-atomic="true") holding a short text description of the current state. The step list itself is ordinary content — a list, not a live region — so it can be read at leisure. The stop control is a real button outside the live region.

TabReaches the stop control at any point during the run; it is never the last thing in the order.
Enter / SpaceActivates stop, which announces Stopping and then a terminal state.
EscapeOptional shortcut for stop; if provided it must be discoverable and not the only way.
Focus
  • Focus does not move when a step starts or completes — the run is not asking for attention.
  • Focus moves only when the run genuinely needs a decision, such as a confirmation for a destructive action (A Suggestion Is Not an Authorization).
  • On completion focus stays where the user left it; completion is announced, not focused.
Announces
  • The run starting: "Generating response".
  • Each meaningful step transition, once: "Searching your documents", "Retrieved 12 results".
  • A tool failure, with its consequence: "Calendar unavailable, answering without it".
  • The terminal state: "Response complete", "Response stopped", "Response ended early".

usually broken by Putting the streaming answer, or a ticking elapsed-seconds counter, inside the same live region — which converts a helpful status announcement into a continuous interruption and makes the whole interface unusable with a screen reader.

When a step fails but the run does not

Partial tool failure is the normal case, not an edge case, and it is where the honesty principle earns its keep. If the calendar lookup failed and the answer was assembled without it, the user is holding an answer with a known gap — and only the interface can tell them that.

Rendering the failure in place, attached to the step that failed, is better than a banner: it keeps the cause next to the consequence, and it survives the user scrolling back later to work out why the answer looked thin.

A failed tool
Failure hidden
Searching your documents…  ✓
Calling calendar…          ✓

"You have no meetings this week."
Failure surfaced with its consequence
Searching your documents   ✓ 12 results
Calling calendar           ✗ unavailable

"Based on your documents only — I could not reach
 your calendar, so this may be missing meetings."
                                    [ Retry calendar ]

The first version states something false with full confidence: it did not check the calendar, so it does not know there are no meetings. The second keeps the claim inside what was actually verified, tells the user why it is limited, and offers the specific recovery — which is also the only version a user can safely act on.

How to build it

Most important first.

  • Report states you can verify: Searching documents, Retrieving 12 results, Calling calendar, Processing result, Completed. Every one of these corresponds to an event the backend actually emitted.
  • Name the tool in the user's terms, not yours. "Searching your documents" beats "invoking vector_search_v2".
  • Keep completed steps visible so the user can see the shape of what happened, collapsed by default once the run finishes.
  • Show failures as failures, in place, without ending the run — and say what it means for the answer.
  • Give long-running steps an elapsed indication so a slow tool is distinguishable from a stuck one.
  • Never claim a step the system did not take. This is a credibility asset that is spent once.

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • Tool progress is exactly the kind of change a status live region is for, and exactly the kind that is easy to overdo. Announce meaningful transitions — the step started, the step failed, the run completed — not every intermediate update (Live Regions and Announcement).
  • Use role="status" (polite) rather than role="alert" for progress. Progress is not an emergency and should not interrupt what the user is reading; a tool *failure* may justify a more assertive announcement.
  • A purely visual spinner conveys nothing. Every indicator needs a text equivalent that describes the state, and that text is what should be announced.
  • If a step needs the user (a permission prompt, a confirmation), that is a focus event, not just a visual one — it must be reachable and announced, and it must not appear so abruptly that it captures a keystroke meant for something else (Focus Management).
  • Elapsed-time indicators that tick every second are an announcement flood if they live inside a live region. Keep the ticking counter out of it and announce only when the state changes.

What can go wrong

Failure modes
  • Progress that only appears after the slow part finishes, which is when it is no longer needed.
  • A tool that fails silently, so the answer is missing information and nothing indicates why.
  • Nested or parallel tool calls flattened into one line, so the user cannot tell what is still outstanding.
  • A status region that updates so often it becomes visual noise, or announces so often it becomes audible noise.
  • Progress state that never resolves because the terminal event was dropped, leaving a permanent "Processing…".
What can arrive out of order
  • Parallel tool calls complete out of order, so a UI that assumes sequential completion mislabels which step finished.
  • A terminal event can be lost on a flaky connection, leaving a step permanently in progress unless the client times it out itself.
Security
  • Tool names and parameters can leak internal structure. Report what the user needs to understand, not your service topology.
  • A tool result rendered into the transcript is untrusted content from a document the agent retrieved, and must be sanitized like any other model output (Sanitization and Trusted HTML).
  • Do not show a tool as completed until the backend confirms it. A UI that reports success optimistically is asserting something about a side effect it does not know occurred (A Suggestion Is Not an Authorization).
Misreads
  • "Users want to see the reasoning." They want to know what happened and whether to trust it. A narrative of reasoning that the system cannot verify is a story, and it is the fastest way to lose the credibility that makes real status messages useful.
  • "A spinner is enough." It is enough for something short. It is not enough for anything with steps, and it is nothing at all without a text equivalent.
  • "More detail is always better." Detail the user cannot act on is noise, and in a live region it is worse than noise.
  • "Progress is cosmetic." It is the main mechanism by which a user decides whether to keep waiting or cancel.

Measuring it, and what changes in the field

How you would see this
  • Duration per tool, from the client's view, which includes the queueing the server-side view often omits.
  • Failure rate per tool — surfaced in the UI as well as in telemetry, because a tool that fails a fifth of the time is degrading answers invisibly (Frontend Error Tracking).
  • Abandonment during long tool steps: if users cancel disproportionately while a particular tool runs, that step needs either a better indication or a different design.
Slow device, slow network, large data, old tab
  • When tools are fast, progress detail is noise and a simple pending state is better.
  • When tools are slow, progress detail is the difference between waiting and giving up.
  • On a screen reader, the same information needs a different cadence — fewer, more meaningful announcements.
What this costs
  • Detailed progress builds trust and exposes internal structure that becomes an implicit contract users notice when it changes.
  • Honest progress sometimes means showing an unglamorous truth — that the system is waiting on a slow third party. That is better than a fabricated narrative, and it also tells you what to fix.
  • Keeping completed steps in the transcript aids understanding and grows the DOM and memory over a long session.

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALReporting verifiable states rather than inferred reasoning is a product and honesty constraint, not a platform one, so it holds regardless of framework or vendor.
  • SPEC-EVOLVINGWhat a backend can report about a run depends on the tool-calling and streaming interfaces available, which differ between providers and change often. Design your own progress vocabulary and map vendor events onto it, rather than surfacing a vendor's event names directly.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

API Designerror-model
Performancellm-latency
Domains that do not exist yet
  • Distributed Systems — a run spanning several tools is a distributed operation, and partial failure with a continuing whole is its defining condition.