Skip to main content
Glama

EpsilonGraph

A typed knowledge graph + epistemic ledger over an Obsidian vault, exposed to coding agents (Claude Code and anything else that speaks MCP).

It answers questions that no RAG system answers:

  • What grounds the rule scaffold split is the default — and has it been tested, in how many projects?

  • If I change this note, which other notes become wrong (not merely stale)?

  • What does the vault itself admit it does not know?

  • Give me the 8k-token subset of this 160k-token vault that this task needs.

[!note] The mathematics is written down Read THEORY.md first if you want to know why any of this should be believed. It states what each sub-system is mathematically, and then says which claims are proved, which are measured, which are mine, and which are still guesses.

The short version: blast radius is a semiring-provenance transitive closure (Green et al., PODS 2007), context packing is submodular maximization under a knapsack constraint (Sviridenko 2004; Amanatidis et al., TCS 1060, 2026), and RRF's k has a derived bound where the practitioner literature has only folklore. Writing those three down found four real bugs that reading the code did not.

Correction, 2026-10-12. A literature check found that the packing formulation is already published — arXiv:2607.00725 (Jul 2026) casts reader-context construction as budgeted monotone submodular maximization, with the task-level eval this repo lacks. That claim is withdrawn. The replacement candidate is a dependency-closure constraint under a budget, which is measurably outside that literature because closure terms are supermodular. THEORY.md §3 and §5. What replaced it, per prior work, is the related-work table below — one row per work, each naming the quantity it leaves open.

[!note] The product view Evaluating this as a product rather than a research project — install, resource auto-acquisition, multi-vault, distribution, scale — is written up in PRODUCT.md.

What each prior work models, what it leaves open, and whether this project's measured results contradict it, confirm it, or are orthogonal (touch a different quantity). Every cell is drawn from RESEARCH.md §4 and §P8.1 and THEORY.md §5; no citation or number appears here that is not already there.

prior work

what it models

what it leaves open

this project's result

Hwang, Retrieval-Governed Context, Zenodo 2026-04-15

scope gates, priority weights, conflict resolution and mandatory injection over a unified instructions+tools corpus; a governance layer

mandatory injection is "always include this", not "if X then deps(X)" — no closure, no graph-theoretic constraint; no theory; and no per-note closed-pack cost, so {i : h({i}) ≤ B} does not arise

orthogonal. Confirms why this corpus makes the question non-trivial (its effect scales with metadata richness) but models no dependency; it supplies the F1 delta for removing a gate, not a price for carrying one

arXiv:2607.00725, What Survives Into Context (Jul 2026)

reader-context construction as budgeted monotone submodular maximization, with a packer over relevance/coverage/representativeness/diversity and a task-level eval (answer-in-context)

the supermodularity of the closure terms — it has no dependency structure at all, so it never meets the fact that both natural closure terms are supermodular; also reports retrieval and answer quality separately, never answer-quality conditioned on presence

contradicts (one assumption) + orthogonal (the rest). Its f assumes monotone; a conflict penalty is supermodular, so the (1−1/e) guarantee stops applying and the correct citation becomes the non-monotone knapsack bound. Its 14B reversal bounds this project

arXiv:2603.03180, Type-Aware RAG with Dependency Closure (Mar 2026)

typed knowledge graph + minimal dependency closure over the graph, for compilability (missing declarations, type inconsistencies, so a solver can execute the model)

the budget — closure is satisfied fully, then the model is built, with no packing and no token cost

orthogonal. Same phrase, different purpose: its closure is a correctness precondition that is never paid for out of an answer's budget, so it has no h({i}), no feasibility frontier, and no supermodularity result

DCTR, Dual-Constraint Subgraph Optimization for KG-RAG (AAAI 2026)

subgraph optimisation for KG-RAG, with dual constraints on the subgraph

the decomposition, and any closure-cost quantity. Read at title/abstract level only — this row is the weakest here and is marked as such

orthogonal (unverified). On the available text it reports neither h({i}) nor the present/correct/given it split; a reason to fetch it before submission, not a claim of difference

vendored facetpack / ACE-RAG (joelvarun/facetpack; nayanananto/Diagnostic-for-Budget-Constrained-Multi-Hop-RAG)

cost-aware submodular context packing as a drop-in for top-k; ACE-RAG supplies the published objective and the answer-in-context metric, both ported (epsilongraph/priorart.py, epsilongraph/aic.py)

no dependency structure. The objective is re-measured monotone and submodular on our corpus exactly as published, but three of f's five terms do not see the query, and neither implementation prices a dependency

confirms (the port) + contradicts (as evidence). The port is faithful (submodularity re-measured, metric matches on 8 cases incl. the adversarial one); but intrinsic objective value is not evidence of retrieval quality — on the task proxy the baseline rank_order wins every column

The three quantities this project stands on — h({i}) / the feasibility frontier, the supermodularity of the closure terms, and the present/correct/given it decomposition — are each attributed above to exactly one prior work that lacks them (RESEARCH.md §P8.1).

Related MCP server: obsidian-mcp-context

Why this is not a RAG project

Measured on the live vault (/home/hugo/Documents/Obsidian, 103 notes):

Measure

Value

Whole vault

642,293 chars ≈ ~160k tokens

Rules + graph skeleton (all [!summary]/[!important]/[!warning] + every Relations block)

≈ ~29k tokens = 18%

Typed relation edges

491

