You choose, the world moves, and the alternative is never observed.
Every operating decision is taken once.You choose, the world moves, and the alternative is never observed.
Parallax gives you the one you did not take.Point it at a context. It proposes a model of what is actually in there, and hands it to you before anything runs.
You accept it, or nothing happens.A model nobody reviewed should not be able to produce numbers that look authoritative. While a blocking question is open, it refuses to activate.
Then fork the history and change one decision.Same initial state, same seed, one policy different. The log is append-only, so a branch costs nothing to create.
Two lines out of one baseline.The angle between them is the measurement. That is what the word parallax means.
And every number says how much of it was real.Typed observed or simulated at birth, carried into the receipt and into the API.
Simulation results you accept before they are active.
Point Parallax at a context. It proposes a model built from what is actually in there, waits for a human to accept it, and only then rolls it forward under the decisions you are considering. Every answer is typed observed or simulated.
git clone github.com/broomva/skillsthencd skills/skills/simulation/parallax/runtimethenbun install && bun run demo
The design target is not a simulator that is right. It is a simulator that cannot lie about being a simulator — and all three of the things that follow from that are enforced in code, not in documentation.
What it enforces
Four guarantees, each one a runtime check rather than a promise.
A simulator’s output is unfalsifiable by default: it produces confident numbers about a world that does not exist. The usual response is to claim more fidelity, which cannot be checked from the outside at all. These four can.
01 · The gate
An ontology nobody accepted cannot run.
Parallax reads your context and proposes a model of it — state, actions, and the questions it could not answer from what was there. Slots it cannot support come back empty rather than plausible.
The accept gate is a runtime check, not a type-system convention. It refuses while any blocking question is open, and a unit on a numeric quantity is always blocking, because a number with no unit fails closed at materialisation and the proposer will not invent one.
activate() → BLOCKING_QUESTIONS_OPEN. The accepted object is minted behind a module-private symbol and checked at runtime, so it cannot be forged and does not survive a JSON round-trip.
Your operations already run on an append-only history, and a history can be forked. Copy-on-write means a branch costs nothing to create, so the question stops being whether to spend a week testing it.
Same initial state, same seed, same twelve steps — one policy different. The angle between the two branches is the measurement, which is what the word parallax means.
Under an ungoverned sales agent the storefront oversells stock it does not have ten times in twelve steps. Forked at the step before the damage, with a governor installed, the same twelve steps produce zero.
No number leaves without saying how much of it was real.
Every value is typed observed or simulated at birth, and derivation joins the tags: an answer is observed only if every input it came from was observed. One simulated input makes the answer simulated, all the way to the top line.
That is why a Parallax figure cannot be quoted as a measurement by accident. The origin is welded to the number, into the receipt and out through the API.
The receipt states its own split unprompted — this run reports 0 of 12 steps observed — alongside the branch class and what the policy declared against what it demonstrated.
A policy is not allowed to certify its own reproducibility.
Determinism is checkable in five seconds, so Parallax checks it rather than claiming it. certifyPolicy runs a policy repeatedly against an identical probe and compares trace hashes. Replay is a hash comparison, not an assurance.
A policy that cannot reproduce its own output under a fixed seed is demoted whatever it declares about itself — and the demotion is written onto the branch rather than reported once and forgotten.
Same seed → identical hash. Different seed → diverges, which is a different world and not a defect. An unpinned actor → the branch withdraws its own reproducibility claim, PINNED down to STABLE.
Parallax reads what is already there. It does not ask you to model your operation first, because the model is the thing it is supposed to produce.
01PointAt a directory, an agent’s own workspace, or a set of business tables.
02ProposeAn ontology assembled from what is in there — state, actions, and the questions it could not answer. Slots it cannot support come back empty.
03AcceptA human answers the blocking questions and accepts. This is the product, not a formality. Nothing runs before it.
04RollFork the log at a point, change one decision, replay the same steps under the new policy.
05TypeEvery value carries observed or simulated, plus a class saying whether it can be re-derived at all.
Three context classes, one intake
Class 01 — business data
A schema and its rows
Orders, ledgers, inventory, tickets. One state field and one insert action per table, and a blocking question per numeric parameter. The conservation identity is usually already in there — money, stock, hours, seats.
Class 02 — agent workspace
An agent’s own directory
The session is spawned with its working directory already set to the tenant’s own folder, and the confinement keys off exactly that. Passing a derived path instead is denied.
Class 03 — local filesystem
An arbitrary directory
Files, commits, exports, logs. No integration and no schema agreed in advance. Same proposer as the workspace class; the difference is who is allowed to name the root.
You are approving a model of your own operation, not a model’s opinion of it. An empty slot is the correct answer when the context does not support one; a plausible guess is not.
For agents
The agent is a user, not a client library.
Every capability a human can reach is reachable programmatically, over the same handler functions. Every failure is a value with a stable machine-readable code rather than a thrown string a caller has to parse, and the error types are per-operation: a plugin failure inside a rollout carries a partial trajectory, the same failure at registration carries nothing, and a single error type cannot express that difference.
$ parallax propose --kind filesystem --root ./ --json
$ parallax accept --proposal p-4c1e --answer src.count=files --by carlos --json
$ parallax run --horizon 12 --seed 42 --governed --json
$ parallax receipt --run bef312a9 --out out/run.html
# --ontology is optional: omitted means the newest acceptance
Seven commands. Every one of them takes --json and prints a value, so a shell script never has to parse prose.
The surfaces diverge in exactly two places, and both are confinement rather than capability. --root: an arbitrary absolute root is safe at a terminal, because the person typing the path is the confinement. It is absent from every tool schema, because inside a sandboxed session a derived path is denied and a denied read comes back as an empty directory rather than an error — so a wrong path would look like an empty workspace. And --out on receipt, which writes the page to a path the tool surface returns but never sends, because a receipt is tens of kilobytes and does not belong in a context window.
That count is not a promise in prose. A test asserts every tool has exactly one CLI command, every command has a tool behind it, and every flag maps to a tool field or to one of those two named divergences — so widening the claim means editing a test that says so. It was written because the claim had been wrong: three tools had no CLI command at all.
The channel
It reaches people where the business already runs.
An operator does not open a console. A real WhatsApp message already runs an agent turn in a confined workspace on our own hardware, and the hub already answers the route that turn calls. Each hop below carries its own status, because three of the four run today and the fourth does not.
01channel
WhatsApp → Kapso
Kapso is the Cloud API gateway. A thread id arrives as kapso:<phoneNumberId>:<waId>, and the principal is the second segment — the first is our own number and is identical on every message.
runs in production
02ingress
Tailscale Funnel
The webhook port is published and nothing else. SSH stays closed. AllowFunnel is keyed by host:port rather than by path, so the port is separate from the one the web app listens on.
runs in production
03runtime
Genesis session
One agent session per thread, in a dedicated workspace directory rather than the whole home. A fail-closed allowlist on waId: an unknown sender is refused, and the refusal is the default rather than the exception.
runs in production
04engine
Parallax hub
POST /api/whatsapp/turn takes {from, text, threadId} and nothing else. Every ontology, run and receipt is authored server-side, so there is no path here that hands the hub content to publish.
runs, driven by the demo
The first three hops carry real WhatsApp traffic today. The fourth is the one that is not joined in production: the hub answers /api/whatsapp/turn and serves /r/:id, and bun run demo:live drives that whole thread against the deployed hub — but no Genesis session is calling it for a real number yet. Parallax’s own channel layer is pure functions with no transport attached, which is exactly why it can be pointed at one.
A domain arrives as data. The runtime never changes.
Six operators are closed over one record — step, observe, check, rollout, diff, traceHash. Adding a domain adds a record. Adding a capability adds an operator, and there are six of those. That asymmetry is what separates a simulation runtime from a pile of bespoke simulators.
ONTOLOGY — the five slots, and who is allowed to compute each one
Slot
Supplies
Who computes it
state
typed fields, units mandatory
schema
actions
name, actor, params
schema
transition
how an action changes the state
code, never a model
invariants
what must always hold
code, never a model
initial
where it starts
data
Domain 01 — storefront
A WhatsApp storefront under a sales agent
Stock and money conserve. The demo runs it ungoverned, catches it overselling inventory it does not have, forks the history at the moment before the damage and replays with a governor installed.
Domain 02 — clinic
An appointment desk, and the generality proof
Its own transition, its own conservation law, its own fourteen tests. It is the answer to “does this only work for your toy storefront?” — the runtime did not change to accept it.
Where it fits
Bring one context and one thing that must never be true.
An ontology is a record, not a codebase: what the state is, what actions exist, how an event folds into the state, and what must always hold. Two of those four are code and always will be.
Operations
A change you only get to make once
A price, a refund policy, an escalation threshold. There is no staging environment for the way a business operates, so the usual way to find out is from a customer.
What happens to stock-outs if the sales agent can promise same-day?
Agent governance
Whether a governor would have caught it
Point it at an agent's own workspace, replay the run it already did, and install the constraint you were considering. The branch says which steps it would have refused.
Which of these twelve steps would a stock governor have stopped?
Capacity
A schedule under a load it has not seen
The clinic domain is an appointment desk with its own transition and its own conservation law. Seats, hours and slots conserve the same way money and stock do.
If two clinicians take leave, how many appointments miss their window?
Rehearsal
A directory you have not modelled yet
No integration and no schema agreed in advance. One non-recursive read and a stat per entry, and what comes back is a proposal you can argue with.
What is this repository as a state machine, and what does it already conserve?
Every figure on this page is printed by a command in the Parallax runtime at seed 42 over a horizon of 12 steps. There are no customer deployments, no accuracy claims and no benchmark numbers here, because we have none.
Runs today
The runtime and its six operators
Append-only log with copy-on-write forking
The reproducibility lattice, and the policy certifier that enforces it
Conservation and safety invariant checking
The accept gate, brand-checked at runtime
The conversation layer as pure functions
The self-contained run receipt
A CLI, an HTTP hub and a tool surface over the same handlers
A second domain — the clinic, the generality proof
Designed, not built
The LLM adapter — today’s actors are seeded and pure
A third domain, and a domain supplied by someone who is not us
The web console
A live WhatsApp number; the channel layer is pure functions with no transport attached
Not measured
Nothing here is calibrated against a real business
No accuracy percentage, because we cannot support one
No customer results, because there are no customers
We would rather say that than publish an accuracy number we cannot support. It is the oldest open item in this project and it cannot be closed by writing code.
Objections
Reasonable things to distrust about this.
It is still a simulation. Why would I trust the number?
Do not trust the number. Check what it says about itself. This run reports 0 of 12 steps observed in its own receipt, unprompted, and its policy’s declared class was measured against a repeated probe instead of believed. Nothing here is calibrated against a real business, and there is no accuracy figure on this page because we cannot support one.
Is a language model computing my numbers?
No. The transition function and the invariants are code — activate refuses an ontology that supplies neither. A model’s only job is reading a mess into typed observations, and nothing downstream of that projection is a model’s opinion. Today the actors are seeded and pure.
What if the proposed ontology is wrong for my business?
Then you answer the questions differently, or reject it, and nothing activates while a blocking question is open. What you cannot do today is accept part of it: acceptance is all-or-nothing plus the set of answered slots. Editing a proposal produces a new proposal with a new hash, which is the honest behaviour and also the more annoying one.
We already A/B test.
A/B testing spends real customers and real weeks, and it cannot evaluate a decision you only make once. A fork is a branch record. The two compose: use this to decide what is worth testing for real.
Our context is a mess.
Then the proposal comes back smaller and emptier. The one it made of the Parallax project directory proposed zero invariants and seven blocking questions, and said so in the message rather than filling the gaps with something plausible. An empty slot is information about your context, not a failure of the tool.
Requires Bun. The demo runs a WhatsApp storefront under an ungoverned sales agent, catches it overselling, forks the history at the moment before the damage, replays the same twelve steps with a governor installed, and prints the difference.
$ git clone https://github.com/broomva/skills
$ cd skills/skills/simulation/parallax/runtime
$ bun install
$ bun run demo# run, observe, check, fork, prove
$ bun run demo:whatsapp# the same thing as one thread, ending in a receipt
$ bun run demo:live# the same thread against the deployed hub
$ bun run mutants# deletes a guarantee, checks whether anything goes red