Skip to content

Releases: Hilbras/Remembra

Remembra 5.7.1 — retrieval, SDK and cold-start fixes

Choose a tag to compare

@lacrous lacrous released this 30 Sep 16:11

Three fixes, all found by installing 5.7.0 from the registry and using it, plus two
cold-start races found by building a reproduction rather than re-running a suite.

Full compatibility statement: docs/v5.7.0-compatibility.md.
Findings and measurements: docs/v5.7.0-audit.md.

Fixed

  • Roadmap §37's budget is reachable through the typed SDK. SearchInput — which
    the SDK's SearchOptions is an alias of — never gained budget, so a typed caller
    could not set a bound. Adding it exposed a second half: the SDK serialises query
    parameters with String(value), so a nested object became the literal string
    "[object Object]" and the server ignored it. The SDK accepted the argument, the
    type allowed it, and the bound was never applied. Nested parameters are now
    flattened as parent.child. The response side had the same gap in reverse: the
    server has returned budget since §37 shipped and SearchResponse did not declare
    it.

  • Non-ASCII queries now reach the lexical path. extractQuery filtered query
    terms with /^[a-z0-9]+$/u, so every non-ASCII query produced zero terms, no
    keyword list was built, and retrieval fell back to the vector path alone — nothing at
    all without an embedding provider. A CJK, Russian, Greek, Hangul or Thai query
    returned an empty result set with no error and no warning, which is
    indistinguishable from a query with no match. Both halves of retrieval now share one
    tokeniser, so they cannot drift apart again.

    One ASCII behaviour changes, pinned by a test: a query with internal punctuation now
    contributes its alphanumeric parts, so don't contributes don and matches a
    document containing don't.

  • latest N <query> now parses. TEMPORAL_RE anchored its alternation with $,
    so each branch had to consume the entire query — latest 3 errors fell through and
    was tokenised as the ordinary words latest and errors. Both branches now carry a
    trailing remainder, with the digit count still required so latest news … stays an
    ordinary query.

    This restores parsing, not meaning. The qualifier's values are read in exactly
    one place, where they switch the recency multiplier from 0.5 to 2. latestCount
    still does not limit to N, and before/after still filter nothing. Measured, with
    four documents of decreasing age and all four matching:

    search(pool, "incident review")   -> d1, d2, d3, d4
    search(pool, "latest 2")          -> d1, d2, d3, d4
    search(pool, "before 2026-08-01") -> d1, d2, d3, d4
    
  • Two cold-start races in the batch idempotency ledger. Found by building a
    reproduction, because the symptom was a single MATRIX-02 failure that passed on every
    rerun.

    • A peer's integrity-key staging file made the next start fail. link() needs its
      source to exist, so publishing the key necessarily has a window in which a
      .claims.key.<pid>.<hex>.tmp is visible in the claim directory, and the
      constructor's allowlist rejected that name. Only a staging file this code creates
      is now tolerated, and the constructor waits for its publisher. Anything else is
      still refused immediately.
    • The ledger identity was published with open(O_EXCL) followed by a write, so
      the file existed empty before being filled; a peer reading in between refused to
      start with "ledger identity is invalid". Same defect as the zero-byte integrity key
      fixed in 5.6.0, which survived because that fix was applied to claims.key and not
      to claims.identity. Now published with link() too.

    Measured over 60 rounds of 16 concurrent cold starts: "unrelated files" goes from
    routine (hundreds of failures) to zero; "ledger identity is invalid" from ~1 in 300 to
    zero.

    Two rarer symptoms in the same family remain and are not fixed — they are in code
    this release does not touch, and a per-symptom wait is what produced five
    manifestations in the first place: "ledger database exists without its identity" and
    "database could not be opened" (SQLite contention), about 3 occurrences across
    ~250,000 cold starts. This store has now produced six manifestations; the real fix is
    a single atomic initialisation, and this release does not pretend six patches reach it.

Known limitations

  • The temporal qualifier parses but does almost nothing beyond the recency multiplier.
    See audit S8.
  • The benchmark set contains no temporal query and no non-ASCII document, so the gate
    cannot see two of the fixes above. The unit tests cover them. Both are gaps in the
    gate, recorded rather than papered over.
  • Two cold-start symptoms listed above remain in the ledger.

Gates

940 test suite · 142 security matrix · 52 recovery matrix · Python SDK ·
47 documentation files · 23 benchmark scenarios · 0 audit findings · 0 npm
vulnerabilities. Full release:check passed for @hilbras/remembra@5.7.1 on
Node 18.20.8.

