Parallax is a simulation runtime built so that it cannot lie about being a simulation. It is not trying to be a simulator that is right; it is trying to be one whose reproducibility you can check in five seconds.
Repository: https://github.com/broomva/parallax · hub: https://parallax-hub.onrender.com
Install
This skill is canonical in the product repo and vendored into the skills monorepo, so either address installs the same bytes:
npx skills add broomva/parallax --skill parallax -g -a claude-code
# or, equivalently:
npx skills add broomva/skills@parallax -g -a claude-code
A CI gate asserts the two copies are byte-identical, so they cannot drift apart without going red.
Then Parallax itself, which the skill drives:
git clone https://github.com/broomva/parallax && cd parallax
bun install
bun link # puts `parallax` on PATH
parallax help
Or drive it as tools inside an agent, with no dependency on ai or zod:
import { generateText } from "ai";
import { parallaxTools } from "./src/tools";
await generateText({ model, tools: parallaxTools(), prompt });
The flow
Nine commands, one for each tool. The order a thread uses them:
| Step | Command | What it is |
|---|---|---|
| 1 | parallax status |
where this workspace stands, read from disk |
| 2 | parallax propose |
an ontology built from what is actually in the directory |
| 3 | parallax render |
re-send the proposal byte-for-byte |
| 4 | parallax parse-reply --text <msg> |
classify a human's reply; records nothing |
| 5 | parallax answer --answer <n>=<value> |
record answers without accepting |
| 6 | parallax accept --by <who> |
the gate: nothing simulates before this |
| 7 | parallax run --horizon N --seed N |
roll forward, write a receipt |
| 8 | parallax receipt --run <id> |
locate or export the receipt |
| — | parallax reject --reason <why> |
archive a refusal; refusals are kept |
Add --json to any of them for the {ok, value} envelope. Same values, different
framing.
The four rules that are not guessable
1. Read the state from disk, never from the conversation. A Parallax session is
a new OS process every turn, and an ActiveOntology is branded with a
module-private symbol that does not survive a JSON round-trip — trust cannot be
serialised. So "we already accepted that" is not a thing you can remember; it is a
thing you look up. Call parallax status first on any turn that mentions
accepting, running, answering, or a previous proposal.
2. Relay text verbatim. Never restate a number. propose and run return a
rendered text. Send it as-is. A restated number is an invented number the moment
it is wrong, and a paraphrased proposal that a human then accepts is an acceptance
of the paraphrase — which is why render exists and re-summarising does not.
3. n indexes the STORED proposal. The numbering the human was shown is the
numbering that binds them. Never renumber the questions yourself.
4. A refusal is a code, not a sentence. Every surface returns
{ok:false,error:{code,reason,detail?}}. reason is for the human; code is what
you branch on. Exit 2 is a typed refusal — a gate doing its job. Exit 1 is a defect.
The deterministic core
Two lookups, because reasoning about them is how they go wrong:
Both run from the workspace Parallax is reading, so the script is addressed by
its own location and never relatively. parallax status reports on the directory
you are standing in; cd-ing into the skill to reach the script would make the
skill directory the workspace, which fails silently by succeeding on the wrong
context.
SKILL="$HOME/.claude/skills/parallax" # a checkout instead: <repo>/skills/parallax
# what to do next, from the state on disk
parallax status --json | python3 "$SKILL/scripts/parallax_next.py" --status -
# next: parallax answer --proposal 94c50e77db74 --answer 1=<value>
# why: 1 blocking question(s) open, no answers recorded yet. ...
# what a refusal actually means
parallax run --json 2>err.json || python3 "$SKILL/scripts/parallax_next.py" --error err.json
# NO_ACCEPTED_ONTOLOGY
# Nothing has been accepted in this workspace. Accept a proposal first.
next comes back null when no Parallax command applies -- an unreadable
workspace is the case -- rather than naming the command that produced the result.
--json on either for a machine-readable answer. Exit 0 determined, 3 the
document parsed but names no state or code on file, 2 the input is not that
document.
scripts/parallax_next.py carries a remedy for all 46
error codes, and the set is pinned against
references/error-codes.txt — captured from
src/tools/errors.ts at a named commit. A code added upstream with no remedy here
goes red rather than being answered with an invented one.
The two refusals that look like bugs and are not
RECONCILIATION_UNACKNOWLEDGED on accept. The executable domain ignores fields
the human was shown as read from their own context. Tell them first — relay
detail.unmappedFromContext — and only then retry with --acknowledge-unmapped.
Setting the flag without telling them defeats the gate, which is the product.
An empty workspace that should not be empty. 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 does not fail; it reads as "your workspace
has nothing in it". Do not retry with a path you computed. There is no path argument
on the tool surface for exactly this reason — --root is CLI-only, where the person
typing the path is the confinement.
What Parallax will not tell you
Nothing here is calibrated against a real business. There are no transcripts. The README, the Spanish product description and the landing page all say so, and it should stay said. If you need an accuracy number, Parallax deliberately does not publish one — determinism is what it offers instead.
An MCP server is not built. The seam is cut and reads finished — toolSpecs()
hands out every tool as data, and a test asserts every tool name is legal as an MCP
tool name — but only the transport is missing. Do not tell anyone to connect over MCP.
Contributing to Parallax itself
Read AGENTS.md in the
repo. The short version: three gates, in order — bun test, bun run typecheck
(bun test does not typecheck), bun run mutants (a passing suite is not a
testing suite). And origin pushes to two remotes and is silently partial;
verify both shas after every push.
Tests
Run from the skill's own directory. The path below is relative on purpose, because
this skill has three homes and only a relative path is correct in all of them:
skills/parallax/ in broomva/parallax (canonical), skills/tooling/parallax/ in
broomva/skills (vendored), and ~/.claude/skills/parallax/ once installed.
python3 -m pytest tests/ -q # 44 passed (37 hermetic + 7 live-CLI)
The 7 live-CLI tests skip unless parallax is on PATH (bun link in a parallax
checkout). A run reporting 37 passed, 7 skipped is the expected result without it;
44 passed means the remedy table was checked against the real binary.
tests/test_skill_md_claims.py asserts the path and the count above are true, because
this section was wrong when it shipped: it named skills/tooling/parallax/tests/,
which does not resolve from an install, and said 29 when there were 36. A documented
command a reader copies and cannot run teaches them the skill is broken -- and a
skill with three homes is exactly where an absolute path rots first.