Markdown for LLMs
Offline: install once, run with no network
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Offline: install once, run with no network
The deadline example evaluates locally: after install, the run path uses
no network. This page splits install from run, names the artifacts the
run needs, says what `offline: true` means, and shows how to confirm the
split — plus what still needs the network.
```text
docs/build/examples/deadline-app
```
## Install versus run
Two phases, different needs:
- **Install** needs the network: `npm ci` fetches `@arxo/law 0.3.3` and
`@arxo/canon-bgb-fristen 0.1.5`, and the Python setup fetches `arxo
0.2.0`. This phase runs once per machine (and again on upgrades).
- **Run** needs no network: opening the pinned model, validating forms,
evaluating, reading answers, storing and replaying captures. Every
answer on this path reports `via` as `local`.
Keep the phases apart in procedure as well as in prose: install with the
network up, then evaluate with it down when you want proof.
## Artifacts the run needs
The run reads only what install placed on disk:
- `node_modules` with `@arxo/law` and `@arxo/canon-bgb-fristen`: the
facade, the canon bindings, and the engine files the canon package
ships, including its packed runtime (wasm) — no download at open
time.
- the example sources: `src/*`, `public/index.html`, and for the Python
path `python/deadline.py` with its installed `arxo` package.
- a writable directory for `captures/<id>.json`.
Nothing else is read at run time: no registry, no remote canon, no
assistant host, no serving endpoint.
## What `offline: true` means
The model opens this way:
```text
open('de.bgb.fristen@0.1.0', { offline: true })
```
The flag pins the call path to local execution: the engine runs the
installed canon build in-process and refuses any step that would reach
the network. An answer from this path carries `via: local`, the three
hashes, and the canonical document bytes — the same bytes on every run
of the same inputs within one SDK.
`offline: true` does not change the law: the collect value stays
`2026-03-20` with `COMPUTED`, the truth stays `TRUE_ONLY`, and the
wrong-date and partial cases stay `NEITHER`. It changes only where
execution happens and what it may touch.
## How to confirm no network
Two checks, both cheap:
1. **Read the run path.** From `src/server.ts` (or `src/cli.ts`)
through `validateFormInput`, `toFacts`, `toCaseInput`,
`buildCollect`, `buildTruth`, the `runtime` evaluate calls,
`read-result`, `toViewModel`, `capture`, and `replay`, there is no
registry call, no assistant-host call, and no serve call. The only
I/O is the HTTP listener, the form file, and the capture files.
2. **Run with the network disabled.** Disconnect the machine (or run
with loopback only) and evaluate the worked case: event
`2026-03-06`, `14` days, candidate `2026-03-20`, legal time
`2026-09-17`. Expect `COMPUTED` with `2026-03-20` and `TRUE_ONLY`,
then replay the stored capture and expect the replayed `result`
hash to match the recorded one. That comparison — result hashes,
not byte-equality of the two documents — is the whole replay
contract; the checksum step separately verifies the stored bytes.
If either check fails, the failure names the leak: a network call in
the path, or an answer that differs with the network down.
## What still needs the network
Per operation, not per slogan — the two explanation calls are
different operations with different network behavior:
| Operation | Network? | Why |
|---|---|---|
| `npm ci` and package installs | Yes | fetch the pinned SDK and canon packages |
| Python package installs | Yes | fetch `arxo` and the canon bindings |
| `explainWhy` = `model.whyNot(...)` | No | local blocker graph from the installed engine |
| `explainDraft` = `model.whyNot(...)` on a draft | No | same local call, fewer facts sent |
| `explain()` through an MCP host | Yes | runs through the host over the carried document |
| serving the canon over HTTP (`law serve`) | Yes | a network service by definition |
| TS serve mode / router | Yes | every question goes to the server |
The application never calls the network rows in its run path: the
only explanation calls it makes are `explainWhy` and
`explainDraft`, and both stay local — no case data leaves the
process through them. The full matrix, both SDKs, lives on the
compatibility page. Plan upgrades accordingly: bring the network up
for install, confirm the pins, then bring it down again before the
answers that must prove locality.
## Check your understanding
Predict before you open each answer.
<details>
<summary>Does `explainDraft` need the network?</summary>
No. It is the same local `model.whyNot(...)` call as `explainWhy`,
with fewer facts sent. Only `explain()` through an MCP host needs the
network.
</details>
<details>
<summary>With the network down, what does the worked case answer?</summary>
The same as with it up: collect computes `2026-03-20` with `COMPUTED`,
and truth on that date answers `TRUE_ONLY`. `offline: true` changes
where execution happens and what it may touch, not the law.
</details>
<details>
<summary>After an offline run, what does replay compare?</summary>
The replayed `result` hash with the recorded one — not byte equality
of two documents. The checksum step separately verifies the stored
bytes.
</details>
## Next
- [Questionnaire: fields to facts, then follow-ups](/build/patterns/questionnaire/)
- [Versions and capability matrix](/build/sdk/compatibility/) for the full per-operation network table