Under the Hood: What's Actually Inside SynapCores's Agentic Memory

Published on June 4, 2026

A recurring piece of feedback on my chatbot-memory posts goes something like this: "The two-table pattern is fine for a tutorial, but is that really all that's in your agentic runtime?"

Fair question. The answer is no — the rolling-summary recipe and the structured-facts variant are teaching shapes. They show the minimum data model a developer needs to build conversation memory in our engine without taking a framework dependency. They are deliberately the floor.

The actual agentic runtime that ships with SynapCores Community Edition has substantially more in it. I haven't written about it because for most readers the floor is the right starting point — and because once I name the components I'm on the hook to keep them shipping. But the gap between "what the blog says" and "what the engine does" is now wide enough that I should close it. This post is the survey.

Everything below is code in crates/aidb-chat-agent/ on CE. None of it is roadmap. The file paths are real.

The four memory subsystems

The engine doesn't have one memory store. It has four, integrated by a UnifiedMemorySystem coordinator:

pub struct UnifiedMemorySystem {
    pub memory_manager:    Arc<AsyncMemoryManager>,        // working / paging tier
    pub semantic_index:    Arc<RwLock<Vec<(Embedding, u64)>>>,  // semantic memory
    pub agent_memory:      Arc<RwLock<AgentMemory>>,       // episodic / experience
    pub behavioral_memory: Arc<BehavioralMemory>,          // behavioral / preferences
}

Semantic memory is the layer the blog posts cover — embeddings over text, cosine recall, the thing most people think of as "vector memory."

Episodic memory is AgentMemory.experiences — a typed record of tasks the agent attempted:

pub struct Experience {
    pub task_id:   String,
    pub goal:      String,
    pub actions:   Vec<String>,
    pub result:    String,
    pub success:   bool,
    pub timestamp: i64,
}

That's a CoALA-style episodic record. Goals, actions taken, result, whether it worked. The agent retrieves similar past experiences when planning a new task — find_similar_experiences(goal) for keyword-matched recall, get_successful_pattern(goal_pattern) for the most-recent successful precedent.

Working memory is the AsyncMemoryManager — explicit Page structures, a BufferPool, and a configurable EvictionPolicy. This is the per-session active context the runtime pages in and out as the conversation moves.

Behavioral memory is the layer that doesn't appear in any short tutorial — and that's a gap I'm fixing in this post. It's structured user-profile state that the agent updates after every interaction. It looks like this:

pub struct Preference {
    pub category:   PreferenceCategory,    // Communication, Topic, Tool, Workflow, ...
    pub value:      PreferenceValue,
    pub confidence: f32,                    // 0.0 – 1.0
    pub frequency:  u32,
    pub last_seen:  DateTime<Utc>,
}

pub struct BehavioralPattern {
    pub pattern_type: PatternType,          // ResponseLengthPreference, ToolUsagePattern, ...
    pub occurrences:  Vec<PatternOccurrence>,
}

pub struct PersonalityTrait {
    pub dimension: TraitDimension,
    pub score:     f32,
}

Confidence-weighted, frequency-tracked, recency-aware. After every recorded interaction, infer_behavioral_patterns(user_message, assistant_response) updates this state — preferred response length, tool-usage patterns, timing patterns, the works. None of it lives in a prose summary.

The thing the rolling-summary tutorial doesn't show: exponential decay

Buried in behavioral/behavioral.rs is the recency model the floor recipes leave out:

// Recency weight (exponential decay, half-life of 30 days)
let recency_weight = 0.5_f32.powf(days_since / 30.0);
...
let weight = pref.confidence * recency_weight * frequency_weight;

That's the Generative Agents weighting formula — confidence × recency × frequency — applied to user preferences. A preference the user expressed once six months ago weighs much less than one they expressed three times in the last week. The agent's "what does this user want" answer at any moment is a weighted view, not a raw lookup.

The two-table tutorial uses pure cosine because pure cosine is enough for the lesson. The runtime uses confidence-weighted recency-decayed retrieval because that's what actually works.

Tool-mediated self-editing memory

The runtime is not a passive database that the LLM reads at the start of every turn. Every memory tier has explicit write tools, and the DecisionEngine chooses when to invoke them:

pub enum Decision {
    Respond(String),
    ExecuteTool(ToolCall),
    AskForClarification(String),
    Delegate(String),
}

When the agent encounters a fact worth keeping, it calls a tool that writes to the right memory tier. behavioral_memory.record_pattern(...) for a behavioral fact, agent_memory.add_experience(...) for an episodic record, semantic-index push((embedding, page_id)) for retrieval anchors. This is the MemGPT/Letta self-editing pattern, implemented as a tool registry instead of a hard-coded paging UX.

The recipes don't show this because tool-mediated memory writes are an agent-loop concept, not a SQL concept. But they are in the engine, and they are the substrate on which any production agent built on SynapCores actually runs.

