Run the store day to day
memhtml write --title "One writer and many readers share the index" --type semantic \ --claim "WAL admits a single writer at a time and any number of concurrent readers."memhtml apply --file ops.jsonl # many memories, ONE commit, ONE index passmemhtml search "one writer many readers"memhtml recall "one writer many readers" --budget 16000memhtml read areas/inbox/some-memory.htmlmemhtml list --type semantic --limit 50Reach for memhtml apply once a task writes more than about three memories. A batch stages every file, makes one commit, and reindexes once, where the same memories written one at a time make one commit and one index pass each. apply is atomic by default, and --continue-on-error makes it best-effort. Both apply and exec read stdin when you omit --file or pass -.
Every removal is a move into the archive
Section titled “Every removal is a move into the archive”memhtml correct <target>writes the superseding file and archives the target in one commit, so an interrupted run can never leave two live memories contradicting each other.memhtml archive <path> --reason ...is agit mvintoarchive/<YYYY>/, with the original path mirrored beneath.--include-archivedonsearch,recall,list, andtask listbrings evicted memories back into view. They still exist; eviction moved them.
git log --follow therefore reads straight through a memory’s whole life, including the eviction.
Reinforcement has a cooldown
Section titled “Reinforcement has a cooldown”memhtml reinforce <path> bumps the access bookkeeping for a path at most once every 900 seconds (packages/domain/src/ranking.ts:17), so a loop reinforcing one path records one bump. That number is written once in the domain layer and once in the salience arm’s SQL, and a property test pins the two to agree at the boundary.
memhtml reinforce areas/inbox/some-memory.html --signal positive--signal takes positive, negative, or neutral, and neutral is the default. A neutral signal bumps the access count without claiming the memory was right. positive and negative are the two values that move the outcome EWMA, a running average of how well a memory has served.
What moves the access plane
Section titled “What moves the access plane”Read this section when a salience number surprises you. Salience is one of retrieval’s four ranking arms, alongside full-text search, vector similarity, and recency, and it favors memories you have opened before by reading the access plane.
Three things bump a memory’s access count:
memhtml read <path>and thememory_readtool, because an explicit open is a chosen memory.- The
memhtml://file/{path}MCP resource, which funnels through the same use case. memhtml reinforce, which is the explicit channel.
Two things leave the count alone:
memhtml searchandmemhtml recall, however many paths they return.- Every sleep phase.
Those two are the ranker’s guess and the schedule’s sweep, and counting either would feed the ranker’s own output back into its input. Today’s top five would rank higher tomorrow purely for having been listed, while the memory that should displace them would never get a first bump.
So a corpus that has been searched all day and never read has an empty state.access. That is the expected state.
The salience arm also ignores task rows and everything under resources/people/. Their access counts exist and never affect a rank, because you reach a task by predicate and a person by key. Salience there would reward a stale task and decay a person’s identity.
Working-tree edits are legitimate
Section titled “Working-tree edits are legitimate”memhtml index update projects uncommitted changes as well as committed ones, so a file you edited by hand is searchable before you commit it. You own the commit, though, and memhtml sleep run refuses to run on a dirty tree.
The cron schedule
Section titled “The cron schedule”*/10 * * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml index update --embed >> /var/log/memhtml/index.log 2>&117 * * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml trace index >> /var/log/memhtml/trace.log 2>&130 3 * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml sleep run >> /var/log/memhtml/sleep.log 2>&10 6 * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml publish >> /var/log/memhtml/publish.log 2>&10 7 * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml doctor >> /var/log/memhtml/doctor.log 2>&1Each line is safe to repeat. An unchanged HEAD and a clean tree touch nothing, and index update on a converged store answers unchanged: true and writes nothing:
{ "apiVersion": "1", "type": "index.report", "data": { "mode": "update", "headSha": "4e232759bfad745b0445ecd83cc9883c30a0c426", "unchanged": true, "added": 0, "modified": 0, "removed": 0, "renamed": 0, "dirty": 0, "embeddingsWritten": 0, "skipped": [] }}memhtml publish is deterministic to the byte, so two runs over an unchanged corpus write nothing and commit nothing.
memhtml sleep merge stays off the cron. A curation run rewrites confidence across the corpus and archives memories, so the branch waits for a person to read memhtml sleep review first. A sleep run produces the branch, and landing it is a decision. See run and review a sleep cycle.
Reading the log files
Section titled “Reading the log files”The cron lines above send both streams to one file, which suits a human reader and defeats a parser. The envelope is pretty-printed across many lines by default, and the progress and warning lines on stderr interleave with it. Split the streams and add --dense, which minifies the JSON and drops null fields, so each run contributes exactly one line:
*/10 * * * * cd $HOME && MEMHTML_ROOT=$HOME/memhtml memhtml index update --embed --dense >> /var/log/memhtml/index.jsonl 2>> /var/log/memhtml/index.errtail -1 /var/log/memhtml/index.jsonl | jq '.data | {added, modified, embeddingsWritten}'--dense is also what you want when the output goes into a model’s context window rather than a log.