Skip to main content
Sometimes the caller is code, not a person, and prose is the wrong answer. Hand run() a schema and the result comes back with typed output.

Typed output

lib/triage.ts
output takes any AI SDK FlexibleSchema, and a zod schema is the usual one. The value is validated before it reaches you, and T is inferred from the schema, so result.output is typed without a cast. Leave output unset and it costs nothing. No schema is sent, and T is void. Read status once and everything you then touch is on the object: output lives on the ok arm alone, so the check above is what types it.

What an unattended run may touch

Nobody is watching a run(), so nobody can be tapped mid-turn. A call the guard wants a person for parks, and parking ENDS the turn: the result comes back interrupted with the cards on it, in the time the run actually took.
Never offered
Destructive and ungraded tools are filtered out of the listing the model sees.
Offered, then checked
Read and write tools are listed, and every call still needs authority captured while a person was present.
No authority
The call parks and the run answers interrupted, carrying the card.
Put those cards in front of a person, then carry on where the run stopped:
resume() is not a rerun. The turn picks up byte for byte from where it parked, and a denied call is a refusal the model reads and works around. The result channel is the one exception. It reaches nothing, so it is never guarded and never parks, which is what keeps a typed run from stranding on a card nobody can answer.

Meter on usage

Every result carries the run’s token totals. This is the number to bill and budget on.
A turn that broke never reached a model, so usage is on the three statuses that ran and the error check is what types it. cacheReadTokens, cacheWriteTokens, and model are optional on TurnUsage; the two token counts are always there. The same figures land on the run’s audit rows in your Cloud console.

Cancel it

There is no cancel(). Cancellation is an AbortSignal, the same as everywhere else on the platform.
An aborted run still answers. What it said, the calls it made, and the usage it spent are all preserved.

What the result carries

Four ends and no fifth. Read status once, and every field you then touch is there. And the run itself: text, threadId and turnId are on all four arms. toolCalls and usage are on the three that ran: a turn that broke never spoke, so error carries the failure and an empty text instead of a sentence in the agent’s voice.

Options

Your own surface

The same run, streamed to a screen you built.

Automate — .on()

The same run, on a schedule or an event, with no caller at all.