Corbie

Start

Machines together

A job can be another machine, so a statechart can run a flow and an agent can use a flow as a tool.

Both examples use title.yaml from First machine. Record lines are shown without their time and run number, and <id> is the child run's ID.

A statechart runs the flow

# watch.yaml

statechart: watch-title
initial: fetching

states:
  fetching:
    job:
      machine: ./title.yaml
      input: { url: "https://example.com" }
    on: { done: finished }
  finished:
    end: done
    output: ${states.fetching.output.title}
corbie test watch.yaml --watch  step 1  <- start  step 1  -> job fetching: a machine, <id>    step 1  <- start {"url":"https://example.com"}    step 1  -> job get_title: a script, on runner    step 2  <- done from get_title {"title":"Example Domain","url":"https://example.com"}    step 2  done  step 2  <- done from fetching {"title":"Example Domain","url":"https://example.com"}  step 2  done  output: "Example Domain"

The job of the state fetching is the flow. The controller starts it as a child run, whose lines are indented. When the child run ends, the statechart receives done with the flow's output and moves to finished.

An agent uses the flow as a tool

# helper.yaml

agent: page-helper
input: { question: string }
model: { name: claude-haiku-5-5, secret: ANTHROPIC_KEY }
prompt: ${input.question}

tools:
  title:
    does: Give the title of a web page
    args: { url: string }
    job:
      machine: ./title.yaml
      input: { url: "${args.url}" }
corbie test helper.yaml --input '{"question": "What is the title of the page at https://example.com?"}' --secret-env ANTHROPIC_KEY --watch  step 1  -> job turn: a script, on runner  step 2  -> job title: a machine, <id>    step 1  -> job get_title: a script, on runner    step 2  done  step 3  <- done from title {"title":"Example Domain","url":"https://example.com"}  step 3  -> job turn#2: a script, on runner  step 4  done

--secret-env ANTHROPIC_KEY reads the model's API key from that environment variable. Each turn is a job that calls the model. In the first turn the model called the tool title, whose job is the flow, and in the second it answered.

As a job the flow keeps its own allow: list: the script may still connect only to example.com.

A statechart sends to a run by its key

# room.yaml

statechart: room
initial: open
states:
  open:
    on:
      knock: open
      leave: closed
  closed: { end: done, output: "${run.key}" }

# desk.yaml

statechart: desk
initial: first
states:
  first:
    entry:
      send:
        - { to: { machine: ./room.yaml, key: "room:1" }, event: knock, data: { by: ana } }
        - { to: { key: "room:1" }, event: leave }
        - { to: { key: "room:2" }, event: knock }
    timers: { pause: 1s }
    on: { pause: last }
  last: { end: done }
corbie test desk.yaml --watch  step 1  <- start  step 1  -> send knock to the key "room:1"  step 1  -> send leave to the key "room:1"  step 1  -> send knock to the key "room:2" (undelivered: it is not running)  step 2  done

The first send finds no run with the key room:1, so it starts room.yaml with that key. The second is delivered to that run. No run has room:2, and the sender's record says so.

Next: A real run runs title.yaml on a controller and a runner.