Skip to content

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.

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.

stepmeasured
git clone https://github.com/openregs/openregsnot measured — the pack is 7.8 MiB, so it is a network-speed question, not a CPU one
uv sync in the engine checkoutnot 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 2000.59–0.73 s, three consecutive runs
the client installed from the repository and the first answer printed7.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 printednot 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.

Terminal window
git clone https://github.com/openregs/openregs
cd openregs
uv sync
.venv/bin/openregs serve --release fixreg@2025.04 --host 127.0.0.1 --port 8080 \
--as-of 2025-06-01

The 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 in
serve: fixreg@2025.04 (commit 0f466185ac19) loaded, 55 units
serve: listening on http://127.0.0.1:8080
serve: 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.

Terminal window
mkdir quickstart && cd quickstart
npm init -y && npm pkg set type=module
npm install @openregs/client

That 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.

quickstart.ts
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:

Terminal window
node quickstart.ts

The 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-01

That 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.

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_value is 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.

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.

  • as_of and release are 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.disclaimer is not boilerplate to strip. It is read from the project’s DISCLAIMER.md at startup and scoped to the release tag. Show it.
  • verification says what was checked before the release was served. verified is the full signature chain; digests is an unsigned local build (which is what the checkout’s own FIXREG release is); skipped means an operator passed --insecure-skip-verify and nothing was checked.
  • answer.corpus.release_meta_sha256 names 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, and verifyAnswerCommand() builds the openregs verify --answer invocation that checks a saved answer against a release you pulled yourself. This package builds that command and never runs it.
  • 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.