--- type: explanation status: current related: - docs/README.md - docs/runbook.md - docs/roadmap.md --- # Design — facts, info, deduction ## Two roots, one transaction `root` — `facts` | `info` — is a column on every node and edge. Both roots live in the **same** Ladybug file and are written inside the **same transaction** (D12). Splitting is semantic, not physical: - `facts` — assertions backed by ≥2 independent sources (`confidence: confirmed`) or marked `partial`/`hypothesis` when the second source is missing. - `info` — narrative/descriptive leafs (how-tos, READMEs, notes). Searchable, never asserted as an answer. ACID: Ladybug commits facts + info atomically; the audit can treat a snapshot as one consistent state. ## Deduction search ``` bin/brain/search.go "question" 1. facts root — confirmed answers only → return with evidence links 2. info root — supporting narrative → snippets, marked (not confirmed) 3. web-search — second independent source → upgrade hypothesis to confirmed (`web` block from `bin/web/search.go` when no facts hit; status `throttled` is not evidence of absence; `--no-web` / `--root` skip it) ``` `--hop N` walks `Leaf-[:FROM_FILE]->File-[:HAS_VERSION]->Commit-[:AUTHORED]->Person` from each hit (1=File, 2=Commit, 3=Person). Rebuild writes FROM_FILE; git import writes HAS_VERSION/AUTHORED ([#17](https://git.produktor.io/eSlider/2dph/issues/17)). Go CLIs parse with **flaggy** via `internal/cli` (D23). Flags may appear after positionals (`search q --hop 1`). Completions: `source <(./bin/cli/complete.go bash)`. ## Who / What / How / Where / When + evidence Every assertion edge carries: | prop | meaning | |------|---------| | who | subject/object persons, services, hosts | | what | the associated subjects/objects (predicate) | | how | methods used to read it (compose file? docker ps? ssh config?) | | where | environment + filesystem path / host | | when | timestamp / source_rev (git commit, mtime) | | evidence | ≥2 source refs (compose path, runtime container, config) | | confidence | confirmed / partial / hypothesis | | root | facts / info | ## Versioning — nothing is timeless Content leafs: `sha256`, `observed_at`, `source_rev`, `confidence`. Stale = a file changed on disk (git HEAD/mtime) after its last observed `source_rev`. `File-[:HAS_VERSION]->Commit-[:AUTHORED]->Person` records the history of every content leaf. Commit records come from `bin/git/import.go` (go-git, no git binary); conversion prints leafs, brain write is `bin/brain/index.go`. `bin/facts/audit stale` flags leafs whose observed revision is behind the corpus HEAD. Fact **interval of truth** (D24 / OQ5): leaf props `valid_from` / `valid_to` (YYYY-MM-DD, inclusive; empty end = open; both empty = legacy always-active). `bin/brain/search.go --as-of YYYY-MM-DD` and MCP/HTTP `as_of` keep hits whose interval covers that day. This is not D16 `temporal_freshness` (source freshness vs HEAD). [#36](https://git.produktor.io/eSlider/2dph/issues/36). Existing `kb.lbug` without the columns: open/search/add runs an idempotent `ALTER TABLE Leaf ADD …` (no full rebuild required). Fresh `--rebuild` still creates them in `CREATE NODE TABLE`. ## Sources (auto-pairing) - A: runtime state — `docker ps` (container running), ports actually bound - B: declared config — `docker-compose.yml`, `~/.ssh/config`, `homeserver.yaml` - C: narrative — READMEs, AGENTS.md, docs Confirmed = A×B or B×C agreement. Single source = hypothesis + `(not confirmed)`. Conflicting pairings (≥2 yes vs ≥2 no) stay hypothesis until `temporal_freshness` or `authority_pairing` fires (`bin/facts/audit contradict`, [#29](https://git.produktor.io/eSlider/2dph/issues/29)). ## Read path `bin/brain/get.go`, `stats.go`, and `eval.go` call `internal/brain` with cgo (`system_ladybug`), compiled by **Zig** (`bin/cgo/zcc`, D21), not gcc. They do not exec Python. Control questions for recall@5 live in `internal/brain/rank` so CI can test the table without libladybug. Python `bin/kb/{get,stats,eval}` remain for GitHub Actions until the runner fetches Zig + libs (`bin/cgo/zig`). Incremental write is `bin/kb/add` (`bin/brain/add.go`). Bulk index/write is still `bin/kb/index` (`docker compose --profile index`). ## Agent API (D20) `bin/brain/serve.go` exposes the same `internal/httpapi.Ops` table as OpenAPI (`GET /openapi.json`) and MCP (`POST /mcp` JSON-RPC `tools/list` + `tools/call`). Tool names match paths: `search`, `get`, `stats`, `audit`, `ingest` (add a leaf; omit body for the CLI hint). Agents should use these endpoints instead of shebang CLIs. ## Reasoner (D18) Pluggable OpenAI-compatible URL. RAM: `Qwen/Qwen3.5-9B`. Quality: `prism-ml/Bonsai-27B-gguf` or `Qwen/Qwen3.6-27B`. No official Qwen3.6-9B. CPU sidecar: compose profile `reasoner` (`OLLAMA_NUM_GPU=0`, `127.0.0.1:11435`). Bake-off: `bin/reasoner/bakeoff.go`. Weights stay out of the 2dph image. See [docs/reasoner.md](reasoner.md). Gap to v1 (hops, corpus, CI eval): [roadmap](roadmap.md), [epic #16](https://git.produktor.io/eSlider/2dph/issues/16).