Menu

WORKFLOW ENGINE — ORCHESTRATION  ·  beta

Graph workflows for AI agents, in one native binary

NullBoiler runs multi-step AI agent workflows: define a graph of steps and it dispatches each one to a worker — an agent runtime like NullClaw, any OpenAI-compatible API, or your own webhook — with retries, concurrency limits, and a checkpoint saved after every node. One ~2 MB native Zig binary with SQLite embedded: no external database, no message broker required.

Continue reading

retries, concurrency limits, and a checkpoint saved after every node. One ~2 MB native Zig binary with SQLite embedded: no external database, no message broker required.

curl -LO https://github.com/nullclaw/nullboiler/releases/latest/download/nullboiler-macos-aarch64.bin
chmod +x nullboiler-macos-aarch64.bin
./nullboiler-macos-aarch64.bin --version

Overview

The useful part, at a glance.

In plain words

What it is
An orchestration engine for AI agents: one native Zig binary, roughly 2 MB with SQLite embedded, that serves an HTTP API and executes workflows defined as graphs of steps. Each step is dispatched to a worker — an agent runtime like NullClaw, any OpenAI-compatible chat API, a plain webhook, or an A2A (Agent2Agent JSON-RPC) endpoint — and a checkpoint is written after every node so runs survive crashes and restarts.
Where it fits
The coordinator — bring it in when one agent in a loop stops being enough and work needs routing, retries and checkpoints across a pool of workers.
Why it exists — and when you need it
Why it exists
The moment work spans multiple steps and multiple agents, something has to own coordination: ordering, routing between branches, retries with backoff, concurrency caps, and recovery when a worker or the whole process dies. That job does not belong inside the task tracker or inside the agent itself, so NullBoiler does exactly that and nothing else — no framework to import, no database or broker to run alongside it.
When you need it
Reach for it when one agent in a loop stops being enough: you want to fan a job out across twenty items in parallel, mix an expensive agent runtime with a cheap completion API in one pipeline, or let agents claim tickets from a backlog overnight under leases and turn limits. It also earns its keep the first time a long run fails at step eight and you resume from the last checkpoint instead of paying for steps one through seven again.

How it works

From zero to running.

NullBoiler is one native binary with SQLite inside, so the whole flow is: download, write one JSON file, start it, POST a run. There is no database to provision and no broker to stand up.

  1. Get the binary

    Download the prebuilt release for your platform — there are seven, covering Linux (x86_64, aarch64, riscv64), macOS (x86_64, aarch64), and Windows (x86_64, aarch64) — each 1.9-2.3 MB with SQLite 3.51.2 statically embedded. Make it executable and check the version.

    curl -LO https://github.com/nullclaw/nullboiler/releases/latest/download/nullboiler-macos-aarch64.bin && chmod +x nullboiler-macos-aarch64.bin
  2. Describe your workers

    Config lives at ~/.nullboiler/config.json (override the directory with NULLBOILER_HOME or the file with --config); a missing file silently yields working defaults. The part you actually write is workers[]: each worker gets an id, a url, a protocol (webhook by default; api_chat, openai_chat, or a2a (Agent2Agent JSON-RPC) also work end-to-end), optional tags for routing, and max_concurrent. Everything else — engine timings, retries, tracker mode — has defaults you can tune later.

    {"workers": [{"id": "coder-1", "url": "http://127.0.0.1:9100/hook", "protocol": "webhook", "tags": ["coder"]}]}
  3. Start the orchestrator

    One process: it opens the SQLite database, seeds workers from config, and serves a hand-written HTTP/1.1 API on 127.0.0.1:8080. A background engine thread polls every 500 ms and walks each run's graph from its latest checkpoint. Note the security posture: localhost-only bind and no auth by default — set api_token before exposing it further.

    ./nullboiler-macos-aarch64.bin --config config.json --port 8080 --db nullboiler.db
  4. Create a run

    POST a graph of steps to /runs — here a single task step routed to any worker tagged "coder", with the prompt built from a {{input.goal}} template expression. The engine dispatches it, retries with backoff on failure, and writes a checkpoint after the node completes.

    curl -sS -X POST http://127.0.0.1:8080/runs -H 'Content-Type: application/json' -d '{"input":{"goal":"summarize the repo"},"steps":[{"id":"step-1","type":"task","worker_tags":["coder"],"prompt_template":"{{input.goal}}"}]}'
  5. Watch it move

    Poll GET /runs/{id} for the final output, or attach to GET /runs/{id}/stream for a live event stream in values, updates, or debug mode with an after_seq cursor for reconnects. Prometheus text is at GET /metrics (11 counters, 4 gauges, all prefixed nullboiler_), and GET /workflows/{id}/mermaid renders any saved workflow as a Mermaid diagram.

    curl -sS http://127.0.0.1:8080/runs/<run_id>/stream?mode=values

