14 KiB
Below is a single copy‑pastable Markdown file that proposes a client‑side architecture which treats memory as a living, hierarchical JSON dictionary‑of‑dictionaries (HDoD), adds semantic + episodic activation and pruning, and composes prompts for your coding agent. It maps cleanly onto the MCP tools you currently expose:
kom.meta.v1.project_snapshotkom.local.v1.backup.import_encryptedkom.local.v1.backup.export_encryptedkom.memory.v1.search_memorykom.memory.v1.upsert_memorykom.memory.v1.recall_contextkom.memory.v1.save_context
It keeps the server simple and pushes intelligence about memory into the client orchestrator, so you can get the behavior you want today, without first redesigning the server.
Kompanion Client Memory Architecture (HDoD)
Version: 0.2 • Scope: Client‑side AI interface to Kompanion MCP Server Author: Χγφτ (Kompanion of Esus / Andre) Purpose: Make memory behave like a nested JSON of concepts that “lights up” semantically and episodically, prunes naturally, and feeds agent prompts with high‑quality domain tokens (e.g., C++ patterns) — without waiting on a more complex server.
1. Why this exists
Large models feel “smart but memoryless.” We want:
- A hierarchical mental map (JSON dictionary‑of‑dictionaries, “HDoD”),
- Activation dynamics (semantic + episodic “lighting up” of nodes/paths),
- Organic pruning (cool down; unload),
- Prefilled knowledge packs (domain seeds: e.g., C++ idioms),
- Deterministic prompt composition for coding agents (Codex/Qwen/etc.).
The server currently provides a minimal memory API. That’s fine: we’ll implement the cognitive part client‑side and use the server as a persistence/search/recall backbone.
2. The mental model (HDoD = dictionary‑of‑dictionaries)
Think of memory as a normalized graph, presented to the user/agent as a dictionary tree. Each node is a Concept with:
{
"id": "skill.cpp.templates.sfinae",
"type": "concept", // concept | skill | fact | snippet | pattern | episode | tool | persona | task
"label": "SFINAE",
"payload": { "definition": "...", "examples": ["..."], "anti_patterns": ["..."] },
"embeddings": { "model": "local-bge|text-embed-3", "vector": [/* ... */] },
"links": [
{"to": "skill.cpp.templates.metaprogramming", "rel": "is_a", "w": 0.8},
{"to": "pattern.cpp.enable_if", "rel": "uses", "w": 0.7}
],
"children": { "intro": "skill.cpp.templates.sfinae.intro", "advanced": "skill.cpp.templates.sfinae.advanced" },
"stats": { "uses": 12, "last_used_at": "2025-10-14T18:42:00Z" },
"resonance": { "value": 0.0, "decay": 0.98, "last_activated_at": null },
"meta": { "source": "seedpack:cpp-core", "version": "1.0.0" }
}
Presentation vs. storage: on the wire we keep nodes normalized (graph), but for agent prompts we can materialize a subtree as a JSON dictionary (exactly your intuition).
3. Core client components
3.1 Memory Orchestrator (library)
A small client library (TypeScript or Python) that owns:
-
Local Cache & Working Set
- in‑memory map of hot nodes (activated concepts, episodes, tasks)
- TTL + resonance decay keeps it naturally pruned
-
Activation Engine Computes a Resonance Score used for selection/exploration:
score(node | query, task) = α * cosine(embedding(node), embed(query)) + β * max_edge_weight_to(frontier) + γ * recency(node) + δ * usage(node) + ε * task_affinity(node, task.tags) + ζ * persona_weight(node, active_persona)Typical: α=0.45, β=0.20, γ=0.15, δ=0.10, ε=0.07, ζ=0.03
-
HDoD Composer Given a set of nodes, materialize a JSON dictionary tree (merge, order by score, trim to token budget).
-
Context Frames Structured blocks that the agent can consume:
- Identity Frame (who am I / tools)
- Problem Frame (task/spec)
- Knowledge Frame (HDoD subtree from semantic activation)
- Episodic Frame (recent steps/outcomes, e.g., compilation logs)
- Constraints Frame (APIs, signatures, tests)
- Scratchpad Frame (space for chain‑of‑thought outside model hidden state—LLM sees a compact, explicit scratch area)
-
Server Adapters (mapping to your MCP tools)
search_memory(query)→ seeds for semantic activationrecall_context(task|session)→ seeds for episodic activationsave_context(blocks)→ write back learned episodes/summariesupsert_memory(nodes)→ persist new/updated concepts/snippetsproject_snapshot()→ immutable snapshot of current mental statebackup.export_encrypted()/backup.import_encrypted()→ Knowledge Packs (see §5)
This is enough to behave like a cognitive system, while your server stays simple and fast.
4. Algorithms (client‑side, concise)
4.1 Probe → Bloom → Trim (context building)
build_context(query, task, budget):
seeds_sem = kom.memory.search_memory(query, k=32)
seeds_epi = kom.memory.recall_context(task_id=task.id, k=32)
frontier = normalize(seeds_sem ∪ seeds_epi)
for hop in 1..H (H=2 or 3):
neighbors = expand(frontier, max_per_node=6) // via node.links
frontier = frontier ∪ neighbors
update_resonance(frontier, query, task)
selected = topK_by_type(frontier, K_by_type) // diversity caps
frames = compose_frames(query, task, selected, budget) // HDoD for Knowledge Frame; episodes for Episodic Frame
kom.memory.save_context({ task_id: task.id, frames })
return frames
Natural pruning: each tick, node.resonance.value *= node.resonance.decay; nodes fall out of the Working Set unless re‑activated.
4.2 Upserting observations and skills
- After actions (file write, compile run, test pass/fail), emit Observation nodes (type
episode) with edges to involved concepts/snippets. - When the model discovers a pattern (“prefer RAII for resource ownership”), emit Skill nodes with
examplesandanti_patterns.
4.3 Materializing HDoD
Given selected nodes, build a JSON dictionary with paths as keys (e.g., skill.cpp.templates.sfinae) and nested maps for child grouping. Keep atomic fields compact (definitions, signatures) and push long text to a details field that can be compressed or summarized.
5. Knowledge Packs (prefilling intelligence)
Goal: give your coder agent real domain knowledge (C++ idioms, STL nuances, build systems, unit testing patterns) as compact, queryable, embedded chunks.
-
Format: Encrypted tar/zip with manifest
pack.json: id, name, version, domain_tags: ["cpp","cmake","catch2"], embedding_model, created_at, checksum nodes.jsonl: {"id":"skill.cpp.raii", "type":"skill", "payload":{...}, "embeddings":{...}, "links":[...]} ... -
Import/Export via existing tools:
kom.local.v1.backup.import_encrypted(pack)kom.local.v1.backup.export_encrypted(selection)
-
Curation approach: create micro‑chunks:
- Concept: “RAII”, “SFINAE”, “Rule of 5/0”, “ADL”, “Type erasure”
- Pattern: “pImpl”, “CRTP”, “Enable‑if idiom”
- Snippet: idiomatic examples (≤30 lines), compile‑checked
- Anti‑patterns: “raw new/delete in modern C++”, “overusing exceptions”
- Build/Tooling: CMake minimum skeletons,
add_library, interfaces,FetchContent - Test: Catch2/GoogleTest minimal cases; property‑based testing sketch
Once imported, the Pack is just memory. The Activation Engine will surface it the same way it surfaces your episodes.
6. Client ↔ Server mapping (today’s API)
6.1 Search (semantic seeds)
await kom.memory.v1.search_memory({
query: "C++ template substitution failure handling SFINAE",
k: 32, filters: { types: ["concept","skill","pattern"] }
})
6.2 Recall (episodic seeds)
await kom.memory.v1.recall_context({
scope: "task", id: task.id, k: 32
})
6.3 Save context (write-back frames)
await kom.memory.v1.save_context({
task_id: task.id,
frames: [
{ kind: "knowledge", format: "hdod.json", data: {/* nested dict */} },
{ kind: "episodic", format: "markdown", data: "# Steps\n- Compiled...\n- Tests..." }
]
})
6.4 Upsert memory (new skills/snippets)
await kom.memory.v1.upsert_memory({
nodes: [ /* normalized nodes like in §2 */ ],
merge: true
})
6.5 Snapshots & Packs
await kom.meta.v1.project_snapshot({ include_memory: true })
await kom.local.v1.backup.export_encrypted({ selection: "domain:cpp", out: "packs/cpp-core.kpack" })
Important: Client keeps resonance state locally. Server remains simple KV/search/recall/persist.
7. Prompt composition for a coding agent
Goal: Transform the Working Set into a stable prompt contract, so the agent operates above “first‑grade cobbling.”
Prompt Frames (ordered):
- Identity/Tools: who the agent is, what tools are available (ACF, tmux, build, test).
- Problem Frame: concise task, interfaces, constraints.
- Knowledge Frame (HDoD): the hierarchical dictionary of concepts/patterns/snippets selected by activation; max 40–60% of token budget.
- Episodic Frame: last N steps + outcomes; keep terse.
- Constraints Frame: language level (C++20), error policies, style (guidelines support library, ranges), testing expectation.
- Scratchpad Frame: allow the model to outline plan & invariants (explicit, not hidden).
Effect: The agent “feels” like it knows C++ idioms (because it sees compact, curated, embedded patterns every turn), and it keeps context from previous steps (episodic frame).
8. Data shape & invariants
- IDs are path‑like:
skill.cpp.templates.sfinae(hierarchy is explicit). - Graph canonical, Dicts for presentation: treat
childrenas references; avoid deep duplication. - Embeddings are per node; you may add type‑specific vectors later.
- Edges carry weights; they contribute to resonance.
- Resonance decays every tick; any node with
value < εleaves the Working Set. - Budgets: Top‑K per type (e.g., 6 skills, 10 snippets, 4 patterns) to avoid monoculture.
9. Minimal TypeScript client surface (sketch)
type NodeType = "concept"|"skill"|"fact"|"snippet"|"pattern"|"episode"|"tool"|"persona"|"task";
interface KomNode {
id: string;
type: NodeType;
label: string;
payload?: any;
embeddings?: { model: string; vector: number[] };
links?: { to: string; rel: string; w?: number }[];
children?: Record<string, string>;
stats?: { uses?: number; last_used_at?: string };
resonance?: { value: number; decay: number; last_activated_at?: string | null };
meta?: Record<string, any>;
}
interface Frames {
identity?: string;
problem: string;
knowledgeHDoD?: Record<string, any>;
episodic?: string;
constraints?: string;
scratchpad?: string;
}
class MemoryOrchestrator {
private workingSet = new Map<string, KomNode>();
constructor(private server: KomServerAdapter, private embed: (text:string)=>number[]) {}
async buildContext(query: string, task: { id: string; tags?: string[] }, budgetTokens: number): Promise<Frames> {
const seeds = await this.server.searchMemory(query, 32);
const epis = await this.server.recallContext(task.id, 32);
this.seed(seeds.concat(epis)); // normalize to nodes in workingSet
this.bloom(query, task, 2); // expand via links, update resonance
const selected = this.selectByTypeCaps(task); // diversity caps
const knowledgeHDoD = this.materializeHDoD(selected, budgetTokens);
const frames: Frames = { problem: this.renderProblem(task), knowledgeHDoD };
await this.server.saveContext(task.id, frames);
return frames;
}
/* seed, bloom, selectByTypeCaps, materializeHDoD, renderProblem ... */
}
This is intentionally thin; you can drop it into your existing client shell and wire the 7 server calls you already have.
10. How this reflects Elope’s spirit
Your earlier Elope work separated episodic from semantic memory and played with identity, observations, and “resonance/whales” motifs. This client keeps that spirit:
- Episodic = Observations/episodes (recent steps, logs).
- Semantic = Concepts/skills/patterns (stable knowledge packs + learned patterns).
- Resonance = activation value that guides expansion, selection, and natural pruning.
11. Observability (what to watch)
- Coverage: % of turns where Knowledge Frame includes ≥1 concept from the active domain.
- Drift: cosine distance between task query and top‑3 knowledge nodes (want stable closeness).
- Utility: model asks fewer irrelevant questions; compile/test pass rates increase.
- Memory hygiene: working set size stays under target (e.g., < 800 nodes), average resonance > threshold.
12. Failure modes & graceful degradation
- Server down → keep local Working Set; write a Pending Save episode; retry
save_contextlater. - Search sparse → fall back to Pack defaults (seed nodes by domain tag).
- Prompt over-budget → trim per type; compress long
payload.detailsinto bullet summaries. - Bad seeds → down‑weight sources with low subsequent utility.
13. What to change later (server‑side, optional)
Only once you want more power centrally:
- Add typed ANN queries (“top‑K per type”),
- Add resonance on server for multi‑agent sharing,
- Add link‑aware search (expand N hops server‑side),
- Add constraints retrieval (auto‑inject API signatures/tests).
Until then, the client gives you the behavior you want now.
14. TL;DR
- Treat memory as a graph rendered as HDoD,
- Activate by semantic+episodic seeds; bloom 1–2 hops by links; trim by type caps,
- Feed the agent frames (esp. Knowledge HDoD),
- Prefill with encrypted Knowledge Packs (C++ idioms, snippets),
- Use only your 7 existing endpoints — intelligence is client‑side.