Agent memory catch-up: the read that has no query
This is Mnemoverse's documentation, and we make the memory service described below.
TL;DR
- A catch-up read is a newest-first listing that takes no query. It answers "what happened lately", which is the only question a returning agent can ask.
- The loop is three moves: read the newest page, pass the cursor back until none comes back, then store the watermark.
- Our cursor continues the listing with no skips and no duplicates while other agents keep writing. In a run on 22 September 2026, a walk of 10 entries with 2 writes landing mid-walk returned exactly the 10, and the next pass picked up the 2.
- An empty answer names the scope it covered, including the shared rooms (Beta) it did not search.
A returning agent faces a problem that search is not built for. It does not know what it missed, so it cannot name a query. A catch-up read is a newest-first listing of memory entries with no search query, used to resume after a break, catch up on a shared room (Beta), or review what was saved recently.
Why a returning agent cannot search for what it missed
Search asks "what do I know about X". A catch-up read asks "what happened lately". The difference is the required query, not time or order.
Our read tool, memory_read, requires a query. It also takes order_by (relevance or recency) and since and until bounds, so search has both order and time. What it cannot do is list everything, because there is nothing to match against. The feed tool, memory_list_recent, takes no query at all. Its own description, as shipped in the package source, draws the line: "Semantic search answers 'what do I know about X'; this answers 'what happened lately': resuming work after a break, catching up on a shared room ('any new messages?'), or reviewing what was saved recently."
A bot that restarts after a failure cannot ask what it missed about the failing test, because it does not know a test failed. It can only ask what happened since its last watermark. If you know what you are looking for, search. If you only know you were away, catch up.
The catch-up loop in three moves
Move one: read the newest page. On MCP, call memory_list_recent with a limit and an optional since:
{
"tool": "memory_list_recent",
"arguments": { "limit": 50, "since": "2026-09-21T18:00:00Z" }
}On REST, the same read is POST /memory/recent, which the REST reference introduces as "The queryless feed: the newest entries first, with no search."
POST /memory/recent
Content-Type: application/json
{ "limit": 50, "since": "2026-09-21T18:00:00Z" }since keeps "Only entries created at or after this ISO-8601 instant, inclusive" (REST reference); on MCP a naive timestamp is read as UTC. Inclusive matters, and the next section comes back to it.
Move two: pass the cursor back. When older entries remain, REST returns next_cursor, "Present when older entries remain; null at the end of the feed". On MCP the cursor arrives in the answer text, and the parameter that takes it back "continues the listing without skips or duplicates" (tool description). The cursor is a value the server hands you. Do not rebuild it from a timestamp or increment it like a page number.
Move three: stop only when no cursor comes back. On REST that is next_cursor: null. On MCP the last page says so, and from version 0.11 of the package a finished feed ends cleanly rather than suggesting that more entries exist (package changelog).
Then, and only then, store the newest created_at you saw as the next since. A watermark committed before the walk finishes can skip the entries the walk had not reached.
The whole loop, with the parameter each line maps to:
watermark = load(scope) # one watermark per scope, per consumer
cursor = null
seen = set() # ids ingested this walk
loop:
page = list_recent(domain = scope, # a room address, or omitted for your own store
since = watermark,
limit = 50, # a ceiling; a page is also bounded by size
cursor = cursor)
for entry in page.entries:
if entry.id in seen: continue # since is inclusive: skip ids you already hold
seen.add(entry.id)
ingest(entry)
if page.cursor is empty: break # the end of the feed, not a short page
cursor = page.cursor
commit(scope, newest_created_at_seen) # only after the walk has endedCursor paging while other agents keep writing
This is the useful difference between a cursor and page numbers. When writes land while a reader pages backwards, a page number points at a different slice than it did a second ago, so entries can repeat or drop out of view. A cursor marks a position in the ordered listing, so a new write at the top does not move the next page.
The contract is written down. The REST reference says that passing next_cursor back "continues the listing with no skips and no duplicates, which LIMIT/OFFSET cannot guarantee while writes are landing". Under the hood the engine walks newest-first by creation time and id, and the cursor carries the last position it returned.
Verification run, 22 September 2026, on a Mnemoverse account and in a store of its own: 10 entries written, then the feed walked over MCP with limit: 3. Two more entries were written after the first page, as a second agent would. The walk continued with the cursor: 4 pages, the 10 entries that existed when it started, none repeated, none missing. The 2 new entries did not appear in that walk. The next pass, with since set to the time of the newest entry seen, returned them at the top. That is one run on one date, not a benchmark; the guarantee itself is the documented contract above.
The same run shows the rule that keeps a loop safe. Because since is inclusive, the next pass returns entries at the watermark again. In that run the watermark sat at the start of the minute in which all twelve entries were written, so the second pass returned the 10 older entries as well as the 2 new ones. Nothing was lost, and the client-side rule is simple: treat an id you already hold as a repeat. Repeats are what an inclusive window is for; they are how a loop avoids ever missing the boundary.
Three habits follow from it:
- Keep one watermark per scope and per consumer. Finishing a walk of your own store must not advance the watermark of a shared room (Beta).
- Keep the old watermark when a pass comes back empty.
- Finish a walk you started even while writes land; what arrived meanwhile is the next pass's job.
Page size is a ceiling, and a short page is not the end
On MCP, limit (1 to 100, default 20) is "A CEILING, not a promise" (tool description). A page is also bounded by size: the tool assembles it from small sub-requests and stops under a 40,000-character budget, so one call stays inside a client's tool-result limit and the cursor carries the rest (MCP server page). A page of long entries comes back shorter than limit with a cursor still attached. Entries in shared rooms (Beta) tend to run long, so expect to page there.
Read to the cursor's absence, not to a short page. If a sub-request fails, the answer says "This page stopped early" and, separately, "The cursor above is unaffected: continue from it" (tool description). On REST, limit means what it says: "Page size, 1–100 (default 20)" entries.
Empty memory results: what an empty answer tells you
A returning bot usually does not meet an error. It meets nothing, and nothing has two readings: nothing was written, or you looked in the wrong store.
Our read tools tell the two apart. The changelog records the change: "An empty answer now describes what it covered instead of asserting that nothing exists, which is the difference between "your store is empty" and "this query, in this scope, matched nothing"." An unscoped read does not cover shared rooms (Beta), so when it comes back empty it names the rooms that went unsearched and ends with an instruction: "Re-run with domain set to one of these to read it." Before that change, the package's own changelog records, an unscoped read answered "Nothing new since your watermark", which it calls "a claim about the world".
Rooms, secrets and superseded revisions
Shared rooms (Beta) are separate stores. A room is addressed as xroom:<room_id> and passed as the domain, and an unscoped call never covers it. Membership "is enforced in the database, not only in the application" (MCP server page). On REST, rooms are a feature of /memory/read and /memory/recent: POST /memory/read-batch and /memory/query refuse an xroom: domain with 400 VALIDATION_ERROR (REST reference).
Secrets stay out of the feed. "Secrets are excluded from this feed by design" (REST reference), and the reason is the right one for a surface without a query: a queryless listing would make every secret's alias enumerable on each first page. Secrets are listed by alias only through vault_list.
Superseded revisions are a visibility setting. For an organisation that has switched read-side filtering on, entries that a newer entry supersedes leave the feed. On REST, include_history: true brings them back ("Include superseded revisions in the feed (default: false)"). It has no effect where filtering is off, and it is inert for shared rooms (Beta), "whose atoms cannot be superseded at all".
The local npm package and the hosted connector register the same ten tools from one shared definition, and deleting a memory is not among them. As the remote MCP server page puts it, "Deleting a memory or wiping a domain is not an MCP tool on either path"; it is an administrative REST operation.
Other memory products ship a feed too
A catch-up feed is not ours alone. Zep's Memory MCP server has list_episodes, to "List the user's raw ingested episodes, most recent first" (Zep, read 19-09-2026). Supermemory's MCP server has list_memories, to "Browse recent extracted memory entries and their source document IDs", with page, limit and containerTag (Supermemory, read 19-09-2026). Mem0's Get Memories reference says to "Pass page and page_size as query parameters to paginate through results"; its response carries count, next, previous and results, and it filters created_at with gte and lte (Mem0, read 19-09-2026).
So the useful comparison is not who has a feed, but what each feed promises while other agents keep writing. Supermemory and Mem0 page by page number and size, and neither vendor page states a guarantee for concurrent writes. Ours is the documented cursor contract, and the run above is what it looked like on one day.
Four checks you can run in ten minutes
- Walk to the end. Call
memory_list_recentwith a smalllimit, pass the cursor back until none comes back, and count the ids. Nothing should repeat inside one walk. - Write while you walk. After the first page, write one entry from another client, then finish the walk. The new entry should not appear in this walk; it should appear at the top of the next pass, which uses your watermark.
- Read an empty scope. Run an unscoped read you expect to come back empty and read the text, not its length: it should name the shared rooms (Beta) it did not search.
- Try a search without a query.
memory_readrefuses a call withoutquery. With a query plussinceanduntilit answers a different question from the feed.
Common questions
How to handle continuous context retention for autonomous software engineering bots?
Treat the return as a read, not a search. Read the newest entries with no query, pass the cursor back until none comes back, and only then store the newest timestamp you saw as the watermark for the next visit. On MCP the tool is memory_list_recent; on REST it is POST /memory/recent.
How do I give my LLM workflow persistent context across independent client connections?
Keep the state in the memory store, not in any connection. Every client reads the same store on the way in, with its own watermark per scope, walks the cursor to the end, and treats entries it already holds as repeats by id. The local npm package and the hosted connector expose the same ten tools, so the loop is the same on both paths.
How to maintain session knowledge across a fleet of specialized developer bots?
Give the fleet a scope to share, such as a shared room (Beta) addressed as xroom:<room_id>, and give each bot its own watermark and its own cursor. The cursor continues a listing rather than re-slicing it by offset, which is what keeps a bot's walk intact while the others keep writing.
How does an agent catch up on memory it missed without knowing what to search for?
With a catch-up read: a newest-first listing that takes no query. Search needs the agent to name what it wants; a returning agent does not know what it missed, so it reads what happened since its last watermark instead.
Why does an empty memory result not always mean nothing was written?
Because an empty answer is a statement about a scope. An unscoped read does not cover shared rooms (Beta), so our read tools name the rooms that went unsearched and say how to read them, instead of reporting that nothing exists.
Related
- What survives when AI agents restart
- MCP servers that share state across IDEs
- Shared memory for multi-agent systems
- Agent memory deletion: what survives a delete
- MCP memory servers for Claude Code and Cursor
- Reference: MCP server, REST API, remote MCP server, changelog
Edward Izgorodin · Mnemoverse · 2026-09-22
Mnemoverse is a persistent-memory API for AI agents. Free key: console.mnemoverse.com · Plans and limits · Docs: Getting Started
