Orchestrator
The Orchestrator is the public Go type you construct once per process and use everywhere else. It owns your model, your sinks, your storage, and the optional coordinator runner, and it exposes the three entry points (RunFlow, RunGoal, RunAgent) that drive every other concern in zenflow.
It is, deliberately, the only top-level type to learn.
orch := zenflow.New(
zenflow.WithModel(llm),
zenflow.WithProgress(sink),
zenflow.WithStorage(zenflow.NewFileStorage(".zenflow")),
zenflow.WithCoordinator(zenflow.NewDefaultCoordRunner(llm)),
)
defer orch.Close()
result, err := orch.RunFlow(ctx, wf)Construction is plain functional options. Every With* option mutates one orchestrator-level field; nothing happens at construction beyond wiring. The first goroutine spawn is on the first RunFlow/RunGoal/RunAgent call.
What it owns
| Field group | What it is | How it's set |
|---|---|---|
| Model & tools | The default provider.LanguageModel, the registered tool catalog, and per-call goai options | WithModel, WithTools, WithGoAIOptions |
| Storage & memory | The Storage backend (memory or file) and the optional shared-memory store for cross-step KV | WithStorage, WithSharedMemory, WithModelResolver |
| Sinks & approval | Progress sink, drop callback, approval handler, permission handler, tracer | WithProgress, WithDropCallback, WithApproval, WithPermissions, WithTracer |
| Coordinator | The hosted *AgentRunner that handles cross-agent messaging when one is installed | WithCoordinator(runner) (optional) |
| Workflow defaults | Default model, max-concurrency, max-turns, max-depth, isolation, output transform | WithDefaultModel, WithMaxConcurrency, WithMaxTurns, WithIsolation, WithOutputTransform |
The orchestrator is stateless across runs for the workflow itself: every RunFlow call constructs a fresh Executor, a fresh MessageRouter, and a fresh in-memory MailboxStore. What persists across runs is the configuration you set on the orchestrator and, if you wired one, the coordinator runner instance (so the coord LLM keeps its conversation continuity if you opt to share it).
Three entry points
| Entry point | What you give it | What it does |
|---|---|---|
RunFlow(ctx, wf) | A parsed *Workflow (from LoadWorkflow or built in code) | Runs a fully-declared YAML DAG end to end |
RunGoal(ctx, goal) | A single-sentence goal string | Asks the coordinator to decompose the goal into a Workflow, then runs it |
RunAgent(ctx, cfg) | An AgentConfig for one agent | Runs a single agent's tool loop with no DAG and no coordinator |
ResumeFlow(ctx, runID, wf) is the fourth entry point for picking up a previously-checkpointed run from Storage.
All four return either a *WorkflowResult (or *AgentResult for RunAgent) plus an error. The result carries per-step status, token totals, and content.
Why it's the only type you import
Almost every other zenflow type is reachable through the orchestrator:
orchestrator ownership graph
Workflow marks the one type you pass in on each Run* call; it travels through the orchestrator but isn't part of its state. Public consumers rarely instantiate any of these directly. Tests construct an Executor or a bare AgentRunner for unit-level tests; production code stays inside the orchestrator surface.
Lifecycle
orch := zenflow.New(opts...) // 1. construct (sync, no goroutines)
defer orch.Close() // 4. drain (handle registry, cancel coord goroutines)
result, err := orch.RunFlow(ctx, wf) // 2. run (spawns Executor + step runners)
// ... inspect result ...
result2, err := orch.RunFlow(ctx, wf2) // 3. run again, same orchestratorClose() is idempotent. It drains every active RunAgentAsync handle (best-effort, bounded) and signals long-lived goroutines (the coordinator's wake loop, the factory cache cleanup) to exit. Once closed, new Run* calls return ErrOrchestratorClosed.
For one-shot CLI invocations, defer orch.Close() is enough. For long-lived embedders (HTTP servers, queue workers), call Close() during shutdown, and remember the orchestrator is concurrency-safe: many goroutines can call RunFlow on the same instance.
Orchestrator vs Coordinator vs Executor
These three terms come up in every doc. They are not synonymous:
- Orchestrator is the public Go type you construct (
zenflow.Orchestrator). It's the owner. - Coordinator is an LLM-backed
AgentRunnerthe orchestrator hosts when you passWithCoordinator(...). It's the messaging hub. - Executor is a per-run internal struct that walks the DAG and spawns one
AgentRunnerper step. It's the scheduler.
See the agent orchestration architecture diagram (external SVG) for the full picture, including the message flow between the three.
Where to next
- Coordinator - what the LLM hub does, the three tools it carries, when to install one.
- Execution Modes - when to choose
RunFlowvsRunGoalvsRunAgent. - DAG Scheduling - what the Executor does inside a
RunFlowcall. - API: Core Functions - the full signature reference for every orchestrator method.