loom is a SQLite-backed CLI for maintaining a falsifiable graph of what a codebase should do, where that behavior lives, and how it is proven.
The model supplies judgment and evidence. loom supplies durable memory, routing, staleness, coverage, integrity checks, and a portable export that can travel with the repository.
Long-running LLM codebase work fails when context is only conversational. Decisions rot, proofs go stale, files lose owners, and advisory smells turn into an undifferentiated debt wall.
loom makes the working model explicit:
- Journey — the authored user or operator flow that roots downstream work.
- Intent — what the code is supposed to do.
- CodeFile — where behavior lives.
- Validation — how behavior is proven.
- InterfaceSurface — the real repository seam through which a Journey is exercised.
- QualityRule — what good looks like.
- Edge — the typed claim connecting those facts.
- Fact — the asserted state of a claim, and the evidence anchoring it.
The goal is not autonomous coding. The LLM still acts. loom routes, remembers, invalidates, and checks the work.
Every asserted fact carries a verification level, and the level is not a label anyone writes — it is derived from evidence loom can independently re-examine:
| Level | Means | Reached by |
|---|---|---|
| verified | loom ran something and watched | loom validation run / loom journey run; a locator that re-resolves to a live symbol; a quality rule's patterns scanned by loom itself |
| cited | anchored to bytes or a journal entry | a file:line citation, fingerprinted and re-checked when the file changes |
| claimed | prose only | recorded in full, and it never satisfies a rung |
| expired | every anchor has since broken | what a verified fact becomes when the code it covered moves |
The type a caller may supply has no way to express a Run. Asking loom to
run something is the only route to verified — "mark it passed without running
it" is a compile error, not a policy.
Prose is still recorded, always. It just does not count. A claim justified only by a sentence never settles, so its lane keeps serving it.
Two consequences worth knowing before you use this:
- An absence can be evidence. "No hardcoded secrets in the code realizing X" has nothing to cite — but loom can scan the rule's patterns itself and record finding nothing. A later hit refuses a contradicting passing verdict, and prints the lines it found.
- Proofs expire on their own. A run records the file hashes in force when it ran. Edit one, and the proof stops counting and its claim re-opens — no one has to remember to re-run it.
Facts are still classed by how they become true (derived recomputed by sync,
asserted persisted until invalidated, statistical advisory and never gating).
Verification is the second axis: not who said it, but what would show it
false.
- Journey-root delivery: a strict
loom.journey/v1artifact states actors, ordered semantic actions, and expected outcomes without implementation or transport detail.loom journey deriveproposes the smallest falsifiable technical Intents; a human accepts the exact hash-bound mapping withderive-accept;loom journey surfacedescribes the structured target-repository CLI contract; andcompileplusrunprove the accepted Journey through that real surface. A Journey is the root. Intents, code, surface, and proof are projections of it. - External diagnostics:
loom scan add/list/remove/runwraps any language's linter, type-checker, or custom diagnostic command. GCC-stylefile:line[:col]: messageoutput works by default, as do two-line diagnostics (a barefile:line[:col]location line with the message on the immediately following line, as svelte-check emits); custom named-group regex maps (file,line, optionalmsg,code) are supported and stay strictly per-line.--format jsonconsumes JSON/JSONL finding arrays instead (pulse, qualirs, and similar tools), withfield=pathlookups, dotted paths, anditems=<path>for envelope objects. Parsed diagnostics become derived findings and converge on re-run when diagnostics disappear. - Pattern pre-screening: seeded quality packs can carry regex
patterns[]. Quality packets embed computedpre_screened_hits(path,line,pattern,excerpt) at packet-build time so the LLM can confirm or refute every candidate before writing the verdict. - Definition-of-Complete:
loom completeness [intent]scores user-visible feature intents across scenarios, prerequisites, boundary, proof, journey, and questions.loom next --mode elaborateserves the most-incomplete feature, tells the LLM to explain that a partial idea is enough, fills inferable gaps, and engages the user one plain-language product question at a time for decisions only they can make. - Scenario families:
loom intent add/update --aspect happy|sad|fallback|edge_caseplusscenario-ofedges model happy paths, sad paths, fallbacks, and edge cases without inventing a separate scenario node type. - Question loop: product questions are captured with
loom question add "..." --intent <intent>and surface throughloom session/graph_state.open_questionsfor batched human answers. Evidence-backed observations useloom finding add, not the inbox. - Cheap residue routing: work packets carry
routing_hint(mechanical|judgment) and sync gradescheap re-confirmvs full re-inspection; orchestrators may batch-reaffirm mechanical items vialoom applyverdicts. - Cold-start assist:
loom bootstrap suggestdrafts a Proposal of non-authoritative Journey clues from codefiles/tests/README. Inspect product evidence, then author and registerloom.journey/v1roots; inferred repository structure never becomes product meaning automatically. - Find + explain:
loom find --tag/--whereandloom explain <intent>for facet search and neighborhood briefs. - Calibrated structural detectors: sync's built-in findings (
oversized_file,complex_symbol,large_symbol,deep_nesting,excess_args) run on configurable thresholds;loom calibrate [--write]fits gates to the repo's own distribution. Calibration only ever RELAXES: the default says "this is bad in any codebase", the repo's distribution says "our normal runs looser than that", and a gate fitted below the default would flag code that is fine by any absolute standard for sitting above its neighbours — which is how a percentile-based detector manufactures a debt wall in proportion to repo size. - Validation-specific proof strength: the S3 call witness is earned by a validation's own evidence — an explicit
loom edge exercises <validation> <codefile> --locator <entry-symbol>entry (the locator is required for S3; a bare file claim is diagnostic only), an entry point derived from its command (cargo test --test …,cargo run --bin …, scripts, binaries), or a compiler-owned Journey Exercises closure (public adapter plus optional operationexerciseswhoseobserved_byassertion passed on a local compiler-v6journey run). Imported or deserialized run records cannot supply that assertion evidence. Compiler-v5 Journey proofs must be recompiled and rerun; schema v12 graphs do not need rebuilding. A sibling validation on the same intent never inherits another proof's reach, and the legacy intent-wide surface is recorded visibly as non-eligible fallback.loom validation showexplains the grade with the exact source, file, entry symbol, reached symbol, and Journey operation provenance when present. - Call graph: extraction records call sites per file (Rust, Python, Go, JS, TS);
loom impactresolves them on demand and reportsexactandheuristicmatches separately. - Advisory debt + promotion:
loom debtranks statistical clusters (size_outlierLOC outliers and git-historyco_change) with stablecluster_ids;loom debt promote <cluster-id> --evidence <TEXT> [--confidence <0..1>]mints exactly one asserted Finding (source: debt_promotion) for ordinary finding triage while the raw feed stays advisory. - Portable configuration:
loom.graph.jsoncarries theconfigmap (layer_order,ignores,codefile_globs,scan_adapters,thresholds,evidence_policy) so imports keep the graph's routing, scan, detector, and policy setup. - Wiki projection:
loom wiki plan/next/record/list/removetracks reader-first documentation pages as graph citizens — an agent writes the prose, the graph governs truth and freshness, andloom syncstales a page precisely when a documented intent, its code, or its proof drifts. - Federation:
loom graph linkcomposes graphs across repositories via committed exports; upstream intents appear as shadow nodes that ripple staleness locally without ever entering local queues or gates. Plaingraph unlinkkeeps shadows (doctor flags orphans); permanent dispose isgraph unlink --pruneorgraph prune-orphans(--cascadeif DependsOn edges remain).
A repository using loom has two graph artifacts:
.loom/graph.sqlite # local SQLite working store
loom.graph.json # deterministic portable export
Commit loom.graph.json when the graph should travel with the codebase. .loom/ is local runtime state; portable setup such as layer order, ignores, codefile globs, and scan adapters lives in the export's config map.
cargo build
cargo run -- --helpInstall from a checkout:
cargo install --path .
loom --helpInitialize a graph in a repository:
loom init .Author behavior first as a semantic Journey:
schema: loom.journey/v1
id: checkout
name: Complete checkout
actor: shopper
goal: Purchase a selected product and receive an accepted order.
inputs:
sku:
type: string
description: The product selected by the shopper.
preconditions:
- The product is available to purchase.
steps:
- id: choose-product
name: Choose product
action: Choose a product to purchase.
expects: []
produces: {}
- id: confirm-order
name: Confirm order
action: Confirm the order.
expects:
- The shopper receives an accepted order with a stable receipt identifier.
produces:
receipt:
type: string
description: The stable receipt identifier for the accepted order.
profiles:
proof:
inputs:
sku:
template: sku-1
workspace: {}Register the authored root, then inspect the proposed technical projection:
loom journey add journeys/checkout.yaml
loom journey derive checkoutderive is read-only. Its only acceptable projection is a strict loom.journey-derivation/v1 JSON manifest: it names proposal_id, proposal_rationale, explicit create or reuse operations for each Intent, each new Intent's falsifiable criterion, per-entry rationale, stable step_ids, declared relationships, and unresolved_question. Review one conversational hash-table batch of that exact manifest with the human; only their exact answer authorizes derive-accept. Acceptance creates an adopted Proposal, reconciles the declared relationships, and is idempotent when the identical hash is replayed. After the accepted Intents are implemented and grounded, loom journey surface checkout emits the contract for a real CLI in the target repository. Loom does not generate that source.
Map accepted behavior to code with the ordinary grounding primitives:
loom intent add \
--name "checkout captures payment" \
--description "payment is captured before fulfillment continues" \
--level feature
loom codefile add 'src/**/*.rs'
loom edge implement \
"checkout captures payment" \
src/checkout.rs \
--locator "fn capture_payment"Attach a proof:
loom validation add \
--name "checkout payment capture test" \
--type test \
--command "cargo test checkout_captures_payment" \
--intent "checkout captures payment"Ordinary validations prove individual Intents. A Journey proof is compiler-owned instead:
loom journey surface checkout
loom journey surface-accept checkout --manifest checkout.surface.json
loom journey compile checkout --profile proof
loom journey run checkout --profile proofThe surface manifest contains structured argv, typed arguments, step bindings, and JSON output—not executable strings. surface-accept is appropriate only after the corresponding real CLI source exists and is registered.
Drive the loop:
loom sync
loom status
loom next --all
loom validation run --all
loom exportUse JSON for agent-facing output:
loom --json status
loom --json next --allThe implemented command surface is the one printed by the binary:
loom --helpCurrent top-level commands:
welcome Plain-English orientation (also the bare `loom` default)
init Initialize a graph store
intent Intent commands
codefile CodeFile commands
checkpoint Read-only semantic Git checkpoint recommendation
export Write loom.graph.json
import Restore from loom.graph.json
apply Apply one atomic batch of mutations from a JSON/YAML file
sync Recompute structural facts and ripple staleness
status Print graph identity and counts
mode Show or set the graph mode (owned | observed)
next Return the next routed work item
edge Edge commands
door Capture free-form input as an inbox item
inbox Inbox commands
question Product questions (human-gated decisions linked to intents)
task TaskRecord commands
note Durable notes on graph nodes
session Turn-zero offer menu
guide Role/lane guidance
find Keyword search over graph facts
explain Read-only neighborhood brief for an intent
context One read-only context packet for an intent, file, or query
detect Repo language detection and quality pack recommendation
scan External diagnostic adapters to derived findings
calibrate Relax structural thresholds to fit this repo (never tightens)
threshold Hand-set structural finding thresholds
policy Evidence policy (review floor + human gates)
completeness Definition-of-Complete scorecard
schema Print the data model
rule Quality rule commands
validation Proof commands
hypothesis Hypothesis commands
surface Interface surface commands
vocab Vocabulary commands
layer Architecture layer-order commands
smells Structural smell report
debt Advisory statistical feed (size outliers, co-change) + explicit promotion
finding Evidence-backed capture and asserted adjudication
doctor Integrity audit
coverage Vertical-spine coverage report
ignore Coverage exclusion commands
whoami Write authority + self-declared executor provenance report
proposal Proposal capture and item adoption
journey Authored Journey roots and their derivation, surface, compile, and proof lifecycle
drive Interactive, journaled human drive session (or freeze one)
hook Install/remove local git hooks keeping the structural plane fresh
decide Record a decision as a REVERSAL (what was chosen, rejected, why)
observe Run a command loom watches, and keep what it saw
absorb Read the working tree and propose the graph mutations it implies
audit Integrity audit of loom's own record (falsifiability)
deepen Rank what to strengthen next, once every floor is met
wiki Reader-first wiki pages tracked as a graph projection
graph Cross-graph federation (link/unlink/list upstreams)
impact What a change here could reach (callers, intents at risk)
bootstrap Cold-start assist: draft non-authoritative Journey clues
mcp Serve loom in-band over MCP (stdio JSON-RPC)
loom is operated through its binary and its docs — it does not depend on any
installed skill or plugin. An agent that has never seen a loom graph can orient
in three commands:
loom --help # the implemented surface; treat this as the source of truth
loom welcome # plain-language state: where this repo's graph is and what is next
loom guide # role/lane guidance: which commands a lane may and may not runThen the standing agent loop:
loom sync
loom --json status # compass, rung ladder, queue depths
loom --json next # the next routed work packet with its prompt contractloom next --all returns a compact roster of every lane's next item;
--full additionally expands each into its full prompt contract.
Every served packet is self-contained: it names its allowed_actions,
forbidden_actions, the required_evidence, a write_back command, and a
stop_condition. Follow the packet, run the write-back, loom sync, and return
to loom status. Deeper reference material lives in docs/README.md,
docs/llm-driver.md (how loom and an LLM cooperate), and docs/commands.md
(the shipped CLI surface). An optional loom-driver skill may exist in a global
skill root for batch orchestration, but it is never required to operate loom.
loom mcp serve speaks MCP over stdio, so an agent pulls context as a tool call
instead of shelling out. Register it with any MCP client:
{ "command": "loom", "args": ["mcp", "serve", "--graph", "/path/to/repo"] }Tools: loom_status (ladder, compass, queue depths), loom_next (the next work
packet with its prompt contract), loom_context (read-only context for an
intent, a file, or a query). Each is a thin wrapper over the same function the
CLI calls — a tool and its CLI twin cannot report different numbers.
Every served packet carries a packet_id and appends one packet_served entry
to the append-only journal. That record is what makes "did loom's context
actually change the outcome?" a measurable question rather than a claim.
docs/commands.md describes the shipped CLI surface plus explicitly marked removed/deferred names. Treat the compiled CLI help as the source of truth for what is implemented now.
loom sync
loom --json status
loom --json next --allThen follow the returned lane. Every lane owns exactly one maturity rung, and a rung is unmet iff its lane's queue is non-empty — so the compass can never point at a lane that would hand you nothing:
| Lane | Rung | Closes |
|---|---|---|
| seed | seeded |
nothing is named yet |
| fix | repaired |
a claim that was true has broken |
| build | grounded |
intents with no code behind them |
| coverage | covered |
registered files no intent owns |
| validate | proven |
implemented behavior with no passing proof |
| quality | measured |
rules never measured against the code |
| analyze | inspected |
relationship claims nobody has judged |
| review | reviewed |
verdicts recorded below the confidence floor |
| triage | triaged |
findings awaiting a durable decision |
| prove | investigated |
hypotheses nobody has tested |
| elaborate | elaborated |
user-visible ideas only half-described |
| ratify | converged |
where evidence and human judgment disagree |
| audit | sound |
graph-integrity issues and open smells |
| export | published |
the committed graph is behind the live one |
fix never records a verdict — it repairs at root cause and lets the owning lane
re-measure. validate runs proofs; it does not accept reported outcomes.
loom impact <symbol|file> [--depth 3]Walks callers backwards from the target and names the intents at risk plus how
well each is proven — "42 callers" is trivia unless it says what could silently
break. Resolution confidence is reported, never blended: a name matched by
exactly one definition is exact; several definitions means the nearest by file
proximity, marked heuristic. Calls into std or third-party code are counted as
unresolved rather than guessed at.
After graph mutations:
loom export
loom export --checkAfter one implemented Intent or cohesive bundle is proven and synchronized, inspect the exact local history checkpoint Loom can justify:
loom checkpoint recommend --intent <intent> [--intent <intent> ...]The command is read-only. It reports included and excluded dirty paths,
current validation/sync/doctor/export checks, scope rationale, and a suggested
message. Loom never stages, commits, or pushes. An acting LLM may create or
defer the exact local commit without interrupting the human, but must stage only
the reported paths (never git add -A) and defer on ambiguous or user-owned
overlap. Publishing remains separate: push requires an explicit human decision
bound to the current repository, remote, branch, and commit; silence or drift
leaves the commit local.
If you wire this into a user-created pre-commit hook, keep the hook defensive: prefer loom export --check, and feature-detect any optional command before invoking it (older loom binaries may not have newer surfaces such as loom wiki or loom graph).
For an explicit blocking local CI gate before each push, point Loom at a repository-owned executable script:
loom hook install --pre-push scripts/local-ci.shThe default loom hook install remains nonblocking and installs the
post-commit/post-merge structural sync hooks. --pre-push instead installs
only the opt-in local CI hook, so existing hooks remain untouched. Loom
confines the gate to an executable file inside the repository, refuses to
clobber a foreign pre-push hook, and blocks the push when the script fails. In
this repository, scripts/local-ci.sh runs the same dogfood gate used by the
manual GitHub Actions fallback; automatic push and pull-request Actions are
disabled.
Start with:
docs/README.md
Useful design docs:
docs/terminology.md canonical vocabulary
docs/graph-model.md nodes, edges, facets, schema, invariants
docs/state-machine.md routing, ripple, lifecycle
docs/llm-driver.md how loom and an LLM cooperate
docs/commands.md shipped CLI surface + removed/deferred names
docs/build-plan.md ring sequencing and shipped-surface corrections
docs/design.md architecture seed and locked decisions
This repository is a greenfield rebuild of loom, mid-way through a hardcut to
the evidence spine described above.
Working and exercised on codebases loom had never seen (a 2.4G polyglot
monorepo, a 270-file Rust workspace, a Rust+Svelte service): cold-start
registration with glob suggestions, symbol extraction across five languages,
candidate-file proposal from a single sentence of intent, the call graph and
loom impact, proof runs that loom performs itself, pattern scans that make an
absence checkable, and evidence expiry that re-opens claims when code moves.
Breaking in 0.30.0. On-disk schema v12 establishes Journey-root semantics and deliberately refuses older SQLite graphs and exports. There is no compatibility migration because translating executable proof specs into authored meaning would fabricate product judgment. Rebuild with loom init, register code, use loom bootstrap suggest for repository clues, author loom.journey/v1 roots, then use loom journey derive and obtain the required human decisions.
Not yet done. The remaining open work is tracked in the graph itself —
loom status --json is the honest map (rungs unmet, queue depths). As of the
0.28 cut: all six anchor floors are live (proof, quality, the locator probe,
grounding, adjudication, relationship); derived proof strength, the ratification
inversion, the deepen/audit lanes, loom absorb/observe, and the
sync-ripple rewrite (reverify_all) are shipped and exercised by the ring
tests. What remains is the ordinary backlog a living graph accumulates: the
repo's own triage and prove lanes carry the structural refactors
(oversized src/workitem/queues.rs, src/scan.rs, …) and the journey proofs
not yet at S3. Sync ripple under real edits, the quality lane, and Journey-root delivery have
not been exercised on foreign code.
Treat the staged constants and the module headers as the honest map: each says what it does today and what it is waiting on.