A company brain is a single store of facts that every agent in the company reads before it answers. The brain in that exchange is a SQLite database, and recall is the tool that brief-writer calls to read it.
That call returns two rows for one price, because someone in pricing wrote $4,200 when the company set that price and wrote $3,600 when the price changed in March. The superseded_by column is empty on both rows, so the agent answers from whichever of the two recall returns first. Amelia therefore cannot tell from that answer whether the agent misread the brain or the database holds the wrong price.
You close that gap in four steps, adding a timestamp for the date pricing wrote each value, along with the id of the row that replaced it, so that recall can take a date as an argument and return the price that was current on it. Each code block below holds one whole file.
Where the wrong answer comes from
The database stores every fact in one table, where each row in fact holds a subject, a predicate, and a value. Three further columns are on the table from the start, and you bring each of them into the recall query as this page goes on: learned_at holds the date on which pricing wrote the value, expires_at holds the date after which the query drops the row, and superseded_by holds the id of the row that replaced it.
| id | subject | predicate | value | source | written_by | learned_at | expires_at | superseded_by |
|---|---|---|---|---|---|---|---|---|
| 1 | Tier 2 renewal | list_price | $4,200 | pricing-sheet | pricing-team | 1768176000000 | null | null |
| 2 | Tier 2 renewal | list_price | $3,600 | pricing-sheet | pricing-team | 1772496000000 | null | null |
superseded_by is null on both of the rows, so nothing in the table separates the two prices.
remember and recall are plain functions over the database, while brainTools wraps them as tools that the agent can call, which leaves the same two functions available to another framework or an MCP server.
openBrain creates the data directory and rebuilds the table on every run, so that the panels below print the same figures whenever you run them. mock-model.ts is there for the same reason, because a real model would word its answer differently on every call. The mock calls recall once and then repeats the value from the first row the tool returned, so its answer is whatever the database holds.
import { ToolLoopAgent } from 'ai'
import { brainTools, remember } from './memory-tools'
import { mockModel } from './mock-model'
import { openBrain, sirannon } from './schema'
const JANUARY = Date.UTC(2026, 0, 12)
const MARCH = Date.UTC(2026, 2, 3)
const db = await openBrain()
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$4,200',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: JANUARY,
})
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$3,600',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: MARCH,
})
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
const stored = await db.query<{ value: string; learned_at: number }>(
'SELECT value, learned_at FROM fact WHERE subject = ? ORDER BY id',
['Tier 2 renewal']
)
console.log(
JSON.stringify(
{
answer: reply.text,
rows: stored.map((row) => ({ value: row.value, learnedOn: new Date(row.learned_at).toISOString().slice(0, 10) })),
},
null,
2
)
)
await sirannon.shutdown(){
"answer": "Tier 2 renewal is $4,200.",
"rows": [
{
"value": "$4,200",
"learnedOn": "2026-01-12"
},
{
"value": "$3,600",
"learnedOn": "2026-03-03"
}
]
}The agent answers with the older price, because recall orders by id and row 1 has the lower one. Even so, every statement in the run succeeds and the run reports no error.
Retire the old row when a new value supersedes it
A new price takes two writes, one that inserts the new value and one that marks the old value as replaced. Run both of them in the same transaction, so that no query ever returns the two rows together as current.
The insert goes first, and the update then writes the new row's id into superseded_by on every earlier row for that subject and predicate. recall returns only the rows whose superseded_by is null, while the old row stays in the table, because you would need it later to explain why the agent answered as it did.
The authors of a 2026 survey of agent memory call this failure stale commitment, and they write that "to mark a value superseded rather than deleted, the store must represent both current and historical truth with authority and timestamps, which many flat-text memories do not" (arXiv 2606.30306).
import { ToolLoopAgent } from 'ai'
import { brainTools, remember } from './memory-tools'
import { mockModel } from './mock-model'
import { openBrain, sirannon } from './schema'
const JANUARY = Date.UTC(2026, 0, 12)
const MARCH = Date.UTC(2026, 2, 3)
const db = await openBrain()
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$4,200',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: JANUARY,
})
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$3,600',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: MARCH,
})
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
const stored = await db.query<{ value: string; learned_at: number }>(
'SELECT value, learned_at FROM fact WHERE subject = ? ORDER BY id',
const stored = await db.query<{ id: number; value: string; learned_at: number; superseded_by: number | null }>(
'SELECT id, value, learned_at, superseded_by FROM fact WHERE subject = ? ORDER BY id',
['Tier 2 renewal']
)
console.log(
JSON.stringify(
{
answer: reply.text,
rows: stored.map((row) => ({ value: row.value, learnedOn: new Date(row.learned_at).toISOString().slice(0, 10) })),
rows: stored.map((row) => ({
id: row.id,
value: row.value,
learnedOn: new Date(row.learned_at).toISOString().slice(0, 10),
supersededBy: row.superseded_by,
})),
},
null,
2
)
)
await sirannon.shutdown(){
"answer": "Tier 2 renewal is $3,600.",
"rows": [
{
"id": 1,
"value": "$4,200",
"learnedOn": "2026-01-12",
"supersededBy": 2
},
{
"id": 2,
"value": "$3,600",
"learnedOn": "2026-03-03",
"supersededBy": null
}
]
}Row 1 now holds 2 in superseded_by, while row 2 holds null, which is why recall returns $3,600 today. Nothing in mock-model.ts changed between the two runs, so the rows in the database are what changed the answer.
Ask for the price that was current on a date you name
Amelia's second question is about the past, and a query that returns only the current row cannot answer it. recall therefore takes the date as an argument, and its WHERE clause keeps a row whose learned_at falls on or before that date, whose expires_at falls after that date, and whose superseded_by is either null or holds the id of a row written later.
The caller passes that date to brainTools, so the agent chooses the subject while the caller fixes the date. An agent that chose its own date would be choosing which version of the price it answers from.
Which Tier 2 renewal price is current on each date
Asked as of a February date, the agent answers $4,200.
Asked today, the agent answers $3,600.
import { ToolLoopAgent } from 'ai'
import { brainTools, remember } from './memory-tools'
import { mockModel } from './mock-model'
import { openBrain, sirannon } from './schema'
const JANUARY = Date.UTC(2026, 0, 12)
const FEBRUARY = Date.UTC(2026, 1, 9)
const MARCH = Date.UTC(2026, 2, 3)
const TODAY = Date.UTC(2026, 7, 27)
const db = await openBrain()
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$4,200',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: JANUARY,
})
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$3,600',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: MARCH,
})
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
const askBriefWriter = async (asOf: number): Promise<string> => {
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db, asOf),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
return reply.text
}
const stored = await db.query<{ id: number; value: string; learned_at: number; superseded_by: number | null }>(
'SELECT id, value, learned_at, superseded_by FROM fact WHERE subject = ? ORDER BY id',
['Tier 2 renewal']
)
console.log(
JSON.stringify(
{
answer: reply.text,
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
rows: stored.map((row) => ({
id: row.id,
value: row.value,
learnedOn: new Date(row.learned_at).toISOString().slice(0, 10),
supersededBy: row.superseded_by,
})),
},
null,
2
)
)
await sirannon.shutdown(){
"askedToday": "Tier 2 renewal is $3,600.",
"askedAsOfFebruary": "Tier 2 renewal is $4,200.",
"rows": [
{
"id": 1,
"value": "$4,200",
"learnedOn": "2026-01-12",
"supersededBy": 2
},
{
"id": 2,
"value": "$3,600",
"learnedOn": "2026-03-03",
"supersededBy": null
}
]
}One agent and one tool now produce two answers, and each of them is correct for the date in the query. A quote of $4,200 sent in February would have matched the table as it stood then, while the same quote sent today would be wrong.
Delete the superseded rows, and lose the history with them
A superseded row stays on the disk, because recall drops it from the results and deletes nothing. Only a delete bounds how large the table grows. forget deletes the superseded rows that pricing wrote before a cutoff you choose, and it returns how many of them it deleted, which is the number you would send to your metrics system.
The panel below shows both answers and the row count, before the delete and after it. Once the older row is gone, the only price left in the table is the current one, so take the cutoff from what your own auditors require.
import { ToolLoopAgent } from 'ai'
import { brainTools, remember } from './memory-tools'
import { brainTools, forget, remember } from './memory-tools'
import { mockModel } from './mock-model'
import { openBrain, sirannon } from './schema'
const JANUARY = Date.UTC(2026, 0, 12)
const FEBRUARY = Date.UTC(2026, 1, 9)
const MARCH = Date.UTC(2026, 2, 3)
const TODAY = Date.UTC(2026, 7, 27)
const SUPERSEDED_RETENTION = 90 * 24 * 60 * 60 * 1000
const db = await openBrain()
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$4,200',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: JANUARY,
})
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$3,600',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: MARCH,
})
const askBriefWriter = async (asOf: number): Promise<string> => {
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db, asOf),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
return reply.text
}
const stored = await db.query<{ id: number; value: string; learned_at: number; superseded_by: number | null }>(
'SELECT id, value, learned_at, superseded_by FROM fact WHERE subject = ? ORDER BY id',
['Tier 2 renewal']
)
const beforeForget = {
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
factRows: (await db.query<{ n: number }>('SELECT COUNT(*) AS n FROM fact'))[0]?.n,
}
const removed = await forget(db, TODAY - SUPERSEDED_RETENTION)
console.log(
JSON.stringify(
{
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
rows: stored.map((row) => ({
id: row.id,
value: row.value,
learnedOn: new Date(row.learned_at).toISOString().slice(0, 10),
supersededBy: row.superseded_by,
})),
beforeForget,
removed,
afterForget: {
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
factRows: (await db.query<{ n: number }>('SELECT COUNT(*) AS n FROM fact'))[0]?.n,
},
},
null,
2
)
)
await sirannon.shutdown(){
"beforeForget": {
"askedToday": "Tier 2 renewal is $3,600.",
"askedAsOfFebruary": "Tier 2 renewal is $4,200.",
"factRows": 2
},
"removed": 1,
"afterForget": {
"askedToday": "Tier 2 renewal is $3,600.",
"askedAsOfFebruary": "I have no record of Tier 2 renewal.",
"factRows": 1
}
}The agent still answers today's question, because forget keeps every row whose superseded_by is null. It cannot answer the February one any more, and it says so in place of guessing. Once you delete the history, recall still returns today's price, while every question about February returns nothing.
Run it against a real model
Swap the mock for a model string, and leave the rest of the file unchanged. The agent would then decide for itself when to call recall, and its wording would change from one run to the next, which is why no panel follows this step.
import { ToolLoopAgent } from 'ai'
import { brainTools, forget, remember } from './memory-tools'
import { mockModel } from './mock-model'
import { openBrain, sirannon } from './schema'
const JANUARY = Date.UTC(2026, 0, 12)
const FEBRUARY = Date.UTC(2026, 1, 9)
const MARCH = Date.UTC(2026, 2, 3)
const TODAY = Date.UTC(2026, 7, 27)
const SUPERSEDED_RETENTION = 90 * 24 * 60 * 60 * 1000
const db = await openBrain()
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$4,200',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: JANUARY,
})
await remember(db, {
subject: 'Tier 2 renewal',
predicate: 'list_price',
value: '$3,600',
source: 'pricing-sheet',
writtenBy: 'pricing-team',
learnedAt: MARCH,
})
const askBriefWriter = async (asOf: number): Promise<string> => {
const briefWriter = new ToolLoopAgent({
model: mockModel('Tier 2 renewal'),
model: 'openai/gpt-5.6-sol',
instructions: 'Answer from the company brain, using the recall tool.',
tools: brainTools(db, asOf),
})
const reply = await briefWriter.generate({ prompt: 'What do we charge for a Tier 2 renewal?' })
return reply.text
}
const beforeForget = {
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
factRows: (await db.query<{ n: number }>('SELECT COUNT(*) AS n FROM fact'))[0]?.n,
}
const removed = await forget(db, TODAY - SUPERSEDED_RETENTION)
console.log(
JSON.stringify(
{
beforeForget,
removed,
afterForget: {
askedToday: await askBriefWriter(TODAY),
askedAsOfFebruary: await askBriefWriter(FEBRUARY),
factRows: (await db.query<{ n: number }>('SELECT COUNT(*) AS n FROM fact'))[0]?.n,
},
},
null,
2
)
)
await sirannon.shutdown()Where to go next
A value someone wrote by mistake needs more than a delete, so rewinding memory restores the whole database to the state it was in before that write. Where a user asks in their own words for something you stored under another subject, retrieval with Narsil searches these same rows by meaning. For a record of which agent wrote each row, read auditing a fleet.