ADR-031: A note is an unnamed drawer written through its own service, and the CLI resolves its project scope¶
Status¶
Accepted.
Amends ADR-029: the CLI now reads the project file, for note only,
and ADR-024: extraction also reads notes.
Context¶
Issue #187 asks for a lightweight way to capture a thought from a shell: memcastle note "...", with no folder, tag or
graph to maintain, filed under the project the user is in, kept as typed, and treated like any other memory afterwards.
Three existing paths write a drawer, and none fits. A checkpoint is a durable job and answers with a job, not a drawer, so the caller cannot be given the stable id it should keep. Mining is built around documents that are discovered, revised and tracked by a cursor, and a one-line thought is none of those. A diary entry is scoped to an agent identity, which a person at a prompt does not have.
One fact about enrichment shapes the design. The extraction sweep reads only drawers that carry a source origin (ADR-024), and a note has no document to cut from, so a note written through any existing path would be searchable and never read for entities.
Decision¶
- A note is an unnamed drawer in an ordinary room, with
source.kind = note, the capture directory assource.uri, andprovenance.requested_bynaming the channel. There is no note table, no vault and no second index: search, recall, embedding, deduplication, supersession and point-in-time queries all apply as they do to any drawer. AppServices::note_writeandPOST /api/noteswrite it synchronously, like a diary entry, so the answer carries the drawer's id. The text is stored exactly as given, the capture time is the drawer'screated_atandvalid_from, and an exact copy in the same room is not stored twice (ADR-025, theMEMORYrules): the existing note is returned withcreated = false.- Enrichment is queued after the write: the embedding sweep, as for every writer, and an extraction sweep. The extraction sweep's selection widens from "has a source origin" to "has a source origin, or is a note". Extraction stays derived data that never writes a drawer.
SourceKind::Noteis a new kind (noteon the wire, accepted by--source-kindandsource_kind), so notes can be asked for or excluded without a tag the user would have to type.- The CLI resolves the project scope itself, per field:
--wing/--room, thenMEMCASTLE_WING/MEMCASTLE_ROOM, then.config/memcastle.toml, then the working directory's name for the wing andnotesfor the room. It sends the daemon an ordinary wing and room, so the daemon still never learns what a project is. The reader moves from the directory adapter tosrc/project.rs, which both call, so mining and noting cannot disagree about discovery, and the shared fixtures now replay the environment cases in Rust too. - A project that cannot be read is an error for
note,memcastle::project::invalid, unlike mining's log and carry on: the user asked for this note to be filed, a wrong scope would put it somewhere unintended, and--wingwith--roomis the way out. - Text comes from arguments,
--file(-for standard input), piped standard input or$VISUAL/$EDITOR. The editor runs on a scratch file that is removed afterwards, a failing editor saves nothing, and text from a file, standard input or an editor loses its trailing whitespace. - There is no MCP tool.
An agent writes memory through
memcastle_checkpointand the diary, which carry the agent identity and the classification a note deliberately has none of. A tool can be added over the same service later without changing it.
Alternatives rejected¶
- A checkpoint job with a
generalitem. Durable and already there, but it returns a job id, derives the drawer id where the caller cannot see it, and still has no origin for extraction. - A built-in
notemining adapter. It would give a note an origin and a document record, but the pipeline's discovery, cursor and revision machinery exists to track documents that change, and a note is written once. It would also make the capture asynchronous. - Giving every note a synthetic origin so the extraction query stays unchanged. It would invent a source and a document that do not exist, and pretend provenance the note does not have.
- The daemon resolving the project from a path. It only works when the daemon and the CLI share a filesystem, and it would teach the daemon a notion of project that ADR-029 deliberately keeps out of it.
- A
note.wingsetting in the daemon's configuration. The project file already says where a project's memory goes, and a machine-wide setting would override the project the user is standing in.
Consequences¶
memcastle noteworks from any directory with no flags, and files under the project's wing when the project says so, so a note, a mined file and a checkpoint of one project meet in one wing.- A note is searchable at once and, with an embedding provider, semantically after the next sweep. With an extraction provider its entities appear in the graph with the note as provenance, valid from the capture time.
- Extraction now reads two kinds of drawer, and a test holds the selection to exactly those.
tempfileis a normal dependency, for the editor's scratch file.- The CLI reads a directory and the environment for one command.
It still touches neither the store nor the jobs, which the
store-isolationhook keeps true. - The TypeScript readers are unchanged: the contract and the fixtures are the same.