The transactional execution core
Every mutation follows one rule: committed PostgreSQL rows are the input and output of the workflow transition. A replica does not continue from a mutable process object retained from an earlier request. This is the ACID rail that carries every workload identically — native APL, imported BPMN, decision tables, agent steps and the Insight loop’s terminal facts.
Atomic command lifecycle
Section titled “Atomic command lifecycle”sequenceDiagram
actor Client
participant API as Controller
participant Command as Core command
participant DB as PostgreSQL
participant Outbox as Outbox dispatcher
Client->>API: mutation + identity + optional idempotency key
API->>Command: typed command
Command->>DB: begin transaction
Command->>DB: load and lock authoritative rows
DB-->>Command: committed state
Command->>Command: authorize and validate
Command->>Command: advance canonical state
Command->>DB: persist state, work, facts, history and outbox
Command->>DB: commit
Command-->>Client: deterministic result
Outbox->>DB: lease committed events
Outbox-->>Client: retryable lifecycle delivery
- Load. Fetch the definition version and mutable aggregate records needed by the command.
- Lock. Use row locks for single-winner transitions and optimistic versions for stale-write detection.
- Authorize and validate. Check the authenticated permission, task assignment, current status, lease ownership and command inputs.
- Advance. Execute the canonical process model — APL and BPMN compile to the same graph — and create successor tokens or durable work.
- Persist. Store variables, tasks, subscriptions, jobs, history, terminal Insight facts, idempotency result and outbox events in the same transaction.
- Commit. Only a successful commit makes the transition visible.
Deterministic law, probabilistic advice
Section titled “Deterministic law, probabilistic advice”The execution core separates what runs inside the transaction from what must not:
| Concern | Where it runs |
|---|---|
Decision tables (FIRST/UNIQUE/COLLECT, otherwise fallback) |
Inside the transaction — reproducible, audited as DECISION_TABLE_APPLIED |
| Scripts and embedded delegates | Inside the transaction, for deterministic local logic |
Agent nodes and engine-tasks |
Durable external tasks; a worker reports back through a command |
| Insight terminal facts | One idempotent row per terminal node visit, written in the command transaction |
| Model and tool calls | Never inside a workflow transaction |
A probabilistic output is never trusted as an engine decision: agent results
merge through the normal external-task completion command, and Insight
proposals are discarded unless AplParser validates them and a human
approves them.
Tokens, jobs and incidents
Section titled “Tokens, jobs and incidents”- Tokens. Each path of an instance is a row in
process_tokens(activity, scope, parent, loop counters, state). Joins count arrived token ids, so a parallel branch that ends early never blocks its join, and loop counters survive restarts. Waiting work names the token it belongs to. - Jobs. Timers,
on_timeoutdeadlines andsla_hourschecks are durable rows acquired withSKIP LOCKED, so exactly one replica fires each one. - Retiring work. When a step leaves early — a timeout, a cancellation, a decision — its open task, external job and timers are retired in the same transaction, before the instance lock, so a late worker report is rejected instead of advancing the process twice.
- Agent work that waits. An agent waiting for a person’s approval of a tool call, or for a child process it delegated to, holds no lease and no worker; the decision or the child’s end makes it acquirable again on the same attempt. Its token stays on the agent step meanwhile.
- Live events. Every history record also writes an outbox row with a sequence number, in the same transaction. Delivery to webhooks leases those rows; the project event stream that Studio follows tails the committed rows in sequence order, holding a gap for a few seconds while an earlier transaction may still commit. Events carry identifiers only.
- Incidents. A path that cannot continue (a failed step without
on_error, an exhausted loop withouton_exhausted, a message wait without a correlation value) stops with an incident row. The rest of the instance keeps running; an operator retry restarts that path in a new command. An agent write interrupted without an idempotency key stops the same way, until a person says whether it happened.
Failure semantics
Section titled “Failure semantics”- Failure before commit rolls back workflow state, new work, facts, history and outbox records together.
- Failure after commit leaves durable progress. An idempotency record lets a duplicate API mutation replay its logical response.
- Scripts and embedded delegates run inside the transaction, but an irreversible external side effect cannot be rolled back by PostgreSQL.
- External tasks are the preferred boundary for remote effects; workers must make at-least-once effects idempotent.
Cache policy
Section titled “Cache policy”The sole execution cache stores immutable parsed definitions keyed by deployment ID. A new process start queries PostgreSQL for the latest version; an existing instance reloads its pinned version. Evicting the cache changes latency, not semantics.
Mutable instances, tokens, joins, tasks, subscriptions, timers, jobs, variables and facts never live in runtime-wide maps. Command-local maps are allowed only while materializing and advancing one locked aggregate.
Executable boundary
Section titled “Executable boundary”Controller-reachable mutations enter services marked with
@AtomicRuntimeCommand. AtomicRuntimeCommandContractTest inventories that
boundary, while PostgreSQL rollback and two-context tests prove the behavior
under failures and concurrent commands.