Skip to main content
TOON is the primary way to define Smithers workflows. A .toon file uses the TOON format (Token-Oriented Object Notation) — a compact, line-oriented data format that combines YAML-style indentation for objects with CSV-style tabular layout for uniform arrays. TOON is not YAML: it requires explicit array lengths ([N]), supports tabular {field} headers, and has no comment syntax. Smithers compiles .toon files to the same durable execution engine as the Effect builder API.

What Is a TOON File?

A .toon file describes a workflow as structured TOON blocks with embedded prompts and optional inline TypeScript. Smithers compiles it into a typed workflow graph at build time.
name: hello-world
agents:
  assistant:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are a friendly assistant.

input:
  greeting: string

steps[1]:
  - id: greet
    agent: assistant
    prompt: "Say hello to the user.\nTheir greeting was: {input.greeting}"
    output:
      message: string
The agents: block declares named agents — each with a type: (the runtime), a model:, and optional instructions: and tools:. Steps reference an agent by name via agent:. That is a complete workflow — no imports, no schema classes, no Effect boilerplate.

Three-Layer Architecture

Smithers offers three ways to define workflows, each building on the one below:
LayerFormatWhen to Use
TOON.toon filesMost workflows. Declarative, prompt-first, zero boilerplate.
Effect BuilderTypeScriptAdvanced control flow, custom services, complex dependency injection.
JSXTSXComponent-based API using React-style JSX syntax.
TOON files compile to the Effect builder internally. You can mix TOON and Effect in the same project — a TOON workflow can import Effect services, and an Effect workflow can reference TOON components.

Why TOON?

Most AI workflows are prompt-centric: define inputs, call a model with a prompt, get structured output, pass it to the next step. TOON makes that the default path:
  • No schema ceremony — field types are declared inline
  • Prompts are first-class — multi-line prompts live directly in the workflow definition
  • Control flow is visual — sequences, parallels, loops, and approvals are node kinds, not nested function calls
  • Components are reusable — extract repeated patterns into parameterized components
When you need something TOON can’t express — custom Effect services, complex branching logic, programmatic graph construction — drop down to the Effect builder API.

Minimal Example

A two-step research workflow:
name: research-report
agents:
  researcher:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are an expert research assistant.
  writer:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are a technical writer who produces clear, concise reports.

input:
  topic: string

steps[2]:
  - id: research
    agent: researcher
    prompt: "Research the following topic and provide a summary with key points.\nTopic: {input.topic}"
    output:
      summary: string
      keyPoints: "string[]"

  - id: report
    agent: writer
    prompt: "Write a report based on this research.\nSummary: {research.summary}\nKey points: {research.keyPoints}"
    output:
      title: string
      body: string
      wordCount: number
Steps execute sequentially by default. The report step references {research.summary} — Smithers resolves this as a typed dependency edge, just like needs in the Effect builder.

Control Flow

Steps execute sequentially by default. TOON supports the same control flow primitives as the JSX and Effect APIs.

Parallel

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

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

  - id: combine
    agent: writer
    prompt: "Combine findings:\nWeb: {research-web.findings}\nDocs: {research-docs.findings}"
    output:
      summary: string
This is the TOON equivalent of <Parallel> in JSX or $.parallel() in the Effect builder.

Loops

Repeat steps until a condition is met with kind: loop (equivalent to <Loop> in JSX):
steps[2]:
  - id: draft
    agent: writer
    prompt: Write a first draft about {input.topic}.
    output:
      content: string

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

      - id: revise
        agent: writer
        prompt: "Revise based on feedback: {review.feedback}"
        output:
          content: string
        skipIf: "{review.approved}"

Branching

Choose between paths with kind: branch (equivalent to <Branch> in JSX):
steps[2]:
  - id: triage
    agent: coder
    prompt: Classify the severity of this issue.
    output:
      severity: "low" | "medium" | "high"

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

Approvals

Suspend execution for human review with kind: approval:
steps[3]:
  - id: build
    agent: coder
    prompt: Build the deployment package.
    output:
      version: string

  - kind: approval
    id: approve-deploy
    request:
      title: "Deploy {build.version}?"
      summary: "Ready for production."
    onDeny: fail

  - id: deploy
    needs[1]: approve-deploy
    run: "return { url: \"https://app.example.com\", deployedAt: new Date().toISOString() };"
    output:
      url: string
      deployedAt: string
All control flow nodes can be nested freely — parallels inside loops, branches inside parallels, etc. See Nodes for the full reference.

Agents

Every prompt: step needs an agent to handle it. Agents are declared in the top-level agents: block and referenced by name on each step.

Agent types

The type: field selects the agent runtime. Smithers supports all the same agent backends as the JSX and Effect APIs:
TypeRuntimeJSX Equivalent
claude-codeClaude Code CLIClaudeCodeAgent
codexOpenAI Codex CLICodexAgent
geminiGemini CLIGeminiAgent
piPI Coding AgentPiAgent
kimiKimi CLIKimiAgent
forgeForge CLIForgeAgent
apiAI SDK (direct API)Agent from ai

Declaring agents

agents:
  coder:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are a senior software engineer.

  reviewer:
    type: claude-code
    model: claude-opus-4-6
    subscription: true
    instructions: You are a thorough code reviewer.
    tools[2]: read,grep

  fast-coder:
    type: codex
    model: gpt-5.4
    fullAuto: true
Each agent has:
  • type — which agent runtime to use
  • model — the AI model
  • instructions — system prompt / persona
  • Additional type-specific options (e.g., tools, fullAuto, permissionMode, timeoutMs)

Using agents in steps

steps[2]:
  - id: implement
    agent: coder
    prompt: Implement the feature described in {input.spec}.
    output:
      files: "string[]"

  - id: review
    agent: reviewer
    prompt: Review the changes in {implement.files}.
    output:
      approved: boolean
      feedback: string
Different steps can use different agents — even different agent types. A workflow can mix Claude Code, Codex, and API agents freely.

Importing agents

You can also import agents defined in TypeScript:
imports:
  agents[1]{from,use}:
    ./agents.ts,"coder,reviewer"
This lets you share agent configurations across workflows or use agents with custom setup that can’t be expressed in TOON syntax. Steps that use run: or handler: instead of prompt: do not need an agent — they execute TypeScript directly. See Inline Code for details.

How It Compiles

A .toon file compiles to the same internal graph as a Smithers.workflow(...).build(...) call:
.toon file
  -> TOON parser
  -> Workflow graph (same as Effect builder output)
  -> Normalized plan
  -> Durable execution
At runtime, there is no difference between a TOON workflow and an Effect builder workflow. They share the same engine, the same SQLite persistence, the same retry/approval/caching semantics, and the same observability.

Running a TOON Workflow

smithers run workflow.toon --input '{"topic": "Effect in TypeScript"}'
Or from TypeScript:
import { Smithers } from "smithers-orchestrator";
import { Effect } from "effect";

const workflow = Smithers.loadToon("./workflow.toon");

const result = await Effect.runPromise(
  workflow.execute({ topic: "Effect in TypeScript" }).pipe(
    Effect.provide(Smithers.sqlite({ filename: "./smithers.db" })),
  ),
);

Next Steps

  • TOON Installation — Install the runtime and optional plugins.
  • TOON Quickstart — Build a complete workflow in a single .toon file.
  • Schemas — Define input and output types inline.
  • Nodes — All node kinds: step, sequence, parallel, loop, approval, branch, worktree.
  • Prompts — Write prompts with interpolation and multi-line syntax.
  • Effect Builder API — When you need more power.