Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
638e84c324 | ||
|
|
bb272d7416 | ||
|
|
e393cc6a99 | ||
|
|
7fb51e9918 | ||
|
|
e5a982e99a | ||
|
|
6b680bdb90 | ||
|
|
77c635b16b | ||
|
|
2fede46dcd | ||
|
|
54201d8c22 | ||
|
|
a6dbe2f6da | ||
|
|
368a757612 | ||
|
|
a8675ac33b | ||
|
|
de632ba6cc | ||
|
|
66c87842e2 | ||
|
|
20b78a9a20 | ||
|
|
5d4b3427a4 | ||
|
|
1c7db6d499 | ||
|
|
0786ddcb06 | ||
|
|
eeb5b79cf2 | ||
|
|
5990feb1f6 | ||
|
|
d27a738fee | ||
|
|
669e184cf6 | ||
|
|
ebc3f948c1 | ||
|
|
fe6a02024c | ||
|
|
4a065d9838 | ||
|
|
e3c6ef5684 | ||
|
|
98c14e23f1 | ||
|
|
ff1716de40 | ||
|
|
fec5325c7a | ||
|
|
27d9521e7f | ||
|
|
a7cb8d4c76 | ||
|
|
53cd00284d | ||
|
|
b73b4d4f97 | ||
|
|
1d1f6a90ff | ||
|
|
f220bcd95a | ||
|
|
678a1d1dba | ||
|
|
8781c0c3eb | ||
|
|
d6b17e8819 |
@@ -3,8 +3,7 @@
|
|||||||
Evidence-first brain over the ops/eSlider stack. Facts need proof or they are
|
Evidence-first brain over the ops/eSlider stack. Facts need proof or they are
|
||||||
`(not confirmed)`.
|
`(not confirmed)`.
|
||||||
|
|
||||||
Read first: [PLAN](PLAN.md) → [docs](docs/) → [roadmap](docs/roadmap.md)
|
Read first: [PLAN](PLAN.md) → [docs](docs/).
|
||||||
(epic [#16](https://git.produktor.io/eSlider/2dph/issues/16)).
|
|
||||||
|
|
||||||
## Method (detective, no fork)
|
## Method (detective, no fork)
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ A brain that loves facts and deduction. Evidence-first knowledge graph + hybrid
|
|||||||
RAG over the operational Brain/ops/eSlider stack. Built like Sherlock
|
RAG over the operational Brain/ops/eSlider stack. Built like Sherlock
|
||||||
Holmes: nothing is asserted unless it has proof.
|
Holmes: nothing is asserted unless it has proof.
|
||||||
|
|
||||||
Status: **in progress** — read path + MCP work; v1 goal is [epic #16](https://git.produktor.io/eSlider/2dph/issues/16)
|
Status: **in progress** — this file is the plan and the record of decisions.
|
||||||
(milestone [v1 detective brain](https://git.produktor.io/eSlider/2dph/milestone/12)).
|
|
||||||
Gap: [docs/roadmap.md](docs/roadmap.md).
|
|
||||||
|
|
||||||
## What
|
## What
|
||||||
|
|
||||||
@@ -43,7 +41,7 @@ detective method: **a fact needs ≥2 independent sources or it is
|
|||||||
| D15 | repo | Gitea [`eSlider/2dph`](https://git.produktor.io/eSlider/2dph) is origin + [issues](https://git.produktor.io/eSlider/2dph/issues). GitHub `eSlider/2dph` is the public clone (PRs + Actions CI). No direct `main` pushes. TDD → PR → CI green → merge. |
|
| D15 | repo | Gitea [`eSlider/2dph`](https://git.produktor.io/eSlider/2dph) is origin + [issues](https://git.produktor.io/eSlider/2dph/issues). GitHub `eSlider/2dph` is the public clone (PRs + Actions CI). No direct `main` pushes. TDD → PR → CI green → merge. |
|
||||||
| D16 | contradictions | ≥2 yes vs ≥2 no → unrelated sources conflict → hypothesis → `(not confirmed)`. Resolution (authority, staleness adjudication) = **v2**, tracked as open question. |
|
| D16 | contradictions | ≥2 yes vs ≥2 no → unrelated sources conflict → hypothesis → `(not confirmed)`. Resolution (authority, staleness adjudication) = **v2**, tracked as open question. |
|
||||||
| D17 | assertion gate | Fact-check every *claim* (facts → info → live → web), not every edit. `bin/brain/search.go` adds a `web` block when there is no facts hit (`throttled`/`skipped`/`refused` ≠ absence). `--root` and `--no-web` stay local. Missing graph ≠ “does not exist”. |
|
| D17 | assertion gate | Fact-check every *claim* (facts → info → live → web), not every edit. `bin/brain/search.go` adds a `web` block when there is no facts hit (`throttled`/`skipped`/`refused` ≠ absence). `--root` and `--no-web` stay local. Missing graph ≠ “does not exist”. |
|
||||||
| D18 | reasoner | Pluggable OpenAI-compatible URL (`REASONER_BASE_URL`). RAM: `Qwen/Qwen3.5-9B`. Quality: `prism-ml/Bonsai-27B-gguf` or `Qwen/Qwen3.6-27B`. No official Qwen3.6-9B. CPU bake-off: `bin/reasoner/bakeoff.go` + compose profile `reasoner` (`OLLAMA_NUM_GPU=0`, `:11435`). PicoClaw is compose profile `picoclaw`; tools are `search`/`get`/`audit`. Weights are not copied into the 2dph image. Agent lever/loop: [#15](https://git.produktor.io/eSlider/2dph/issues/15). |
|
| D18 | reasoner | Pluggable OpenAI-compatible URL (`REASONER_BASE_URL`). RAM: `Qwen/Qwen3.5-9B`. Quality: `prism-ml/Bonsai-27B-gguf` or `Qwen/Qwen3.6-27B`. No official Qwen3.6-9B. CPU bake-off: `bin/reasoner/bakeoff.go` + compose profile `reasoner` (`OLLAMA_NUM_GPU=0`, `:11435`). PicoClaw is not shipped; tools are `search`/`get`/`audit`. Weights are not copied into the 2dph image. |
|
||||||
| D19 | git history | [go-git](https://github.com/go-git/go-git) via `bin/git/import.go`. No subprocess of the git binary. Conversion prints commit leafs; brain write is `bin/brain/index.go`. |
|
| D19 | git history | [go-git](https://github.com/go-git/go-git) via `bin/git/import.go`. No subprocess of the git binary. Conversion prints commit leafs; brain write is `bin/brain/index.go`. |
|
||||||
| D20 | agent API | OpenAPI + MCP are generated from the same `internal/httpapi.Ops` table as `bin/brain/serve.go` handlers. `GET /openapi.json`, `POST /mcp` (JSON-RPC tools/list + tools/call). Tool names match OpenAPI paths (`search`/`get`/`stats`/`audit`). |
|
| D20 | agent API | OpenAPI + MCP are generated from the same `internal/httpapi.Ops` table as `bin/brain/serve.go` handlers. `GET /openapi.json`, `POST /mcp` (JSON-RPC tools/list + tools/call). Tool names match OpenAPI paths (`search`/`get`/`stats`/`audit`). |
|
||||||
| D21 | CGO | Ladybug/tokenizers CGO is compiled with **Zig** (`bin/cgo/zcc` → `zig cc -target …-linux-gnu`), not gcc. `bin/cgo/zig` pins Zig 0.14.1 + liblbug 0.19.1 + libtokenizers 1.27.0. Compose `target: api` has no CPython; write/rebuild is profile `index`. |
|
| D21 | CGO | Ladybug/tokenizers CGO is compiled with **Zig** (`bin/cgo/zcc` → `zig cc -target …-linux-gnu`), not gcc. `bin/cgo/zig` pins Zig 0.14.1 + liblbug 0.19.1 + libtokenizers 1.27.0. Compose `target: api` has no CPython; write/rebuild is profile `index`. |
|
||||||
@@ -85,8 +83,7 @@ detective method: **a fact needs ≥2 independent sources or it is
|
|||||||
|
|
||||||
Node tables: `Person, Service, Host, Container, Repo, File, Commit, Leaf`.
|
Node tables: `Person, Service, Host, Container, Repo, File, Commit, Leaf`.
|
||||||
`Leaf(embedding FLOAT[N])` — FTS on `text`, HNSW vector index on `embedding`.
|
`Leaf(embedding FLOAT[N])` — FTS on `text`, HNSW vector index on `embedding`.
|
||||||
Edges: `RUNS / USES / FROM_FILE / HAS_VERSION / AUTHORED / ABOUT / ASSOCIATED / SIMILAR_0.85`.
|
Edges: `RUNS / USES / HAS_VERSION / AUTHORED / ABOUT / ASSOCIATED / SIMILAR_0.85`.
|
||||||
`FROM_FILE` / `HAS_VERSION` exist in schema; search `--hop` does not walk them yet ([#17](https://git.produktor.io/eSlider/2dph/issues/17)).
|
|
||||||
|
|
||||||
Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`,
|
Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`,
|
||||||
`where`, `when`, `source_rev`.
|
`where`, `when`, `source_rev`.
|
||||||
@@ -111,9 +108,9 @@ Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`,
|
|||||||
## Open questions (v2)
|
## Open questions (v2)
|
||||||
|
|
||||||
- OQ1: mutually-contradicting evidence — how to resolve (authority weighting,
|
- OQ1: mutually-contradicting evidence — how to resolve (authority weighting,
|
||||||
temporal freshness, audit adjudication). **v2**; does not block epic #16.
|
temporal freshness, audit adjudication).
|
||||||
- OQ2: OCR — poppler `pdftotext` fast-path exists; scans still docling.
|
- OQ2: OCR pipeline for pdfs/images/docs — mostly solved: poppler pdftotext
|
||||||
[#6](https://git.produktor.io/eSlider/2dph/issues/6) (v2, does not block #16).
|
fast-path for born-digital PDFs, docling fallback for the ~5% textless ones.
|
||||||
- OQ3: optional duckdb-md layer for `SELECT … FORMAT MARKDOWN` export/write-back.
|
- OQ3: optional duckdb-md layer for `SELECT … FORMAT MARKDOWN` export/write-back.
|
||||||
- OQ4: YAML-first storage for leafs — deferred: JSON is ~10x faster to
|
- OQ4: YAML-first storage for leafs — deferred: JSON is ~10x faster to
|
||||||
serialize and unambiguous; YAML only where humans edit files.
|
serialize and unambiguous; YAML only where humans edit files.
|
||||||
@@ -141,7 +138,6 @@ Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`,
|
|||||||
3. python -m unittest discover -s bin/tools (includes published-docs SoT)
|
3. python -m unittest discover -s bin/tools (includes published-docs SoT)
|
||||||
4. `bin/facts/audit self` (lexicon internal consistency; `bin/facts/audit.go` is the D14 wrapper)
|
4. `bin/facts/audit self` (lexicon internal consistency; `bin/facts/audit.go` is the D14 wrapper)
|
||||||
5. `bin/kb/eval` (recall@5 ≥ 0.95). Local SoT is `bin/brain/eval.go` via Zig CGO.
|
5. `bin/kb/eval` (recall@5 ≥ 0.95). Local SoT is `bin/brain/eval.go` via Zig CGO.
|
||||||
CI SoT switch: [#19](https://git.produktor.io/eSlider/2dph/issues/19).
|
|
||||||
6. `bin/cgo/zig go build -tags system_ladybug` (compile search with zig cc; fetches pinned zig+libs).
|
6. `bin/cgo/zig go build -tags system_ladybug` (compile search with zig cc; fetches pinned zig+libs).
|
||||||
|
|
||||||
Feedback loop: every commit → PR → CI → green/gate → merge. Same discipline as
|
Feedback loop: every commit → PR → CI → green/gate → merge. Same discipline as
|
||||||
@@ -155,23 +151,5 @@ Feedback loop: every commit → PR → CI → green/gate → merge. Same discipl
|
|||||||
4. .venv: ladybug + model2vec + mistune
|
4. .venv: ladybug + model2vec + mistune
|
||||||
5. schema + tools with TDD (kb + md + facts + brain)
|
5. schema + tools with TDD (kb + md + facts + brain)
|
||||||
6. ~/.config/brain config
|
6. ~/.config/brain config
|
||||||
7. corpus extraction (facts/info) — **open**: [#18](https://git.produktor.io/eSlider/2dph/issues/18)
|
7. corpus extraction (facts/info)
|
||||||
8. verify: web-search smoke, onlyoffice pg, md-db round-trip, eval, audit
|
8. verify: web-search smoke, onlyoffice pg, md-db round-trip, eval, audit
|
||||||
|
|
||||||
## Gap to v1 (epic #16)
|
|
||||||
|
|
||||||
Read path + MCP are in. The detective brain is not closed until the graph is
|
|
||||||
**writable incrementally** and search can **walk** it. Board:
|
|
||||||
[epic #16](https://git.produktor.io/eSlider/2dph/issues/16),
|
|
||||||
milestone [v1 detective brain](https://git.produktor.io/eSlider/2dph/milestone/12).
|
|
||||||
Narrative: [docs/roadmap.md](docs/roadmap.md).
|
|
||||||
|
|
||||||
| Order | Issue | Gap |
|
|
||||||
|-------|-------|-----|
|
|
||||||
| 1 | [#14](https://git.produktor.io/eSlider/2dph/issues/14) | Write stays Python rebuild; `brain/add` / `POST /ingest` are hints. Ladybug 0.19 WAL corrupts if new leafs land while FTS/HNSW exist. |
|
|
||||||
| 2 | [#17](https://git.produktor.io/eSlider/2dph/issues/17) | `--hop` errors. `FROM_FILE` / `HAS_VERSION` are in schema; search does not walk them. |
|
|
||||||
| 3 | [#18](https://git.produktor.io/eSlider/2dph/issues/18) | Rebuild is mostly `info` (repo md + mail). `facts/extract` and chats are not a first-class index input. WhatsApp sync is a stub. |
|
|
||||||
| 4 | [#15](https://git.produktor.io/eSlider/2dph/issues/15) | Lever = 2dph fact-check. Loop = PicoClaw/MCP `search` → `get` → `audit`. Specify in-repo, not only live config. |
|
|
||||||
| 5 | [#19](https://git.produktor.io/eSlider/2dph/issues/19) | GitHub CI recall still runs Python `bin/kb/eval`. |
|
|
||||||
|
|
||||||
Does **not** block epic close: [#6](https://git.produktor.io/eSlider/2dph/issues/6) OCR, OQ1, OQ3, OQ4.
|
|
||||||
@@ -7,16 +7,14 @@
|
|||||||
[](https://github.com/eSlider/2dph/releases)
|
[](https://github.com/eSlider/2dph/releases)
|
||||||
[](https://github.com/eSlider/2dph/stargazers)
|
[](https://github.com/eSlider/2dph/stargazers)
|
||||||
|
|
||||||
An evidence-first brain. **Facts need two independent sources, or they are
|
An evidence-first brain over the operational eSlider stack. **Facts need two
|
||||||
`(not confirmed)`.** Cursor is not the runtime.
|
independent sources, or they are `(not confirmed)`.**
|
||||||
|
|
||||||
`2dph` is a single embedded knowledge graph (LadybugDB) with native **HNSW
|
`2dph` is a single embedded knowledge graph (LadybugDB = Kuzu successor) with
|
||||||
vector** + **BM25 full-text** indexes. Search is *deduction*: confirmed facts
|
native **HNSW vector** + **BM25 full-text** indexes, built from markdown,
|
||||||
first, supporting info second, `web-search` as the independent second source
|
compose files, ssh config, docker state, and git history. Search is
|
||||||
when the local graph cannot confirm.
|
*deduction*: confirmed facts first, supporting info second, `web-search` as
|
||||||
|
the independent second source when the local graph cannot confirm.
|
||||||
Run it: [docs/runbook.md](docs/runbook.md). Design: [docs/design.md](docs/design.md).
|
|
||||||
Docs index: [docs/README.md](docs/README.md).
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -76,7 +74,7 @@ graph TB
|
|||||||
## The method
|
## The method
|
||||||
|
|
||||||
Every assertion is `Who / What / How / Where / When + evidence + confidence`,
|
Every assertion is `Who / What / How / Where / When + evidence + confidence`,
|
||||||
mirroring the detective method: **≥2 independent sources confirm a
|
mirroring the detective detective skill: **≥2 independent sources confirm a
|
||||||
fact; conflicting sources or a single source → `hypothesis` → `(not confirmed)`.**
|
fact; conflicting sources or a single source → `hypothesis` → `(not confirmed)`.**
|
||||||
|
|
||||||
| root | meaning | used for answers |
|
| root | meaning | used for answers |
|
||||||
@@ -126,15 +124,17 @@ bin/brain/search.go "invoice from last week" # same s
|
|||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
- **LadybugDB** — single `var/kb.lbug`, Cypher + HNSW + BM25, embedded.
|
- **LadybugDB** — single `var/kb.lbug`, Cypher property graph, HNSW + BM25
|
||||||
Read tools (`get` / `stats` / `eval`) are Go + Zig CGO (`bin/cgo/zcc`).
|
in one engine, embedded (no server), ACID, read-only-safe for concurrent
|
||||||
Python fallbacks stay for CI until the runner fetches Zig. Write is
|
readers. Read tools (`get` / `stats` / `eval`) are Go + Zig CGO (`bin/cgo/zcc`); Python
|
||||||
Compose profile `index` (`bin/brain/index.go`).
|
`bin/kb/{get,stats,eval}` is the CI fallback. **Never `DROP INDEX` FTS/VECTOR** on Ladybug 0.19: DROP leaves
|
||||||
- **model2vec** — `potion-multilingual-128M` (256-dim), CPU, no Ollama
|
ghost catalog tables (`_0_Leaf_vec_UPPER`) so recreate fails while
|
||||||
runtime dependency.
|
`SHOW_INDEXES` omits HNSW. Fresh indexes = delete `var/kb.lbug` +
|
||||||
- facts and info split by `root` but written in the same transaction.
|
`bin/brain/index.go --rebuild`. Use `ensure_indexes()` after upserts.
|
||||||
|
- **model2vec** — `potion-multilingual-128M` static embeddings (256-dim),
|
||||||
Ladybug 0.19 DROP INDEX warning: [docs/runbook.md](docs/runbook.md).
|
CPU-fast, deterministic, no Ollama runtime dependency.
|
||||||
|
- facts and info split semantically by `root` column but written inside the
|
||||||
|
same transaction.
|
||||||
|
|
||||||
## Tooling conventions
|
## Tooling conventions
|
||||||
|
|
||||||
@@ -145,13 +145,11 @@ machines. Tests gate every commit. HTTP: `bin/brain/serve.go` calls
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See the portable runbook: [docs/runbook.md](docs/runbook.md).
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv .venv
|
uv venv .venv # Python 3.12, uv-managed
|
||||||
uv pip install -r requirements.lock.txt
|
uv pip install -r requirements.lock.txt # pinned toolchain
|
||||||
bin/facts/audit.go self
|
bin/facts/audit.go self # lexicon consistency gate
|
||||||
go test ./... && uv run python -m unittest discover -s bin/tools -t .
|
go test ./... && python -m unittest discover -s bin/tools -t .
|
||||||
```
|
```
|
||||||
|
|
||||||
Docker (optional, cached model + var volumes):
|
Docker (optional, cached model + var volumes):
|
||||||
@@ -172,9 +170,7 @@ docker compose up brain-watch # auto re-index on change
|
|||||||
skills (`web-search`, `postgres`, …) that 2dph integrates
|
skills (`web-search`, `postgres`, …) that 2dph integrates
|
||||||
- detective method — the two-source method
|
- detective method — the two-source method
|
||||||
|
|
||||||
Work board (issues): [epic #16](https://git.produktor.io/eSlider/2dph/issues/16)
|
Work board (issues): [git.produktor.io/eSlider/2dph/issues](https://git.produktor.io/eSlider/2dph/issues).
|
||||||
on [git.produktor.io/eSlider/2dph/issues](https://git.produktor.io/eSlider/2dph/issues).
|
|
||||||
PRs and CI: GitHub [`eSlider/2dph`](https://github.com/eSlider/2dph).
|
PRs and CI: GitHub [`eSlider/2dph`](https://github.com/eSlider/2dph).
|
||||||
|
|
||||||
See [PLAN.md](PLAN.md) for decisions, [docs/roadmap.md](docs/roadmap.md) for
|
See [PLAN.md](PLAN.md) for decisions, execution status, and v2 open questions.
|
||||||
the gap to v1, and v2 open questions.
|
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ class PublishedDocsTest(unittest.TestCase):
|
|||||||
ROOT / "docs" / "design.md",
|
ROOT / "docs" / "design.md",
|
||||||
ROOT / "skills" / "brain" / "SKILL.md",
|
ROOT / "skills" / "brain" / "SKILL.md",
|
||||||
ROOT / "skills" / "diataxis-docs" / "SKILL.md",
|
ROOT / "skills" / "diataxis-docs" / "SKILL.md",
|
||||||
ROOT / "docs" / "runbook.md",
|
|
||||||
ROOT / "docs" / "README.md",
|
|
||||||
ROOT / "docs" / "roadmap.md",
|
|
||||||
]
|
]
|
||||||
# Command-style `--hop 1` / `--hop N` plus follow/walk = the old lie.
|
# Command-style `--hop 1` / `--hop N` plus follow/walk = the old lie.
|
||||||
# Honest "not implemented" notes must not match.
|
# Honest "not implemented" notes must not match.
|
||||||
@@ -158,35 +155,3 @@ class PublishedDocsTest(unittest.TestCase):
|
|||||||
lie.search(text),
|
lie.search(text),
|
||||||
f"{path.relative_to(ROOT)} still claims --hop walks the graph",
|
f"{path.relative_to(ROOT)} still claims --hop walks the graph",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_docs_are_portable_diataxis(self) -> None:
|
|
||||||
index = (ROOT / "docs" / "README.md").read_text()
|
|
||||||
self.assertIn("type: reference", index)
|
|
||||||
for d in ("D3", "D6", "D14", "D15", "D17", "D18"):
|
|
||||||
self.assertIn(d, index)
|
|
||||||
runbook = (ROOT / "docs" / "runbook.md").read_text()
|
|
||||||
self.assertIn("type: howto", runbook)
|
|
||||||
self.assertIn("bin/brain/search.go", runbook)
|
|
||||||
self.assertIn("bin/brain/index.go", runbook)
|
|
||||||
self.assertNotIn("search.ops.io", runbook)
|
|
||||||
self.assertNotIn("/mnt/", runbook)
|
|
||||||
self.assertNotIn("/home/", runbook)
|
|
||||||
readme = (ROOT / "README.md").read_text()
|
|
||||||
self.assertIn("docs/runbook.md", readme)
|
|
||||||
self.assertNotIn("search.ops.io", readme)
|
|
||||||
|
|
||||||
def test_v1_epic_is_named_in_docs(self) -> None:
|
|
||||||
plan = (ROOT / "PLAN.md").read_text()
|
|
||||||
self.assertIn("Gap to v1", plan)
|
|
||||||
self.assertIn("eSlider/2dph/issues/16", plan)
|
|
||||||
self.assertIn("eSlider/2dph/issues/17", plan)
|
|
||||||
self.assertIn("eSlider/2dph/milestone/12", plan)
|
|
||||||
road = (ROOT / "docs" / "roadmap.md").read_text()
|
|
||||||
self.assertIn("type: explanation", road)
|
|
||||||
self.assertIn("issues/16", road)
|
|
||||||
self.assertIn("issues/14", road)
|
|
||||||
index = (ROOT / "docs" / "README.md").read_text()
|
|
||||||
self.assertIn("roadmap.md", index)
|
|
||||||
self.assertIn("epic #16", index)
|
|
||||||
agents = (ROOT / "AGENTS.md").read_text()
|
|
||||||
self.assertIn("roadmap.md", agents)
|
|
||||||
|
|||||||
+9
-31
@@ -1,38 +1,16 @@
|
|||||||
---
|
# 2dph (deductionphile)
|
||||||
type: reference
|
|
||||||
status: current
|
|
||||||
related:
|
|
||||||
- docs/runbook.md
|
|
||||||
- docs/design.md
|
|
||||||
- PLAN.md
|
|
||||||
- docs/roadmap.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 2dph docs (Diataxis)
|
Evidence-first knowledge graph + hybrid RAG over the operational
|
||||||
|
Brain/ops/eSlider stack. Facts need proof or they are
|
||||||
Evidence-first knowledge graph. Facts need proof or they are
|
|
||||||
`(not confirmed)`.
|
`(not confirmed)`.
|
||||||
|
|
||||||
| Type | Doc |
|
- [PLAN.md](../PLAN.md) — decisions, execution order, open questions (v2)
|
||||||
|------|-----|
|
- [design](design.md) — schema, deduction model, sources
|
||||||
| tutorial / howto | [runbook](runbook.md) — run anywhere (uv, Go, Docker) |
|
- [reasoner](reasoner.md) — D18 CPU bake-off (Qwen3.5-9B vs Bonsai / Qwen3.6-27B)
|
||||||
| explanation | [design](design.md) — two roots, deduction, D17/D20/D18 |
|
- [Gitea issues](https://git.produktor.io/eSlider/2dph/issues) — work board (origin)
|
||||||
| explanation | [roadmap](roadmap.md) — gap to v1 (epic #16) |
|
|
||||||
| howto | [picoclaw](picoclaw.md) — MCP agent profile |
|
|
||||||
| howto | [reasoner](reasoner.md) — CPU bake-off (D18) |
|
|
||||||
| reference | [PLAN.md](../PLAN.md) — decisions D1–D21 |
|
|
||||||
|
|
||||||
Decisions the public face must name: **D3** SearXNG compose, **D6** Go service /
|
|
||||||
Python write sidecar, **D14** `bin/{subject}/{method}.go`, **D15** Gitea origin,
|
|
||||||
**D17** assertion gate (facts → info → web), **D18** pluggable reasoner.
|
|
||||||
|
|
||||||
Search: `bin/brain/search.go "query"` (HTTP: `bin/brain/serve.go` —
|
Search: `bin/brain/search.go "query"` (HTTP: `bin/brain/serve.go` —
|
||||||
`/health` `/search` `/get` `/stats` `/audit` `/ingest`). `--hop` is
|
`/health` `/search` `/get` `/stats` `/audit` `/ingest`). `--hop` is
|
||||||
not a walk; the flag errors. Schema has `FROM_FILE`; search does not
|
not a walk; the flag errors until File/FROM_FILE edges exist.
|
||||||
use it ([#17](https://git.produktor.io/eSlider/2dph/issues/17)).
|
|
||||||
|
|
||||||
Work board: [Gitea issues](https://git.produktor.io/eSlider/2dph/issues)
|
Published docs live here and mirror the project state.
|
||||||
([epic #16](https://git.produktor.io/eSlider/2dph/issues/16)).
|
|
||||||
PRs and CI: GitHub [`eSlider/2dph`](https://github.com/eSlider/2dph).
|
|
||||||
|
|
||||||
Published docs live here and match live commands.
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# CRM association proof (oo CLI ↔ corpus)
|
||||||
|
|
||||||
|
Proven with `oo` (eslider/go-onlyoffice) against the OnlyOffice portal
|
||||||
|
(`office.produktor.io`). Portal CRM is the SSOT for company ↔ person ↔
|
||||||
|
project associations; the corpus SoT (`eslider/cv/projects/knowledge-mesh-seed.yaml`)
|
||||||
|
is the second, independent source. Facts that can be backed by both are
|
||||||
|
written to the brain under `root=facts` by `bin/facts/crm`.
|
||||||
|
|
||||||
|
## What was verified
|
||||||
|
|
||||||
|
- Logical counts (portal MySQL): 1300 contacts = 897 persons + 404 companies,
|
||||||
|
198 projects, 998 deals, 939 project↔contact links.
|
||||||
|
- Every client company linked to a project has ≥1 person underneath.
|
||||||
|
- Every person `company_id` resolves to an existing company.
|
||||||
|
- Corpus org list (9) maps 1:1 onto CRM companies:
|
||||||
|
ProProdukt SL / produktor.io, Dyvenia, Immowelt AG, WhereGroup,
|
||||||
|
Keynote SIGOS, D2S/SYSTEMS, GRID, Pack und Cup, Markets Platform.
|
||||||
|
- 78 person↔company association facts written to the brain
|
||||||
|
(`how=crm-crosscheck`, `type=association`). Recall@5 in `bin/kb/eval` = 1.0.
|
||||||
|
|
||||||
|
## Mistakes found
|
||||||
|
|
||||||
|
| # | Mistake | Fix |
|
||||||
|
|---|---------|-----|
|
||||||
|
| 1 | Duplicate legal entity `GoldenRatio.Exchange` (contact 759) vs `Golden Ratio Exchange` (763); 3 deals (211, 287, 559) were linked to 759 | `oo contacts merge 759 763` — 763 kept, 759 removed, deal links re-pointed to 763 |
|
||||||
|
| 2 | `env/`-wide: OnlyOffice creds file used wrong UX (user `eslider`, password with `$2` suffix) making `oo` auth fail | `.env` fixed to `eslider@gmail.com` + clean password; `.env` stays gitignored |
|
||||||
|
|
||||||
|
## Gates after fix
|
||||||
|
|
||||||
|
- `uv run python -m unittest discover -s bin/tools -t .` → 26 tests OK
|
||||||
|
- `bin/facts/audit self` + `bin/facts/audit db` → ok
|
||||||
|
- `bin/kb/eval` → recall@5 = 1.0
|
||||||
|
- `go test ./...` (bin/server + bin/watch) → ok
|
||||||
+3
-16
@@ -1,12 +1,3 @@
|
|||||||
---
|
|
||||||
type: explanation
|
|
||||||
status: current
|
|
||||||
related:
|
|
||||||
- docs/README.md
|
|
||||||
- docs/runbook.md
|
|
||||||
- docs/roadmap.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Design — facts, info, deduction
|
# Design — facts, info, deduction
|
||||||
|
|
||||||
## Two roots, one transaction
|
## Two roots, one transaction
|
||||||
@@ -34,9 +25,8 @@ bin/brain/search.go "question"
|
|||||||
is not evidence of absence; `--no-web` / `--root` skip it)
|
is not evidence of absence; `--no-web` / `--root` skip it)
|
||||||
```
|
```
|
||||||
|
|
||||||
`--hop` is not implemented. `FROM_FILE` / `HAS_VERSION` exist in schema;
|
`--hop` is not implemented yet (needs File/FROM_FILE edges). The flag is an
|
||||||
search does not walk them ([#17](https://git.produktor.io/eSlider/2dph/issues/17)).
|
error; it is not a graph walk.
|
||||||
The flag is an error; it is not a graph walk.
|
|
||||||
|
|
||||||
## Who / What / How / Where / When + evidence
|
## Who / What / How / Where / When + evidence
|
||||||
|
|
||||||
@@ -96,7 +86,4 @@ 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.
|
`prism-ml/Bonsai-27B-gguf` or `Qwen/Qwen3.6-27B`. No official Qwen3.6-9B.
|
||||||
CPU sidecar: compose profile `reasoner` (`OLLAMA_NUM_GPU=0`,
|
CPU sidecar: compose profile `reasoner` (`OLLAMA_NUM_GPU=0`,
|
||||||
`127.0.0.1:11435`). Bake-off: `bin/reasoner/bakeoff.go`. Weights stay out
|
`127.0.0.1:11435`). Bake-off: `bin/reasoner/bakeoff.go`. Weights stay out
|
||||||
of the 2dph image. See [docs/reasoner.md](reasoner.md).
|
of the 2dph image. See [docs/reasoner.md](reasoner.md).
|
||||||
|
|
||||||
Gap to v1 (write, hops, corpus, agent loop): [roadmap](roadmap.md),
|
|
||||||
[epic #16](https://git.produktor.io/eSlider/2dph/issues/16).
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
type: explanation
|
|
||||||
status: current
|
|
||||||
related:
|
|
||||||
- PLAN.md
|
|
||||||
- docs/design.md
|
|
||||||
- docs/runbook.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Gap to v1 — detective brain
|
|
||||||
|
|
||||||
Goal: a brain that does not assert without proof. Search is deduction
|
|
||||||
(`facts` ≥2 sources → `info` → `web`). `confirmed` only from the facts root.
|
|
||||||
|
|
||||||
**v1 is a living graph the agent can write and walk**, not “more RAG”.
|
|
||||||
|
|
||||||
Epic: [Gitea #16](https://git.produktor.io/eSlider/2dph/issues/16).
|
|
||||||
Milestone: [v1 detective brain](https://git.produktor.io/eSlider/2dph/milestone/12).
|
|
||||||
Decisions: [PLAN.md](../PLAN.md).
|
|
||||||
|
|
||||||
## In (do not reopen)
|
|
||||||
|
|
||||||
Read path Go + Zig CGO (D21). HTTP + OpenAPI + MCP (D20). PicoClaw compose
|
|
||||||
profile + CPU reasoner (D18). Mail sync → import → rebuild. D14 shebangs.
|
|
||||||
Compose `api` (no CPython) / `index` (Python write). Issues #1–#5, #7–#13.
|
|
||||||
|
|
||||||
`POST /ingest` is a rebuild **hint**. `add` is not implemented.
|
|
||||||
|
|
||||||
## Blockers
|
|
||||||
|
|
||||||
```
|
|
||||||
question
|
|
||||||
│
|
|
||||||
├─ FTS + HNSW ← in
|
|
||||||
├─ facts / info roots ← in
|
|
||||||
├─ web (D17) ← in
|
|
||||||
├─ Cypher hop ← #17 schema yes, search no
|
|
||||||
├─ brain/add ACID ← #14 rebuild only
|
|
||||||
└─ facts+chats corpus ← #18
|
|
||||||
```
|
|
||||||
|
|
||||||
1. **[#14](https://git.produktor.io/eSlider/2dph/issues/14) write** —
|
|
||||||
`bin/brain/index.go --rebuild` (Python `kblib`). No incremental
|
|
||||||
`brain/add`. Watch/mail/git cannot become facts “now”.
|
|
||||||
2. **[#17](https://git.produktor.io/eSlider/2dph/issues/17) hops** —
|
|
||||||
`Leaf-[:FROM_FILE]->File-[:HAS_VERSION]->Commit-[:AUTHORED]->Person`
|
|
||||||
exists; `--hop` still errors. Without a walk, D9/D10 are paper.
|
|
||||||
3. **[#18](https://git.produktor.io/eSlider/2dph/issues/18) corpus** —
|
|
||||||
rebuild loads repo markdown + mail as `info`. `facts/extract` pairing
|
|
||||||
and `bin/chats` are not indexed. WhatsApp is a stub. PII stays in `var/`.
|
|
||||||
4. **[#15](https://git.produktor.io/eSlider/2dph/issues/15) lever/loop** —
|
|
||||||
2dph is the lever (`search` → `get` → `audit`). PicoClaw is the loop.
|
|
||||||
Document the contour in-repo; CPU turns need a large context window.
|
|
||||||
5. **[#19](https://git.produktor.io/eSlider/2dph/issues/19) CI eval** —
|
|
||||||
recall SoT should be `bin/brain/eval.go` via Zig, not Python `bin/kb/eval`.
|
|
||||||
|
|
||||||
## Not v1
|
|
||||||
|
|
||||||
[#6](https://git.produktor.io/eSlider/2dph/issues/6) OCR (OQ2), OQ1
|
|
||||||
contradiction resolution, OQ3 duckdb-md export, OQ4 YAML-first leafs.
|
|
||||||
|
|
||||||
## Close epic #16 when
|
|
||||||
|
|
||||||
- facts+info can be written without a full rebuild for every leaf
|
|
||||||
- `--hop` stops erroring and runs a Cypher path from search hits
|
|
||||||
- ops pairing + chat import land as leafs on rebuild
|
|
||||||
- MCP tool order is documented and still gated by tests
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
type: howto
|
|
||||||
status: current
|
|
||||||
related:
|
|
||||||
- docs/README.md
|
|
||||||
- PLAN.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# Run 2dph (portable)
|
|
||||||
|
|
||||||
No laptop-absolute paths. Config lives in env files under `$HOME/.config/brain/`
|
|
||||||
(mode 0600), not in git.
|
|
||||||
|
|
||||||
## Toolchain
|
|
||||||
|
|
||||||
- Go (see `go.mod`)
|
|
||||||
- Python 3.12 + [uv](https://docs.astral.sh/uv)
|
|
||||||
- Optional: Docker, Zig CGO via `bin/cgo/zig` (not gcc)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv venv .venv
|
|
||||||
uv pip install -r requirements.lock.txt
|
|
||||||
eval "$(bin/cgo/zig env)" # when compiling Ladybug read tools
|
|
||||||
go test ./...
|
|
||||||
uv run python -m unittest discover -s bin/tools -t .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Config
|
|
||||||
|
|
||||||
| File / env | Purpose |
|
|
||||||
|------------|---------|
|
|
||||||
| `$BRAIN_SEARCH_ENV` (default `$HOME/.config/brain/search.env`) | `BRAIN_SEARCH_URL` (SearXNG). Optional Basic Auth. |
|
|
||||||
| `$HOME/.config/brain/db-profiles.yml` | read-only Postgres profiles (OnlyOffice via tunnel) |
|
|
||||||
|
|
||||||
If the host already runs SearXNG, point `BRAIN_SEARCH_URL` at it. Do not start
|
|
||||||
a second copy (D3). Optional Compose instance:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SEARXNG_SECRET=$(openssl rand -hex 32) docker compose --profile searxng up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
That binds `127.0.0.1:8888`. JSON format must stay enabled.
|
|
||||||
|
|
||||||
## Index then search
|
|
||||||
|
|
||||||
Write path is Compose profile `index` (Python Ladybug rebuild) until
|
|
||||||
`brain/add` is v2. The operator command is `bin/brain/index.go`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bin/brain/index.go --rebuild
|
|
||||||
bin/brain/search.go "LadybugDB vector index" # facts → info → web (D17)
|
|
||||||
bin/brain/search.go "upstream flag" --no-web
|
|
||||||
bin/brain/get.go <id> --body
|
|
||||||
bin/brain/stats.go
|
|
||||||
```
|
|
||||||
|
|
||||||
`--hop` is not implemented. Empty web results are `throttled`, not absence.
|
|
||||||
Gap to v1: [roadmap](roadmap.md) / [epic #16](https://git.produktor.io/eSlider/2dph/issues/16).
|
|
||||||
|
|
||||||
Ladybug 0.19: never `DROP INDEX` FTS/VECTOR (ghost catalog). Fresh indexes =
|
|
||||||
delete `var/kb.lbug` then `--rebuild`.
|
|
||||||
|
|
||||||
## HTTP / MCP
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d brain # :8630 Zig CGO serve
|
|
||||||
docker compose --profile index run --rm index # rebuild
|
|
||||||
docker compose --profile picoclaw up brain-mcp # MCP 127.0.0.1:8630
|
|
||||||
```
|
|
||||||
|
|
||||||
`GET /openapi.json`, `POST /mcp`. Agent tool order: `search` → `get` → `audit`.
|
|
||||||
|
|
||||||
## Reasoner (optional, D18)
|
|
||||||
|
|
||||||
CPU sidecar on `127.0.0.1:11435`. Weights are not in the 2dph image.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose --profile reasoner up -d reasoner
|
|
||||||
REASONER_BASE_URL=http://127.0.0.1:11435/v1 ./bin/reasoner/bakeoff.go --json
|
|
||||||
```
|
|
||||||
|
|
||||||
See [reasoner.md](reasoner.md).
|
|
||||||
@@ -31,9 +31,8 @@ bin/brain/stats.go # index health
|
|||||||
bin/brain/eval.go # recall@5 >= 0.95 gate (Go; Python bin/kb/eval is CI fallback)
|
bin/brain/eval.go # recall@5 >= 0.95 gate (Go; Python bin/kb/eval is CI fallback)
|
||||||
```
|
```
|
||||||
|
|
||||||
`bin/kb/search` is a deprecated wrapper. `--hop` errors (schema has
|
`bin/kb/search` is a deprecated wrapper. `--hop` errors (File/FROM_FILE edges
|
||||||
`FROM_FILE`; search does not walk it yet, [#17](https://git.produktor.io/eSlider/2dph/issues/17));
|
are not wired yet); do not treat it as a graph walk.
|
||||||
do not treat it as a graph walk.
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user