Give your agent persistent memory with MCP in five minutes
11 October 2026 · Semurg · 7 min read · Markdown
Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)
Agents forget everything when a session ends. This guide gives yours a memory that outlives the session: a Semurg node on your own machine, seven memory tools over MCP, and the same calls over plain HTTP. There is no account and nothing is uploaded; the memory lives on your disk. By the end, an agent in a new session finds a fact you stored over plain HTTP.
What you will have
- A node on your own machine, answering on
http://localhost:4000. - Seven memory tools:
memory_status,memory_remember,memory_recall,memory_relate,memory_forget,memory_contextandmemory_document, which stores a whole document, PDF, Word, HTML or text, as passages in one call. - Scopes. A scope is a named, separate memory. Agents that use the same scope share what they learn; a different scope stays private.
Step one: install a node
Ubuntu 24.04 on x86_64, as root:
curl -fsSL https://one.semurg.io/install | sudo bashThe installer checks the machine first and stops with a plain reason if something is missing; the install page lists what it needs and what it changes. To read the script before you run it: curl -fsSL https://one.semurg.io/install | less. In a container instead? The install page has the published image and how to verify it.
The installer ends with a SUCCESS banner that prints your API token. Keep it. It is also stored on the node, so you can read it back at any time with sudo grep '^SEMURG_API_TOKEN=' /etc/semurg/semurg.env.
Step two: check the memory over plain HTTP
Each block starts by putting your token in the shell; replace <your token> with yours, or, if you saved it in ~/.config/semurg/token as the Claude Code guide shows, run export SEMURG_API_TOKEN="$(cat ~/.config/semurg/token)" once and skip that line. First, ask the node whether memory is on:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/status \
-H "Authorization: Bearer $SEMURG_API_TOKEN"Expected: a JSON answer with "ok": true, saying whether memory is on and which scopes hold facts
Now store two entities and the relationship between them, in a scope of their own. This call lists both ends of the relationship, so it stands on its own; a relationship may also name an entity an earlier call stored.
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/remember \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope": "five-minutes",
"source": "five-minute guide",
"facts": {
"entities": [{"name": "Acme", "type": "Company"},
{"name": "Dana", "type": "Person"}],
"relations": [{"source": "Dana", "target": "Acme",
"type": "WORKS_AT"}]}}'Expected: a JSON answer with "ok": true
Storing the same facts again changes nothing, so it is safe to run twice. Now recall one of the entities by name. The answer is each fact connected to it, with its relation and the ids of the entities at both ends:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"five-minutes","subject":"Dana"}'Expected: "state": "answered", with the WORKS_AT fact that connects Dana to Acme
An agent usually has a question rather than a name. memory_context takes the question, starts from every stored name it mentions, walks the relationships around them and returns the ranked facts:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/context \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"five-minutes","query":"Where does Dana work?"}'Expected: "state": "answered", with the fact that Dana works at Acme
When nothing matches, the node says so instead of guessing. A name it has never stored answers with an empty list and a note saying what was tried:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"five-minutes","subject":"Nobody"}'Expected: "state": "nothing_stored" and no items: an empty answer that says it is empty
Read state before you use the items. That one habit keeps an agent from treating "nothing found" as a fact about the world.
Step three: connect your agent over MCP
Point any MCP client at http://127.0.0.1:4000/v1/mcp. It speaks streamable HTTP and takes your token as a bearer token. The optional X-Semurg-Scope header sets the default scope for the connection, so the agent's calls land in the scope you choose.
For Claude Code, one command connects every Claude Code session you start, in any project. The settings file holds only a reference to the environment variable, never the token, so set SEMURG_API_TOKEN in the shell that starts Claude Code (your shell profile, for example):
export SEMURG_API_TOKEN=<your token>
claude mcp add-json --scope user semurg '{
"type": "http",
"url": "http://127.0.0.1:4000/v1/mcp",
"headers": {
"Authorization": "Bearer ${SEMURG_API_TOKEN}",
"X-Semurg-Scope": "five-minutes"
}
}'
claude mcp listclaude mcp list should show the node as connected. In a new session, ask what the memory holds about Dana: the agent can call memory_recall in the scope you set and find the fact you stored over HTTP in step two. That is the point of the exercise. What one client writes, any other client on the same node and scope reads back.
Step four: the same memory from your framework
Agent frameworks reach the same seven tools as ordinary code on your side. The agents page carries a complete program for each framework, with the release it last ran on in our tests; each one stores two facts and reads them back.
LangChain and LangGraph, LlamaIndex, Google ADK, Mastra, the Vercel AI SDK and LangChain for Elixir connect through their own MCP clients, and in our tests each of those clients reconnected by itself when we restarted the node underneath it. That matters, because an update restarts the node. Jido calls the same memory over the HTTP routes from step two instead: every call is its own request, so there is no session to lose.
Reconnecting is not the same as giving up in time. If the node is up but stalled, a plain client waits as long as its own timeout allows, and in our tests some framework clients waited more than three minutes before they returned. Set the per-call timeout your framework offers, so an agent that cannot reach its memory says so quickly instead of hanging.
Four other clients, CrewAI, the Microsoft Agent Framework, the OpenAI Agents SDK and Pydantic AI, needed a new client after a restart.
What to expect on today's release
Said plainly, so you can plan around it:
- Recall and context find stored names by their spelling: exact, letter case, a similar spelling, one whole word of a longer name, or the start of a word. They also find by meaning once the node's embedding model is installed: a node that agrees to downloads installs it by itself when the signed catalogue lists it. Until then a remember reply says
meaning: not_armedand only names match; after, it saysmeaning: queued. - Raw text sent as
contentis stored at once and linked to the entities already stored (the reply saysextraction: known_names_only). Where an extraction model is installed and serving, the node also reads the text afterwards and adds the entities it finds, and the reply'smodel_extractionsays whether that is pending, done, or why it is idle. Sending the facts your agent has extracted, as in step two, is still the fast path. - A relationship may name an entity stored by an earlier call. If it names one that is neither in this call nor stored, the call is refused by name and nothing is stored, so a misspelt name never slips through.
- Each recalled item carries a citation: the source it came from, when it was stored and the exact span it quotes.
- Forgetting deletes one thing per call: an entity, one fact, a document, everything under a source label, or the whole scope. Forgetting an entity deletes it, its facts and the passages linked to it; a passage that contains the name without being linked to it (for example text stored before the entity was) stays, and the reply lists it in
passages_retained, so forget that document too if it must go. What is deleted is unreachable at once, and its bytes are wiped from the disk later, with space reclaim, which is not released yet. Forgetting the same entity again answers200withalready_forgotten: true; a name that was never stored answers404with a close spelling suggested.
Each of these is being worked on, and this post will be updated, with the release named, as each one changes.
Where to go next
- The agents page: configuration for more agents (Hermes Agent, OpenClaw, Cursor, the OpenAI Codex CLI, Gemini CLI) and every framework program.
- The develop page: the HTTP routes in full, the limits each call refuses by name, and a rules file that helps a coding agent build an app on the node.
- The install page: data disks, containers, updating and removing a node.
How these commands were checked
Our test farm ran each of the 5 command blocks marked runnable, exactly as written, with the answer checked against the expectation printed under it, against a fresh node of the published release r11.78.20261012.bc0d94f. A post is published only when every one of them passed there.