Skip to content

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) and Schedule (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 + JobHandler registry + the Schedule verb 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.

  1. One unified primitive. ETL and automation are the same thing with different trigger+body shapes: an ETL is a Process with a Schedule trigger and an Assert-heavy body; correlation is a Process with a Subscribe(alarm) trigger and a Function-recompute→Act body. 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.
  2. 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 an Assert/Call that fires a Subscribe.
  3. 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.
  4. 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).
  5. 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.
  6. The substrate is the existing worker. The Schedule verb + the worker + the JobHandler registry are the Process runtime, formalized — the long-lived “deployed” counterpart to a Function’s serverless per-invocation (ADR-0033).
  7. 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/Subscribe trigger 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.
  • Subscribe gets an execution rung. It is NOT_YET_SERVING today (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).