A support agent that remembers every customer: a blueprint
11 October 2026 · Semurg · 7 min read · Markdown
Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)
build-on-semurgcustomer-supportagentsmemoryblueprint
A customer writes in for the third time about the same problem, and the agent answering has never heard of them. The history exists, scattered across a ticket system, a chat log and an email thread, but nothing hands it to the agent at the moment it starts typing. This blueprint gives a support agent, human-assisted or fully automated, a memory of each customer that outlives the conversation and the channel.
Everything below uses only what the published release does.
The shape of the data
- One scope per support desk. A scope is a separate memory; one desk's customers are invisible to another's. Agents on the same desk share one memory, so what one agent learned yesterday is in front of the next one today.
- Entities: each customer, their company, each ticket, each product or feature they ask about.
- Relationships: a customer
WORKS_ATa company,OPENEDa ticket; a ticket isABOUTa feature. - Conversations are stored as text with their date. The node cuts the text into passages and links each one to the customers and features it names, so a later question about the customer brings back what was said, and when.
Step one: the customer and their ticket
When a ticket is opened, your helpdesk integration writes what it already knows as facts:
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":"support-desk","date":"2026-10-02","source":"helpdesk: new ticket",
"facts":{"entities":[{"name":"Priya Raman","type":"Customer"},{"name":"Northwind Retail","type":"Company"},
{"name":"Invoice export ticket","type":"Ticket"},
{"name":"Invoice export","type":"Feature"}],
"relations":[{"source":"Priya Raman","target":"Northwind Retail","type":"WORKS_AT"},
{"source":"Priya Raman","target":"Invoice export ticket","type":"OPENED"},
{"source":"Invoice export ticket","target":"Invoice export","type":"ABOUT"}]}}'Expected: a JSON answer with "ok": true
Step two: every conversation, as it ends
At the end of each conversation, send what was said as text, with its date. No model is needed for this: the node stores the text now, cuts it into passages and links each passage to every entity it already knows that the text names.
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":"support-desk","date":"2026-10-09","source":"chat transcript",
"content":"Priya Raman wrote again about the Invoice export. The CSV file still drops the currency column for orders in euros. She asked to be contacted by email rather than phone."}'Expected: a JSON answer with "ok": true; the reply also says how many passages no stored entity reaches
The reply tells you how many passages reached no stored entity. A passage that names nobody the desk knows is stored, but nothing will lead back to it, so write the customer and the topic as facts first, as in step one.
Step three: the history, before the agent answers
When Priya writes in again, the agent asks the memory first. Context takes the question, starts from every stored name in it, walks out from them and returns the facts and the conversations that mention them, each with its date:
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":"support-desk","query":"What has Priya Raman contacted us about?","hops":2}'Expected: "state": "answered", with the invoice export ticket and the conversation about the missing currency column
An agent often has only a first name. Recall finds an entity by one whole word of a longer name, so "Priya" reaches Priya Raman:
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":"support-desk","subject":"Priya"}'Expected: "state": "answered", with Priya Raman's company and her invoice export ticket
If two customers share a first name, recall can reach either of them: check the facts that come back before you rely on them, and use the full name when you have it.
The agent loop, in Python
The same two calls frame every conversation: read the history before the first reply, write the conversation when it ends. With the Python client from the client archive at https://one.semurg.io/dl/clients.tar.gz (unpack it, then pip install -e clients/python):
import datetime
import os
from semurg import Client
DESK = "support-desk"
def history_for(memory, customer, message):
# before the agent's first reply: what the desk already knows about this customer and this topic
answer = memory.context(DESK, customer + ": " + message, hops=2)
if answer.get("state") != "answered":
return []
return [(item.get("source_date"), item.get("text")) for item in answer.get("items", [])]
def conversation_ended(memory, transcript):
# after the conversation: store it with today's date; it links itself to the customers and topics it names
today = datetime.date.today().isoformat()
return memory.remember(DESK, content=transcript, date=today, source="chat transcript")
with Client("http://localhost:4000", token=os.environ["SEMURG_API_TOKEN"]) as memory:
for when, text in history_for(memory, "Priya Raman", "the export is still broken"):
print(when, text)Hand the agent the history as context in its prompt, and tell it to say so when the history is empty rather than guess.
Connecting an agent framework
If your support agent is built on an agent framework, it can call the memory itself over MCP, with the desk's scope in the X-Semurg-Scope header. The agents page has a complete program for each framework we test, with the release it last ran on.
When a customer asks to be forgotten
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.
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/forget \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"support-desk","subject":"Priya Raman","reason":"customer request"}'Be clear about what that is on today's release, because a support desk has obligations here:
- It is not yet erasure from the disk. The customer, their facts and the conversations linked to them are unreachable at once, but the deleted records stay on disk as dead records until space reclaim, which is not released yet; nothing is rewritten in place.
- A conversation that contains the name without being linked to the customer, for example one stored before you wrote the customer as a fact, is kept: the reply lists it in
passages_retained. Forget that document too if it must go. - The name must match exactly. A close spelling is refused with up to three suggestions and is never forgotten by accident.
- Forgetting the same customer again answers
200withalready_forgotten: true, and a later write that names the customer brings them back.
If your obligations require erasure of every copy, this release does not do that for you yet.
Scale and cost
A single node is free, with no licence key, and it is the full engine. A cluster is licensed per node; talk to us about the one you need. We have not measured this workload, so this post states no speed for it.
What it does not do today
- It does not find new names in a conversation. Text sent as content is linked to customers and topics the desk already knows; a new customer or topic has to be written as a fact first, unless an extraction model is installed and serving on the node, which then reads the stored text afterwards (the reply's
model_extractionsays which). How quickly it does that has not been measured from outside yet. - It matches meaning only with the node's embedding model installed: until then, a customer who writes about "the spreadsheet download" is not matched to "Invoice export". A node that agrees to downloads installs the model once the signed catalogue lists it.
- It does not detect, mask or classify personal information.
When something is refused
401: the token is missing or wrong.422missing 'scope': every HTTP memory call needs a scope in its body.contentandpassagescannot travel in one call: send them in two.- A forget of a name that does not exist exactly answers
404, with up to three close names to check. "state": "nothing_stored"is an answer, not an error: nothing in the desk's memory matched.
Next
- Give your agent persistent memory with MCP in five minutes: the node and the seven memory tools.
- A matter memory for a legal team: the same pattern for parties and clauses.
How these commands were checked
Our test farm ran each of the 4 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.