TotalRecall.Infrastructure — Agent Guide
The infrastructure layer owns all side-effects: storage, embedding, importers, ingestion,
telemetry, search, and Cortex sync. Pure logic lives in TotalRecall.Core (F#).
AOT requirement: IsAotCompatible=true and InvariantGlobalization=true are set in the csproj.
No reflection, no dynamic code generation. All JSON via source-generated JsonContext.
Subsystem Map
| Directory | Interfaces / Key Types | Purpose |
|---|---|---|
Storage/ | IStore, SqliteStore, PostgresStore, RoutingStore | CRUD for memory/knowledge entries across tiers |
Embedding/ | IEmbedder, OnnxEmbedder, RemoteEmbedder, EmbedderFactory | Embedding generation (local ONNX or remote API) |
Search/ | IVectorSearch, IHybridSearch, IFtsSearch, VectorSearch, HybridSearch | Vector + FTS hybrid retrieval |
Importers/ | IImporter, ClaudeCodeImporter, CopilotCliImporter, … | One-way import from host tool memory dirs |
Ingestion/ | IFileIngester, FileIngester, HierarchicalIndex, IngestValidator | File/dir ingestion into cold KB tier |
Telemetry/ | CompactionLog, UsageEventLog, UsageDailyRollup, UsageWatermarkStore | Retrieval events, compaction history, usage telemetry |
Usage/ | IUsageImporter, UsageIndexer, UsageQueryService | Token usage tracking and query |
Sync/ | CortexClient, SyncQueue, SyncService, PeriodicSync, RoutingStore | Bidirectional Cortex sync |
Config/ | ConfigLoader, TomlConfig | TOML config loading + ~/.total-recall/config.toml |
Diagnostics/ | ExceptionLogger | AOT-safe exception chain logging |
Migration/ | MigrationRunner, PostgresMigrationRunner | Sequential schema migration framework |
Json/ | JsonContext (Infrastructure-level) | Source-generated JSON for Infrastructure DTOs |
Memory/ | Memory-specific helpers | Memory operation utilities |
Eval/ | Eval query services | Retrieval quality metrics |
Storage Layer
IStore
interface IStore
{
Task<Entry?> GetAsync(string id, ...);
Task<IReadOnlyList<Entry>> ListAsync(Tier tier, ContentType type, ...);
Task StoreAsync(Entry entry, ...);
Task UpdateAsync(string id, ...);
Task DeleteAsync(string id, ...);
Task MoveAsync(string id, Tier targetTier, ...); // for promote/demote
}
Borrowed-connection pattern: SqliteStore accepts a SqliteConnection and does NOT dispose it.
The process-lifetime connection is owned by ServerComposition.OpenSqlite() → ServerCompositionHandles.
Backend Selection
ServerComposition.OpenProduction() reads [storage] mode from config:
"local"(default) →SqliteStore+VectorSearch(sqlite-vec)"postgres"→PostgresStore+PgvectorSearch"cortex"→RoutingStorewrappingSqliteStore+CortexClientfor bidirectional sync
Fallback: If Cortex/Postgres fails to open, the composition root catches the exception and
falls back to SQLite, setting StorageMode to "sqlite (cortex failed)".
Schema
8 sequential migrations in Storage/Schema.cs. Never modify existing migrations — only append.
| Migration | What it adds |
|---|---|
| 1 | 6 content tables (hot/warm/cold × memory/knowledge) + vec0 virtual tables + indexes + system tables |
| 2 | _meta KV store + benchmark_candidates |
| 3 | FTS5 virtual tables + insert/delete/update triggers + backfill |
| 4 | compaction_log.source column (default 'compaction') |
| 5 | Orphan row cleanup (one-time hot-fix for 0.6.7) |
| 6 | Usage telemetry: usage_events, usage_daily, usage_watermarks |
| 7 | sync_queue for Cortex outbound buffering |
| 8 | scope TEXT NOT NULL DEFAULT '' on all 6 content tables + indexes |
Table naming: {tier}_{type} (e.g., hot_memories, cold_knowledge). Vec tables: {table}_vec. FTS tables: {table}_fts.
MigrationRunner.TableName(tier, type) is the canonical helper — use it, don't inline strings.
Embedding Layer
IEmbedder
interface IEmbedder
{
Task<float[]> EmbedAsync(string text, CancellationToken ct = default);
int Dimensions { get; } // 384 for local all-MiniLM-L6-v2
}
Factory: EmbedderFactory.CreateFromConfig(cfg.Embedding) selects the implementation:
provider = "local"(default) →OnnxEmbedderusing bundledmodels/all-MiniLM-L6-v2/model.onnxprovider = "openai"→RemoteEmbedderwith OpenAI-compatible endpointprovider = "bedrock"→RemoteEmbedderwith Amazon Bedrock
Model path: ModelManager.cs handles model discovery. Falls back to HuggingFace download if
models/all-MiniLM-L6-v2/model.onnx is missing (e.g., after a git-source install without LFS).
Search Layer
HybridSearch combines two sources:
- Vector search (
VectorSearch/PgvectorSearch) — cosine similarity via sqlite-vec / pgvector - FTS search (
FtsSearch/PostgresFtsSearch) — BM25-ranked full-text via FTS5 / tsvector
Results are merged and re-ranked by TotalRecall.Core.Ranking (F# pure function).
similarity_threshold from config filters low-score vector results before merge.
KbSearchHandler can additionally query a IRemoteBackend (Cortex) for global team KB.
Importers
IImporter
interface IImporter
{
string SourceTool { get; } // e.g., "claude-code"
Task ImportAsync(CancellationToken ct);
}
Each importer:
- Detects the host tool's memory directory (platform-specific paths)
- Reads the host's memory format (markdown, JSON, etc.)
- Deduplicates via
ImportLog(content hash lookup inimport_logtable) - Calls
IStore.StoreAsync()+IVectorSearch.UpsertAsync()for new entries
Importers run during session_start step 2. Order in ServerComposition matches the registration order.
Adding a new importer: implement IImporter, add to the importers list in both OpenSqlite
and OpenPostgres / OpenCortexCore in ServerComposition.cs.
Current importers: ClaudeCode, CopilotCli, Cursor, Cline, OpenCode, Hermes, ProjectDocs.
Ingestion
FileIngester → HierarchicalIndex → TotalRecall.Core.Chunker (F# pure) → IStore + IVectorSearch.
Flow:
IngestValidatorchecks for duplicate collections (by source path hash)Chunker.chunk()splits file content into semantic chunks (Markdown heading-aware or regex code parser)- Each chunk is embedded and stored as a
cold_knowledgeentry withcollection_idlinking siblings HierarchicalIndextracks parent/child relationships viaparent_id
Supported parsers (in TotalRecall.Core/Parsers.fs): Markdown, code (regex-based).
Telemetry
| Class | Table | Purpose |
|---|---|---|
CompactionLog | compaction_log | Records tier movements (promote/demote/compact) with semantic drift and preservation ratio |
UsageEventLog | usage_events | Raw token usage events (one row per assistant turn) |
UsageDailyRollup | usage_daily | Aggregated daily token counts; 30-day raw retention |
UsageWatermarkStore | usage_watermarks | Per-host scan watermark for incremental indexing |
RetrievalEventLog | retrieval_events | Per-search retrieval events with score, tier, and outcome signal |
Retrieval event logging: memory_search and kb_search handlers call LogRetrievalEvent()
after each search. Pass ctx.ConfigSnapshotId (set by session_start) so events are tagged to
the active config snapshot.
Cortex Sync
RoutingStore wraps SqliteStore:
- Reads go to local SQLite (fast, offline-safe)
- Writes enqueue items to
sync_queueAND write locally SyncServicedrainssync_queue→CortexClientHTTP callsPeriodicSynccallsSyncServiceevery N seconds (default 300s) on a background thread
Offline resilience: if Cortex is unreachable, local reads/writes continue. The queue is durable (SQLite) and survives process restarts.
Config
ConfigLoader.LoadEffectiveConfig() merges:
- Embedded defaults (
Config/defaults.tomlcompiled as an embedded resource) - User config at
~/.total-recall/config.toml(TOML via Tomlyn) - Environment variable overrides (e.g.,
TOTAL_RECALL_DB_PATH,TOTAL_RECALL_CORTEX_URL)
ConfigLoader.GetDbPath() → ~/.total-recall/total-recall.db (or TOTAL_RECALL_DB_PATH).
ConfigLoader.GetDataDir() → ~/.total-recall/.
Diagnostics
ExceptionLogger.LogChain(prefix, ex) walks the entire InnerException chain and writes to
Console.Error with indented -> <Type>: <Message> formatting. AOT-safe — no reflection.
Use at every catch boundary that can hit a static-constructor failure or P/Invoke failure:
- Migration guard
- Server composition (host startup)
- CLI commands that touch the embedder or open the DB
A bare Console.Error.WriteLine(ex.Message) at these boundaries hides DllNotFoundException
under TypeInitializationException and produces unactionable output.
AOT Checklist (for new Infrastructure code)
- No
Type.GetType(),Activator.CreateInstance(), orAssembly.Load() - All JSON serialization uses source-generated
JsonContextattributes - No
dynamictypes - P/Invoke signatures (sqlite-vec, ONNX) are declared as
[DllImport]or viaNativeLibrary.Load -
IsAotCompatible=truein the csproj — verify withdotnet publish -p:PublishAot=true -r win-x64producing 0 trim warnings