Skip to main content

Module store

Module store 

Source
Expand description

Where basis’s conversations are persisted, and how they are scoped.

mentra persists every agent to its runtime store — since basis 0.7 the file-backed one, plain files under one root, no database (ADR-0023; upstream mentra#28) — and tags each record with a runtime identifier. basis uses that tag to answer one question: which conversations belong to this workspace? — which is what ACP’s session/list asks and the only reading of “my sessions” that is both honest and useful, since ACP scopes a session to a cwd from the moment session/new opens it.

§Why the workspace path, verbatim

mentra’s default identifier is the literal string "default", and its default store is one shared root. Listing under "default" would therefore enumerate the agents of every mentra program on the machine — worse than returning nothing, because a client would offer a user conversations that are not theirs.

The identifier is the canonicalized workspace path with a basis: prefix. No hash: the identifier never becomes a filename — mentra keeps it as a field of each agent’s agent.json and filters listings by comparing it — so every character survives, and a readable value is one a person debugging the store can understand by opening the file. The prefix keeps basis’s records from ever colliding with another program’s "default".

Changing the identifier does not move the store — mentra’s default path is independent of it — so nothing already written is lost. Records created before this scheme carry "default" and do not appear in any workspace’s list, which is the correct answer for a conversation whose workspace was never recorded. They are not stranded: mentra loads an agent by id alone, so resuming one still works. WorkspaceBuilder::open is where the tag is set, and where that ruling is written down.

They stopped re-tagging themselves in mentra 0.27, and mentra 0.28 turns that back on as an explicit basis-side choice. Every persist used to re-derive the tag from the live runtime’s own current identifier, so the first resume-and-run of an old "default"-tagged conversation silently adopted it into the resuming workspace’s list. mentra 0.27’s fix for mentra#54 rebound a resumed agent to its own stored identifier and carried that forward instead, which was the right fix for #54’s bug (a shared runtime’s resumed-then-run rows re-filing under its generic tag) but also closed this door: a legacy record stayed "default"-tagged forever, with no code path left to adopt it — filed as mentra#59. mentra 0.28 reopens it explicitly: SessionResumeOptions::runtime_identifier rehomes a resumed agent under a chosen identifier on its next persist, without retagging any other agent or the shared runtime’s own identifier. Runtime::resume_minted (crate-private) passes this workspace’s own identifier on every resume, not only for a legacy "default" row — the check it makes immediately after already refuses to hand back a session whose base_dir disagrees with this workspace, so every row a resume succeeds on is provably this workspace’s own, and restating its identifier is always correct.

Every workspace tags its own rows, on a private runtime and on a shared one alike: Runtime’s mint states the identifier per session, so one store file serving five repositories still lists each one’s conversations apart.

The gap that used to live here is closed. A resumed session used to carry no identifier — mentra’s SessionResumeOptions had no field for one — so a resumed conversation re-persisted under the runtime’s own tag on its next save, taking its row out of the workspace’s list on a shared runtime the moment it was used again. mentra 0.27 fixes this at the source rather than by adding that field: every persisted-agent reconstruction now retains the row’s own stored runtime identifier through every later save (mentra#54), so Runtime::resume_minted needed no change to pick it up.

§Where the files go

mentra’s default directory is keyed by the process’s current directory, not by the workspace basis opened, so every program started from one place shares one store root whatever workspace it went on to open — including every test binary in one cargo test. RuntimeBuilder::with_store_dir is how a caller says otherwise, and list_in is how the same caller reads back what it wrote. The directory the caller names is the store’s root — mentra lays agents/, rules.json and runs.jsonl inside it — and the root is bound to the store in exactly one place, store_in, because two places would eventually disagree and a conversation written under one root and looked for under another is simply missing.

§When the directory holds a database instead

basis 0.6 and earlier kept conversations in runtime.sqlite under the same directory. This build links no SQLite and does not migrate (ADR-0023’s E2 precedent): opening, listing or deleting against a directory that holds one is refused with RunError::LegacyStore, which names the two ways forward, rather than starting an empty file store beside data it cannot see — the refusal is refuse_legacy_store, and every path that opens a store the caller pointed somewhere runs it.

§When there is no file

RuntimeBuilder::with_ephemeral_history answers where with nowhere, and opens mentra’s in-memory store instead. Nothing in this module can see one of those conversations: there is no file for list_in to read and no row for list to filter, whichever directory either is pointed at. One file still gets written even then — a compaction snapshot, which mentra persists without consulting the store — and it goes to a per-runtime directory under the OS temp directory, which is as close to nowhere as that file gets. Everything else below is about the durable case.

Structs§

PersistedSession
A conversation mentra has on disk, as basis reports it.

Functions§

default_directory
The directory mentra keeps basis’s conversations in, for a caller that wants to say where the history lives.
forget
Removes a conversation from the default directory’s store for good.
forget_in
The same, for conversations kept somewhere of the caller’s choosing.
list
Every conversation persisted for workspace, most recently used first.
list_in
The same, for conversations kept somewhere of the caller’s choosing.
runtime_identifier
The runtime identifier for conversations in workspace.