Workflows
A workflow is how one AI employee runs a multi-step job: an orchestrator runs named sub-agent steps in order, and a completion gate holds each step until the one before it proves it finished. It is a plain .workflow.js file that ships inside the employee and runs in your cell.
A workflow is how one AI employee runs a multi-step job. An orchestrator kicks off named sub-agent steps in a set order, and a completion gate between each step means the next step never starts until the one before it proves it finished. It is a plain .workflow.js file that ships inside the employee and runs inside your cell.
What a workflow is#
An employee does not always finish a job in one pass. A workflow breaks the job into named steps, hands each to a sub-agent, and has an orchestrator run them in order. The orchestrator manages the steps; it does not do the step work itself. The order and the gate are enforced by the file, not left to the model, and that is what separates a workflow from a single chat turn.
A workflow is plain JavaScript, so there is no compile step. It lives in the employee's folder at agents/templates/<name>/workflows/<name>.workflow.js and is projected into your cell's .claude/workflows/ when the cell is seeded. The step bodies start trivial and grow into real logic in place.
The gate is the point
A step must emit an exact completion token before the next step may start. If it does not, the workflow stops rather than run a step out of order. That gate is what makes a workflow dependable instead of hopeful.
The .workflow.js shape#
Every workflow is two parts: a meta export the platform reads to name the run and list its phases, and a body that drives the steps with a small set of built-in functions.
export const meta = {
name: 'template',
description: 'One orchestrator runs one worker as three named, sequential steps, each gated on a completion token the prior step must emit.',
whenToUse: 'The copy-me starting point for a real employee workflow. Pass args.intent to give the steps something real to do.',
phases: [
{ title: 'Strategist', detail: 'the orchestrator kicks off the run and states the gated steps' },
{ title: 'Step 1', detail: 'first step; must emit its token to unlock step 2' },
{ title: 'Step 2', detail: 'gated on step 1; emits its own token to unlock step 3' },
{ title: 'Step 3', detail: 'gated on step 2; emits the final token' },
],
}Below meta, the body calls these built-ins. Each agent() call runs one sub-agent step and returns a structured result that must match the schema you pass, a closed shape that is validated as a hard gate.
| Function | What it does |
|---|---|
agent(prompt, opts) | Run one sub-agent step. opts carries { phase, label, schema }; the step's return must match schema (a closed JSON shape). |
phase(title) | Mark the current phase. title matches a meta.phases entry, so the surface shows phased progress. |
parallel([fn, fn]) | Run several steps at once. Fault-isolated: a step that throws becomes null instead of failing the batch. |
log(line) | Append one honest progress line to the run. |
args | The workflow input: a JSON string or an object. Parse it for your parameters. |
return { ... } | The workflow's final result object. |
The completion gate#
The canonical example is the template workflow: one orchestrator runs one worker as three named, sequential steps. Each step carries the token it must emit and the token it waits on. The gate is enforced twice: a step will not start until the prior step's token is in hand (the pre-condition), and the next step unlocks only when this step returns its exact token (the post-condition).
// One worker persona, run as three named steps. Each carries the token it
// must emit and the token it waits on.
const STEPS = [
{ name: 'step-1', emits: 'STEP_1_COMPLETE', waits_on: null },
{ name: 'step-2', emits: 'STEP_2_COMPLETE', waits_on: 'STEP_1_COMPLETE' },
{ name: 'step-3', emits: 'STEP_3_COMPLETE', waits_on: 'STEP_2_COMPLETE' },
]
let lastToken = null
for (const s of STEPS) {
phase(s.name)
// pre-gate: do not start until the prior step's token is in hand
if (s.waits_on && lastToken !== s.waits_on) {
return { ok: false, failed_at: s.name, note: `gate not satisfied: ${s.name} needs ${s.waits_on}` }
}
const r = await agent(
`${WORKER}\nRun the step named "${s.name}", then set completion_token EXACTLY to "${s.emits}".`,
{ phase: s.name, label: s.name, schema: STEP_RESULT },
)
// post-gate: the next step unlocks only if this one emitted its exact token
if (!r || r.completion_token !== s.emits) {
return { ok: false, failed_at: s.name, note: 'step did not emit its token; stopping' }
}
log(`${s.name} complete; gate satisfied; next step unlocked.`)
lastToken = s.emits
}Because the gate lives in the file, a step that fails to emit its token stops the run cleanly with an honest result, instead of letting a later step run on unfinished work.
Fanning out sub-agents#
A workflow does not have to be a single line of steps. There are two ways one employee fans work out to sub-agents.
In a workflow: parallel steps
Inside a .workflow.js, parallel([...]) runs several agent() steps at once. The shipped starter onboarding workflow does this: a discover phase runs one agent that learns who signed up and one that maps the market together, then a strategy step, then a wider build-and-launch fan-out. Each parallel agent writes a disjoint path, so two writers never touch one file.
phase('Discover')
// enrichment (who signed up) and research (the market) have no dependency,
// so run them together. Each writes a disjoint path.
const [enrichment, research] = await parallel([
() => agent(enrichmentPrompt, { phase: 'Discover', label: 'enrichment', schema: RESULT }),
() => agent(researchPrompt, { phase: 'Discover', label: 'research', schema: RESULT }),
])In a live employee: explore and worker
When you talk to your employee over `jetsyn agent`, it fans out to two sub-agents through the Task tool: a read-only explore scout and a build worker. Sub-agents never carry the Task tool themselves, so a sub-agent cannot spawn another, and the read-only scout can never write.
| Sub-agent | Role | Can write |
|---|---|---|
explore | Read-only scout: reads files, searches, and reports back what it found. | No |
worker | Builder: makes the change and writes the files. | Yes |
Add a workflow to an employee#
An employee is authored as plain Claude Code files, and a workflow is one of the pieces in its folder. To add one:
- 1
Copy the primitive
Start from
agents/templates/template-worker/workflows/template.workflow.js(three gated steps) orhello-world(one real end-to-end step). Copy it toagents/templates/<your-employee>/workflows/<name>.workflow.js. - 2
Set meta
Give it a
name, a one-linedescription, awhenToUse, and aphasesarray. Setmeta.nameto match the file and the employee; nothing validates that match for you, so a mismatch fails silently at render time. - 3
Write the steps
In the body, call
phase()to mark each stage,agent()to run each step with aschema, andlog()for progress. Gate later steps on earlier ones, or fan out withparallel(). - 4
Let the seeder project it
When a cell is provisioned, the seeder copies the employee's folder and projects its workflow into the cell's
.claude/workflows/. There is no separate build. - 5
Run it
The orchestrator runs the workflow: at onboarding for the provisioning fan-out, or as the copy-me primitive you exercise directly. Pass
argsto ground it in real input.
Tip
The workflow is only one of the pieces of an employee folder. For the full folder, the persona file, and how to register the employee so it lands in a cell, see Build an AI employee.
What ships today#
Three .workflow.js orchestrations ship as running code, plus the default persona the run path uses.
| Name | What it is |
|---|---|
template | The canonical deployment primitive: one orchestrator and one worker instantiated as three gated steps. The copy-me starting point. |
hello-world | The smallest real end-to-end run: an orchestrator kicks it off, one agent composes and dispatches a single email, a dry run by default. |
starter | The onboarding fan-out: a parallel discover phase, a strategy barrier that authors the company spine, then a parallel build-and-launch phase. |
poc | The default run persona a live inbound event runs today. A single grounded agent that surfaces its progress and writes its deliverable, not a multi-step orchestration. |
Availability
Available now: the .workflow.js primitive with agent(), phase(), parallel(), and log(); the template, hello-world, and starter workflows; and the interactive explore + worker fan-out over jetsyn agent. Coming soon: per-role workflow selection in the run path. Today a live inbound event runs the single poc persona; picking a specific .workflow.js per employee in that path is on the way.
Where to go next#
Build an AI employee
The full employee folder a workflow ships inside, and how to register it so it lands in a cell.
Read →The agent runtime
How the platform runs an employee: spawn-per-event, durable resume, and the sub-agent fan-out a workflow drives.
Read →The CLI
Reach your cell from the terminal and talk to your employee, where the explore and worker fan-out runs live.
Read →Set up your COO
The chief employee that plans a job and runs a crew of workers across your workspace.
Read →