JJETSYNGet in touch

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.

workflows/template.workflow.js
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.

FunctionWhat 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.
argsThe 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).

the gated step loop
// 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.

workflows/starter.workflow.js (discover phase)
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-agentRoleCan write
exploreRead-only scout: reads files, searches, and reports back what it found.No
workerBuilder: 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. 1

    Copy the primitive

    Start from agents/templates/template-worker/workflows/template.workflow.js (three gated steps) or hello-world (one real end-to-end step). Copy it to agents/templates/<your-employee>/workflows/<name>.workflow.js.

  2. 2

    Set meta

    Give it a name, a one-line description, a whenToUse, and a phases array. Set meta.name to match the file and the employee; nothing validates that match for you, so a mismatch fails silently at render time.

  3. 3

    Write the steps

    In the body, call phase() to mark each stage, agent() to run each step with a schema, and log() for progress. Gate later steps on earlier ones, or fan out with parallel().

  4. 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. 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 args to 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.

NameWhat it is
templateThe canonical deployment primitive: one orchestrator and one worker instantiated as three gated steps. The copy-me starting point.
hello-worldThe smallest real end-to-end run: an orchestrator kicks it off, one agent composes and dispatches a single email, a dry run by default.
starterThe onboarding fan-out: a parallel discover phase, a strategy barrier that authors the company spine, then a parallel build-and-launch phase.
pocThe 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#