Release binaries

One binary, ready to run.

Cross-compiled by nullbuilder for supported platforms — no language runtime or system-wide installer required.

Exact digests come from the repository manifest. If a future release also publishes a checksum file or detached signature, that upstream evidence appears beside the asset.

Capabilities

What NullBoiler does.

Checkpoints after every node

The engine writes a checkpoint after every node and each tick resumes from the latest one. Replay a run from any checkpoint, fork it into a new run with POST /runs/fork, set breakpoints with interrupt_before/after, or inject state mid-flight.

Seven graph node types

task, agent, route, interrupt, send, transform and subgraph nodes, wired with directed edges. Route nodes pick the next edge by label; hand a send node an array — twenty files to review, say — and it dispatches the target step once per item, then gathers the results.

One state, seven reducers

Each run carries a typed state object with a reducer per key — last_value, append, merge, add, min, max or add_messages — so parallel branches merge deterministically. Prompt templates read it with {{input.X}}, {{state.X.Y}} and {{item}} expressions.

Four dispatch protocols

Workers speak plain webhook, api_chat, openai_chat, or A2A (Agent2Agent over JSON-RPC 2.0). One config can mix the NullClaw agent runtime with any OpenAI-compatible endpoint in the same worker pool. MQTT and Redis Streams can publish requests too, but their response listeners are stubs in the current release, so neither works end to end yet.

Pull mode against NullTickets

NullTickets is its companion task tracker for agent work. Point NullBoiler at it and it polls for open tasks, claims them by role, keeps leases alive with heartbeats, prepares a per-task workspace with your shell hooks, then runs each task in a NullClaw subprocess with health checks, turn limits and stall detection.

Operations built in

Per-worker circuit breakers, retry backoff with jitter, drain mode with a graceful-shutdown grace period, and idempotency keys on POST /runs. Watch a run live at GET /runs/{id}/stream, scrape Prometheus /metrics, or export any workflow as a Mermaid diagram.

Use it for

Where it earns its place.

A review pipeline that fans out over twenty files

You want every changed file reviewed by an agent, then one summary. Define a workflow where a Send node takes the file array and dispatches a review step once per item across your tagged worker pool, an append reducer collects the results, and a final task step summarizes {{state.reviews}}. The engine caps concurrency per worker via max_concurrent, retries flaky steps with jittered backoff, and checkpoints after every node — kill the process mid-run and it resumes from the last checkpoint on restart.

curl -sS -X POST http://127.0.0.1:8080/workflows/{id}/run -d '{"input":{"files":[...]}}'
Agents working a ticket backlog overnight

Turn on tracker mode and NullBoiler becomes a pull-mode supervisor against NullTickets (the stack's task tracker for agent work): it polls for open tasks, claims them by role under leases with heartbeats, prepares a per-task workspace with your shell hooks, and runs each task in a NullClaw (the stack's agent runtime) subprocess with health checks, a 20-turn ceiling, and stall detection. Concurrency is capped per pipeline, per role, and per state, so ten agents grinding through a backlog cannot trample each other. Check on it in the morning at GET /tracker/stats.

One worker pool mixing agent runtimes and plain APIs

Your graph has steps that need a full agent and steps that just need a completion. Register a NullClaw worker over webhook and any OpenAI-compatible endpoint over openai_chat in the same workers[] array, tag them ("coder", "cheap-llm"), and let each step's worker_tags pick the right one. Per-worker circuit breakers open after repeated failures so a dead endpoint stops receiving dispatches, and GET /rate-limits shows what the engine is tracking per worker.

Debugging a failed run without re-running everything

A ten-step run died at step eight. Because a checkpoint with a parent-chained ID exists after every node, you can POST /runs/{id}/replay from any checkpoint, POST /runs/fork to branch an experiment off the same history, set interrupt_before/interrupt_after breakpoints, or POST /runs/{id}/state to inject corrected state mid-flight and resume. The whole trail is inspectable at /runs/{id}/steps, /events, and /checkpoints.

What's inside Counted in the source, not the brochure. 41 listed

7 graph node types

Route picks the next edge by label, Send fans a step out once per array item, Interrupt pauses a run for a human, Subgraph nests a whole workflow as one node.

  • Task
  • Agent
  • Route
  • Interrupt
  • Send
  • Transform
  • Subgraph

7 state reducers