Remembra 5.7.0 — Advanced Retrieval Engine (§31–§38)

Choose a tag to compare

@lacrous lacrous released this 30 Sep 07:34

Roadmap §31–§38 — the Advanced Retrieval Engine milestone.

Full compatibility statement: docs/v5.7.0-compatibility.md.
Benchmark gate: docs/benchmark-gate.md. Audit: docs/v5.7.0-audit.md.

Two default-on behaviour changes

Everything else here is additive with unchanged defaults. These two are not.

  1. Exact duplicate results are collapsed. Two records of the same statement are
    one answer to a question. Escape hatch: dedupeExact: false.
    Same-source collapsing is off by default (dedupeSameSource) — identical text
    from two different sources is often corroboration, and discarding the second
    source is a worse error than showing a repeat.
  2. Superseded memories are excluded. A query for the current policy no longer
    returns v1 and v2 side by side. Escape hatch: includeSuperseded: true.
    Suppression applies only when the replacement is actually available, so a
    dangling supersededBy keeps the old copy visible rather than replacing it with
    nothing.

Neither deletes anything. The store still holds every version, each readable by id.

New in this release

  • Token efficiency and duplicate rate in the evaluation harness — the two of
    §38's seven metrics that were missing, and the guardrail the deduplication work
    needed.
  • Whole-token matching, phrase scoring and field weights. A query for cat
    scored "concatenate the streams" at exactly the same 60.00 as "the cat sat",
    because matching was a single String.includes.
  • Real inverse document frequency. The old "idf" was computed from the document
    being scored
    — a coverage discount wearing the name of IDF, which cannot tell a
    rare term from a common one.
  • One retrieval budget object covering all four §37 dimensions. Exceeding
    maxLatencyMs returns the best results so far and reports the truncation rather
    than erroring: a bound that returns nothing has not degraded, it has failed.
  • Configurable fusion weights via retrieval.fusionWeights or
    REMEMBRA_RETRIEVAL_FUSION_WEIGHTS. Every default reproduces the previous
    behaviour exactly.
  • A retrieval benchmark gate is now a release stage. §38 asked that every
    retrieval change be measured; that was a rule people could forget. A 23-scenario
    set now runs through the real pipeline and fails on a regression in precision,
    recall, MRR, nDCG, duplicate rate or a latency percentile.

Fixed

  • §37's budget was unreachable and its report was discarded.
    MemoryService.search neither accepted a budget nor returned the report — the
    budget was computed and then dropped, so no caller could set a bound or learn
    which one bound. Both fixed, and the budget is settable over HTTP as
    ?budget.maxItems= and friends, with a malformed bound rejected rather than
    silently ignored.
  • The relation-expansion re-rank built its search policy separately from the
    first search, so a configured fusion weight could apply to one ranking and
    silently not the other.

Known limitations

  • Non-ASCII queries return no lexical results. extractQuery filters query
    terms with /^[a-z0-9]+$/u, so a query in any non-ASCII script produces zero
    terms and retrieval falls back to the vector path alone. The CJK branch in
    tokenize is therefore unreachable from the query path. Documents in those
    scripts are stored and tokenised correctly; querying is what is affected. Not
    fixed here because the one-line change alters what existing deployments get back
    for non-ASCII input. Full analysis in docs/v5.7.0-audit.md (S7).

Unchanged contracts

Four runtime dependencies, unchanged: @modelcontextprotocol/sdk,
better-sqlite3, yaml, zod. redis remains an optional peer and is not even a
devDependency. No new subpath, no removed export, /health/ready byte-identical to
5.6.0, and every V4.9/V5.0.3 security boundary intact.

Gates

923 test suite · 142 security matrix · 52 recovery matrix · Python SDK ·
47 documentation files · 23 benchmark scenarios · 0 audit findings · 0 npm
vulnerabilities. Full release:check passed for @hilbras/remembra@5.7.0, on
Node 18.20.8 and Node 24.21.0.

Remembra 5.6.0 — distributed Remembra

Choose a tag to compare

@lacrous lacrous released this 29 Sep 11:52

Distributed Remembra: cross-instance primitives, an optional Redis backend, and an
explicit process split. A single-process installation is unaffected — with no
REMEMBRA_REDIS_URL the optional package is never imported, no shared store is
opened, and the readiness payload is byte-identical to 5.5.1.

Runtime dependencies are unchanged at four. redis is an optional peer and is
not even a devDependency, which is what keeps its missing-package startup failure
a real test rather than a mocked one. Six new published subpaths: ./lock,
./job-store, ./worker, ./redis, ./shared-state, ./process-roles.

