Motivation
A model forgets everything between sessions, and the note it writes on the way out is a summary. Memory here is plainer than that: files under/memories, read and written through one contract, with the reading step
told that the contents are data, not instructions. Your program decides
which files a step sees, so a step’s context holds the few files that
matter and nothing stale.
Parts
- The port.
@obversa/apidefines one storage-neutralMemoryinterface: ascopeand oneexecutemethod that takes a command,view,create,str_replace,insert,deleteorrename, on a path under/memories. A result isok: truewith a typed value, orok: falsewith one of a fixed set of error codes, so a step handles every adapter the same way. - The adapters.
@obversa/memory-simplekeeps files in one process, for tests and short runs.@obversa/memory-gitkeeps each scope in a private Git reference, with no commits and no change to your branch, index or worktree, so memory outlives the process.@obversa/memory-markdownopens a directory of your own Markdown files as a read-only corpus, finds the passages that match, and returns each with its path and line range. The runtime imports none of them; pass the one you chose torun()asmemory, and every job in the run reads it from its context. - The mechanics.
@obversa/runtime/memorygives three functions that work over the port and choose no engine.groundreads the sources you name into one prompt, headed by a warning that the text is untrusted data.curatecalls a function you supply to pick which of those documents apply and write a short brief.consolidatefolds several files and an earlier summary into one target and writes it once. Each has a size limit, so a step’s memory never crowds out its task. - The reason in the commit. A stage that runs in its own worktree with
isolated(step, { record })can carry anopenReasoningRecordfrom@obversa/memory-git. It captures what the writer said while it worked and puts the why into the body of the commit that carries the change, making no commit of its own. Blame a line later and the reason is there.
Example
One grounded document and a curate step that picks it:examples/memory.ts
curate hands the grounded documents to your decide function, checks
that every source it names exists, and returns the brief with the paths it
rests on. In a team, decide is an engine call, and the brief is what the
next step reads instead of the whole corpus:
Example record
Limits
- One file holds 65,536 bytes, one scope 1,048,576 bytes and 256 files, in the simple and Git adapters by default. Past a limit, the adapter removes the earliest-written files until the write fits, never the file being written.
- Six file types.
.txt,.md,.json,.py,.yamland.yml. - The Markdown corpus is read-only.
viewreads it; every writing command returns an error, so a search can’t edit what it selects. - A Git reference travels with the repository. Mirror clones, mirror
pushes and
--allbundles copy it in plain text, and a deleted path stays in Git’s objects until Git removes unused ones. Don’t treat deletion as secure erasure.