Gateway Module — `context/`
1. Purpose
The RAG assembly engine — the single retrieval surface behind assemble_context.
It fans out parallel searches across five source stores (psychic_cache, memory,
agent_memory_own, agent_memory_peer, vault_index), applies per-caller adaptive
source weights and recency boosts, enforces per-source token caps within
a resolved budget, optionally enriches with knowledge-graph triples and
cross-namespace expansion, and returns a formatted context_block with a logged
assembly_id. It owns the other half of the RAG loop too: context_feedback
records which sources were used, and a nightly eval cycle adjusts weights and
budgets from that feedback (Dream Phase 6.8).
2. File Inventory
| File | Lines | Responsibility |
|---|---|---|
service.ts | 498 | createContextAssemblyService.assemble(): budget resolution, query embed (+fallback), cross-namespace expansion, 6-way parallel fanout, weight/recency/priority scoring, dedup, KG enrichment pass, explicit-link reading (path-traversal-guarded), per-source-cap token allocation + greedy overflow, context_block formatting, assembly logging |
repository.ts | 414 | createContextRepository: source-weight + token-share + budget getters (caller→wildcard fallback), logAssembly, recordFeedback, runEvalCycle (3-dimension weight/share/budget learning), assembly-log query, distinct callers |
index.ts | 253 | createContextLayer factory + 4 search adapters (memory, psychic, agentMemory-own, peerMemory) that normalise each store to SearchResult |
types.ts | 220 | AssembleContextInputSchema, ContextFeedbackSchema, SourceStore/SourceEntry, GraphContextEntry (8-field, KG), SourceWeight, CallerBudgetConfig, AssemblyLogEntry, eval result types, SearchAdapter, repo/service/layer interfaces |
routes.ts | 146 | createContextRouter: /assemble, /feedback, /weights, /budgets, /log, /log/callers, /eval |
| Total | 1,531 |
3. Public API Surface
REST Endpoints
Mounted at /api/context. Auth per AK-415: GETs → Bearer;
writes → Bearer (write only). /api/context was explicitly dropped from the
former GET-exemption set (D913 Fix 2) — every route is authed.
| Method | Path | Auth | Body / Query | Response | Side Effects |
|---|---|---|---|---|---|
| POST | /assemble | Bearer | AssembleContextInputSchema | {assembly_id, context_block, sources[], total_tokens, retrieval_ms} / 400 / 500 | Embeds query; 5-store fanout; writes context_assembly_log (unless log:false); logs memory_search activity |
| POST | /feedback | Bearer | ContextFeedbackSchema | {success:true} / 400 | Updates context_assembly_log.used_source_ids + tokens_used |
| GET | /weights | Bearer | — | {weights: SourceWeight[]} | none |
| GET | /budgets | Bearer | — | {budgets: CallerBudgetConfig[]} | none |
| GET | /log | Bearer | ?caller&mission_namespace&limit&offset | {data: AssemblyLogEntry[]} | none |
| GET | /log/callers | Bearer | — | {data: string[]} | none |
| POST | /eval | Bearer | — | {weight_adjustments[], budget_adjustments[], weights_count, budgets_count} | Runs eval cycle → updates source_weights + caller_budget_config + source_weight_history; marks log rows processed |
MCP Tools
| Tool Name | Raw payload? | Endpoint | Notes |
|---|---|---|---|
assemble_context | No | POST /api/context/assemble | Primary retrieval tool; returns assembly_id + sources[] |
context_feedback | No | POST /api/context/feedback | Closes the RAG loop |
context_feedback_raw | Yes ({payload}) | POST /api/context/feedback | Staleness-immune twin |
4. Internal API
createContextLayer(db, embeddingProvider, memoryRepo, embeddingRepo, psychicRepo, agentMemoryRepo, activityRepo?, fallbackProvider?, vaultIndexRepo?, kgRepo?, crossRefRepo?): {service, contextRepo, router}— 9 required + optional additive positional args (KG/cross-ref threaded D1484; pre-Fuda 9-arg callers stay valid).ContextRepository—getWeight/getTokenShare/getDefaultBudget(withcaller='*'wildcard fallback),logAssembly,recordFeedback,runEvalCycle,getAssemblyLog,getDistinctCallers. Consumed by dashboard-api + curator + Dream.- Search adapters (module-private): each wraps a repo's search into the
SearchAdapterinterface with RRF +normaliseRrfScoresso all stores share a[0,1]metric space.
5. Background Services
- Eval cycle (
runEvalCycle) — not timer-driven inside the module; fired byPOST /api/context/eval(Dream Phase 6.8 nightly + manual). Batches unprocessedcontext_assembly_logrows per caller (minEVAL_BATCH_SIZE=10); computes effectiveness =0.5·confidence + 0.3·usageRate + 0.2·appearanceRate; adjusts weight + token_share (learning rate 0.1, clamped) skippingmanualrows; adjusts per-caller budget (±10%/−5% on utilisation/saturation, clamped min/max). - Assembly logging — synchronous within
/assemble(skippable vialog:falsefor health probes; returnsassembly_id=0sentinel).
6. Data Contracts
AssembleContextInputSchema={query≥1, caller≥1, mission_namespace?, token_budget?≤16000, explicit_links?[], log?}.ContextFeedbackSchema={assembly_id:int>0, used_source_ids[], tokens_used?:int≥0}.SourceStore='psychic_cache'|'memory'|'agent_memory_own'|'agent_memory_peer'|'vault_index'(5 members).GraphContextEntry(8-field: id, subject, predicate, object, valid_from, valid_to, tokens, content) — KG enrichment, flows throughcontext_blocktext with[GRAPH]prefix only; not insources[].
Scoring constants (service.ts): EXPLICIT_LINK_BUDGET_SHARE=0.3,
NULL_NAMESPACE_BUDGET=2000, MIN_KG_BUDGET_TOTAL=500, KG_BUDGET_SHARE=0.075,
CROSS_NAMESPACE_SCORE_DISCOUNT=0.5. Eval constants (repository.ts):
LEARNING_RATE=0.1, WEIGHT_MIN/MAX=0.1/1.5, TOKEN_SHARE_MIN/MAX=0.05/0.8,
EVAL_BATCH_SIZE=10.
Owned tables (declared in memory/db.ts): source_weights,
source_weight_history, context_assembly_log (+2 indexes), caller_budget_config.
7. Dependencies
- Gateway modules consumed:
memory/types(EmbeddingProvider,EmbeddingRepository,MemoryRepository),psychic-cache/types(PsychicCacheRepository),agent-memory/types,activity/repository,vault-index/repository(VaultIndexRepository),knowledge-graph/types(KGRepository,CrossRefRepository),shared/search-utils,shared/zod-error. - External libraries:
better-sqlite3,express,zod, Nodecrypto(query hash),node:fs/promises+node:path(explicit-link reads). - Environment variables:
VAULT_ROOT(explicit-link resolution root; defaults to 5-levels-up fromimport.meta.dirname— path-traversal guarded viaresolve+startsWith).
8. Test Coverage
| Layer | File | Tests |
|---|---|---|
| Adapters + layer wiring | test/context-index.test.ts | 11 |
| KG enrichment pass | test/context-kg-enrichment.test.ts | 8 |
| Repository (weights, budgets, eval cycle, log) | test/context-repository.test.ts | 28 |
| Routes (7 endpoints, Zod negatives) | test/context-routes.test.ts | 17 |
| Assembly service (fanout, scoring, caps, links) | test/context-service.test.ts | 18 |
Note: context-kg-enrichment.test.ts:356-365 carries a preserved-verbatim
GraphContextEntry fixture (Fuda v1.1 §15 A15) — the production interface shape
is anchored to it; do not drift without updating the fixture.
9. Known Limitations
assemblewritesdispatch_id: nullunconditionally inlogAssembly(service.ts:426) — the assembly log never captures the calling dispatch even when one exists. Under-populated analytics dimension.- KG + cross-namespace paths are optional-degrade — absent
kgRepo/crossRefReposilently skips enrichment; no signal to the caller that enrichment was unavailable. - Entity extraction is heuristic —
extractEntityCandidatesmatches uppercase-initial ≥3-char runs; can over/under-match. Capped at 10 entities × 5 triples per assemble. emptyResult+log:falseboth returnassembly_id=0— callers must treat 0 as "nothing to feed back".
10. Change History
| Date | Dispatch | Summary |
|---|---|---|
| 2026-07-04 | 2295 | Initial per-module spec filed (audit campaign Phase 4) |
| (historical) | D27 | Removed legacy memoryRetrieval param + dead ContextRetrievalService chain; adapters own retrieval |
| (historical) | D23 | Score-divergence fix bundle: normaliseRrfScores shared helper; recency boost moved to uniform funnel |
| (historical) | D41 | log:false opt-out for health-probe assemblies |
| (historical) | D1484 (AK-347) | KG enrichment + cross-namespace expansion resurrection (additive positional deps) |
| (historical) | D913 Fix 2 | /api/context moved off the former GET-exemption set (now Bearer-gated) |