Pattern detection, not just recall

The semantic index is a recall mechanism. The behavioral layer is a pattern-detection mechanism. After every interaction, the runtime runs analyses like these:

// Infer response length preference from the assistant's own output history.
if response_len < 500 {
    record_pattern("response_length", ResponseLengthPreference, "short_response", 0.8);
} else if response_len > 2000 {
    record_pattern("response_length", ResponseLengthPreference, "long_response", 0.8);
}

// Infer interaction timing pattern from the hour-of-day.
let time_context = match hour {
    6..=11  => "morning",
    12..=17 => "afternoon",
    18..=23 => "evening",
    _       => "night",
};
record_pattern("interaction_time", ..., time_context, 0.7);

These updates are continuous, not batch. They are also recallable as structured rows, not buried inside a prose summary that gets regenerated every N turns.

The simple way to say what this gives you: the engine learns the user as the conversation runs, separately from what it remembers about specific turns.

Safety, eviction, and the things that aren't optional in production

The same crate ships:

  • SafetyEngine (agent/safety.rs) — RiskAssessment, RiskLevel, SafetyAction. The hooks for input sanitization and prompt-injection-poisoning mitigation are here. The tutorial recipes don't show them because they aren't part of the data-model story.
  • EvictionPolicy (memory/eviction.rs) — explicit policy configuration for the working memory tier. Pages don't grow unbounded.
  • BufferPool (memory/buffer_pool.rs) — bounded, pooled working memory. Same pattern a real database uses, applied to agent context.
  • PreferenceCategory / PreferenceValue / PatternType / TraitDimension — typed enums, not free-form prose. The behavioral layer is structured by construction.

If you're evaluating a memory architecture against the 2024–2026 canon — CoALA's semantic/episodic/procedural taxonomy, MemGPT's self-editing, the Generative Agents weighting formula, Mem0's structured extraction — this crate covers most of it. Not all (I'll be honest about what's missing in the next post), but most.

What's not in the engine yet — and what I'm shipping next

I want to be straight about gaps too. The runtime today does not ship:

  • An explicit conflict-resolution operator (Mem0's ADD / UPDATE / DELETE / NOOP discriminator). We approximate it through confidence-weighted averaging on the behavioral side; it's not surfaced as a SQL primitive yet. Coming as AGENT_MEMORY_UPSERT() in v1.8.
  • A cross-encoder reranker pass after cosine top-k retrieval. This is the single highest-leverage retrieval-quality lever. Coming as a chat-agent reranker tier in v1.8.
  • A temporal knowledge graph in the Zep/Graphiti sense — valid-time intervals on facts, fact-supersedes-fact edges, time-travel queries. Our graph backend supports the building blocks (the engine already runs Cypher on the same SQL surface), but the chat-agent doesn't wire memory into it. On the roadmap; not v1.8.
  • A public benchmark against LongMemEval / LoCoMo. I run internal numbers; I haven't published them. That's a "next quarter" commitment.

I'd rather underclaim what we ship and let the code speak than the inverse.

Why the blog posts start with two tables

A reasonable question: if all of the above is in the engine, why does the original post show two tables and the new one shows three?

Two reasons.

One: I want the floor pattern to be yours. A developer who reads the rolling-summary post and the fact-collection post can build chatbot memory without depending on our agentic runtime at all. They get the architectural lesson — store everything, retrieve selectively, structure what's durable — using nothing but CREATE TABLE, EMBED(), and GENERATE(). If they later decide they want self-editing tools, behavioral inference, pattern detection, and eviction, they're already on an engine that ships those. But they didn't have to take that dependency to learn the lesson.

Two: every component the engine adds is one more thing that has to keep working, one more API surface to support, one more name that has to mean what it says. I'd rather ship five tutorials about two tables that 100% of readers can run in 14 minutes than one tutorial about the UnifiedMemorySystem that takes an hour to understand and depends on six configuration values.

The two-table version is the floor. The fact-collection version is the next step. The runtime under both is what makes the engine worth running. The plan is to write each of those at the level of detail it deserves — and to stop letting reviewers conclude from the floor what the rest of the building looks like.

Where to start

Pick the lowest layer that maps to your problem:

  • You're building a chatbot today and want the simplest possible memory pattern → start with Conversation Memory + Rolling Summary.
  • Your chatbot's conversations have durable structure (preferences, constraints, goals) → add the Structured Facts variant on top.
  • You're building a multi-step agent that needs episodic, behavioral, and procedural memory → use the UnifiedMemorySystem and the AGENT_RUN() SQL function directly. The engine handles the layering.
  • You're evaluating whether the engine actually has what this post claims → read crates/aidb-chat-agent/ on the open-source repo. Every file referenced above is publicly accessible.

The database is still the brain. There's just more of it than the tutorials show.