Files
openhuman/docs/tinycortex-drift-ledger.md
T

129 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TinyCortex Drift Ledger (Phase 0.1)
**Purpose.** The `tinycortex` port was taken at a point in time; the OpenHuman host
engine has continued to evolve since. This ledger enumerates every host commit that
touched an engine-mapping memory module after the port line, and classifies each as:
- **DRIFT → tinycortex PR** — a real engine behavior change absent from the crate; must be
re-applied upstream (submodule PR against `tinyhumansai/tinycortex`) before that module cuts over.
- **HOST-OWNED** — the change lives in a layer that stays in OpenHuman (RPC, agent tools,
event bus, embedding *compute*, live sync). No upstream needed.
- **HOST-RETAINED (crate excludes)** — an engine-adjacent feature the crate *deliberately*
does not own (declared in its own module docs). Stays host; may imply a **seam gap** (see
the API gap audit).
- **ALREADY PRESENT** — the change is already in the crate (port captured it).
> **Gate rule (plan §2/§6):** no module cuts over while its drift ledger row is open.
> A row is *closed* when its DRIFT items are merged upstream and the submodule SHA is bumped,
> or when the item is reclassified HOST-OWNED / HOST-RETAINED / ALREADY PRESENT.
## Anchors
| Thing | Value |
| --- | --- |
| Host repo | `tinyhumansai/openhuman` |
| Host audit SHA | `7850cf363559bcbb7ba688cbc4fccdb6bd9ce754` (`main`, 2026-07-04) |
| TinyCortex submodule | `vendor/tinycortex``tinyhumansai/tinycortex` |
| TinyCortex audit SHA | `d1a8c7be2babc8fff7a72ed93861f459f3d6fa58` |
| TinyCortex crate version | `0.1.1` |
| **Port line (derived)** | **after 2026-06-25, before 2026-06-28** (see below) |
### How the port line was located
The port commits in `vendor/tinycortex` are all dated **2026-06-29**, but that is the date the
*port* was authored, not the host state it captured. The line was pinned by **content**, not date:
- **≥ 2026-06-25 is captured.** Host `feat(memory_diff): back change ledger with git instead of
SQLite` (`040e6e20d`, 06-25) replaced the SQLite `mem_diff_read_markers` table with a git-backed
ledger. The crate's `diff/` uses the **git-backed** `ledger.get_read_marker(...)`
(`vendor/tinycortex/src/memory/diff/diff.rs:98`, `ledger.rs`) — i.e. the post-06-25 shape. So the
port base includes the 06-25 memory_diff work.
- **< 2026-06-28 for engine features.** Host `feat(memory): track summary-only wiki git history`
(`6395f642e`, 06-28) added `memory_store/content/wiki_git/`. The crate has **no** `wiki_git` file
anywhere — but see the reclassification below: the crate *deliberately* excludes it, so this is not
proof of a stale base, it is a declared boundary.
Net: only commits after 2026-06-25 that touch engine-mapping modules are drift candidates, and each
was verified against crate content individually below.
---
## Drift candidates (verified individually against crate content)
Scan: `git log --since=2026-06-20 -- src/openhuman/memory_store memory_tree memory_queue memory_diff
memory_goals memory_entities memory_graph memory_archivist memory_conversations memory_sources`,
then per-commit file lists intersected with engine-mapping modules, then content-diffed against
`vendor/tinycortex`.
### DRIFT → needs tinycortex PR
| # | Host commit | Module | Change | Crate state (verified) | Upstream target |
| --- | --- | --- | --- | --- | --- |
| D1 | `007a99b62` (06-30) `perf(memory_conversations): rank before cloning hits in cross-thread search` | `memory_conversations/inverted_index.rs` | Rank matches on cheap borrowed keys (`(doc_id:u32, matched:usize, created_at:&str)`), truncate to `limit`, **then** materialize the KB-sized `CrossThreadHit`. Order-equivalent to score ranking. | **ABSENT.** `vendor/tinycortex/src/memory/conversations/inverted_index.rs:286301` builds the full `CrossThreadHit` (with `content.clone()`, `message_id.clone()`, `created_at.clone()`) for **every** matched doc, then `sort_by(score)` + `truncate`. Pre-fix clone-then-rank shape. | `conversations::inverted_index` — port the rank-before-materialize refactor + its `ranks_by_score_then_recency_before_truncating` test. |
| D2 | `d7bee77e3` (06-30) `fix(memory-queue): classify host-FS I/O errors to stop the tree_jobs Sentry flood` | `memory_queue/worker.rs` | Adds `is_host_io_error(&anyhow::Error) -> bool` classifying **persistent** host-FS failures (EIO/ENOSPC/EROFS) distinct from transient SQLite busy/I-O, so the worker backs off and reports Sentry **once** instead of ~10k events/50min (Sentry CORE-RUST-19J). | **PARTIAL.** `vendor/tinycortex/src/memory/queue/worker.rs:89107` has `is_sqlite_io_transient` (transient family) but **no** `is_host_io_error` (persistent host-FS family). | `queue::worker` — port the `is_host_io_error` predicate + its unit tests (EIO/ENOSPC/EROFS, context-layer, text fallback). **Only the predicate.** The Sentry-once emission and the `mark_storage_degraded` flag are host-owned (see D2-host below). |
| D3 | `c43f79641` (07-03) (within TinyAgents migration) | `memory_store/vectors/store.rs` | `count()` reads `COUNT(*)` as `i64` and converts via `usize::try_from(...).context(...)` instead of `row.get::<usize>` directly — robustness against platform `usize`/`i64` mismatch. | **ABSENT.** `vendor/tinycortex/src/memory/store/vectors/store.rs:370380` still does `let count: usize = ... row.get(0)` then `Ok(count)`. | `store::vectors::store` — small; port the `i64` + `try_from` guard. |
**Open drift rows: D1, D2 (predicate), D3.** These are the only three engine behavior changes since the
port line. All three are small-to-moderate and independent.
- D1 gates **W7** (long tail — conversations).
- D2 gates **W4** (queue).
- D3 gates **W3** (store + chunks).
### HOST-OWNED — same commits, layers that stay in OpenHuman (no upstream)
| Host commit | File(s) | Layer | Why host |
| --- | --- | --- | --- |
| `0304d145f` (07-03) | `memory/tools/store.rs`, `memory/tools/forget.rs` | Agent tools | Tool contract/prompt text; agent tools stay host (plan §1). |
| `7bf18562a` (06-30) | `memory/read_rpc/{types,vault}.rs` | RPC read surface | `read_rpc` stays host; JSON-RPC surface. |
| `f84eec533` (06-30) | `memory_conversations/bus.rs` | Event bus | `bus.rs` = `EventHandler` impls, host-owned by canonical module shape. |
| `6edaa77b1` (06-29) | `memory_tree/score/embed/openai_compat.rs` | Embedding **compute** | Network-calling embedding backend; the crate abstracts compute behind `EmbeddingBackend` and "never makes a network call". Wires into the W1 `embeddings.rs` seam. |
| `d7bee77e3` (06-30) [D2-host] | `memory_tree/health/{mod,doctor}.rs` (`mark_storage_degraded`/`clear_storage_degraded`), `memory_tree/tree/rpc.rs` | Health signal + RPC | Degraded-state flag + Sentry wiring + doctor RPC. Crate defers tree health entirely (see gap audit); this is the host-side consumer of D2's predicate. |
| `c43f79641` (07-03) | `memory_search/{vector,tools}/*`, `memory_sync/composio/*` | Agent tools / live sync | Import-path churn from the TinyAgents cutover + live-sync; not engine semantics. |
### HOST-RETAINED — crate deliberately excludes (not drift)
| Host commit | File(s) | Crate declaration |
| --- | --- | --- |
| `6395f642e` (06-28) `feat(memory): track summary-only wiki git history` | `memory_store/content/wiki_git/` (mod + tests, ~690 LOC), plus a seal-time hook in `memory_tree/ingest.rs` + `memory_tree/tree/bucket_seal.rs` | `vendor/tinycortex/src/memory/store/content/mod.rs:1920`: *"The Obsidian-vault registry (`content::obsidian*`) and the git-backed wiki mirror (`content::wiki_git`) pull host config and git surfaces beyond this."* The crate explicitly leaves `wiki_git` **and** `obsidian*`/`obsidian_registry` host-side (host `memory_store/content/mod.rs:17,18,23`). |
**Reclassification note (important).** At first pass this looked like drift (feature absent from crate).
It is **not** — the crate's own content module doc names `content::wiki_git` and `content::obsidian*` as
host surfaces it does not own. So `wiki_git`, `obsidian`, `obsidian_registry` join `memory_sync` as
**host-retained** parts of an otherwise-moving module. **Consequence:** the seal-time hook that
`6395f642e` wired into `bucket_seal.rs` has **no counterpart callback in the crate's `bucket_seal`**
(`vendor/tinycortex/src/memory/tree/bucket_seal.rs` exposes no post-seal sink). That is tracked as an
**API gap** (a `TreeJobSink`-style "summary sealed" callback the host implements to drive `wiki_git`),
not as drift. See `tinycortex-api-gap-audit.md`.
---
## Per-module drift status (the gate table)
| Engine module | Maps to crate | Open drift | Gates workstream | Status |
| --- | --- | --- | --- | --- |
| `memory_store` (chunks, content, vectors, kv, entity_index, safety) | `store/`, `chunks/` | **D3** (vectors count guard). `wiki_git`/`obsidian*` host-retained (not drift). | W3 | **OPEN** (D3) |
| `memory_tree` (tree, retrieval, score, summarise) | `tree/`, `retrieval/`, `score/` | none (health/rpc/embed-compute are host-owned) | W5 | **CLEAR** |
| `memory_queue` | `queue/` | **D2** (predicate) | W4 | **OPEN** (D2) |
| `memory_conversations` | `conversations/` | **D1** (rank-before-clone) | W7 | **OPEN** (D1) |
| `memory_diff` | `diff/` | none (git-ledger captured) | W7 | **CLEAR** |
| `memory_entities` | `entities/` | none | W7 | **CLEAR** |
| `memory_graph` | `graph/` | none | W7 | **CLEAR** |
| `memory_goals` | `goals/` | none | W7 | **CLEAR** |
| `memory_archivist` | `archivist/` | none | W7 | **CLEAR** |
| `memory_sources` (registry + local readers) | `sources/` | none | W7 | **CLEAR** |
| `memory_tools` (engine part) | `tool_memory/` | none | W7 | **CLEAR** |
| `memory_search` (`vector`, `scoring` engine parts; `tools` are host) | `retrieval/`, `score/` | none (churn only) | W5 | **CLEAR** (classify tools vs engine in W5) |
**Summary:** 3 open drift rows (D1, D2, D3), each small and independent, each a single-module
tinycortex PR. Nothing else drifted. `memory_search` is a mixed module not in the plan's move table —
its `tools/` stay host (agent tools), its `vector`/`scoring` are engine (W5) — flagged for the gap audit.
## Closing the ledger (procedure)
For each open row:
1. Branch in `vendor/tinycortex`, port the change (impl + test), PR against `tinyhumansai/tinycortex`.
2. Merge upstream; bump the submodule in a standalone host commit
`chore(vendor): bump tinycortex — <what> (tinycortex#<n>)`, keeping the `[dependencies]` pin in lockstep.
3. Flip the row to **CLOSED** here; only then may the gated workstream cut over.