Workflows
A Workflow is a durable program an agent can run that holds its place across long waits. Unlike a single conversation — which happens in one session and ends — a workflow can pause for minutes or days waiting on a human to answer a question, a timer to fire, or an external event to arrive, then pick up exactly where it left off.
When to use a workflow
Reach for a workflow whenever the work spans more than one "turn" of thinking and might need to wait on something external.
- Scheduled jobs that need reliable completion — run every morning, summarize, notify.
- Multi-step agent work where a step depends on a human answering first.
- Anything that should survive a deploy, a crash, or just the passage of time.
If you just want to have a conversation with an agent or run a one-shot task, use a regular session or agent.run(...) — workflows add no value for those.
The three pieces
Workflows sit on top of two other primitives:
| Piece | What it is |
|---|---|
| Session | A single conversation with an agent — one LLM loop, one workspace. Ephemeral. |
| Workflow | A durable program that coordinates one or more sessions, asks humans, waits for time, and branches on outcomes. |
| Approval | A durable question an agent asks its human, answerable over any channel, days later if needed. See Approvals. |
Rules of thumb:
- Workflows never think — they coordinate. Every "thinking" step delegates to a session.
- Sessions don't know about workflows. You run one session of a chat freely, or you let a workflow run sessions on your behalf.
- Interactive chat never needs a workflow. The REPL experience stays simple.
Defining a workflow
A workflow is a set of named nodes and a starting node. Each node declares what it does and what runs next.
typescript
import { defineWorkflow } from "swarmlord";
const morningBrief = defineWorkflow({
startNodeId: "research",
sharedSession: { title: "Morning brief" },
nodes: {
research: {
id: "research",
type: "task",
prompt: "Read my inbox and list the 3 most important emails.",
saveAs: "emails",
next: "check",
},
check: {
id: "check",
type: "approval",
// Include enough context for the user to answer without re-asking.
question: "Scanned 47 emails — 3 look important. Send them as an SMS summary now?",
options: ["yes", "no"],
next: { yes: "send", no: "done" },
},
send: {
id: "send",
type: "task",
prompt: "Compose a concise SMS with the top 3 emails: {{emails}}",
next: "done",
},
done: { id: "done", type: "end", result: "emails" },
},
});Start it:
typescript
import { createClient } from "swarmlord";
const client = createClient();
const handle = await client.startWorkflow({
definition: morningBrief,
title: "Morning brief",
});
const final = await handle.result();
console.log(final.status); // "completed" | "failed" | "cancelled"You can also declare a workflow in swarmlord.jsonc and attach it to a schedule — the workflow runs automatically on your cron.
Node types
| Type | What it does |
|---|---|
task | Runs a prompt with an agent. You can save its result into a named variable. |
approval | Asks your user a natural-language question and pauses until they answer. |
wait | Pauses for a fixed duration. |
branch | Picks a next node based on a variable's value. |
parallel | Fans out to multiple branches and joins. |
end | Terminates the workflow. Only end nodes that declare result trigger the workflow's outputs. |
Task nodes
jsonc
{
"type": "task",
"prompt": "Summarize today's commits in 3 bullets. Context: {{repo}}",
"sessionRef": "shared", // "shared" = reuse one session across tasks (shared workspace), "new" = fresh session per task
"saveAs": "summary", // save the agent's final text under `summary` for later {{summary}} references
"retries": 1, // auto-retry this task on failure
"next": "review",
}Approval nodes
jsonc
{
"type": "approval",
"question": "Ship build {{version}} to production?",
"options": ["yes", "no"],
"expiresInMs": 3600000,
"next": { "yes": "deploy", "no": "rollback", "expired": "abort" },
}See Approvals for how the question gets to the user and back.
Branch nodes
Safe expressions on variables — not arbitrary code:
jsonc
{ "type": "branch", "cond": "summary contains SHIP_IT", "truthy": "deploy", "falsy": "review" }Supported operators: ==, !=, contains, not_contains, starts_with, ends_with, or a bare variable name for truthy check.
Wait nodes
jsonc
{ "type": "wait", "forMs": 3600000, "next": "retry" }Good for backoffs, delayed follow-ups, or "check again in an hour."
End nodes
jsonc
{ "type": "end", "result": "summary" }Only end nodes that declare result trigger the workflow's configured outputs. End nodes without result terminate silently — useful for "no-op" branches like the no arm of a confirmation prompt.
Durability
Workflows persist their entire state across waits — if nothing is running, nothing costs you. When the event you're waiting on arrives (an answer, a timer, an external webhook), the workflow resumes from the exact point it paused. Deploys don't kill in-flight workflows; they resume where they left off.
Retries
Retry lives at the natural layer:
- Inside a single LLM call (transient network, 429, 503) — retried automatically by the platform.
- A whole task failed — set
retries: Non the task node. - A whole workflow should re-run daily — use a schedule + cron, not workflow-internal retry.
See also
- Approvals — durable questions agents ask their users
- Triggers & Integrations — how inbound user replies route back
- SDK reference —
defineWorkflow,client.startWorkflow,WorkflowHandle