Build a support agent with memory
Stop re-asking customers what they already told you by recalling per-customer facts before each ticket.
Support bots have a short memory. A customer explains their setup in one ticket, then has to explain it all over again in the next. The naive fix — stuffing the full transcript history into every prompt — doesn't scale: it inflates token cost, adds latency, and buries the few relevant facts under months of small talk.
The fix is to remember the durable facts, not the transcript. After each resolved ticket you write what matters back to FishMem, scoped to the customer. Before answering a new ticket you search that customer's memories and inject only what's relevant.
Walkthrough
Get an API key
Create a project key in the dashboard. Keep it
server-side; it looks like fm_.... See Authentication
for details.
export FISHMEM_API_KEY="fm_..."Add memories after each resolved ticket
When a ticket closes, write the durable facts back, scoped to the customer with
user_id. Attach metadata so you can trace a memory to its origin later.
import { FishMem } from 'fishmem';
const client = new FishMem({ apiKey: process.env.FISHMEM_API_KEY });
await client.add(
[
{ role: 'user', content: "I'm on the Enterprise plan and we deploy on AWS eu-central-1." },
{ role: 'assistant', content: 'Resolved: increased the rate limit on your eu-central-1 project.' },
],
{
user_id: 'cust_8842',
metadata: { ticket_id: 'T-3391', category: 'rate-limits', resolution: 'limit-raised' },
}
);FishMem extracts the durable facts ("On the Enterprise plan", "Deploys on AWS eu-central-1") rather than storing the raw chat.
Search before answering a new ticket
When a new ticket arrives, search the customer's memories and feed the results
into your system prompt. Keep top_k small so you inject signal, not noise.
import { FishMem } from 'fishmem';
import { llm } from './llm'; // your model call
const client = new FishMem({ apiKey: process.env.FISHMEM_API_KEY });
async function answerTicket(customerId: string, question: string) {
const { results } = await client.search(question, {
user_id: customerId,
top_k: 5,
});
const context = results.map((m) => `- ${m.memory}`).join('\n');
const system = [
'You are a support agent. Use the known facts about this customer.',
'Do not ask for information already listed below.',
'',
'Known facts:',
context || '(none on file)',
].join('\n');
return llm({ system, user: question });
}Keep tickets isolated per customer
Because every read and write is scoped to user_id, one customer's facts are
never visible to another. The search above can only return memories for
customerId. See scoping for how user_id,
agent_id, and run_id compose.
The add + search loop without the SDK
If you're calling the REST API directly, the same two steps look like this.
curl -X POST https://fishmem.com/v1/memories \
-H "Authorization: Bearer $FISHMEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I am on the Enterprise plan and deploy on AWS eu-central-1."}
],
"user_id": "cust_8842",
"metadata": {"ticket_id": "T-3391", "category": "rate-limits", "resolution": "limit-raised"}
}'curl -X POST https://fishmem.com/v1/memories/search \
-H "Authorization: Bearer $FISHMEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "what plan and region is this customer on?", "user_id": "cust_8842", "top_k": 5}'Why this beats stuffing the transcript
- Cost — you inject a handful of extracted facts, not thousands of tokens of past conversation, on every turn.
- Latency — a scoped search returns the relevant memories quickly; prompt size stays flat as ticket history grows.
- Relevance — hybrid recall surfaces the facts that bear on the current question, instead of forcing the model to find them inside a wall of text.