CAVEAT Language
A decision ledger for agents.
Caveat is an embeddable decision runtime with a small rules language. It
tracks evidence through computations, preserves the grounds of recorded
decisions, and supports reconsideration under authored policies when evidence
changes.
The published preview is 0.1.0-rc.18. See the
rc.18 release record for what it changes.
npm publication verified 2026-10-12T01:35:33.034Z: exact caveat-lang@0.1.0-rc.18,
tested Linux artifact SHA256 dc000fc7c521f6cc62d1f91b533196db0173f8284b2731e716047662de913495.
At verification, latest names rc.18 and next remains rc.17; the owner moved next to rc.18 at about 01:44 UTC. Published by publish-npm.yml from tag v0.1.0-rc.18 with an npm provenance attestation.
Publication record.
When a tool result is corrected or a memory turns out to be stale, an application
needs to reconsider its decision without losing the reasons for the original.
Caveat is a programming language that keeps evidence and caveats with computed
values, freezes a decision's grounds, and records why it was reopened.
Try the published preview, 0.1.0-rc.18, in an empty directory with Node 20 or
later. No Rust installation is needed:
npm init -y
npm install caveat-lang@0.1.0-rc.18
npx --no-install caveat-lang init
npx --no-install caveat-lang test umbrella.scenarios.json
npx --no-install caveat-lang explain umbrella.cav events.jsonl
The two scenarios pass. The explanation shows umbrella@1 = 70 reopened by
sky, still based on rain_chance@1 with its forecast_is_old caveat. New
knowledge changed the decision's status; its original grounds remain visible.
Getting started walks through those files, and
the language in brief covers the syntax.
The rc.18 verified publication record identifies
the exact tested npm artifact. rc.18 adds unqualify, which takes back a caveat
that qualify added, on every held occurrence of the evidence. The take-back is
recorded, not erased, and decisions keep their basis. A save of a program that
uses unqualify does not restore in rc.17 or earlier; other saves do. View 0.2
now lists a binding's evidence in the order it was first observed, and
explain names the event and payload that observed each ground.
rc.17 introduced View 0.2, source digests in saves and the integration starter
(caveat-lang/starter); the digest identifies the source and does not
authenticate a save. rc.16 introduced departure of eligible retired history, host-drained archives
and collection of unreachable withdrawal groups; see the
history integration guide. rc.15 introduced integer clocks, history windows and caveat test checking each
program first. rc.14 corrected the package's MCP server name to
io.github.WSattazahn/caveat-lang. rc.13 introduced interface JSON and TypeScript
declarations, stricter restore checks and refused invalid id_text operations.
Restoring a save still does not authenticate its history. A refused operation is
not completed; hosts must inspect its outcome. The package has no npm runtime
dependencies.
With rc.18 installed, use npx --no-install caveat-lang doctor to check it and
npx --no-install caveat-lang demo agent to see observation, assessment,
correction and revision in one run. Introduced in rc.7, the unambiguous
caveat-lang command remains preferred; caveat is supported shorthand.
See CLI names.
Integrating an agent? Follow the agent quickstart
and the official Python client. Caveat
records supplied evidence and authored policy; it does not authenticate evidence
or authorize external actions. Repository agent guidance includes
how to report a capability missing from a real integration.
What stands behind it? What is checked, and how
separates the Lean model theorems (proofs/lean),
sampled native and WebAssembly conformance with mutation controls, runtime
tests, and the boundaries nothing proves. It also states the limits: windows
do not bound saves, nothing is authenticated, and hosts set their own resource
budgets.
Principles and capabilities
For the thinking behind Caveat, read
The Caveatist way.
Say what you know, and what it rests on.
Carry the caveats honestly.
Give uncertainty the attention its consequences deserve.
Act when there is enough to proceed.
Remember why you chose, and remain willing to choose again.
These are the project's principles. They are not additional license
conditions: Caveat is under the MIT License, and using it does not
require adopting them.
- Values carry their evidence. Every number computed from an observation carries that evidence and its caveats through every sum, comparison and label.
- Explanations are checked against recorded dependencies. Anything a program shows can say why, and the runtime rejects an explanation that cites something outside the value's recorded dependencies. That does not prove the evidence true or the explanation complete.
- Decisions remember. A decision records what it was made on, reopens when the world disagrees, and keeps a journal.
- Late knowledge is one line. A late qualification reaches current values built on the evidence and future values that read it. Archived readings and decisions already made keep what they knew.
What Caveat is for, on one page: the problem, one example to run, and the limitations.
Play the glowcap explainer. Four look-alike mushrooms, a belief, a trust decision that is made, doubted and remade, and a "why?" under everything on the page. The rules and explanations all live in game/glowcap.cav; the page only renders them.
Start with Change what you know at the top of that page. Make a decision in one click, learn something new in a second, and see what changed, why the decision reopened, and the original reasons it kept.
Trail Rescue is a new, complete mechanic: spend three scout tokens, weigh
fallible reports, choose a tunnel and reconsider when its evidence changes or
ages. Its requirements and 24 scenarios
were committed before implementation. The Caveat program
owns the rules; the browser page displays its decisions
and frozen explanations. Build and run it with npm run build, npm run serve,
then open http://127.0.0.1:4173/trail-rescue.html.
The mechanic added state caveat queries:
has_caveat(plan_basis, stale) checks the actual grounds of a saved basis, and
reopen route because caveated(plan_basis, stale) cites the precise observations
that have gone stale. The journal now preserves elapsed time and the chosen
numeric value, and restore checks its consistency with commitments and evidence.
See the implementation record.
Fresh-agent authoring: six fresh contexts authored three specified policies
using a frozen documentation packet, without private-test feedback. Two first
submissions and all six final submissions passed the registered corpus: 20,512
final-source events and 4,788 restores. All four initial failures exposed an
unclear numeric-bound restriction, now documented. Two final programs still
have known clock-horizon contract violations outside the corpus. The
study report preserves those limits,
every revision and the independent verification.
Reactive source can now read the runtime clock directly with
elapsed(): bind hud.elapsed = elapsed();.
It uses the same time as scheduled caveats and decision journals, survives
save/restore, and removes the need for a separate bounded state timer.
A four-context follow-up repeated
the two clock tasks with this read and the updated guide. Three first submissions
and all four final submissions passed: 13,704 event dispatches and 3,158 restores.
All four used elapsed() directly, without a separate state clock. The remaining
first-source failure was unsupported comment syntax, repaired by its author.
This is evidence on two repeated tasks, not a general reliability claim.
The reactive runtime also offers structured dispatch outcomes.
dispatch_outcome distinguishes an authored policy rejection from invalid input,
state-bound failure and execution-budget exhaustion. Unclassified errors remain
fatal. Existing dispatch methods retain their behavior; new integrations can use
the explicit outcome contract when testing why an event was refused. A host that
redraws from the view uses dispatch_view_outcome (session.dispatchView in the
developer kit): the same outcome, with the view in place of the snapshot.
What Caveat makes part of the language
These mechanisms can also be implemented and checked in a general-purpose
language, through application code or libraries. Caveat supplies them together
as language and runtime facilities; the comparison is about what an author
has to provide, not what another language can express.
| Caveat | In a general-purpose language |
|---|
| Explanations | bind label = "Could be a duskcap" when … because contradiction; The runtime checks cited evidence and caveats against recorded dependencies. | Implement dependency tracking and citation checks, or use a library that supplies them. |
| Caveats through computation | qualified(1, taste, tasted_in_dark) carries evidence and caveats through computations that use it. | Use provenance-aware values and operations to propagate the same information. |
| Late caveats | qualify taste with taste_faded; qualifies current values built on the taste while earlier decision records retain their frozen caveats. | Track dependencies or resolve qualifications when read, and freeze decision records. |
| Decisions that remember | commit, reopen, and decision-series revisions retain each revision's grounds and publish its changes in decision_journal. | Implement decision records, frozen bases and an ordered change journal. |
| Grounds and lineage | The runtime distinguishes a value's content grounds from its wider control lineage and enforces grounds ⊆ lineage. | Represent both kinds of dependency and enforce their relationship in code or a library. |
| Atomic events | reject "already absorbed"; rolls back the event's changes to the Caveat session. | Use transactions, immutable state or explicit rollback to publish changes atomically. |
Citation checking establishes that cited dependencies were recorded; it does
not establish that supplied evidence or authored explanation prose is true.
Citations may omit dependencies, so the check does not establish completeness.
The evidence: the glowcap benchmark
The glowcap benchmark implements the same game beat twice, in TypeScript and in Caveat:
- a frozen, pre-registered scenario suite;
- sixteen change requests, twelve committed before either implementation changed for those rounds;
- seeded differential fuzz comparing accepted and rejected events, views, and save/resume behavior.
| Round | Change cost, Caveat vs TypeScript | Result |
|---|
| 1 · first version | 55 vs 57 (CR1–4) | Parity. The weak spots became the language's work list. |
| 3 · blind requests | 97 vs 53 (CR5–8) | TypeScript. Caveat's lineage fought the designer's meaning. |
| 5 · replay after language changes | 71 vs 110 (CR1–8) | Caveat, with 134 lines against 164. Written knowing the requests. |
| 6 · blind requests | 92 vs 103 (CR9–12) | TypeScript. Caveat passed two of four phases on the first run, capped regrowth at eight lives, and exceeded the 1 ms event budget. |
| 6 · replay after language changes | 88 vs 103 (CR9–12 plus correction) | Lower change cost; mixed size (168 vs 233 lines, 12,701 vs 11,586 bytes). Three of four phases passed on the first run. Written knowing the requests. |
| 7 · fresh authors, blind requests, rc.11 | 186 vs 282 (CR13–16) | TypeScript. Two of four phases on the first run against four; more regressions; no Caveat save stayed bounded with a fast resume in long play. Neither decision rule met. |
Round 7 left a work list: a save that forgets what the program forgot, ordered grounds, timing to the tick and the adapter glue. No claim that Caveat is better than TypeScript goes into the README, AGENTS, the site or other documents on the strength of this round.
The earlier losses led to grounded explanations, grounds, reject, define, typed parameters, the view, late qualification and the decision journal.
Round 6 led to incremental evaluation, a load-time observation-order check, procedures that take evidence, renewable evidence and timed caveats, and save/restore without event replay. The replay passes all 38 scenarios and a separate twelve-life regrowth check beyond the old eight-life cap. Its evidence still has a declared limit of 1,024 lives per mushroom: the request for unlimited regrowth remains unmet. Its first save-format attempt exceeded the size limit; that failed run remains in the results.
The replay's first 2,000-sequence fuzz found five disagreements in about 7.3 million events. JSON parsing changed some floating-point values by one unit in the last place, moving timed behavior across a boundary. Exact round-trip parsing fixed those timing differences. Final review then found the fuzz was hiding a decision-basis ordering bug by sorting that list. The adapter now uses the journal's ordered evidence, and an order-sensitive comparator checks it. That correction adds two changed lines to the original 86. The five captured sequences and both stricter 2,000-sequence fuzz runs now agree: 14,443,471 fuzz events with zero divergences. The blind round remains a TypeScript win, and this replay does not establish an adoption win under the same rule: size is mixed and only change cost is clearly better.
What Caveat still costs, honestly:
- 51.6 µs median per event plus view for the replay program, against 2.5 µs for TypeScript, measured after the fuzz;
- 482,716 gzipped bytes (about 483 KB) for the Caveat policy, adapter and runtime;
- rounds 4, 5 and the round-6 replay were written knowing the requests; round 7, the blind round with fresh authors that followed, went to TypeScript.
A taste
event absorb target kind mushroom, sort in glowcap duskcap;
for mushroom as $m {
on absorb when $m_here and $m_consumed == 1 reject "already absorbed";
on absorb when $m_here and sort == sort.glowcap
set support = support + qualified(1, absorb_$m);
on absorb when $m_here and sort == sort.duskcap and committed(trust)
reopen trust because absorb_$m;
on tick when $m_tasted_at >= 0 and now - $m_tasted_at >= 60
qualify taste_$m with taste_faded;
bind $m.label = "Probably a glowcap" when $m_unknown and probably_safe because support;
bind $m.label = "Could be a duskcap — taste first" when $m_unknown and uncertain because contradiction;
};
Read Why Caveat for side-by-side code and the Caveatist way of working. To write your first program, start from the authoring guide.
Status
CAVEAT 0.3 is executable, and the 0.4 game profile adds executable knowledge requirements, evidence-dependent outcomes, and source-authored spatial presentation. The Rust reference runtime parses source, evaluates the epistemic graph, exposes the CAVEAT Map, and runs the same game program in a terminal or a WebAssembly browser session. The new features are specified in Draft 0.4.
Other playable examples include Light the Way, a direct-control ferry rescue powered by the new reactive CAVEAT profile. Movement, scouting, collisions, damage, score, and outcomes are source rules. The Last Beacon remains a separate 3D island mystery whose available decisions, evidence, retained uncertainty, nine outcomes, locations, cameras, and paths are authored in game/the_last_beacon.cav. Moon Garden remains a short mobile-first mystery with both a 2D presentation and a separate Moon Garden 3D presentation driven by the same CAVEAT session. The earlier The Door scenario remains the world/action stress test.
CAVEAT 3D 0.2 now consumes normalized Rust action-runtime executions (Move, Inspect, Operate, Open, Observe, Stay) and maps those commands to semantic place/entity/symbol presentation bindings. Moon Garden no longer needs normal per-action camera choreography. See spec/caveat3d-0.2.md. The remaining graphics problem is art-direction automation: semantic identifiers can now drive the scene, but attractive camera composition and assets still require authored presentation bindings.
Play Light the Way. Hold and drag the lighthouse beam, or use the arrow keys. Steer the ferry around reefs into the green harbor. Hold the light over the current marker to take a reading while the ferry keeps moving. When the storm shifts, decide whether to correct the old plan manually or spend another second scouting. Damage, sampling progress, and rescued passengers appear directly in the scene. Retry immediately. No download or reading panels are required.
The new reactive language profile adds bounded numeric state, typed events, arithmetic, ordered conditional rules, and atomic event execution. The same rules can read and change CAVEAT's actual evidence graph. In the game source, spotting a reef records evidence, pays to examine the associated caveat, reopens the initial course commitment, and makes the ferry slow near the known danger. The original chart claim and contrary evidence both remain in the graph.
state boat_x = ferry.x min -9.5 max 13;
event tick dt min 0 max 0.1;
on tick when phase == 1 set boat_z = boat_z - boat_speed * dt;
The reactive 0.2 extension adds reusable numeric functions, evaluated presentation bindings, explicit sound/visual cues, source-declared controls, and a source-declared clock. The rescue's screen transitions, feedback, visual state, and keyboard steering now live in the Caveat program. Cues publish only when their event commits; a failed calculation rolls back its graph changes and feedback together.
Reactive 0.3 carries evidence and caveats through numeric values, arithmetic, function calls, comparisons, and conditional decisions. A commitment made using a derived value automatically retains its caveats and records a frozen numeric basis plus relies_on evidence edges. The crosscurrent's source-authored observation and steering functions exercise these semantics; the browser continues consuming ordinary rendering values.
The crosscurrent demonstrates why that matters beyond moving code: a qualified forecast supports an initial navigation commitment; an opposing observation reopens it and leads to counter-steering while retaining the unresolved caveat. Observation changes the navigator's response, not the physical current. Both evidence histories remain inspectable. Preserving Caveat's essence records the invariants and the remaining Rust/browser boundaries.
The current shifts twice during the crossing. A previously measured steering correction keeps its old value until a fresh sample supports a revised plan. Bright arrows show the surface flow; faint arrows preserve the last reading, and the HUD shows its age. Rescouting has an immediate cost: the same light must stay over the marker while the moving ferry still needs steering.
Reactive 0.4 adds bounded text expressions and conditional expressions. The standard functions abs, min, max, and clamp are implemented in Caveat source, replacing their Rust algorithms. Source functions also format the game's clock, numbers, and percentages; source state owns notice lifetimes and hull indicators. Rust still implements the evaluator and browser bridge. This is a first source-defined standard library, not a self-hosted compiler.
Reactive 0.5 adds bounded reading streams and decision series. Each sample creates a distinct evidence occurrence, even when the measured number repeats. latest(flow) reads its qualified value; a decision series records a new frozen basis after its previous decision is explicitly reopened. Old occurrences remain addressable in snapshots and graph relations. The game uses one sampling procedure and one navigation series across repeated weather changes, replacing its separate first-reading and storm-reading implementations.
The same capability is exercised by a thermostat program: cold, warm, and cold readings revise the heating command while preserving every reading and the unresolved calibration caveat. Run cargo test --manifest-path runtime/Cargo.toml --test thermostat_history to exercise its input history and rollback behavior. This is a separate source consumer of the generic language feature, with no thermostat logic in the interpreter.
Reactive 0.6 makes retained history available to source computation through history_count, zero-based history_at, and fold_history. Reducers are ordinary pure Caveat functions: summing, calculating a range, or choosing a history-based policy requires no corresponding Rust algorithm. Results retain the observations, caveats, and selection dependencies involved. Invalid indexes, reducer errors, and bounded-work failures roll back the whole event. The thermostat calculates mean/range/trend in source, while the game compares archived readings to report a measured flow reversal. This is bounded history computation, not yet arbitrary collections or self-hosting.
Reactive 0.7 adds reusable effect procedures. A proc can share input handling or a sequence of observation and revision steps across source events. Calls freeze their numeric arguments and entry guard, retain every argument's qualifications, and run within the calling event's atomic transaction. The game shares keyboard cleanup, held-input activation, and aim movement; the thermostat records and revises through a source procedure. Rust provides bounded calls and validation, while the behavior remains Caveat source.
Explanations 0.1 lets a binding say what it cites: bind label.text = "…" when … because contradiction;. The runtime checks the citation against the binding's lineage, so an explanation may leave dependencies out but can never cite evidence or a caveat the value and its conditions did not read. The full lineage stays available for audit.
Explanations 0.2 separates what a value is based on (its grounds) from everything that could have influenced it (its lineage). A rule’s guard, a skipped rule, the guard that revealed evidence, and a decision’s predecessor all stay in lineage but never enter grounds. Citations read grounds, set x = e because c narrows them, and grounds are always a subset of lineage.
Reject 0.1 adds reject "MESSAGE": an event that is not allowed fails atomically with that message, without a dummy state or a require trick.
Define 0.1 adds define NAME = EXPRESSION;, a named expression over state and the graph that is inlined wherever it is read, including per member inside a for block.
View 0.1 adds dispatch_view: the same transaction as dispatch, returning only what a host redraws after an event (bindings and their citations, cues, effects, commitments and their grounds, relations) as compact JSON.
Typed Parameters 0.1 lets an event take an entity by name (target kind mushroom) or one of a list of names (sort in glowcap duskcap). The host sends names; the source reads positions, with sort.duskcap and target.pool as constants.
Late Qualification 0.1 adds qualify EVIDENCE with CAVEAT: a caveat learned after the fact reaches every current value built on that evidence, while decisions already made keep what they were made on.
Decision Journal 0.1 publishes every commitment and reopening, in order, with the evidence each was based on in the order it was observed.
Observation Order 0.1 rejects, when a program loads, a rule that qualifies a value with evidence only a later rule of the same event can reveal: a use that can only fail. Uses that could succeed on some dispatch are left to the runtime.
Procedure Symbols 0.1 lets a procedure take evidence, a claim, a caveat, a reading stream or a decision series by name (proc learn(e evidence, sort)), so every way of observing something can share one set of rules. Each call is specialized for the names it passes when the program loads.
Identifiers 0.1 lets an event carry text the program first learns while it runs, such as a commit SHA (event pushed commit id;). The program sees a numeric handle for each distinct text, and id_text shows the text again.
Withdrawal 0.1 records that an observation is no longer stood behind (withdraw latest(checks) because recheck;). Nothing is erased: decisions keep what they were made on, current values and later reads carry a withdrawn caveat, and rests_on_withdrawn(D) lets a program decide what the withdrawal means for a decision.
Permission 0.1 records what permitted a decision, apart from what it rests on (commit merge … permitted by latest(approvals) for head;). A missing, withdrawn or mismatched grant refuses the commit as policy/not_permitted, and the frozen record says which grant permitted it, for which value.
Reopening Triggers 0.1 lets a decision series declare which readings reopen it (decisions merge limit 8 reopened by pushes, checks opposing ready;), instead of a hand-written reopening rule after every such reading. The reopening is exactly the authored one, run at the moment the reading is taken.
Check 0.1 is caveat check: advisory warnings about patterns worth a second look, such as rules repeated across events that one procedure could share, or a decision made on readings that nothing reopens. A warning is not a proven fault; # caveat check: allow CODE records a pattern that is intended. The kit's one-page reference gives the language in brief, and its worked example takes one program through every command, with output the tests check.
Renewal 0.1 gives evidence an identity that events create: renewable taste_cave limit 256; and renew taste_cave make the name mean a new, unobserved occurrence while earlier ones keep what they were about. qualify taste_cave with taste_faded after 60 fades that occurrence on its own clock, and carries(taste_cave, taste_faded) asks whether it has.
Save 0.1 saves a session and restores it without replaying events: WebReactiveSession.save() and WebReactiveSession.restore(source, saved). Restoring costs what loading costs plus the size of the save. Restore validates the saved names, values and histories; mutation tests check that an altered save is either refused or remains playable without a crash.
Incremental Evaluation 0.1 makes an event cost what it touches: a binding is evaluated again only when something it reads changed, and a transaction copies only what its effects write. Round 6's 32-entity program went from 1.1 ms to 0.16 ms per event with identical results, checked against full evaluation after every event in the test suite.
Water time and accumulated rain travel now come from Caveat state too. The source-defined wrap function keeps travel bounded; the renderer maps those values onto its existing wave shader and seeded rain geometry. Pausing or replaying a session preserves the corresponding weather pose.
The browser sends input and elapsed time to generic WebReactiveSession, then draws its snapshot. It does not calculate the ferry's movement, collisions, damage, route rules, or rescue result. Rust implements the language interpreter; the game-specific rules are Caveat. Design notes explain the controls and the source/runtime/renderer boundary.
Keyboard policy also lives in Caveat: source-declared physical-key controls
retain independent presses and releases, combine aliases and opposing keys,
and steer during source ticks. The browser forwards those input facts. A
source-only remapping changes the playable controls; observing, examining,
and reopening still follow the same qualified event rules.
Pure Caveat functions are callable through the source library API from Rust and WebAssembly. The Door's movement timing and turning policy now live in its Caveat source, and The Last Beacon's feedback classification and copy use source functions. The same compiled evaluator serves both hosts. Full program validation and host transaction boundaries remain explicit.
For headless authoring, caveat-reactive validate checks a reactive program and caveat-reactive replay executes JSONL event histories with qualified snapshots. The authoring guide includes commands and a thermostat input fixture. Development insights separates demonstrated behavior from hypotheses about broader usefulness and AI adoption.
The Slime glow ability policy is a small host-integration
consumer: a reported mushroom absorption creates one qualified learning receipt,
and ordinary ability toggles retain that basis without growing a per-input
history. Glow controls require a separate host-verified renderer capability;
without it, the source reports the mushroom discovery without claiming light.
Native and real WebAssembly tests exercise 10,001 ready toggles, early
discovery, duplicate absorption, reset and source-only policy variation. Vessel's
world contact, inventory transaction and visible glow remain host responsibilities.
The Last Beacon — story experiment
On stormbound Saint Orin, thirty-two ferry passengers are approaching a failing lighthouse. Spend three watches investigating uncertain evidence, try provisional plans, reopen them when the world disagrees, and choose between restoring the light, sending the island pilot, or holding the ferry offshore until daylight. Six interactions produce 324 legal decision sequences and nine outcomes across three final destinations. Earlier investigations unlock targeted preparations; performing those preparations changes what the same final order accomplishes. See the game design and action contracts.
The game also develops the language runtime. The generic Rust GameSession and WebAssembly WebGameSession apply world actions and epistemic effects atomically, expose the graph reached by the player's actual decisions, and restore saves by replaying source-checked selections. The parser now supports quoted semicolons, escaped and Unicode text, line comments, and source locations in errors. These capabilities are available to any CAVEAT program; see the game-session specification and source-text specification.
These are actual game-source statements:
require bridge_reserve observed reserve_charge;
resolve relight_beacon as beacon_guided when observed measured_pulses;
resolve relight_beacon as beacon_limited when observed hot_cable;
resolve relight_beacon as beacon_limited when observed split_beam;
resolve relight_beacon as beacon_unverified otherwise;
The runtime checks the reached evidence graph before an action. Declared but undiscovered evidence cannot unlock it; contradictory evidence remains recorded. The browser receives available choices, blocked reasons, and the selected outcome from CAVEAT. It supplies reusable graphics and controls. Presentation declarations also place the world and compose its views without changing JavaScript.
Play the earlier story. This hosted copy opens directly in your browser; downloading an HTML file is optional.
Build and play
With rustup and Node.js 20 or newer installed, run from the repository root. rust-toolchain.toml pins the compiler, and rustup installs it with the WebAssembly target on first use:
cargo install wasm-bindgen-cli --version 0.2.104 --locked
npm ci
npm run build
npm run serve
The build also writes dist/pkg-reactive/: the same runtime without the sequential, graphics and 3D sessions, for hosts that only run reactive programs (cargo build --no-default-features). It is about a fifth smaller.
Open Light the Way locally. The earlier story experiment remains available. The build compiles the Rust runtime to WebAssembly and assembles dist/ with game sources, browser assets, and a local copy of Three.js. Existing games remain available in the same build.
To play the earlier Last Beacon story in a terminal, without a browser or JavaScript:
cargo run --manifest-path runtime/Cargo.toml --bin caveat -- --game game/the_last_beacon.cav
For an optional offline copy, run npm run package:game after building. Open dist/Light-the-Way.html in a full modern browser (dist/The-Last-Beacon.html contains the earlier story): it embeds the actual CAVEAT WebAssembly runtime, story, and graphics and works without a server or network connection. Some file-preview applications disable scripts or module imports; those previews cannot run the game. The launch screen now includes browser guidance, a retry action, and a timeout for failed or stalled imports. In the earlier story game, device-local saves preserve your watch. Choose with the buttons or keys 1–3, open the journal with J, and toggle ambient sound with M.
The workflow notes explain how the video's visual inspection and testing loop informed this implementation.
Verify
cargo test --manifest-path runtime/Cargo.toml
cargo clippy --manifest-path runtime/Cargo.toml --all-targets -- -D warnings
npx playwright install chromium
npm run test:beacon
npm run test:rescue
npm run test:slime-glow
Reactive runtime tests check atomic rollback and malformed input, and rescue simulations verify a complete crossing, damage, timing, and the direct effect of observed evidence on motion. Browser rescue tests use real mouse, keyboard, and touch input. The story tests exhaust legal Beacon routes, checking unavailable-action rejection, investigation costs, retained caveats, reopening, physical destinations, evidence-dependent outcomes, and save restoration. Browser tests exercise the WebAssembly game and its presentation, starting their own local server. Run npm run build first; a separate npm run serve process is not required for tests. After npm run package:game, npm run test:launch also exercises blocked imports, a stalled module, and disabled-script previews. npm run test:slime-glow needs no browser: it drives the installed WebAssembly runtime directly and checks the host-integration policy's bounded toggles, verified renderer capability, qualified escape, atomic failure, and malformed input. It runs in the same continuous integration job that assembles dist/.
Repository layout
spec/ — versioned language specifications
runtime/ — reference runtime
runtime/prelude.cav — source-defined standard math and display functions
tests/ — canonical semantic tests
examples/ — example CAVEAT programs
game/ — playable CAVEAT scenarios, including The Last Beacon, Moon Garden, and The Door
web/ — browser presentations and generic world rendering
scripts/ — reproducible browser build, local server, and game verification
Do not save CAVEAT by redefining it. If the computational model collapses into an existing paradigm, record the result.
License
Caveat's code and documentation are available under the MIT License.
Third-party components keep their own licenses: the Rust crates compiled into
the WebAssembly runtime are listed with their notices in
THIRD_PARTY_NOTICES.md, and the site's copy of
three.js ships with its license as vendor/THREE-LICENSE.txt.
Culture
Caveatism is the philosophy that grew up around Mr. Caveat, a mechanical fortune teller who always has a caveat; it lives in caveatism/ and is culture, not a contract of the language. The Archive and Atlas are its texts, and the canon keeps the Archive as a Caveat program with scenarios.
