# Introduction (/docs)



<DocsSpotlight />

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 <TechnicalName public="work item" api="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 <Term name="Worker" /> identity ships as the worker registry, and the
signed <Term name="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 [#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.

<Journey>
  <JourneyStep title="Run Ductor locally" href="/docs/getting-started" icon="run">
    Start the stack, publish a workflow, and prove that it resumes after a restart.
  </JourneyStep>

  <JourneyStep title="Build a durable workflow" href="/docs/guides/define-workflow" icon="build">
    Author, validate, publish, and trigger a DAG with the full definition lifecycle.
  </JourneyStep>

  <JourneyStep title="Route an event" href="/docs/guides/routing-rules-strategies" icon="route">
    Create pools, write CEL rules, and select a routing strategy.
  </JourneyStep>

  <JourneyStep title="Connect a provider" href="/docs/guides/add-connector" icon="connect">
    Register a provider config, establish a connection, and dispatch an action.
  </JourneyStep>

  <JourneyStep title="Prepare for production" href="/docs/deployment/production" icon="deploy">
    Work through deployment, security, observability, and recovery requirements.
  </JourneyStep>
</Journey>

## What it does [#what-it-does]

<Cards>
  <Card title="Durable DAG execution" href="/docs/concepts/coordinator-workers">
    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.
  </Card>

  <Card title="Decision routing" href="/docs/concepts/routing-pipeline">
    A staged pipeline turns an incoming event into a concrete assignment using
    pluggable selection strategies and CEL-based rules.
  </Card>

  <Card title="Hundreds of providers" href="/docs/connectors/catalog">
    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.
  </Card>

  <Card title="AI & agent surface" href="/docs/ai">
    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.
  </Card>

  <Card title="SDKs & the bridge" href="/docs/sdks">
    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.
  </Card>

  <Card title="Multi-tenant by default" href="/docs/concepts/tenancy">
    Every run, rule, and connector is scoped to a tenant, with plan- and
    quota-based entitlements enforced on the hot path.
  </Card>
</Cards>

## Who it's for [#who-its-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 [#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.

```mermaid
flowchart LR
  E["Event / trigger"] --> C["Coordinator<br/>(serial writer)"]
  C -->|assigns work| W["Step Workers"]
  W -->|record attempts| C
  C -->|commit atomically| DB[("Run state<br/>Postgres")]
  DB -.->|load state| C
```

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](/docs/concepts/idempotency).

## The other core idea: the routing pipeline [#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](/docs/concepts/routing-pipeline#strategies) for the
selection algorithms and shadow evaluation.

```mermaid
flowchart LR
  R["Routable event"] --> V["Validate"] --> EN["Enrich"] --> F["Filter"] --> S["Select"] --> AS["Assign"] --> D["Decision"]
```

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](/docs/concepts/routing-pipeline) for the
stages, strategies, and pools in depth.

## How work settles [#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 <Term name="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](/docs/billing) for what meters and charges now.

## How work is proven [#how-work-is-proven]

Every consequential step already emits evidence: a per-decision
[explanation](/docs/concepts/routing-pipeline), signed agent-tool manifests, and
per-request cost accounting. The opt-in <Term name="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 [#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):

```json
{
  "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](/docs/getting-started), or learn the full vocabulary — scatter/gather,
sub-workflows, waits, and more — in [The DAG Workflow Model](/docs/concepts/dag-workflow-model).

## Featured sections [#featured-sections]

<DocsMapSummary />

* **[Getting Started](/docs/getting-started)** — install, configure, and verify a
  local instance.
* **[Core Concepts](/docs/concepts)** — the execution model,
  the DAG workflow vocabulary, the routing pipeline, the tiered fair queue,
  optimistic locking, idempotency, events, connectors, tenancy, and
  entitlements.
* **[Architecture](/docs/architecture)** — the layered
  design, the coordinator-worker model in depth, data flow, the storage model,
  and extension points.
* **[AI & agents](/docs/ai)** — the inbound MCP server, the in-product chat
  agent, and the AI inference proxy.
* **[SDKs](/docs/sdks)** — compare language support and define durable workflows
  in code over the workflow bridge.
* **[Billing & Usage](/docs/billing)** — 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.

<Callout title="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.
</Callout>
