Skip to content

v0.1.41

Choose a tag to compare

@memtomem memtomem released this 22 Jul 02:38
7b25b0e

Upgrade notes

  • The response cache is upgraded to schema v4 and performs a one-time cache
    reset on first run after upgrade; a cache-hit now retains JSON-safe
    structuredContent and result _meta. mms daemon status and the daemon
    ping gain queue-telemetry fields in both human and JSON output. Opt-in
    Unicode token estimation and _context_query schema advertisement are both
    off by default (character-budget behavior unchanged). (#731)
  • On a core reporting a non-RRF score_scale, surfacing now injects up to
    max_results memories per call where it previously filtered most or all of
    them out against an incommensurable min_score threshold; dedup and feedback
    demotion remain the quality controls. Set
    surfacing.scale_gated_min_score=false to restore unconditional filtering.
    (#730)
  • On a bypass-capable core with server-side rerank enabled, surfacing scores
    now return on the RRF scale ((0, ~0.033]) that min_score and the
    auto-tuner are calibrated against; set surfacing.rerank=none to restore
    server rerank policy. (#726)
  • With a compatible Toolgraph and the default on_unreachable: open, a typed
    backend outage now starts loudly degraded under STM-native rules instead of
    failing startup as a protocol error. (#736)
  • hook.daemon_timeout_seconds=inf/nan is now a config ValidationError
    instead of an accepted value. (#722)
  • Default daemon installs now write surfacing_events telemetry rows on the
    hook path; growth is bounded by the existing stats_retention_days /
    query_retention_days cleanup. (#723)

Added

  • Developer-first project routing and runtime controls for the proxy. mms project route previews and applies a project-selected registry entry with
    config locking, validation, backups, provenance, conflict protection, and
    idempotency (previews by default; never overwrites a name/prefix conflict or
    prunes routes). Opt-in Unicode runtime token estimation
    (token_estimation_mode, default static — character-budget behavior
    unchanged) resolves with proxy/server/tool precedence. mms status and mms doctor surface compression-tuning readiness. _context_query schema
    advertisement is available opt-in (disabled by default) and never forwards
    the proxy-only argument upstream. Behavior change: the response cache is
    upgraded to schema v4 so a cache-hit now retains JSON-safe
    structuredContent and result _meta (errors and mixed/non-text responses
    remain uncached); the upgrade performs a one-time cache reset. mms daemon status and the daemon ping now expose bounded queue telemetry, adding fields
    to both the human and JSON output. (#731)

  • Surfacing now reads the score scale a compose-capable core names on the
    composed bundle itself (score_scale/reranker on the context_compose
    envelope, context_compose schema 4, core #1796, first available in the
    core release after v0.3.11). This closes the compose blind spot the
    mem_search score-scale work (#727) left open: STM stamps the scale onto
    the compose retrieved results (never pinned blocks), so the scale gate
    (#730), the definitive score_scale_mismatch diagnostic (#727), the
    relevance-bucket suppression, surfacing_events persistence, and
    stm_surfacing_stats all now cover the composed retrieval path, not just
    the legacy mem_search fallback. The two keys cross the hook-daemon wire
    additively (no PROTOCOL_VERSION bump, emitted only at the negotiated
    schema 4) and survive the surfacing cache. Dormant against every released
    core (schema ≤ 3 never carries the keys → all-None → today's behavior);
    the live effect requires a core release carrying #1796, a reinstall, and a
    daemon restart. The process-level stm_surfacing_stats Score scale:
    line now also reflects compose batches. (#734)

  • Surfacing now suspends the RRF-calibrated min_score filter when the core
    names a foreign score scale. min_score (default 0.03) and the auto-tuner
    are calibrated against RRF fusion scores ((0, ~0.033]); on a batch whose
    core-reported score_scale (#727) is a known non-RRF label
    (bm25/dense/none/rerank — e.g. raw cross-encoder logits with a
    negative median), no fixed constant is meaningful, so the global/auto-tuned
    floor is not applied (results stay bounded by max_results) and auto-tune
    pauses: maybe_adjust skips the batch and the tuner's rating ratios count
    only feedback earned on RRF-stamped or unstamped surfacings. Per-tool
    context_tools.<name>.min_score pins always keep the filter active; the
    relevance-bucket tags ([weak]/[related]/[strong]) are suppressed for
    results stamped with a non-RRF scale (the [min_score, 1.0] band math only
    holds on RRF); the first suspended batch marks a lingering
    score_scale_mismatch/score_ceiling_below_min episode recovered, so the
    score_scale_mismatch diagnostic and mms doctor's ltm_score_scale FAIL
    now occur exactly when the filter actually applies (pin present, or gate
    disabled). New surfacing.scale_gated_min_score bool
    (MEMTOMEM_STM_SURFACING__SCALE_GATED_MIN_SCORE, default true); set
    false to restore unconditional filtering. Unstamped batches (compact
    format, pre-#1781 cores, and compose bundles on a core older than the
    schema-4 stamp) and unrecognized labels behave exactly as before; #734 later
    in this release extends the scale stamp to the composed retrieval path on a
    schema-4 core, so the gate is no longer limited to the legacy mem_search
    fallback.
    Behavior change: on a core reporting a non-RRF scale, surfacing now
    typically injects up to max_results memories per call where it previously
    filtered most or all of them out on an incommensurable threshold — dedup
    and feedback demotion remain the quality controls. (#730)

  • Surfacing now reads the score scale the core names in structured
    mem_search output (score_scale: rrf/bm25/dense/none/rerank
    plus the active reranker model ID, core #1781, first available in the
    core release after v0.3.11) and threads it through observability:
    every parsed result carries the scale (including across the hook-daemon
    wire and the surfacing cache), each surfacing_events row records it
    (additive nullable column, migrated in place), stm_surfacing_stats
    renders a Score scale: line plus a per-scale event distribution, and
    the below-threshold tripwire gains a definitive tier — when the core
    names a non-RRF scale while the ceiling sits under the RRF-calibrated
    min_score, STM fires score_scale_mismatch on first observation
    (no five-call streak) and mms doctor names the mismatch and the fix.
    Observability only: filtering, thresholds, and unreported-scale paths
    (compact format, older cores, and — until #734 later in this release —
    compose) behave exactly as before. (#727)

  • Surfacing retrievals now ask the core to skip its cross-encoder rerank
    stage per call (core #1766, first available in the core release after
    v0.3.11). On a rerank-enabled core that stage is ~99% of retrieval latency
    (compose p50 4.2s vs 42ms bypassed) and blew the surfacing budget on every
    builtin call, while survival past the default min_score is measured
    identical either way — the bypass trades ranking precision, not result
    existence. New surfacing.rerank tri-state (MEMTOMEM_STM_SURFACING__RERANK):
    false (default) bypasses, true forces the server-configured rerank,
    none omits the parameter so server config decides. The parameter is only
    sent when the connected core advertises it in its mem_search tool schema
    (negotiated once per session, like the result_format downgrade), so on
    older cores every value degrades to today's behavior instead of tripping
    the server's argument validation and charging the circuit breaker.
    Behavior change: on a bypass-capable core with rerank enabled
    server-side, surfacing scores return on the RRF scale ((0, ~0.033]) —
    the scale min_score and the auto-tuner were calibrated against; set
    rerank: none to restore server policy. (#726)

Fixed

  • Aligned proxy CLI defaults and clarified mms eject recovery. The default
    STM proxy config path is now centralized across CLI modules, and mms project route's runtime resolution stays consistent with its help output. An
    origin-less mms eject failure now lists the valid --to targets, and
    prune-backup hints render as a concrete retry command. (#732)
  • Toolgraph stdio consults now recognize the producer's typed
    backend_unavailable MCP envelope and route it through on_unreachable;
    legacy, unknown, malformed, and contract errors remain on
    on_protocol_error. Behavior change: with compatible Toolgraph and the
    default on_unreachable: open, a backend outage now starts loudly degraded
    under STM-native rules instead of failing startup as a protocol error. (#736)
  • A transient DB failure while closing a score_scale_mismatch diagnostic no
    longer leaves the persisted episode open until the next full mismatch cycle.
    The warning latch still re-arms immediately on a healthy observation, while
    the recovery UPDATE now retries on later healthy observations or
    scale-gated suspended batches until it succeeds. (#733)
  • The daemon now hands the surfacing engine the time left in the client's
    deadline instead of letting that deadline cancel the engine from outside.
    hook.daemon_timeout_seconds (2.5s) is smaller than
    surfacing.timeout_seconds (3.0s), so on the daemon path every slow LTM
    search was aborted by the transport before the engine's own timeout could
    fire. That abort raised CancelledError, which bypasses the engine's
    asyncio.TimeoutError handler — the one place that records the
    error_timeout fault, logs the warning, and counts the failure toward the
    circuit breaker (#579). The result was an invisible failure loop: no fault
    rows, no log line, and a breaker that could never open, so every eligible
    hook call re-paid the full timeout and respawned the LTM stdio child. A slow
    LTM now trips the breaker as designed and shows up in
    mms stats / surfacing_faults. When queue and lock wait have already eaten
    the deadline, the daemon skips the LTM round trip entirely rather than
    starting one it must cancel mid-RPC. A surfacing call that times out inside
    the engine is also recorded as a timeout latency sample rather than a
    success one roughly the length of the whole budget, keeping the censored
    duration out of the percentiles mms daemon status's timeout recommendation
    is derived from. (#719)
  • Closed the race #719 left between the surfacing engine's own timeout and the
    daemon's outer deadline backstop. The engine now receives the client's
    deadline as an absolute monotonic point rather than a relative budget, and
    derives its window right before the LTM attempt — so its pre-timeout work
    (gate, query extraction, privacy scan) debits that window instead of silently
    eating the fixed response margin. The remaining gap was that the engine only
    raised its timeout once the LTM adapter it had just cancelled finished
    unwinding — an unbounded wait, since a stdio child can be slow to give up —
    which is what let the caller's backstop cancel the engine from outside first
    and skip the error_timeout fault row, the warning log, and the
    circuit-breaker increment that only the engine's own timeout path records
    (#579). The LTM operation is now shielded, so that abort lands the moment the
    engine's timer fires and the abandoned unwind is left to finish on its own
    (the adapter already expects a caller to leave mid-RPC and marks the session
    for lazy reconnect; shutdown waits a bounded moment for that cleanup rather
    than cancelling it a second time, and declines new attempts once too many
    cancelled operations are still unwinding — warning once per draining episode
    and refunding the rate-limit slot it claimed, since a refusal starts no LTM
    work). The engine also
    books its timeout off which timer fired — a flag set inside the timer
    callback, which the loop runs in scheduled order ahead of any backstop
    scheduled later — rather than off elapsed time, the caller's deadline, or a
    timeout scope's own expiry, none of which can tell "my timer fired first"
    from "my timer also fired, later, while something else was cancelling me".
    A cancellation that is not this call's own timeout is left unbooked, so a
    shutdown or a client hanging up never charges a healthy LTM a breaker
    failure. A window fully consumed by pre-work is booked without starting an
    LTM round trip that would be cancelled mid-RPC, and gives its rate-limit
    slot back the same way a refusal does. (#721)
  • The daemon's admission check now rejects a deadline_monotonic it cannot
    actually enforce, answering expired instead of admitting the request:
    NaN compared False against the expiry check and reached
    asyncio.timeout_at(NaN), +inf admitted with a backstop that can never
    fire, and an int too large for a float made the dispatch raise instead of
    respond. #721 fixed the same family in _surface_deadline; both sites now
    share one validation helper. The one configuration that could legitimately
    produce an infinite deadline is closed at the source:
    hook.daemon_timeout_seconds must now be finite (it becomes the client
    deadline as now + budget, so +inf is not a big budget but a deadline
    the daemon can never enforce — and would have every request rejected).
    Behavior change: daemon_timeout_seconds=inf/nan is a config
    ValidationError instead of an accepted value. (#722)
  • Successful surfacing on the hook/daemon path now records a surfacing_events
    telemetry row, so stm_surfacing_stats / mms stats / mms doctor no
    longer report 0 events for a working hook path ("0 events" was previously
    indistinguishable from "0 surfacing" — only seen_memories and
    surfacing_faults were written there). Hook-path rows carry
    server='builtin' and a sha256: digest in place of the query (the daemon
    forces persist_query_text=false), and the injected block still advertises
    no stm_surfacing_feedback rating prompt. hook.record_feedback_events now
    gates only the rating prompt and feedback loop, not event persistence.
    Behavior change: default daemon installs start writing these rows;
    growth is bounded by the existing stats_retention_days /
    query_retention_days cleanup. (#723)

Security

  • Raised the declared mcp[cli] floor to >=1.28.1 so a fresh install cannot
    resolve a version affected by CVE-2026-52869 and CVE-2026-52870 (fixed in
    1.27.2) or CVE-2026-59950 (fixed in 1.28.1). #728 moved the lockfile to
    1.28.1 to clear the pip-audit CI gate, but the published wheel resolves
    against the declared floor, not uv.lock — the previous >=1.26.0 still let
    a downstream install pull a vulnerable mcp. The floor now matches the
    audited lock. (#737)