One model, and all of it written down
Everything in this block is one object model, and every part of it is persisted. Nothing that matters is held in memory.
- A model is the definition of a process: its task types and their fields, its statuses, the transitions between them, the conditions on those transitions, and the roles. Publishing a model produces a version.
- A task is an instance of a task type. It carries field values, participants, a status, a place in a tree - parent, children, root - and a history of every change made to it.
- A transition is a described move between statuses. When it is gated by an approval, a timer or a piece of work, the attempt at it is itself a record rather than a moment in flight.
- A scenario is a definition that compiles into nodes - the executable steps. Each node's state, its result, and when the next one is due are rows.
Two runtimes execute that model. The process service owns where the work stands: tasks, statuses, transitions, roles, approvals. The orchestrator owns what runs between the moves: scenarios that call things, wait, branch, retry and compensate.
They exchange events and results across defined seams, and because both sides of every seam is a row, a failure there is a delayed row rather than a lost one. That is the same reason a scenario can take three days and a restart on the second day changes nothing: the state was never anywhere else.
The process service
It is written in Java on Spring, and the choice follows from what this component actually does: hold transactional state that many people change at once, and be exactly right about it.
- Moving a task is not one write. It is a status change, a history entry, participants resolved, guards evaluated and an event queued - and either all of that happens or none of it does. A mature transaction manager is the tool for that shape of work, not a convenience.
- Two people press the button at the same time. Contention on the same task is the normal case, not the edge case, so the service locks the row deliberately instead of hoping. This is the ecosystem where that has been ordinary practice for two decades.
- Scheduled work has to run once across replicas. Timer sweeps and delivery loops run with a distributed lock, so raising a second instance adds capacity rather than duplicate work.
Isolation is by schema, and it sits under the data layer. The connection itself is routed per organisation from the identity in the request, so a query in a process is physically scoped instead of filtered, and isolation is not something every query has to remember to do. The routing comes from the caller's own token, which is also why service credentials are not accepted as a substitute for a user's identity on the paths where per-model access is decided.
Guards are structured, not code. A transition's condition is a stored predicate evaluated by the service, not an expression language nobody can review. Field validation is separate from it, applied as unary tests on the value. Both are inspectable, which matters when the question is "why did this request take that branch".
Roles resolve at the moment they are needed. Participants are held at tenant level rather than per process model, and delegation rides the same resolution layer that resolves roles - so a stand-in is a resolution result, not a copy of an assignment. Fields flagged as naming the assignee or a watcher bridge into participants automatically.
Field-level access is opt-in per process. Deny-by-default grants in both directions - a write requires an explicit edit grant per field, and a read omits values the caller has no grant for, including from the bodies returned by writes and transitions. Off by default because most processes do not need it; on, it is strict, not advisory.
Nothing is hard-deleted. A removed task is deactivated, keeps its history, and emits an event; reads filter it out.
The durable orchestrator
The database row is the source of truth. Workflow state is persisted at every step instead of held in memory, so a run survives a restart of the service executing it. When a step is due to fire is a column, and the fast dispatch path is an optimisation on top of it - a reconciler sweeps for anything the fast path missed, which is what makes "fires exactly once" true rather than usually true.
An agent is a node, and review is a step. A call to an agent runs on the same durable machinery as any other node - its result is persisted, its cost is metered, its call is recorded - and where what it produced is consequential, the next node is a human step: the scenario stops, a person looks, and it continues or takes the error path. Nothing about that is special-cased for AI, which is the reason it can be trusted with the same guarantees as everything else on the board.
A node package cannot contain code. An installed package declares an outbound call or an inbound webhook as data, and the same trusted runtime that executes built-in nodes executes that declaration. So extending the palette is a data change, not a deployment - nothing third-party is loaded into the process, and there is no sandbox to trust because there is nothing to sandbox. Anything that genuinely needs code runs on the customer's own infrastructure behind an ordinary HTTP node.
The step vocabulary is deliberately small: call a service, call a connector, ask a human and wait, wait for a period, branch, set, return, loop, repeat until, run in parallel, and try with compensation. A saga's compensation is part of the definition, not an afterthought - it is how a scenario that already changed something outside the platform gets undone when a later step fails.
Fan-out is bounded at compile time. The parallelism of a branch or a loop is checked when the definition is compiled instead of discovered at run time, and the per-iteration item count is capped at execution. A parent fails atomically if a branch fails: no half-successful fan-out.
Ingress is authenticated and quota-limited. Webhook tokens are signed, revocable by rotation, and rate-limited; cron definitions carry their timezone and are validated when saved, not at the first missed midnight.
Background loops re-resolve their tenants each pass, so an organisation created while the system is running is picked up on the next sweep, not at the next restart.
Payloads are encrypted and retained for a bounded time, and every model call inside a scenario goes through the same metered transport as everywhere else - there is no cheaper unmonitored path for an agent step.
The seams between the runtimes
Three of them, each with a defined failure mode.
- Events out. Task created, status changed, participant changed - delivered through an outbox rather than an in-process call, so a consumer being down delays delivery instead of failing the transition.
- Actions in. A transition bound to a scenario holds the task in a processing state and records the attempt. The scenario's completion writes back through a single-use resume token - so a duplicate completion is applied once - and a sweeper finalises records that outlive their timeout, because a task stuck in "processing" forever is the failure people actually complain about.
- A lost start is reconciled. A start event that never linked to a run is found by a sweep and either re-dispatched or cleaned up, instead of sitting as an orphan nobody looks for.
Tasks as data
Process data does not have to be exported to be reported on. A published process model is synced to the data engine as a live relation, so tasks, statuses and their custom fields become an ordinary queryable model - date fields included, so grouping by month works on them like any other date. The sync runs on every application publish that touches a process, not as a manual step.
That is why a task board and a revenue chart can sit on one dashboard and filter each other: they are the same kind of thing to the engine. Access to such a model is granted at model level.
Related
- Forms and records - what the reader meets on top of this machinery.
- Automation - the orchestration side, and the step vocabulary in use.
- Data engine - the plane a published process model is synced into.
- Connectors - the typed operations a process step calls.
- AI harness - what an agent step in a process actually runs through.