Skip to main content

Zep Google ADK Integration

A memory integration package that enables Google ADK agents to leverage Zep's long-term memory platform for persistent conversation storage and context-aware responses.

Installation

pip install zep-adk

Choosing a component

zep-adk ships the same set of capabilities across Python, TypeScript, and Go, though the exact symbol names differ per language's ADK idioms:

Capability Python TypeScript Go
guaranteed context injection ZepContextTool ZepContextTool or createZepBeforeModelCallback NewBeforeModelCallback
assistant-turn persistence create_after_model_callback createZepAfterModelCallback NewAfterModelCallback
explicit provisioning create_user/create_thread createUser/createThread CreateUser/CreateThread
custom context block context_builder contextBuilder WithContextBuilder
injection template context_template contextTemplate WithContextTemplate
model-callable graph search (pin-or-expose, 6 scopes) ZepGraphSearchTool ZepGraphSearchTool NewGraphSearchTool
ADK-native memory service ZepMemoryService ZepMemoryService NewMemoryService

Note: Go intentionally has no tool-based injection (callbacks are the Go-ADK-idiomatic hook).

Identifiers

Zep v4 addresses every user, thread, and graph by a server-generated UUID. A v4 create call takes no client-chosen identifier, thus a v4 resource has no name. The public API of this package takes user_uuid and thread_uuid.

Provision the Zep resources one time, read the UUIDs from the responses, and store the UUIDs in your own database. The integration does not look an identifier up at run time.

Quick Start

Define one agent, shared across all users. Per-user identity is passed via ADK session state.

import os
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from zep_cloud.client import AsyncZep
from zep_adk import ZepContextTool, create_after_model_callback, create_user, create_thread

# Initialize Zep client
zep = AsyncZep(api_key=os.getenv("ZEP_API_KEY"))

# One shared agent definition
agent = Agent(
    name="my_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant with long-term memory.",
    tools=[ZepContextTool(zep_client=zep)],
    after_model_callback=create_after_model_callback(zep_client=zep),
)

session_service = InMemorySessionService()
runner = Runner(agent=agent, app_name="my_app", session_service=session_service)

# Provision the Zep user and thread out-of-band, BEFORE the first turn --
# e.g. during account/session onboarding in your app. Zep generates the UUIDs.
user = await create_user(
    zep,
    first_name="Jane",
    last_name="Smith",
    email="jane@example.com",  # optional
)
thread = await create_thread(zep, user_uuid=user.uuid_)

# Store user.uuid_, user.graph_uuid, and thread.uuid_ in your own database, next
# to your own user ID, and put them into the ADK session state. This dict stands
# in for that database.
zep_user_uuids: dict[str, str] = {"user_123": user.uuid_}

await session_service.create_session(
    app_name="my_app",
    user_id="user_123",
    session_id="session_abc",
    state={
        "zep_user_uuid": user.uuid_,
        "zep_thread_uuid": thread.uuid_,
        "zep_graph_uuid": user.graph_uuid,
        "zep_first_name": "Jane",
        "zep_last_name": "Smith",
    },
)

Session State Keys

Identity is resolved at runtime from ADK session state. Set the Zep UUIDs when you create the session:

Key Required Default Description
zep_user_uuid Yes ADK user_id The UUID of the Zep user. The ADK user_id is used only when it holds the Zep user UUID.
zep_thread_uuid Yes ADK session_id The UUID of the Zep thread. The ADK session_id is used only when it holds the Zep thread UUID.
zep_graph_uuid Optional resolved from the user The UUID of the graph of the user. ZepGraphSearchTool and ZepMemoryService resolve it through user.get when it is absent.
zep_first_name Recommended "Anonymous" User's first name. Attached as the author name on persisted messages so Zep can anchor them to the user's identity node in the knowledge graph.
zep_last_name Optional "User" User's last name.

The user's name and email on the Zep user profile itself are set during provisioning -- pass them to create_user, not session state.

How It Works

The integration uses four components that work together to give your ADK agent persistent memory:

create_user / create_thread

Explicit, out-of-band provisioning helpers. Call these once -- during onboarding, account creation, or before the first turn of a new conversation -- before the agent runs. Each calls the Zep v4 create method and returns the created object, so the caller reads user.uuid_, user.graph_uuid, and thread.uuid_. Every call creates a new resource, thus the helpers are not idempotent. Failures (auth, network, 5xx) raise, so misconfiguration is caught immediately rather than silently swallowed.

Run one-time per-user setup -- a per-user ontology, custom instructions, or summary instructions -- directly after create_user returns.

The ADK turn path (ZepContextTool) never creates users or threads itself -- it assumes they already exist.

ZepContextTool