Not verified: a live Redis server. The quota scripts are executed through a
Lua 5.3 VM with a shim of the Redis commands they use (28 checks, covering their
logic but not a real server), and the failure matrix verifies our response to a
store outage rather than Redis itself. Treat shared-state operation as
reviewed-and-unit-tested until a Redis instance is in CI.

Added

  • The §30 failure matrix (src/test/failure-matrix.test.ts, with a real
    second OS process in src/test/matrix-peer.ts): all nine distributed-failure
    scenarios, each asserting a stated invariant rather than "did not crash". The
    two-process rows force the same expected version into both processes rather
    than relying on interleaving, so they detect the lost-update defect that
    motivated the milestone. A live Redis is still not exercised — the
    outage rows verify our response to a store outage, not Redis itself.

  • Process roles (src/process-roles.ts, published as
    @hilbras/remembra/process-roles): remembra serve, remembra worker, and
    remembra scheduler. The no-subcommand path is unchanged and still requires no
    configuration, so a single-process install is unaffected. A role without an HTTP
    surface refuses --http/--port and refuses data verbs rather than ignoring
    them — a worker that silently bound a port would serve traffic from a process
    whose premise is that it has no HTTP surface. worker runs a durable worker over
    a SQLite ledger; scheduler only enqueues periodic jobs, so exactly one worker
    anywhere claims each one.

  • Shared-state configuration with honest degradation
    (src/shared-state.ts, published as @hilbras/remembra/shared-state). The
    capability manifest advertises distributed unconditionally — it describes the
    build, like webhooks, not the configuration. A single-process readiness
    payload is byte-identical to the previous release: it does not even gain a field
    saying "absent". When a configured store becomes unreachable the limiter
    fails closed with 503 rather than degrading to per-instance limits, which
    would let a fleet exceed a tenant's quota while every instance reported a limit
    it was not enforcing; readiness reports unready so a load balancer drains the
    instance, liveness stays up so it can be diagnosed, and it recovers on its own
    at the next successful operation. Reports carry a classified label only — never
    the URL, host, port, or password.

  • An optional Redis adapter (src/redis.ts, published as
    @hilbras/remembra/redis) providing RedisLockProvider and
    RedisQuotaRateLimiter. redis is an optional peer dependency and not a
    runtime dependency; the only reference to it is a dynamic import(). There is
    no silent fallback: with REMEMBRA_REDIS_URL unset the process imports no
    Redis code at all, and with it set but unreachable, startup fails rather than
    degrading to per-instance state that would report a limit it is not enforcing.
    Locks use SET … NX PX so the lease and its expiry are one atomic operation,
    and renew/release are owner-checked by the server.
    Quota is evaluated as one Lua script across every dimension rather than one
    limiter per dimension, because the in-process limiter's mutex does not cross
    instances and interleaved dimension checks would let a shared organization
    budget be exceeded. Precedence and policy validation match the in-process
    limiter exactly, and each dimension keeps its own window.
    The script bodies are verified by running the shipped text through a Lua 5.3 VM
    (scripts/verify-redis-lua.mjs, 28 checks, not a release gate). A live Redis
    server is not exercised by the test suite.

  • A durable worker (src/durable-worker.ts, published as
    @hilbras/remembra/worker), the shared-execution counterpart to the in-memory
    JobQueue, which is unchanged and remains what a single process uses. Its
    centrepiece is the partition guard: a claimed job's lease is renewed while it
    runs, and if a renewal ever fails the job's AbortSignal fires — finishing
    anyway would be the lost-update failure below wearing a lease. A refused
    complete() is reported as lease_lost rather than as success. Claims are
    restricted to the types a worker declares, so a heterogeneous fleet never
    claims work it cannot run, and stop() releases in-flight leases so a peer
    takes over immediately instead of waiting out the lease. The poll timer is
    deliberately not unref'd, since §28 allows a worker-only process that has
    nothing else holding its event loop open.

  • A durable job ledger (src/job-store.ts, published as
    @hilbras/remembra/job-store), the second distributed primitive. All six §27
    states and ten fields, with InMemoryJobStore and SqliteJobStore behind one
    JobStore interface. A claim is a single UPDATE ... RETURNING, so the
    database picks the winner and returns the row it actually claimed — a
    read-then-write is the shape that produced the lost update below. Claims are
    ordered oldest-first with the id as tie-break, so every claimer agrees on the
    next job. A dead worker's job returns to retrying once its lease expires, and
    renew/complete/fail return false for a lease the caller no longer
    holds. Payloads, job types, tenant digests, and stored error labels are all
    bounded. 28 tests, with the whole behavioural suite run against both
    implementations so they cannot drift.

  • Lease-based locking (src/lock.ts, published as @hilbras/remembra/lock),
    the first primitive of the distributed-runtime milestone. Every acquisition is a
    lease with an owner and an absolute expiry rather than a lock, so a crashed
    holder is reclaimed instead of deadlocking. release() is owner-checked and
    cannot clear a peer's lease; renew() returns false once a lease is lost, so
    a partitioned worker stops rather than resuming on a belief it still holds it —
    the same failure as the lost update below, wearing a lock. Two implementations
    ship: an in-process provider (the default) and a file-backed one that reclaims
    on the expiry timestamp rather than pid liveness, which is what makes it
    portable beyond one host. lockKey hashes any non-digest part, so a raw tenant
    cannot reach a key that is written to disk. Documented in
    docs/lock.md. 15 tests, two of which caught defects in the
    implementation: an unreadable lease file livelocked acquire forever, and
    isHeld() never observed the lease it had just written.