Retrieval collapse is documented at the 10K–100K document range. At 103 notes this is a long-context selection-and-ordering problem, not a recall problem. The gap the vault actually has is epistemic state — grounding, evidence, cross-project status, contradictions, admitted gaps, blast radius — and the vault already models all of it. epsilongraph supplies the missing interface.

See DESIGN.md for the full argument, the boundary conditions that exclude GraphRAG/RAPTOR/semantic chunking at this size, and the explicit not-doing list.

Install

No third-party dependencies. Python 3.11+ (stdlib sqlite3 with FTS5).

cd ~/codes/EpsilonGraph
python3 -m venv .venv && source .venv/bin/activate   # a venv: Arch's PEP 668 blocks system pip
pip install -e .            # gives you the `epsilongraph` console script
# or, without installing anything:
python3 -m epsilongraph stats

Then one command fetches and verifies every external resource:

epsilongraph acquire status          # what is missing, and the exact fix for each
epsilongraph acquire obsidian-mcp    # checksummed prebuilt Layer 0 binary
epsilongraph acquire model           # ollama server + qwen3.5:4b (pull)

Optional adapters for URL/file acquisition (installed into the venv, never required):

pip install -e '.[acquire]'          # trafilatura, markitdown, html2text
# or pin-by-pin:
.venv/bin/epsilongraph acquire repo install trafilatura

Vault resolution order: --vault → $EPSILONGRAPH_VAULT → ~/.config/epsilongraph/config.json → Obsidian's own registry (obsidian.json, the vault marked "open": true) → cwd. The index lives at ~/.cache/epsilongraph/index.sqlite — outside the vault, so it is never committed and never syncs. The Layer 0 binary, the OSS clones, fetched content, and the acquisition ledger live under ~/.cache/epsilongraph/ too.

Installed state (2026-10-11)

Both MCP servers are registered in ~/.claude.json at user scope and both report ✓ Connected:

epsilongraph:       python3 -m epsilongraph.mcp_server                                    ✓
obsidian-mcp: /home/hugo/codes/EpsilonGraph/vendor/obsidian-mcp/target/release/...  ✓

The skill is installed at ~/.claude/skills/vault-retrieval/. The config was backed up to ~/.claude.json.bak-kgraph-<timestamp> before the change, and the three pre-existing servers (github, filesystem, MiniMax) are untouched.

obsidian-mcp points at the local release build rather than cargo install, so vendor/ must stay. If you would rather not depend on vendor/, run cargo install obsidian-mcp (~31 min) and change the command to obsidian-mcp.

LICENSE: MIT — added 2026-10-16 when the repo went public.

Use

epsilongraph index                       # build; ~0.05s for 103 notes
epsilongraph stats                       # layer / provenance breakdown
epsilongraph skeleton                    # the ~29k-token decision layer (L1)
epsilongraph pack "how should I lay out a new training project" --budget 8000
epsilongraph claim "scaffold split"      # grounding + evidence + projects + contradictions
epsilongraph impact "Principles/Planning Before Execution"   # blast radius
epsilongraph gaps                        # what the vault admits it does not know
epsilongraph search "docking power"
epsilongraph check                       # staleness + integrity
epsilongraph acquire status              # every external resource, with its fix
epsilongraph graph report                # derived-graph connectivity

Progressive disclosure ladder

Level

Content

Size

L0

root note + layer indexes

~2k tokens

L1

epsilongraph skeleton — every rule and relation, no bodies

~29k tokens

L2

epsilongraph pack <query> — the selected full notes

budgeted

L3

the whole vault

~160k tokens

pack runs three passes over the budget: cores first, then key points and gaps, then graph context. A tight budget drops detail, never candidates — and the output states what it dropped.

Derived edges (--auto)

Every index now carries a second, derived graph next to the curated one (graph.py): wikilink / backlink from the author's own links, cotag, couples (co-citation), and lexical similarity. Two rules keep it honest:

  • derived edges live in their own table and are never consulted by impact / load-bearing — a guessed edge must not be able to falsify a note;

  • retrieval uses them only when asked: search --auto, pack --auto, neighbors --auto, ranked below every lexical result (auto:* is glossed with its kind and weight so a reader can tell inference from curation).

epsilongraph graph build                 # rebuild the live index + derived graph
epsilongraph graph report                # connectivity: components, orphans, kinds
epsilongraph search "packing" --auto     # derived context, opt-in
epsilongraph neighbors "Flow Matching" --auto

graph build --source <any markdown tree> builds a graph over foreign markdown (an acquired repo, a fetched inbox) in its own index under ~/.cache/epsilongraph/graphs/ — the live vault's index is never touched.

graph suggest ranks unlinked note pairs by derived evidence (co-citation first, lexical last), asks the local reader model for one of the eight typed buckets, and writes a report — paste-ready lines for the note's ## Relations block. Nothing is ever written into the vault: the typed graph stays hand-curated, which is the product's core claim about quality.

epsilongraph graph suggest --top 8 --out suggestions.md

Resource auto-acquisition

Everything this project does not own — the Layer 0 Rust binary, the reader model, the adapted OSS projects — is fetched, verified, and recorded by one command. All of it stdlib-only, checksum-verified, and --dry-run-able.

