Agentic Memory in TruvaG3: A Team With a Shared Notebook
A guided tour of TruvaG3's four-tier shared memory system — the academic research it came from, the five pipeline hooks that wire it into the orchestrator, how a fleet of agents stops repeating each other's work, and how a single chat agent finally remembers what a user said last week.
Contents
- The amnesia problem
- Where the design came from
- Four interfaces, swappable backends
- How memory plugs into the pipeline
- Activity compaction: surviving thousands of events
- Real-time coordination signals
- Knowledge extraction and the reflection job
- User memory: per-user personalization
- Domain scoping and physical isolation
- What the wiring looks like
- Architect's cheat sheet
- Where to go next
The amnesia problem
In TruvaG3, every ProcessRequest call starts with a blank slate. The orchestrator plans against the registry, executes the DAG, synthesizes a response, and forgets everything. The next call — even one made one second later, by the same agent, against the same entity — begins again from zero.
That is fine for a single tool. It is a problem the moment you have two agents that can act on the same entity. Without shared memory, agents fail in three concrete ways. Duplicated work — two agents independently investigate the same root cause, burning LLM tokens and tool invocations on the same five-step plan. Missing context — Agent B makes a decision without knowing Agent A's recent action on the same entity, leading to conflicting remediations or contradictory customer messages. No institutional learning — the same class of incident gets resolved fifty times, and the fifty-first still runs the full investigation, because the system has accumulated nothing.
The picture below is what this looks like in a typical TruvaG3 deployment. An event-driven agent reacts to an OOMKilled alert and creates a JIRA ticket. Five minutes later, a chat agent receives a related question about the same pod. Without shared memory, the second agent has no idea the first one ran.
Everything in the rest of this article is an answer to the same question asked at different timescales: how does Agent B see what Agent A just did? Five seconds ago. Five minutes ago. Five weeks ago. And how does that visibility survive a backend restart, scale to thousands of events, and not block the request path when it fails?
Where the design came from
A survey, not a vector DB bolt-on
The temptation when adding "memory" to an agent framework is to attach a vector database and call it done. Every paper that took that route — store everything, retrieve by similarity — found the same failure mode: as memory grows, signal-to-noise collapses and decision quality degrades. TruvaG3's memory system was designed against a literature survey first, then implemented. The relevant lineage:
| Source | What it gave us |
|---|---|
| Stanford Generative Agents (2023) Park et al., arXiv 2304.03442 |
The three-tier model — memory stream, reflection, planning — and the now-standard recency · importance · relevance retrieval triad. Their hard finding: without a periodic reflection pass, agents became incoherent within a few hundred turns. |
| MemGPT (2023) Packer et al., arXiv 2310.08560 |
The LLM as its own memory manager — paging information in and out of the context window via explicit function calls. TruvaG3's enrichment hook is the "page in" mechanism. |
| A-MEM (2025) Xu et al., arXiv 2502.12110 |
Agentic memory — the store actively organizes, connects, and evolves its contents instead of passively accumulating them. Source of the ADD / UPDATE / CONTRADICT / DUPLICATE reconciliation pattern. |
| DAMCS (2025) arXiv 2502.05453 |
The cognitive-science three-tier model — sensory (raw tool output), episodic (events), procedural (learned how-to). Maps cleanly onto TruvaG3's ExecutionStore → AgentEvent → KnowledgeFragment progression. |
| G-Memory (2025) arXiv 2506.07398 |
Provenance — every fragment tracks who created it, what evidence it was based on, and the derivation chain. Critical for enterprise audit trails. Lives in KnowledgeFragment.SourceEvents (which events produced the fragment) and AgentEvent.ParentEvent (which request derived from which). |
| Multi-Agent Memory from CompArch (2026) arXiv 2603.10062 |
The L1/L2/RAM/Disk analogy and the consistency-model benchmark. Strong consistency cost 3-5× latency; eventual consistency caused decision conflicts in ~12% of scenarios. Causal consistency — what Redis Streams give you for free — was the sweet spot. |
| AgentRM (2026) arXiv 2603.13110 |
The number that motivated the entire compaction layer: with no memory management, effective context degrades to noise within ~50 turns. With simple compression, agents retain ~37% of entity information across sessions. |
Three findings that shaped everything
Three results recurred across the literature, and shaped the design directly.
Raw event logging is necessary but insufficient. Every "store everything" approach degraded under load; a reflection or distillation layer is required. Retrieval must be multi-signal. Pure embedding similarity misses temporally relevant information, and the recency-importance-relevance triad has held up across every follow-on paper. Consistency model matters. Stale reads cause measurable decision quality loss, but strong consistency is too expensive — causal consistency is the right floor.
Four interfaces, swappable backends
The memory system decomposes into four independent interfaces defined in core — each solves a distinct problem, each can be implemented and scaled separately, each degrades gracefully when missing. The interfaces are the framework's contract; the backends that implement them are the framework's defaults — opinionated but replaceable. A separate fifth interface, user memory, handles per-user private facts and is covered in §7.
| Memory type | Interface (in core) | What it stores | Default backend (in memory) |
|---|---|---|---|
| Episodic | EpisodicMemory |
Structured events: "Agent X did Y to entity Z at time T with outcome W" | StreamEpisodicMemory · Redis Streams (or in-memory for tests) |
| Coordinator | InvestigationCoordinator |
Who currently holds the claim on which entity (with TTL) | AtomicLockCoordinator · Redis SETNX |
| Knowledge | SharedKnowledge |
Embedded knowledge fragments — learned patterns, resolution strategies | VectorSharedKnowledge · Qdrant (vector DB, gRPC) |
| Reflector | MemoryReflector |
Higher-level synthesized insights from many episodic events | LLMMemoryReflector · uses the configured AIClient + episodic backend |
Each interface lives in the core kernel; concrete backends live in the separate memory module, which absorbs the heavy client dependencies (Redis client, Qdrant gRPC + protobuf) so core stays lightweight. A Phase-1 deployment that needs only episodic memory and coordination compiles with none of the vector-DB dependencies. A Phase-2 deployment that wants knowledge search opts in by passing an embedding client.
core interfaces, swappable backends. Every tier lives behind a Go interface defined in core. The boxes name the interface first and the default backend second — Redis Streams and Qdrant ship as the production defaults because most platforms already operate Redis and Qdrant is purpose-built for vector search, but they are replaceable. Any backend that satisfies the same core interface is a drop-in alternative; the rest of the framework never knows the difference. The application's main.go is the only place that wires backends to hooks; the orchestrator itself never imports memory, only the core interfaces.One detail in Figure 2 deserves its own paragraph, because it is the design choice that makes the rest of the system safe to deploy. Every backend is fail-open, regardless of which implementation is wired in. Runtime errors return (nil, nil), not (nil, error). If the knowledge backend is unreachable, search returns empty; the LLM plans without enrichment. If the episodic backend is unreachable, the activity coordinator returns empty signals; the planner proceeds without coordination. If the LLM-driven compaction times out, the cached digest is used; if there is no cached digest, the raw last-N events are sent through. Memory is an enhancement, never a prerequisite — the orchestration pipeline cannot fail because of a memory backend, whichever one you have plugged in.
How memory plugs into the pipeline
Five hooks, three stages
Memory is not invoked imperatively — agent code does not call memory.Recall() or memory.Record(). Everything happens through TruvaG3's existing pipeline-hook mechanism: middleware that runs at well-defined stages of ProcessRequest, registered once at startup. There are five hooks; they run in a fixed order on every request; they are fail-open individually so a failure in one does not break the others.
What the LLM actually sees
The hooks contribute two new XML sections to the planning prompt. The first is Coordination — real-time signals from agents currently active in the same domain:
<agent_coordination>
Other agents currently active in domain 'infrastructure':
- event-driven-agent: investigating "TruvaG3HighLatency on product-catalog-api"
(status: executing, started 3s ago)
</agent_coordination>
The second is Episodic + Knowledge — a compacted digest of recent domain activity plus the most recent events at full detail, plus any vector-retrieved knowledge fragments:
<agent_memory>
Domain activity summary (last 2 hours, 106 events):
- Deployment restart: agent-with-human-approval restarted via rollout (200ms)
- JIRA: ticket DEVOPS-49 created for the restart, comment added with details
- Slack: notification sent to #notifications with JIRA reference
- Monitoring: 4 Prometheus queries run by event-driven-agent
Most recent events (detail):
- [2026-05-26 23:31] devops-chat-agent: Queried logs for orch-1774... (success)
- [2026-05-26 23:30] devops-chat-agent: Created JIRA ticket DEVOPS-52 (success)
Prior knowledge relevant to this request:
- OOMKilled on pods with <256Mi memory is typically resolved by doubling
the memory limit. Check resource quotas first. (importance: 0.82)
</agent_memory>
This is what the LLM sees when it generates its plan. It is not asked to "consider memory" — the memory is already in the prompt, and the LLM treats it like any other context. The behaviour change is emergent: the planner naturally writes shorter plans when the digest tells it the work was already done, and it naturally references existing tickets when the digest names them.
Activity compaction: surviving thousands of events
The naive version of the enrichment hook sends every recent event through to the LLM on every request. At 200 events per planning prompt, that is 5,000-10,000 tokens of context overhead — every request, every agent. It will work, and it will be expensive.
Episodic compaction solves this with an LLM-summarized digest that lives in the digest cache (core.DigestCache — default backend is Redis, in-memory ships for tests, anything else can be plugged in) and is shared across every agent in the domain. The hook's behaviour depends on what is in the cache and how many new events have arrived since the last compaction:
TRUVAG3_SHARED_MEMORY_DIGEST_INCREMENTAL_THRESHOLD events have arrived (default 20). The digest cache is shared across every agent in the same domain — when one agent pays the cost of recompaction, all peers benefit on their next request.The cache key is the domain, not the agent. When event-driven-agent compacts the digest, devops-chat-agent reads it back as a cache hit on the next request. This is what makes the compaction pattern affordable in a fleet — the LLM cost is amortized across every agent in the domain, not paid per agent.
The span event activity.compaction.cache_decision labels the path taken on each request — cached, incremental, full, or full_recompact. If a production deployment shows path=full on every request, the cache is broken — the configured digest-cache backend is unreachable, the TTL is too short, or the digest cache wasn't wired into the enrichment hook. The condition is visible without reading a single application log.
Real-time coordination signals
Episodic memory has a built-in latency: events are visible after execution completes, which is typically 2-4 seconds. For most workloads that is fine. For concurrent requests — an alert that fires while a chat session is already mid-investigation against the same pod — 2-4 seconds is too slow. The two agents can both start a five-step plan before either of them sees the other's event.
This is what the Coordinator solves. It is a separate, lightweight store implementing core.ActivityCoordinator: transient keys with TTL, no JSON payloads, no LLM calls. The default backend writes through Redis (with an in-memory fallback for tests); any KV store with atomic SETNX + TTL semantics can be substituted. An agent writes a signal the moment it starts processing a request and removes it the moment the response is synthesized:
<agent_coordination> prompt block — giving it the option to defer, coordinate, or adapt rather than blindly starting a parallel investigation. The signal is removed when the originating agent finishes; if it crashes, the TTL expires automatically. No cleanup process, no leaked locks.Two design choices keep this affordable. The signal is a KV entry with a TTL — on the default Redis backend that is one SET … EX 300, but the interface only assumes "put, get, expire," so the same hook works against any KV-with-TTL backend. No JSON, no large payloads, no LLM call to write or read. The TTL is longer than the longest expected request (default 5 minutes), so a crash never leaves a permanent claim. The signal is plain text written in natural language — "investigating TruvaG3HighLatency on product-catalog-api" — and the LLM reads it the same way it reads the rest of the prompt. The decision of whether to defer is made by the planner, not by a hardcoded rule, so agents can coordinate even when their work overlaps in ways the framework cannot anticipate.
Knowledge extraction and the reflection job
Episodic events answer "what did Agent X just do?" Knowledge fragments answer the harder question: "what have we, collectively, learned about this class of problem?" These two questions need different storage. Episodic events expire after 60 days (the default EpisodicMemory backend honours TRUVAG3_SHARED_MEMORY_STREAM_MAXLEN and TTL); knowledge fragments live in the SharedKnowledge backend with no TTL, surfaced through semantic search. The default SharedKnowledge implementation is vector-DB-backed (Qdrant out of the box), but any backend that satisfies the interface works — pgvector for teams already running Postgres, valkey-search once it matures, or a custom implementation for an internal system.
TruvaG3 produces knowledge fragments through two complementary paths, running on different timescales. The first is the KnowledgeExtractionHook — it runs after every synthesis, distills the current request's findings into a 1-2 sentence fragment, embeds it, and stores it. Fast, immediate, high-volume. The second is the Reflector — a background job that runs every 24 hours, scans episodic events older than seven days, and asks the LLM to identify patterns across many past events for the same entity. Slower, distilled, pattern-aware.
SharedKnowledge fragments. A new incident months later, semantically related to those patterns, gets a 2-step plan instead of a 5-step investigation — using knowledge derived from events that no longer exist in the episodic store.The reflection job is the part of the system that most directly maps onto the Stanford Generative Agents finding — agents without a reflection layer became incoherent within a few hundred turns. TruvaG3's version (memory/reflection_job.go) runs as a framework Runnable with one line of wiring:
// Reflection job: long-term knowledge retention via background distillation.
// Returns nil silently if Phase 2 backends aren't available.
if reflectionJob, _ := memory.BuildReflectionJob(memBackends.ToDeps(), agent.AI, agent.Logger); reflectionJob != nil {
framework.RegisterRunnable(reflectionJob)
}
Two operational properties matter. It is multi-replica safe. The job acquires a distributed lock keyed by domain before each pass (default backend: Redis; the lock is a core.DistributedLock abstraction so any KV with atomic check-and-set will do). Other replicas log "lock held by another replica, skipping" and return. Model choice matters more than for any other LLM call in the system. Reflection writes durable knowledge that influences every future request — a bad fragment lingers in the knowledge store and surfaces as confident "prior knowledge" for as long as it remains. The default model is the agent's main AI client; TRUVAG3_REFLECTION_MODEL=fast trades fragment quality for cost, which is acceptable for high-volume cost-sensitive deployments but not the right default.
User memory: per-user personalization
Everything covered so far is shared agent memory — cross-agent visibility within a domain. TruvaG3 also ships a separate, complementary system for per-user private facts: user memory. Different store, different hooks, different privacy boundary, different lifecycle. The mental model is a personal assistant's notebook versus a team's shared notebook.
| Shared memory | User memory | |
|---|---|---|
| Scope | All agents in a domain | One user only |
| Content | "Agent X restarted pod Y" | "User prefers DFW airport" |
| Visibility | Domain-wide | Private to user |
| Lifetime | 24h lookback + knowledge persists | Months / years; evolves with user |
| GDPR delete | Not per-user | Forget("user-42") → one call |
| Enrichment tag | <agent_memory> | <user_profile> |
| Interface | core.EpisodicMemory + core.SharedKnowledge | core.UserMemory |
| Default backend | Redis Streams + Qdrant (swappable) | VectorUserMemory (Qdrant) or InMemoryUserMemory (swappable) |
Fact reconciliation in four operations
The interesting part of user memory is not the storage — it is what happens when a new fact contradicts an existing one. A user who says "I am vegetarian" on Monday and "I'm not vegetarian anymore" on Friday should not have both facts in their profile. The system needs to recognize the relationship and update accordingly.
Five parallel recall paths
Recall is structured around five parallel paths, each contributing different facts to the planning prompt: identity (who the user is — always recalled), stable_namespace (facts tagged with stable identifiers), query (semantic match against the current request), summary (rolling session summaries from previous conversations), and universal (facts that cross every agent — name, language, timezone). Each path emits its own span in Jaeger, so a developer can see which paths contributed to a given response.
The result, in the planning prompt, is a <user_profile> tag with confidence-banded facts:
<user_profile>
Identity:
- User lives in Coppell, TX (explicit, high confidence)
Constraint:
- User and family are vegetarian, no eggs or gelatin (explicit, high confidence)
Preference:
- User prefers DFW airport for flights (explicit, high confidence)
Relationship:
- Family of three: user, wife, and 12-year-old son (explicit, high confidence)
Summary:
- Planned a week-long family itinerary in Zurich with hotel picks and vegetarian restaurant recommendations near each one (derived, 3 days ago)
- Asked about Switzerland weather, currency, and travel tips (derived, 3 days ago)
</user_profile>
The travel agent now plans around the user's dietary constraints, family size, and airport preference — without the user repeating any of it. Extraction runs after the response is streamed, so the user sees the answer immediately and the new facts are stored asynchronously. GDPR erasure is a single API call: userMemory.Forget(ctx, userID) deletes every vector and payload for that user. The interface is the boundary; swapping Qdrant for PostgreSQL with pgvector is a backend change, not an application change.
Domain scoping and physical isolation
An enterprise deployment has multiple agent types, each with multiple replicas, serving different business domains. A platform team needs to know exactly what is shared, what is isolated, and how compliance boundaries are enforced. TruvaG3 operates at two levels: logical isolation by default, physical isolation when regulated.
SearchKnowledge call includes a callerDomain filter that runs inside the backend's index traversal, not as a post-filter. Regulated domains (HIPAA, PCI-DSS) get separate backend instances in a different K8s namespace with a NetworkPolicy that denies all ingress. Same code, same interfaces; only the connection address (and, optionally, the backend implementation itself) differs. The framework module does not enforce compliance — infrastructure does, and that boundary is the right place for it.Every AgentEvent and KnowledgeFragment carries three fields that determine visibility: AgentDomain, AgentName, and Scope. Scope is one of three values:
| Scope | Visible to | Example |
|---|---|---|
ScopePrivate | Owning agent only | Internal reasoning trace, intermediate failure state. Never stored in shared knowledge. |
ScopeSharedDomain | Agents in the same domain | DevOps incident details. Visible across the infrastructure fleet, invisible to commerce agents. |
ScopeGlobal | All agents, any domain | "service-X is down" — entity state changes that cross domain boundaries. |
The investigation-coordinator claim has one known limitation: the lock key is the entity ID only, not type:id. Two entities of different types that happen to share an ID — pod/foo and service/foo — will collide on the lock. The framework documents this honestly because it has low practical impact in most domains and a fix would require breaking the lock-key format. It is the kind of trade-off worth surfacing rather than hiding.
What the wiring looks like
The wiring is shorter than the architecture might suggest. Two function calls — one in the memory module to create backends, one in the orchestration module to create hooks from those backends — and the orchestrator gets memory on every request. The example below shows the default wiring (Redis + Qdrant) from the bundled devops-chat-agent; replacing either backend is covered after the snippets.
// 1. Memory module creates all backends. The default factory takes a Redis
// client because Redis is the default backend; to swap, skip this factory
// and construct your own backends against core.EpisodicMemory etc. directly.
// Phase 1 (episodic + coordination + activity + digest cache) always created.
// Phase 2 (knowledge store) auto-detected from TRUVAG3_VECTOR_DB_URL.
backends, _ := memory.NewSharedBackends(redisClient, agent.Logger,
memory.WithAgentName("my-agent"),
memory.WithDomain("infrastructure"),
memory.WithEmbeddingClient(embedder), // enables Phase 2
)
defer backends.Close()
// 2. Orchestration module creates the 5 hooks from the backends.
hooks, activityCoord := orchestration.BuildMemoryHooks(
backends.ToDeps(), agent.AI, agent.Logger,
)
// 3. Pass into the orchestrator.
deps := orchestration.OrchestratorDependencies{
Discovery: discovery,
AIClient: agent.AI,
PipelineHooks: hooks,
ActivityCoordinator: activityCoord,
}
// 4. Long-term retention: reflection job as a framework Runnable.
if reflectionJob, _ := memory.BuildReflectionJob(
backends.ToDeps(), agent.AI, agent.Logger,
); reflectionJob != nil {
framework.RegisterRunnable(reflectionJob)
}
For a user-memory-enabled chat agent (the travel-chat-agent pattern), one more block:
// 5. User memory: separate backend, separate hooks.
userMemBackend, _ := memory.NewUserMemoryBackend(agent.Logger,
memory.WithUserMemoryNamespace("travel"),
memory.WithUserMemoryEmbeddingClient(embedder),
)
defer userMemBackend.Close()
userHooks, userHooksCloser := orchestration.BuildUserMemoryHooks(
userMemBackend.ToDeps(), agent.AI, agent.Logger,
)
defer userHooksCloser.Close()
// Shared + user hooks compose into a single pipeline.
deps.PipelineHooks = append(hooks, userHooks...)
Both wirings ship as working production code. Behavioural customization — domain-specific entity extraction, importance scoring, custom retrieval weights, longer lookback windows — is opt-in via additional options to BuildMemoryHooks. Numeric tuning (token budgets, TTLs, summarizer model alias) goes through TRUVAG3_* environment variables.
Swapping a backend
Replacing the default Redis or Qdrant backend is a constructor-line change, not a framework change. The application skips NewSharedBackends (the convenience factory that assumes Redis), constructs whichever backend it wants, and assembles its own core.SharedMemoryDeps:
// Example: a deployment that prefers pgvector for SharedKnowledge
// (Postgres is already on the platform's bill of materials).
import (
"github.com/truvaagents/truva-g3/core"
"github.com/truvaagents/truva-g3/memory"
"acme/pgvectormem" // user-owned package implementing core.SharedKnowledge
)
episodic, _ := memory.NewStreamEpisodicMemory( // keep Redis here
memory.WithEpisodicRedisClient(redisClient),
memory.WithEpisodicDomain("infrastructure"),
)
coordinator, _ := memory.NewAtomicLockCoordinator(
memory.WithCoordinatorRedisClient(redisClient),
memory.WithCoordinatorDomain("infrastructure"),
)
knowledge := pgvectormem.New(pgPool, ...) // ← custom backend
deps := &core.SharedMemoryDeps{
AgentName: "my-agent",
AgentDomain: "infrastructure",
Episodic: episodic,
Coordinator: coordinator,
Knowledge: knowledge, // any core.SharedKnowledge
Embedder: embedder,
// ActivityCoordinator and DigestCache are optional; leave nil
// to disable signals/cache, or wire memory.NewRedisActivityCoordinator
// and memory.NewRedisDigestCache against the same client.
}
hooks, activityCoord := orchestration.BuildMemoryHooks(deps, agent.AI, agent.Logger)
The rest of the framework — the five hooks, the activity compactor, the reflection job, the LLM prompt builders — never knows the backend changed. The same applies to every core memory interface: EpisodicMemory can be swapped for a NATS JetStream implementation, DigestCache for any KV cache, UserMemory for a Postgres-backed implementation. What does not ship yet is one shared conformance suite for memory backends. A custom implementation should carry its own contract tests for the guarantees its interface needs — round trips and retention, cross-instance visibility, atomic ownership where coordination is involved, and the documented failure behavior. The framework's existing reusable suites cover task delivery and orchestration state, not these memory interfaces.
One subtle wiring property is worth pulling out. The orchestration module does not import the memory module. The dependency graph is orchestration → core (interfaces only), memory → core + telemetry (default implementations), and main.go → orchestration + memory + ai (wiring). This means a Phase-1 deployment that does not import the memory module's vector backend never pulls in gRPC or protobuf, and a deployment that wires only custom backends does not need to import the bundled memory module at all.
Architect's cheat sheet
Six things to remember when you walk into a design review with this system on the whiteboard.
1 · The interfaces are the framework; the backends are defaults
Four core interfaces — EpisodicMemory, InvestigationCoordinator, SharedKnowledge, MemoryReflector — are the framework's contract. Redis and Qdrant are the production defaults; pgvector, NATS, Weaviate, or a custom backend each work by satisfying the same interface.
2 · Memory is an enhancement, not a prerequisite
Every backend is fail-open, whichever one is plugged in. If the episodic backend is unreachable, the planner runs without enrichment. If the knowledge backend is unreachable, search returns empty. The pipeline cannot fail because of a memory backend.
3 · Recency · Importance · Relevance — all three
Pure vector similarity misses temporally relevant information. The Stanford triad held up across every follow-on paper. Weights are tunable per agent type; defaults are balanced.
4 · The digest cache is what makes this affordable
The naive version sends 200 events through the LLM per request. The cached digest is shared across every agent in the domain — first agent pays the cost, all peers read the cache. Watch activity.compaction.cache_decision.
5 · Reflection is what makes memory durable
Episodic events age out on the configured TTL; the activity compactor only looks back 24 hours. Without reflection, every event older than that window is invisible and eventually gone. The background job converts ephemeral activity into permanent SharedKnowledge fragments.
6 · Shared and user memory are separate concerns
Different interfaces, different hooks, different lifecycles. Shared memory is domain-wide; user memory is private and per-user. Forget(userID) is a single call that deletes everything for one user — GDPR-shaped from day one.
Where to go next
Recommended reading order, by goal:
- Operate it. The Agent Memory User Guide is the user-facing tour — every environment variable, every observability signal, every troubleshooting path.
- Understand the architecture. memory/ARCHITECTURE.md covers the storage backend contract, the sharing topology, and why the four interfaces are the way they are.
- Read the research. The references throughout this article link directly to the cited papers and provider documentation.
- Read working code. The devops-chat-agent wires shared memory plus reflection. The travel-chat-agent wires shared plus user memory. Both are deployed in the bundled Kind setup.
© 2026 Neelabh Tripathi. Licensed under CC BY 4.0.