Persistent Memory for
Google ADK Agents
Google's Agent Development Kit is a powerful framework for building multi-agent Gemini systems — but it resets between sessions. Every new user interaction starts with no knowledge of what came before. This guide shows how to wire Kronvex into ADK agents so they remember users, context, and facts across every session.
- What is Google ADK and where memory fits
- The session state limitation
- Installing Kronvex alongside ADK
- Recall at session start: before_turn callback
- Store at session end: after_turn callback
- Alternative: Kronvex as an ADK Tool
- Full agent example with persistent memory
- Multi-agent memory sharing
- Best practices
What is Google ADK and where memory fits
Google's Agent Development Kit (ADK) is an open-source Python framework for building multi-agent systems powered by Gemini. Released in 2025, ADK introduced a structured way to compose agents into hierarchies: a root agent coordinates specialised sub-agents, each with their own tools and instructions. It integrates natively with Google Cloud (Vertex AI, Cloud Run, Dialogflow) and supports streaming responses via Server-Sent Events.
ADK's architecture revolves around three concepts: Agents (the decision-making units), Tools (functions an agent can call), and Sessions (the in-memory conversation context for a single interaction). Sessions are where the memory gap lives.
The session state limitation
ADK's Session object holds the full conversation history and any key-value state you add via session.state. This works perfectly within a single interaction. But the moment the session ends — the HTTP request completes, the user closes the chat, or your Cloud Run instance scales down — that state is gone forever.
The next time the same user starts a conversation, ADK creates a fresh Session with no prior context. Your agent cannot remember:
- The user's name, role, or organisation
- Previous decisions or preferences expressed in past sessions
- Ongoing tasks or project state
- What was already explained so you don't repeat yourself
Kronvex provides a persistent, searchable memory layer that your ADK agents can read and write at the start and end of each session — or on demand via a tool call.
Installing Kronvex alongside ADK
pip install google-adk kronvex
export GOOGLE_API_KEY="your-gemini-api-key" export KV_API_KEY="kv-your-kronvex-api-key" export KV_AGENT_ID="your-agent-uuid-from-dashboard"
from google.adk import Agent, Runner from google.adk.sessions import InMemorySessionService from kronvex import Kronvex import os kv = Kronvex( api_key=os.environ["KV_API_KEY"], agent_id=os.environ["KV_AGENT_ID"], )
Recall at session start: before_turn callback
ADK agents support lifecycle callbacks. The before_turn callback runs before the model processes the user's message — this is the perfect place to recall relevant memories and inject them into the agent's instructions or session state.
from google.adk.agents import CallbackContext from google.genai import types def before_turn_recall(callback_context: CallbackContext) -> None: """Recall relevant memories and inject into session state.""" user_id = callback_context.state.get("user_id", "anonymous") # Get the latest user message latest_msg = "" if callback_context.user_content: for part in callback_context.user_content.parts: if part.text: latest_msg = part.text break if not latest_msg: return # Recall top memories for this user memories = kv.recall( query=latest_msg, top_k=5, session_id=user_id, threshold=0.35, ) # Store in session state for the agent to use if memories: mem_lines = [f"- {m['content']}" for m in memories] callback_context.state["memory_context"] = "\n".join(mem_lines) else: callback_context.state["memory_context"] = ""
Store at session end: after_turn callback
The after_turn callback runs after the model produces its response. This is where you capture and store the key information from this turn as a Kronvex memory.
def after_turn_remember(callback_context: CallbackContext) -> None: """Store the conversation turn as a persistent memory.""" user_id = callback_context.state.get("user_id", "anonymous") # Store the user message if callback_context.user_content: for part in callback_context.user_content.parts: if part.text: kv.remember( content=f"User: {part.text}", memory_type="episodic", session_id=user_id, ) break # Store the agent response if callback_context.agent_response: response_text = callback_context.agent_response kv.remember( content=f"Agent: {response_text[:500]}", # cap at 500 chars memory_type="episodic", session_id=user_id, ttl_days=90, )
Alternative: Kronvex as an ADK Tool
Instead of callbacks, you can expose Kronvex as ADK tools that the agent can call autonomously. This is more flexible — the agent decides when to store and recall based on the conversation flow. It also gives the agent more control over what gets remembered.
from google.adk.tools import FunctionTool def remember_fact(content: str, memory_type: str = "semantic") -> dict: """Store an important fact or event in persistent memory. Args: content: The fact or event to remember (1-3 sentences). memory_type: 'episodic' for events, 'semantic' for facts, 'procedural' for recurring patterns. Returns: Confirmation with memory ID. """ result = kv.remember(content=content, memory_type=memory_type) return {"status": "stored", "memory_id": result.get("id")} def recall_memories(query: str) -> dict: """Recall memories relevant to a query from persistent storage. Args: query: Natural language query describing what to remember. Returns: List of relevant memories with confidence scores. """ memories = kv.recall(query=query, top_k=5) return {"memories": [m["content"] for m in memories]} memory_tools = [ FunctionTool(func=remember_fact), FunctionTool(func=recall_memories), ]
Full agent example with persistent memory
Here is a complete ADK agent with Kronvex memory wired in via callbacks. The agent has access to Gemini 2.0 Flash and remembers every user interaction across sessions.
from google.adk import Agent, Runner from google.adk.sessions import InMemorySessionService from google.genai import types from kronvex import Kronvex import os kv = Kronvex( api_key=os.environ["KV_API_KEY"], agent_id=os.environ["KV_AGENT_ID"], ) def before_turn(ctx): uid = ctx.state.get("user_id", "default") msg = "" if ctx.user_content: for p in ctx.user_content.parts: if p.text: msg = p.text; break if msg: context = kv.inject_context(message=msg, session_id=uid) ctx.state["memory_context"] = context def after_turn(ctx): uid = ctx.state.get("user_id", "default") if ctx.user_content: for p in ctx.user_content.parts: if p.text: kv.remember(p.text, "episodic", session_id=uid) break agent = Agent( name="persistent_assistant", model="gemini-2.0-flash", instruction="""You are a helpful assistant with persistent memory. At the start of each turn, you have access to {memory_context} in your session state — this contains relevant past memories about this user. Use it to personalise your responses. If no memory context is available, respond normally without mentioning the absence of memory.""", before_turn_callbacks=[before_turn], after_turn_callbacks=[after_turn], ) session_service = InMemorySessionService() runner = Runner( agent=agent, app_name="kronvex_adk_demo", session_service=session_service, ) def run_turn(user_id: str, message: str) -> str: session = session_service.get_or_create( app_name="kronvex_adk_demo", user_id=user_id, session_id=user_id, ) session.state["user_id"] = user_id content = types.Content( role="user", parts=[types.Part(text=message)], ) result = "" for event in runner.run( user_id=user_id, session_id=user_id, new_message=content, ): if event.is_final_response() and event.content: for part in event.content.parts: if part.text: result += part.text return result
Multi-agent memory sharing
ADK supports multi-agent architectures where a root agent delegates to specialised sub-agents. Kronvex works naturally in this pattern because all agents share the same API key and agent ID — meaning memories stored by one sub-agent are immediately available to others.
For example: a research sub-agent stores findings from web searches as semantic memories. A reporting sub-agent later recalls those findings to write a summary. A scheduler sub-agent can recall past scheduling patterns stored as procedural memories. All three operate on the same Kronvex memory pool, scoped per user via the session_id.
research-{user_id} vs prefs-{user_id}. This gives you logical isolation within the same agent.
Best practices
Session ID as user identity
Always pass the user's stable identifier as the session_id. In ADK, store it in session.state["user_id"] at the start of the session so all callbacks can access it. A UUID from your auth system, a hashed email, or a Google Identity user ID all work well.
Keep memories short and atomic
Kronvex uses cosine similarity on embeddings — shorter, focused memories retrieve with higher precision than long paragraphs. Aim for 1–3 sentences per memory. If the agent response is long, extract a summary before calling kv.remember().
- Use
ttl_daysfor ephemeral state — session summaries after 30 days, user preferences can be permanent - Async ADK agents — use
AsyncKronvexfromkronvex.async_clientto avoid blocking the ADK event loop - Error isolation — wrap Kronvex calls in try/except so a memory error never breaks the ADK agent response
- GDPR compliance — all Kronvex data is stored in EU (Frankfurt). For deletion, call
kv.delete_memory(memory_id)or delete the entire session's memories via the dashboard
Add persistent memory to your ADK agents
Free plan — 1 agent, 100 memories. No credit card. Works with Gemini, Vertex AI, and any ADK multi-agent setup.
Get your free API key →