LIVE DEMO → Home Product
FeaturesUse CasesCompare
Docs
DocumentationQuickstartIntegrations
PricingBlog DASHBOARD → LOG IN →
Tutorial Google ADK Python May 4, 2026 · 9 min read

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.

In this article
  1. What is Google ADK and where memory fits
  2. The session state limitation
  3. Installing Kronvex alongside ADK
  4. Recall at session start: before_turn callback
  5. Store at session end: after_turn callback
  6. Alternative: Kronvex as an ADK Tool
  7. Full agent example with persistent memory
  8. Multi-agent memory sharing
  9. 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.

ADK vs LangGraph vs CrewAI. ADK is Google's answer to LangGraph and CrewAI for orchestrating multi-agent workflows. Its main differentiator is tight Gemini/Vertex AI integration and a first-class streaming API. Like the others, it has no built-in cross-session memory — that's where Kronvex comes in.

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:

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

Install both packages
pip install google-adk kronvex
Set environment variables
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"
Initialize both clients
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.

before_turn: inject memory context
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.

after_turn: store 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.

Define Kronvex tools for ADK
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),
]
Callbacks vs Tools — which to choose? Use callbacks when you want automatic, transparent memory without the agent having to decide. Use tools when you want the agent to have agency over what gets stored — useful for research agents that selectively persist only high-value findings.

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.

adk_agent_with_memory.py
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.

Tip: use different session IDs for different agent contexts. If you want research memories to be separate from user-preference memories, use different session ID prefixes: 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().

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 →

Or read the full integration docs

Integration guide
Google ADK Persistent Memory — Setup Guide
Step-by-step setup · code snippets · 2 min
Read the guide →
Related articles
Free API Key
Get started free
No credit card required. 1 agent, 100 memories on the demo plan.