ADR-0037: Processes are triggered userland programs — the continuous kinetic member
ADR-0037: Processes are triggered userland programs — the continuous kinetic member
Status
Accepted. Completes the kinetic layer alongside
ADR-0035 (Action
is the discrete kinetic member; Process is the continuous one, and object-set /
QuerySpec-wide bulk was deferred to it) and
ADR-0036 (Functions,
which a Process invokes). Formalizes as a primitive what
ADR-0027 built ad hoc (correlation is a
convergent recompute). Grounded in
ADR-0002 (the six verbs — Schedule and
Subscribe are the two triggers), and relies on
ADR-0013 (at-least-once,
idempotent execution) and
ADR-0015 (materialization is
convergent).
Date
2026-07-05
Context
The kinetic layer of the ontology has two members: Action (discrete — one bounded, replayable Act leaving one decision) and Process (continuous — long-running, scheduled, or event-triggered). ADR-0032 named Process and pushed three things onto it — long-running work, ETL, and QuerySpec-wide / large object-set bulk — but did not decide what a Process is.
Meanwhile the pieces already exist, unnamed:
- The six verbs are two triggers + four effects (the-seam §V):
Subscribe(react to an Event) andSchedule(a timer). Those two trigger verbs exist precisely so userland can write Processes — “a workflow is the first userland program; it decomposes into the syscall surface with nothing left over.” - The worker +
JobHandlerregistry + theScheduleverb are a working long-running-job substrate, but nothing frames them as “the Process runtime.” - ADR-0027 already built the canonical Process — fault correlation as a debounced, domain-scoped, convergent recompute — but as a bespoke engine, not an instance of a general primitive.
Two shapes could be mistaken for two primitives: event-driven automations and scheduled data pipelines (Palantir keeps Automate and Pipelines separate). We need to decide whether Vinxi follows suit or unifies them.
Decision
A Process is a triggered userland program: a declared trigger plus a body of effects and Functions. It is one primitive — the continuous kinetic member — and it is userland (it composes privileged Actions and Functions), never kernel Rust.
- One unified primitive. ETL and automation are the same thing with
different trigger+body shapes: an ETL is a Process with a
Scheduletrigger and anAssert-heavy body; correlation is a Process with aSubscribe(alarm)trigger and a Function-recompute→Actbody. The six-verb model already unifies them (“windowed/continuous queries are Subscribe-to-a-view; emit a domain event is an Act”), so no second primitive is introduced. - Triggers are the two trigger verbs, plus an Action. A Process is ignited by
Schedule(a timer),Subscribe(an Event or a view diff), or an Action (process:<ref>— an Act fires the Process and its decision Event records only the trigger). An external inbound crossing is anAssert/Callthat fires aSubscribe. - The body composes Actions and Functions — same privileged/normal discipline (ADR-0032). A Process reaches reality only through privileged Actions and the Functions it invokes; it holds no ambient authority. It is authored in userland (declared trigger + TS/Python body), not compiled into the kernel.
- Graph-recompute Processes must be convergent. A Process that recomputes world state from inputs must be idempotent / convergent — the same inputs settle to the same state regardless of when or how often it runs (ADR-0027’s debounced-subtree recompute; ADR-0015’s bitemporal-LWW materialization). This is a required property, enforced by construction, because the substrate is at-least-once (ADR-0013).
- A Process emits its own Events over time. Unlike an Action (one decision Event), a Process leaves a stream — started / progress / completed — on the log/spine. The Act that triggered it recorded only the trigger; the outcomes are the Process’s own records.
- The substrate is the existing worker. The
Scheduleverb + the worker + theJobHandlerregistry are the Process runtime, formalized — the long-lived “deployed” counterpart to a Function’s serverless per-invocation (ADR-0033). - Declared in the ontology, referencing kinds/actions/functions. A Process is declared alongside actions in the schema document; the deferred bulk work lives here — QuerySpec-defined and large object-set operations (the set-authorization ADR-0032 kept out of the bounded Action path).
Alternatives Considered
- Two primitives — Automation and Pipeline (mirror Palantir): rejected — the six-verb model already covers both with one trigger+body shape; the difference is which trigger and which effects dominate, not a different kind. A second primitive is surface area without a new capability.
- Processes as privileged kernel code (as ADR-0027’s correlation is today): rejected — that is the same layering leak ADR-0031/0032 removed for Actions; Process behaviour is domain software over privileged Actions, so it belongs in userland. Correlation is migrated onto this primitive, not blessed as kernel.
- Best-effort (non-convergent) recompute: rejected — with at-least-once execution, a non-convergent Process produces order-dependent world state; convergence is what makes retries and debounced re-runs safe (ADR-0027).
Consequences
- Build order: after the recipe interpreter (ADR-0032) and the Function runtime
(ADR-0033), formalize the Process substrate —
Schedule/Subscribetrigger wiring,process:<ref>resolution from an Action, convergence guarantees, and Process-Event emission. Then migrate correlation (ADR-0027) from its bespoke Rust engine onto the primitive as the worked instance, and open QuerySpec / object-set bulk. Subscribegets an execution rung. It isNOT_YET_SERVINGtoday (ADR-0030); Process is where it lands — a governed, seam-filtered event/view stream that ignites Process bodies (not the raw CDC ADR-0028 banned).- The three-layer ontology is complete in canon: semantic (kinds/traits/links) · kinetic (Action discrete + Process continuous) · logic (Function) — ADRs 0032/0034/0033 respectively, all over the same privileged-Action instruction set.
- Open (reserved): the debounce/scheduling policy surface (how a Process declares its trigger cadence and coalescing) and back-pressure under high Event volume — implementation choices to fix when the substrate is built; this ADR fixes the contract (triggered, convergent, userland, its-own-Events).