Airgapped-agent guide
How to drive a local, offline model over a large Rust workspace using nornir as the ground-truth provider — "go through the source and crates, find dependencies / function calls" without the model ever walking raw source (where it hallucinates edges). This is the practical companion to the Explore and Architecture chapters in design.md.
The principle: tooling-first, model-second
A small local model is good at summarizing and navigating structured facts and bad at deriving them from raw code. So don't ask it to "read the repo and work out the call graph" — point it at nornir's MCP server and let it query. nornir already holds every projection (deps, calls, symbols, full-text, vectors) keyed by git SHA, served over MCP for exactly this. Reserve a frontier model (e.g. Opus) for judgement; offload "index it, list it, find it" to the local one.
Model + harness (as of mid-2026)
- Model (24 GB VRAM, agentic tool-calling): Qwen3-Coder-Next (MoE, ~256K context, fits a single 24 GB GPU) or Qwen3.6-27B (Apache-2.0, first-class tool-calling). Both are strong on Rust for mechanical work.
- Harness: Aider / opencode / Cline over Ollama or vLLM. (Claude Code is Anthropic-only.) Point the harness's MCP client at a running
nornir-mcp. - nornir stays embedded or client, never both on one warehouse — see the lock rule in design.md. For a shared/served setup, run
nornir-serverand let the agent host talk to it.
The query surface (what the model calls)
Start every session — and after any context reset — with orientation, then drill in. None of these require a checked-out binary except where noted.
Orient
repo_overview <repo>— one call: internaldepends_on+dependents(with the crates that justify each edge), build-order position, and a knowledge digest (symbol + call-edge counts, sample symbol names). The cheapest "where am I".
Crate dependencies (Mímir)
deps_of/dependents_of— direct neighbours.affected_by_change <repos>— the invalidation set (changed + transitive dependents) in build order — the re-check set for a release.build_order·dep_path <from> <to>(why does A depend on B?) ·external_dep_users.
Function calls & symbols
- From syn (source, always available once scanned):
knowledge_callers/knowledge_callees/knowledge_call_path/knowledge_symbol_lookup/knowledge_defined_in. - From DWARF (binary truth, persisted — see below):
dwarf_callers/dwarf_callees/dwarf_call_path/dwarf_symbol_lookup/dwarf_defined_in. Each takes an optionalshafor time-travel. Inlined edges only.
Search
search(BM25 lexical) andvector_search(semantic; hybrid = RRF of both).
Populating the facts (operator, once per repo/release)
The query tools read what's been persisted. Seed it with:
nornir map <repo> # syn scan + workspace index build + snapshot
nornir introspect symbols --persist \
--repo <repo> target/debug/<bin> # DWARF symbols + callgraph → dwarf_* tables
nornir map is the one-shot for the syn/knowledge + full-text side; introspect symbols --persist adds the DWARF side from a debug-info binary (keyed to the member's HEAD SHA). Both write to the warehouse, so every later query — and every past SHA — is a warehouse read, no re-scan.
Why this beats raw-source RAG
- No hallucinated edges. Call/dep graphs come from
cargo metadata,syn, and DWARF — not the model's guess. - Fits the context budget. The model pulls a few hundred bytes of answer per query instead of dumping files; a 24 GB model with a modest KV cache is plenty.
- Time-travel for free. Every fact is SHA-keyed, so "as of release X" is just a
shaargument. - Airgapped. No code leaves the machine; the model only ever sees query results.