A BaseTool subclass that hooks into ADK's process_llm_request() lifecycle method (the same pattern ADK's own PreloadMemoryTool uses). On every LLM turn it:

  1. Extracts the user's latest message from the invocation context.
  2. Resolves the user's Zep identity from session state.
  3. Persists the message to Zep -- via thread.add_messages(return_context=True) in a single API call by default, or in parallel with a custom context_builder for advanced use cases. Over-limit message content (> 4096 chars) is truncated before persisting rather than dropped.
  4. Injects the returned context (facts, relationships, prior knowledge) into the LLM's system instructions, wrapped by a configurable context_template (default: DEFAULT_CONTEXT_TEMPLATE, using <ZEP_CONTEXT> tags).

The tool is never called by the model directly; it modifies the outgoing LLM request before it is sent. If persistence fails because the user or the thread does not exist, a warning naming create_user/create_thread is logged and the turn continues without Zep memory.

create_after_model_callback

A factory function that returns an after_model_callback for persisting assistant responses to Zep. This ensures both sides of the conversation are stored in Zep's memory. The callback also resolves the thread UUID from session state at runtime.

Both ZepContextTool and create_after_model_callback include per-thread message deduplication to handle ADK's tool-use cycles, where the framework may call hooks multiple times per turn.

ADK-native memory service

ZepMemoryService implements ADK's own BaseMemoryService extension point, so Zep can back ADK's built-in load_memory/preload_memory tools. Register it on the Runner.

ADK gives search_memory only the ADK app_name and user_id, and the ADK user_id is your own identifier, not a Zep UUID. Give the service a resolve_user_uuid callable that returns the Zep user UUID that you stored for that user. This example continues the Quick Start, so zep, session_service, and zep_user_uuids are already defined:

from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.tools import load_memory
from zep_adk import ZepMemoryService


def resolve_user_uuid(app_name: str, user_id: str) -> str | None:
    # Read the Zep user UUID that you stored when you called create_user.
    return zep_user_uuids.get(user_id)


agent = Agent(
    name="my_agent",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant. Use load_memory to recall prior context when relevant.",
    tools=[load_memory],
)

runner = Runner(
    agent=agent,
    app_name="my_app",
    session_service=session_service,
    memory_service=ZepMemoryService(
        zep=zep,
        scope="edges",
        resolve_user_uuid=resolve_user_uuid,
    ),
)

resolve_user_uuid can also be an async function, for example one that reads your database. The constructor raises ValueError when you give neither resolve_user_uuid nor graph_uuid. When resolve_user_uuid returns None or a value that is not a UUID, the service logs an error and returns no memories.

When the model calls load_memory, ADK invokes ZepMemoryService.search_memory(app_name=..., user_id=..., query=...). The service calls resolve_user_uuid(app_name, user_id) to get the Zep user UUID, resolves the graph UUID of that user, and runs the v4 search method for the configured scope (for example zep.graph.search_edges(graph_uuid, query=query, page_size=limit)) and maps each result into a MemoryEntry (the same result shapes -- edges, nodes, episodes, observations, thread_summaries, auto -- as ZepGraphSearchTool). A Zep failure is logged (as a warning, with lengths/counts only) and returns an empty result rather than raising into the agent.

add_session_to_memory is intentionally a no-op: Zep already ingests each turn live via ZepContextTool (or thread.add_messages) and create_after_model_callback, so re-ingesting the full session here would persist the same conversation into the graph twice.

When to use this vs. ZepContextTool: ZepMemoryService is ADK-native and model-opt-in -- the model decides, per turn, whether to call load_memory. ZepContextTool guarantees injection -- it runs on every LLM turn regardless of what the model decides. The two are complementary: keep ZepContextTool for always-on context, and add ZepMemoryService when you also want the model to be able to explicitly search for more via ADK's own memory tools, or when integrating with ADK code paths that expect a memory_service (e.g. evaluation harnesses).

Adding Zep to an Existing Agent

If you already have an ADK agent serving all users, adding Zep memory requires only a few changes -- no restructuring needed:

  1. Add ZepContextTool to your agent's tools list:
from zep_adk import ZepContextTool

agent = Agent(
    name="my_existing_agent",
    model="gemini-2.5-flash",
    instruction="...",
    tools=[your_existing_tool, ZepContextTool(zep_client=zep)],
    after_model_callback=create_after_model_callback(zep_client=zep),
)
  1. Provision the Zep user and thread out-of-band in your app/onboarding code, before the first turn:
from zep_adk import create_user, create_thread

user = await create_user(zep, first_name=first_name, last_name=last_name)
thread = await create_thread(zep, user_uuid=user.uuid_)
  1. Include the Zep UUIDs and the user's name in session state when creating sessions (you're already creating sessions -- just add the keys):
