# TinyCortex API Gap Audit (Phase 0.2)

**Status (2026-07-22):** Historical gap inventory for the completed engine
cutover. Counts and source anchors below describe the audit base; unresolved
host consolidation work, including G1 re-homing and sync dedupe, is tracked in
`tinycortex-migration-plan-2026-07-22.md`.

**Purpose.** Map every host call site into the engine-mapping modules
(`memory_store`, `memory_tree`, `memory_queue`, and the long tail) onto a TinyCortex
public API. Where no crate API covers a host need, record a **gap** — each gap becomes a
`tinycortex` issue/PR (engine-side) or an explicit host-adapter decision, and gates the
workstream that touches it.

Anchors: host `7850cf363` · tinycortex `d1a8c7be` (v0.1.1). Crate API surface inventoried
from `vendor/tinycortex/src/memory/**` (`pub` traits/fns/types, verbatim signatures).

## Call-site volume (host → engine, external references only)

External refs = uses of `memory_<mod>::…` from host code **outside** that module.

| Engine module | External refs | Maps to crate | Coverage |
| --- | --- | --- | --- |
| `memory_store` | 454 | `store/`, `chunks/` | High — see per-API below |
| `memory_tree` | 169 | `tree/`, `retrieval/`, `score/` | High (gaps G3, G5) |
| `memory_queue` | 42 | `queue/` | High (drift D2) |
| `memory_sources` | 35 | `sources/` | Full (local readers only) |
| `memory_conversations` | 24 | `conversations/` | Full (drift D1) |
| `memory_tools` | 12 | `tool_memory/` | Full |
| `memory_diff` | 11 | `diff/` | Full |
| `memory_archivist` | 9 | `archivist/` | Full |
| `memory_goals` | 6 | `goals/` | Full |
| `memory_search` | 1 | `retrieval/`, `score/` | tools stay host |
| `memory_entities` | 0 | `entities/` | Full |
| `memory_graph` | 0 | `graph/` | Derive-on-read (gap G2) |

Highest-traffic `memory_store` sub-APIs (external): `chunks::store` (152), `chunks::types`
(93), `trees::types` (54), `create_memory` (31), `trees::store` (27), `profile` (26),
`types` (19), `content` (19), `content::compose` (14), `content::paths` (10),
`content::read` (9), `content::atomic` (8), `content::raw` (7). All of `chunks::*`,
`trees::*`, `content::*`, `profile` map to crate re-exports (`chunks::…`, `tree::…`,
`store::content::…`, `config::WeightProfile`). `create_memory` is host glue (see below).

## Seam-trait roster (the W1 adapter targets — all present in crate)

The crate exposes every seam the plan expects, plus more. Host implements these:

| Trait | Path | Host adapter (W1) |
| --- | --- | --- |
| `EmbeddingBackend` | `store/vectors/embedding.rs:32` | `tinycortex/embeddings.rs` over `openhuman::embeddings` *(amended: bridged to `tinyagents::harness::embeddings::EmbeddingModel` in W-EMB, plan §8.2)* |
| `Embedder` (retrieval/summarise) | `score/embed.rs:28` | same seam file |
| `ChatProvider` | `score/extract/llm.rs:70` | `tinycortex/chat.rs` over `memory::chat` / `inference` |
| `Summariser` (bucket-seal, structured) | `tree/summarise.rs:70` | `tinycortex/chat.rs` |
| `Summariser` (time-tree, string) | `tree/runtime/engine.rs:29` | **divergent — see G6** |
| `EntityExtractor` (regex + LLM) | `score/extract/composite.rs:16` | `tinycortex/chat.rs` (LLM variant) |
| `QueueDelegates` | `queue/handlers.rs:96` | `tinycortex/queue_driver.rs` |
| `TreeJobSink` | `ingest/types.rs:15` | `tinycortex/ingest.rs` |
| `TreeLeafSink` | `archivist/sink.rs:44` | `tinycortex/ingest.rs` |
| `SnapshotItemSource` | `diff/source.rs:28` | host diff adapter |
| `EntityOccurrenceIndex` | `graph/types.rs:44` | host graph adapter |
| `SelfIdentity` | `store/entity_index/store.rs:25` | host identity registry |
| `GoalsGenerator` | `goals/reflect.rs:57` | `tinycortex/chat.rs` |
| `SourceReader` | `sources/readers/mod.rs:1732` | local readers only *(amended: live-sync readers join the crate in W-SYNC, plan §8)* |
| `SyncEventSink` *(new, W-SYNC)* | `sync/traits.rs` (planned) | `tinycortex/sync_sink.rs` → `MemorySyncStage` bus events |
| `SkillDocSink` *(new, W-SYNC)* | `sync/traits.rs` (planned) | forwards to `MemoryClient::store_skill_sync` (host-retained unified tier) |
| `ConversationEventBus` / `ChannelEventHandler` | `conversations/bus.rs:106/95` | host `memory_conversations/bus.rs` (translate to `DomainEvent`) |

