Task
The life of one step — pending, running, done, failed. A list of what an agent is doing, in the same four words a tool call uses.
- DoneRead Q-1058 off the board
- DoneResolve the party through the roster
- FailedCheck “a writ needs a seal”
- RunningFind a cantor who is ready today
- PendingDraft the quartermaster's note
Usage
import { Task, TaskList, TaskStatus, TaskTitle } from "@kanzo-tech/ai";<TaskList>
<Task state="done">
<TaskStatus />
<TaskTitle>Read Q-1058 off the board</TaskTitle>
</Task>
<Task state="running">
<TaskStatus />
<TaskTitle>Find a cantor who is ready today</TaskTitle>
</Task>
</TaskList>A plan the model is working through, or a plan it says it will work through. Nothing here is stateful and nothing here streams — the states arrive from wherever your run does, and the list re-renders.
Four words, one vocabulary
RunState is pending, running, done or failed, and Tool imports the same
type. A tool call is a step with a name, so the two share one vocabulary rather than each
growing their own four words that then disagree about whether a call is error or failed.
TaskStatus draws the state: a Status dot in the matching tone, or a
Spinner while it runs. TaskTitle mutes a pending step and tints a
failed one, both read off the row's data-state through a group selector — the same one-declaration
rule Message follows for its role.
The dot is not the announcement
Status is aria-hidden, and so is the spinner. A coloured dot is not a status to a screen
reader — nothing about a green circle says "done" — so the state is carried by a visually hidden
word inside TaskStatus: "Pending", "Running", "Done", "Failed".
Pass children to TaskStatus to say it in your own words — "Waiting on the archivist" rather than
"Pending". The dot and the spinner are unaffected; only what is announced changes.
That is also why the title is not enough on its own. A row that reads Find a cantor who is ready today with a red dot beside it says nothing at all to somebody who cannot see the dot.
Anatomy
TaskList (ol, the plan in order)
└── Task (li, carries data-state and the group)
├── TaskStatus (dot or spinner + the visually hidden word)
└── TaskTitle (truncates — a step is one line)TaskList is an ordered list, deliberately: the steps have an order, and a screen reader
enumerating "3 of 5" is the cheapest progress indicator there is. TaskTitle truncates rather than
wrapping, so a long step does not cost two lines on every row above and below it.
API Reference
Task
Renders an li.
| Prop | Type | Default |
|---|---|---|
state | "pending" | "running" | "done" | "failed" | "pending" |
Mirrored to data-state, which is what the parts select on. TaskStatus throws by name if it is
rendered outside a Task.
TaskStatus
Renders a span. Children replace the announced word only; the marker is chosen from the state.
TaskList · TaskTitle
TaskList renders an ol with the markers off; TaskTitle renders a span. Both take that
element's props, carry a data-slot, and accept asChild.
TaskProps is exported, so a wrapper can take the same props without restating them.
Tool
One call the model made — its name, its state, what went in and what came back. The payload is children, not a JSON prop, and that is the reason to use this one.
useAiStream
The streaming engine under every AI surface — one iterator, one abort controller, one status. It never calls a model; you hand it a function returning an async iterable.