Skip to content

Agent memory

An agent’s database is its memory. One FTS5-backed table is the whole store — no embeddings, no vector database, no extra infrastructure. FTS5 ships in PerSQL’s SQLite, so keyword recall is built in.

import { PerSQL } from "@persql/sdk";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });
const mem = persql.database("acme/agent-memory");
await mem.query(`
CREATE VIRTUAL TABLE IF NOT EXISTS memory USING fts5(
kind UNINDEXED, -- 'fact' | 'episode'
topic,
body,
created_at UNINDEXED
)
`);
// Remember something.
await mem.query(
"INSERT INTO memory (kind, topic, body, created_at) VALUES (?, ?, ?, ?)",
["fact", "billing", "Acme prefers net-30 invoicing", Date.now()],
);
// Recall by keyword — BM25-ranked, most relevant first.
const { data } = await mem.query(
"SELECT kind, topic, body FROM memory WHERE memory MATCH ? ORDER BY rank LIMIT 5",
["invoice OR billing OR payment"],
);
// → data: [{ kind: "fact", topic: "billing", body: "Acme prefers net-30 invoicing" }]
KindLives inLifetime
Factual — durable preferences, profiles, learned rulesthe base databasepersists across every run
Working — scratch for the task in flighta throwaway branchdeleted with the run
Episodic — what happened on a given run, for later recallthe base databaseappend-only history

Working memory belongs in a per-run branch so a failed run leaves no residue; facts and episodes belong in the base database so the next run can read them. See Per-agent sandbox for the branch-per-run mechanics.

FTS5 gives lexical, BM25-ranked recall — enough for the keyword and preference lookups most agents actually make. PerSQL stays the durable, structured store; if a workload needs semantic similarity, layer a vector or memory service over these rows and keep the source of truth here.

Memory is rows like any other — metered on rows read, rows written, and storage, with no per-table or per-feature fee. An FTS5 index counts toward storage; keep working memory in a TTL branch so it bills only for the run’s lifetime.

Give the agent the schema in one round-trip with db.describe(), then let it write its own recall queries against the memory table.