Config seam: `MemoryConfig{workspace, embedding: EmbeddingConfig{dim,model,strict},
tree: TreeConfig{input_token_budget,output_token_budget,summary_fanout,flush_age_secs},
retrieval: RetrievalConfig{default_profile}, sync_budget: SyncBudgetConfig}` +
`WeightProfile{graph,vector,keyword,freshness}` — all `pub` with the exact fields the W1
`config.rs` adapter maps `Config` onto.

---

## Gaps (each → tinycortex issue/PR or explicit host-adapter decision)

### G1 — Raw SQLite connection escape hatch (`sqlite_conn()`) — **HARD, high blast radius**

- **Host:** two trait methods return a live handle:
  `memory/traits.rs:277` and `memory_store/memory_trait.rs:458`
  `fn sqlite_conn(&self) -> Option<Arc<Mutex<Connection>>>`. ~312 references to
  `sqlite_conn`/`raw_conn`/`with_connection` across the host.
- **Crate:** deliberately omits it (`traits.rs:3-6`). Offers instead a **scoped** accessor
  `chunks::with_connection(config, |conn| …)` (`chunks/mod.rs:217`) over the cached handle;
  `conn_cache()` is private.
- **Decision (W2):** do **not** upstream a handle-getter (it breaks the crate's connection-cache
  invariants). Instead: (a) migrate host internal raw-SQL sites to `chunks::with_connection`
  during W3; (b) keep `sqlite_conn()` as a **host-side extension trait** over the crate store for
  the transition, shrinking to zero as sites migrate. Track residual call sites in the deletion
  ledger. **Gates W2/W3.**

### G2 — Graph relation-edge persistence (E2GraphRAG accumulation) — **HARD**

- **Host:** `memory_store/namespace_store/{graph,query}.rs` persist and query co-occurrence
  / LLM-triple relations; `NamespaceMemoryHit.supporting_relations: Vec<GraphRelationRecord>`
  is populated at retrieval.