await session_service.create_session(
    app_name="my_app",
    user_id=user_id,
    session_id=session_id,
    state={
        **your_existing_state,
        "zep_user_uuid": user.uuid_,
        "zep_thread_uuid": thread.uuid_,
        "zep_graph_uuid": user.graph_uuid,
        "zep_first_name": first_name,
        "zep_last_name": last_name,
    },
)
  1. That's it. No factory function, no per-session agent instances.

Migrating from Zep V3

Follow the V4 migration guide for the general procedure: the nullable UUID columns, the one-time lookup backfill, and the order of the steps.

The changes that are specific to this package are:

  • The package requires zep-cloud>=4.0.0,<5. There is no V3 compatibility layer.
  • ensure_user is replaced by create_user(client, *, first_name=None, last_name=None, email=None). The user_id parameter is removed. The function returns the User; store user.uuid_ and user.graph_uuid.
  • ensure_thread is replaced by create_thread(client, *, user_uuid). The thread_id and user_id parameters are removed. The function returns the Thread; store thread.uuid_.
  • The on_created parameter of ensure_user and the UserSetupHook type are removed. Run one-time user setup after create_user returns.
  • The session-state key zep_user_id is now zep_user_uuid, and zep_thread_id is now zep_thread_uuid. The new optional key zep_graph_uuid takes the stored user.graph_uuid. ZepContextTool, create_after_model_callback, and ZepGraphSearchTool read these keys. They use the ADK user_id and session_id only when no key is set, so those values must then be the Zep UUIDs.
  • ContextInput.user_id is now ContextInput.user_uuid, and ContextInput.thread_id is now ContextInput.thread_uuid. A custom context_builder must call the V4 SDK, for example ctx.zep.graph.search_edges(graph_uuid, query=ctx.user_message) in place of ctx.zep.graph.search(user_id=ctx.user_id, ...).
  • The graph_id parameter of ZepGraphSearchTool is now graph_uuid.
  • ZepMemoryService now requires the new resolve_user_uuid parameter, which maps the ADK app_name and user_id to the stored Zep user UUID, or the new graph_uuid parameter, which gives a fixed graph UUID.

For an existing V3 user or thread, do not call create_user or create_thread. Get its UUID one time with the backfill in the migration guide, and store it.

Changes in 0.3.0

  • Injected context is wrapped via a configurable template. Pass context_template to ZepContextTool to override the wrapper around retrieved context (default: DEFAULT_CONTEXT_TEMPLATE, which still uses <ZEP_CONTEXT> tags):

    from zep_adk import ZepContextTool, DEFAULT_CONTEXT_TEMPLATE
    
    tool = ZepContextTool(
        zep_client=zep,
        context_template="Relevant memory:\n{context}",
    )
    

    The template is rendered via plain string replacement (template.replace("{context}", context_text)), never str.format -- so context text or templates containing {, }, or % are always safe to inject.

  • The default injected context wording changed in 0.3.0. Pre-0.3.0 hardcoded:

    The following context is retrieved from Zep's long-term memory service. It contains relevant facts, relationships, and prior knowledge about the user. Use it to inform your responses.
    

    DEFAULT_CONTEXT_TEMPLATE (0.3.0+) now reads:

    The following context is retrieved from Zep, the agent's long-term memory. It contains relevant facts, entities, and prior knowledge about the user. Use it to inform your responses.
    

    The <ZEP_CONTEXT>...</ZEP_CONTEXT> wrapper is unchanged. If you depend on the exact previous wording, pass it explicitly as context_template. This wording is canonical across zep-adk's Python, Go, and TypeScript implementations.

Features

  • Shared-agent architecture -- one Agent definition serves all users
  • Session-state-driven identity -- per-user configuration via ADK's standard mechanism
  • Single round-trip -- persist messages and retrieve context in one API call
  • Explicit out-of-band provisioning -- create_user/create_thread provision Zep resources before the first turn and return the server-generated UUIDs
  • Per-thread deduplication -- prevents double-persistence during tool-use cycles
  • Graceful error handling -- Zep API failures are logged but never crash the agent
  • Context injection -- Zep's knowledge graph context is injected as system instructions
  • On-demand graph search -- ZepGraphSearchTool lets the model actively search the knowledge graph
  • Configurable graph search -- pin parameters at construction or let the model choose
  • ADK-native memory service -- ZepMemoryService implements BaseMemoryService so Zep backs ADK's own load_memory/preload_memory tools

Configuration

Environment Variables

# Required
export ZEP_API_KEY="your-zep-api-key"
export GOOGLE_API_KEY="your-google-api-key"

Constructor Parameters

create_user

Parameter Type Required Default Description
client AsyncZep Yes -- Initialised Zep async client
first_name str No None User's first name
last_name str No None User's last name
email str No None User's email

Returns the created User. Read user.uuid_ and user.graph_uuid and store them. Raises on failures (auth, network, 5xx).

