Memory
What the agent keeps across conversations, as opposed to the window it reads inside one. Two different things wear that word, and they behave differently enough that confusing them costs an afternoon:
- Notes — what the agent has written down about the person and the work. Rows, durable, fed into every turn.
- Looking a conversation up — reaching back past the window into what was actually said. Not storage at all: a search over the chat of record.
Notes: a curated page and dated entries
The agent's memory is markdown it wrote, not chunked messages. One curated page — its standing account of the person and the work — plus dated notes, one per day. Any single item is capped at 2 000 characters; the write path refuses a longer one rather than silently trimming it, because a note truncated mid-sentence is worse than a refusal the agent can act on.
Three things write notes, and there is no fourth: importing a package, the agent's own remember tool, and the curator — a background pass that reads the conversation after it has moved on and decides what deserves to outlive it. The curator runs on a threshold rather than after every turn (roughly every four exchanges, or 8 000 characters of conversation), because paying for a re-reading of every turn costs about as much as the turn.
A write error is more expensive than a read error. A misread note is forgotten by the next turn; a miswritten one is fed into every turn until somebody notices, and noticing is hard — the agent cites a fact confidently and the fact was never true.
What a turn is given, and the one ceiling over it
There is exactly one composer, called once per turn, by both brains. It returns the prompt blocks and the message prefix and what they cost, because only something that sees all of it can trade one layer against another.
Before it existed there were two — one for notes, one for the conversation — each with its own ceiling and nothing anywhere holding the sum. That is how a prompt grows by a thousand characters without anyone deciding it should.
persona → last exchange → summary → notes → passages → deeper windowThat is the eviction order: when the ceiling binds, room is given up from the right. The first two are never cut — the persona is not drawn from the budget at all, and the last exchange is granted before anything else can spend. The summary outranks the notes because it is about this conversation.
The ceiling is 18 000 characters for the whole prefix — the sum of what the layers ask for, so today nothing is actually refused. Counted in characters, not tokens, because there is no tokenizer here and the block rides on every turn: it is a permanent price, not a one-off.
Reading is best-effort. A store that does not answer costs the turn its notes, never the turn.
Ordering notes by meaning — built, and it has never run
Notes can be ordered by what the turn is about rather than by date: the question is embedded, the closest notes are found in the vector store, and their scores become an ordering. The ceiling and every layer's ask are untouched — what changes is which notes the notes budget is spent on.
The vectors live in infra/vector as L2-normalised float32 bytes, keyed by a namespace that carries the team, so one tenant's notes are not even comparable to another's. A vector is matched only when the embedding model and the width both agree; a vector that disagrees with either is not a weak match, it is invisible.
And none of it has ever executed. No environment in this repository sets an embedding key, so the embed call throws before a search happens, and the development database holds zero vectors. Without the ordering, notes come back newest-first — which is not a degraded mode, it is exactly the behaviour that predates the feature. A note missing from the ranking is never excluded; it sorts after the scored ones, by date among its own kind.
That is also why the relevance floor is untuned rather than badly tuned: tuning it needs the shipped provider's own score column, and nothing here can produce one.
Two obligations follow from having an index at all, and both are enforced rather than remembered:
- Anything that writes a note must reindex it. A writer that forgets leaves a note that exists in the database and can never be recalled — the worst kind of fault, because nothing fails. A spec refuses a new writer that does not.
- Deleting a note removes its vector. Otherwise the person deletes a note, the screen agrees it is gone, and the search keeps returning it — the agent remembering what it was told to forget.
Looking the conversation up
Two read-only tools reach the whole thread: recent messages and search over the conversation (Postgres full text, ranked). Both are limited to the agent whose turn it is and the person in it — never somebody else's conversation.
But a tool only fires when the model suspects something is missing, and the defect this is meant to cover is precisely the one where it does not suspect. Measured: on a question whose answer sat twelve days back, the answer was right in every run where the model reached for the tool and wrong in every run where it did not, with nothing else separating them.
So the first search is the turn's, not the model's. The whole user message is looked up before the model sees anything, hits already visible in the window are dropped, and at most four passages (2 000 characters) ride along — in the prompt, never as history, because a line quoted out of order is not the previous message and replaying it as one breaks the alternation the provider expects. A question carrying fewer than two real words is not searched at all, so "thanks" costs nothing.
The tools stay: a model that wants a different query must still be able to ask.
Memory vs knowledge
- Memory = what this agent knows about the person and the work (this page) →
agent/memory. - Knowledge = the store's catalog and docs, ingested into LightRAG →
agent/knowledge.
See also
- Storage — the conversation these notes and passages are drawn from.
- Short memory — the window inside one conversation.
- What the agent did — where a note the agent wrote shows up as an event.