Skip to content
Daniel KliewerThe book

Hermes Atlas v0.1: A Local-First, Incrementally-Compiled, Falsifiable Knowledge System

By
7 min read
Hermes Atlas v0.1: A Local-First, Incrementally-Compiled, Falsifiable Knowledge System

Hermes Atlas v0.1: A Local-First, Incrementally-Compiled, Falsifiable Knowledge System

August 9, 2026 · Daniel Kliewer


Most "agentic knowledge" tooling is query-time retrieval with a graph bolted on. Microsoft's GraphRAG is the reference case, and it has two documented weaknesses that a compile-time design fixes:

  1. No native incremental update. New information forces a full graph rebuild.
  2. Community summaries at query time. Adds 2–3× latency to every query, repeated forever.
  3. Conflicts are handled by majority vote + minority caveat — a good instinct left unformalized.

Hermes Atlas v0.1 takes the opposite stance: extract once, at compile time, into a versioned static artifact; keep contradictions as first-class nodes; and prove the graph is doing work a keyword search cannot with a Popper-style canary.

This post is a replication guide. Every command below was run for real, on a Mac, on Python 3.14. The numbers are from the actual runs, not illustrations. The source, README, and the longer replication guide live at kliewerdaniel/hermes-atlas.


0. Environment

# Python 3.14
/opt/homebrew/bin/python3.14 --version   # => Python 3.14.6

mkdir -p ~/Documents/Projects/hermes-atlas
cd ~/Documents/Projects/hermes-atlas
python3.14 -m venv .venv

# stdlib-only at runtime; only dev dep is pytest
env -u PYTHONPATH -u PYTHONHOME ./.venv/bin/python -m pip install -q pytest

Note on the env -u … prefix. Under the Hermes shell the active venv leaks PYTHONPATH/PYTHONHOME from a different interpreter, which breaks imports (ModuleNotFoundError: pydantic_core). Stripping both before invoking the project interpreter is the fix. On a clean shell you don't need it. This cost me real debugging time; record it so you don't lose the same hour.

The package is hermes_atlas/ with modules store, extract, confidence, contradictions, graph, gaps, personas, canary, compiler, loop, cli, ingest, inference. 53 tests. All green.

env -u PYTHONPATH -u PYTHONHOME ./.venv/bin/python -m pytest -q -p no:cacheprovider
# 53 passed in 0.65s

1. The changelog is built first, on purpose

GraphRAG lacks a durable mutation log, so it cannot know what a delta recompile is allowed to leave alone — every ingest degenerates into a full rebuild. Atlas inverts that: the append-only changelog is the source of truth, and the store is silent on unchanged content.

# store.py — the no-op that makes delta provable
def write(self, collection, record, cycle=0, pass_id=""):
    chash = content_hash(record)
    existing = self.read(collection, record["id"])
    if existing is not None and prev_hash == chash:
        return False          # silence: nothing written to the changelog
    ...

A changelog that stays silent when inputs are identical is what lets you prove a delta recompile touched nothing it shouldn't.


