Answer with citations from TypeScript
The goal of this page is one printed sentence of regulation with the provision, the release and the date it was read for attached to it.
How long this actually takes
Section titled “How long this actually takes”The honest answer depends entirely on whether you already have the engine, because this package is a client and a client needs something to talk to.
Every figure below was timed by hand, on one developer machine, in one sitting,
against the fixture corpus, with git and Node already installed. They describe
the shape of the wait and nothing more: they are not a benchmark, and they are
not a promise about your machine or your connection. Where nobody has a number,
the row says so rather than carrying one.
| step | measured |
|---|---|
git clone https://github.com/openregs/openregs | not measured — the pack is 7.8 MiB, so it is a network-speed question, not a CPU one |
uv sync in the engine checkout | not measured. It resolves and installs six Python dependencies (lxml and pyarrow are the large ones); on a warm uv cache it is seconds, on a cold one it is a download |
openregs serve --release fixreg@2025.04 — process start to /readyz 200 | 0.59–0.73 s, three consecutive runs |
| the client installed from the repository and the first answer printed | 7.4–7.7 s, three consecutive runs, each in a directory that did not exist a moment earlier |
npm install @openregs/client and the first answer printed | not measurable — the package is not published, so that command cannot run |
Most of those seven seconds are the clone and the TypeScript build that a git install does on the way in, which is work a published package would arrive without. So: under ten seconds once the engine is on the machine, and otherwise however long a clone and a Python dependency install take on your connection — which is the part this page cannot promise. Nothing here needs an account, a key, or a network call beyond those downloads.
The corpus you will query is FIXREG, the engine’s fixture regulation. It is a synthetic act written for testing, it carries no real law, and it is derived from no real publication. It is the right thing to learn on and the wrong thing to rely on.
1. Get something to talk to
Section titled “1. Get something to talk to”git clone https://github.com/openregs/openregscd openregsuv sync.venv/bin/openregs serve --release fixreg@2025.04 --host 127.0.0.1 --port 8080 \ --as-of 2025-06-01The releases are committed in that repository, so there is nothing to build. The server verifies the release before it binds, and says what it checked:
serve: verify fixreg@2025.04 — …/regimes/fixreg/releases/2025.04 digests valid every artifact hashes to what release-meta.yaml declares provenance checked the bundle names the regime, tag and commit of the directory it sits inserve: fixreg@2025.04 (commit 0f466185ac19) loaded, 55 unitsserve: listening on http://127.0.0.1:8080serve: POST /v1/answer, POST /v1/unit, GET /readyz, GET /healthz, GET /metrics--as-of 2025-06-01 pins the date a request that states none is answered for. It
is the one value in a response that is not a function of the release and the
request, so pinning it makes every response reproducible — useful while you are
learning, and how the tests in the client repository are run.
That instance is anonymous because the checkout’s config/serving.yaml says so,
and it says so in a warning at startup. A deployment serving real regulation sets
auth.anonymous: false and lists its tokens.
2. Ask it something
Section titled “2. Ask it something”mkdir quickstart && cd quickstartnpm init -y && npm pkg set type=modulenpm install @openregs/clientThat third line is the one that 404s today. Substitute npm install github:openregs/openregs-js, per the notice at the top of this page; nothing else
on this page changes.
import { OpenRegsClient, citations, formatCitation } from "@openregs/client";
const client = new OpenRegsClient({ baseUrl: "http://127.0.0.1:8080" });const answer = await client.searchRegulation({ query: "do operators have to keep a register of critical services", as_of: "2025-06-01",});for (const citation of citations(answer)) { console.log(`[${citation.role}] ${citation.text}\n ${formatCitation(citation)}\n`);}console.log(answer.disclaimer.text);Nine lines to an answer with citations. Run it — Node 22.6+ runs TypeScript directly, or compile it first:
node quickstart.tsThe first four of the eleven blocks that come back, verbatim:
[match] Operators must maintain a register of critical services and review it at least annually. art_5__para_2 — fixreg@2025.04, as of 2025-06-01
[ancestor] This Article applies to operators providing one or more critical services.Operators must maintain a register of critical services and review it at least annually.Operators must report to the authority any incident causing losses above EUR 10000. art_5 — fixreg@2025.04, as of 2025-06-01
[definition] 'critical service' means a service the disruption of which would have a significant effect on the provision of financial services in the Union. art_2__point_3 — fixreg@2025.04, as of 2025-06-01
[definition] 'operator' means a natural or legal person that provides a critical service in the Union; art_2__point_1 — fixreg@2025.04, as of 2025-06-01That is the retrieval closure, not a search result: the units that matched, the
article each sits in, the definitions in force on that date, and whatever amends
or disapplies them — including [derogation] Article 5(2) shall not apply to small operators., which is the block that changes the answer and which a keyword search
would not have returned.
3. Read a provision as of a date
Section titled “3. Read a provision as of a date”A provision only means something on a day.
const before = await client.getUnit({ eid: "art_5__para_3", as_of: "2025-01-15" });const after = await client.getUnit({ eid: "art_5__para_3", as_of: "2025-06-01" });console.log(before.unit?.own_text);console.log(after.unit?.own_text);Operators must report to the authority any incident causing losses above EUR 5000.Operators must report to the authority any incident causing losses above EUR 10000.One release, one eId, two dates, two texts — because an amending act took effect
in between and the release holds both versions. unit.versions lists them with
the window each was in force for, and unit.atoms carries the obligation written
into the provision, including its executable constant:
after.atoms[0].executable_name; // "incident_report_threshold_eur"after.atoms[0].executable_value; // 10000
executable_valueis served but is not declared in the OpenAPI document, so the generated types do not have it and you will need a cast to reach it. That is engine drift, not a client limitation; see the drift this package has found.
4. Point an agent at it instead
Section titled “4. Point an agent at it instead”If what you are building is an agent, the client is not the interesting half. The same engine speaks MCP over the same release:
import { mcpServerConfig } from "@openregs/client";console.log(JSON.stringify(mcpServerConfig({ release: "fixreg@2025.04" }), null, 2));{ "mcpServers": { "openregs": { "command": "openregs", "args": [ "serve", "--mcp", "--release", "fixreg@2025.04" ] } }}Merge that into your MCP client’s configuration. It exposes five tools;
search_regulation, get_unit, get_atom and list_obligations are the same
four calls this package makes over REST. diff_releases is the fifth and has no
REST route by decision — mcpOnlyTools().diff_releases is the document’s own
statement of why — so MCP, or openregs diff over the release itself, is how it
is reached. Point an agent at the law is the same server from the
client’s side: what the tools return, and why quoted law arrives wrapped in a
notice.
examples/agent-context.ts
shows the other shape: using the REST client and handing a model a context block
where every passage carries the identifier it came from.
What to hold on to
Section titled “What to hold on to”as_ofandreleaseare echoed on every response. An answer that does not say which corpus and which day it is about is not an answer you can check later.- Every block carries a citation. Never show a sentence without it, and never hand a model regulation without it either.
answer.disclaimeris not boilerplate to strip. It is read from the project’sDISCLAIMER.mdat startup and scoped to the release tag. Show it.verificationsays what was checked before the release was served.verifiedis the full signature chain;digestsis an unsigned local build (which is what the checkout’s own FIXREG release is);skippedmeans an operator passed--insecure-skip-verifyand nothing was checked.answer.corpus.release_meta_sha256names the bytes, where the tag only names the release. Keep it beside anything you store:sameCorpus(a, b)tells you two answers were read out of one corpus, andverifyAnswerCommand()builds theopenregs verify --answerinvocation that checks a saved answer against a release you pulled yourself. This package builds that command and never runs it.
Where to go next
Section titled “Where to go next”- Quickstart — the CLI side: pull a release, watch a tampered copy be refused, serve it.
- MCP setup — the same five tools wired into an agent.
- API reference — every route this client calls, generated from the server’s own route table.
- Glossary — eId, atom, expression, closure, as-of.
- The client repository — the source, the examples this page quotes, and how to report drift like the one in step 3.