Docs · Task 02 · Docs for 1.0.10

Ask about your own files

The command-line tool retrieves from a folder of your files and answers from what it found, naming each file it read. Every command and every number below was run against armore 1.0.10 before it was written here.

The steps are the same either way. Switching changes how each one is done — and it is remembered as you move through the guide.

This page uses the CLI, which is a separate download from the installer — see install and first run if you only have the app.

Step 1

Check your binary can run a model at all

The app's Ask screen on a fresh device. A checkbox reads Use my files
                  with a count of zero, the history panel says there are no conversations
                  yet and that history is stored on this device where no server can read
                  it, and the main area is headed What can I help you with above a note
                  that asks stay on this device unless you turn on paired devices or web
                  search.
A fresh install, before anything is indexed — Use my files (0), and no history. The badge above the prompt reads PRIVATE · RUNS ON THIS DEVICE, and the app states its own boundary in the same breath: asks stay here “unless you turn on paired devices or web search.” The model doing the work is named beside the heading, so you can see what you are running before you run it.
→
The app does this — just not as a separate step. It reports its own capability on the This device screen — the chat model in use is named there, alongside what the machine has. If the app is running and names a model, it can run one.

Inference is compiled in or it is not, and this tells you which — before you spend time on anything else.

armore models capabilities --json

{
  "backends": {
    "llama_cpp": true
  },
  "cli_version": "1.0.10",
  "command": "models.capabilities",
  "commands_requiring_inference": [
    "ask",
    "extract",
    "rag"
  ],
  "inference": true,
  "note": "on-device inference is compiled in — ask/extract/rag will run with a --model.",
  "ok": true
}

"inference": true is the line that matters. The three commands listed under commands_requiring_inference are the ones that will refuse to work without a model file you supply yourself.

Step 2

You supply the models — and grounding needs two

→
The app does this — just not as a separate step. The app fetches and manages models for you after asking, which is Step 4 of the install guide. Bringing your own and naming it on every call is the command line's way, not a missing feature.

No model is bundled with the CLI. Point it at a directory of .gguf files and it will tell you what it found, with the size and content address of each:

armore models list --dir ~/models --json

{
  "command": "models.list",
  "count": 2,
  "dir": "~/models",
  "models": [
    {
      "blake3": "b244ec2a2bbaf54c643239aa2894fd71f680252525969dfc81fa35a768e096a0",
      "bytes": 36806944,
      "name": "bge-small.gguf",
      "path": "~/models/bge-small.gguf"
    },
    {
      "blake3": "22a0829973f064026cacce3584596851a79048c7c847bbf05ceecc5093e6ce1d",
      "bytes": 8540775840,
      "name": "llama-3.1-8b-q8.gguf",
      "path": "~/models/llama-3.1-8b-q8.gguf"
    }
  ],
  "ok": true
}
!
Answering from your files needs two different models, not one. A chat model for --model, and a separate embedding model for --embed-model — armore ask --help states that --rag “Needs --embed-model”. They are not interchangeable: the embedding model here is 36.8 MB and the chat model is 8.5 GB.
Step 3

Retrieve before you ask

→
The app does this — just not as a separate step. Indexing happens when you add a file, so there is no separate retrieve to run. The answer then shows which file it drew on — the from row in the next step.

Retrieval on its own needs only the small embedding model, so it is the cheapest way to find out whether your folder even contains the answer. All four flags shown are required by armore rag search.

armore rag search --json \
  --query "when does the Hartley contract renew" \
  --dir ~/Documents/contracts \
  --model ~/models/bge-small.gguf

{
  "command": "rag.search",
  "ok": true,
  "query": "when does the Hartley contract renew",
  "results": [
    { "blake3": "0c2498735b51fe33…", "score": 0.9127750992774963, "source": "hartley-contract.txt" },
    { "blake3": "114f8548f8d62c92…", "score": 0.5476594567298889, "source": "office-move.txt" },
    { "blake3": "77738ff47cba4c3c…", "score": 0.46502354741096497, "source": "q3-numbers.txt" }
  ],
  "top_k": 3
}

Three files, ranked, in 0.17 s once the model was in the page cache. --top-k defaults to 3. Each result carries the BLAKE3 of the file it scored, which is what lets you tie an answer back to a specific byte sequence on your disk rather than to a filename someone could have swapped.

!
The corpus is top-level text files only, and nothing warns you. Measured on a directory containing top.txt, report.md, subdir/nested.txt and binary.bin, the returned sources were exactly report.md and top.txt. Subfolders are not walked and non-text files are skipped — so a folder whose documents live one level down retrieves nothing, and the command still exits successfully. Any UTF-8 text file counts, not just .txt. Flatten the folder, or point --dir at the subfolder itself.
Step 4

Ask, grounded on those files

The app's Ask screen. A question about calibration has been answered in
                one sentence with a bracketed citation marker. Below the answer sits a
                green Signed and verified badge, and a from row naming the single source
                file the answer was drawn from.
Asked something its index can support, the app answers and shows its working: a Signed · verified badge, and a from row naming the file it read. The sidebar notes the conversation is “stored on this device — no server can read it,” and the badge beside the heading names the model doing the work.

Same folder, now with a chat model as well. --model and the prompt are required; --rag and --embed-model are what make it read your files instead of answering from the model's own training.

armore ask --json \
  --model ~/models/llama-3.1-8b-q8.gguf \
  --rag ~/Documents/contracts \
  --embed-model ~/models/bge-small.gguf \
  --top-k 2 \
  "When does the Hartley contract renew, and how much notice does termination need?"

