Skip to content

The argument

Ask a system "what was our monthly revenue by region?" and something has to turn that into steps: look at the database schema, draft a query, run it, notice it failed, work out why, try again.

A program that does this — choosing each step from the result of the last — is what people now call an AI agent. The part that decides which step runs next, and remembers what happened, is orchestration. That is what Swarmplane is.

The remembering turns out to be the hard part.

Why remembering is hard

One attempt at a task like that is a run. When a run finishes — or fails at 3am, for one user, against a model that will answer differently next time — there is only one question worth asking: what did it actually do, and why?

Most orchestrators make that harder than it sounds, because a run leaves behind two separate artefacts. There is the state the runtime saved as it went — a checkpoint, meaning a snapshot of what the system knew at some moment — and there is the trace it emitted alongside for monitoring. Neither one is the run. Answering the question means holding both up against each other and hoping they agree.

The two dominant designs each solve half of the problem:

Plan-and-execute asks a model to lay out the steps up front, then runs them. You get a structure you can show someone before anything executes. Then step three returns something nobody anticipated — a column that doesn't exist, a service returning 403 — and the plan is fiction.

ReAct loops (think, act, observe, repeat) drop the plan and decide each step from the last result. They adapt beautifully, because adapting is all they do. What they leave behind is a transcript: a flat list of messages you cannot diff, cannot ask which step caused which other step, and cannot hand to an auditor.

One gives structure without adaptation. The other gives adaptation without structure.

What Swarmplane does instead

Swarmplane records the run as a graph. Each step is a node. An arrow between two nodes — an edge — is a claim that the first one caused the second.

The graph is not drawn in advance. It grows as results arrive, and it only ever grows: expansion is append-only, meaning nothing already recorded is edited or deleted. So a retry is not a second go at an old step. It is a new node whose parent is the failure.

Because every new node attaches to nodes that already exist, no arrow can ever point backwards into the past. The graph is acyclic by construction — it has no loops — and reading it start to finish reads the run in the order it actually happened.

Here is that revenue question, run end to end:

%%{init: {"flowchart": {"rankSpacing": 34, "nodeSpacing": 26, "padding": 8}}}%%
graph LR
    T(Task: monthly revenue by region) --> S(Inspect schema)
    S --> Q1(Draft SQL v1)
    Q1 --> F("Failed: no column 'region'")
    F --> D(Discover: region lives in dim_store)
    D --> Q2(Draft SQL v2 with join)
    Q2 --> R(Validated result)

    %% LR, not TD: this renders inline in the philosophy essay's ~688px column,
    %% where the SVG scales down to fit (~0.76 today). Every node added to the
    %% chain shrinks the whole strip there — check that page before growing it.
    %% Semantic color lives in the stroke, not the fill: Material themes node
    %% fill and label color per scheme (light/dark) via --md-mermaid-* variables
    %% and overrides classDef `color:`, so a colored fill cannot guarantee
    %% readable labels in both schemes. A colored border can.
    %% NOTE: every comment line needs content after `%%` — a bare `%%` line is
    %% parsed as a node and renders as a stray box labelled "%%".
    %% `start` is a mid-copper, not --sp-accent: a classDef takes one literal for
    %% both schemes, and neither accent token survives the other background
    %% (#a2561f is 2.7:1 on dark, #d9915b is 2.6:1 on light — both under 3:1).
    %% #c07a3e sits between them at 5.5:1 dark / 3.3:1 light. Do not "fix" it
    %% back to the token.
    classDef start stroke:#c07a3e,stroke-width:2.5px
    classDef fail stroke:#c9705f,stroke-width:2.5px
    classDef ok stroke:#5e8f6c,stroke-width:2.5px
    class T start
    class F fail
    class R ok

Six steps. The run failed at the fourth — the query referenced a region column that did not exist — then recovered by finding where region actually lived and rewriting the query with a join.

Now notice what became of the failure. It was not erased and retried in place. It sits in the graph as the parent of the discovery it caused, which is why the final node can answer why does this query contain a join: the reason is a step you can point at. A transcript would record the same six events and be unable to tell you which one explains the answer.

That is the whole idea.

Two properties follow

The execution graph is meant to be the audit log — the durable record you produce when someone asks why the system did what it did. Every node is a span, the unit distributed tracing already uses for a single timed operation, so the graph is a trace without being a second copy of one. Every edge is a causal claim. The thing that ran and the record of what ran are one object.

Acyclicity alone does not deliver that. A record only becomes an audit record when it is also complete, durable, ordered, attributable, and protected against silent mutation, which is a commitment the implementation has to earn rather than a property that falls out of the shape. The design specifies how each of those is met; no running code has earned any of them yet.

Every seam is an open protocol. MCP for how agents call tools, A2A for how they hand work to other agents, and A2UI for how an agent describes an interface to be rendered. Nothing proprietary sits between layers — including Swarmplane itself, which you can remove and keep your agents.

Three opinions

  1. The graph is primary, not the agent. Agents are node executors.
  2. Every boundary is an open protocol. No proprietary abstraction at any seam.
  3. GCP-native, not cloud-agnostic. Portability is not a goal.

Swarmplane is not unopinionated. It is transparent: every decision is visible, inspectable, and replaceable. Each opinion is stated with what it costs in Why Swarmplane — disagreeing with any of them is a good reason to use something else.

On the name

"Swarm" means fleet, not emergence — nothing here is non-deterministic by intent. A plane, as in control plane, coordinates a fleet of workers against shared state. The longer version.

Why append-only makes serverless work

Cloud Run, Google's serverless container platform, allows a request to last up to 60 minutes — but a request dispatched through Cloud Tasks, its managed queue, is capped at 30 and defaults to 10. Frameworks that model an agent run as one long-lived process fight that ceiling, and lose at exactly the moment a run gets interesting.

Swarmplane has no long-lived process. Each expansion is one short request against durable state — read the frontier (the set of nodes eligible to grow next), execute a node, append the outcome. A crash resumes by reading that same frontier, which is the operation the scheduler performs anyway, so recovery is not a separate code path that only runs during incidents.

The stronger half of the argument is that a node need not occupy a process at all. A node parked on a human approval, a webhook, or a remote agent enters waiting: still alive, holding no worker and no open request, resuming when the external event arrives. An agent can wait six hours without anything running for six hours.

The constraint that breaks other designs is one this design already had to satisfy.

Status

Pre-alpha. Swarmplane is a reference implementation — a small, deliberately readable codebase meant to be read, copied, and forked. It is installable (uv add swarmplane) so you can build on the argument, but there is no API-stability promise: the version stays 0.x, and any release may break any API (ADR-0007). Anything on PyPI at 0.0.x is a name reservation, not a release.

Read next:

  1. Why Swarmplane — the case for building this at all, and what it costs.
  2. The Living Graph — the argument everything else rests on.
  3. Prior Art — an honest account of what is and isn't new here, including where this came from.