Fixed

  • A cold-start race in the batch idempotency ledger. Two processes
    starting against a fresh ledger directory could observe each other's
    not-yet-0600 claims.sqlite — better-sqlite3 creates the file with the
    process umask and it was only chmoded after the open — and one would refuse to
    start with "claim database must not be group/world accessible". The permission
    check is unchanged; the file is now created 0600 with O_CREAT|O_EXCL before
    it is opened, so there is no window to lose. Without this a fleet cannot
    cold-start against a fresh store.

  • A spurious IO_ERROR from a racing delete on the file backend. Two
    MemoryStore instances deleting the same memory could have one unlink the file
    after the other resolved it; the resulting ENOENT surfaced as an error to a
    caller that had done nothing wrong. ENOENT on the unlink is now the
    already-reported "missing" result, so a racing delete is genuinely idempotent.
    This was a pre-existing intermittent failure, caught by a release gate and
    disproving the audit's own assessment of forget as safe — idempotent state
    is not the same as a non-throwing call.

  • A cross-instance lost update on the SQLite backend, with a success
    response.
    MemoryBackend requires mutating calls to be safe under
    "cross-process concurrency (advisory lock)", and SqliteBackend only
    satisfied the same-process half: withLock is a promise queue over one
    instance's field, with no BEGIN IMMEDIATE and no db.transaction() anywhere
    in the file. update read the row, checked expectedVersion in application
    code, and then wrote WHERE id = ? with no version predicate — so two
    instances that read the same version both passed the check and both wrote.
    Reproduced 5 times out of 5 with two instances over one data.sqlite: both
    were told they wrote version 2, and one write was discarded with no error.
    The write is now a compare-and-swap on the version it read, and a zero-row
    result distinguishes NOT_FOUND from CONFLICT so a caller does not retry a
    write whose row is gone. Six tests in src/test/sqlite-cas.test.ts cover the
    race, the error text, delete-in-between, and the two behaviours that must not
    change. Found by
    v5.6.0-audit.md finding S1, the first task of the
    distributed-runtime milestone — reachable before any distributed work, and
    routine after it.

Known limitation, now measured

The native RemoveEnvironmentCleanupHook abort previously documented as a test
teardown nuisance can kill a running server on Node 24. Measured against the
published 5.4.0 and 5.5.1 packages under 2 500 requests of mixed write,
search, and scrape traffic:

Runtime Result
Node 18.20.8 2 000 requests, n...
Read more

Remembra 5.5.1 — log field-policy fixes

Choose a tag to compare

@lacrous lacrous released this 27 Sep 16:49

Remembra 5.5.1 — a patch release for two defects in the log field policy
introduced by 5.5.0.

Both were found by a post-release audit that enumerated every logEvent field
name in the tree and diffed it against the policy's lists, rather than by reading
the code — in both cases the call site looks perfectly correct.

npm install @hilbras/remembra@5.5.1
pip install hilbras-remembra==5.5.1

A documented diagnostic was silently removed

file sat on the blanket drop list alongside path and root. So
memory_parse_skipped and memory_normalized stopped emitting the file field
that the documentation advertises:

5.5.0  {"event":"memory_parse_skipped","msg":"...memory file bad.md"}   ← no file field
5.5.1  {"event":"memory_parse_skipped","file":"bad.md","reason":"missing frontmatter"}

The event still said a memory file was unparseable, but not which one — which is
the only thing an operator needs from it.