epsilongraph acquire status                 # one screen: what is missing, and the fix
epsilongraph acquire obsidian-mcp           # checksummed prebuilt binary -> ~/.cache/epsilongraph/bin/
epsilongraph acquire model                  # ollama + qwen3.5:4b (starts the server, pulls the model)
epsilongraph acquire repo trafilatura       # pinned-commit clone -> ~/.cache/epsilongraph/repos/
epsilongraph acquire fetch --url https://example.com/doc --to ./inbox
epsilongraph acquire import --path paper.pdf --to ./inbox
  • the obsidian-mcp download verifies the release's own sha256 digest before the binary is made executable, and deletes on mismatch;

  • repos are pinned by immutable commit SHA and hash-locked in ~/.cache/epsilongraph/acquisitions.json — a later fetch that does not match the recorded hash is reported as tampering;

  • fetch prefers trafilatura, import prefers markitdown; when neither is installed the stdlib fallback runs and says which path it took;

  • fetched notes carry acquired: true provenance so the vault's epistemic ledger never mistakes external content for ours.

MCP server

Dependency-free newline-delimited JSON-RPC 2.0 over stdio. Nine tools:

vault_pack vault_claim vault_impact vault_gaps vault_search vault_neighbors vault_get vault_skeleton vault_stats

python3 -m epsilongraph.mcp_server --selftest   # drives a real subprocess session
python3 -m epsilongraph.mcp_server              # stdio server

Merge integrations/claude/mcp.json.snippet into ~/.claude.json, and append integrations/claude/CLAUDE.md.snippet to your CLAUDE.md. The skill at integrations/claude/skills/vault-retrieval/SKILL.md installs to ~/.claude/skills/vault-retrieval/.

Eval

python3 evals/run_eval.py                                    # dev set (30)
python3 evals/run_eval.py --queries evals/queries-holdout.tsv # held-out (30)
python3 evals/tune_rrf.py                                    # choose k, check on holdout
python3 evals/compare_pack.py                                # 3 packers, paired sign test
python3 evals/compare_packers.py                             # 8 packers x 2 objectives x AiC proxy
python3 evals/closure_gap.py                                 # the §3 measurement, in one run
python3 evals/synth_corpus.py --out /tmp/epsilongraph-synth        # generate an off-vault corpus
PYTHONPATH=vendor/bm25s python3 evals/compare_bm25s.py       # second BM25, cross-check

Two 30-query sets with expected note paths, R@1/3/5 + MRR, a four-config ablation, and a separate coverage metric for graph expansion.

dev (queries.tsv — where a parameter may be chosen):

config        R@1    R@3    R@5     MRR  misses
lexical      0.70   0.97   0.97   0.821  0
+priors      0.70   0.97   0.97   0.821  0
+graph       0.70   0.97   0.97   0.821  0
full         0.70   0.97   0.97   0.821  0

held out (queries-holdout.tsv — where it is checked):

lexical      0.80   0.97   0.97   0.871  0
graph expansion:  without graph 0/30 (0%)   with graph 24/30 (80%)

The eval has falsified three of the design's own claims, each recorded in the code rather than quietly dropped:

  1. Structural priors do not help. They first measured as harmful (MRR 0.817 → 0.767); after the RRF fix they measure as exactly +0.000 on both sets. Off by default.

  2. Graph expansion's MRR delta is exactly 0.000 — because expansion is kept out of the ranked list by construction, so MRR cannot move. The right metric is coverage: 0/30 → 24/30.

  3. RRF_K = 60 was wrong. See below.

Packing: rank order vs greedy vs Sviridenko

retrieve.pack used to fill the budget in rank order, optimising nothing in particular. packopt.py writes the objective down (THEORY.md §2) and scores all three algorithms on it. Untuned weights, budget 1800 tokens:

split     algorithm    mean f   vs rank_order   sign test
DEV       rank_order    3.098   --              --
(30 q)    greedy        3.576   +15.4%          20W/3L,  p=0.0005
          sviridenko    3.692   +19.2%          20W/0L,  p=0.0000

HOLDOUT   rank_order    3.167   --              --
(30 q)    greedy        3.766   +18.9%          27W/1L,  p=0.0000
          sviridenko    3.813   +20.4%          28W/0L,  p=0.0000

Holdout improves more than dev, and nothing was tuned. rank_order lands within 4% of best_single (3.167 vs 3.041 on holdout) — the old pack was, in objective terms, "pick the single best note and stop".

⚠️ The +20.4% above is not evidence of anything. It was measured before any task-level proxy existed. With one (answer-in-context, ported from the paper that owns this formulation), the ordering reverses: rank_order beats every optimizer on hit@pack, rule_coverage and AiC. The cause is structural — three of f's five terms do not see the query. Full table and the fix in THEORY.md §2; run python3 evals/compare_packers.py.

The dependency-closure gap

Where this is going: RESEARCH.md — the plan and the decision points. The executable version is todo.json (15 tasks, each with a falsifiable done_when, all runnable locally: pure Python + qwen3.5:4b via ollama).

The gap below is the project's research problem. Short version: 10 of 103 notes have no dependency-closed pack at all under an 1800-token budget (h({i}) runs min 17 / median 816 / max 2495 tokens), while at 4000 every one of them does — a feasibility threshold, not a binary verdict. Reframed as h(S) = cost_full(S) + cost_skel(closure(S)) it becomes a monotone submodular constraint (verified exactly), so the problem is a non-monotone submodular objective under a submodular knapsack — the corner neither half of the published literature covers.

The vault's own loading rule ("rules before procedures … you will follow the steps while violating the intent") is a constraint on what a pack must contain, and no packing objective expresses it. Measured on the 30 holdout queries: 24 packs contain the note that answers the query, and 0 of those 24 carry its dependencies.

