03 — Workflow
workflow() builds a deterministic chain of agent steps. Each step's output feeds the next. Steps can be bare AgentHandles or configuration objects with conditional logic, input transforms, and retry behaviour.
Workflow with a .run(input) method.false → skip the step, pass the previous output forward unchanged.prev.text.status: "failed". Combined with retryDelay (ms).Run it
npm run workflow
"urgent".
Step configuration
Each element in the step array is either a bare AgentHandle (shorthand for { agent: handle }) or a full step configuration object:
{
agent: AgentHandle; // required
when?: (prev: MessageResponse) => boolean;
// If false, skip this step and pass prev forward unchanged.
transform?: (prev: MessageResponse) => string | MessagePart[];
// Returns the input for this step. Default: prev.text ?? "".
retries?: number; // default 0 — no retries
retryDelay?: number; // ms between retries, default 0
credentials?: CredentialStore; // per-step credential override
}
| Option | Default | Behaviour |
|---|---|---|
when |
always run | Receives the previous MessageResponse. Return false to skip this step entirely — the previous response is passed forward as-is. |
transform |
prev.text |
Called only when the step will run (after when passes). Return a string or an array of message parts. |
retries |
0 |
Number of additional attempts on failure. Total attempts = retries + 1. |
retryDelay |
0 |
Milliseconds to wait between retry attempts. |
Walkthrough
1 · Create three focused agents
const summarizer = await agents.create({
name: "wf-summarizer",
description: "Summarises a clinical note in one sentence.",
systemPrompt: "Summarise the note in a single sentence.",
});
const classifier = await agents.create({
name: "wf-classifier",
description: "Classifies a summary as 'urgent' or 'routine'.",
systemPrompt: "Reply with exactly one word: 'urgent' or 'routine'. No punctuation.",
});
const escalator = await agents.create({
name: "wf-escalator",
description: "Drafts an escalation for urgent cases.",
systemPrompt: "Draft a one-line escalation to the on-call physician.",
});
Each agent is given a hyper-focused system prompt. The classifier's prompt is constrained to exactly one word — this makes the when predicate reliable.
2 · Define the pipeline
const note = "Patient reports severe chest pain radiating to left arm, onset 30 minutes ago.";
const result = await workflow([
summarizer, // step 1: bare handle — no conditions
classifier, // step 2: bare handle — receives summarizer's text as input
{ // step 3: conditional escalation
agent: escalator,
when: (prev) => (prev.text ?? "").toLowerCase().includes("urgent"),
transform: () => note, // give escalator the original note, not "urgent"
retries: 2,
retryDelay: 500,
},
]).run(note);
3 · How data flows
The flow for a chest-pain note:
- Input →
summarizerreceives the rawnotestring. Returns: "Patient has severe chest pain radiating to the left arm starting 30 minutes ago." - Step 2 →
classifierreceives the summarizer's text. Returns: "urgent" - Step 3
whencheck →"urgent".includes("urgent")→true, so the step runs. - Step 3
transform→ Instead of feeding"urgent"to the escalator,transform: () => notesends the original clinical note — giving the escalator something meaningful to work with. - Step 3 →
escalatorreturns: "Escalate immediately: patient presenting with acute STEMI symptoms."
When a step is skipped
If the note were "Patient has a mild sore throat.", the classifier would return "routine". The when check fails, the escalator step is skipped, and result.output would be the classifier's response ("routine").
Retry behaviour
{
agent: escalator,
retries: 2, // try up to 3 times total
retryDelay: 500, // wait 500 ms between attempts
}
Retries fire only when a step returns status: "failed" — not on "input-required" or "auth-required". If all attempts fail, the workflow sets stoppedEarly: true and returns the last failed response as output.
Result shape
workflow(...).run(input) returns a WorkflowResult:
| Field | Type | Description |
|---|---|---|
output | MessageResponse | Response from the last step that actually ran. |
steps | MessageResponse[] | One entry per step that ran (skipped steps are absent). |
stoppedEarly | boolean | true if a step exhausted its retries and the workflow halted. |
Full code
Source: examples/ts/03-workflow.ts
/**
* 03 — Deterministic workflows.
*
* Chain agents into a deterministic pipeline. Each step transforms the
* previous response, may be conditionally skipped, and may retry on failure.
*
* Run: `npm run workflow`
*/
import { AgentsClient, workflow } from "@corti/agent-sdk";
import { makeClient } from "./_client";
async function main() {
const agents = new AgentsClient(makeClient());
const summarizer = await agents.create({
name: "wf-summarizer",
description: "Summarises a clinical note in one sentence.",
systemPrompt: "Summarise the note in a single sentence.",
});
const classifier = await agents.create({
name: "wf-classifier",
description: "Classifies a summary as 'urgent' or 'routine'.",
systemPrompt:
"Reply with exactly one word: 'urgent' or 'routine'. No punctuation.",
});
const escalator = await agents.create({
name: "wf-escalator",
description: "Drafts an escalation for urgent cases.",
systemPrompt: "Draft a one-line escalation to the on-call physician.",
});
const note =
"Patient reports severe chest pain radiating to left arm, onset 30 minutes ago.";
// Pipeline: summarise → classify → escalate (only when classifier says urgent).
const result = await workflow([
summarizer, // step 1: summarise the note
classifier, // step 2: classify the summary
{ // step 3: conditional escalate
agent: escalator,
when: (prev) => (prev.text ?? "").toLowerCase().includes("urgent"),
// Skip escalation input is just the word "urgent"; give escalator the
// original note so it has something to escalate.
transform: () => note,
retries: 2,
retryDelay: 500,
},
]).run(note);
console.log("Final output:", result.output.text);
console.log("Steps executed:", result.steps.length);
console.log("Stopped early:", result.stoppedEarly);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
What to expect
when returns false, the step is skipped — it doesn't appear in steps and the previous response becomes the final output. That's why steps.length is 2, not 3, for routine cases.