Skip to content
docs
Arxo ↗

Data flow, storage, and retention

For LLMs10 sections

Trace every byte a question touches: where questions, facts, documents, drafts, and answers travel, where each copy rests, who can read it, and when it disappears.

Component law-mcp (MCP host; default profile all, default --port 8722, default bind 127.0.0.1). Scenario: one tools/call round trip over stdio or HTTP, plus the optional Lens publication and neural-search legs. All case names, hashes, and hostnames below are synthetic examples.

BranchCoverage in this article
MCP (law-mcp-server)full data-flow trace incl. answers/neural/OTLP legs
law servenot covered — serve state is the world + the shared decision journal, see Deploy a private HTTP service, Backup and restore
  • The server root (--root / LAW_MCP_ROOT, real), or a checkout the server can find by its two markers — the profiles directory and the compiled-corpus directory (real root discovery).
  • Know which legs are enabled: HTTP (--http), call journal (on by default, LAW_MCP_LOG=0 disables), OTLP (LAW_MCP_OTLP_URL), Lens answers (LAW_ANSWERS_URL + LAW_ANSWERS_TOKEN), neural appendix (LAW_NEURAL_URL + LAW_NEURAL_TOKEN). All real (law-mcp/src/main.rs, publication.rs, reference/search.rs).
  1. Send the question. Over stdio the request is one JSON line on stdin; over HTTP it is POST /mcp with a single JSON-RPC object (real: batches are refused, http.rs). The request carries the tool name, the predicate with arguments, and the case facts with provenance.
  2. The server reads the corpus and the case. Reads are confined to the root: a case package <root>/<package>/ must contain law.toml (real, execution.rs), and a path that canonicalizes outside the root is refused (case_ask.outside_root, real). Symlink escapes are refused, not followed.
  3. The engine evaluates in memory. Pure evaluation writes no files: the answer document (statuses, applied rules, provenance hashes programHash/caseHash/resultHash/codeHash) is built in the call thread and returned in the response (real). The writing tool classes — editing (case packages), drafting (author-tree files) — are the exception, listed in the effects matrix.
  4. Optional leg A — call journal. On the HTTP transport only (stdio never journals, real, reqlog.rs), each call’s primary record goes to the journald native socket /run/systemd/journal/socket, or — if the socket is absent — as one JSON line to stderr (real, journal.rs). A late-finishing call adds a second record for the same call id (OVERRUN_FINISHED), so journal reads correlate by id rather than counting lines (see Logs, audit, decision journals). Bodies are capped at 64 KiB per side by default (LAW_MCP_LOG_MAX_BODY, 0 uncaps, real).
  5. Optional leg B — Lens answers. The four publication tools (law_capture_answer, law_prepare_answer, law_publish_answer, law_revoke_answer, real, publication.rs) forward to POST {LAW_ANSWERS_URL}/internal/mcp/call with schema law.answers.mcp-call/0.1 (real). Without LAW_ANSWERS_URL and a non-empty token the tools answer ANSWERS_UNAVAILABLE and nothing leaves the host.
  6. Optional leg C — neural appendix. law_search posts the query text plus the lexical taken candidates (kind/id/point, no case facts) to {LAW_NEURAL_URL}/v1/appendix (real, reference/search.rs). Without LAW_NEURAL_URL the leg reports active:false and the answer stays purely lexical.
  7. Optional export — --export-neural-input PATH writes the search index input document to exactly that path (real, main.rs). Together with the editing/drafting tool writes and per-call temp workbench files, this is the complete file-write surface — see the effects matrix for the authoritative list.
  • The client holds the full answer document in the response. The MCP host has no separate persistent answer store and keeps no long-lived response object after the call returns (real) — but with journaling on (the HTTP default), request and response bodies, including case facts, are persisted in the log infrastructure (journald, stderr capture, or the external collector) and must be handled as case data: same access rights, retention, backup, and cleanup rules as the case directory itself. Audit the whole deployment, not just the absence of a database inside the process.
  • Case inputs live where the operator put them and where the deployment copies them: the case directory (law.toml, law.lock, queries/*.json, .lawcase files), the client that sent the facts, and — when journaling is on — the journal records described below.
  • Journal records live in the host journal (journald) or the process stderr stream — see article 09 for fields and redaction.
  • A prepared Lens document is a non-public frozen document held by the answers service for 24 hours (reference behavior of law_prepare_answer); publishing returns a URL plus a separate revoke secret, and revoking closes HTML, API, and download. The MCP host stores none of these.
  • With the journal on stderr, one successful call produces one primary JSON line containing MCP_TOOL and MCP_CALL_ID (real field names). Line count is NOT a call count: a late-finishing computation may add an OVERRUN_FINISHED record for the same call, a journal-write failure can drop a record while the call itself succeeds, an oversized datagram is resent truncated (content lost, record survives), and transport failures produce no call record at all. Correlate by MCP_CALL_ID and event type; see article 09. The guarantees of the MCP call log and the law serve decision journal differ — do not treat either as a backup or as an audit-completeness proof on its own.
  • With LAW_ANSWERS_URL unset, law_prepare_answer returns isError:true with code ANSWERS_UNAVAILABLE (real).
  • With LAW_NEURAL_URL unset, law_search carries neural.active:false (real).
  • GET /healthz is never journaled (real, reqlog.rs): the balancer’s polls leave no case-data trace.
  • 413 on POST /mcp: request body over the 1 MiB implementation limit (MAX_BODY, real). Reduce the request only in ways that preserve its meaning: use the supported references to a prepared case, remove genuinely redundant content, or split independent objects. Do NOT split one logically connected case into separate computations without established equivalence: A ∧ B → C evaluated on {A, B} yields C, while two separate runs on {A} and {B} may not — and in models with exceptions and conflicts the effect of splitting is even less obvious.
  • ANSWERS_TOO_LARGE: Lens response over 32 MiB (real, publication.rs). BODY_SIZE from other services: response over 4 MiB (real, service_client.rs).
  • SERVICE_CONFIG with https_required: a service URL that is not HTTPS to a non-loopback host (real). Fix the URL; there is no bypass flag.
  • Oversized journal datagram: resent without bodies with MCP_TRUNCATED=oversize (real). The record survives; the content does not — do not treat the journal as a backup.
  • Retention of journal records is the host’s policy (journald MaxRetentionSec, log rotation): the server sets no TTL and runs no cleanup job. Retention of prepared/published Lens documents is the answers service’s behavior (24 hours frozen, explicit revoke).
  • Backups cover the inputs the operator owns: case directories, law.toml/law.lock, and the journal archive. There is no server database to dump.
  • The MCP host never deletes caller data: cleanup means the operator’s retention policy plus law_revoke_answer for published documents.
  • “Secure” in this article means only: confined reads (root containment), no silent copies (stdio journals nothing; each network leg needs an explicit URL + token), and named storage places. It does not mean encryption at rest — that is the host’s disk policy.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.