Skip to main content

Overview

Memory gives agents the ability to remember. Without it, every agent.run() is a blank slate. With it, agents maintain conversation history, learn user preferences, track entities, remember decisions, and build knowledge graphs. All memory features share a single storage backend and are configured through the memory field on AgentConfig.

Quick Start

UnifiedMemoryConfig

The complete memory configuration. Only storage is required — everything else is opt-in.
Reading the boolean | XConfig types. Several features (like summaries, userFacts, entities) accept a value of type boolean | SomeConfig. That’s a union — it means you have three ways to set each one:
So when the table says boolean | EntityConfig, both entities: true and entities: { namespace: "acme" } are valid — true is simply a shorthand for “turn it on, use the defaults.” Pass the config object only when you want to change a default. This is why you’ll see both forms throughout the examples.(Features that always need a backend — learnings needs a vectorStore, graph needs a GraphStore — take only a config object, not a boolean, because there’s no sensible default without that backend.)

Memory Architecture

Memory is not a single store — it’s a layered subsystem with one orchestrator coordinating up to nine specialized stores over a shared storage backend.

The memory lifecycle

Every agent.run() wraps the LLM call with two memory phases:
  1. Before the run — Context Assembly. MemoryManager.buildContext() reads every enabled store, ranks the results, fits them within the token budget, and injects the result into the system prompt. Each block is wrapped in a <memory section="…" scope="current_user"> sentinel so the LLM never conflates one store’s data with another’s.
  2. After the run — Background Extraction. MemoryManager.afterRun() fires (non-blocking) and runs up to six parallel extractions against the last 6 turns of the conversation: user facts, profile, entities, learnings, graph, procedures. Failures emit memory.error events rather than failing silently.

The stores

The same memory config works identically across Agent, VoiceAgent, and BrowserAgent. The only difference: voice agents load context once at session start (not per turn), because the realtime session is persistent.

Sessions and History

Sessions are the foundation. Every agent.run() with a sessionId appends messages to a persistent history.
What happens when maxMessages is exceeded: The oldest messages are removed from the session. If summaries is enabled (the default), those removed messages are first summarized so the context isn’t lost entirely. Token-based trimming: Instead of counting messages, you can limit by tokens:

Summaries

Default: ON. When session history overflows maxMessages, the overflow messages are summarized by the LLM and stored. These summaries are injected into the system prompt on future runs so the agent remembers older context.

SummaryConfig

Understanding the options:
  • maxCount caps how many “recap notes” exist for a single long conversation. Each overflow event creates one summary. More summaries = longer memory of the conversation’s arc, but older ones eventually drop off. Lower it for short-lived chats; raise it for long-running threads (support cases that span weeks).
  • maxTokens is the budget for how much of those summaries gets injected into the prompt. This directly affects cost — every run pays for these tokens. Lower it (e.g. 1000) to save money; raise it (e.g. 4000) when the conversation history is rich and the agent needs more of it.

User Facts

Default: OFF. Automatically extracts persistent facts about the user from conversations: preferences, location, profession, interests, communication style. Facts are stored per userId and injected into the system prompt on every run for that user, across all sessions.

UserFactsConfig

Understanding maxFacts: This is the ceiling on how much the agent remembers about one person. When a user accumulates more facts than this, the framework keeps the most important and recent ones (not just the newest — a high-importance fact like “allergic to peanuts” outranks a trivial recent one).
  • Default 100 is plenty for most consumer apps.
  • Lower it (e.g. 30) to keep prompts lean and costs down when you only care about a handful of key preferences.
  • Raise it (e.g. 300) for power users or B2B accounts where the agent benefits from deep history. Higher values mean more storage and slightly larger prompts.
What gets extracted: The LLM analyzes each conversation and extracts concrete facts like:
  • “User prefers dark mode”
  • “User is a software engineer in Mumbai”
  • “User’s favorite language is TypeScript”
Contradiction handling: If a new fact contradicts an old one (e.g., user moved from Mumbai to Berlin), the old fact is soft-deleted (invalidatedAt is set) and the new one takes over. Each invalidation records a reason:
  • "superseded" — a newer fact about the same subject replaced it (e.g. “moved to Berlin” supersedes “lives in Mumbai”). Superseded facts disappear silently — they are never shown to the model.
  • "forgotten" — the user explicitly asked the agent to forget the fact (e.g. “forget my birthday”). Forgotten facts are surfaced in a dedicated “the user asked you to forget these” block so the model knows not to restate them, and recall_user_facts will not return them.