A bare basename is not a disclosure; a path is. Filename fields now pass through
only when the value is bounded, separator-free, and free of parent references, so
/home/someone/.remembra/secret.md, ../../etc/passwd, C:\Users\..., and an
over-long name are all still refused in a file field.

The policy only inspected top-level keys

applyLogFieldPolicy ran once on the caller's field object, so a nested path
walked straight through:

logEvent("warn", "x", { details: { path: "/home/someone/..." } })
5.5.0  {"event":"x","details":{"path":"/home/someone/..."}}   ← leaked
5.5.1  {"event":"x","details":{}}

The redactor was already recursive; the policy was not. It is now applied at
every depth — three levels down, and inside objects held in arrays — with the
top level left alone so an identity is not hashed twice.

Also in this release

The roadmap's renumbering table — added at the 5.5.0 release — was garbled, was
missing the V5.2 and V5.3 slots entirely, and claimed a shift its own table did
not describe. It is corrected, and the compatibility report matches it.
docs/observability.md notes the policy's history so the next reader knows the
rules were tightened after the fact.

Release evidence

Gate Result
Build pass
Full suite 688 tests, 0 failures
Security matrix 142 tests, 0 failures
Recovery matrix 52 tests, 0 failures
Python SDK 28 tests, 0 failures
Documentation links 41 files, 0 broken
npm audit --omit=dev 0 vulnerabilities
Node runtimes 18.20.8 and 24.21.0
Registry install verified from npm and PyPI after publication

Compatibility

No route, environment variable, metric, or default changes. The only difference
from 5.5.0 is which log fields survive. ^5.5.0 consumers pick this up
automatically.

Remembra 5.5.0 — production infrastructure

Choose a tag to compare

@lacrous lacrous released this 27 Sep 14:22

Remembra 5.5.0 — production infrastructure.

This is the roadmap §17–§24 milestone the plan calls V5.1.0. It ships as
5.5.0 because V5.4.0 was released first, so the planned 5.1.0 is now a
downgrade of an already-published version. The roadmap carries a renumbering
banner so the shift is explicit; milestone intent is unchanged.


Install

npm install @hilbras/remembra@5.5.0
pip install hilbras-remembra==5.5.0

What is new

Rate limiting — a RateLimiter interface (check / consume / reset,
async) with a bounded in-process implementation, injected through
HttpOptions.rateLimiter. check decides without charging, so a caller can ask
whether it has budget without spending it.

Quotas — REMEMBRA_QUOTAS sets per-dimension policies across organization,
project, user, agent, API key, IP, endpoint, and provider, layered on top of the
base budget. Precedence is the declared dimension order, charging is
all-or-nothing, and a 429 now names the dimension that refused it without ever
naming a principal.

Health — /health/live, /health/ready, /health/storage, and
/health/provider. Liveness is public and reads process state only, so it stays
200 when storage is broken and reports draining once a shutdown begins.

Metrics — p50/p95/p99 over recorded histograms, the six series §21 named that
did not exist, and a hard bound on label cardinality.

Logging — every line carries requestId, operation, and durationMs
through an AsyncLocalStorage request context, so events from the service,
storage, provider, and job-queue layers correlate without threading anything by
hand.

Shutdown — a bounded, idempotent, honest sequence in both HTTP and MCP mode.


Eight defects found and fixed

This release opened with an audit, and the two with the most weight:

The rate limiter was an unbounded memory-exhaustion vector, reachable without
a credential.
A window was reclaimed only if its own key was checked again
after expiry, so any identity used once and abandoned lived for the process
lifetime — 50,000 abandoned keys retained 50,001 entries. The anonymous bucket is
charged before authentication and keyed by source address. Now capped at
10,000 with a deterministic sweep and least-recently-used eviction.

A concurrent burst bypassed the quota entirely. Decide and charge were
separated by an await, so 500 simultaneous requests against an organization
cap of 20 were all admitted. A rate limiter that can be defeated by sending
requests in parallel is not a rate limiter. Found by the stress matrix, fixed by
serializing decide-and-charge, pinned by three regression tests.

The other six: /health performed a full storage scan and a durable
recovery-state write on every unauthenticated call; /metrics was rate limited
against its own documentation, so a scraper lost observability during the storms
it exists to diagnose; seven metrics were declared and never written, so
dashboards on them rendered empty and looked like data loss; the retrieval debug
trace logged raw query text behind the wrong environment flag and
migration_start logged a storage path unconditionally; MCP mode installed no
signal handler at all, so SIGTERM killed it with no drain; and an injected
embedding adapter bypassed provider metrics entirely — the path V5.4 most wanted
working.

