Use with Google ADK
Connect Neo4jMemoryService to an ADK Runner, consume one complete run, persist its session, and verify the user’s exact text. This recipe selects Bolt with OpenAI embeddings and a Gemini model through the Google AI API.
1. Prepare the selected environment
Use Python 3.10+ and a POSIX shell. The recipe uses BoltSettings to select Neo4j explicitly and disables entity extraction so that message persistence can be checked independently. Use a dedicated AuraDB instance with vector-index support and no incompatible existing vectors. Follow the Aura connection setup and copy its connection values into the exports below. These recipes use the published Python 0.7.0 package; provider access and database execution must be verified in your environment.
Create a local folder and virtual environment for the examples on this page. The commands reuse an existing environment without changing its files:
mkdir -p ~/agent-memory-tutorials
cd ~/agent-memory-tutorials
if [ -e .venv ]; then
printf '%s\n' 'Using the existing virtual environment.'
else
python3 -m venv .venv
fi
source .venv/bin/activate
Expected: ~/agent-memory-tutorials is your working directory and its virtual environment is active. Install the published SDK with the command below.
Each complete code block labelled Save as names a file to create in this folder using your editor. Copy the entire block, including imports and the entry point. Expand each helper disclosure and use Copy code to copy its full source. Keep all files together so their imports resolve.
When continuing from another tutorial or guide, retain the existing environment, configuration, session files, and .tutorial-state/. Reuse unchanged helper files; compare an existing file before replacing it, and finish any pending cleanup or recovery before changing the code that owns its state.
python -m pip install 'neo4j-agent-memory[google-adk,openai]==0.7.0'
export NEO4J_URI='neo4j+s://<instance-id>.databases.neo4j.io'
export NEO4J_USERNAME='neo4j'
export NEO4J_PASSWORD='replace-with-your-Aura-password'
export NEO4J_DATABASE='neo4j'
export OPENAI_API_KEY='replace-with-your-provider-key'
export GOOGLE_API_KEY='replace-with-your-Google-AI-key'
unset GOOGLE_GENAI_USE_VERTEXAI
export ADK_MODEL='replace-with-an-available-Gemini-model-id'
The OpenAI-backed recipes require access to text-embedding-3-small; it produces 1536-dimensional vectors in this configuration. Configure the selected chat model separately where required. Credentials are read from the environment, never printed by the programs.
2. Read the complete recipe
Create the subdirectory below, then save both complete files under the displayed names. Run commands from agent-memory-tutorials/; Python resolves common from the script’s integrations/ directory.
mkdir -p integrations
The script imports this shared helper from the same directory. It supplies explicit database settings, closes clients through the calling context manager, and fails when expected records are absent. Task traces record the observable outcome of the call; they do not expose hidden model reasoning.
Complete integrations/common.py
integrations/common.py"""Shared Aura connection through Bolt and readback for integration recipes."""
import os
def settings(embedding=None):
from neo4j_agent_memory import BoltSettings
from neo4j_agent_memory.llm import from_provider
return BoltSettings(
neo4j={
"uri": os.environ["NEO4J_URI"],
"username": os.environ["NEO4J_USERNAME"],
"password": os.environ["NEO4J_PASSWORD"],
"database": os.getenv("NEO4J_DATABASE", "neo4j"),
},
embedding=embedding or from_provider("openai/text-embedding-3-small", kind="embedding"),
extraction={"extractor_type": "none"},
)
async def verify_messages(client, session_id, expected):
conversation = await client.short_term.get_conversation(session_id)
contents = [message.content for message in conversation.messages]
if not all(text in contents for text in expected):
raise RuntimeError(f"Message readback failed for {session_id}")
print(f"Verified stored messages; session={session_id}")
async def recorded_turn(client, session_id, prompt, respond):
"""Record a task outcome; this does not claim to capture hidden model reasoning."""
trace = await client.reasoning.start_trace(session_id=session_id, task=prompt)
try:
await client.short_term.add_message(session_id, "user", prompt, extract_entities=False)
context = await client.get_context(prompt, session_id=session_id)
reply = str(await respond(context))
if not reply.strip():
raise RuntimeError("The framework returned no text")
await client.short_term.add_message(session_id, "assistant", reply, extract_entities=False)
await verify_messages(client, session_id, [prompt, reply])
except Exception as exc:
await client.reasoning.complete_trace(
trace.id, success=False, outcome=f"Run failed: {type(exc).__name__}"
)
raise
await client.reasoning.complete_trace(
trace.id, success=True, outcome="Reply stored and read back"
)
stored = await client.reasoning.get_trace_with_steps(trace.id)
if stored is None or stored.success is not True:
raise RuntimeError(f"Trace readback failed for {trace.id}")
print(f"Verified task trace: {trace.id}")
return reply
Complete integrations/google_adk_recipe.py
integrations/google_adk_recipe.py"""Consume the ADK Runner stream, persist its session, then verify readback."""
import asyncio
import os
from common import settings, verify_messages
async def exercise(client):
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import load_memory
from google.genai import types
from neo4j_agent_memory.integrations.google_adk import Neo4jMemoryService
app_name, user_id = "docs-memory", "fictional-user"
service = Neo4jMemoryService(client, user_id=user_id, extract_on_store=False)
sessions = InMemorySessionService()
agent = LlmAgent(
name="checklist_agent",
model=os.environ["ADK_MODEL"],
instruction="Answer briefly. Use load_memory when prior context is needed.",
tools=[load_memory],
)
runner = Runner(
app_name=app_name, agent=agent, session_service=sessions, memory_service=service
)
session = await sessions.create_session(app_name=app_name, user_id=user_id)
prompt = "The fictional project code is cedar-lantern-47. Acknowledge it briefly."
async for event in runner.run_async(
user_id=user_id,
session_id=session.id,
new_message=types.Content(role="user", parts=[types.Part(text=prompt)]),
):
if event.error_code:
raise RuntimeError(f"ADK run failed: {event.error_code}")
if event.is_final_response() and event.content and event.content.parts:
print("".join(part.text or "" for part in event.content.parts))
stored_session = await sessions.get_session(
app_name=app_name, user_id=user_id, session_id=session.id
)
if stored_session is None:
raise RuntimeError("ADK session is missing after the run")
await service.add_session_to_memory(stored_session)
await verify_messages(client, session.id, [prompt])
response = await service.search_memory(app_name=app_name, user_id=user_id, query="project code")
print(f"ADK SearchMemoryResponse: {len(response.memories)} candidate(s)")
return session.id
async def main():
from neo4j_agent_memory import MemoryClient
async with MemoryClient(settings()) as client:
await exercise(client)
if __name__ == "__main__":
asyncio.run(main())
3. Run and verify persistence
python integrations/google_adk_recipe.py
Expected: a final response, Verified stored messages, and an ADK search candidate count. run_async() is an async generator and must be iterated; session creation and lookup must be awaited. An error event or exception stops the run. The sample persists the session once after completion; repeatedly ingesting the full session can duplicate messages.
Troubleshooting and cleanup
Stop when the program raises an exception; a printed model response alone does not verify persistence. Check the configured database, provider access and vector dimensions before retrying. The program prints the session or trace IDs needed for inspection and closes the client. Retained example records remain in the dedicated database; follow the Aura tutorial’s cleanup procedure for that dedicated instance only when you no longer need them. Reusing a session groups records; it does not authorize a user to read them.
Adapt the integration
Memory is a Runner-level service: Runner(memory_service=…). LlmAgent receives load_memory (or a deliberately selected preload_memory tool), not a memory= argument or an invented @agent.tool decorator.
search_memory(app_name=…, user_id=…, query=…) returns SearchMemoryResponse; iterate .memories. Each ADK entry carries content.parts, author, timestamp and custom_metadata. The convenience get_memories_for_session(session_id) returns the library’s own entries with string content, so do not mix the two result shapes.
add_session_to_memory(session) stores text-bearing events. add_events_to_memory(app_name=…, user_id=…, events=…, session_id=…) supports incremental ingestion. The adapter preserves the agent author in metadata and omits tool-only events; it does not turn tool JSON into extracted facts or automatically record a full reasoning trace.
For NAMS, message search requires a real conversation ID. The service tracks the last session written and uses it for search, or accepts an explicit session_id. Without one it skips message search and can still search workspace entities/preferences. This is not cross-conversation message search, and Bolt’s unscoped semantic message search is not user authorization. The selected complete program above is Bolt; follow the hosted guide before adapting it.
See Vertex AI embeddings and Google Cloud deployment for the distinct cloud credentials and infrastructure path.
See Backend capabilities and scoping before adding hosted or multi-user behavior. Neo4j Agent Memory is a Neo4j Labs project with community support.