{
  "command": "ask",
  "grounded_on": [
    { "blake3": "0c2498735b51fe33…", "score": 0.9541630148887634, "source": "hartley-contract.txt" },
    { "blake3": "114f8548f8d62c92…", "score": 0.5768898129463196, "source": "office-move.txt" }
  ],
  "model": "~/models/llama-3.1-8b-q8.gguf",
  "ok": true,
  "reply": "According to [hartley-contract.txt], the Hartley contract renews on 14 March 2027,
             and termination requires a notice period of 90 days.…"
}

29.22 s wall clock end to end, including loading the 8.5 GB chat model from disk. grounded_on is the part worth reading: it names which files were actually put in front of the model, so a wrong answer is traceable to a wrong source rather than a mystery.

!
Do not ask the model which model it is. In the run above, the reply went on to say “The base model loaded is not certain” and suggested checking elsewhere — which is the right instinct but useless as an answer. The authoritative value is the model field in the JSON above, and armore models list --dir … --json gives you its BLAKE3.
Step 5

Get a receipt, then check it offline

→
The app does this — just not as a separate step. The receipt is shown inline on the answer — the Signed · verified badge in the next step is it. Verifying it is still a command-line act, for the same reason checking a download is: a check you run inside the thing being checked proves less.

armore extract pulls structured fields out of one document and will sign a receipt over the run. Two flags are required together, by design: --receipt-key-file and --seal-store.

# a 32-byte Ed25519 key, 64 hex chars, in a file you control
openssl rand -hex 32 > receipt.key && chmod 600 receipt.key

armore extract --json \
  --model ~/models/llama-3.1-8b-q8.gguf \
  --template contract \
  --receipt-key-file receipt.key \
  --seal-store ./sealed \
  ~/Documents/contracts/hartley-contract.txt

{
  "command": "extract",
  "input_blake3": "0c2498735b51fe335734ccb1fe2d8ddf14b2379f055dee382cd27165a14b9908",
  "ok": true,
  "output": {
    "effective_date": "2027-03-14",
    "fees": "none",
    "jurisdiction": null,
    "parties": [ "Hartley Inc." ],
    "term": "90 days"
  },
  "receipt": { "…": "author, model_blake3, input_blake3, ran_offline, signature" },
  "sealed_output": { "blake3": "cd04cf5dba148854…", "file_id": "extract-output-cd04cf5dba148854" },
  "template": "contract"
}
!
A key typed on the command line is refused, not warned about. Passing --receipt-key <hex> exits with: “a literal key on the command line … puts it in ps output and your shell history”. Use --receipt-key-file <path>, --receipt-key-file - for stdin, or the ARMORE_RECEIPT_KEY environment variable. --seal-store is required alongside it for the same reason — so the directory that ends up holding key material is one you named, not a default nobody showed you.

Now verify it, with no network and nothing of ours involved:

# re-run the command above with `> extract.json` to keep the output, then:
jq '.receipt' extract.json > receipt.json
armore audit receipt --json receipt.json

{
  "author": "da04f0a8303f1110c4c63d9ad6cacd5560267ff977456e723cb2439310feb238",
  "command": "audit.receipt",
  "input_blake3": "0c2498735b51fe335734ccb1fe2d8ddf14b2379f055dee382cd27165a14b9908",
  "model_blake3": "22a0829973f064026cacce3584596851a79048c7c847bbf05ceecc5093e6ce1d",
  "ran_offline": true,
  "valid": true
}
✓
Prove the check can fail, or it told you nothing. Changing one hex digit of input_blake3 in the receipt and re-running the same command flips "valid" to false. Worth doing once on your own machine — a verifier that cannot say no is decoration.

Tying the receipt to your file

input_blake3 is only meaningful if you can show it is your document. Hash it yourself:

armore files hash --json ~/Documents/contracts/hartley-contract.txt

{
  "blake3": "0c2498735b51fe335734ccb1fe2d8ddf14b2379f055dee382cd27165a14b9908",
  "command": "files.hash",
  "ok": true
}

Same value as input_blake3 in the receipt, and as the blake3 of hartley-contract.txt in Steps 3 and 4. Those four figures agreeing is the whole point: the receipt is about this file, this model, and nothing else.

Step 6

See what it kept — and erase it

→
The app does this — just not as a separate step. Settings → Privacy & data exports or erases everything the app holds, and that page shows both surfaces doing it. ⚠️ The two stores do not reach each other, which is the one thing on this subject worth getting right.

Nothing above stores your documents, but the CLI does keep state of its own. Read it rather than take our word for it:

armore privacy list --json

{
  "command": "privacy.list",
  "present": [
    { "key": "file-store", "irreversible": true,  "size_bytes": 1252,
      "what": "Encrypted file store, INCLUDING epoch.key — the 32-byte symmetric key that seals every file in it." },
    { "key": "identity",   "irreversible": true,  "size_bytes": 3878,
      "what": "This device's identity: member-root seed, enrollment record, device certificate." },
    { "key": "serve",      "irreversible": false, "size_bytes": 219 },
    { "key": "mcp",        "irreversible": false, "size_bytes": 0 }
  ],
  "absent": [ { "key": "memory" }, { "key": "msg" } ],
  "roots": [ "~/.config/armore" ],
  "total_bytes": 5349
}

armore privacy export copies all of it into a directory you choose; armore privacy purge deletes it.

!
This command cannot see the desktop app's data, and the app cannot see this. The two roots are disjoint — privacy list reports the CLI's own stores under ~/.config/armore only. Clearing one leaves the other untouched, in both directions.