Two of the test's own premises were also wrong, and were corrected rather than
papered over: refused requests under concurrency turned out to be 429 from the
default budget rather than the concurrency cap, and a 14 KB payload that appeared
to lose data was the token rule of the default sensitive-data policy matching
hyphen-separated words — a shape judgement, not a size limit.


Release evidence

Gate Result
Build pass
Full suite 684 tests, 0 failures
Security matrix 142 tests, 0 failures
Recovery matrix 52 tests, 0 failures
Python SDK 28 tests, 0 failures
Documentation links 40 files, 0 broken
npm audit --omit=dev 0 vulnerabilities
Package dry run 249 files, 564.4 kB
Tenant benchmark pass (within documented ceilings)
Scale benchmark pass (within documented ceilings)
Node runtimes 18.20.8 and 24.21.0

Not claimed

No distributed quotas, no distributed workers, no shared-state runtime, and no
advanced retrieval engine. The rate limiter, quota ledger, webhook queue, and
idempotency ledger remain single-host and in-process. The interfaces added here
exist so a shared implementation can be added later without changing the
request path — adding one is later work and is not claimed.

Compatibility

Additive everywhere. Authentication still precedes rate-limit consumption, rate
identity is still an opaque digest (now enforced rather than merely
conventional), REMEMBRA_RATE_LIMIT and REMEMBRA_RATE_WINDOW_MS still
configure the base budget, /health returns the same shape and status codes, and
SEC-RL-001, SEC-RL-002, and SEC-LOG-001 pass unmodified.

Four new limits a deployment could notice are listed in
docs/v5.5.0-compatibility.md.

Remembra 5.4.0 — Developer platform

Choose a tag to compare

@lacrous lacrous released this 26 Sep 00:02

Remembra 5.4.0 — Developer platform

Remembra is external memory for AI assistants. This release makes it
straightforward to integrate: explicit API versioning, hardened SDKs in two
languages, frozen batch semantics with durable idempotency, signed webhook
delivery, and a machine-readable contract with a verified example suite.

It is additive. Existing /api/v1 paths, legacy routes, and response bodies
are unchanged, and no upgrade action is required.

What is new

  • Versioned API metadata — GET /api/v1/capabilities and an
    X-Remembra-API-Version header, with /api/v1 remaining the canonical
    namespace. No second /v1 alias.
  • Hardened TypeScript SDK — typed snapshot create/restore, bounded timeout
    composition, request-ID propagation, typed network-error normalization,
    opt-in read-only retries, stable opaque cursor pagination, and a client-side
    batch budget guard.
  • Frozen batch contract — explicit per-item and read-only execution
    metadata, sanitized per-item failures, bounded read-only batch search, and a
    transactional SQLite Idempotency-Key ledger with current-authorization
    preflight and recheck, integrity-protected rows, non-expiring claims,
    capacity limits, and a durable data gate.
  • Signed webhook delivery (@hilbras/remembra/webhooks) — a closed event
    set, allowlist-only payloads with no embedding vectors or credential-like
    fields, HMAC-SHA256 t,v1 signatures with a bounded timestamp window, a
    receiver replay guard, an integrity-protected durable queue, and bounded
    exponential backoff. Opt-in through REMEMBRA_WEBHOOKS.
  • Receiver examples — the delivery contract is only useful if the
    subscriber checks it, so verified Node and Python receivers ship alongside
    the sender. Each reads the raw body, verifies x-remembra-signature against
    those exact bytes, and rejects a repeated delivery id. An end-to-end test
    drives the real CLI into the real receiver and confirms a forged body, an
    expired timestamp, a foreign secret, and a replayed delivery id are each
    refused.
  • Dependency-free Python SDK (hilbras-remembra) — synchronous and
    asynchronous clients with the same routes, authentication, error hierarchy,
    retry policy, pagination, and idempotency rules, using only the standard
    library. A parity test reads the published TypeScript contract so the two
    SDKs cannot drift apart.
  • OpenAPI 3.1 contract and examples — docs/openapi.yaml documents the
    full /api/v1 surface, with a regression test that fails when a served
    route, limit, idempotency rule, or capability is undocumented. Runnable
    examples cover Node, Python, FastAPI, RAG, local-LLM, Next.js, React, and an
    AI agent.
  • An honest CLI — remembra --version and remembra --help answer before
    any configuration validation, storage-directory creation, or server start.

