Sessions, Memory & Skills
LLMs are stateless: every API call is born remembering nothing. For an agent to remember, learn and personalize, someone must assemble — turn by turn — the right information inside the context window. That craft is Context Engineering. This guide summarizes the paper "Context Engineering: Sessions, Memory" (Kimberly Milam & Antonio Gulli, Google) in interactive form: clickable diagrams, simulators, a quiz and cheatsheets.
It assumes familiarity with LLMs and software development. It does not assume prior knowledge of agent frameworks — ADK and LangGraph are presented as examples, not prerequisites.
What is Context Engineering
The craft of assembling, every turn, the right information inside the context window — no more, no less.
LLMs are inherently stateless: all their reasoning happens inside the context window of a single API call. Nothing that came before exists for the model — unless someone puts it there. Context Engineering is the process of dynamically assembling and managing the information inside the context window to enable stateful, intelligent agents.
It is the natural evolution of Prompt Engineering: instead of polishing a static instruction, the context engineer orchestrates the entire payload — selecting, summarizing and strategically injecting different types of information on every turn, with maximum relevance and minimum noise. External systems (RAG, session stores, memory managers) manage the context; the framework orchestrates it all.
The chef doesn't cook holding the recipe — the chef cooks with a ready station
A chef who only has the recipe uses whatever random ingredients happen to be around. The dish turns out… okay. That's the static prompt: a good instruction, with no prepared context.
The chef gathers and prepares all the ingredients, arranges the tools and defines the plating style before cooking. That's the full context: history, memories, facts, tools — everything in place, at the right time.
Prompt = the recipe
- says what to do
- doesn't know what's in the fridge
- unpredictable result
Context = prepared ingredients
- recipe + right ingredients + tools + plating
- assembled per dish (per turn)
- no more, no less than what's needed
The goal is not to fill the context window — it is to deliver the most relevant information for this turn. No more, no less.
The context payload
Three layers of components — click each one to see what it contains.
Everything the model "sees" in a turn fits into three categories. Assembly is dynamic: memories aren't static, few-shot examples must be relevant to the task (not hardcoded), and RAG responds to the immediate query.
Context that guides reasoning
behaviorFacts & evidence
what to reason aboutImmediate conversation
the current task🧭 Guia o raciocínio
…
Context rot & the continuous cycle
Why the history must be mutated on every turn — and the four stages that make it happen.
The conversation history grows without stopping. Bigger windows survive long transcripts, but: cost and latency rise with every token, and context rot appears — the model's attention to critical information decreases as the context grows. The solution is to mutate the history dynamically: summarization, selective pruning, compaction.
1 · Fetch Context
…
The Session is the workbench; Memory is the organized filing cabinet
Tools, notes and drafts scattered around: everything accessible, but temporary. At the end of the day the bench is cleared — and the next conversation starts clean.
You review the materials from the bench, discard the drafts and file only the essentials. Nobody shoves the whole messy bench into the cabinet — that's why upload is consolidation, not copying.
Sessions: fundamentals
The chronological container of ONE conversation — events + state, bound to a single user.
A session encapsulates the dialogue history and working memory of one continuous conversation: a self-contained record bound to a specific user. A user can have multiple sessions — separate, disconnected logs.
The agent appends events and mutates the state according to business logic. The structure echoes the list of Content objects in the Gemini API — each Content has a role (user/model) and parts (text, images, tool calls): one turn = one Event.
# o histórico é uma lista de Content: role + partsresponse = client.models.generate_content( model="gemini-2.5-flash", contents=[{"role": "user", "parts": [{"text": "Quero ir a Paris em novembro"}]},{"role": "model", "parts": [{"text": "Direto ou com escala?"}]},{"role": "user", "parts": [{"text": "Direto, por favor"}]}, ], )
Production runtimes are stateless — the history must be persisted. In-memory storage is fine for development; production demands robust databases (e.g. Agent Engine Sessions).
Frameworks: the universal translator
ADK and LangGraph implement sessions differently — but the core ideas are the same.
The framework is a universal translator between your code and the LLM: it keeps the history and state, builds the requests, parses and stores the responses. For Gemini, the request is List[Content] — each Content with a role and parts. The framework maps your internal object (e.g. an ADK Event) to role/parts before the call. This abstraction decouples the agent's logic from the specific LLM — and prevents vendor lock-in.
Your logic
Agent's internal events and state
Framework
builds the request · parses the response
Gemini API
List[Content] · role + parts
Session store
history + state persisted
ADK — explicit Session object
A Session with a list of Events + a separate state object. Think of a folder with one file for the history and another for the working memory — everything in its place, clearly delimited.
session = Session( id="s_771", events=[event_1, event_2, event_3], # histórico cronológicostate={"cart_items": [...]}, # working memory separada)
LangGraph — the state IS the session
There is no formal "session" object: the comprehensive, mutable state (history as a list of Messages + working data) is the session. And it can be transformed — for example with history compaction — which is valuable for long conversations and token limits.
class AgentState(TypedDict): messages: Annotated[list, add_messages] # históricocart: dict # dados de trabalho# o grafo pode reescrever o state a cada passo (ex.: compactar)
Multi-agent sessions
When several agents collaborate, the architecture defines who sees whose history.
Session history is the permanent, complete transcript. Context is the carefully built payload for ONE turn — it may be a relevant excerpt with special formatting. This section is about what passes between agents, not necessarily what goes to the LLM.
Shared history
- all agents read and write the same chronological log
- ideal for coupled tasks: one's output is the next one's input
- even so, each agent can filter/label events before passing them to the LLM
- e.g. LLM-driven delegation in ADK — sub-agent events land in the root agent's session (with
output_key)
Separate histories
- each agent keeps a private history: reasoning, tool use and intermediate steps stay hidden
- communication happens only through explicit messages — the final output, not the process
- via Agent-as-a-Tool (invoke another agent as a tool and receive a self-contained output)
- or via the A2A Protocol (direct, structured messages)
Interoperability & the A2A protocol
The abstraction that frees the agent from the LLM also isolates it from other frameworks — the memory layer is the bridge.
There is a critical trade-off: the same abstraction that decouples the agent from the LLM isolates it from agents of other frameworks. The isolation hardens at the persistence layer — the database schema gets coupled to the framework's internal objects, and the record becomes non-portable. A LangGraph agent cannot natively interpret the Session/Event objects of an ADK agent: seamless handoff, impossible.
Isolated session stores
- A2A exchanges messages, but doesn't share rich contextual state
- the history lives in each framework's internal schema
- sending session events via A2A requires a custom translation layer
Shared memory layer
- knowledge abstracted into a framework-agnostic data layer
- stores processed, canonical information: summaries, entities, facts as strings/dicts
- heterogeneous agents achieve collaborative intelligence by sharing a common cognitive resource — no translators needed
Session stores keep raw, framework-specific objects; the memory layer keeps processed, canonical information. It is the universal data layer.
Sessions in production
Three critical areas a managed session store (e.g. Agent Engine Sessions) addresses.
🔐 Security & privacy
- Strict isolation is the most critical principle: a session belongs to one user — no one else can access it (ACLs; every request authenticated against the owner)
- PII: redact before writing to storage — shrinks the "blast radius" of a breach (tools like Model Armor)
- simplifies GDPR/CCPA compliance and builds trust
🗂️ Integrity & lifecycle
- sessions shouldn't live forever: TTL policies delete inactive sessions (storage cost + overhead)
- clear retention policy: how long before archiving or deleting
- events appended in deterministic order — correct chronological sequence = log integrity
⚡ Performance & scale
- session data sits on the hot path of every interaction — reads/writes must be very fast
- stateless runtimes fetch the entire history from the central database on every turn (network latency)
- mitigation: shrink what's transferred — filter/compact the history before sending (e.g. drop old, irrelevant function call outputs)
Compacting long conversations
Four pressures, one suitcase and three strategies — play with the simulator.
In a simple architecture, the session is an immutable log. As it scales, token usage explodes — and four limitations bite latency-sensitive applications:
📏 Window
exceeding the maximum processable text = the API call fails
💸 Cost
you pay per token sent/received — smaller history, smaller bill
🐢 Latency
more text = more processing time = slower response
📉 Quality
more tokens = worse performance: noise + autoregressive errors
The context window is a suitcase with limited space
A heavy, disorganized suitcase: you pay excess baggage (cost) and can't find anything (slowness). That's uncompacted history.
You forget your passport and coat — you lose critical context and answer wrong. Compacting well means packing only what's needed.
The three compaction strategies
🪟 Keep last N turns
the simplest: a sliding window over the most recent N turns — everything older is discarded
✂️ Token-based truncation
counts tokens from the most recent backward and includes as many messages as possible without exceeding a predefined limit (e.g. 4,000 tokens) — the rest is cut off
📜 Recursive summarization
older messages become a summary prefixed to the recent ones — best fidelity/cost balance (expensive LLM operation → runs in background)
# limita o contexto enviado ao LLM, sem modificar o log persistidoplugin = ContextFilterPlugin(num_invocations_to_keep=10)# ou: compactação agendada de eventosconfig = EventsCompactionConfig(compaction_interval=5, overlap_size=1)
Trigger mechanisms
🔢 Count-based
token or turn-count threshold — simple and "good enough"
⏰ Time-based
lack of activity (e.g. 15–30 min without interaction) → background compaction
🎯 Event-based
detects a completed task, sub-goal or topic — semantic trigger
Recursive summarization must run asynchronously in the background and persist its results (the client doesn't wait; the computation isn't repeated). The agent records which events are already in the compacted summary — so it doesn't re-send the verbose originals. Memory generation is the broad capability behind this: extracting persistent knowledge from noisy sources, discarding the filler.
Memory 101
The symbiotic relationship between sessions and memory — and the five layers that collaborate on every turn.
Sessions and memory live in symbiosis: sessions are the primary source for generating memories, and memories are the key strategy for managing the size of sessions. Each one feeds the other, in a continuous cycle.
A memory is a snapshot of extracted, meaningful information from a conversation or source: a condensed representation that preserves the important context, persisted across sessions for a continuous, personalized experience.
Some frameworks call the verbatim conversation "short-term memory". In this paper, memories are extracted information — not the raw dialogue.
Four capabilities a memory system enables
🎯 Personalization
remembering preferences, facts and past interactions — the favorite team, the preferred airplane seat
📦 Context window management
compacting long histories into summaries and key facts, preserving context without sending thousands of tokens per turn — less cost, less latency
📊 Data mining & insight
analyzing memories from many users in an aggregated, privacy-preserving way — e.g. a retail chatbot discovers many people asking about a product's return policy
🔁 Agent self-improvement
procedural memories about its own performance — which strategies, tools and paths led to success — become a playbook the agent reuses and adapts
The five layers collaborating on every turn
1 · User
provides the raw data — sometimes directly, via a form
2 · Agent (developer logic)
decides what and when to remember, and orchestrates the memory manager — from "always fetch/generate" to memory-as-a-tool, where the LLM decides
3 · Agent framework (ADK, LangGraph)
the plumbing: structures and tools to interact with memory, access the history, inject into the context window — it does not manage long-term storage
4 · Session storage (Agent Engine Sessions, Spanner, Redis)
stores the conversation turn by turn; the raw dialogue is the raw material ingested by the memory manager
5 · Memory manager (Agent Engine Memory Bank, Mem0, Zep)
storage, retrieval and compaction — the complete lifecycle: Extraction → Consolidation → Storage → Retrieval
A memory manager is not a passive vector database. Its core value is to extract, consolidate and curate memories intelligently over time — not just do similarity search.
RAG × Memory
Distinct, complementary roles: RAG makes the agent an expert in facts; Memory, an expert in the user.
Memory retrieval is often compared to RAG, but the architectural principles are different: RAG deals with static external data; Memory, with dynamic, user-specific context. They are complementary — and a truly intelligent agent needs both.
RAG · the research librarian
- works in a vast public library: encyclopedias, official docs, a static and shared base
- retrieves established, authoritative facts
- read-only, global — the same for every user
- knows nothing personal about you
Memory · the personal assistant
- carries a private notebook, recording details of every interaction
- dynamic and highly isolated: preferences, past conversations, goals
- writes on every turn or at session end — event-based
- adapts as the relationship evolves
| Dimension | RAG Engines | Memory Managers |
|---|---|---|
| Primary goal | inject external factual knowledge | personalized, stateful experience: remembers facts, adapts to the user, maintains long context |
| Data source | pre-indexed external knowledge base (PDFs, wikis, docs, APIs) | the user-agent dialogue |
| Isolation | usually shared (global, read-only) | highly isolated (per-user, prevents leaks) |
| Information type | static, factual, authoritative | dynamic, user-specific, with inherent uncertainty |
| Write pattern | batch processing (offline administrative action) | event-based (every turn / session end) or memory-as-a-tool |
| Read pattern | almost always as-a-tool (the agent decides when it's needed) | memory-as-a-tool OR static retrieval at the start of the turn |
| Format | natural-language chunks | natural-language snippets OR structured profile |
| Data preparation | chunking + indexing (embeddings for fast search) | extraction + consolidation (no duplication or contradiction) |
Memory types
Anatomy, cognitive taxonomy, organization, storage, creation, scope and multimodality.
Memories are classified by how they are stored and captured — and they work together for a rich, contextual understanding. Golden rule: memories are descriptive, not predictive.
Anatomy of a memory
A 'memory' is an atomic piece of context that is returned by the memory manager and used by the agent as context. While the exact schema can vary, a single memory generally consists of two components: content and metadata.
📄 Content
the substance extracted from the source data, in a framework-agnostic format. Structured ({"seat_preference": "window"}) or unstructured ("The user prefers a window seat").
🏷️ Metadata
the context about the memory: unique identifier, owner and labels describing content and source.
Declarative × Procedural (cognitive science)
Declarative
- facts, numbers, events
- includes general knowledge (semantic) and user-specific facts (episodic)
- e.g. "the user prefers a window seat"
Procedural
- skills and workflows
- guides actions by implicitly demonstrating how to execute a task
- e.g. the correct sequence of tool calls to book a trip
Organization patterns
🗃️ Collections
multiple self-contained natural-language memories per user — several per topic, searched in a larger, less structured pool
🪪 Structured user profile
a set of core facts, like a continuously updated contact card — fast lookup of the essentials (names, preferences, account)
📜 Rolling summary
ONE single, evolving memory: the summary of the entire user-agent relationship, continuously updated — used to compact long sessions
Storage architectures
🧮 Vector databases
retrieval by semantic similarity (not exact keywords): memories become embeddings and match by concept. Excellent for unstructured facts
🕸️ Knowledge graphs
memories as a network of entities (nodes) + relationships (edges); retrieval = traversing the graph. Ideal for relational queries ("knowledge triples")
🔀 Hybrid
graph entities enriched with vector embeddings — relational and semantic search at once: the best of both worlds
Creation mechanisms
🗣️ Explicit
direct command from the user: "remember my anniversary is October 26th"
🕵️ Implicit
the agent infers without a command: "my anniversary is next week, help me find a gift" → memory created
🏠 Internal
management embedded in the framework — convenient, with fewer features
☁️ External
specialized service (Memory Bank, Mem0, Zep): semantic search, entity extraction, automatic summarization
Scope: who the memory describes
👤 User-level
the most common: bound to the user ID, persists across sessions — "the user prefers the middle seat"
💬 Session-level
persistent record of insights from ONE session — replaces the verbose transcript with concise facts, isolated to that conversation
🌐 Application-level
global context accessible to all users — common case: procedural memories ("how-to" for the agent's reasoning)
Application-level memories must be sanitized of sensitive content — otherwise they become a leak vector between users.
Multimodal memory: source × content
Multimodal source
- the agent processes text, image or audio — but the memory created is a textual insight
- e.g. voice memo → transcription → "user expressed frustration about shipping delay" (the audio is not stored)
Multimodal content
- the memory contains non-textual media directly
- e.g. "remember this design for our logo" → the memory contains the image file
Most managers focus on multimodal sources → textual content: converting everything to text is the simplest way to keep a searchable format.
from google.genai import types client = vertexai.Client(project=..., location=...) response = client.agent_engines.memories.generate( name=agent_engine_name, direct_contents_source={"events": [{"content": types.Content( role="user", parts=[ types.Part.from_text("This is context about the multimodal input."), types.Part.from_bytes(data=CONTENT_AS_BYTES, mime_type=MIME_TYPE), types.Part.from_uri(file_uri="file/path/to/content", mime_type=MIME_TYPE) ] )}]}, scope={"user_id": user_id})
Memory generation: the ETL pipeline
How raw conversational data becomes structured insights — an LLM-directed ETL.
Generation autonomously transforms raw conversational data into structured, meaningful insights — an LLM-directed ETL pipeline (Extract, Transform, Load). That's what distinguishes memory managers from RAG engines and traditional databases: instead of the dev specifying database operations manually, the LLM decides when to add, update or merge memories — abstracting the complexity of managing content, chaining calls and running background services.
Ingestion
the client provides the raw data source — typically the conversation history
Extraction & filtering
the LLM extracts only what fits predefined topic definitions — no match, no memory created
Consolidation
the most sophisticated stage: conflict resolution + deduplication — merge, delete or create
Storage
the new or updated memory is persisted in durable storage (vector DB / knowledge graph)
memories.generate( scope={"user_id": "u_8123"}, direct_contents_source=session_events, # matéria-prima rawconfig={"wait_for_completion": False}, # assíncrono, em background)A healthy garden doesn't grow by itself — it demands constant curation
New seeds and seedlings arrive at the garden: the LLM identifies what deserves to be planted — and discards what doesn't fit the defined beds (topics).
Pull out the weeds (delete the redundant and conflicting), prune branches (refine and summarize the existing) and plant each seedling in the optimal spot. Without curation, the garden turns to weeds — a continuous, background process.
A managed memory manager (e.g. Agent Engine Memory Bank) automates the entire pipeline — extraction, consolidation and storage — with a single asynchronous API call.
Deep-dive: Extraction
"What information here is meaningful enough to become a memory?" — intelligent filtering, not summarization.
The fundamental question of extraction is: "what information in this conversation is meaningful enough to become a memory?" It's not simple summarization — it's intelligent, targeted filtering: separating signal (facts, preferences, goals) from noise (pleasantries, filler).
"Meaningful" is not universal — it's defined by the agent's purpose. A customer-support agent extracts order numbers and technical issues; a wellness coach extracts long-term goals and emotional states. Customizing that definition is the key to an effective agent.
How the LLM knows what to extract
🧩 Schema / template-based
a predefined JSON schema or template (structured output); the LLM builds the JSON with the matching information
📝 Natural-language definitions
the LLM is guided by a simple natural-language description of what each topic is
🎓 Few-shot prompting
the LLM "sees" what to extract through examples: input + ideal high-fidelity memory. Very effective for nuanced topics that are hard to describe
Most managers work out-of-the-box with common topics (preferences, key facts, goals) — and many allow custom topics. The paper's example: a conversation about a coffee shop generates two feedback memories — "the drip coffee was lukewarm" and "the music was too loud".
config = MemoryGenerationConfig( memory_topics=[ ManagedTopicEnum.USER_PERSONAL_INFO, # tópico built-inCustomMemoryTopic( name="business_feedback", description="feedback about the coffee shop", ), ], generate_memories_examples=[...], # few-shot: conversa → fatos)
Although it isn't summarization, the algorithm can incorporate it: a rolling summary of the conversation enters the extraction prompt, giving condensed context to extract from recent interactions — without reprocessing the full dialogue every turn.
Deep-dive: Consolidation
The stage that turns a collection of facts into curated understanding — LLM-directed self-curation.
Consolidation integrates new information into a coherent, accurate, evolving knowledge base. It's the most sophisticated stage: without it, memory becomes a noisy, contradictory, unreliable log. This LLM-managed "self-curation" is what elevates the memory manager beyond a simple database.
The four problems it addresses
👯 Duplication
the same fact in several forms: "I need a flight to NYC" + "I'm planning a trip to New York" — naive extraction would create two redundant memories
⚔️ Conflict
the user's state changes over time — without consolidation, contradictory facts coexist in the base
🌱 Evolution
a simple fact becomes more nuanced: "interested in marketing" → "leading a Q4 customer-acquisition project"
⌛ Decay
not every memory stays useful: the agent practices forgetting — pruning the old, obsolete and low-confidence (prioritize the new, or TTL)
The three-step process
1 · Find similar
existing memories similar to the freshly extracted ones become consolidation candidates
2 · LLM analyzes
existing memories + new information, together: the LLM identifies the required operations
3 · Transaction
the memory manager translates the LLM's decision into a transaction that updates the store
UPDATE
modify an existing memory with new or corrected information
CREATE
a wholly new, unrelated insight → create a new memory
DELETE / INVALIDATE
the new information made the old memory irrelevant or incorrect → delete or invalidate
Provenance: lineage & trust
"Garbage in, confident garbage out" — every memory needs a record of origin and history.
"Garbage in, garbage out" is even more critical for LLMs: here it's "garbage in, confident garbage out". For trustworthy decisions and effective consolidation, the agent must critically evaluate the quality of its own memories — and reliability derives from provenance: the detailed record of origin and history.
one memory ← several sources
one source → several memories
trust = origin + age
Lineage during management
⚔️ Conflict resolution
sources conflict — provenance establishes the trust hierarchy: prioritize the most reliable source, favor the most recent information, seek corroboration across multiple data points.
🧹 Deleting derived data
if the user revokes access to a source, derived data must be removed. Deleting every memory "touched" can be too aggressive — the most precise (and expensive) approach is to regenerate the affected memories from scratch using only the remaining valid sources.
Trust evolves — and pruning is active
Trust is not static: it grows with corroboration (multiple consistent reliable sources) and decays with age and conflict. Memory pruning (active forgetting) identifies and discards memories that are no longer useful — by time-based decay (a meeting from 2 years ago is worth less than last week's), low confidence (a weak inference never corroborated) or irrelevance (old trivial details in the face of current goals). Reactive consolidation + proactive pruning = a curated knowledge base, not an ever-growing log of everything.
Memories and their confidence scores are not shown to the user — they're injected into the system prompt so the LLM can weigh the evidence, consider reliability and make more nuanced decisions.
Triggering generation & memory-as-a-tool
The agent decides WHEN to generate — a balance between data freshness, cost and latency.
Memory managers automate extraction and consolidation after generation is triggered — but who decides when to attempt generation is the agent. It's a critical architectural choice: balancing data freshness against computational cost and latency.
Trigger strategies
🏁 Session completion
at the end of a multi-turn session — most economical, lower-fidelity memories
🔁 Turn cadence
after N turns (e.g. every 5) — a middle ground between freshness and cost
⚡ Real-time
after EVERY turn — detailed, fresh memories, higher LLM/database cost
🗣️ Explicit command
direct command from the user: "remember this" — maximum fidelity, clear intent
Frequent generation = fresh, detailed memories, but higher cost and potential latency. Infrequent generation = economical, but the LLM summarizes much larger blocks (lower fidelity). And beware: don't reprocess the same events multiple times — unnecessary cost.
Memory-as-a-tool: the agent decides
In the most sophisticated approach, generation is exposed as a tool (e.g. create_memory) whose definition describes which types of information are meaningful. The agent analyzes the conversation and calls the tool autonomously when it identifies something worth persisting — shifting the responsibility of identifying the "meaningful" from the memory manager to the agent/dev.
def generate_memories(tool_context):# opção 1: histórico completo da sessionmemory_service.add_session_to_memory(tool_context.session)# opção 2: só o último turno, assíncronomemories.generate(..., config={"wait_for_completion": False}) runner = Runner(agent=agent, memory_service=VertexAiMemoryBankService())
# aqui o AGENTE extrai (extract_memories) e envia ao Memory Bank# apenas para CONSOLIDAR com as memórias existentesmemories = extract_memories(direct_memories_source={"fact": query}) memory_bank.store(memories) # consolidação delegada ao serviço
Background, always
Memory Retrieval
Which memories to fetch, when to fetch them — and how to score them across multiple dimensions.
The retrieval strategy depends on the organization: a structured user profile is a simple lookup (the whole profile or one attribute); a collection is a complex search problem — finding the most pertinent information in a large, loosely structured pool.
Effective retrieval is crucial: irrelevant memories confuse the model and degrade the response; perfect context produces a remarkably intelligent interaction. The central challenge is balancing utility against a strict latency budget.
The three scoring dimensions
Relevance
how conceptually related to the current conversation?
Recency
how recently was the memory created?
Importance
how critical is it overall? (set at generation — different from relevance)
Relying only on vector relevance makes retrieval surface conceptually similar memories — but old or trivial ones. The best strategy is a blended approach: combining all three dimensions.
Precision techniques (and their cost)
✍️ Query rewriting
the LLM improves its own query — rewriting ambiguous input into a precise query or expanding one query into several related ones. Improves quality, adds the latency of an extra call
🏆 Reranking
broad initial retrieval (e.g. top 50) by similarity; then the LLM re-evaluates and re-ranks the set into a more precise final list
🔬 Specialized retriever
fine-tuning the retriever — requires labeled data and significantly raises costs
1) If these techniques are needed and memories don't go stale quickly, use a caching layer — store expensive results temporarily and avoid latency on identical requests. 2) The best approach starts before retrieval: better memory generation (a high-quality corpus, free of irrelevance) is the most effective way to guarantee useful retrieval.
Timing: when to fetch
Proactive
- context always available — but unnecessary latency on turns that don't need it
- since memories are static during a turn, they can be cached (mitigates the cost)
- e.g. ADK
PreloadMemoryToolor abefore_model_callbackthat appends memories to the system_instruction
Reactive · memory-as-a-tool
- more efficient and robust — the extra call only happens when needed
- risk: the agent may not know relevant information exists
- mitigation: describe the types of memories available in the tool itself (e.g.
LoadMemoryToolorload_memory(query))
# Option 1: PreloadMemoryTool embutida — busca por similaridade em todo turnoagent = LlmAgent( ..., tools=[adk.tools.preload_memory_tool.PreloadMemoryTool()] )# Option 2: callback customizado — mais controle sobre como as memórias são buscadasdef retrieve_memories_callback(callback_context, llm_request): user_id = callback_context._invocation_context.user_id app_name = callback_context._invocation_context.app_name response = client.agent_engines.memories.retrieve( name="projects/.../locations/.../reasoningEngines/...", scope={"user_id": user_id, "app_name": app_name}) memories = [f"* {memory.memory.fact}" for memory in list(response)]if not memories:return # nenhuma memória para acrescentar às System Instructions# anexa as memórias formatadas às System Instructionsllm_request.config.system_instruction += "\nHere is information that you have about the user:\n"llm_request.config.system_instruction += "\n".join(memories) agent = LlmAgent( ..., before_model_callback=retrieve_memories_callback, )
# Option 1: LoadMemoryTool embutida — o agente decide quando buscaragent = LlmAgent( ..., tools=[adk.tools.load_memory_tool.LoadMemoryTool()], )# Option 2: tool customizada — descreva que tipos de informação podem estar disponíveisdef load_memory(query: str, tool_context: ToolContext):"""Retrieves memories for the user. The following types of information may be stored for the user: * User preferences, like the user's favorite foods. ..."""# busca memórias por similaridaderesponse = tool_context.search_memory(query)return response.memories agent = LlmAgent( ..., tools=[load_memory], )
Inference with memories
The final step: strategically positioning the retrieved memories in the context window.
Positioning influences the LLM's reasoning, operational costs and response quality. In practice, a hybrid strategy works best: system prompt for stable/global memories (the user profile, always present); dialogue injection or memory-as-a-tool for transient/episodic memories (relevant only to the immediate context).
Memories in the System Instructions
Appending memories to the system prompt with a preamble gives them high authority and separates the context from the dialogue — ideal for stable, global information. Typically via a template (e.g. Jinja) with a <MEMORIES> block iterating over retrieved_memory.memory.fact.
from jinja2 import Template template = Template("""{{ system_instructions }}<MEMORIES> Here is some information about the user:{% for retrieved_memory in data %}* {{ retrieved_memory.memory.fact }}{% endfor %}</MEMORIES> """) prompt = template.render( system_instructions=system_instructions, data=retrieved_memories )
Over-influence: the agent tries to relate EVERY topic to the core memories, even when inappropriate. Also: it requires a framework that supports a dynamic system prompt on every call; it is incompatible with memory-as-a-tool (the system prompt must be finalized BEFORE the LLM decides to call the retrieval tool); and it handles non-textual memories poorly.
Memories in the Conversation History
Injecting directly into the dialogue — before the full history or right before the user's last query. Risks: noise (more tokens, confusion if irrelevant) and dialogue injection (the model treats the memory as something actually said in the conversation). Mind the perspective: if you use role "user" with user-level memories, write in first person. Special case: retrieval via tool calls — memories arrive as tool output.
def load_memory(query: str, tool_context):"""Search the user's long-term memories."""response = tool_context.search_memory(query)return response.memories # entra no contexto como tool output
And what about procedural memories?
The paper focused on declarative — a reflection of today's commercial market, whose platforms are architected to extract/store/retrieve the "what". But storing the "how" is not an information-retrieval problem — it's a reasoning-augmentation problem, with its own lifecycle:
⛏️ Extraction
specialized prompts distill a reusable strategy — a "playbook" — from a successful interaction, not just a fact
🧬 Consolidation
curates the WORKFLOW: integrates new successful methods with existing best practices, patches failing steps, prunes obsolete procedures
🔎 Retrieval
the goal isn't retrieving data to answer a question, but retrieving a PLAN that guides the execution of a complex task
Both aim to improve behavior — but the mechanisms are fundamentally different. Fine-tuning is a slow, offline process that alters the model's weights. Procedural memory is fast, online adaptation: dynamically injecting the right "playbook" into the prompt — in-context learning, no fine-tuning.
Testing & evaluation of memory
Does it remember the right things? Does it find them when needed? And does using memory actually help?
Memory evaluation is a multi-layered process: verifying the agent remembers the right things (quality), finds memories when needed (retrieval), and that using them actually helps achieve goals (task success). In academia, reproducible benchmarks; in industry, direct impact on the production agent.
🧪 Generation quality
🔎 Retrieval performance
🏁 End-to-end task success
Evaluation is not a one-time event: establish a baseline → analyze failures → tune the system (refine prompts, adjust retrieval algorithms) → re-evaluate to measure impact. And beyond quality, production-readiness demands performance: sub-second retrieval on the hot path, sufficient throughput for asynchronous generation. A successful memory system = intelligent + efficient + robust.
Memory in production & security
From prototype to enterprise: decoupling, concurrency, resilience — and the corporate archivist.
From prototype to production, the focus shifts to enterprise-grade concerns: scalability, resilience and security. Rule number one: decouple memory processing from the main logic — the UX can never be blocked by expensive generation.
1 · Agent pushes data
after a relevant event (e.g. session end), a non-blocking API call "pushes" the raw data
2 · Process in background
the service acknowledges, queues internally and does the heavy lifting — LLM, extraction, consolidation
3 · Memories persisted
final memories written to a dedicated durable database (managed managers have built-in storage)
4 · Agent retrieves
the application queries the store directly when it needs context for a new interaction
Failures and latency in the memory pipeline don't impact the user-facing application. The pattern also informs the choice between online processing (real-time, conversational freshness) and offline (batch, ideal for backfilling historical data).
Concurrency, failures and global scale
🔀 Concurrency
high-frequency events without deadlocks/race conditions when multiple events modify the same memory: transactional operations or optimistic locking, with a robust message queue as buffer
🩹 Failure handling
resilience to transient errors: LLM call failed → retry with exponential backoff; persistent failures → dead-letter queue for analysis
🌍 Global
multi-region replication built-in — client-side replication isn't viable (consolidation requires a single, transactionally consistent view); the system replicates internally and presents a single logical datastore
Privacy & security risks
Memories derive from — and include — user data. Think of a secure corporate archive managed by a professional archivist: it preserves valuable knowledge while protecting the company.
🔐 Data isolation
the cardinal rule: just as the archivist never mixes confidential files from different departments, memory is strictly isolated per user/tenant (restrictive ACLs). Users have programmatic control: opt out of generation or delete all their files
🖊️ PII redaction
before filing any document, the archivist redacts sensitive personal information — knowledge is saved without creating liability
☠️ Memory poisoning
the archivist is trained to spot forgeries: validating and sanitizing information BEFORE committing to long-term memory prevents a malicious user from corrupting persistent knowledge via prompt injection (safeguards like Model Armor)
📡 Exfiltration risk
memories shared across users (e.g. procedural "how-to") are like a company-wide memo: if one user's memory becomes an example for another, the archivist performs rigorous anonymization first — preventing leaks across user boundaries
Conclusion
From a single conversational turn to persistent, actionable intelligence.
The journey from a conversational turn to persistent intelligence is governed by Context Engineering — dynamically assembling history, memories and external knowledge into the context window. It depends on the interplay of two distinct, interconnected systems:
The Session governs the "now"
- challenge = performance + security: low-latency access and strict isolation
- compaction via token truncation and recursive summarization
- PII redaction BEFORE persisting — security paramount
Memory governs the "always"
- goes beyond RAG (expert in facts) to make the agent an expert in the USER
- LLM-directed ETL pipeline: extraction → consolidation → retrieval
- asynchronous background generation + provenance + poisoning safeguards = assistants that learn and grow with the user
Context is a managed resource, not an accident
Every token in the window has cost, latency and attentional weight. Assemble the payload dynamically: maximum relevance, minimum noise.
Sessions and memory are symbiotic, but distinct
The session is the chronological log of one conversation; memory is knowledge extracted and curated across conversations. One feeds the other — never confuse the two.
Trust is tracked, weighed and pruned
Provenance says where it came from; confidence scores say how much to weigh it; active forgetting keeps the base curated. Memory without curation is just a log with pretensions.
Memory governs the always."
Quiz
Eight questions to consolidate the cycle, sessions and the memory pipeline.
Cheatsheets
Three copyable artifacts to take to your next agent project.
CONTEXT CYCLE — per-turn checklist ---------------------------------- [ ] FETCH - memories + RAG + recent events (query + metadata) [ ] PREPARE - full payload assembled (blocking, hot path) [ ] INVOKE - LLM + tools, append outputs as they arrive [ ] UPLOAD - persist events, trigger memory gen (background) COMPACTION decision tree history < 4k tokens - keep as-is 4k-16k tokens - keep-last-N / token truncation > 16k / long-running - recursive summarization (async + persist) TRIGGERS: count-based | time-based | event-based GOLDEN RULE: maximum relevance, minimum noise
MEMORY ETL — design recipe -------------------------- INGEST - raw conversation events (from the session store) EXTRACT - topic filter: define "meaningful" per agent purpose (schema / natural-language defs / few-shot examples) CONSOLIDATE - LLM decides: UPDATE | CREATE | DELETE-INVALIDATE dedup + conflict resolution + active forgetting STORE - vector DB / knowledge graph / hybrid TRIGGERS : session-end | every-N-turns | real-time | explicit SCOPE : user-level | session-level | application-level TIMING : generation ALWAYS async in background RULE : memories are descriptive, not predictive
RETRIEVAL — scoring and timing ------------------------------ SCORE = w1*relevance + w2*recency + w3*importance (never vector-similarity alone -> old/trivial memories resurface) TIMING proactive - preload each turn (cacheable, always available) reactive - memory-as-a-tool (agent decides, extra LLM call) PRECISION BOOSTERS (cost up, latency up) query rewriting -> reranking (top-50 -> top-K) -> specialized retriever + caching layer when memories are stable METRICS generation : precision / recall / F1 retrieval : recall@K / latency < 200ms (hot path) end-to-end : LLM judge vs golden answer
Implementation checklists
Check off what you already master — your progress is saved in this browser.
📦Ship sessions to production
🧠Build a memory system
🛡️Harden for production
Glossary
The paper's essential terms, in plain language.
Continue the journey
The companion papers in the series — each guide follows the same interactive, trilingual format.
Agents Whitepaper Series — hub
All series guides in one place.
The New SDLC with Vibe Coding
The new software development lifecycle: from written code to orchestrated intent.
Agent Tools & Interoperability
The 5 open protocols that connect agents to tools and to each other.
Vibe Coding Agent Security and Evaluation
How to evaluate and protect agents: quality gates, metrics and production security.
Spec-Driven Production Grade Development
Spec-driven development to take vibe coding to production grade.
References
All 30 endnotes from the original paper, in order of appearance.
MILAM, Kimberly; GULLI, Antonio. "Context Engineering: Sessions, Memory" — Agents Whitepaper Series, Google, November 2025.