The obvious fix is unavailable. Both natural closure terms — edges with both endpoints inside the pack, and edges leaving it — are supermodular (worst violation +2.0), so a closure-aware packer is outside the submodular-packing literature that the 2026 papers, and this repo, live in. Three mechanisms were built and all three fail: post-hoc repair reaches 100% closure at 0.00 hit@pack, closure-by-construction at 0.03, and a soft term is a no-op (the budget is already spent). Details in THEORY.md §3; run python3 evals/closure_gap.py.

Does it hold off-vault? (evals/synth_corpus.py)

Every number above comes from one corpus: the author's own vault, 103 notes, hand-typed relations. That is the project's weakest flank — a result that holds only on a graph you built by hand is a case study, and no second real vault has the typed-edge, two-tier structure the question needs. So the second corpus is generated, with the dependency graph, each note's closure size, and each query's target set by construction rather than observed.

python3 evals/synth_corpus.py --out /tmp/epsilongraph-synth --notes 120 --depth 3
python3 -m epsilongraph --vault /tmp/epsilongraph-synth index
EPSILONGRAPH_VAULT=/tmp/epsilongraph-synth EPSILONGRAPH_INDEX=/tmp/epsilongraph-synth.sqlite \
    python3 evals/closure_gap.py --queries /tmp/epsilongraph-synth/_queries.tsv

It reproduces the vault's three structural facts — the gap (28/30 packs contain the answer, 15/30 carry its dependencies), both closure terms supermodular at +2.0, f itself exactly submodular — and it turns the vault's observed slot-competition explanation into a controlled curve. --depth sets closure size; the closure-requiring packer's hit@pack falls monotonically as it rises while the unclosed packer stays flat:

closure median      2      5      6     12     <- --depth 2 3 4 6
amort+closed     1.00   0.90   0.93   0.63    hit@pack
amort+rel        1.00   1.00   1.00   0.90    hit@pack, unclosed

Closure stays affordable at every depth — 100% of notes are closable at 1800 — and still costs retrieval. That separation, "affordable" against "free", is what the live vault could show but not vary. The generator prints a manifest so the packer's output can be checked against ground truth fixed before it ran.

Scope, stated so the table is not over-read: the synthetic notes carry planted lexical signal and a uniform-depth graph, where the real vault has neither. Only the shape of the tradeoff transfers across corpora, not absolute R@k.

The reader grid was re-run on this corpus at two closure depths (seed 7, 120 notes, --depth 2 the control and --depth 6 the treatment, qwen3.5:4b, n=30; archived to evals/reader-eval-synth-{d2,d6}-2026-10-12.json). What transferred and what did not — one row per headline number, with a verdict — is the table in RESEARCH.md §P6.3. The short version: the depth-vs-hit@pack curve and the slot-competition mechanism transfer; the levels do not, and the closed-vs-unclosed present gap is invisible at depth 2 because a median closure of 2 leaves nothing to carry.

What the second BM25 found

evals/compare_bm25s.py ranks the same 103 notes with xhluca/bm25s — an independent BM25 on numpy sparse matrices, different tokenizer, different scoring constants, no field weights. Two independent implementations over one corpus is the only way to tell "the ranking is fine" from "my ranking agrees with itself".

It agreed on 64% of top-10 sets and disagreed usefully: it exposed a query where the correct note is ranked #1 by the title retriever and #18 after fusion.

The cause was arithmetic, not taste. Score is 1/(k + rank). With k=60 and a candidate depth of 50, every contribution lands in [1/110, 1/61] — under 2× spread. So appearing in a second list (2×) beat being ranked first (1.8×). Ubiquity outranked strength. k=60 is tuned for corpora of thousands of documents; at 103 notes it is a flattening filter.

k=1 was chosen on dev and confirmed on holdout, where MRR is monotonic in k:

k

dev MRR

holdout MRR

1

0.821

0.871

8

0.826

0.861

60

0.817

0.830

The split earned its keep: k=8 scored best on dev and worse on holdout — it was fitted to the 30 dev queries and would have shipped as an improvement. evals/tune_rrf.py flags that case as FITTED automatically.

With n=30, one query is 0.033 MRR. Differences below ~0.03 are a single query and are not findings.

Vendored

Four upstream projects, cloned to vendor/ and adapted. None is imported by epsilongraph/ — the package stays dependency-free; they are measurement and infrastructure, not runtime dependencies.

Project

Role

Adaptation

Verified

lstpsche/obsidian-mcp

Layer 0, raw vault I/O

built from source (v3.1.0, 29 MB, 30m51s); wired into integrations/claude/mcp.json.snippet

live MCP session: initialize, tools/list → 10 tools under the read profile, search_text "scaffold split" → Concepts/Data Splitting and Leakage, the same note epsilongraph returns

xhluca/bm25s

independent BM25

evals/compare_bm25s.py — the cross-check that found the RRF bug

runs from the clone with numpy alone; 64% top-10 agreement with epsilongraph, 2 queries only epsilongraph finds, 0 neither

nayanananto/Diagnostic-for-Budget-Constrained-Multi-Hop-RAG (ACE-RAG)

the published objective and the task-level metric

epsilongraph/priorart.py reimplements ace_rag/evidence_packer.py's objective; epsilongraph/aic.py ports ace_rag/context_quality.py verbatim

objective re-measured monotone and submodular on our corpus (their published claim), so the port is faithful; the metric matches the original on 8 cases including the adversarial bag-of-words one — pinned by tests/test_closure.py

joelvarun/facetpack

cost-aware submodular packing, a second independent implementation

read, not imported; its single-best guard was compared against ACE-RAG's