Safety properties that are now enforced

  • The durable data gate records its owner. A data restore is completed with
    remembra recover verify; an interrupted tenant migration stays resumable
    through migrate apply, and verification refuses to publish a half-applied
    migration.
  • An unsafe or replaced gate marker is never auto-cleared and can no longer
    discard replay claims.
  • A released keyed-batch claim keeps binding the key to its original operation,
    so a released key can never be reused for a different operation.
  • A pre-existing claim ledger is verified row by row and rebuilt in place, so
    replay history survives an upgrade while tampering still fails closed.
  • An unreadable durable recovery state fails that one write closed without
    latching Failed for every later write and read.
  • Webhook payloads never carry embedding vectors, provenance internals, or
    credential-like fields, and a failed delivery can never fail the write that
    produced the event.

Gate evidence

Verified on Node 18.20.8 and Node 24.21.0:

Gate Result
Build pass
Full suite 567 tests, 0 failures
Security matrix 139 tests, 0 failures
Recovery matrix 52 tests, 0 failures
Python SDK 28 tests, 0 failures
Documentation links 39 files, 0 broken
npm audit --omit=dev 0 vulnerabilities
Package dry run 241 files
Registry install npm install @hilbras/remembra@5.4.0; every subpath imports and a signature verifies
PyPI publication pip install hilbras-remembra==5.4.0; zero dependencies, identical batch limits
Artifact identity published wheel and sdist hash-match the artifacts built from this commit
Tenant and scale benchmarks within documented ceilings

Deliberate non-claims

V5.4.0 does not claim distributed quotas, distributed workers, or the advanced
retrieval engine — V5.1 through V5.3 remain unfinished. Public batch embedding
remains unavailable, idempotency and webhook delivery state are single-host,
and no V5.5 tenancy or V6 runtime work is included.

Known limitation

The pinned better-sqlite3 11.x binding can abort a test process during
environment teardown after every assertion has reported. The test scripts
re-run only the aborted files, log each recovery, and still fail immediately on
any real failure. Upgrading past 11.x requires Node 20+, which conflicts with
this release line's Node 18 gate.

Documentation

Thanks to everyone who reported issues and tested the pre-release builds.

v5.0.3

Choose a tag to compare

@lacrous lacrous released this 24 Sep 19:19

Remembra v5.0.3

Reliability and recovery hardening for the V5 line.

Highlights

  • Deterministic startup validation and explicit Healthy/Degraded/Recovering/Failed/ReadOnly health states.
  • Durable, atomically replaced recovery state with remembra recover read-only and remembra recover verify.
  • Snapshot semantic validation, SQLite restore journaling/reconciliation, atomic SQLite batch imports, and durable file-import rollback journals.
  • Permanent recovery coverage for restart, corruption, symlinks, SIGKILL, permission, disk, I/O, and SQLite-full failures.
  • V4.9/V5 compatibility preserved; V5.1 and V6 remain deferred.

Verification

  • Node 18.20.8 and Node 24.21.0 release gates passed.
  • Full suite: 466 passed, 0 failed.
  • Security matrix: 64 passed; recovery matrix: 48 passed.
  • Documentation: 36 files; dependency audit: 0 vulnerabilities.
  • Package dry-run: 222 files, 440.7 kB packed / 1.9 MB unpacked.

Remembra v5.0.2 — Authorization and Tenant-Isolation Hardening

Choose a tag to compare

@lacrous lacrous released this 24 Sep 17:07

Remembra 5.0.2

V5.0.2 hardens V5 authorization consistency and tenant isolation while preserving V4.9/V5 compatibility. It does not introduce V6 policy, schema, provider, API, or migration implementation.

Security and correctness

  • Adds a centralized, pure authorization evaluator with explicit operation/capability mappings and non-informative denials.
  • Enforces conjunctive organization/project/user/agent visibility across file and SQLite point reads, candidate SQL/counts, search/context, history, audit, entities, batches, snapshots, and scoped migration.
  • Prevents administrative capabilities from erasing explicit project/user/agent selectors; organization-wide access is explicit.
  • Requires tenant:export for full and selected exports and rechecks configured tenant membership before ordinary service work, provider calls, and mutations.
  • Normalizes reserved identity ingress across HTTP/MCP/SDK surfaces and sanitizes provider/IO errors at public boundaries.
  • Applies configured sensitive-data redaction to store, update, and import paths.
  • Preserves the V5.0.1 fail-closed storage, history containment, signed snapshot, and idempotent migration guarantees.

Documentation

  • Finalizes the V5 threat model with assets, trust boundaries, attackers, attack surfaces, controls, residual risks, operator responsibilities, and permanent regression evidence.
  • Documents the exact file-backend, SQLite, snapshot, backup, and transport encryption boundaries; strict mode refuses legacy global Markdown/encryption commands.
  • Adds the V5.0.2 authorization contract and clarifies preflight-versus-atomic snapshot restore behavior.

