Skip to content

v4.1.0 — Every door asks the same question

Choose a tag to compare

@varun369 varun369 released this 23 Aug 09:08
· 25 commits to main since this release
Immutable release. Only release title and notes can be modified.

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 regraph rebuilds 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-code told you to run a command that does not exist.
    It pointed at slm plugin install; there is no slm plugin subcommand, 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-cli has never existed, so the documented
    install could not produce the mcp-remote binary that the config we write
    points at. All three places now name mcp-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
    stopping slm from 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, and slm db migrate --status marks 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.