This distinction matters: an over-cautious model that sees both the new fact and an old one in the “forget” block can refuse to answer at all. Separating the two reasons fixes that. Re-stating a previously forgotten fact reactivates it rather than dropping it. Timezone-aware dates: facts like “my birthday is today” are resolved to an absolute date using the timezone config. Without it, a user near midnight gets the wrong date. Recurring events (birthdays, anniversaries) are stored without a year unless the user states one explicitly.

User Profile

Default: OFF. A structured profile object with built-in fields (name, role, timezone, language) plus custom fields. More structured than user facts.

UserProfileConfig

Understanding customFields: The profile always tracks four built-in fields automatically: name, role, timezone, language. customFields lets you add your own domain-specific fields the extractor will look for.
  • It’s a whitelist — the agent can only populate the fields you list. It can’t invent a salary field unless you ask for it. This keeps the profile clean and predictable.
  • Use it for structured attributes your product cares about: "company", "plan" (free/pro/enterprise), "department", "accountTier".
  • Don’t use it for free-form preferences (“likes dark mode”) — those belong in User Facts, which is unstructured by design.
Profile vs Facts: use Profile for a fixed set of structured attributes (one value each, like a form). Use Facts for open-ended things you can’t predict in advance.

Entity Memory

Default: OFF. Extracts companies, people, projects, and products mentioned in conversations. Each entity has facts, events, and relationships.

EntityConfig

Understanding namespace: Entities are always scoped to the user who created them (privacy). namespace adds a second, orthogonal partition on top of that — think of it as which “team workspace” the entities belong to.
  • Leave it "global" (default) if you have one product and don’t need to separate entity sets. Simplest, works for most apps.
  • Set a per-tenant namespace ("acme", "meridian") when one deployment serves multiple organizations and you want each org’s entity knowledge kept in its own bucket.
  • Use a hierarchical path ("acme/engineering", "acme/sales") when you want sub-divisions within a tenant — e.g. the engineering team’s “Stripe” entity shouldn’t mix with the finance team’s.
Rule of thumb: start with the default. Only set a namespace once you actually have separate teams or tenants whose entity knowledge should not mix.
What gets extracted: After each conversation, the LLM identifies entities and stores structured data:
  • Entity: “Stripe” (type: company) — facts: [“Payment processor”, “Used for billing”]
  • Entity: “Raj” (type: person) — facts: [“Frontend engineer”, “Works on checkout”]

Decision Log

Default: OFF. Records what the agent decided and why. Useful for auditing and learning from past decisions.

DecisionConfig

Understanding maxContextDecisions: Every decision the agent logs is stored permanently (you can always search the full history with the search_decisions tool). This setting only controls how many of the most recent decisions are automatically shown to the agent at the start of each run, so it stays consistent with what it decided before.
  • Lower it (e.g. 3) to save prompt tokens, or if recent decisions aren’t very relevant to the next one.
  • Raise it (e.g. 10) for agents that need strong continuity — e.g. a negotiation or approval agent that should remember its recent rulings to avoid contradicting itself.
  • It does not limit how many decisions are stored — only how many are surfaced in context. Storage is unlimited.
When enabled, the agent gets log_decision, record_outcome, and search_decisions tools automatically.

Learned Knowledge

Default: OFF. Vector-backed insights that the agent learns over time. Requires a vector store for semantic search.

LearningsConfig

Understanding the options:
  • vectorStore is required because learnings are recalled by meaning, not exact match — that needs a vector index (Qdrant, Pinecone, in-memory, etc.). This is why learnings takes a config object, not just true: there’s no default backend.
  • collection is the named bucket within that store. Change it only if you run multiple agents that should keep their learnings in separate collections within the same vector database.
  • topK controls how many learnings are pulled into the prompt per run. Higher = the agent considers more accumulated knowledge but spends more tokens and risks diluting focus. 3 is a good default; raise to 5–8 for knowledge-heavy agents (research, support playbooks), lower to 1–2 for tight token budgets.
See Scope Hierarchy for sharing learnings across a team or tenant.

Graph Memory

Default: OFF. A knowledge graph where entities are connected by typed, directed relationships. Enables the agent to answer questions that require traversing relationships (e.g., “Who works with Raj?”).

GraphMemoryConfig

