Skip to main content
TOON workflows are built from nodes. Each node kind maps to a builder primitive in the Effect API.

Step

The most common node. Sends a prompt to an agent and produces structured output.
agents:
  coder:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are a senior software engineer.

steps[1]:
  - id: analyze
    agent: coder
    prompt: "Analyze the bug described below.\nDescription: {input.description}"
    output:
      summary: string
      severity: "low" | "medium" | "high"
Steps execute sequentially by default (top to bottom). A step can reference outputs from any earlier step.

Step Properties

PropertyTypeRequiredDescription
idstringyesUnique identifier within the workflow
agentstringnoName of a declared agent (required for prompt: steps)
promptstringnoThe prompt sent to the agent. Supports {interpolation}
runtypescriptnoInline TypeScript code block (alternative to prompt)
handlerstringnoPath to a TypeScript handler function
outputschemayesOutput shape — inline or imported
needsstring[]noExplicit dependency on other step ids
retryobjectnoRetry policy
timeoutstringnoTimeout duration (e.g. "30s", "5m")
cacheobjectnoCache policy
skipIfstringnoCondition expression to skip the step

Retry Policy

steps[1]:
  - id: flaky-api
    prompt: "Call the external API."
    output:
      result: string
    retry:
      maxAttempts: 3
      backoff: exponential
      initialDelay: 250ms

Timeout

steps[1]:
  - id: slow-step
    prompt: "Process a large dataset."
    output:
      status: string
    timeout: 5m

Sequence

Steps are sequential by default. Use an explicit sequence node when you need to group steps inside a parallel block:
steps[1]:
  - kind: parallel
    children[2]:
      - kind: sequence
        children[2]:
          - id: step-a
            prompt: Do A.
            output:
              result: string
          - id: step-b
            prompt: Do B using {step-a.result}.
            output:
              result: string
      - id: step-c
        prompt: Do C independently.
        output:
          result: string

Parallel

Execute steps concurrently:
steps[2]:
  - kind: parallel
    children[2]:
      - id: research-web
        prompt: Search the web for {input.topic}.
        output:
          findings: string

      - id: research-docs
        prompt: Search internal docs for {input.topic}.
        output:
          findings: string

  - id: combine
    prompt: "Combine these findings:\nWeb: {research-web.findings}\nDocs: {research-docs.findings}"
    output:
      summary: string

Concurrency Limit

steps[1]:
  - kind: parallel
    maxConcurrency: 2
    children[3]:
      - id: task-1
        prompt: Do task 1.
        output:
          result: string
      - id: task-2
        prompt: Do task 2.
        output:
          result: string
      - id: task-3
        prompt: Do task 3.
        output:
          result: string

Loop

Repeat steps until a condition is met:
steps[2]:
  - id: draft
    prompt: Write a first draft about {input.topic}.
    output:
      content: string

  - kind: loop
    id: review-loop
    maxIterations: 5
    until: "{review.approved} == true"
    children[2]:
      - id: review
        prompt: "Review this draft:\n{draft.content}"
        output:
          approved: boolean
          feedback: string

      - id: revise
        prompt: "Revise the draft based on feedback:\n{review.feedback}"
        output:
          content: string
        skipIf: "{review.approved}"

Loop Properties

PropertyTypeRequiredDescription
idstringnoLoop identifier
maxIterationsnumbernoMaximum iterations (default: 5)
untilstringyesStop condition expression
onMaxReached"fail" | "return-last"noBehavior at max iterations (default: "return-last")
childrennodesyesSteps to repeat

Approval

Suspend execution for human review:
steps[3]:
  - id: build
    prompt: Build the deployment package.
    output:
      version: string
      commitSha: string

  - kind: approval
    id: approve-deploy
    needs[1]: build
    request:
      title: "Deploy {build.version}?"
      summary: "Commit {build.commitSha} passed all checks."
    onDeny: fail

  - id: deploy
    needs[2]: build,approve-deploy
    prompt: Deploy version {build.version}.
    output:
      url: string

Approval Properties

PropertyTypeRequiredDescription
idstringyesGate identifier
needsstring[]noDependencies
requestobjectyesTitle and summary for the reviewer
onDeny"fail" | "continue" | "skip"noDenial behavior (default: "fail")

Branch

Choose between paths based on a condition:
steps[2]:
  - id: classify
    prompt: Classify the severity of {input.description}.
    output:
      severity: "low" | "medium" | "high"

  - kind: branch
    condition: "{classify.severity} == 'high'"
    then[1]:
      - id: escalate
        prompt: Escalate to senior engineer.
        output:
          action: string
    else[1]:
      - id: auto-fix
        prompt: Generate an automated fix.
        output:
          patch: string

Worktree

Execute steps in an isolated JJ worktree:
steps[1]:
  - kind: worktree
    path: ./feature-branch
    children[1]:
      - id: implement
        prompt: "Implement the feature described in {input.spec}."
        output:
          files: "string[]"
          summary: string
The worktree is created before the children execute and cleaned up after.

Nesting

Node kinds can be nested freely:
steps[1]:
  - kind: parallel
    children[2]:
      - kind: sequence
        children[2]:
          - id: a
            prompt: Do A.
            output:
              result: string
          - kind: loop
            until: "{check.done}"
            children[1]:
              - id: check
                prompt: Check status.
                output:
                  done: boolean
      - id: b
        prompt: Do B.
        output:
          result: string

Next Steps