docs← Back to article

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.

Download this articlePlain text ↗
# 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