
How to add persistent memory to a LangGraph agent with FishMem
August 23, 2026Guides

Build a support agent that recalls verified outcomes without replaying ticket history. Keep account data, source documents, and durable customer memory in separate authority layers.
A support agent that forgets previous tickets forces customers to repeat the same context. An agent that remembers everything creates a different problem: stale account facts, copied transcripts, and unverified guesses can shape later answers.
The useful middle ground is a narrow memory loop. Recall a small set of verified outcomes before answering, then write a new durable conclusion only after the ticket or business action is resolved.
The example support flow has four parts:
The result is not a claim about an existing FishMem customer. It is a production-shaped implementation pattern based on the public support-agent cookbook.
Plan state, current limits, active incidents, order status, and permissions belong to the product database or another system of record. Query them during the request. Do not rely on a remembered copy when the live value is available.
Manuals, runbooks, contracts, and policies belong in source RAG. Preserve the exact source and return identifiers or citations with retrieved passages so the answer can be audited.
Memory is for smaller conclusions that should affect a later ticket: a stable preference, a verified environment detail, a prior troubleshooting outcome, or a correction that supersedes an earlier belief.
A durable support record should be useful on another ticket and understandable without replaying the transcript. Good candidates include:
Avoid storing passwords, tokens, payment details, complete transcripts, speculative diagnoses, raw tool output, or a copied account field that the application can query live. A memory policy should be narrower than the data that happens to appear in a ticket.
pnpm add @fishmem/sdk
import { FishMem } from "@fishmem/sdk";
const fishmem = new FishMem({
apiKey: process.env.FISHMEM_API_KEY!,
});
For a multi-tenant product, the authenticated workspace selects the FishMem project key. The application then derives user_id from the authorized customer or account identity.
function supportScope(accountId: string) {
return {
user_id: accountId,
agent_id: "customer-support-agent",
};
}
Do not accept an arbitrary customer ID from model output. If a browser submits an account ID, validate membership before using it as memory scope.
async function recallSupportMemory(
accountId: string,
question: string,
) {
const { results } = await fishmem.memories.search({
query: question,
...supportScope(accountId),
top_k: 5,
search_strategy: "precision",
});
return results;
}
Start with a small result count. A support answer rarely needs every record associated with the account. It needs the few conclusions that can change the current troubleshooting path.
function formatSupportMemory(
memories: Array<{ id: string; memory: string }>,
) {
if (memories.length === 0) {
return "No relevant prior outcomes were recalled.";
}
return memories
.map((item) => "[" + item.id + "] " + item.memory)
.join("\n");
}
Preserve the IDs so an operator can inspect the exact records that influenced the answer. Label the block as background and tell the model that current account data and cited source documents take precedence when they conflict.
async function buildSupportContext(input: {
accountId: string;
question: string;
}) {
const [account, memories, sourceHits] =
await Promise.all([
loadCurrentAccount(input.accountId),
recallSupportMemory(input.accountId, input.question),
searchSupportDocuments(input.question),
]);
return {
currentAccount: account,
durableMemory: formatSupportMemory(memories),
sourceEvidence: sourceHits,
};
}
loadCurrentAccount and searchSupportDocuments are application-specific. Their separation matters: a stale memory should never override a current subscription or permission, and a remembered summary should not replace the policy source that the answer must cite.
After the ticket closes, create a self-contained statement that will still make sense without the complete transcript.
async function rememberResolution(input: {
accountId: string;
ticketId: string;
resolution: string;
}) {
return fishmem.memories.add(
{
content: input.resolution,
infer: false,
...supportScope(input.accountId),
metadata: {
source: "resolved-support-ticket",
ticket_id: input.ticketId,
},
},
{
idempotencyKey:
"ticket:" + input.ticketId + ":resolution:v1",
},
);
}
A useful example record is: Ticket T-3391: CSV export failed because the workspace timezone was unset; setting UTC resolved the issue. It records the verified outcome without copying acknowledgements, guesses, or raw tool output.
Imagine a later ticket asks why a scheduled export is empty. The recall query may surface the earlier timezone outcome. The agent can check the current workspace timezone first instead of repeating a browser troubleshooting sequence.
The memory does not prove that timezone is the current cause. It changes the order of investigation. Current account data and the new tool results still decide the answer.
Support knowledge changes. A workaround can become obsolete, an account can migrate regions, or an earlier conclusion can be wrong.
Do not silently rewrite memory from one model answer. Route corrections through explicit application or operator actions and retain the memory history needed for debugging.
Customer support often touches personal or operationally sensitive information. Before enabling automatic writes, define which fields are prohibited, how long each class of memory remains active, and which application event triggers deletion.
Keep the policy enforceable outside the prompt. The model can propose a candidate conclusion, but server code should bind scope, remove prohibited fields, and decide whether the record is eligible for persistence. Project deletion and structural customer deletion should be tested against canonical memories, history, events, and retrieval projections.
FishMem provides memory deletion and scoped entity deletion surfaces, but the application still owns its complete retention contract across the ticket system, source documents, logs, backups, and any downstream analytics.
The support agent can continue without recalled history, fail visibly, or retry under a bounded policy. Make the choice explicit so a degraded answer is not presented as fully informed.
Return an empty labelled block and continue. Do not manufacture a customer profile or infer that missing memory means an event never happened.
If the input needs inference, retain the FishMem event ID and observe its terminal state. For an already-distilled resolution, infer: false writes the exact record synchronously.
Canonical reads can succeed before an asynchronous vector projection becomes searchable. If the next step must search immediately, retry with bounded backoff instead of creating another write.
Record one application request ID across account lookup, document retrieval, memory search, the model call, tools, and any durable write. Keep memory IDs and document identifiers in structured evidence while avoiding secrets and unrestricted prompt logging.
For inferred writes, persist the event ID and monitor pending, retrying, completed, or failed status. For verbatim resolutions, record the returned memory identity. Support operators should be able to answer four questions after a bad reply: what current data was read, which source passages were retrieved, which memories entered the prompt, and whether the new outcome was actually stored.
A support-memory test set should include more than happy-path recall:
Evaluate the stored record, retrieved evidence, final answer, latency, and failure behavior together. A relevant memory result is not useful if the downstream answer ignores it or treats it as stronger than current evidence.
No. Keep the full ticket in the support system. Store only durable outcomes or account context that is likely to improve a later decision.
No. Query current authoritative account data during the request. Memory is useful for historical context and prior outcomes, not as a stale mirror of a live database.
Use source RAG for manuals and policies so passages remain linked to an inspectable source and version. Store a smaller memory only when the workflow produces a durable conclusion about an account or outcome.
Yes, but scope it deliberately. Shared account knowledge can use one user_id, while distinct workflows retain their own agent_id when their operating knowledge should remain separate.
Follow the support-agent cookbook and the multi-tenant memory cookbook. Read Agent memory vs RAG vs context windows for the authority model behind this implementation.
You can inspect the open-source FishMem engine or create a free FishMem Cloud workspace and run the acceptance cases against two isolated test accounts.