# Architecture Co-Engram is built in five layers. Each layer has one job and communicates with adjacent layers through well-defined boundaries. ## Layered View ```mermaid flowchart TB subgraph L1["1. Host Layer"] direction LR CC["Claude Code"] OC["OpenClaw"] Custom["Custom Agent"] end subgraph L2["2. Adapter Layer (thin glue)"] direction LR A1["@co-engram/claude-code
MCP server (stdio)"] A2["@co-engram/openclaw
Plugin SDK"] A3["Your adapter"] end subgraph L3["3. Core Layer (host-agnostic)"] Tools["Tools (30)
engrams · synapses · skills · learning · doctor · synthesize"] Retrieval["Retrieval
FTS (word-level) · graph traversal"] Engine["Maintenance Engine
light · deep · rem"] Signals["Signals
event sink · extract · RPE"] Verify["Verification
metacognition · upgrade/refute"] end subgraph L4["4. Data Layer (separate Git repo)"] Engrams["/.md
(frontmatter + body)"] Synapses["synapses//syn-.yaml"] Skills["skills/"] Intentions["intentions/"] Config["config/"] end subgraph L5["5. Cache Layer (gitignored)"] FTS[".co-engram/engram-index.json"] Vectors[".co-engram/digest.jsonl"] Graph[".co-engram/graph.json"] end CC --> A1 OC --> A2 Custom --> A3 A1 --> Tools A2 --> Tools A3 --> Tools Tools --> Retrieval Tools --> Engine Engine --> Signals Engine --> Verify Retrieval --> FTS Retrieval --> Graph Tools --> Engrams Tools --> Skills Engine --> Engrams ``` ## Layer Responsibilities ### 1. Host Layer The application that uses Co-Engram. Currently supported: - **Claude Code** — desktop / CLI AI coding assistant - **OpenClaw** — open-source agent gateway - **Custom** — any TypeScript/JavaScript process that can call MCP or import `@co-engram/core` directly ### 2. Adapter Layer Thin glue that translates between the host's protocol and the core API. Each adapter: - Receives tool calls in host-specific format (MCP JSON-RPC, OpenClaw plugin API) - Converts to core `ToolContext` and dispatches to the right `Tool.execute` - Wraps the result back into host-specific format - Optionally injects `signalSink` and starts the maintenance engine **Hard rule:** adapters contain no business logic. If you find yourself writing memory rules in the adapter, it belongs in core. ### 3. Core Layer (`@co-engram/core`) The heart of Co-Engram. Zero host dependencies — no `@modelcontextprotocol/sdk`, no `openclaw`, no MCP types. Five sub-modules: - **Tools** — 30 self-describing tools with Zod schemas, used by both MCP and plugin adapters - **Retrieval** — in-memory inverted index over `digest.jsonl` (Intl.Segmenter word-level tokenizer for CJK + word tokenizer for English), plus graph traversal via synapse edges - **Maintenance Engine** — runs `light` / `deep` / `rem` stages on intervals (see [maintenance-engine.md](./maintenance-engine.md)) - **Signals** — collects `ToolCallEvent`s, extracts behavioral signals, computes RPE (prediction error) - **Verification** — five-dimension truth scoring (cross-context / time-stable / mutually-supported / source-reliable / executable) ### 4. Data Layer A **separate Git repository** at `$CO_ENGRAM_DATA_ROOT` (default: `~/team-memory`). This is the source of truth. ``` team-memory/ ├── / # Engram files organized by domain │ └── .md # One engram = one file (frontmatter + body) ├── synapses/ # Per-edge synapse storage │ └── / │ └── syn-.yaml # One edge = one file ├── skills/ # Procedural memory ├── intentions/ # Pending intentions └── config/ # Repo-level config ``` **Why one file per engram?** See [design-rationale.md](./design-rationale.md). TL;DR: content diffs stay reviewable in Git while metadata evolves independently, and a ULID (decoupled from the file path) keeps synapse references stable across renames and moves. ### 5. Cache Layer Gitignored `.co-engram/` inside the data repo. Derived artifacts: - `engram-index.json` — fast ULID → entry lookup, drives `engram_doctor` incremental scans and `engram_list_paths` - `digest.jsonl` — one-line-per-engram catalog used by the retrieval orchestrator; rebuilt when content hashes change - `graph.json` — synapse graph snapshot for fast traversal - `index.db` *(default since 0.2.0; opt-out via `CO_ENGRAM_SEARCH_ENGINE=memory`)* — SQLite-derived index (WAL + FTS5 trigram) for scaling to 5k+ engrams; see [Search Engine](#search-engine) below Rebuildable at any time by deleting `.co-engram/` and triggering an incremental rebuild (e.g. via `engram_doctor` or restarting the host). ## Data Flow ### Write Path ``` Host tool call → Adapter → Tool.execute(ctx, input) → Zod validates input → Repository writes /.md (frontmatter + body) → Git commit → FTS index updated (async) → Return EngramRef to host ``` ### Read Path ``` Host tool call → Adapter → engram_search → FTS query (Intl.Segmenter word-level tokenizer) → Graph expansion (follow consolidates/extends edges) → Score by: relevance · recency · importance · reinforcementScore · access heat (hotness) → Bump retrieval stats (effectiveRetrievals, lastRetrievalScore) → Return ranked EngramRef[] ``` ### Maintenance Path ``` Every 5 min (light): drain signal sink → extract behavioral signals → RPE update → bump effectiveRetrievals / failedUses / reinforcementScore → auto-merge near-duplicate engrams (consolidates synapse) Every 1 hour (deep): re-run light dreaming (extra consolidation pass) → evaluate freshness decay (age vs halfLife); forget/archive stragglers by importance threshold → sweep long-forgotten engrams into .trash/ Every 1 day (rem): run abstraction dreaming + metacognition 5-dim scoring → generate rem-verification proposals (land only after user accepts in Proposals) ``` ## Search Engine Co-Engram ships two interchangeable search backends behind the `SearchEngine` interface. Pick one with the `CO_ENGRAM_SEARCH_ENGINE` env var (default: `sqlite`). ### `sqlite` (default, scaling path) Derived SQLite index at `.co-engram/index.db` (WAL mode, FTS5 trigram tokenizer). Designed for the 5k+ engram target. Default since 0.2.0 — at small scale (≤1k engrams) cold start is a few dozen ms and steady-state overhead is negligible; at large scale it stays sub-100ms where `memory` blows past 1s. - **Filesystem stays the source of truth.** SQLite is purely derived — drop the file, run `engram_doctor` or just restart, and it gets rebuilt from `engrams/*.md` on cold start. - **Write-through.** `EngramRepository.createEngram / updateEngram / deleteEngram / mutateFrontmatter` transparently upsert/delete the derived row after the file lands. SQLite write failures are fail-silent at the repository layer (file truth still wins; `engram_doctor` + cold start reconcile drift). - **Recall parity with `memory`.** The LIKE fallback covers title + summary + content_tokens + domain tags for queries below the trigram minimum length (3 UTF-16 code units). At ≥3 chars, FTS5 trigram matches memory-FTS recall on the same text (Jaccard = 1.0 in the regression suite). - **Cold start.** First launch against a non-empty repo triggers a one-shot full rebuild inside a single transaction. Hot start (db already has rows) is a no-op. - **Concurrency.** WAL allows multiple reader processes alongside a single writer. Both host adapters (`claude-code-mcp`, `openclaw-plugin`) can mount the same `dataRoot` simultaneously. - **Fail-safe fallback.** If SQLite is unavailable at boot time (Node < 22.17, file system permission error, schema corruption, disk full), `bootstrapRepositoryAndSearch` catches the error, logs `[co-engram] search engine: sqlite unavailable (...) falling back to memory`, and transparently degrades to the `memory` engine — host startup never crashes. - **Node version requirement.** Uses the built-in `node:sqlite` module (stabilized in Node 22.17). The `engines.node` field in every package is pinned to `>=22.17.0`; older Node silently falls through to the `memory` fallback above. ### `memory` (opt-out) In-process FTS over `digest.jsonl` lines. Tokenizer is Intl.Segmenter word-level (CJK) + word (English). Suitable for repos up to ~1k engrams — `digest.jsonl` is parsed on every `rebuildSearchIndex()` call, and the FTS index lives in heap. - Zero disk footprint beyond `digest.jsonl`. - Recomputed on every watcher invalidation (cheap at small scale). - Set `CO_ENGRAM_SEARCH_ENGINE=memory` to opt out of SQLite — useful for embedded / read-only-fs / sandboxed deployments where the `index.db` side-effect is undesirable. Unknown values fall back to `sqlite` (fail-safe toward the stronger engine — a typo never silently downgrades you to the less-scalable backend). ## Boundary Rules 1. **Host code never imports core internals directly** — only through adapter packages or the published `@co-engram/core` barrel. 2. **Adapters never add new tools** — they only expose existing core tools via host protocols. 3. **Core never reads host config** — all configuration is injected via `ToolContext` or constructor params. 4. **Data repo never contains executable code** — only Markdown / YAML / JSON. No `.ts`, no `.js`, no scripts. ## Extending Co-Engram - **New host adapter** — copy `packages/claude-code-mcp/` as a starting point, swap the MCP SDK for your host's protocol - **New tool** — add to `packages/core/src/tools/`, register in `tools/registry.ts`, declare in `openclaw.plugin.json` contracts.tools - **New maintenance stage** — extend `MaintenanceEngine` with a new `run` method, add to `DreamingScheduler` See [CONTRIBUTING.md](../CONTRIBUTING.md) for the development workflow.