@@ -30,14 +30,14 @@ sources:
3030
3131` useChat ` is right for a single conversation. ` chat() ` with tools is right for a single LLM call that may loop through tools (the agent loop). Reach for ` @tanstack/ai-orchestration ` only when there are ** multiple distinct LLM steps that need to coordinate** — and the steps either run in a fixed sequence (workflow) or the next step depends on the last (orchestrator).
3232
33- | Building this | Use |
34- | ----------------------------------------------------------------------------- | -------------------------------------------------- |
35- | One conversation, one model, optional tool calls in an agent loop | ` chat() ` + ` useChat ` (see ai-core/chat-experience) |
36- | Multi-step LLM pipeline with a fixed order (A → B → C) | ` defineWorkflow ` (Pattern 1) |
37- | Multi-step run where the next step depends on the previous result | ` defineOrchestrator ` (Pattern 2) |
38- | Any of the above, paused mid-run for human approval | ` approve() ` primitive (Pattern 3) |
39- | Carry context from one user submission to the next (refinement, iteration) | ` initialize() ` + ` previousX ` input (Pattern 4) |
40- | Wrap a flaky step with retry + backoff | ` retry() ` primitive (Pattern 5) |
33+ | Building this | Use |
34+ | -------------------------------------------------------------------------- | -------------------------------------------------- |
35+ | One conversation, one model, optional tool calls in an agent loop | ` chat() ` + ` useChat ` (see ai-core/chat-experience) |
36+ | Multi-step LLM pipeline with a fixed order (A → B → C) | ` defineWorkflow ` (Pattern 1) |
37+ | Multi-step run where the next step depends on the previous result | ` defineOrchestrator ` (Pattern 2) |
38+ | Any of the above, paused mid-run for human approval | ` approve() ` primitive (Pattern 3) |
39+ | Carry context from one user submission to the next (refinement, iteration) | ` initialize() ` + ` previousX ` input (Pattern 4) |
40+ | Wrap a flaky step with retry + backoff | ` retry() ` primitive (Pattern 5) |
4141
4242## Setup
4343
@@ -54,10 +54,7 @@ Peer dependency: `@tanstack/ai`. The provider adapter you're calling (`@tanstack
5454Use when the path is known up front and steps are typed.
5555
5656``` typescript
57- import {
58- defineAgent ,
59- defineWorkflow ,
60- } from ' @tanstack/ai-orchestration'
57+ import { defineAgent , defineWorkflow } from ' @tanstack/ai-orchestration'
6158import { chat } from ' @tanstack/ai'
6259import { openaiText } from ' @tanstack/ai-openai'
6360import { z } from ' zod'
@@ -72,7 +69,10 @@ const translate = defineAgent({
7269 outputSchema: z .object ({ translated: z .string () }),
7370 stream: true ,
7471 messages: [
75- { role: ' user' , content: ` Translate to ${input .target }: ${input .text } ` },
72+ {
73+ role: ' user' ,
74+ content: ` Translate to ${input .target }: ${input .text } ` ,
75+ },
7676 ],
7777 }),
7878})
@@ -119,10 +119,7 @@ export const pipeline = defineWorkflow({
119119Use when the next step depends on the previous result. The ` router ` is a generator returning ` { done: true, output } ` or ` { agent, input } ` each turn.
120120
121121``` typescript
122- import {
123- defineOrchestrator ,
124- defineRouter ,
125- } from ' @tanstack/ai-orchestration'
122+ import { defineOrchestrator , defineRouter } from ' @tanstack/ai-orchestration'
126123
127124const orchestratorConfig = {
128125 agents: { triage , extractTopics , draftOutline , expandSection },
@@ -143,7 +140,8 @@ const router = defineRouter(
143140 if (lastResult && typeof lastResult === ' object' ) {
144141 const r = lastResult as Record <string , unknown >
145142 if (Array .isArray (r .topics )) state .topics = r .topics as Array <string >
146- if (Array .isArray (r .headings )) state .headings = r .headings as Array <string >
143+ if (Array .isArray (r .headings ))
144+ state .headings = r .headings as Array <string >
147145 if (typeof r .heading === ' string' && typeof r .body === ' string' ) {
148146 state .sections .push ({ heading: r .heading , body: r .body })
149147 }
@@ -211,7 +209,7 @@ Inside an orchestrator router, deny-with-feedback routes back to a refinement st
211209
212210``` typescript
213211if (triage .next === ' await-approval' ) {
214- const decision = yield * approve ({ title: ' ...' , description: ' ...' })
212+ const decision = yield * approve ({ title: ' ...' , description: ' ...' })
215213 if (decision .approved ) {
216214 return { agent: ' implement' , input: { spec: state .spec } }
217215 }
@@ -229,14 +227,16 @@ Client side:
229227``` tsx
230228const run = useWorkflow ({ connection: fetchWorkflowEvents (' /api/feature' ) })
231229
232- {run .pendingApproval && (
233- <ApprovalPrompt
234- title = { run .pendingApproval .title }
235- description = { run .pendingApproval .description }
236- onApprove = { () => run .approve (true )}
237- onDeny = { (feedback ) => run .approve (false , feedback )}
238- />
239- )}
230+ {
231+ run .pendingApproval && (
232+ <ApprovalPrompt
233+ title = { run .pendingApproval .title }
234+ description = { run .pendingApproval .description }
235+ onApprove = { () => run .approve (true )}
236+ onDeny = { (feedback ) => run .approve (false , feedback )}
237+ />
238+ )
239+ }
240240```
241241
242242### Pattern 4: Refinement across separate runs
@@ -297,18 +297,17 @@ const submit = () =>
297297``` typescript
298298import { retry , SchemaValidationError } from ' @tanstack/ai-orchestration'
299299
300- const result = yield * retry (
301- () => agents . parser ({ document }),
302- {
300+ const result =
301+ yield *
302+ retry (() => agents . parser ({ document }), {
303303 attempts: 3 ,
304304 backoff: ' exponential' ,
305305 baseDelayMs: 200 ,
306306 maxDelayMs: 5000 ,
307307 // Don't retry schema violations — re-running the same model with the
308308 // same prompt won't fix them. Retry only network/rate-limit errors.
309309 retryOn : (err ) => ! (err instanceof SchemaValidationError ),
310- },
311- )
310+ })
312311```
313312
314313` retry(fn, options) ` reinvokes ` fn() ` on failure — the underlying generator restarts. Works around agents, nested workflows, or any yieldable.
@@ -383,7 +382,10 @@ run: ({ input }) => chat({ adapter, messages })
383382run : ({ input , signal }) => {
384383 const abortController = new AbortController ()
385384 if (signal .aborted ) abortController .abort ()
386- else signal .addEventListener (' abort' , () => abortController .abort (), { once: true })
385+ else
386+ signal .addEventListener (' abort' , () => abortController .abort (), {
387+ once: true ,
388+ })
387389 return chat ({ adapter , messages , abortController })
388390}
389391` ` `
@@ -394,14 +396,22 @@ The run itself aborts either way — the engine checks `signal.aborted` on every
394396
395397` ` ` typescript
396398// ❌ Router stays blind to results; triage loops forever.
397- const decision = yield * agents .triage ({ /* ... */ })
399+ const decision =
400+ yield *
401+ agents .triage ({
402+ /* ... */
403+ })
398404
399405// ✅
400406if (lastResult && typeof lastResult === ' object' ) {
401407 const r = lastResult as Record <string , unknown >
402408 if (' spec' in r ) state.spec = r.spec as Spec
403409}
404- const decision = yield * agents .triage ({ /* ... */ })
410+ const decision =
411+ yield *
412+ agents .triage ({
413+ /* ... */
414+ })
405415` ` `
406416
407417### 3. Not clearing ` pendingFeedback ` after the spec agent consumes it
0 commit comments