Understanding the options:
  • store is required — the graph needs somewhere to live. Use InMemoryGraphStore for development and Neo4jGraphStore for production (persistent, queryable at scale).
  • autoExtract: true (default) means the agent automatically builds the graph as people talk (“Raj works at Acme” → creates Raj —works_at→ Acme). Set it to false if you want to build the graph manually via tools and avoid the extra extraction LLM call per turn.
  • maxContextNodes caps how much of the graph is summarized into the prompt. Higher = the agent sees more of the relationship web (better for “who connects to whom” questions) but uses more tokens. Raise it for richly-connected domains (org charts, supply chains); keep it low otherwise.
See Graph Memory for traversal, Neo4j setup, and temporal awareness.

Procedural Memory

Default: OFF. Records successful multi-step tool workflows and suggests them when similar tasks arise.

ProceduresConfig

Understanding maxProcedures: Each procedure is a learned, reusable playbook (“to do X, call these tools in this order”). This caps how many the agent keeps. When the limit is hit, the least-used / oldest procedures are dropped first, so frequently-successful workflows survive.
  • Default 50 suits most agents — they only repeat a handful of distinct workflows.
  • Raise it (e.g. 200) for agents that handle many distinct repeatable tasks (a broad ops agent).
  • Lower it if you want the agent to only retain its few most-proven workflows.
See Procedural Memory for details and the Scope Hierarchy for sharing procedures across a team.

Scope Hierarchy

Most memory is personal — “User prefers dark mode” belongs to one user. But some knowledge is genuinely shared: a workflow like “how to reconcile an invoice with a PO mismatch” belongs to a whole team, and a policy like “refunds over $500 need VP approval” belongs to the whole organization. Learnings and Procedures support an explicit four-level scope so shared knowledge isn’t trapped in one user’s silo:
Reads union all accessible scopes. When Alice (user) talks to the invoice-recon agent at tenant acme, a recall_procedure / search_learnings call returns her personal items plus the agent’s shared items plus the tenant’s items plus globals — but never another user’s personal scope. Writes pick one scope. The save_learning tool exposes a scope parameter so the model can promote an insight to the team or org level:
Saving directly through the store:
Auto-extracted learnings/procedures always save as scope: "user". The framework never auto-promotes an LLM-extracted insight to a shared scope — promotion requires an explicit caller decision. This prevents one user’s accidental statement from leaking to the whole team.
See Multi-User Isolation for the full scope contract and security guarantees.

Context Budget

Controls how the memory context string is distributed across sections when injected into the system prompt.

ContextBudgetConfig

Understanding the options: Once you enable several memory types, their combined context can grow large. contextBudget is the spending limit for that combined block and the rule for who gets priority when it overflows.
  • maxTokens — the ceiling for the entire memory section of the prompt. Without it, every enabled store is injected in full (fine for small setups, risky once you have many). Set it to keep prompts predictable, e.g. 4000.
  • priorities — when the stores together exceed maxTokens, higher-weighted sections are kept and lower ones are trimmed or dropped first. Weights are relative, not absolute — { summaries: 3, userFacts: 2, entities: 1 } means summaries get roughly half the budget, facts a third, entities a sixth. Valid keys: "summaries", "userProfile", "userFacts", "entities", "decisions", "graph", "procedures". Any section you don’t list uses its sensible default weight.
Tuning by use case: a support agent might favor summaries (conversation continuity); a research agent might favor learnings; a CRM agent might favor userFacts + entities. Bump the weight of whatever matters most for your job.

Using a Cheaper Model for Extraction

All background extraction (summaries, facts, entities, profiles) uses the agent’s primary model by default. This can be expensive. Set model to use a cheaper model:

Every option enabled:

Observability

Memory mutations and failures emit typed events on the agent’s EventBus. The most important is memory.error — without it, a broken extractor (malformed LLM JSON, an unreachable embedding service) fails silently and you only find out weeks later when a user complains.
These flow into @agentium/observability (OpenTelemetry, Prometheus, Langfuse) with no extra wiring.

Multi-Tenant Example

A single agent serving many organizations. Personal facts stay private; tenant policies are shared org-wide.

Debugging Memory Context

If the model seems to “forget” something, inspect exactly what buildContext produced:
The output shows each store wrapped in a scope sentinel:
If a section is missing, the store either isn’t enabled, has no data for that user, or wasn’t given the scope identifier it requires (e.g. an agent-scoped learning needs agentName).

When to Enable What


Storage Options

The storage field accepts any StorageDriver. Choose based on your needs:
See Storage Overview for setup details for each driver.