Prepare and publish an answer
Answering happens in the conversation. Publishing moves an answer out of it: first into a frozen, checkable document, then optionally to a public address that can later be withdrawn. This page describes the mechanism, separates what was observed from what was not run, and distinguishes three states that are easy to confuse: holding an answer, holding a prepared document, and publishing.
Publication is a separate permission. Never call a publication tool as a side effect of answering; run it only when the user asked for a frozen or public document, and enforce that check in your application code (Safety and reliability).
The flow
Section titled “The flow”ask (law_ask) --> capture (law_capture_answer) --> prepare (law_prepare_answer) | v frozen document (24 h, not public) | v publish (law_publish_answer) | v public address + separate revoke secret | v revoke (law_revoke_answer) closes page, API and downloadPreparing re-executes rather than trusts: it repeats the earlier calls through the engine and compares every input, the law, the resources, the tool code and the result; any mismatch rejects the whole document. That behavior is contract-described. No prepare succeeded in this track, so the re-execution itself is NOT RUN. The step order and tool names are verified surfaces.
The four publication tools are a thin proxy to an external answers service.
Without its address and token (LAW_ANSWERS_URL, LAW_ANSWERS_TOKEN) they
refuse before reading any argument, and nothing leaves the host.
Before planning: the capabilities matrix
Section titled “Before planning: the capabilities matrix”law_answer_capabilities reports which producers can be captured and
prepared. Observed (schema law.answers.capabilities/0.2): 9 supported, 3
declared_only (a schema without an executable witness, so capture cannot
succeed).
| Producer id | Status |
|---|---|
law_ask | supported |
law_case_ask | supported |
law_process_run | supported |
law_editions | supported |
law_argue | supported |
law_amend | supported |
law_draft_impact | supported |
law_finit | supported |
law_loophole | supported |
compliance | declared_only |
law_query | declared_only |
rule_application | declared_only |
Producer support is about capture and preparation, not about the same-named
tool running. In this track law_argue is a supported producer while the
law_argue call itself refused on a world merge, and the structural
law query command runs while law_query is a declared-only producer. Do not
carry support in either direction.
Failure codes
Section titled “Failure codes”The refusal observed on capture and prepare (server locale Russian):
Ошибка: Arxo Answers не настроен: нужны LAW_ANSWERS_URL и LAW_ANSWERS_TOKEN;обычный law_ask продолжает работать без нихwith isError: true and structured code ANSWERS_UNAVAILABLE. The shipped
English string reads: “Arxo Answers is not configured: LAW_ANSWERS_URL and
LAW_ANSWERS_TOKEN are required; an ordinary law_ask keeps working without
them”.
| Code | Meaning | Status |
|---|---|---|
ANSWERS_UNAVAILABLE | Service address or token not configured | Observed |
ANSWERS_BAD_RESPONSE | The service answered with an invalid result | Read in the server source |
ANSWERS_TOO_LARGE | The analysis exceeds the transfer limit; split it across publications | Read in the server source |
On ANSWERS_UNAVAILABLE, stop the publication plan and name the missing
setting; retrying does not help.
The threaded sequence
Section titled “The threaded sequence”The refusal reached step by step, each request carrying the control values
really returned by the previous step. The case is the feeding-break question
over kz-labour-code (Labour Code of the Republic of Kazakhstan): one child,
a 25-minute break, legalTime 2026-09-01.
Step 0: ask. law_ask returns COMPUTED / TRUE_ONLY through the
one-child rule. The response carries a replay control (schema
law.answers.replay-control/0.1) that later steps echo back: the request as
asked, the checks caseHash, codeHash, programHash, resultHash,
rustCodeHash (equal to codeHash here), and responseHash:
caseHash sha256:d6ae0115bc6d23dda0734a4be87d055d2925a2624f397580c0cf94ae54389251codeHash sha256:5a64bffcce72767c5db83b46698ef11f3bac835c9e71455f82964e3993dd5038programHash sha256:09de6428d383eeea903d37a1a1aba64c46d7c868089d57cd37df9ee7b02b8302resultHash sha256:cf19c47a357f480896d7715f1583ed3bee4fa4c7bec389e96e4dc009478ec5d9responseHash sha256:75fc31d73d336c0369b6b7e6fba1e9dfac14c85c5191226e9e863d35d7091b21codeHash moves with the engine build; resultHash stays.
Step 1: capture. The request threads the producer id and the request echoed from step 0:
{"producer": "law_ask", "request": {"kind": "truth", "package": "kz-labour-code", "predicate": "feeding_break_too_short", "args": ["urn:kz:tk:employee:1", "urn:kz:tk:employer:1"], "facts": [ {"predicate": "children_under_eighteen_months", "args": ["urn:kz:tk:employee:1", 1]}, {"predicate": "child_feeding_break_minutes", "args": ["urn:kz:tk:employee:1", "urn:kz:tk:employer:1", 25]}], "legalTime": "2026-09-01"}}Observed: isError: true, ANSWERS_UNAVAILABLE. No producer control was
issued.
Step 2: prepare. The request carries the human question and one
calculation holding the same request and the full replayControl from
step 0 (the wire call carries the request twice: beside the replay control and
inside it). Shape, with the objects abbreviated:
{"question": "Is the feeding break too short?", "calculations": [{"id": "c1", "label": "feeding break truth", "request": {"kind": "truth", "package": "kz-labour-code", "…": "as in step 1"}, "replayControl": {"schemaVersion": "law.answers.replay-control/0.1", "request": {"…": "as in step 1"}, "checks": {"caseHash": "…", "codeHash": "…", "programHash": "…", "resultHash": "…", "rustCodeHash": "…"}, "responseHash": "…"}}]}Status of this block: labeled shape; the sent call carried the full objects and the hash values printed in step 0. The runnable script below takes both objects from the step-0 response as objects, never retyped.
Observed: the identical refusal. No preparation id was issued.
Step 3: publish. NOT RUN. Publish takes a preparation id, and no prepare
call issued one. Blocker: the answers service is not configured.
Reproduction: set LAW_ANSWERS_URL and LAW_ANSWERS_TOKEN, rerun steps 1
and 2 to obtain a preparation id, then call law_publish_answer with it.
Step 4: revoke. NOT RUN. Revoke takes the public id and the separate
secret from a publish that never ran. Reproduction: after a successful
publish, call law_revoke_answer with the returned public id and revoke
secret.
Not run: the end-to-end path
Section titled “Not run: the end-to-end path”No prepare, publish or revoke call completed in this track. Capture and prepare ran and refused with real threaded values; publish and revoke never ran. What follows is the tool contract, not an observed run.
- Prepare takes 1 to 12 earlier
law_askcalls, or 1 to 12 producer runs (the two inputs exclude each other), each with its request and replay control, plus the human question and an optional assistant summary and explanations tied to calculation ids. It returns a preparation id for a frozen, non-public document held for 24 hours. - Publish takes a preparation id and returns a public address. Repeating it with the same id returns the same address and the same revoke secret instead of creating a second document.
- Revoke takes the public id and the separate secret (the public id alone carries no control rights) and closes the page, the API and the download.
This track therefore makes no claim about preparation latency, link formats, access control on the preparation link, or how a published page renders.
Three states, three promises
Section titled “Three states, three promises”| State | How it arises | Who can read it | What it promises |
|---|---|---|---|
| Held answer | A computed answer in the conversation, with proof and hashes | Conversation participants | The bytes reproduce from the recorded inputs; nothing about access or permanence |
| Prepared document | law_prepare_answer succeeds; a preparation id exists | Whoever holds the preparation link, for 24 hours | Frozen inputs and re-executed results; explicitly non-public |
| Published page | law_publish_answer succeeds; a public address exists | Anyone with the address, until revoked | A stable public address; withdrawal via the revoke secret |
Confusing these states is the main publishing failure. A hash is not access: identical hashes say the computation reproduces, not who can read it. Engine determinism does not mean two agents write the same summary. And a prepared document is not published: it expires, and it was never public.
How to verify
Section titled “How to verify”// 1. MCP: the support matrix. Only supported producers can be captured.{"tool": "law_answer_capabilities", "arguments": {}}// 2. MCP: ask first; the response carries the replay control.{"tool": "law_ask", "arguments": {"package": "kz-labour-code", "predicate": "feeding_break_too_short", "args": ["urn:kz:tk:employee:1", "urn:kz:tk:employer:1"], "facts": [ {"predicate": "children_under_eighteen_months", "args": ["urn:kz:tk:employee:1", 1]}, {"predicate": "child_feeding_break_minutes", "args": ["urn:kz:tk:employee:1", "urn:kz:tk:employer:1", 25]}], "legalTime": "2026-09-01"}}- Runnable:
examples/publishing/thread_sequence.pythreads the step-0 objects into capture and prepare. It needs a source checkout with the prebuilt MCP server binary (standard library only; it finds the checkout root from its own location). From this section’s directory:
LAW_MCP_PROFILE=kz python3 examples/publishing/thread_sequence.pyExpected without the answers service: step 0 COMPUTED / TRUE_ONLY; steps
1 and 2 isError: true with ANSWERS_UNAVAILABLE; steps 3 and 4 NOT RUN with
the reason taken from the prepare response. On a configured service a
successful prepare prints its preparation id, and the script still stops
there.
// 4. NOT RUN: publish needs a preparation id no prepare call issued.{"tool": "law_publish_answer", "arguments": {"preparationId": "(from a successful prepare)"}}For connection setup, see the MCP connection guide.
Validation record
Section titled “Validation record”Show commands, versions and results
Transport: the session’s harnessed MCP tools were unusable (“MCP stdio
connection is closed”), so the evidence below came from direct stdio to the
prebuilt local MCP server with profile kz (server arxo-law-kz 0.1.0,
canon kz.sources/2026.2.0). Commit not recorded.
| Step | Date | Result |
|---|---|---|
tools/list | 2026-10-03 | The four publication tools present |
| Step 0 ask | 2026-10-03 | COMPUTED / TRUE_ONLY, resultHash sha256:cf19c47a… |
| Capabilities matrix | 2026-10-03 | 9 supported, 3 declared_only, as tabled |
| Capture, full step-0 request | 2026-10-03 | isError: true, ANSWERS_UNAVAILABLE |
| Prepare, full request and replay control | 2026-10-03 | Identical refusal, no preparation id |
Threaded rerun (thread_sequence.py) | 2026-10-04 | Step 0 same resultHash (newer codeHash); steps 1 and 2 ANSWERS_UNAVAILABLE; steps 3 and 4 NOT RUN |
| Publish, revoke | — | NOT RUN: answers service unconfigured |
ANSWERS_BAD_RESPONSE, ANSWERS_TOO_LARGE, refusal strings (ru, en) | — | Read in the server source and shipped strings |
law_argue refusal (producer/tool asymmetry) | 2026-10-03 | Ошибка: мир не слить: пакет "calc-obligations" объявляет семантику 0.2.2, остальные — 0.2.4 (isError, no structured code) |
Previous: Safety and reliability Next: Evaluate, debug and upgrade
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.