Octov0.11.7
Getting Started

Your First Flow

Run a hello-world flow with the octo CLI and invoke a flow by name.

This page runs two flows from the repository's samples/ directory: one on a schedule, one called from the command line. You need the bin/octo binary from Installation.

A scheduled flow

samples/hello-world.yaml is the smallest complete config: one connector, one named processor, and one flow.

samples/hello-world.yaml
service:
  name: hello-world

connectors:
  - name: ticker
    type: cron

processors:
  - name: greeter
    type: log
    settings:
      level: info
      message: '"hello world! the date is " + body.date'

flows:
  - name: greet
    source:
      connector: ticker
      type: cron
      settings:
        schedule: "0,30 * * * * *"
        payload: '{"date": string(now)}'
    process:
      - ref: greeter

connectors declares a cron connector named ticker. processors defines a reusable, named block: greeter is a log block whose message is a CEL expression that can read the message body, vars, eventID, and correlationID. flows binds them together: the source attaches the flow to ticker with a six-field cron schedule (with seconds) that fires at seconds 0 and 30 of every minute, the payload expression (with now bound to the fire time) becomes each message's body, and the one process step references the named processor with ref: greeter.

Run it:

bin/octo run --config samples/hello-world.yaml

run is the default subcommand, so bin/octo --config samples/hello-world.yaml works too, and flags accept one or two dashes. The runtime prints a ready banner, then logs a greeting every 30 seconds:

time=... level=INFO msg="starting runtime" version=0.11.7 connectors=1 flows=1╭────────────────────────────────────────────────╮│  🐙  octo v0.11.7 is up and ready to roll!  🐙  ││          1 flow · 1 connector serving          │╰────────────────────────────────────────────────╯time=... level=INFO msg="hello world! the date is 2026-07-09T12:00:00Z"time=... level=INFO msg="hello world! the date is 2026-07-09T12:00:30Z"

Stop it with Ctrl-C.

Add --watch to reload the runtime whenever the config file changes, so an edit to the schedule or the message takes effect without a restart.

Invoking a flow directly

A flow with no source: gets an implicit source and becomes callable by name. samples/hello-invoke.yaml is exactly that:

samples/hello-invoke.yaml
service:
  name: hello-invoke

flows:
  - name: greet
    process:
      - type: set-payload
        name: build-greeting
        settings:
          value: '{"greeting": "hello, " + body.name + "!"}'

set-payload replaces the message body with the result of its value expression. Call the flow with octo invoke, passing the request body as JSON:

bin/octo invoke --config samples/hello-invoke.yaml --flow greet --data '{"name":"Ada"}'
{"event_id":"5c12a0e3f47b4d19a6c8f0b21d3e4a97","body":{"greeting":"hello, Ada!"}}

What prints is the whole result message: its event_id, the variables the flow set (none here), and the body. Pipe it through jq '.body' when the body is all you want. When --data is omitted, invoke reads the body from stdin:

echo '{"name":"Grace"}' | bin/octo invoke --config samples/hello-invoke.yaml --flow greet

In invoke mode no sources are started, so nothing binds a port or fires a schedule; only the requested flow runs. That makes invoke the cleanest way to test a flow. Sourceless flows are also the building block for flow composition with flow-ref.

Lock it in with a test

Invoking a flow by hand tells you it works right now. A test tells you it still works after the next edit. The greet flow above already ships with one, and Unit-Testing a Flow runs it, breaks it on purpose, and shows what a suite can assert.

Next steps

Continue to the HTTP Quickstart to build a flow that serves real requests, or read Core Concepts for the full model behind flows, sources, and blocks.

On this page