diff --git a/README.md b/README.md index 95060bf..a23da89 100644 --- a/README.md +++ b/README.md @@ -2,32 +2,192 @@ An arborist for trees and forests of cross-linked information. -Aborist ingests documents into a content-addressed, Merkle-committed store. Every -document carries a URI for backtrack and cross-link. Search hooks return hits with -declared audit modes (`STRICT` / `HYBRID` / `VISUAL`) so callers never overclaim -what a result actually proves. +Aborist ingests documents into a content-addressed, Merkle-committed SQLite store, distills them into recursive cores, and answers questions over the resulting corpus via an OpenAI-compatible LLM. Every cached answer carries a verifiable Merkle proof tying it back to its source documents — Merkle-AGI v9.8 / Merkle Providence Reverse RAG, runnable end-to-end. -## Design +## What this gets you -Aborist implements the runtime spec from Merkle-AGI v9.8 / Merkle Providence -Reverse RAG. The 8-dim admissibility key (`source_root`, `question_hash`, -`model_profile_hash`, `conversation_hash`, `governance_policy_hash`, -`schema_version`, `canonicalization_version`, `chunking_version`) plus -falsification state ensures cached records are never reused under drift. +``` +make bootstrap # one-time: venv + deps +make fetch-cur # download Wikipedia 2003-05-16 (~82 MB) +make ingest-cur-attached # ~3 min — 128k articles, 4 parallel shards +make distill-shards-parallel +make distill-shards-tfidf-parallel +make query Q="What is anarcho-capitalism?" +``` -Two layered document kinds: -- **surface** — diverse ingested content (full chunks, FTS-indexed) -- **core** — distilled records (haiku/equation/snippet) Merkle-signed back to - source surface roots via `derivations` +After the last command, a Hermes-3 inference runs against the local corpus, picks 4–8 source articles by Merkle root, and returns an answer with a cryptographic proof of its sources. Repeat the same question and a STRICT-mode cache hit replays in ~100 ms. -Reversible eviction via chunk `tier` ∈ {`hot`, `warm`, `cold`}. Cold = leaf hash -+ URI only; rehydratable from URI, identity verifiable via leaf hash. +## Setup -## Quick start +Requirements: Python 3.10+, `bzip2`, `curl`, `make`. SQLite 3.35+ (CPython's bundled sqlite3 is fine). - make all # bootstrap + fetch + ingest + verify + stats - make search Q='…' +``` +make bootstrap +``` + +Creates `.venv/`, installs the package in editable mode with the `[dev,html]` extras, and exposes `aborist` at `.venv/bin/aborist`. No system-wide install. Re-running `make bootstrap` is a no-op if the venv is up to date. + +After bootstrap, every workflow lives behind a `make` target. Run `make help` to list them. + +## Data: Wikipedia 2003-05-16 + +The 2003 dataset lives at as three files: + +| file | size | what | +|---|---|---| +| `20030516_cur_tablesql.bz2` | 82 MB | one row per article: snapshot of every Wikipedia page on 2003-05-16 | +| `old_tablesqlbz2.1` | 640 MiB | first half of the revision-history table (split bzip2 stream) | +| `old_tablesqlbz2.2` | 252 MiB | second half — `cat` them together to decompress | + +``` +make fetch-cur # snapshot only (~82 MB on disk) +make fetch-old # full history (~1.4 GB on disk after concat) +make fetch # both +``` + +Files land in `data/`. Re-running is idempotent (curl skips if already present). + +### Sharded ingest (the canonical path) + +Per-shard SQLite files, no write-lock contention. Each shard process owns its own DB; cross-shard reads attach all shards as `UNION ALL` views. + +``` +make ingest-cur-attached SHARDS=4 # cur snapshot, 4 parallel shards (~3 min) +make ingest-old-attached SHARDS=4 # full history, ~30–40 min +``` + +Shards land in `~/.aborist/shards/`. Override with `SHARDS_DIR=/path/to/somewhere`. + +### Resumable + +Add `--resume` (or just re-run the make target — `--resume` is the default for attached ingests). Each shard tracks its own high-water mark in `meta`; an interrupted ingest picks up where it left off without re-hashing already-cached docs. + +### Single-DB ingest (simpler, smaller) + +For experiments under a few thousand docs, a single SQLite file is fine: + +``` +make ingest-cur INGEST_LIMIT=1000 # one DB at $(DB), default ~/.aborist/aborist.db +make ingest-cur-parallel SHARDS=4 # 4 processes, one shared DB (WAL serialized) +``` + +### Distillation (cores feed retrieval) + +Two distillers ship: `first-sentence-v1` (one sentence per chunk) and `tfidf-keywords-v1` (top-K distinctive terms per doc). Cores are Merkle-signed back to their source docs and serve as enriched titles for retrieval. + +``` +make distill-shards-parallel # first-sentence cores, per-shard +make distill-shards-tfidf-parallel # TF-IDF cores, per-shard +``` + +Run both — they generate independent cores per source. TF-IDF cores let neologisms (like a personal term that never appears in any title) match retrievals via keyword overlap. + +## Data: personal Grok export + +If you have an xAI data-export bundle, point `GROK_EXPORT` at its root directory (the one containing `ttl/30d/export_data//`): + +``` +export GROK_EXPORT=$HOME/Downloads/ +make ingest-grok-attached # conversations -> $(SHARDS_DIR)/grok.db +make ingest-grok-media-attached # media prompts -> $(SHARDS_DIR)/grok.db +``` + +Both walk the export tree, find `prod-grok-backend.json`, and yield one Document per conversation (or media-generation post). Conversation titles, full message text, and turn ordering are preserved. Each becomes a normal queryable doc in the cluster — your prior chats become memory the corpus can consult. + +`--resume` is the default for these targets. Re-run any time to pick up new exports. + +## Data: OpenAI / ChatGPT export (planned) + +Not implemented yet. The shape will be one new `Source` subclass at `aborist/sources/openai.py` plus a Makefile target. The OpenAI ChatGPT data export is a `.zip` containing `conversations.json` with the `mapping`/`messages` tree shape. Adding it follows the same pattern as `aborist/sources/grok.py` — see that file as the template. + +``` +# (placeholder) +make ingest-openai-attached # OPENAI_EXPORT=$HOME/Downloads/ +``` + +When this lands, conversations from both Grok and OpenAI will sit in the same shard cluster; queries fan out across all of them. + +## Asking the corpus + +``` +make query Q="What is the philosophy of stoicism?" +make query Q="tell me about permacomputer ?" +make query QUERY_TOP_K=12 Q="…" # widen the source set +make query-dry Q="…" # assemble context but skip the LLM call +``` + +The query path: + +1. **Search** — FTS5 (body) + SQL `LIKE` (title) + `JOIN` over derivations (TF-IDF core keywords) across every shard. Three accept paths to the relevance filter. +2. **Concept overlay** — synonym groups (`Athlon` ↔ `AMD`) widen retrieval; rivalry pairs (AMD ↔ Intel) narrow it unless the query has comparative phrasing ("compare X vs Y"). +3. **Context assembly** — top-K sources concatenated up to a 60 KB budget, fed to Hermes-3 with strict attribution rules in the system prompt. +4. **Cache** — the v9.8 8-dim cache_key (`source_root | question_hash | model_profile | conversation | governance_policy | schema | canonicalization | chunking`) keys the answer in `qa.db`. Cache hits replay STRICT-mode in ~100 ms. Per-phase timings are returned in every result. + +LLM endpoint defaults to `https://hermes.ai.unturf.com/v1` (Hermes-3 Llama-3.1-8B, 82K context, no auth). Override: + +``` +export ABORIST_LLM_ENDPOINT="https://your-vllm.example/v1" +export ABORIST_LLM_MODEL="meta-llama/Llama-3.1-70B-Instruct" +export ABORIST_LLM_API_KEY="..." +``` + +## Mark a wrong answer + +If the LLM produced something incorrect, mark its cache record stale so the next ask re-runs inference fresh: + +``` +make falsify KEY= REASON="why it was wrong" +``` + +The original record stays in the database (history matters). A `falsifications` row + `falsify` audit event record the act. Future lookups skip records whose `falsification_state != 'live'`. + +## Inspecting + +``` +make stats-shards # totals across shards +make analyze-shards # compression spectrum, depth histogram, audit chain integrity +make verify-shards # round-trip Merkle proofs on a random sample +make activity # recent Q&A + ingests + derives + falsifications (agent timeline) +make activity ACTIVITY_LIMIT=20 +``` + +`activity` is JSON; pipe through `jq` to drill in. + +## Architecture (one screenful) + +``` +aborist/ +├── merkle.py Python port of proxy.unturf.com/pkg/verified/merkle.go +│ conventions: non-commutative HashCombine (0x03 prefix), +│ explicit IsLeft per sibling, self-duplicate odd elements. +├── store.py SQLite v9.8 schema (8-dim cache key, falsification state, +│ audit chain, surface/core kinds, hot/warm/cold tier). +├── ingest.py normalize → chunk → merkle → upsert. Bulk-batched writer. +├── document.py Document, Edge, Chunker (tok-512-v1 default). +├── source.py Source ABC: iter_documents() -> Iterator[Document]. +├── sources/ +│ ├── wikipedia.py cur + old MediaWiki SQL dumps (bz2-streamed). +│ ├── html_page.py URL list + selectolax + httpx (robots-aware). +│ └── grok.py xAI data export (conversations + media prompts). +├── distill/ Distiller ABC + first_sentence + tfidf + recursion runner. +├── search/ FTS5 backend + SearchBackend ABC + AuditMode. +├── qa/ ask (single-doc) + query (multi-source RAG) + concepts overlay. +├── evict.py hot ↔ cold tier transitions; rehydrate via source pipeline. +└── cli.py argparse entrypoint (the make targets call into here). +``` + +Source papers (read first if confused): + +- `~/Downloads/merkle-providence-reverse-rag-whitepaper.pdf` — public spec, AGPL-3.0 +- `~/Downloads/merkle-agi-dag_v7.txt` — formal substrate (TLV/canonical encoding, theorems T1–T5) + +## Tests + +``` +make test # 79 tests, all stdlib + pytest +make bench # ETL throughput across configs (serial / shared-WAL / attached) +``` ## License -AGPL-3.0-only. +AGPL-3.0-only. Whitepaper, code, and derived knowledge are public-domain inheritable per the unturf.com permacomputer charter.