Quickstart
In about ten minutes you will have a cryptographically verified release of a regulatory corpus running on your machine, answering a legal question with citations — and you will have watched it refuse a tampered copy of the same release, which is the part worth trusting.
Everything after the install runs offline. That is not a convenience: an artifact you can only check by asking a server whether it is genuine is an artifact you cannot check.
Before you start
Section titled “Before you start”You need git and uv, which manages the Python
3.12 toolchain the engine is built on. Everything else the commands below
install for you.
git --versionuv --version1. Install the CLI
Section titled “1. Install the CLI”git clone https://github.com/openregs/openregs.gitcd openregsuv syncexport PATH="$PWD/.venv/bin:$PATH"openregs --versionopenregs 0.1.0schema majors: 1commit: b053990a6e17cc9e7ac291e33ce379eee1103fd4The commit is not decoration. A release is a function of the commit that built it, and the CLI prints which one it is so that any artifact it produces can be traced back to the exact source that produced it.
2. Pull a release
Section titled “2. Pull a release”openregs pull copies a release out of a registry and verifies the whole chain
over the copied bytes before any of them reach the target directory. A
release that fails a check is never unpacked.
openregs pull fixreg@2025.04 --from fixtures/registry --into ~/openregs-releasespull: fixreg@2025.04 from fixtures/registry/fixreg/2025.04 staged 21 file(s), 246770 byte(s) -> ~/openregs-releases/.pull-fixreg-2025.04.partialverify: fixreg@2025.04 — ~/openregs-releases/.pull-fixreg-2025.04.partial signature valid 8 blob signature(s) under ecdsa-p256-sha256 key 24f0b959505940ce trust anchored signing key openregs-fixture (24f0b959505940ce) and log key openregs-local-log (02801639ad00ebed) are in config/trust.yaml digests valid 5 artifact digest(s) and 8 signed blob(s) recomputed from the bytes on disk provenance complete 2025.04 -> 0f466185ac19 -> run fixture-registry-1 -> 3/3 snapshot(s), every hash the corpus cites is in the attested SOURCES.lock log inclusion proven 8 entries proven in openregs.io/fixreg at tree size 8, root f94e81357156a4ff verified 5 of 5 check(s) passed unpack -> ~/openregs-releases/fixreg/2025.04 artifacts corpus.sqlite, graph.idx, embeddings.parquet, constants-src, 2025.04.diff.jsonpull: OK — fixreg@2025.04 verified, then unpacked into ~/openregs-releases/fixreg/2025.04Five independent checks, and each one answers a different question:
| Check | The question it answers |
|---|---|
| signature | were these exact bytes signed? |
| trust | by a key this checkout was told to accept, rather than one the release brought along? |
| digests | does every artifact match the digest its own manifest declares? |
| provenance | does the tag lead to a commit, to a CI run, to the source snapshots the corpus cites? |
| log inclusion | is each signature in the transparency log — proven against the checkpoint bundled in the release, so no network is needed? |
Long paths above are shortened for reading; the real output prints them in full.
3. Watch it refuse a tampered release
Section titled “3. Watch it refuse a tampered release”The repository also ships the same release with one byte of corpus.sqlite
changed. Nothing else about it is different — same layout, same manifest, same
attestation directory.
openregs pull fixreg@2025.04 --from fixtures/registry-tampered --into /tmp/openregs-tamperedecho "exit: $?" FAIL corpus.sqlite: signature does not verify under key 24f0b959505940ce — the bytes, the signature or the key is not the one this release was signed with FAIL corpus.sqlite: sha256 is 81ded414141632a7268bbb57a87bde59f3deb97144b52683aabab36c16e3c0a3, release-meta.yaml says 76032f7f0f665de313ed0c261bb5bdfdc48359e03a8c1c58cbba615f89912758 FAIL corpus.sqlite: sha256 is 81ded414141632a7268bbb57a87bde59f3deb97144b52683aabab36c16e3c0a3, signature-manifest.yaml says 76032f7f0f665de313ed0c261bb5bdfdc48359e03a8c1c58cbba615f89912758pull: FAIL — fixreg@2025.04: 3 problem(s); nothing was unpacked and /tmp/openregs-tampered/fixreg/2025.04 was not writtenexit: 1Exit code 1, three named problems, and — the part that matters —
/tmp/openregs-tampered/ is empty. The staging directory was removed and
the target was left exactly as it was. Verification that runs after unpacking
is verification you have already lost.
4. Serve it
Section titled “4. Serve it”openregs serve \ --release fixreg@2025.04 \ --path ~/openregs-releases/fixreg/2025.04 \ --port 8787serve: bound http://127.0.0.1:8787 — GET /healthz answers now; GET /readyz is 503 until a verified release is loadedserve: verify fixreg@2025.04 — ~/openregs-releases/fixreg/2025.04 signature valid 8 blob signature(s) under ecdsa-p256-sha256 key 24f0b959505940ce ...serve: verified — 5 of 5 check(s) passed before the listener openedserve: fixreg@2025.04 (commit 0f466185ac19) loaded, 55 unitsserve: listening on http://127.0.0.1:8787serve: POST /v1/answer, POST /v1/unit, GET /readyz, GET /healthz, GET /metricsThe release is verified again, from scratch, before the socket serves
anything. /healthz answers from boot so a load balancer can see the process;
/readyz stays 503 until a release has passed all five checks.
5. Ask it a question
Section titled “5. Ask it a question”In another terminal:
curl -s -X POST http://127.0.0.1:8787/v1/answer \ -H 'content-type: application/json' \ -d '{"query":"When must an operator report an incident?","as_of":"2025-06-01"}' \ | python3 -c "import json, sysanswer = json.load(sys.stdin)print(answer['verification'], answer['release'], answer['as_of'])for block in answer['blocks'][:3]: text = ' '.join(block['text'].split()) print(' ', block['citation']['eid'].ljust(16), text[:88])"verified fixreg@2025.04 2025-06-01 art_5__para_3 Operators must report to the authority any incident causing losses above EUR 10000. art_5 This Article applies to operators providing one or more critical services. Operators mus art_2__point_2 'incident' means an event that compromises the availability, integrity or confidentialitThree things came back that a search engine would not have given you:
- The provision that answers the question,
art_5__para_3, with the release tag and the date it was answered for stamped on the citation. - The article it sits in, so the answer is not a sentence torn out of its context.
- The definition of “incident” from Article 2, because the answer uses the word and the definition was in force on that date.
Drop the python3 filter to see the whole response: the retrieval closure, the
obligation atoms, whatever amends or disapplies each unit, and the disclaimer
the server carries on every answer. The full response also reports applicability
honestly:
FIXREG-Art5.2-Ob1 -> art_9 | retrieved: FalseFIXREG-Art5.3-Ob1 -> art_5__para_3 | retrieved: TrueThe first line is the engine volunteering that Article 9 disapplies the register-keeping duty for small operators — a duty that did not match the query, reported anyway because whether it binds you is a question no retrieval score can answer. Name an entity profile in the request and it decides instead of reporting.
6. The same question, five months earlier
Section titled “6. The same question, five months earlier”for date in 2025-01-15 2025-06-01; do curl -s -X POST http://127.0.0.1:8787/v1/answer \ -H 'content-type: application/json' \ -d "{\"query\":\"incident reporting threshold\",\"as_of\":\"$date\"}" \ | python3 -c "import json, sysanswer = json.load(sys.stdin)text = next(b['text'] for b in answer['blocks'] if b['citation']['eid'] == 'art_5__para_3')print(answer['as_of'], ' ', text)"done2025-01-15 Operators must report to the authority any incident causing losses above EUR 5000.2025-06-01 Operators must report to the authority any incident causing losses above EUR 10000.One release, two dates, two different laws — because an amending act changed the threshold on 2025-03-01 and the release carries every version of every unit with the window it was in force for. This is what “point-in-time” means in practice, and it is the question compliance actually asks: not what does the law say but what did the law say on the day the thing happened.
7. Point an agent at it
Section titled “7. Point an agent at it”The same release, the same verification, over the Model Context Protocol on stdin/stdout:
openregs serve --mcp \ --release fixreg@2025.04 \ --path ~/openregs-releases/fixreg/2025.04It exposes five tools — search_regulation, get_unit, get_atom,
diff_releases and list_obligations — over the same retrieval closure the
REST door serves, so an agent’s answers carry the same citations. In an MCP
client’s configuration that is:
{ "mcpServers": { "openregs": { "command": "openregs", "args": [ "serve", "--mcp", "--release", "fixreg@2025.04", "--path", "/absolute/path/to/openregs-releases/fixreg/2025.04" ] } }}Use an absolute path: an MCP client does not start the server in your shell’s working directory.
What you just proved
Section titled “What you just proved”You verified a signed corpus offline, watched a single flipped byte be caught and refused, and got two different legally correct answers to the same question because you asked about two different dates. No step trusted a server, and no answer arrived without a citation.
Where to go next
Section titled “Where to go next”- What OpenRegs is — the six planes, end to end.
- CLI reference — every command and flag, generated from the engine’s own parser.
- Building a release and signing it — the other side of what you just pulled.
- GitHub Actions — how a repository that pins a release finds out a new one exists.
- How to contribute — one obligation atom, from the text it is read out of to the release that ships it.