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

# Approval Engine (Beta)

> Multi-step approval workflows where AI agents and humans review work together.

## What is the Approval Engine?

Build approval flows as a graph. Define the workflow once, dispatch it against a trigger, and the engine runs everything: state, retries, fan-out, parallel review, SLAs, and webhooks.

You write nodes (`agent`, `human`) and connect them with edges. You record reviewer decisions and agent resolutions through the REST API. The engine does the rest. Every state change is persisted, replayable, and (when you configure a webhook) pushed to your receiver in real time.

This is the part of an approval system you don't want to build yourself: the runtime that keeps state, handles concurrent reviewers, enforces quorum, fires events, and survives outages.

## What you get

* **Agent + human steps in one flow.** Run AI agents inline. Park them for human review when needed. Mix and match without writing glue code.

* **Parallel review with quorum.** Run reviewers in parallel. Gate downstream work behind a quorum policy: wait for everyone, advance once N approve, or require specific people regardless of count.

* **Real-time webhooks.** Every state change is POSTed to your receiver with an HMAC-SHA256 signature and exponential-backoff retry. Missed events are recoverable via `/executions/getEvents`.

* **Idempotent dispatch.** Replay safely. Same `idempotencyKey`, same execution id, no duplicates. Even concurrent dispatches with the same key return the original id.

* **SLA-aware.** Set a deadline per step. The engine transitions to `breached` and routes through dedicated `breached` edges.

* **Versioned definitions.** Updates increment the version. In-flight executions stay pinned to the version they started on.

* **Static linting.** Definitions are validated at write time for cycles, dangling edges, unreachable nodes, and quorum misconfiguration. No surprises at runtime.

## Use cases

<CardGroup cols={2}>
  <Card title="Marketing copy approval" icon="newspaper">
    An AI agent drafts copy. Legal and brand reviewers approve in parallel. A publish agent ships the asset once both approve.
  </Card>

  <Card title="AI agent + human review" icon="robot">
    Park a blocking agent step until N external resolutions arrive, then continue. Useful for agent-assisted moderation or QA pipelines where humans review specific outputs.
  </Card>

  <Card title="Regulated workflows" icon="shield-check">
    Multi-stakeholder sign-off where specific reviewers (compliance, legal) must approve even if a numeric quorum is otherwise met. Every decision is auditable with correlation IDs.
  </Card>

  <Card title="Multi-stakeholder review" icon="users">
    3-of-5 quorum with `cancelOnQuorum` stops bothering remaining reviewers once the threshold is met. System-actor cancellation events go into the audit trail.
  </Card>

  <Card title="Contract sign-off" icon="file-signature">
    Sequential or parallel approval chains with SLAs. If a step breaches its deadline, route to an escalation node or fail-fast the execution.
  </Card>

  <Card title="Document approval" icon="file-check">
    Scope workflows per-document so each instance is bound to a specific `documentId`, with per-execution webhook receivers for downstream integrations.
  </Card>
</CardGroup>

## How it works

1. **Define the workflow.** Register a definition with nodes, edges, and optional parallel groups. The engine lints it at write time.
2. **Dispatch an execution.** POST a `definitionId`, an idempotency key, and optional `triggerContext` (exposed as `execution.input.*` in edge expressions). The engine pins the current version and enqueues the first step.
3. **The engine drives the flow.** Agent nodes run immediately. Human nodes (and blocking agent nodes) park in `waiting` until decisions arrive.
4. **Record decisions.** Call `/steps/recordReviewerDecision` for humans and `/steps/recordAgentResolution` for blocking agents. Matching edges fire. Quorum policies trigger their side effects.
5. **Consume events.** Subscribe via webhook for real-time delivery. Poll `/executions/getEvents` with `sinceSeq` to reconcile after an outage.

## Mental model

### Definition

A **definition** is the blueprint. An **execution** is one live run of that blueprint. A **step** is one node executing inside an execution.

### Nodes

Work units that can run.

| Type    | What it does                                                                                              | Parks in `waiting`?        |
| ------- | --------------------------------------------------------------------------------------------------------- | -------------------------- |
| `agent` | Runs an agent. Non-blocking by default. With `blocking: true`, parks until external resolutions arrive.   | Only when `blocking: true` |
| `human` | Requires reviewer approval. Use `reviewers: [{ userId, mandatory }]`. Legacy `reviewerIds[]` still works. | Yes                        |

### Edges

Connections between nodes. Edges optionally carry a `when` expression like `output.passesBrandCheck == true`. Expressions compile at write time (pure AST, no `eval`) and walk at runtime.

Supported operators: equality, comparison, boolean, regex, `includes`, `startsWith`, `endsWith`, `length`, `isEmpty`.

Path roots: `output.*`, `step.*`, `execution.input.*`.

### Execution

An execution is one live run of a definition. Dispatch pins the definition version, stamps a correlation ID and idempotency key, and enqueues the first step(s).

**Lifecycle:**

```
pending → running → completed | failed | cancelled
```

### Step

A step is one runtime instance of a node.

**Lifecycle:**

```
pending → running → (waiting) → completed | failed | skipped | cancelled | breached
```

`waiting` only applies to human steps and blocking agent steps.

Step IDs are deterministic, so retries land on the same doc:

* **Root steps** (no incoming edges): `step_<nodeId>_<timestamp>_<rand>`
* **Per-edge fan-out**: `${parentStepId}__to__${childNodeId}`
* **`joinOnQuorum` fan-out**: `group_<groupId>__to__<childNodeId>` (one instance regardless of how many group members ran)

## Scope

Pick the level that matches how your product is structured.

| Level          | Bound to                             |
| -------------- | ------------------------------------ |
| `apiKey`       | Workspace-wide                       |
| `organization` | A specific `organizationId`          |
| `document`     | A specific `documentId` under an org |

Defaults to `{ level: "apiKey" }`.

## Idempotency

Dispatch is idempotent on `idempotencyKey`. Replay with the same key returns `{ deduplicated: true, executionId: <original> }`. Safe to retry from clients and queues.

## Events and webhooks

Every state change writes an event doc with a monotonic `seq`. Set `webhookUrl` and `webhookSecret` on dispatch and externally-visible events are POSTed to your receiver with an HMAC-SHA256 signature and exponential-backoff retry. Recover missed events via `/executions/getEvents` with `sinceSeq`.

## Get started

<CardGroup cols={2}>
  <Card title="Setup" icon="gear" href="/ai/approval-engine/setup">
    Author a workflow definition, dispatch an execution, configure your webhook receiver, and record decisions end-to-end.
  </Card>

  <Card title="Customize Behavior" icon="sliders" href="/ai/approval-engine/customize-behavior">
    Node configuration, edge gating expressions, parallel groups and quorum policies, SLAs, linter rules, event reference, and the error vocabulary.
  </Card>
</CardGroup>