Each state key declares its reducer, so parallel branches merge deterministically — the same unified-state model LangGraph uses, here as a language-agnostic server.

  • last_value
  • append
  • merge
  • add
  • min
  • max
  • add_messages

6 worker protocols — 4 work end to end

The four HTTP protocols actually transport work today; the MQTT and Redis Streams response listeners are explicit stubs in the current release.

  • Webhook
  • API chat
  • OpenAI chat
  • A2A (JSON-RPC 2.0)
  • MQTT (stub)
  • Redis Streams (stub)

36 HTTP operations

Runs, workflows, workers, tracker, and operations groups — every run is inspectable down to individual steps, events, and parent-chained checkpoints.

  • POST /runs
  • GET /runs/{id}/stream
  • POST /runs/{id}/replay
  • POST /runs/fork
  • POST /runs/{id}/state
  • GET /runs/{id}/checkpoints
  • POST /workflows/{id}/run
  • GET /workflows/{id}/mermaid
  • GET /tracker/status
  • GET /rate-limits
  • POST /admin/drain
  • GET /metrics
  • +24 more

6 template expressions, 2 shipped strategies

Prompt templates read run input, unified state, fan-out items, and the shared key-value store served by NullTickets (the stack's companion task tracker); strategies are JSON files that expand a one-word field into a depends_on graph.

  • {{input.X}}
  • {{state.X.Y}} with [-1] indexing
  • {{item}}
  • {{task.X}}
  • {{attempt}}
  • {{store.ns.key}}
  • Sequential (chain)
  • Parallel (independent)

Quickstart

Up and running.

Full walkthrough in the docs — verified with v2026.5.29.

Start the orchestrator, hand it one task, watch the run move. No external database, no broker.

# start the orchestrator — one binary, SQLite inside
./zig-out/bin/nullboiler --config config.json --port 8080 --db nullboiler.db

# create a run: one task step, dispatched to workers tagged "coder"
curl -sS -X POST http://127.0.0.1:8080/runs \
  -H 'Content-Type: application/json' \
  -d '{"input":{"goal":"summarize the repo"},"steps":[{"id":"step-1","type":"task","worker_tags":["coder"],"prompt_template":"{{input.goal}}"}]}'

# track progress; final output lands in steps[].output_json.output
curl -sS http://127.0.0.1:8080/runs/<run_id>

Common questions

Questions, answered.

Do the MQTT and Redis Streams protocols work?

Not end to end, no. Requests can be formed for them, but the response listeners are explicit stubs that sleep until shutdown, and the vendored C libraries are no-op fakes. Today the four HTTP protocols — webhook, api_chat, openai_chat, and A2A — are the ones that actually transport work; treat mqtt and redis_stream as a preview of the enum, not a feature.

Do I need Postgres, Redis, or a message broker?

No. SQLite 3.51.2 is statically embedded in the ~2 MB binary and holds runs, steps, events, and checkpoints; dispatch to workers happens over plain HTTP. The trade-off is that one NullBoiler process owns one database file — there is no multi-node clustering story.

Does it require the rest of the Null ecosystem?

The core engine does not: point it at any webhook or OpenAI-compatible endpoint and it orchestrates them standalone. Tracker mode is the exception — pull-mode task claiming needs a NullTickets server (the stack's task tracker), and its subprocess runner shells out to the nullclaw command (the stack's agent runtime CLI). NullHub (the stack's installer console) is optional sugar: nullboiler --export-manifest emits its 22-step install-wizard manifest and --from-json generates a config from wizard answers.

Is it safe to expose on a network?

The defaults are deliberately conservative: it binds 127.0.0.1 only, and authentication is disabled until you set api_token (bearer token) in config or via --token. If you move it off localhost, set the token first and put TLS in front — the built-in server speaks plain HTTP/1.1 with an 8 MiB request cap.

How does it relate to LangGraph?

It borrows LangGraph's execution model — a unified state with per-key reducers (including add_messages), route/send/subgraph nodes, checkpointing, interrupts — but implements it as a language-agnostic server in Zig rather than a Python library. Your workers can be written in anything that answers HTTP; you talk to the graph over 36 REST operations instead of importing a framework.

Is it production-ready?

It is pre-1.0 with CalVer releases (v2026.5.29 currently), so config and CLI shapes may change between versions. The engineering is serious for its age — 355 test blocks, an e2e harness, circuit breakers, drain mode, idempotency keys — but pin a release, read the changelog before upgrading, and know that building from source without the -Dversion flag stamps a stale version string into --version.

Pre-1.0 with CalVer releases: config and CLI may change between releases. Default bind is 127.0.0.1 with auth off — set api_token before exposing it beyond localhost.