Uteke v0.21.0: honest recall counting, MCP read-only policy, signed releases

Uteke v0.21.0 splits machine recall from explicit gets in a new recall_count field, gives MCP over HTTP a real read-only policy, and signs release checksums with cosign. 39 PRs since v0.20.1.

Uteke v0.21.0: honest recall counting, MCP read-only policy, signed releases

Uteke v0.21.0 shipped on October 13, 2026 with 39 merged pull requests since v0.20.1. Three changes define the release: memory scoring stops treating machine recall as attention, MCP over HTTP gets one consistent access policy, and release checksums now carry a cosign signature.

For readers new to the project: Uteke is a local-first memory engine for AI agents. It is a single Rust binary bundling SQLite, an HNSW vector index and a local embedding model, with an optional HTTP server and an MCP server on top. Apache-2.0 licensed, 273 stars on GitHub at writing time. Small project, openly stated. The release is worth reading regardless of the star count.

Recall no longer reinforces noise

Until now, every recall, search and recall_context call bumped the same access_count field as an explicit get. Worse, a default fusion recall touched the same memory three times per call and counted candidates that were never returned. A session hook that pulls context at the start of every conversation was, functionally, voting its own queries onto the salience leaderboard. The memories your automation asked for kept floating up regardless of whether anyone read them.

v0.21.0 separates the two. Recall and search hits now go to a new recall_count field, and access_count grows only on an explicit get. Salience, aging and orphan scoring combine them as access_count plus 0.5 times recall_count, so automated traffic still counts, but it cannot out-shout deliberate retrieval. A recall is counted once per user-level call, on the final results, including when it is served from the recall cache. recall --explain no longer touches counters at all.

This needs a schema change, and the release handles it the boring way. The first open by 0.21.0 runs an in-place ALTER TABLE that adds memories.recall_count, existing rows start at 0, and historical access_count values stay exactly as they are. Splitting old counts retroactively would have meant inventing data.

One access policy for MCP over HTTP

Before this release, POST /mcp was classified as a write route, which produced two wrong outcomes at once: a read-only token could not call any MCP tool, and a token that could reach the endpoint could call every tool. The routing said nothing about what the token should be allowed to do.

MCP tools are now gated individually. A read-only token can call the read tools (recall, search, list, get, stats, room and document reads) and gets a tool error from every write tool. Write tokens and the stdio transport behave as before.

The release also closes a quiet deletion bug. Id prefix resolution used SQL LIKE matching without escaping, so a forget id of %25 matched every memory whose id contains the characters 25, and deleted one if exactly one matched. The % and _ characters in a forget id are now escaped for every caller, and the HTTP forget endpoint rejects ids outside the expected charset with a 400. MCP was never affected.

Release checksums are signed

The Release workflow now signs checksums-sha256.txt with cosign keyless signing and publishes the signature bundle next to it. The certificate is bound to the repository's workflow identity, so there is no long-lived key to trust or leak. v0.21.0 is the first release with the bundle, and SECURITY.md documents verification. One caveat, stated in the docs as well: the uteke upgrade command does not verify the signature yet.

The upgrade path itself got hardened in the same release. Release tags must match a strict charset, archives are capped at 128 MiB and checksum files at 1 MiB while streaming, and archive entries must be plain relative paths with no traversal components. A dependency bump to quinn-proto 0.11.19 clears RUSTSEC-2026-0338 through RUSTSEC-2026-0342 in the HTTP client stack.

The server reads the same config as the CLI

uteke-serve silently ignored several uteke.toml sections that the CLI honoured: [embed_fallback], the graph rerank weights in [recall] (graph_density_weight, graph_authority_weight, graph_rerank_enabled, jaccard_weight), and the [tier] keys hot_days, warm_days and hot_boost. You could set them, the CLI would use them, the server would not. Both read the same file now. Keys you never set keep the previous behaviour, so existing deployments see no change unless they had configured these values and wondered why the server disagreed.

Namespaces start their deprecation

Namespaces are deprecated and will be removed in a future 0.x release. The direction is rooms, tags and author. Phase 1 is a signal only: writes to a non-default namespace return a one-sentence warning through the HTTP warning field, MCP tool text or a stderr line, depending on the surface. Reads, routing and storage are unchanged, and responses for the default namespace are byte-identical to 0.20.x. Migration tooling lands in a later phase, so nothing forces a flag day.

Everything else

  • uteke_room_recall reaches parity with uteke_recall: min_score, tags, pack mode with budget_chars and exclude_ids, full_ids, and memory ids shown per result.
  • uteke_room_list accepts a name filter, a case-insensitive substring match on room id or title.
  • A conventions-first rule: uteke guide, GET /guide, the bundled skill and a new instructions field in the MCP initialize response now tell agents to find the workspace memory convention before writing.
  • Recall, list, remember and forget share one typed request per operation across HTTP, MCP and the CLI, with parsing, defaults, caps and validation in uteke-core. Error texts and defaults are unchanged.
  • An explicit --store naming a different store now always runs locally. Previously, with the server enabled, six CLI commands silently rerouted to the server's own store, so --store /path/to/other could read and write the wrong database.
  • New docs cover LAN access, MCP authentication, Docker env handling and how to read recall scores.

That --store fix is easy to overlook and worth a second look if you run more than one store. It produced no error and wrong data, which is the worst combination a bug can have.

Upgrading

No manual steps. The first open by 0.21.0 migrates the store to schema v21 automatically. Back up the store directory first anyway; it costs nothing.

Fresh installs:

curl -fsSL https://raw.githubusercontent.com/codecoradev/uteke/main/install.sh | sh

# or pin the version
UTEKE_VERSION=v0.21.0 curl -fsSL https://raw.githubusercontent.com/codecoradev/uteke/main/install.sh | sh

Binaries ship for Linux x86_64 (AVX2, plus a legacy SSE4.2 bundle for older CPUs), Linux ARM64, macOS Apple Silicon and Windows x86_64. Each archive carries the uteke CLI, the uteke-serve HTTP daemon, the uteke-mcp server and the ONNX Runtime library. Verify downloads against the signed checksums following SECURITY.md.

What's next

The roadmap follows the published phase plan: namespace migration tooling first, then removal in a future 0.x release, and signature verification inside uteke upgrade.

Repo: github.com/codecoradev/uteke. Full changelog: v0.21.0 release notes. Product page: codecora.dev/uteke.