- **Crate:** persists the **entity occurrence index** at persist time
  (`score::persist_score`/`persist_score_tx`, `score/mod.rs:847-906`) but **does not write
  co-occurrence graph edges** — docstring `score/mod.rs:843-846`: *"Co-occurrence graph edges
  (OpenHuman's E2GraphRAG accumulation) are not written here."* The `graph` module **derives**
  edges on read from the occurrence index (`graph/query.rs`, "owns no state, performs no
  writes"). `GraphRelationRecord` exists as a type but nothing in the crate writes it.
- **Decision (W6/W7):** verify whether derive-on-read reproduces the host's persisted-relation
  retrieval (`supporting_relations`) with parity. If yes → host relation store stays host
  (namespace doc/graph store is explicitly host per plan §1); if the crate must own accumulation
  → upstream a relation-persist path. **Gates W6/W7.** Parity-critical for retrieval output.

### G3 — Seal-time embedding — **MEDIUM (parity timing)**

- **Host:** computes summary embeddings around seal (`memory_tree/tree/bucket_seal.rs`,
  `memory_tree/score/embed/*`, `set_summary_embedding`).
- **Crate:** `bucket_seal.rs:21` / `tree/mod.rs:22` list "seal-time embedding" as **not ported**;
  `seal_one_level` computes labels but never embeds. Summary embedding runs later via the queue's
  `reembed_batch` delegate. The `mem_tree_summary_embeddings` sidecar + setters exist.
- **Decision (W5):** confirm retrieval parity on freshly-sealed summaries — if the host embeds at
  seal but the crate defers to the queue, a summary is momentarily unembedded until the worker
  drains. Either drive an embed job at seal from the host queue driver, or upstream seal-time
  embedding. **Gates W5.**

### G4 — Post-seal notification sink (for host `wiki_git` mirror) — **SOFT**

- **Host:** `6395f642e` wired a seal-time hook in `bucket_seal.rs` to commit summary markdown to
  the git-backed `wiki_git` mirror (host-retained, per the drift ledger).
- **Crate:** no observer/sink trait for seal events — but `append_leaf` / `cascade_all_from` /
  `TreeFactory::seal_now` **return `Vec<String>` of sealed summary ids**. The host can drive
  `wiki_git` from the returned ids without a callback.
- **Decision (W3/W5):** no crate change required; the host queue driver calls `wiki_git` after each
  seal using returned ids. Record as a seam-wiring task, not an upstream gap. Revisit only if a
  push-notification is later wanted.

### G5 — Tree health / doctor (storage-degraded state machine) — **SOFT (host-retained)**

- **Host:** `memory_tree/health/*` (`mark_storage_degraded`/`clear_storage_degraded`, degraded
  flag), consumed by `memory/tools/doctor.rs` and fed by the D2 host-FS classifier.
- **Crate:** defers tree health/doctor entirely (`tree/mod.rs:20`); only `Memory::health_check()
  -> bool`. Queue uses its own `JobFailure` (no storage-degraded machine).
- **Decision (W5):** **host-retained** (like `wiki_git`). `memory_tree::health` stays host; the D2
  predicate (queue classifier) upstreams, its Sentry-once + degraded-flag consumers stay host.

### G6 — Two divergent `Summariser` traits — **MEDIUM (ambiguity)**

- Crate has **two** traits named `Summariser`: `tree/summarise.rs:70` (structured batch fold —
  `summarise(&[SummaryInput], &SummaryContext) -> SummaryOutput`) used by bucket-seal, and
  `tree/runtime/engine.rs:29` (string in/out — `summarise(Option<&str>, &str) -> String`) used by
  the markdown time-tree runtime. They are distinct contracts with the same name.
- **Decision (W1/W5):** the host seam must implement **both**. Flag for possible upstream
  unification (rename one, e.g. `TimeTreeSummariser`) to reduce confusion — non-blocking. Note
  which tree path OpenHuman actually drives (bucket-seal SQLite trees vs markdown time-tree) so we
  don't wire a dead seam.

### Non-gaps (expected host glue — no crate change)

| Host API | Refs | Disposition |
| --- | --- | --- |
| `memory_store::create_memory` / `create_memory_with_local_ai` (`factories.rs:297,322`) | 31 | Host glue building `UnifiedMemory`. Crate has no `UnifiedMemory`/`MemoryClient` (by design). **W3 re-implements these factories over `tinycortex::store` + `chunks`, keeping the host-facing `MemoryClient` API stable.** Not a crate gap. |
| `memory_store::profile` (`unified::profile`) | 26 | Maps to `config::WeightProfile` (fields `graph/vector/keyword/freshness`, `by_name`). |
| `memory_store::fts` / keyword search | 7 | Crate provides `retrieval::keyword_relevance` + `hybrid_score` (not SQLite FTS5). **Verify keyword-recall parity in W5** (behavioral, not a missing API). |
| Namespace document/graph store | — | Explicitly host-retained per plan §1 until/unless upstreamed (ties to G2). |

---

## Gap → workstream gate summary

| Gap | Severity | Gates | Resolution path |
| --- | --- | --- | --- |
| G1 escape hatch | High | W2/W3 | Migrate to `with_connection`; host extension trait for transition |
| G2 graph relations | High | W6/W7 | Parity check derive-on-read; upstream persist iff needed |
| G3 seal-time embedding | Medium | W5 | Drive embed at seal from host queue driver, or upstream |
| G4 seal notification | Soft | W3/W5 | Drive `wiki_git` from returned sealed ids (no crate change) |
| G5 tree health/doctor | Soft | W5 | Host-retained |
| G6 two `Summariser` traits | Medium | W1/W5 | Implement both; optional upstream rename |

**No hard gap blocks W1 (seam scaffolding) or W4 (queue, only drift D2).** W3 is gated by G1;
W5 by G3/G6; W6/W7 by G2. These are recorded here and mirrored in the spec's deletion ledger.

**Amendment 2026-07-09 (plan §8):** the crate's sync data model (`SourceKind::Composio`,
`SyncBudgetConfig`, the TOML source registry with `upsert_composio_source` /
`memory_sync_defaults_for_toolkit`) — previously host-consumed only — becomes **engine-consumed by
W-SYNC**: the crate's own sync engine reads the registry and enforces the budget. New seam traits
`SyncEventSink` / `SkillDocSink` land with W-SYNC.1 (see roster above); no other new gaps.