its guard compares a score against a sum of scores — two different quantities — where ACE-RAG's correctly calls its own _objective_value. Noted, not copied

The last two are the 2026-10-12 prior-art adaptation. Their ace_rag/ cannot run here (it needs Qwen2.5 3B/7B/14B from HuggingFace, which this machine cannot reach), so the two pieces that are reader-independent were extracted instead: the objective and the metric. vendor/ is gitignored except ace-rag/ace_rag/, which tests/test_closure.py imports for the parity check.

One vault-pollution note. obsidian-mcp keeps per-vault state at {vault}/.obsidian-mcp. Setting OBSIDIAN_MCP_DATA moves the heavy cache (BM25 index, embeddings) to ~/.cache/obsidian-mcp/vaults/{slug}/, but the server still creates a small vault-local .obsidian-mcp/ignore by design. It contains no markdown. epsilongraph/parse.py skips the directory anyway — that is insurance against the coupling, not a fix for a bug that happened.

Not vendored: AnswerDotAI/rerankers was the right second choice on paper — cross-encoder reranking is the one technique in the "non-negotiable tier" this project has not implemented, its core is dependency-free, and its FlashRank backend is ONNX rather than torch. It was rejected because its models download from HuggingFace, which this machine cannot reach (curl: (35) Recv failure: Connection reset by peer; PyPI is fine). A reranker that cannot load a model is not a reranker. The design's no-dense conclusion is unchanged either way: at 103 notes with R@5 0.97 there is no measured headroom to win back.

Tests

python3 -m unittest discover -s tests

126 tests in eleven groups:

  • parity with the pre-existing validate_links.py on the live vault — note set, link count, broken-link set, and per-note relation buckets must match exactly. Two parsers for one corpus is a drift generator; the agreement is tested, not asserted.

  • ranking — pins the three properties that measurement found broken: that rank outweighs list membership, that a near-exact title match reaches the top 5, and that the held-out set is still held out.

  • closure — three kinds, kept separate on purpose. Parity: epsilongraph/aic.py must agree with the vendored original, and the ported ACE objective must be monotone and submodular on our corpus as published. Properties: both natural closure terms must stay supermodular — if one is ever found submodular, THEORY.md §3 collapses into §2 and the test says so. Invariants: the mechanisms return closed, within-budget sets, and the production packer still does not.

  • docs consistency — the numbers the documents agree to repeat are recomputed from the repo and compared: the corpus size, the dependency-edge count, the test count, and the file layout. Five numbers were corrected in code and left stale in prose on 2026-10-12, each found by grepping by hand. This turns that into a failure. It deliberately allows a correction note to name the value it corrects — forbidding that would forbid the retraction itself, which is what the first version of the check did.

  • reader harness — the four claims of evals/reader.py, testable without a model: that it is stdlib-only (the AST is parsed and the import set checked against a whitelist), that the request sends think: false (without it the model puts everything in thinking and every answer scores as wrong), that the answer is read from response, and that the cache key covers the whole request rather than the prompt alone.

  • synthetic corpus — the artefact assertions of evals/synth_corpus.py, the off-vault validation. Every other test in this repo assumes the live vault; this one asserts that a generated corpus actually has typed dependency edges, a non-degenerate closure distribution, a depth knob that moves it, and query targets that exist in it. It exists because the generator failed in exactly that way twice during construction — a bullet-prefixed Relations header and a bucket name that was a sentence — and both times it produced a corpus that indexed cleanly, looked plausible, and had zero typed edges. A closure measurement against such a corpus reports that closure costs nothing, and is believed.

  • reader report — the confidence intervals and paired tests of evals/reader_eval.py's report(), testable without a model. report() reads an acc dict of per-query 0/1 lists, which is exactly what the archived reader-eval-*.json holds, so the offline path (--from-json) is exercised both against a hand-built dict and against the real archive. The assertions require the Wilson [k/n] count and the paired [n=...] marker to be present, because a p-value without its denominator is the failure this project keeps recording — a test that merely checks "a p appears" would pass on the report that drops the n.

  • unit — gloss retention, gap-bullet splitting, the table-escaped-pipe contract.

  • acquire — platform→asset mapping for the Layer 0 download, checksum verification (mismatch ⇒ deleted, never kept), tarball extraction and ledgering, the stdlib HTML fallback, the one-screen status report, and the zero-runtime-dependency invariant (the optional trafilatura / markitdown / html2text adapters may only ever be imported inside a function).

  • graph — the derived-edge computation (exact edge sets, thresholds, co-citation, orphan agreement) on a fixture vault; that curated pairs are never offered as missing-link suggestions; that a foreign --source gets its own index under graphs/ and never resolves the live vault's.

Layout

epsilongraph/
  parse.py       # frontmatter, callouts, glossed relations, gap bullets
  config.py      # vault resolution (5 steps), index location
  index.py       # SQLite + FTS5 build; provenance classification; staleness
  retrieve.py    # 4-retriever RRF, budgeted packing, skeleton
  ledger.py      # claim / impact / gaps — the epistemics
  severity.py    # blast radius as a semiring closure over a severity lattice
  packopt.py     # f as a set function; greedy / Sviridenko; closure machinery
  priorart.py    # the published objective (arXiv:2607.00725), ported
  aic.py         # answer-in-context, ported verbatim — the task-level proxy
  cli.py         # subcommands
  acquire.py     # resource auto-acquisition: Layer 0 binary, ollama model, OSS clones, URL/file fetch
  graph.py       # automatic KG construction: derived edges over any markdown tree, suggestions
  mcp_server.py  # stdio JSON-RPC, 9 tools, --selftest
