#How eLucid8 fits together
Status: 2026-10-04. A guide to the parts of eLucid8, how they relate, and how to use each one today, with a short hands-on tutorial. For the language itself, see the tutorial; for the browser playground, the Playground Tutorial.
#The big picture
You write one program. Everything else is a way of running it, and every way must give exactly the same results.
flowchart LR
P["program (.el8)"] --> C["compiler (TypeScript)"]
C --> R["reference evaluator (TypeScript)<br/>the definition; traces; check mode"]
C --> PL["plan (JSON)<br/>the contract between engines"]
PL --> JS["fast paths in TypeScript<br/>group levels, fused kernels"]
PL --> RS["Rust runtime<br/>kernels + eduction + clocks + services"]
R -. "checks" .-> JS
R -. "checks" .-> RS
| part | what it is for | where it lives |
|---|---|---|
| Language | values over named dimensions; demand-driven; exact outcomes (Value, Undefined, Error) | semantics, tutorial |
| Compiler | parses, checks and expands a program; works out what can be computed fast; writes the plan | packages/evaluator/src/syntax/ |
| Reference evaluator | the definition of what a program means: demand-driven evaluation (eduction), traces, check mode, caches | packages/evaluator/src/evaluate.ts |
| Fast paths (TypeScript) | group levels and fused kernels for values that recur together, for example training steps | src/syntax/dense.ts, src/plan.ts |
| Plan | a versioned JSON description of the program: every equation, kernels, groups, sources, clocks, services | plan format |
| Rust runtime | runs plans natively: kernels, full eduction, clocks and alignment, service calls, caches and memory management; the elucid8 command, a 1.8 MB binary | runtime/ |
| Deployment | a plan, the elucid8 binary and a configuration file that binds sources to CSV or JSON-lines files, clocks and services; no Node at run time | deployment configuration |
| Services | external programs that effects call, built in or as separate processes | services, protocol |
| Playground | learning and exploring in a browser | the Playground Tutorial |
#Two tiers
- The reference (TypeScript) is where you develop, debug and inspect. It traces every demand and has check mode.
- Production (the Rust runtime, and later other engines) runs the same program from its plan, fast and small.
- The reference checks production: every Rust result is compared with the reference's, outcome by outcome, in the test suite. Production can never quietly mean something different.
Exactness across languages holds because the plan fixes the operation order (for example, sums always add in the same order), and both languages use one maths library (fdlibm-5.3+exp1+msun-tanh).
#How the stages fit
Each stage built on the last, and each was checked against what came before it.
| stage | what it added | result |
|---|---|---|
| Milestones 1–5 | the language, the reference evaluator, the playground, training a network | exact, but up to 1,300× slower than plain TypeScript |
| Stage 2 (placement) | placement derived from the equations, simulated | the equations choose data parallelism by themselves |
| Stage 3 (hybrid runtime) | group levels and fused kernels in TypeScript | a training step from 3.6 s to 3.2 ms, near plain TypeScript |
| A1 | the plan, made explicit | one contract for every engine |
| A2a | Rust runs kernels from plans | faster than TypeScript, 0.85 MB |
| A5 | one maths library in both languages | the same bits on every machine and browser |
| A2b stage 1 | Rust evaluates any program by demand (eduction) | about 200,000 outcomes identical to the reference |
| A2b stage 2 | clocks, alignment and service calls in Rust | about 680,000 outcomes and 200,000 calls identical |
| A2b stage 3 | source versions, caches across queries, memory management | identical counts to the reference |
| A2c part 1 | the elucid8 command; CSV and JSON-lines connectors; npm run compile | a million-row CSV loads in 0.09 s |
| Next: A2c part 2 | WebAssembly; what a long-running service needs | |
| Then: F1, F2, pilots | command-line tool, file and database connectors; AI model building and feature pipelines |
#Tutorial: one program, three ways
The files used here are in packages/evaluator/demo/guide/. You need Node.js. For parts 3 and 4 you also need Rust; build the elucid8 command once with cargo build --release from the repository root (it is then target/release/elucid8).
#1. In the playground
Paste this program into the playground, give source x the table t,value with rows 0,3 · 1,1 · 2,4 · 3,0 · 4,5 · 5,9, and query running at t = 4:
-- A running sum, and a ratio that fails where x is 0.
dimension t : int range 0..5
source x @ (t)
running @ (t) = x fby.t (running + next.t x)
ratio @ (t) = running / x
You get 13 (3 + 1 + 4 + 0 + 5). running at t = 5 is 22, and ratio at t = 3 is Error: division by zero, because x is 0 there. Turn on trace to see every demand.
#2. In the TypeScript reference
The same program, run from a script (demo/guide/reference.ts):
cd packages/evaluator
node demo/guide/reference.ts
running at t = 4: 13
running at t = 5: 22
ratio at t = 3: Error: division by zero
#3. Compile a plan and run it with elucid8
Compile the plan. The compiler is TypeScript; the plan is plain JSON you can read:
cd packages/evaluator
npm run compile -- demo/guide/running.el8 -o /tmp/running-plan.json
Give it data. running-x.csv holds the same table as the playground (a header naming the source's dimensions and value, then one row per point):
t,value
0,3
1,1
2,4
3,0
4,5
5,9
Bind it. A deployment configuration (running.deploy.json) says where each source's data is and what to compute. It never holds credentials; paths are relative to the file (the format):
{ "format": "elucid8-deploy/1",
"sources": { "x": { "csv": "running-x.csv" } },
"queries": [ "running@{t = 4..5}", "ratio@{t = 3}" ] }
Run it. From the same directory:
../../target/release/elucid8 run /tmp/running-plan.json --config demo/guide/running.deploy.json
{"name":"running","context":{"t":4},"outcome":{"kind":"value","value":13}}
{"name":"running","context":{"t":5},"outcome":{"kind":"value","value":22}}
{"name":"ratio","context":{"t":3},"outcome":{"kind":"error","error":{"kind":"failed","message":"division by zero"}}}
One line per point, each outcome in the same JSON form the reference uses. A few variations:
--query 'running@{t = 0..5}'runs that query instead of the configuration's (repeat it for several).--format csv(or--out results.csv) writes plain values instead, in the table notation, so the file reads back as a source:name,t,value running,4,13 running,5,22 ratio,3,error: division by zero--report report.jsonwrites what the run used and did: the version of each file, timings, counts and service calls.elucid8 check /tmp/running-plan.json --config demo/guide/running.deploy.jsonreads everything and checks the bindings and queries without computing;elucid8 info /tmp/running-plan.jsonlists what a plan needs bound.
Change the data. Edit running-x.csv so that t = 4 is 50, and run again: running at t = 5 becomes 67. Each file's version is a hash of its bytes, so changed data is a new version (the report shows it). A configuration may also list several files as versions of one source; with "options": { "pointCache": true }, a later query that pins another version recomputes only the points whose inputs changed (R3).
#4. Add a clock and a service
alert.el8 reads x on a clock, sends it to a service with a deadline, and raises an alert:
dimension t : int clock sensor
source x @ (t)
effect total @ (t) =
call sum_service(x, 1)
deadline 20ms
alert @ (t) = total > 5
Its configuration (alert.deploy.json) reads x from JSON lines (alert-x.jsonl: {"t": 0, "value": 2} and {"t": 1, "value": 7}), the clock's timestamps from a CSV file (alert-sensor.csv: tick 0 at 0 ms, tick 1 at 100 ms), and binds the service:
{ "format": "elucid8-deploy/1",
"sources": { "x": { "jsonl": "alert-x.jsonl" } },
"clocks": { "sensor": { "csv": "alert-sensor.csv" } },
"services": { "sum_service": { "builtin": "sum_service", "latencyMs": 5 } },
"queries": [ "alert@{t = 0..1}" ] }
Compile and run as before (npm run compile -- demo/guide/alert.el8 -o /tmp/alert-plan.json, then elucid8 run /tmp/alert-plan.json --config demo/guide/alert.deploy.json --report /tmp/alert-report.json):
alertis false at t = 0 (2 + 1 = 3) and true at t = 1 (7 + 1 = 8);- the report lists every service call: started at the tick's timestamp (0 and 100 ms) and completed 5 ms later.
Change latencyMs to 30 and run again. Both answers become late errors, for example deadline 20 ms, completed 30 ms: the deadline counts from each tick's timestamp.
A service can also be a separate program that speaks a one-line-of-JSON protocol, holding its own credentials; the configuration names it ("process": ["node", "scorer.ts"]) and may name the environment variables it receives ("env": ["ANTHROPIC_API_KEY"]), never their values. See the protocol and the example in demo/services/.
#5. Check Rust against the reference
The test suite runs every example, tutorial example and hundreds of random programs through both engines and requires identical outcomes:
npm test # from the repository root; includes the Rust comparisons when cargo is available
cargo test # Rust's own tests, against golden files the reference wrote
#What is not ready yet
- The cluster cache, and group levels with a point cache, in Rust. Point and source caches and memory management are built (A2b stage 3).
- WebAssembly and a long-running service (A2c part 2). The
elucid8command runs one batch of queries and exits. - Database connectors (F2). Files (CSV and JSON lines) are supported.
- Traces from the Rust runtime. Use the reference to see why a value was computed.