# Connectors (/docs/concepts/connectors)



Connectors are how the *executed* stage of the clearing lifecycle reaches the
outside world — the **action inventory** a <Term name="Route" />, a workflow
step, or an agent draws on to actually do the work. Ductor ships with
implementations for a large catalog of third-party providers (browse the live
directory at [ductor.io/connectors](https://ductor.io/connectors)), and the same
machinery lets you add your own. A step names a provider and an action; Ductor
resolves the credentials, runs the action, and records the result as a durable
attempt. Each action is *governed*, not just callable: it carries typed
semantics (mutation class, idempotency, retry, approval) so the runtime — and any
agent, human or automated — knows what is safe to retry and what needs a human.

<Callout type="info" title="This page is the summary; the deep reference is its own section">
  For the full treatment — every auth type, the credential encryption scheme, the
  management API with real payloads, triggers and polling, and how to author your
  own provider — see the [Connectors section](/docs/connectors). This concept page
  is the one-screen mental model.
</Callout>

## The registries [#the-registries]

Connectors are described by two registries:

* **`ProviderRegistry`** maps a `providerKey` to a provider definition — what it
  is and how to authenticate to it.
* **`ActionRegistry`** maps a `(providerKey, actionKey)` pair to an
  `ActionSpec`, which carries the function that actually executes the action.

At dispatch time a step names a provider and an action; Ductor looks up the
`ActionSpec` and runs it with a resolved authentication context. Provider
implementations live under `infrastructure/connector/providers/`.

## Connections and auth [#connections-and-auth]

A &#x2A;*`ConnectionService`** resolves the `AuthContext` for a connection when a step
is dispatched. Credentials are **AEAD-encrypted at rest** using
XChaCha20-Poly1305, keyed by the base64 32-byte key in
`connector.encryption_key` (`infrastructure/connector/crypto/`). Plaintext
secrets never touch the database — the row holds ciphertext, and decryption
happens in-process at dispatch time using the injected key.

<Callout type="warn" title="Protect the encryption key">
  `DUCTOR_CONNECTOR_ENCRYPTION_KEY` decrypts every stored credential. Treat it
  like a database password: inject it from your secret manager, rotate it
  deliberately, and never commit it. Losing it means losing access to every
  stored connection; leaking it means exposing all of them.
</Callout>

## Dispatch is durable [#dispatch-is-durable]

A connector call is not a fire-and-forget SDK call — it's a workflow step, which
means it inherits the durable attempt model. The
[Coordinator](/docs/concepts/coordinator-workers) dispatches the action to a
Step Worker post-commit; the worker executes it and records the outcome as an
`eec_workflow_step_attempt` row (the `eec_` prefix marks Ductor's durable
*Enterprise Eventing Core* tables). If the call fails, the step's retry policy
governs the retry, and the Coordinator folds the eventual result back into run
state. Because dispatch is at-least-once, a connector action can run more than
once across a crash — so provider actions that mutate downstream state should
pass an idempotency key where the provider supports one (see
[Idempotency & Exactly-Once](/docs/concepts/idempotency)).

```mermaid
sequenceDiagram
    participant C as Coordinator
    participant W as Step Worker
    participant P as Provider
    C->>W: Dispatch action (post-commit)
    W->>P: Execute action
    P-->>W: Result
    W->>W: Record attempt row
    C->>C: Fold result into run state
```

## AI connectors [#ai-connectors]

Two step types — `ai_action` (a single LLM inference call) and `ai_agent` (an
agentic tool-calling loop with execution bounds) — route through the same
connector machinery to an AI inference proxy. From the workflow's perspective an
AI call is just another durable, retryable step with encrypted credentials and
uniform observability.

## Custom connectors [#custom-connectors]

Adding a connector is a matter of implementing the action functions and
registering the provider — no changes to the core engine. Connectors contribute
to the runtime through fx option groups (for example, `connector_interceptors`),
which is the same extension pattern used by routing strategies and workflow
listeners. See [Extension Points](/docs/architecture/extension-points).

## Why it's a connector, not an SDK call [#why-its-a-connector-not-an-sdk-call]

Routing a step through the connector layer instead of calling a vendor SDK
inline gives you four things for free:

* credentials are encrypted and resolved centrally, not scattered through code;
* every call is observable through the same metrics and tracing;
* retries are governed by the durable attempt model from
  [Coordinator & Step Workers](/docs/concepts/coordinator-workers);
* and the call is tenant-scoped and rate-limited like everything else.

## Where to go next [#where-to-go-next]

* [Connectors: full reference](/docs/connectors) — architecture, auth, connections, triggers, catalog, and provider authoring.
* [Connector Architecture](/docs/connectors/architecture) — the registries, dispatch, and the durable attempt and async-job models.
* [Authentication & Credentials](/docs/connectors/authentication) — auth types and XChaCha20-Poly1305 credential encryption.
* [The DAG Workflow Model](/docs/concepts/dag-workflow-model) — where connector steps sit in a graph.
* [Idempotency & Exactly-Once](/docs/concepts/idempotency) — making duplicate dispatch safe.