evals/
  queries.tsv           # 30 dev queries
  queries-holdout.tsv   # 30 held-out queries (do not edit)
  run_eval.py           # R@k / MRR / ablation / graph coverage
  tune_rrf.py           # choose k on dev, check on holdout, flag FITTED rows
  compare_pack.py       # 3 packers, paired sign test -- where THEORY 2's p-values come from
  compare_packers.py    # 8 packers x 2 objectives x the AiC proxy (means, no test)
  stats.py              # the shared statistics: sign test, Wilson + bootstrap CIs, permutation
  closure_gap.py        # the dependency-closure measurement (§3)
  closure_sweep.py      # the depth x budget frontier (P1b)
  synth_corpus.py       # a second, generated corpus with a known dependency graph
  config_manifest.py    # one JSON: vault/index paths, budgets, SEED, reader model, deps
  reader.py             # the local reader: stdlib only, self-starting, cached
  reader_eval.py        # the task-level eval and its grader (P2)
  compare_bm25s.py      # second independent BM25 over the same corpus
  reader-eval-*.json    # the recorded runs, so a number in the docs is traceable
tests/           # parity + ranking + closure + reader + report + docs + unit + synthetic
integrations/    # Claude Code mcp.json / CLAUDE.md / skill
vendor/          # upstream clones — only ace-rag/ace_rag/ is committed
Makefile         # make repro (offline evals -> results/), make repro-reader, make test

Available Tools

10 tools
vault_claimA

The epistemic state of one rule or note: its grounding, whether it has been tested and in how many distinct projects, recorded disagreements, blast radius if changed, and what the note admits it does not know. Use before relying on a rule, and before changing one.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteYesnote path, basename, or distinctive substring

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden, and it discharges most of it by disclosing the substantive contents of the response — tested status, project count, recorded disagreements, change blast radius, and acknowledged unknowns. It does not explicitly state that this is a pure read operation or note any cost/format constraints, but nothing is contradicted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with what the tool returns and ending with the usage condition. The first sentence is a dense list but every item earns its place by signaling a distinct return dimension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description must convey return content — and it does, enumerating the components of the epistemic state. For a single-item lookup this is nearly sufficient; only response format or aggregation behavior is left unspecified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single parameter with 100% schema description coverage ('note path, basename, or distinctive substring'), so the schema fully documents it. The description adds no further lookup-syntax guidance, making the baseline 3 appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (one rule or note) and enumerates the dimensions of its 'epistemic state' — grounding, testing, disagreements, blast radius, unknowns. That is far more informative than a generic getter, though it never distinguishes itself from siblings like vault_impact (blast radius) or vault_get (generic retrieval).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives two explicit triggers: 'Use before relying on a rule, and before changing one.' That is clear context for invocation. It stops short of naming an alternative or an exclusion (e.g., when vault_impact or vault_gaps is the better choice), so it sits just below the top band.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_gapsA

