FishMem

A personal assistant that tracks changing preferences

Use bi-temporal memory so a changed city, job, or diet supersedes the old fact instead of contradicting it.

People change. They move cities, switch jobs, go vegetarian. A personal assistant that just appends every new fact ends up holding both "lives in Lisbon" and "lives in Berlin" with no idea which is current — and contradicts itself the next time you ask.

FishMem handles this with bi-temporal facts. When a new fact supersedes an old one, FishMem emits an UPDATE: search returns the current truth, while the superseded fact is retained in history rather than deleted. You get a consistent answer now and an audit trail of how it changed.

Add evolving facts over time

Scope everything to the user and let inference reconcile contradictions as they arrive.

track-preferences.ts
import { FishMem } from 'fishmem';

const client = new FishMem({ apiKey: process.env.FISHMEM_API_KEY });

// Week 1
await client.add([{ role: 'user', content: 'I live in Lisbon.' }], { user_id: 'sam' });

// Week 8 — Sam moves. FishMem recognizes this supersedes the earlier fact.
const { results } = await client.add(
  [{ role: 'user', content: 'I just moved to Berlin.' }],
  { user_id: 'sam' }
);

console.log(results);
// [ { id: "mem_...", memory: "Lives in Berlin", event: "UPDATE" } ]

The UPDATE event tells you the move replaced an existing fact rather than adding a new, unrelated one.

Search returns the current truth

A search for the user's location returns Berlin, not both cities. The old fact no longer competes in recall.

answer-current.ts
const { results } = await client.search('where does sam live?', {
  user_id: 'sam',
  top_k: 3,
});

console.log(results[0].memory); // "Lives in Berlin"

History reveals the supersession

The old fact isn't lost — it's in the memory's history. Fetch it to see exactly how the fact evolved, including the previous and new values.

inspect-history.ts
const { results } = await client.history('mem_...'); // the memory id from the UPDATE

for (const entry of results) {
  console.log(entry.event, entry.previous_value, '->', entry.new_value, '@', entry.created_at);
}
// ADD    null         -> Lives in Lisbon  @ 2026-04-01T...
// UPDATE Lives in Lisbon -> Lives in Berlin @ 2026-05-24T...

This is the difference between valid-time (when the fact was true in the real world) and record-time (when FishMem learned it). Superseded facts are kept, so you can always reconstruct what the assistant believed at any point.

On this page