> ## Documentation Index
> Fetch the complete documentation index at: https://usenaive.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Provisioning Workflow

> How per-tenant agent profiles are provisioned as a durable, idempotent, async-native workflow.

Provisioning a real-world agent profile is not a synchronous call. Forming a legal
entity, passing KYB, registering an A2P/10DLC campaign, and propagating DNS take
hours to days. Naïve models this as a **durable workflow** so a signup never
blocks and a retried webhook never forms a second entity.

## The state machine

```
provisioning → verifying → active
                    ↘ needs_action (KYB doc, A2P rejection) → verifying
                    ↘ awaiting_payment (card funding / formation checkout) → active
                    ↘ failed → reconciled (orphans cancelled)
                    ↘ revoked
```

Regulated steps run the **real provider calls up to the payment step** and then
stop: the card step calls Stripe Issuing and returns `awaiting_payment` with a
funding `checkout_url` in `step.result` (no charge, no issuance); formation returns
the `$249` checkout; KYC starts a real hosted verification when members are
supplied. The agent never auto-spends — a human completes the checkout.

* **Idempotent on the agent profile key** — a retried signup webhook resumes the same
  workflow; it never forms a second entity or issues a second card. Pass
  `idempotencyKey` to `provision()`.
* **Async-native** — formation and KYB take hours; A2P takes days; DNS needs
  propagation. The workflow holds state across all of it and emits
  `agentProfile.ready` / `agentProfile.needs_action` events.
* **Self-healing** — a reconciliation sweep cancels any capability-resource not
  bound to an active agent profile within N minutes. The answer to "orphaned card" is
  a janitor, not a saga engine.

It is implemented as a **Postgres-backed durable workflow** (idempotency keys + a
step ledger + a sweeper), not a Temporal cluster — so self-hosters get durable
provisioning without standing up Temporal + Cassandra.

## Driving it from your app

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const op = await naive.forUser(tenant.id).provision("sdr", {
  idempotencyKey: `op:${tenant.id}`,
});
// op.status === "provisioning" — don't block; subscribe to events.

export async function onAgentProfileEvent(evt: NaiveEvent) {
  if (evt.type === "agentProfile.ready")
    await naive.runtime("pool").start(evt.data.agentProfileId, { goal: "Run outbound." });
  if (evt.type === "agentProfile.needs_action")
    /* surface the KYB doc / A2P step to the tenant in your dashboard */;
}
```

Poll status any time with `await op.refresh()` or `GET /v1/agent-profiles/:id`.

<Note>
  `provision()` runs this workflow for real: it plans a Postgres step ledger from
  the template, advances each step, and derives the agent profile status. Steps that are
  regulated/async by nature (KYB, LLC formation, A2P phone) or that need an
  unconfigured provider resolve to `needs_action` with a precise requirement (read
  them from `agentProfile.steps`); fully-automatable steps (e.g. an email inbox when a
  verified domain + provider exist) execute immediately. The internal cron
  `POST /v1/cron/reconcile-agent profiles` re-advances unfinished agentProfiles.
</Note>

## Why durable execution earns its keep here

This is the one place durable execution is load-bearing — and it is *not* atomic
rollback. It's the multi-hour reality of regulated onboarding (the source of
today's "stuck verification" friction): entity formation, KYB review, A2P/10DLC
registration, DNS propagation. The workflow is scoped to provisioning, not
general agent orchestration.
