Workflow
A workflow is a state machine as data. Not code, not a generator, not a coroutine — a definition the runner interprets against the events on a scroll. That one choice is what makes replay exact, authoring portable, and LLMs plausible workflow authors.
Data, not a code path
Most agent frameworks express a workflow as imperative code — a function with loops, branches, and awaits. To re-run it, you re-execute the code. To inspect it, you read the code. To migrate it, you rewrite the code.
Weave goes the other way. A workflow is a compiled definition — nodes, triggers, emits, terminals — stored as data. The runtime ships a single, pure runner that interprets any workflow definition against any scroll. The code path doesn't change between workflows; the data does.
The consequences are not subtle. A workflow you can inspect as a graph. A workflow you can diff between versions. A workflow an LLM can write. A workflow that replays exactly because there is no hidden code-path state — only the scroll has state.
The runner
The runner is a tiny loop — one piece of code for every workflow in the system:
- 1 Read
Subscribe to the scroll and see every event in order, from offset 0 to the live head.
- 2 Match
For each event, find the workflow node whose trigger matches — a topic, a guard, a terminal condition.
- 3 Emit
When a node fires, append the derived event it declares (ai.request, tool.dispatch, workflow.step) back to the scroll.
- 4 Wait
The runner has no in-memory state of its own. It waits for the next external commit, and the loop repeats.
There is nothing else. No coroutine suspended in memory, no promise chain, no retry controller with its own state machine. The runner's only job is "given this definition and this scroll, decide what to emit next." Restart the process mid-run and the runner picks up from the scroll — not from saved execution context, because there is none to save.
Anatomy
A workflow definition names the steps and what triggers each one. Here's the shape, expressed as plain data:
# A workflow is data. This is the shape; authoring UX
# (Go builders, .weave DSL) compiles down to this.
workflow: greet
nodes:
- id: ask
emit: ai.request
payload:
prompt: "greet the traveler warmly: {{.input.name}}"
- id: speak
on: ai.response
emit: tool.dispatch
payload:
name: speak
text: "{{.response.text}}"
- id: done
on: tool.result
terminal: trueThe authoring UX is sugar on top. Whether you write a .weave file or use the typed builder in Go, the
output is the same definition format:
// Typed authoring surface in Go. Compiles to the same
// workflow definition as a .weave file would.
var greet = workflow.Define("greet",
workflow.Step("ask").Emit(ai.Request{
Prompt: "greet the traveler warmly: {{.input.name}}",
}),
workflow.Step("speak").On(ai.ResponseRef).Emit(tool.Dispatch{
Name: "speak",
Text: "{{.response.text}}",
}),
workflow.Step("done").On(tool.ResultRef).Terminal(),
)What the runner does on a scroll
Put the definition and the runner together, and a run looks like this — externals arrive from the outside, deriveds are emitted by the runner in response:
# Same scroll, interpreted. Externals arrive from the outside;
# the runner emits derived events in response.
scroll: run-abc123
[0] workflow.step derived { id: "ask" }
[1] ai.request derived { prompt: "greet …" }
[2] ai.response external { text: "welcome, traveller" }
[3] workflow.step derived { id: "speak" }
[4] tool.dispatch derived { name: "speak", text: "welcome, …" }
[5] tool.result external { ok: true }
[6] workflow.step derived { id: "done" }External commits are the only thing the runner waits on. Everything else is a consequence it computes. If you have the definition and the externals, the entire trace is reproducible — that is replay.
Why replay is exact, not approximate
Replay works because there is nothing to reproduce that isn't
either in the scroll or in the definition. The runner is a pure
function of (definition, events-so-far). Feed it
the same definition and the same events, and it emits the same
next event. Every time.
That is not achievable in frameworks where workflow semantics live inside code paths. Line numbers, retry timers, and implicit async state all drift. Weave moves that state out of the code and into data, where it can be captured, replayed, and proven.
Authoring
Three ways to write a workflow, all producing the same data:
- The
.weaveDSL. A purpose-built language with its own LSP and lint rules. Best for workflows you want humans (and LLMs) to read and edit. - Typed builders. Native Go, TypeScript, or Python APIs that compile to the same definition. Best when the workflow is co-located with application code that references strongly-typed payloads.
- LLM-authored. Because workflows are data, an LLM can produce them as text. The runner doesn't care who wrote them as long as they parse — which opens up workflows that are generated, refined, or adapted at runtime.
Status
The workflow runner is the newest major piece of weave and the one where docs lead code most visibly. The definition format and DSL are solid; the full multi-step runner with recovery and workflow-level replay is the active milestone.
Not to be confused with
- Temporal. Temporal guarantees workflows run to completion across failures — durable execution of code. A weave workflow is a definition the runner interprets; its "durability" comes from the scroll, not from a runtime that remembers where it left off. Different primitive, different guarantee.
- LangGraph / agent framework graphs. Graph shapes look similar on the surface, but LangGraph executes in memory and treats the graph as code. Weave's graph is data and the runner is a separate, reusable interpreter. That's why weave workflows replay and LangGraph runs don't.
- A BPMN / YAML workflow engine. Those tools are definition-based too, but aimed at business-process orchestration with human tasks. Weave is purpose-built for AI workflows — the primitives assume model calls, tool dispatches, and replay from captured externals.