v4.1.0 — Every door asks the same question
Fixed
-
Nothing was recording which memories a recall actually returned, so nothing
could learn from it. The step that notes "these memories were shown, for this
question, in this session" had no caller anywhere — a worker started with the
service and waited for events that were never sent. On a real store that left
every one of 162 recorded outcomes with no way to trace back to the recall that
produced it, every per-memory usefulness score sitting at its untouched
starting value, and the ranking model unchanged for eleven weeks. Recalls now
leave that record. It costs 1.6 microseconds and is written outside the reply,
so recall returns the same answers in the same time — measured across 30 warmed
queries, the difference was 3.5 ms in favour of the change, which is to say
none. It records only: whether what is learned may reorder your results remains
a separate setting that stays off unless you turn it on. -
Memories stored since the last graph analysis were ranked as unconnected.
Recall weighs a memory partly by where it sits in your knowledge graph. That
position was only recalculated when a consolidation happened to run or you
asked for it by hand, so on a real store 1,036 of 4,034 memories had no
position recorded — including every memory from the previous four days. They
were still found, then ranked as though nothing linked to them. The
calculation now runs on a schedule and again shortly after startup, it covers
every memory including ones with no links yet, and it no longer ranks memories
the store is not allowed to return, which had been diluting the scores of the
ones it can. -
One small calculation could push a handful of memories above everything
else. A separate routine gave every memory in a group the same score and
wrote it where the ranking reads. On a real store that score was more than ten
times the highest genuine one, so those memories took the largest ranking
bonus available for no reason beyond having shared a subject with a few
others. It has been removed; nothing depended on it. -
Turning on per-user access did not apply to agent connections. Company
mode is set from the dashboard, and the setting it writes was read by the web
interface but not by the connection your AI tools use — so with per-user access
on, two users configured, and one of them restricted to read-only, a write
arriving over the tool connection was accepted as the machine owner while the
same write through the dashboard was refused. Both now read the same setting,
and a tool connection that cannot identify who is calling is refused. Personal,
single-user use is unchanged and needs no login. -
The settings could claim a storage engine that was not there. A store
recorded the fast graph and vector engines as active with neither present on
disk and nothing in progress to explain it, so the dashboard reported one thing
while retrieval used another, on every restart. The claim is now checked
against what is actually on disk when the service starts, and dropped if there
is nothing behind it. Nothing is disabled: an engine that is installed is still
detected and used. -
A note about which schema versions had been applied was stored thousands of
times over. Seven versions were recorded as 3,496 rows on one store and
234,348 on another, because the six places that write it all assumed a
uniqueness rule the table did not have. The rule is now there, so those writes
do what they always intended, and the duplicates are collapsed to one row each
keeping the original date. -
A second workspace could take over a memory belonging to the first. Where
two workspaces on one store were handed the same identifier for a memory or a
fact — an import keyed on an external record id, feeding one source into two
workspaces, does exactly this — the later write could claim the first
workspace's entry outright: new owner, new content, and the notes attached to
it discarded, with nothing raised. Whether it was refused or went through in
silence depended on unrelated details of the entry, so neither outcome could
be relied on. A write that would move an entry from one workspace to another
is now refused, and says which workspace owns it. -
Restricting someone to read-only did not restrict every way in. Per-user
access was applied where a memory is written, and not where one is deleted or
corrected over the network, not on four of the compliance controls, and not on
bulk import — which established who was calling and then never asked whether
they were allowed to write. Three ways of reading stored memories back asked
nothing at all. Every entrance now asks the same question of the same setting. -
A workspace that could not answer "what is this person allowed to do?"
assumed the most permissive answer. If that lookup failed — a busy store, a
locked file — the caller was treated as able to write. A lookup that fails is
not a lookup that said yes; it is now refused outright and says so, and the
caller can retry. -
Deleting a memory left its connections behind. The connections were
expected to be removed along with it and nothing removed them, so recall kept
walking links to memories that no longer existed. Both kinds of connection are
now removed with the memory. Erasing a person had the same shape: their entry
was removed from the store and left in the copy that search reads, name and
all, which is not what an erasure request means. -
Housekeeping could remove connections it had no way to announce. If the
copy that search reads could not be told what had gone, the removal was
committed anyway and the two drifted apart silently. It is now undone and
retried instead.slm db regraphrebuilds that copy from the store if they
have already drifted. -
A request to be told what is held about you left out what had been learned
about how you work. Erasure removed it; the export did not include it. Both
now cover the same ground. -
Storing a memory waited on a model that was still loading. A write
computes the memory's vector on the spot so it can be found by asking a
question rather than only by quoting its own words, and gives that one second
before handing the job to the background worker. It started that wait even
when the model had not loaded, which it could never win — loading takes about
ten seconds. On a copy of a real store the first twelve writes after a fresh
start took a median of 1,055 ms and eleven stored no vector; they now take
34 ms. Writes against a loaded model are unchanged at about 75 ms. The service
now loads the model on startup, in the background, so the wait is not simply
moved onto whoever searches first. -
The dashboard reported "Healthy" whenever the service answered at all, and
the page never reloaded after an upgrade because the version it looked for was
never filled in — so an upgraded install kept showing the previous version's
page until someone cleared their cache by hand. Both fixed; the card now
reports what the service is actually doing, including how far the search copy
is behind. -
An unrecognised result was recorded as a mildly positive one. A client
reporting "ok" instead of one of the three accepted words had that counted as
partial success and fed to the ranking as though somebody had meant it. It is
now refused. An answer can also be reported on by name: recall returns an
identifier and a result quoting it is matched to that exact answer rather than
guessed at from overlap within a time window. -
A hosted model was reported as available before a key was set for it.
Switching to Mode C names a provider and leaves the key empty until you supply
one, and every surface said nothing was wrong while each model-backed feature
was about to fall back to assembling from your notes. It now says which step
is missing. -
Repeated failures in a background task looked the same as one hiccup. A
step failing every cycle for days reported exactly what a single transient
failure reported. It now says how long it has been failing. -
Upgrading a large store could appear to hang. Collapsing duplicated
schema-version records compared every record against every other of the same
version; on a store with 234,348 of them that had not finished after 25
minutes. It now takes 324 ms, and the whole upgrade of that store takes 40
seconds with every memory, fact and connection preserved. -
A refusal from the service was reported as a crash. Commands that were
correctly denied printed a stack trace instead of the reason, and one denial
told users to run a command that does not exist. -
slm connect claude-codetold you to run a command that does not exist.
It pointed atslm plugin install; there is noslm pluginsubcommand, so
the only instruction it gave ended in an error with nothing else offered. It
now names the two Claude Code commands that actually install the plugin, and
the setup wizard that runs both for you. Reported by @barrygfox (#123). -
The bridge install in the IDE guide named a package that is not on npm.
@modelcontextprotocol/client-clihas never existed, so the documented
install could not produce themcp-remotebinary that the config we write
points at. All three places now namemcp-remote, which does provide it.
Reported by @tonydzi (#122). -
Two settings did not survive a restart. The consistency threshold and the
per-channel retrieval weights were never written to the config file and never
read back, so tuning either one — by hand or by switching mode — lasted until
the next restart and then reverted to the default with nothing said. Both are
now saved and restored, a pair stays a pair through the file, and a section
someone has hand-edited into nonsense falls back to defaults instead of
stoppingslmfrom running. Reported by @barrygfox (#124). -
A migration failure said which, never why. A migration recorded complete
is re-checked by its own verification on every start; when that check stops
passing, the log still reads complete while the health endpoint reports a
failure, and nothing said the two were answering different questions. The
health output now carries the reason for each failure, andslm db migrate --statusmarks any migration whose recorded state and actual end-state
disagree instead of only reprinting the log. Reported by @unfall103-debug
(#125).
Changed
- Every table that only grows now has a stated limit, or states why it has
none. Three of them had a cleanup routine, each written and wired
separately; the fourth was found by reading a disk-usage report and the fifth
by reading the fourth. All 45 are now declared in one place with the reasoning
attached, one pass enforces them, and a table added without a decision is
something the tests can see. On a real store the first pass removed 7,342 rows
in 0.4 s, and 123,918 on a larger one — the biggest single group being 83,623
records describing memories that had already been deleted.
Nothing that holds your memories, your corrections, an erasure record, or the
feedback that improves ranking is touched; each of those says so explicitly.
Two tables are recorded as growing faster than the store with no rule decided
yet, rather than being quietly given one. - Recall assembles your knowledge graph from the graph store. On a store with
208,000 connections that assembly took 2.5 seconds and now takes 0.4. It runs
whenever the graph changes, so it was on the path of a recall. The answers are
the same — every probe query returns the same memories with the same scores
from either source. Recalls that also span shared or global memories continue
to read SQLite, which is the only store that holds them.