Skip to content

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:

PieceWhat it is
SessionA single conversation with an agent — one LLM loop, one workspace. Ephemeral.
WorkflowA durable program that coordinates one or more sessions, asks humans, waits for time, and branches on outcomes.
ApprovalA 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

TypeWhat it does
taskRuns a prompt with an agent. You can save its result into a named variable.
approvalAsks your user a natural-language question and pauses until they answer.
waitPauses for a fixed duration.
branchPicks a next node based on a variable's value.
parallelFans out to multiple branches and joins.
endTerminates 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: N on the task node.
  • A whole workflow should re-run daily — use a schedule + cron, not workflow-internal retry.

See also