How it works

The shape of the system in one page. Read this first — everything else assumes it.

Deeper: the process engine for how a run advances, storage for what is persisted, expressions for how templates make decisions.


The two objects

A template is a recipe. A process is one cooking of it.

You write a template once. Every time someone starts it you get a new process — a live instance carrying its own answers.

Template  "allocation-risk-assessment-app"    written once
   │
   ├── Process  a3f9…   Acme deal      running
   ├── Process  7b21…   Beta deal      completed
   └── Process  c40e…   Gamma deal     running

Steps are a linked list

A template holds steps[], but array order means nothing. Each step names the next one:

firstStepKey ──▶ input_description
                        │  nextStepKey
                        ▼
                 review_description
                        │  nextStepKey
                        ▼
                 review_condition          ◀── a condition step
                    │        │
      thenStepKey ──┘        └── elseStepKey
            │                          │
            ▼                          ▼
   input_risk_model            input_description   ← loops back
            │
            ▼
   agent_recommendation
            │
            ▼
      nextStepKey: null  ──▶  run completes

A condition step uses thenStepKey / elseStepKey instead of nextStepKey. Pointing elseStepKey at an earlier step is how you build a loop. nextStepKey: null ends the run.


Context — where the answers live

Every value a run collects lands in process.context, keyed by step:

context.input_description = { description: "we need a new halo because…" }
context.review            = { approved: true }
context.run               = { dealName: "Acme" }     // reserved, from runInputs

A later step reads an earlier answer as ${input_description.description}.

Every write appends to stepContextAudit[] — { at, userId, stepKey, updates } — append-only, never overwritten. Automation writes use the actor id "system". That trail is what audit and export reads.


Two kinds of step

This is the distinction everything else follows from.

inputThe other 11
Waits fora personnothing
Advanced byan API requestthe worker, every second
Fails howrarelythree different ways

Without a worker running, only input steps advance. npm run dev starts the web app alone; npm run dev:all starts both. A run that reaches a condition or a slack_notify step with no worker sits there indefinitely.

See the process engine for how each stage works.


What sits underneath

Storage — MongoDB or JSON files, decided once at startup by whether MONGO_URL is set.

Permissions — Auth0 with Organizations, checked at process/start access, step contracts and field permissions. Source-only metadata is not an authorization guarantee.

Expressions — one evaluator, six slots. How a template branches, gates completion, and shows or hides fields without code changes.

UI — the read-only template viewer and process runner.

Skins — the same app deployed several times with different branding and navigation. Never an access boundary.

The whole thing

App source / Domain + Integration builders
  → author-time expansion and compilation
  → registration: immutable execution + versioned presentation
  → start: pin execution, initialize memory, enter named activity
  → human interaction OR generic effect suspension
  → authorized command / journaled effect outcome
  → continue, finish, fail, or hold for reconciliation

The executor reduces closed syntax, not source strings or provider handler names. Current presentation may overlay a public read without changing pinned execution. The machine distinguishes failure; the public compatibility status remains completed plus error. See engine and language.