2. Compile-time extraction (reuse, don't rebuild)

The extraction module is ported, not rewritten, from the Sovereign Knowledge Compiler: split into sentences, tag each with a controlled-vocabulary stance (support / contradict), extract co-mentioned entities as relationships. It is deterministic — no model — so it runs once per source, keyed on checksum.

Extraction provenance is tracked separately from the source checksum:

# compiler.py
def _needs_extraction(self, source):
    prev = self.store.read("sources", source["id"])
    if prev is None:
        return True
    return prev.get("extracted_checksum") != source.get("checksum")

Bug we hit (and you will too). The first version compared the source checksum to decide extraction. But ingest writes the source without extracting — so the compiler saw checksum == checksum and skipped extraction, producing a 0-claim graph silently. The fix: track extracted_checksum independently, and preserve it across recompiles so a second identical compile is a true no-op. There is a regression test: test_ingest_does_not_pre_extract.


3. Confidence that never silently overwrites

A claim with no supporting evidence scores exactly 0.0 — not a small positive from a recency prior. That load-bearing line is what guarantees "never silently overwrite":

# confidence.py
if not evidence:
    return ConfidenceResult(score=0.0,
        note="no supporting evidence: claim is asserted, not evidenced")

Independence is counted by domain/author, not source count — ten posts from one blog are one independent source, and a claim repeated across syndicated copies is penalised, not boosted (corroboration only counts distinct independent domains). Conflicting evidence lowers confidence but both sides survive.


4. Contradictions are first-class nodes

When two claims oppose each other, Atlas creates a contradiction record with its own id. Both claims keep their own record and confidence. Resolution is an explicit, logged act — never a side effect of the newest source winning.

# contradictions.py
def mine_contradictions(claims, min_overlap=0.30):
    # pair on shared NON-generic content words, opposite polarity
    # anchored on >=1 non-generic word (trims "AI is X / AI is not X" noise)
    # NO upper-overlap bound: a direct negation shares 100% of its words
    # and IS the contradiction we want, not a duplicate

Two bugs surfaced here, both real:

  1. The miner originally anchored only on proper nouns, so thesis-level contradictions ("the model is local-first" vs "…is not local-first") were missed. Fixed by anchoring on shared non-generic content words.
  2. The pair's claim_a/claim_b were ordered by dict iteration, which shifts when new sources are added — so the contradiction record's content hash changed on every delta recompile, looking like corruption. Fixed by canonicalising a = min(claim_a.id, claim_b.id). Regression test: test_delta_recompile_keeps_internal_contradictions_stable.

5. Incremental communities + cached summaries

Community detection is a label-propagation append: existing labels are the seed, only new/adjacent nodes may move (stability() reports the drift vs a full recompute). Summaries are compiled once and cached against a membership hash.

# Track A: full compile vs delta recompile
compile #0 (full)            in 10.55s   changelog entries added 3471
compile #1 (incremental)     in  0.34s   changelog entries added   11
community stability vs full rebuild: 1.000

The delta added 11 entries (vs 3471 for full) and prior-record violations: 0 — the delta path provably touched only the 3 new sources plus their 2 new claims. That is the whole point of building the changelog first.

Another determinism bug: Graph node order followed dict insertion order, which depends on g.nodes iteration — non-reproducible across runs. Fixed by keeping an explicit _order list and sorting the propagation order. And summary_cached (a diagnostic flag) was being persisted on the community record, flipping False→True on rerun and breaking byte-identity. Removed it from the artifact; it belongs in the compile report, not the stored record.


6. The four-persona question generator

Gap analysis picks the weakest region; four adversarial personas each propose a next research question; they are ranked by expected information gain, not vote-counting. The Skeptic (falsifiability) gets a multiplier.

# Track A gap analysis → 4 proposals
[selected] Skeptic       0.938  (region=weakest_claims, falsifiable=True)
[logged ] Practitioner   0.709  (region=weak_central_concepts)
[logged ] Historian      0.511  (region=stale_nodes)
[logged ] Analogist      0.047  (region=incoherent_communities)

The loop has an explicit stopping condition: N iterations (default 5) or a confidence gate. No open-ended loops in v0.1.


7. The falsifiability canary (the honest part)

Before trusting the loop's self-assessment, the canary checks the compiled graph against cheaper baselines and external ground truth.

Negative controls (trackB_probes.json, 5 probes):

probe                       graph  keyword  random   margin  ok
probe-ai-agent-def           0.33    0.33    0.00     0.00   Y
probe-compile-cache          0.75    0.75    0.00     0.00   Y
probe-local-first            1.00    1.00    0.67     0.00   Y
probe-community-summary      0.00    0.50    0.00    -0.50   N
probe-conflict-handling      0.00    0.00    0.00     0.00   N
verdict: trusted (pass rate 0.6)

Two probes fail — one where the graph genuinely underperforms keyword on a generic term. That is the canary working: it flagged the weak spot instead of hiding it.

Held-out rediscovery (3 valid probes, real sentences withheld):

probe-local-machine    withheld: 4   targeted: False  leaked: False
probe-local-fragile    withheld: 1   targeted: False  leaked: False
probe-compile-artifact withheld: 5   targeted: False  leaked: False
verdict: flagged (pass_rate 0.0)

All three withheld sentences were correctly not leaked into the graph (redaction works). But the loop did not regenerate a question targeting the removed fact. On a 40-source extractive corpus, the deterministic gap heuristic targets the weakest scored region, not the specific missing phrase.

This is the headline lesson. The current gap-analysis is a confidence/centrality heuristic. It does not localise to a specific held-out fact well enough to regenerate a targeted question. That is a real v0.2 item, and the canary caught it. A system that never fails this test is more suspicious than one that fails occasionally and says so.


8. What I would do differently / v0.2

  1. Gap localisation. Replace the region-level heuristic with claim-level coverage gaps so a withheld fact produces a targeted question.
  2. Real incremental Leiden. The frozen-core append is honest but drifts; a proper incremental Leiden (or regular full recompute gated by changelog size) would tighten stability.
  3. Synthesis passes. The --local model path (llama.cpp on :8080) was available but the synthesis passes (richer summaries, persona questions) want a real model. Deterministic compile works without it; synthesis is a strict upgrade.

9. Reproduce it

git clone https://github.com/kliewerdaniel/hermes-atlas hermes-atlas
cd hermes-atlas
python3.14 -m venv .venv && ./.venv/bin/python -m pip install -q pytest

# Track A: 20 blog posts + 1 repo
./.venv/bin/python -m hermes_atlas.cli --store trackA \
  ingest ~/Projects/blogback/sovereign-ai-site/content/blog \
  --repo ~/Projects/sovereign-knowledge-compiler --limit 20 --repo-limit 20
./.venv/bin/python -m hermes_atlas.cli --store trackA compile

# Track B: canary
./.venv/bin/python -m hermes_atlas.cli --store trackA canary --probes trackB_probes.json

Expected: ~990 entities, ~565 claims, ~something relationships, ~15 communities, ~6 contradictions after the full compile; delta recompile adds ~11 changelog entries with 0 prior-record violations; canary negative-controls trusted at 0.6, held-out rediscovery flagged at 0.0. If you get different numbers, the first place to look is the determinism bugs in §4–§5 — they are the ones that bite.

The repo is the artifact. The failures above are the findings.


Full source, README, and the longer replication guide: github.com/kliewerdaniel/hermes-atlas

Related research

Aug 9, 2026

Hermes Atlas v0.2: Claim-Level Coverage, a Determinism Gate, and a Knowledge-Mutation Canary

Hermes Atlas v0.2 is a benchmark-and-fix release: claim-level coverage localisation, a determinism gate that proves compile reproducibility, and a knowledge-mutation canary. Every number below was measured, not asserted.

Aug 9, 2026

Hermes Atlas v0.3: An Evidence Ledger, a Provable Claim Lifecycle, and an Explain Command

Hermes Atlas v0.3 adds an evidence ledger to every claim, a provable claim lifecycle (including DELETE-driven invalidation that retains history), and an `atlas explain` command. Every number below was measured, not asserted.

Aug 9, 2026

Hermes Atlas v0.4: Proving the Evidence Ledger & Claim Lifecycle with a Gold-Labeled Benchmark

Hermes Atlas v0.4 proves the v0.3 evidence ledger and provable claim lifecycle with a gold-labeled benchmark. Every number was measured on a fixed, model-free corpus; two real bugs were found and fixed in the process. Everything below is reproducible.

Feb 15, 2026

Building This Blog: A Technical Deep Dive into My Next.js AI-Powered Publishing Platform

An in-depth look at the technical architecture behind this blog - how I built a performant, AI-powered publishing system with Next.js, Vercel AI SDK, MCP, semantic search, and knowledge graph visualization.

Sep 4, 2026

The Sovereign Agent Stack — A Local-First, Compile-Time AI Agent Framework

The Sovereign Agent Stack (SAS) is a local-first, compile-time AI agent framework that operationalizes the sovereignty thesis from The Rented Sovereign. A complete 7-layer architecture with 156 tests, 23 E2E tests, plugin system, MCP server, and package manager distribution.

Jul 23, 2026

Compiling the State of Human Understanding: A Roadmap for the Scientific State Compiler

A research roadmap: the four compiler directions — Mechanistic Reasoning, Scientific Question, Scientific State, and Discovery — are not competing projects. They are five passes in one architecture that compiles the scientific corpus into a versioned, executable model of human understanding. Here is the full plan.

More from the archive