In the previous lesson, you learned the reasoning memory schema — how ReasoningTrace, ReasoningStep, and ToolCall nodes connect, and how cross-memory relationships tie all three layers together in a single graph. Now you will use the Pydantic AI integration to record those traces automatically.
In this lesson, you will learn how to use create_memory_tools() and record_agent_trace() to record a complete reasoning trace with minimal code.
Using the Pydantic AI integration
create_memory_tools() returns 3 pre-built agent tools that give a Pydantic AI agent access to memory search, preference saving, and preference recall. The agent can call these tools to store and retrieve memory as part of its normal reasoning process.
import os
from neo4j_agent_memory import MemoryClient, MemorySettings
from neo4j_agent_memory.config import Neo4jConfig, EmbeddingConfig
from neo4j_agent_memory.integrations.pydantic_ai import create_memory_tools
from pydantic_ai import Agent
async with MemoryClient(settings) as memory:
# Returns 3 ready-to-use agent tools: search_memory, save_preference, recall_preferences
tools = create_memory_tools(memory)
agent = Agent(
model="openai:gpt-4o-mini",
system_prompt="""You are a helpful assistant with persistent memory.
Use memory tools to recall past conversations and preferences.""",
tools=tools
)This creates a Pydantic AI agent with the 3 memory tools registered. When the agent runs, it can call search_memory, save_preference, and recall_preferences to interact with the memory graph.
Recording a trace from an agent run
After the agent runs, pass the result to record_agent_trace() to write the full trace to Neo4j. record_agent_trace() takes the ReasoningMemory instance, a session ID, and the completed RunResult:
from neo4j_agent_memory.integrations.pydantic_ai import create_memory_tools, record_agent_trace
async with MemoryClient(settings) as memory:
tools = create_memory_tools(memory)
agent = Agent(model="openai:gpt-4o-mini", tools=tools)
# Run the agent
result = await agent.run("What can you tell me about Jessica Norris?")
print(result.data)
# Record the trace from the completed run
trace = await record_agent_trace(
memory.reasoning,
"user_123",
result,
task="Answer user question"
)record_agent_trace() inspects the completed RunResult, extracts every tool call and its result, and writes ReasoningTrace, ReasoningStep, and ToolCall nodes to Neo4j in a single pass. It returns the completed ReasoningTrace object.
Understanding what gets written to Neo4j
After calling record_agent_trace(), the following reasoning nodes will exist in your database:
-
One
ReasoningTracenode for the agent run -
One
ReasoningStepper reasoning iteration -
One
ToolCallper tool the agent invoked, with full arguments and results
If your code also calls memory.short_term.add_message() to store the conversation — for example, before and after the agent run — you will additionally have Conversation and Message nodes linked to the trace. record_agent_trace() itself only writes the reasoning layer.
Using the low-level reasoning API
record_agent_trace() is a convenience wrapper around the four low-level methods. Use the low-level API when you need manual control — for example, when integrating with a framework other than Pydantic AI, or when you want to record a trace for a non-agent workflow:
from neo4j_agent_memory import MemoryClient, MemorySettings
from neo4j_agent_memory.config import Neo4jConfig
async with MemoryClient(settings) as memory:
# 1. Start a trace — creates the ReasoningTrace node
trace = await memory.reasoning.start_trace(
task="Evaluate credit limit for Jessica Norris",
session_id="user_123"
)
# 2. Record a reasoning step
step = await memory.reasoning.add_step(
trace_id=trace.id,
thought="Retrieving customer entity from long-term memory",
action="search_entities"
)
# 3. Record a tool call within the step
await memory.reasoning.record_tool_call(
step_id=step.id,
tool_name="search_entities",
arguments={"query": "Jessica Norris", "limit": 5},
result={"entities": ["Jessica Norris (EntityPerson)"]},
status="success"
)
# 4. Complete the trace with an outcome
await memory.reasoning.complete_trace(
trace.id,
outcome="Approved — risk score within threshold",
success=True
)start_trace() returns a trace object whose id you pass to subsequent calls. record_agent_trace() calls all four of these automatically, wrapping your agent run so every tool call becomes a ToolCall node without any extra code.
Check your understanding
Starting a Reasoning Trace
Which neo4j-agent-memory method starts recording a new reasoning trace?
-
❏
memory.short_term.add_message() -
❏
memory.reasoning.record_step() -
✓
memory.reasoning.start_trace() -
❏
memory.long_term.add_entity()
Hint
Reasoning traces are recorded using the memory.reasoning.* namespace. A trace must be started before steps can be recorded inside it.
Solution
The correct answer is memory.reasoning.start_trace().
memory.reasoning.start_trace() creates the ReasoningTrace node in Neo4j and returns a trace object whose ID you pass to subsequent calls like add_step() and complete_trace(). memory.reasoning.record_step() does not exist — the correct method name is add_step(). memory.long_term.add_entity() stores long-term memory entities and has no effect on reasoning traces.
Summary
In this lesson, you learned the key integration components:
-
create_memory_tools(memory)— returns 3 Pydantic AI tools (search_memory,save_preference,recall_preferences) that give the agent access to the memory graph -
record_agent_trace(memory.reasoning, session_id, result)— inspects a completedRunResultand writes the full trace (ReasoningTrace,ReasoningStep,ToolCallnodes) to Neo4j -
Single agent run — after one run, all three memory layers are populated and cross-linked in Neo4j
In the next lesson, you will query the reasoning trace to understand what the agent did and why.