Introduction
Ductor is the clearing layer for autonomous work — it prices, routes, executes, and settles each task with proof, as a single Go binary you run inside your own platform.
Durable execution, under your control
Route the work. Run the graph. Keep every outcome visible.
One self-hosted runtime for decision routing, durable workflows, connectors, and governed agent work.
- Deployment
- Single Go binary
- Control plane
- REST + Connect-RPC
- Source of truth
- Postgres
Ductor is the clearing layer for autonomous work: it takes each unit of
work — a lead, a ticket, an AI task, an inbound event — and gives it a price, a
route to the right worker (human or agent), durable execution, and a settled,
provable outcome. One generic work item (Routable)
runs the same path — the cargo changes, the machine doesn't.
Underneath that loop is a durable engine, not a fire-and-forget queue. Ductor runs your directed acyclic graphs (DAGs) to completion — retrying, resuming, and fanning out across steps — while a routing pipeline decides, per event, who or what each unit of work goes to. Durable execution is the guarantee that makes the rest safe, not the headline.
It ships as a single Go binary you run inside your own platform, backed by Postgres and Redis (or Dragonfly), and driven over a REST and Connect-RPC API rather than a UI. There is no DSL to learn upfront. An optional web dashboard ships alongside the engine, but it's never required — the engine is headless and API-first, and the dashboard only ever reads and drives what the API already exposes. Author workflows through that API directly, or define them in your own codebase with the SDKs and have the engine call back into them — either way, the durable engine stays a service you operate, not a library you compile in.
The full lifecycle — priced → routed → executed → settled → proven — is how Ductor clears work. Pricing and market clearing run today for lead distribution. The unified Worker identity ships as the worker registry, and the signed Receipt ships as an opt-in surface you enable with a signing key. Anything still unbuilt is labeled as such throughout these docs.
Choose your path
Start with the outcome you need. Each route begins with a working task, then links into the concepts and reference material behind it.
- Run Ductor locally
Start the stack, publish a workflow, and prove that it resumes after a restart.
- Build a durable workflow
Author, validate, publish, and trigger a DAG with the full definition lifecycle.
- Route an event
Create pools, write CEL rules, and select a routing strategy.
- Connect a provider
Register a provider config, establish a connection, and dispatch an action.
- Prepare for production
Work through deployment, security, observability, and recovery requirements.
What it does
Durable DAG execution
Workflows survive process restarts. State lives in Postgres, not memory, so a step that was mid-flight when a pod died resumes exactly where it left off.
Decision routing
A staged pipeline turns an incoming event into a concrete assignment using pluggable selection strategies and CEL-based rules.
Hundreds of providers
Dispatch steps to third-party providers through a typed action registry, with credentials encrypted at rest. The catalog ships hundreds of providers across every major category, plus hand-authored Go connectors.
AI & agent surface
Workflows can run agent work as durable steps — ai_action for a single
model call, ai_agent for a tool-calling loop. Alongside them, an inbound
MCP server exposes Ductor's read-and-trigger tools to agent harnesses, an
in-product chat agent answers over your data, and an AI inference proxy
fronts model calls — all behind the same auth chain.
SDKs & the bridge
Define durable workflows in code with the Go and Python SDKs and run them against the engine over the workflow bridge, instead of authoring DAGs by hand through the API.
Multi-tenant by default
Every run, rule, and connector is scoped to a tenant, with plan- and quota-based entitlements enforced on the hot path.
Who it's for
Ductor is a backend building block for teams building platforms, not a turnkey app. Reach for it when you have stateful, long-running, or fan-out work that must not be dropped:
- Orchestrating multi-step provider calls (enrichment, notifications, payouts) where each step can fail and retry independently.
- Routing an inbound event to the right recipient, queue, or downstream system based on rules that change without a redeploy.
- Scatter/gather and sub-workflow patterns that need exactly-once progress semantics across many workers.
If all you need is a fire-and-forget background job with no durable state, a plain queue is simpler — Ductor earns its keep once correctness under failure matters.
The core idea: coordinator plus workers
Most workflow bugs come from two things writing to the same run state at once. Ductor removes that class of bug by design.
- A serial Coordinator is the only writer of run state. It loads the mutable state for a run, applies any pending results, computes the next tick, and commits atomically with optimistic locking.
- Parallel Step Workers do the actual work — calling connectors, evaluating rules, running your logic — and record their results as attempts. They never mutate run state directly.
The result is a system where progress is always observable and never silently lost: work either advances the run or fails visibly as a recorded attempt. This one decision — a single serial writer behind a durable commit — is what produces Ductor's exactly-once progress guarantee.
The other core idea: the routing pipeline
The second half of Ductor decides, per event, where work should go. A routable event runs through a fixed, ordered pipeline that turns it into a concrete decision — the recipient, queue, or downstream target that handles it.
- Rules pick a pool — a named destination that holds recipients — using priority-ordered CEL expressions that you change without a redeploy.
- A pluggable strategy then selects a specific recipient within that pool. Swapping the strategy swaps the selection algorithm, not the pipeline.
Before you commit to a strategy, you can run a candidate in shadow mode against live traffic — evaluated on every real event, never committed as a side effect — so you can compare its decisions against the current one with zero blast radius. See Strategies for the selection algorithms and shadow evaluation.
The pipeline runs the same five stages every time — Validate, Enrich, Filter, Select, Assign — so a routing outcome is reproducible and auditable rather than ad hoc. See The Routing Pipeline for the stages, strategies, and pools in depth.
How work settles
Once a route is chosen and the work runs, the outcome has to settle: who gets paid, what it cost, and what happens if the work is bad. Ductor tracks per-decision cost today and runs a returns/claw-back window — a delivered lead can be sent back within its warranty window for a credit and an optional reroute.
A unified Settlement plane — balanced-entry accounts in integer micros, whose every posting is forced to net to zero by a deferred database constraint checked at commit — ships as an opt-in journal. Until you enable it, the ledger and Stripe wallet remain the charging path for the lead-distribution vertical. See Billing & Usage for what meters and charges now.
How work is proven
Every consequential step already emits evidence: a per-decision explanation, signed agent-tool manifests, and per-request cost accounting. The opt-in Receipt surface joins them into one signed, verifiable object — routing decision, tool-manifest hash, cost, consent trail, and settlement reference — so any cleared unit of work can be audited end to end when you enable it with a signing key.
What a definition looks like
A workflow is just plain JSON you POST over REST — there is no separate DSL to
learn. Each definition lists its steps and the edges between them; here a
dataflow step (a deterministic in-coordinator transform) feeds a wait step
(a durable delay):
{
"family_slug": "hello-world",
"title": "Hello World",
"steps": [
{"ref": "greet", "type": "STEP_TYPE_DATAFLOW", "title": "Build greeting",
"args": {"operation": "project", "project": {"message": "\"hello\""}}},
{"ref": "pause", "type": "STEP_TYPE_WAIT", "title": "Short delay",
"args": {"duration": "2s"}}
],
"edges": [{"source_ref": "greet", "target_ref": "pause", "type": "EDGE_TYPE_SUCCESS"}]
}You publish a definition to make it runnable, and each publish is captured as an immutable version — a run always executes against the exact definition it was triggered with. Walk this exact example end to end in Getting Started, or learn the full vocabulary — scatter/gather, sub-workflows, waits, and more — in The DAG Workflow Model.
Featured sections
- Learn
- Concepts and architecture
- Build
- Guides, SDKs, and API reference
- Operate
- Security, deployment, and operations
- Getting Started — install, configure, and verify a local instance.
- Core Concepts — the execution model, the DAG workflow vocabulary, the routing pipeline, the tiered fair queue, optimistic locking, idempotency, events, connectors, tenancy, and entitlements.
- Architecture — the layered design, the coordinator-worker model in depth, data flow, the storage model, and extension points.
- AI & agents — the inbound MCP server, the in-product chat agent, and the AI inference proxy.
- SDKs — compare language support and define durable workflows in code over the workflow bridge.
- Billing & Usage — how plans map onto entitlements, the durable usage-metering and budget-policy plane, per-recipient pricing/returns/compliance, and the (experimental) Stripe wallet + invoicing integration.
One principle, everywhere
Every production code path either works correctly or fails visibly — no silent no-ops, no stale stubs, no unobservable data loss. If a page describes a limit, a retry, or a failure, expect it to surface as a typed error or a recorded attempt, never as a dropped request.