What the vault itself admits it does not know, gathered from the notes' own [!todo] and [!question] callouts. Read this BEFORE proposing a new direction: the corpus may already record the gap, or may already have closed it.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
queryNo

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses the data source (the notes' own callouts) and implicitly frames this as a read operation, but says nothing about pagination, limit behavior, ordering, or what the results look like.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the resource definition and followed by the actionable directive. Every clause earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Purpose and timing are well covered, but with no annotations, no output schema, and 0% parameter coverage, the definition leaves the calling mechanics (what `query`/`limit` do, what comes back) unexplained for an agent trying to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description explains neither `limit` nor `query`. An agent cannot tell whether `query` filters the gaps, searches the notes, or something else, and there is no hint about default or bounds for `limit`.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific and distinctive resource: gaps the vault admits it doesn't know, sourced from [!todo] and [!question] callouts. This clearly separates it from generic siblings like vault_search or vault_get. It lacks a crisp verb, but the scope and data source are unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit when-to-use condition: 'Read this BEFORE proposing a new direction', plus the rationale that the corpus may already record or have closed the gap. No named alternatives or when-not guidance, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_getB

Full markdown of one note. Use after pack/search to drill in.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteYes

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It says what is returned but not that this is a read-only retrieval, what happens for a missing/ambiguous note, or whether the identifier is a path, title, or ID. For a tool with zero annotation coverage, this is thin.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short fragments, zero waste, with the primary outcome front-loaded before the usage hint. It borders on under-specification rather than verbosity, but the structure is sound.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The simple shape (one required param, no output schema, no annotations) is partly covered: return content ('full markdown') is stated. However, identifier format and failure behavior for a missing note are unaddressed, which an agent needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single 'note' parameter has 0% schema description coverage, and the description only restates that it targets 'one note'. It never clarifies the identifier format (name vs. path vs. slug) or whether it is case-sensitive, leaving the only parameter effectively undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource and scope: 'Full markdown of one note.' The word 'Full' implicitly contrasts with vault_skeleton (outline) and vault_search (matching), so an agent can roughly separate it from siblings, though no sibling is named. Not a tautology.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use after pack/search to drill in' gives an explicit ordering/context: call this to expand a note surfaced by those tools. It names alternatives and a triggering condition, but offers no when-not guidance or mention of what to use if the note isn't found.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_impactA

The blast radius of changing a note. By default this is the TRANSITIVE closure, not depth-1: depth-1 understates the falsified set by a median of 3.9x and up to 9.5x on the notes that matter. semantics='falsified' (default) answers 'what is now FALSE?' and follows only 'Governed by', because that is the one bucket asserting content conformance. semantics='needs_reread' also follows 'Read first', which asserts a reading order rather than conformance, and answers 'what must I re-examine?' -- a much larger set. Run before editing any framework note.

ParametersJSON Schema
NameRequiredDescriptionDefault
deepNofalse = the old depth-1 view
noteYes
semanticsNofalsified

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full load and does so well: it discloses the transitive-by-default behavior, quantifies how badly depth-1 understates impact (median 3.9x, up to 9.5x), and explains what each semantics mode follows ('Governed by' vs 'Read first'). It omits return format and cost/performance, but the behavioral core is genuinely disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with a clear one-line purpose, then rationale and mode semantics. It is dense and jargony ('Governed by', 'framework note', the multiplier statistics), but each sentence carries information rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-param analytical tool with no output schema and no annotations, the description supplies the semantics of both meaningful parameters and the default behavior, enough to call it correctly. It does not describe the shape of the returned set, which an agent might want.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 33% (note is undocumented; semantics has enum values but no description), so the description must compensate. It adds substantial meaning for semantics, distinguishing 'falsified' from 'needs_reread' by edge type and framing the resulting question, and clarifies deep implicitly via the transitive-vs-depth-1 contrast.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The phrase 'blast radius of changing a note' states a specific resource and operation, and the transitive-vs-depth-1 discussion pins down exactly what is computed. It stops short of contrasting itself with graph-adjacent siblings like vault_neighbors or vault_load_bearing, so an agent must infer the difference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Run before editing any framework note' is explicit when-to-use guidance scoped to a class of notes. It does not name an alternative tool or state exclusions (e.g., when depth-1 or neighbors is preferable), so it falls short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_load_bearingB

Every note ranked by how much of the vault a change to it would falsify. On this vault the answer is bimodal: a handful of notes falsify 30-50% of the corpus and the large majority falsify nothing. Use this to decide where to be careful.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
semanticsNofalsified

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses the character of the result (bimodal: a few notes at 30-50% falsification, most at none) and the semantics of the ranking, which is real behavioral value. It says nothing about truncation via limit, cost, or what the returned records look like.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with what the tool returns. The middle sentence is domain flavor but it does convey the actual distribution an agent will see, so it earns most of its space.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No annotations, no output schema, and two undocumented parameters with zero schema coverage. The description covers the ranking concept but not what a call actually returns or how limit/semantics shape it, leaving the agent under-informed for a tool with this much structured ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and neither parameter is explained. The description only implies the default "falsified" semantics; the "needs_reread" enum value and the limit/pagination behavior are left entirely undocumented, so it fails to compensate for the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific operation: rank every note by how much of the vault a change to it would falsify. That is a clear verb+resource, well beyond a tautology. It does not, however, differentiate itself from the sibling vault_impact, which sounds like it could compute a similar thing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use this to decide where to be careful" gives an intended purpose, but there is no when-not guidance, no prerequisites, and no routing against the ten sibling tools (notably vault_impact). Usage is implied rather than specified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_neighborsB

Typed neighbours of a note. The eight buckets are not equivalent: Governed by and Read first are constraints, Source and Evidence are grounding, Applied in is where it is executed, See also is the weakest.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteYes
bucketsNocomma-separated subset

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are supplied, so the description carries the full behavioral burden. It does disclose genuinely useful semantics — that the eight buckets have unequal strength and what each family means — but omits default behavior when 'buckets' is omitted, error handling for unknown notes, and any read-only assurance.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with what the tool returns, then the payoff sentence on bucket hierarchy. Dense but not padded; the only weakness is the clipped noun-phrase opening.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter read tool with no annotations and no output schema, the description covers the conceptual output structure well but leaves the default bucket set, per-neighbour return shape, and note identifier expectations unspecified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 50% and there are no enums, so the description must compensate. It names six of the eight valid bucket values (Governed by, Read first, Source, Evidence, Applied in, See also) and explains their relationships, which the schema's bare 'comma-separated subset' does not; it stops short of listing all eight or the exact token format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource ('neighbours of a note') and the qualifier 'typed', so an agent can tell it returns graph-related notes rather than note content (vault_get) or a search result set. It lacks a verb and makes no explicit sibling contrast, but the scope is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to call this versus vault_get, vault_impact, or vault_gaps. The bucket commentary describes how to interpret output, not when the tool is the right choice, so usage is only faintly implied by the resource name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_packA

Budgeted, provenance-annotated context pack for a task or question. Returns ranked notes with WHY each matched, whether it is grounded (and by what), what constrains it, and what it applies to. Use this first for any question about how to do something in this vault. Cheaper and more trustworthy than reading whole notes.

ParametersJSON Schema
NameRequiredDescriptionDefault
kNo
queryYesthe task or question
full_bodyNo
budget_tokensNo
grounded_onlyNoonly notes with a Source or Evidence edge

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does substantial work: it discloses a token budget, that results are ranked and provenance-annotated, and what each result is annotated with (why matched, grounded-by, constraints, applicability). It stops short of stating it is strictly read-only, what happens when nothing matches, or any result-size guarantees beyond the budget framing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with what the tool is, then what it returns, then when to use it. Dense but every clause carries information; minor jargon ("provenance-annotated", "what constrains it") costs a little clarity without bloat.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no annotations and no output schema, the description is the sole carrier of behavior and it adequately explains the return shape and the raison d'être (cheaper than reading whole notes). Gaps remain around the undocumented k and full_body parameters and any failure/empty-result behavior, but nothing essential to calling it correctly is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is only 40%, so the description needs to compensate and only partly does: "Budgeted" maps to budget_tokens and "grounded" maps to grounded_only, but k and full_body are never addressed in prose. Baseline 3 is appropriate given the schema's partial coverage and the description's partial fill of the gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (a budgeted, provenance-annotated context pack for a task or question) and states what it returns: ranked notes annotated with match reasons, grounding, constraints, and applicability. It implicitly separates itself from siblings like vault_search and vault_get by framing itself as the cheaper first-pass reader, though it never names those siblings directly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use this first for any question about how to do something in this vault" gives clear when-to-use guidance and positions the tool ahead of alternatives ("Cheaper and more trustworthy than reading whole notes"). What is missing is an explicit when-not / routing rule naming the sibling tools (vault_search, vault_get, vault_load_bearing) for cases this tool does not cover.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_skeletonA

Every rule and every typed relation in the vault, with no bodies (~29k tokens here, 18% of the corpus). Small enough to hold for a whole session, which is usually better than repeated searching.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations, so the description carries the full burden. It discloses token cost (~29k, 18% of corpus) and behavioral scope (no bodies) — genuinely useful context for planning whether to hold it in context. It doesn't disclose auth or freshness/caching behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the scope and followed by the usage rationale. Every clause earns its place — token count and corpus percentage are concretely useful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given zero params, no annotations, and no output schema, the description does a good job of framing scope and cost tradeoffs. Could be stronger by stating what the agent is expected to do with the skeleton next (e.g., pair with vault_get for bodies).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so the baseline is 4. The description correctly implies the parameterless nature by framing it as a whole-vault view.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource ('every rule and every typed relation in the vault') with scope exclusions ('with no bodies'). Without named siblings, it doesn't explicitly distinguish itself from vault_load_bearing or vault_gaps, but the scope wording implies a complete-but-shallow view.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives guidance: 'Small enough to hold for a whole session, which is usually better than repeated searching.' This steers the agent to prefer skeleton over vault_search for session-wide holds. It doesn't name which sibling to use when bodies ARE needed, which is a minor gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

vault_statsB

Index size, layer and provenance distribution, and freshness.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden but discloses nothing beyond the return metrics. It does not state that the operation is read-only, nor does it mention any permissions, side effects, or rate limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no wasted words. It efficiently communicates the key return categories.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter, read-only stats tool, the description lists the returned metrics but omits details about format (e.g., counts vs percentages) and does not clarify domain-specific terms like 'layer' and 'provenance'. It is adequate but leaves notable gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes no parameters, so there is nothing to document in the description. The baseline score for zero parameters is 4, and the description adds no misleading parameter information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the metrics returned: index size, layer and provenance distribution, and freshness. It distinguishes from siblings by being a read-only stats report rather than a search or retrieval action. However, it lacks a specific verb like 'retrieve' or 'get', so it stops just short of the top score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance on when to use this tool versus alternatives. The implied usage is to obtain vault statistics, but the description gives no context about prerequisites or conditions that select it over siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 10 tool updatesv0.1.0
    • First observedvault_claim
    • First observedvault_gaps
    • First observedvault_get
    • First observedvault_impact
    • First observedvault_load_bearing
    • First observedvault_neighbors
    • First observedvault_pack
    • First observedvault_search
    • First observedvault_skeleton
    • First observedvault_stats

TDQS

A3.5/5.0

Scored across 10 tools

Disambiguation4/5

Most tools target clearly distinct operations (search, get, pack, neighbors, skeleton, stats, gaps). However, vault_load_bearing vs vault_impact both compute falsification/blast radius (global ranking vs per-note transitive closure), and vault_claim also surfaces blast radius, so an agent could conflate these three. The rich descriptions mostly resolve the overlap, but the boundaries are not as crisp as they could be.

Naming Consistency5/5

Every tool uses the same vault_ prefix followed by a single descriptive noun (search, get, pack, neighbors, skeleton, stats, gaps, claim, impact, load_bearing). The convention is uniform snake_case throughout with no mixing of styles or verb forms.

Tool Count5/5

Ten tools is well-scoped for a knowledge-vault query and analysis server, with each tool earning its place across retrieval (search, get, pack), structure (neighbors, skeleton), and epistemic analysis (claim, impact, load_bearing, gaps, stats). Nothing feels redundant or padding.

Completeness4/5

The read/analysis surface is comprehensive: retrieval, structure, provenance, epistemic state, and impact are all covered, plus a session-level skeleton and stats. The one notable gap is that descriptions repeatedly reference editing and changing notes, yet no mutation tool (edit/update) exists in the set, leaving a workflow dead-end if agents are meant to act on the vault.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Local-first agentic knowledge layer over Obsidian notes, enabling MCP-aware agents to search, retrieve, and compile knowledge with provenance and task contracts.
    37
    11 npm
    MIT
  • F
    license
    B
    quality
    B
    maintenance
    Parses an Obsidian vault and exposes structured context (notes, blocks, tasks, headings) through MCP tools, enabling AI clients to query and reason over local markdown notes.
    19
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Local-first knowledge backend for AI agents that connects MCP hosts to an Obsidian-compatible vault with indexed retrieval, token-budgeted memory recall, and secure ingestion.
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Serves as a unified MCP gateway to an Obsidian vault, enabling AI agents to perform hybrid lexical/vectorial semantic search, explore note graphs, and write through consent-based intent batches with conflict detection.
    -