Verification

  • Node 18.20.8: complete release gate passed.
  • Node 24.21.0: complete release gate passed.
  • Full suite: 431 passed, 0 failed; security matrix: 64 passed; recovery matrix: 13 passed.
  • Documentation check: 36 files passed; dependency audit: 0 vulnerabilities.
  • Package dry-run: 207 files, 422.7 kB compressed; 10K/100K tenant and bounded scale benchmarks passed.

Upgrade and rollback guidance

Read the V5.0.2 authorization contract, V5 threat model, and security/self-hosting guidance before enabling strict tenant operation. Keep a verified backup before applying migrations; failed per-record restore operations are idempotent but may require backup restoration or a verified retry.

Artifacts

Remembra v5.0.1 — Security and Migration Hotfix

Choose a tag to compare

@lacrous lacrous released this 24 Sep 15:22

Remembra 5.0.1

V5.0.1 is the security, storage, history, and tenant-snapshot correctness hotfix for the V5 platform. It preserves V4.9/V5 compatibility and does not introduce V6 behavior.

Security and reliability fixes

  • Authenticates requests before consuming protected rate-limit buckets; anonymous and authenticated identities are isolated and opaque.
  • Adds centralized secret redaction for structured logs, debug search diagnostics, provider errors, nested headers, and credentials; public provider errors are sanitized.
  • Makes SQLite initialization and startup migration readiness fail closed. The file backend is available only with REMEMBRA_ALLOW_FILE_FALLBACK=1, is warned about, and is reported by health.
  • Validates and contains file-history paths and rejects symlinked history storage/sidecars.
  • Rejects tenantless snapshots during ordinary strict restore before any write.

Explicit tenant migration

  • Adds read-only remembra migrate analyze.
  • Adds signed, target-bound remembra migrate plan and migrate apply --dry-run/apply commands.
  • Requires a trusted strict operator tenant and signed snapshot key; ordinary import cannot enable migration.
  • Verifies plan signatures, snapshot digests, manifests, and destination tenant scope; repeated applies are idempotent.

Verification

  • Node 18.20.8: complete release gate passed.
  • Node 24.21.0: complete release gate passed.
  • Full suite: 414 passed, 0 failed; security matrix: 47 passed; recovery matrix: 13 passed.
  • Documentation check: 35 files passed; dependency audit: 0 vulnerabilities.
  • Package dry-run: 203 files, approximately 410 kB compressed.

Upgrade and rollback guidance

Read the V5.0.1 security and migration guide before enabling strict tenant restore or explicit migration. Keep a verified backup before applying a tenant plan; a failed migration must be investigated and restored/retried with the same signed, target-bound plan.

Artifacts

Remembra v5.0.0 — Production Memory Platform

Choose a tag to compare

@lacrous lacrous released this 24 Sep 12:50

Remembra 5.0.0

Remembra 5 is the production memory platform release, preserving V4.9 compatibility while adding tenant-safe context and retrieval.

Highlights

  • Adds deterministic memory_context assembly with a 4,000-token default budget, hard 100,000-token ceiling, bounded candidates, authorized retrieval, and vector omission from responses.
  • Adds trusted host-resolved tenant contexts, strict/legacy tenant modes, versioned organization membership, organization/user/project/agent entity services, trusted HTTP/SDK routes, and fail-closed isolation across storage, retrieval, relations, history, audit, jobs, snapshots, migrations, and recovery.
  • Adds effective embedding reranking, bounded pool-local relation expansion, cache partitions, and deterministic ordering.
  • Adds signed snapshot and migration manifests, checksum/reference preflight, durable checkpoints/resume/failure records, verified SQLite backup/restore, retained-previous rollback, and corruption/symlink/sidecar rejection.
  • Preserves all eleven memory types, Markdown compatibility, V4.9 clients, and the thirteen existing MCP tools; memory_context is additive and the MCP manifest version is 2.
  • Adds reproducible release gates for Node 18 and Node 24, including full tests, security/recovery matrices, documentation, audit, package contents, and 10K/100K tenant benchmarks.

Verification

  • Node 18.20.8: full release gate passed at 5.0.0.
  • Node 24.21.0: full release gate passed at 5.0.0.
  • Strict tenant benchmark: 10,000 p95 50.87 ms; 100,000 p95 398.81 ms in the final gate environment, below the documented ceilings.
  • Dependency audit: zero vulnerabilities; package dry-run and export checks passed.

Artifacts