Agent Memory
A second product built on this engine — how to run it, what it stores, which parts of TessariDB it leans on, and what it does not have yet.
TessariDB Agent Memory is a separate product built on this engine: the state an agent framework normally keeps in files — goals and the evidence behind them, plans and waves, tasks and claims, checklists, decisions and risks, and what each agent remembers — held as queryable records instead, behind one service with a command line, a REST API and an MCP surface.
It is documented here for one reason. Reading a list of engines tells you what a store can do; a real application tells you which of them you actually end up using, and in what combination. This page is that second thing.
0.4.1-alpha, on TessariDB0.33.1-beta, published as a container image:tessaridb/agent-memory, forlinux/amd64andlinux/arm64. Twenty-two of its twenty-five verbs are carried on all three surfaces; the other three — catching up from a position, export and import — are declared in the contract and refused by name, with what would have to change. Versions before 1.0 promise no compatibility with each other; a0.1.xstore upgrades itself on its first start under0.2.0-alpha.
Run it
The image holds the database and the memory service together. Choose the first owner's passphrase — it is what you type in the browser when an agent asks to be authorized — and start it:
mkdir -m 700 -p ~/.tessaridb
read -rs "p?Passphrase for the first owner: " && printf '%s\n' "$p" > ~/.tessaridb/owner-passphrase; unset p
chmod 644 ~/.tessaridb/owner-passphrase
docker run -d --name agent-memory --restart unless-stopped \
-p 127.0.0.1:39142:39142 \
-v tessaridb-agent-memory:/var/lib/tessaridb \
-v ~/.tessaridb/owner-passphrase:/run/secrets/owner-passphrase:ro \
-e TESSARI_AM_OWNER=ada -e TESSARI_AM_OWNER_IDENTITY=acme \
-e TESSARI_AM_OWNER_PASSPHRASE_FILE=/run/secrets/owner-passphrase \
tessaridb/agent-memory:0.4.1-alpha
Then connect Claude Code through the plugin, from the TessariDB agent plugins marketplace:
/plugin marketplace add tessaridb/tessaridb-agent-plugins
/plugin install tessaridb-agent-memory@tessaridb
The plugin registers the memory server at http://127.0.0.1:39142/mcp and carries a
skill that tells the agent when to reach for memory. It does not carry the memory
itself: until the container above is running, /mcp shows the server as failed to
connect, which is the container missing rather than a plugin fault. For a whole
project, the marketplace's README shows the .claude/settings.json that enables it
for everyone who trusts the folder.
In Codex the same plugin installs from the same marketplace:
codex plugin marketplace add tessaridb/tessaridb-agent-plugins
codex plugin add tessaridb-agent-memory@tessaridb
The plugin expects port 39142. If the container publishes another one, register the
server by its address instead — and keep one of the two registrations, not both:
claude mcp add --transport http -s user --client-id claude-code --callback-port 39143 \
tessaridb-am http://127.0.0.1:<port>/mcp
/mcp → tessaridb-am → Authenticate opens the browser; sign in as ada and
approve. Every Claude Code on the machine shares that sign-in and renews it without a
person. In Codex the same step is codex mcp login tessaridb-am, against a service
at 0.3.0-alpha or later. The store underneath always has a declared user — its password is drawn on
the first start and kept in the volume — and only the memory service is published.
A project is its folder. An agent sends the absolute folder it works in with
session_start. The first time, nothing is declared there, so the start is refused
saying what to do: the agent looks for project markers in the subfolders, asks you
whether the folder is a group of projects — and which subfolders are its projects —
or one project, and starts again with that answer. From then on every agent working
anywhere below that folder lands in the same group and project without asking. Names
are the folders' own, lowercased, with any character a name cannot hold turned into
_.
What it stores
| Area | What it holds |
|---|---|
| Identities | the authenticated tenant; one identity's whole world lives inside its own namespace |
| Agents | identity across time — kind, and the capabilities it declares |
| Sessions | one run of one agent: host, client, model, framework, what it claims |
| Projects | the registry, profiles and policies, as structured children rather than files |
| Goals | objectives, and the per-criterion evidence each was validated against |
| Plans and waves | the plan, the waves it is cut into, and steps that trace back to what asked for them |
| Tasks | decomposition, status, dependencies, claims |
| Checklists | a phase gate with a header — which phase, which flow, and whether it was written before the phase it guards; counts derived from the rows, never stored |
| Questions, decisions, risks, ideas | four lifecycles kept apart: a question owes an answer, a decision is superseded rather than edited, a risk is carried until mitigated or accepted, an idea waits to be promoted or dropped |
| Artifacts | the documents a flow produced — research, reports, snapshots, critiques, concepts, audits — with the rows attached to each |
| Memories | what an agent remembers, typed by how long it should live — with the vector it arrived with, the files and symbols it is about, and who may read it: the whole project, its author alone, or one team |
| Facts and rules | what a project has established, and what binds work there — a fact cites the records it rests on, and knows when one of them is withdrawn |
| Teams | a named set of people and agents; a grant may name a team, and one team reads another's facts only through a grant that ends |
| Activity | what happened, when, by whom — the user and the credential as well as the session — against which object |
| Answers | a model's answers, kept against the question's vector until a lifetime the caller named, and reused only above a similarity the caller named |
| Witnesses | what a recall showed — its query and the version of every hit — so it can be shown again as it stood |
| Coordination | who is holding what, how far a reader has caught up, and whether a request has already been carried out |
| Guidance | the prompts and hints themselves, as data rather than baked into a binary |
What it asks of the engine
This is the part worth reading if you are building something similar. Each row is a demand the application makes and the part of TessariDB that answers it.
| The demand | What answers it |
|---|---|
| several agents writing the same records at once | transactions — a write lands whole or not at all, instead of the advisory locking a directory of files needs |
| finding a memory by what it says | full-text search over the prose a memory and a fact carry, through one declared analyzer |
| finding a memory by what it means | vectors stored in the memory itself, compared only within one model |
| ranking by the words and the meaning at once | several orders at once — ORDER BY FUSE combines each record's place in the text order and in the vector order, never a score and a distance; the engine does it in one read per family |
| reusing an answer a model already gave | expiring keys — a cached answer is a key of a space that stops being answered at the instant its lifetime ends, on every node, whether or not its bytes are gone yet |
| what did the agent see when it decided | the record's own history, read back to the version a recall showed (history) |
| knowledge that links to knowledge | the graph — a link is an edge, and one to a record that does not exist is refused when it is written rather than when it is followed |
| what did this record say before | VERSION — the store as it stood, back as far as reclamation has left |
| one tenant's records never appearing in another's answer | the tenancy — the authenticated identity selects the namespace, so isolation is a property of the credential rather than a filter somebody has to remember |
| a private memory staying private on every read | the reader's audience is a condition in the statement of every search, the activity page included, so a record the reader may not see is never fetched, counted or ranked; a read by key answers it as absent |
| the same words written twice being one memory | a key record whose identifier is a digest of the words, created in the write's own transaction — the store refuses the second create, so two writers racing with the same words leave one memory confirmed twice |
| knowing what rests on a withdrawn fact | the citations are an edge table, walked back a level at a time in the same change that withdraws |
| declaring the shape once, and being told when a write does not fit | definitions are statements, so the schema ships with the code that needs it |
The engine is deliberately invisible to the agents themselves. The service is the
only database user, the only reader and the only writer, and an agent knows a set
of methods and nothing else. That is not tidiness: this store has a field default
that fills an omitted value and no clause that recomputes one on write, so a
maintained updated_at exists only if exactly one writer maintains it.
What it does not have yet
Three of the twenty-five verbs are withheld rather than missing, which is a different claim: each is described in the contract and refused by name, carrying what would have to change. Catching up from a position is half there: the engine now streams changes in order from a position (protocol), but no read reports the position it was answered at, so there is nothing exact to hand a caller to resume from. Export and import wait on a backup format that is the owner's to decide, and inventing one here would decide it by accident. The export is the one the rest wait on, because a store you cannot take your data out of is not a choice anybody should make twice.
A caller told only not yet waits for a release that may never come, which is why the refusal says which half is missing.
Where to read about it
The product's own page is on the main site: tessaridb.com/agent-memory. The image, its variables and its volumes are described on Docker Hub.