Use relate with LangChain
Give a LangChain agent a recall tool that reads an agent's memory from a relate store on a read-only connection, checked before any model call.
pythonagents
Before you start: Embed relate in a Python app
This guide shows you how to give a LangChain agent a tool that recalls what an agent's memory holds, from a relate store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary, without letting the agent change it. Your app writes the memory; the agent gets one recall tool that opens the store read-only An open mode (--read-only) that never writes, checkpoints or migrates the store; writes fail, and a directory store can be read beside its writer. Read more in the glossary on every call and follows the graph from a name to the episodes that mention it and the people who work on it. The tool runs without a model, so you can check what the agent gets before it makes a call.
Prerequisites
- Python 3.10 or later, with the relate client A language package that runs the relate engine inside a program's process: the Python, Node.js, Rust, Java, Go and Swift clients. Read more in the glossary and LangChain:
pip install relate-db langchain. The package isrelate-db; the import isimport relate. The LangChain code on this page was checked againstlangchain1.4.9 andlangchain-core1.7.6, as of 2026-11-08. - For Give the tool to an agent only: a chat model's integration package and credential, such as
pip install "langchain[anthropic]"withANTHROPIC_API_KEYin the environment. - relate on your
PATHfor Verify (Install relate).
How this differs from the LangGraph guide
Use relate with LangGraph builds a StateGraph by hand, whose steps write and recall memory. This page hands a read-only tool to LangChain's create_agent, which builds the agent loop itself:
| This page | The LangGraph guide | |
|---|---|---|
| Who writes memory | Your app, on its own writable connection | The graph's remember tool |
| What the agent can do | Read, through recall | Write and read, through remember and recall |
| The connection | A new read-only one per call | One writable connection on a dedicated thread |
| The agent loop | create_agent | A StateGraph with two nodes |
Both store memory the same way: each turn is an Episode node An entity in the graph: it carries zero or more labels and a map of properties, and relationships connect it to other nodes. Read more in the glossary IN_CONVERSATION a Conversation, and an episode MENTIONS the Person and Project nodes of the team graph the Get started pages use. Model agent memory explains the model this one reduces.
Write the tool
The program opens ./memory, a directory store A store kept as a folder of snapshot files, a WAL folder and lock files; the layout for any --database path that is not :memory: and does not end in .rl. Read more in the glossary, for writing in a setup block that stands in for the app: it writes the team graph and two episodes from two stand-ups. The recall tool opens its own read-only connection on each call. The last lines print the tool as the model sees it and call it twice, the way the agent does, with a name that exists and one that doesn't:
import relate
from langchain.tools import ToolException, tool
TEAM = """
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
(grace:Person {name: 'Grace', role: 'engineer'}),
(noor:Person {name: 'Noor', role: 'maintainer'}),
(relate:Project {name: 'relate', language: 'Rust'}),
(tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
(ada)-[:WORKS_ON {since: 2024}]->(relate),
(grace)-[:WORKS_ON {since: 2023}]->(relate),
(grace)-[:WORKS_ON {since: 2022}]->(tck),
(noor)-[:MAINTAINS]->(relate),
(ada)-[:KNOWS]->(grace)
"""
REMEMBER = """
MERGE (c:Conversation {id: $conversation})
CREATE (e:Episode {seq: $seq, text: $text})-[:IN_CONVERSATION]->(c)
WITH e
UNWIND $mentions AS name
MATCH (x {name: name})
MERGE (e)-[:MENTIONS]->(x)
"""
STORE = "./memory"
# Setup: the app that owns ./memory writes the team graph and two episodes, on a writable connection.
with relate.connect(STORE) as owner:
if owner.execute("MATCH (p:Person) RETURN count(p) AS n").single()["n"] == 0:
owner.execute(TEAM)
owner.execute(REMEMBER, {"conversation": "standup-monday", "seq": 1, "text": "Ada fixed the CSV import bug in relate.", "mentions": ["Ada", "relate"]})
owner.execute(REMEMBER, {"conversation": "standup-tuesday", "seq": 2, "text": "Grace will review the relate release notes.", "mentions": ["Grace", "relate"]})
@tool(parse_docstring=True)
def recall(name: str) -> list[str]:
"""Return what memory holds about a person or project: the episodes that mention it, oldest first, then who works on it.
Args:
name: The exact name of a person or project, such as "Ada" or "relate".
"""
# Read-only: the engine refuses every write on this connection.
with relate.connect(STORE, read_only=True) as conn:
if conn.execute("MATCH (x {name: $name}) RETURN count(x) AS n", {"name": name}).single()["n"] == 0:
raise ToolException(f"Nothing in memory is named {name!r}.")
episodes = conn.execute(
"""
MATCH (x {name: $name})<-[:MENTIONS]-(e:Episode)-[:IN_CONVERSATION]->(c:Conversation)
RETURN c.id AS conversation, e.text AS text
ORDER BY e.seq
""",
{"name": name},
).fetchall()
people = conn.execute(
"""
MATCH (x {name: $name})<-[w:WORKS_ON]-(p:Person)
RETURN p.name AS person, w.since AS since
ORDER BY since
""",
{"name": name},
).fetchall()
return [f"[{r['conversation']}] {r['text']}" for r in episodes] + [
f"{r['person']} has worked on {name} since {r['since']}" for r in people
]
# A ToolException goes back to the model as the tool's answer instead of ending the run.
recall.handle_tool_error = True
# Check: what the model sees, then two calls the way an agent makes them.
print(recall.name, recall.args)
for line in recall.invoke({"name": "relate"}):
print(line)
print(recall.invoke({"name": "relate-engine"}))recall {'name': {'description': 'The exact name of a person or project, such as "Ada" or "relate".', 'title': 'Name', 'type': 'string'}}
[standup-monday] Ada fixed the CSV import bug in relate.
[standup-tuesday] Grace will review the relate release notes.
Grace has worked on relate since 2023
Ada has worked on relate since 2024
Nothing in memory is named 'relate-engine'.The first call returns both episodes about relate, oldest first, and then two lines that no episode stated: they come from the team graph's WORKS_ON relationships. The second call returns the ToolException message as the tool's answer. The parts that matter:
- A connection per call.
create_agentruns a synchronous tool on a worker thread, and a relate connection belongs to the thread that opened it. Opening inside the tool keeps the open and the statements on one thread, and each call sees every episode the app committed before it. parse_docstring=Truetakes thenameargument's description from the docstring'sArgs:section, so the model gets it in the tool's schema; the first line printed shows it.handle_tool_error = Trueturns aToolExceptioninto the tool's answer, so a misspelled name gets a sentence the model can act on instead of ending the run.- Every value is a
$parameter. The model chooses onlyname; the Cypher is fixed, and the read-only connection refuses a write even if a statement tried one.
Give the tool to an agent
create_agent takes a model name and the tools. Replace the check lines at the end of recall_tool.py with this; it needs the model's credential:
from langchain.agents import create_agent
agent = create_agent(
"anthropic:claude-opus-5-5",
tools=[recall],
system_prompt="Answer from memory. Call recall with the exact name of a person or project.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "What happened on relate this week?"}]})
print(result["messages"][-1].content)The model name is <provider>:<model>, and the provider's package must be installed. The model can call recall, read the lines it returns, and answer from them.
Verify
The tool's output already shows what it read. To see the links it followed, ask the store from the CLI, read-only, in the directory the program ran in:
relate --database ./memory --read-only --mode table "MATCH (e:Episode)-[:MENTIONS]->(x) RETURN e.seq AS seq, x.name AS mentions ORDER BY seq, mentions"+-----+----------+
| seq | mentions |
+-----+----------+
| 1 | "Ada" |
| 1 | "relate" |
| 2 | "Grace" |
| 2 | "relate" |
+-----+----------+Each episode links to the person and the project it names, which is the path recall follows from relate back to both episodes.
If it goes wrong
| Message | Cause | Remedy |
|---|---|---|
Relate connection used from a thread other than the one that opened it: connections are thread-affine (DB-API threadsafety = 1). Create the connection on the thread that uses it. (relate.ProgrammingError) | The tool used a connection opened outside it, such as one at module level, from the worker thread create_agent runs it on | Open the connection inside the tool, as recall does. |
storage error: read-only open requires an existing database at <path> (relate.OperationalError) | STORE names a directory that holds no store; a read-only open never creates one | Create the store with a writable open first, as the setup block does, or fix the path. |
storage locked: <path> (relate.OperationalError) | The store is a single-file .rl store that the app holds for writing, and a read-only open refuses it | Keep memory in a directory store, as ./memory is, so the tool reads beside the app. |
Initializing ChatAnthropic requires the langchain-anthropic package. Please install it with `pip install langchain-anthropic` (ImportError) | create_agent got an anthropic: model name without the provider's package | Install langchain[anthropic], or the package for the provider you name. |
The errors reference lists the Python exception class for each engine error.
Related
- Use relate with LangGraph: tools that write memory, inside a graph you build.
- Model agent memory: the labels and relationships for what an agent remembers.
- Agent memory: a fuller memory graph, with facts that supersede older ones.
- Agent memory, the problem: what relate offers for memory that outlives a session.
- Embed relate in a Python app: the client's connections, parameters, transactions, and errors.