Velt Approval Engine Best Practices
Implementation guide for the Velt Approval Engine — a declarative workflow runtime for multi-step agent + human approval processes. Covers the workflow model (definitions = nodes + edges + groups), the 14 REST endpoints under /v2/workflow/* (definitions, executions, steps), webhook delivery with HMAC signature verification, and idempotency/recovery patterns.
When to Apply
Reference these guidelines when:
- Authoring approval workflow definitions — nodes (
agent, human), edges (with optional when gating expressions), groups (parallel quorum)
- Dispatching executions — always with an
idempotencyKey; configuring webhookUrl + webhookSecret together
- Recording decisions — human reviewer approvals/rejections (
/steps/recordReviewerDecision) or external blocking-agent resolutions (/steps/recordAgentResolution)
- Building webhook receivers — HMAC-SHA256 signature verification on raw bytes, idempotency on
(executionId, seq), retry schedule, missed-event recovery
- Using quorum groups with
waitAll / cancelOnQuorum / joinOnQuorum policies
- Configuring SLAs (
slaMs on a node) and the required breach-routed outgoing edge
- Admin-scoped operations (
/steps/cancel, /steps/resolve)
- Debugging
INVALID_ARGUMENT linter rejections (cycle, dangling-edge, unreachable-node, missing-breach-edge, group-joinonquorum-members-must-share-successors)
For general Velt REST API patterns (Comments, Notifications, Activity, etc.), see velt-rest-apis-best-practices — only the Approval Engine domain is covered here.
Rule Categories by Priority
| Priority |
Category |
Impact |
Prefix |
| 1 |
Concepts |
HIGH |
concepts- |
| 2 |
REST Endpoints |
HIGH |
rest- |
| 3 |
Webhooks |
HIGH |
webhooks- |
Quick Reference
Concepts (HIGH)
concepts-workflow-model — definitions = nodes + edges + groups + loops; node shapes (agent / human); reviewerEmails (0–50, surfaces in step output); commentBody (stored on output — engine does NOT auto-create annotations); onReject shorthand (Form A: routeToNodeId; Form B: loopBack); strict-mode: every human node needs onReject or loop membership; top-level loops[] (loopId, entryNodeId, bodyNodeIds 1–50, maxIterations 1–20, previousAttempts threading); default loop predicate decision == 'reject' && rejectorMandatory == true does NOT fire when step resolved via /steps/resolve reject actions (use recordReviewerDecision instead); edge when expressions; group quorum policies (waitAll / cancelOnQuorum / joinOnQuorum)
REST Endpoints (HIGH)
rest-foundations — auth headers, request/response envelope, canonical error codes (including DEADLINE_EXCEEDED), schema-level validation message reference
rest-definitions — 5 endpoints: create / update (with ifVersion optimistic lock) / get / list / delete; full 25-rule linter reference (16 graph+group rules + 9 new loop rules); scope per-level required fields (organizationId for organization scope; both organizationId + documentId for document scope); server-namespaced ID caveat; triggers shape ({ triggerId, eventName?, filters? }) — descriptive metadata only in v1, engine does NOT auto-dispatch
rest-executions — 5 endpoints: dispatch (idempotencyKey + webhook config) / get / list / cancel / getEvents (sinceSeq recovery + externally-visible event enumeration cross-referenced to webhooks-delivery, including loop events); v1 list limitation: no organizationId/documentId filter support
rest-steps — 4 endpoints: recordReviewerDecision (unknown reviewer silently accepted as unknown responder — no INVALID_ARGUMENT) / recordAgentResolution / cancel (admin, actorId REQUIRED 1–256 chars, error matrix: INVALID_ARGUMENT/FAILED_PRECONDITION/NOT_FOUND) / resolve (action-discriminated: force-approve, force-reject, force-complete, force-fail, reviewer-approve, reviewer-reject; actorId REQUIRED 1–256 chars; reason ≤ 2000 chars; reject actions do NOT populate rejectorMandatory — loop predicates won't fire; response shape { resolved, executionId, stepId, action })
rest-object-views — TypeScript shapes for ExecutionView, StepView, DefinitionView, ApprovalEventView, the human step output aggregator rollup, and the joinOnQuorum successor input
Webhooks (HIGH)
webhooks-delivery — HMAC-SHA256 signature verification on raw bytes, delivery headers, retry schedule, event-type catalog (now includes loop.iteration-started and loop.exhausted), at-least-once idempotency
webhooks-inbound-handler — inbound HTTP endpoint that external systems POST raw JSON to (no {data: ...} envelope), bearer-token auth, signed callback tokens, per-source rate limiting, body-size limits, SSRF URL guard; distinct from outbound delivery and from the deferred node.type === "webhook"
How to Use
Read individual rule files for detailed explanations and code examples:
rules/shared/concepts/concepts-workflow-model.md
rules/shared/rest/rest-foundations.md
rules/shared/rest/rest-definitions.md
rules/shared/rest/rest-executions.md
rules/shared/rest/rest-steps.md
rules/shared/rest/rest-object-views.md
rules/shared/webhooks/webhooks-delivery.md
rules/shared/webhooks/webhooks-inbound-handler.md
Each rule file contains:
- Brief explanation of why it matters
- Concrete request/response examples
- Common pitfalls and what NOT to do
- Verification checklist
- Source pointers to official docs
Compiled Documents
AGENTS.md — Compressed index of all rules with file paths (start here)
AGENTS.full.md — Full verbose guide with all rules expanded inline
1---2name: velt-approval-engine-best-practices3description: Best practices for the Velt Approval Engine — the declarative workflow runtime for multi-step agent + human approval processes. Use whenever the user is building approval workflows, multi-step review processes, agent-then-human approval chains, parallel reviewer quorums, SLA-aware approval steps, rejection-loop retry regions, or any workflow that combines AI agents with human gatekeeping. Triggers on any task involving the Velt Approval Engine, /v2/workflow/ REST endpoints, workflow definitions (nodes/edges/groups/loops), executions dispatch with idempotencyKey, recording reviewer decisions or agent resolutions, workflow webhooks (execution.dispatched/completed/failed/cancelled, step.awaiting-approval/completed/failed/breached/cancelled, group.quorum-met, loop.iteration-started, loop.exhausted), HMAC-SHA256 webhook signature verification, SLA-routed breach edges, quorum policies (waitAll/cancelOnQuorum/joinOnQuorum), onReject shorthand, loop regions, or recovering missed webhook events via /executions/getEven4license: MIT5---67# Velt Approval Engine Best Practices89Implementation guide for the Velt Approval Engine — a declarative workflow runtime for multi-step agent + human approval processes. Covers the workflow model (definitions = nodes + edges + groups), the 14 REST endpoints under `/v2/workflow/*` (definitions, executions, steps), webhook delivery with HMAC signature verification, and idempotency/recovery patterns.1011## When to Apply1213Reference these guidelines when:14- Authoring approval workflow **definitions** — nodes (`agent`, `human`), edges (with optional `when` gating expressions), groups (parallel quorum)15- **Dispatching executions** — always with an `idempotencyKey`; configuring `webhookUrl` + `webhookSecret` together16- **Recording decisions** — human reviewer approvals/rejections (`/steps/recordReviewerDecision`) or external blocking-agent resolutions (`/steps/recordAgentResolution`)17- **Building webhook receivers** — HMAC-SHA256 signature verification on raw bytes, idempotency on `(executionId, seq)`, retry schedule, missed-event recovery18- Using **quorum groups** with `waitAll` / `cancelOnQuorum` / `joinOnQuorum` policies19- Configuring **SLAs** (`slaMs` on a node) and the required breach-routed outgoing edge20- Admin-scoped operations (`/steps/cancel`, `/steps/resolve`)21- Debugging `INVALID_ARGUMENT` linter rejections (cycle, dangling-edge, unreachable-node, missing-breach-edge, group-joinonquorum-members-must-share-successors)2223For general Velt REST API patterns (Comments, Notifications, Activity, etc.), see `velt-rest-apis-best-practices` — only the Approval Engine domain is covered here.2425## Rule Categories by Priority2627| Priority | Category | Impact | Prefix |28|----------|----------|--------|--------|29| 1 | Concepts | HIGH | `concepts-` |30| 2 | REST Endpoints | HIGH | `rest-` |31| 3 | Webhooks | HIGH | `webhooks-` |3233## Quick Reference3435### Concepts (HIGH)36- `concepts-workflow-model` — definitions = nodes + edges + groups + loops; node shapes (agent / human); `reviewerEmails` (0–50, surfaces in step output); `commentBody` (stored on output — engine does NOT auto-create annotations); `onReject` shorthand (Form A: routeToNodeId; Form B: loopBack); strict-mode: every human node needs onReject or loop membership; top-level `loops[]` (loopId, entryNodeId, bodyNodeIds 1–50, maxIterations 1–20, previousAttempts threading); default loop predicate `decision == 'reject' && rejectorMandatory == true` does NOT fire when step resolved via `/steps/resolve` reject actions (use `recordReviewerDecision` instead); edge `when` expressions; group quorum policies (waitAll / cancelOnQuorum / joinOnQuorum)3738### REST Endpoints (HIGH)39- `rest-foundations` — auth headers, request/response envelope, canonical error codes (including `DEADLINE_EXCEEDED`), schema-level validation message reference40- `rest-definitions` — 5 endpoints: create / update (with `ifVersion` optimistic lock) / get / list / delete; **full 25-rule linter reference** (16 graph+group rules + 9 new loop rules); scope per-level required fields (`organizationId` for organization scope; both `organizationId` + `documentId` for document scope); server-namespaced ID caveat; triggers shape (`{ triggerId, eventName?, filters? }`) — descriptive metadata only in v1, engine does NOT auto-dispatch41- `rest-executions` — 5 endpoints: dispatch (idempotencyKey + webhook config) / get / list / cancel / getEvents (sinceSeq recovery + externally-visible event enumeration cross-referenced to webhooks-delivery, including loop events); v1 list limitation: no `organizationId`/`documentId` filter support42- `rest-steps` — 4 endpoints: recordReviewerDecision (unknown reviewer silently accepted as unknown responder — no INVALID_ARGUMENT) / recordAgentResolution / cancel (admin, `actorId` REQUIRED 1–256 chars, error matrix: INVALID_ARGUMENT/FAILED_PRECONDITION/NOT_FOUND) / resolve (action-discriminated: `force-approve`, `force-reject`, `force-complete`, `force-fail`, `reviewer-approve`, `reviewer-reject`; `actorId` REQUIRED 1–256 chars; `reason` ≤ 2000 chars; reject actions do NOT populate `rejectorMandatory` — loop predicates won't fire; response shape `{ resolved, executionId, stepId, action }`)43- `rest-object-views` — TypeScript shapes for `ExecutionView`, `StepView`, `DefinitionView`, `ApprovalEventView`, the human step `output` aggregator rollup, and the `joinOnQuorum` successor input4445### Webhooks (HIGH)46- `webhooks-delivery` — HMAC-SHA256 signature verification on raw bytes, delivery headers, retry schedule, event-type catalog (now includes `loop.iteration-started` and `loop.exhausted`), at-least-once idempotency47- `webhooks-inbound-handler` — inbound HTTP endpoint that external systems POST raw JSON to (no `{data: ...}` envelope), bearer-token auth, signed callback tokens, per-source rate limiting, body-size limits, SSRF URL guard; distinct from outbound delivery and from the deferred `node.type === "webhook"`4849## How to Use5051Read individual rule files for detailed explanations and code examples:5253```54rules/shared/concepts/concepts-workflow-model.md55rules/shared/rest/rest-foundations.md56rules/shared/rest/rest-definitions.md57rules/shared/rest/rest-executions.md58rules/shared/rest/rest-steps.md59rules/shared/rest/rest-object-views.md60rules/shared/webhooks/webhooks-delivery.md61rules/shared/webhooks/webhooks-inbound-handler.md62```6364Each rule file contains:65- Brief explanation of why it matters66- Concrete request/response examples67- Common pitfalls and what NOT to do68- Verification checklist69- Source pointers to official docs7071## Compiled Documents7273- `AGENTS.md` — Compressed index of all rules with file paths (start here)74- `AGENTS.full.md` — Full verbose guide with all rules expanded inline