create_thread

Parameter Type Required Default Description
client AsyncZep Yes -- Initialised Zep async client
user_uuid str Yes -- The UUID of the Zep user that owns the thread

Returns the created Thread. Read thread.uuid_ and store it. Raises on failures (auth, network, 5xx).

ZepContextTool

Parameter Type Required Default Description
zep_client AsyncZep Yes -- Initialised Zep async client
context_builder ContextBuilder No None Custom async callable (receives a ContextInput) for context retrieval
context_template str No DEFAULT_CONTEXT_TEMPLATE Template wrapping retrieved context; must contain a literal {context} placeholder
ignore_roles list[str] No None Roles to exclude from graph ingestion

create_after_model_callback

Parameter Type Required Default Description
zep_client AsyncZep Yes -- Initialised Zep async client
assistant_name str No "Assistant" Display name for the assistant in Zep
ignore_roles list[str] No None Roles to exclude from graph ingestion

ZepGraphSearchTool

Parameter Type Required Default Description
zep_client AsyncZep Yes -- Initialised Zep async client
graph_uuid str No None Fixed graph UUID for shared-graph search
name str No "zep_graph_search" Tool name visible to the model
description str No (default) Tool description visible to the model
search_filters dict No None Zep search filters (constructor-only)
bfs_origin_node_uuids list[str] No None BFS seed node UUIDs (constructor-only)
**pinned any No -- Pin any search param: scope, reranker, limit, mmr_lambda, center_node_uuid

Every search parameter is tri-state, set via **pinned:

State How to set it Effect
Pinned pass a concrete value, e.g. scope="edges" Hidden from the model's tool schema. Always used, even if the model would have chosen differently.
Hidden pass None, e.g. mmr_lambda=None Hidden from the model's tool schema AND omitted from the search call entirely. Only optional parameters can be hidden this way.
Exposed (default) omit the kwarg entirely Included in the model's tool schema with the default below, so the model chooses a value per call.

scope accepts:

Scope Result
edges (default) facts and relationships
nodes entities and their summaries
episodes raw text data (unstructured text, messages, or JSON)
observations derived memories
thread_summaries incremental thread summaries
auto Zep's own pre-assembled mix of results

ZepMemoryService

Parameter Type Required Default Description
zep AsyncZep Yes -- Initialised Zep async client
scope str No "edges" Graph search scope used by search_memory. Same enum as ZepGraphSearchTool: edges, nodes, episodes, observations, thread_summaries, auto
limit int No None Maximum results per search. None omits the parameter so the Zep SDK applies its own default
resolve_user_uuid Callable[[str, str], str | None] (sync or async) Yes, unless graph_uuid is set None Receives the ADK app_name and user_id, and returns the stored Zep user UUID. The ADK user_id is never used as a Zep UUID
graph_uuid str No None A fixed graph UUID. When set, every search targets that graph, and resolve_user_uuid is not called

Examples

See the examples/ directory for complete working examples:

  • basic_agent.py -- Full example with fact seeding and memory recall using the shared-agent pattern

Development

Setup

git clone https://github.com/getzep/zep.git
cd integrations/adk/python
make install

Commands

make format      # Format code with ruff
make lint        # Run linting checks
make type-check  # Run mypy type checking
make test        # Run test suite
make all         # Run all checks
make pre-commit  # Development workflow with auto-fixes
make ci          # Strict CI checks

Requirements

  • Python 3.11+
  • zep-cloud>=4.0.0,<5
  • google-adk>=1.19.0,<3

Support

License

Apache 2.0 - see LICENSE for details.

Contributing

Contributions are welcome! Please see our Contributing Guide for details.

Metadata

Release files for zep-adk 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zep-adk 1.0.0
File Size Uploaded
zep_adk-1.0.0.tar.gz 55.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zep-adk 1.0.0
File Interpreter ABI Platform
zep_adk-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.1 kB

Release files / zep_adk-1.0.0.tar.gz

Download URL zep_adk-1.0.0.tar.gz
Size 55.2 kB
Tags Source
SHA-256 checksum
How to use checksums
8f5171e749b4266a2d36a72f4fcc5bdec8de7bc3fafba82adcf20424aa7da0e6
BLAKE2b-256 checksum
How to use checksums
9a489df00c6070b67cc8292082450b611e494483c1e9e6222df4003a2770152f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / zep_adk-1.0.0-py3-none-any.whl

Download URL zep_adk-1.0.0-py3-none-any.whl
Size 31.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
72722ae6f7c8beca23d0412745f7ecb38e87625aa1c7b1b93d86ac7431b9c332
BLAKE2b-256 checksum
How to use checksums
65c3ce5f14b57575f0767afec13bb6ccd903a4a99606177ec1ddc688a92ba98f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page