SynapCores v1.14.3 — Your agent's memory was quietly sorting by insertion order

Published on August 18, 2026

SynapCores v1.14.3 — Your agent's memory was quietly sorting by insertion order

Every number looked right. That was the problem.

We shipped a memory subsystem, wrote it a test suite, watched it pass, and then went to prove it worked on a real instance. A recall query for "what does the user like in coffee?" came back with a relevance score of 0.4988 on the coffee memory — and 0.4988 on the standup time, and 0.4988 on the editor theme.

A flat 0.5 doesn't look like a bug. It looks like a score.

What was actually happening

Every memory in that database had a NULL embedding.

The engine initialises its AI service lazily — the first SQL statement that needs a model wakes it up. That check is a list of function names, and it knew about EMBED() and GENERATE() and the older memory primitives. It did not know about REMEMBER.

So every memory written before some unrelated query happened to wake the service was stored with no embedding. Semantic recall then fell back to recency, and the fallback score for a fresh row is about 0.4988.

Here's the part worth internalising: that fallback is worse than a missing score. A real but unrelated match scores around 0.12. So an un-embedded row didn't just rank badly — it outranked genuinely relevant memories, quietly winning retrievals it should have lost.

The codebase had warned us. A comment three lines above that list documents the same trap from v1.8.5, when MEMORY_STORE hit it. The difference is that the old code failed loudly"empty embedding response". Our new write path degrades on purpose, so that a model outage can never lose an episode. That deliberate resilience is what converted a loud failure into a silent one.

Three fixes: the wake-up check now asks one shared predicate so it can't drift again; embedding failures log at warn instead of only returning a warning in a response body; and the consolidator now re-embeds any row left un-embedded, so a transient outage repairs itself instead of being permanent.

CREATE MEMORY

That bug was found in the flagship feature of this release, which is agent memory as a database object rather than a table convention you rebuild per project:

CREATE MEMORY assistant IDENTITY user_id;

REMEMBER assistant FOR user_id = 42 'I prefer dark mode and oat milk';
RECALL   assistant FOR user_id = 42 ABOUT 'what are their preferences?';
CURRENT  assistant FOR user_id = 42 ATTRIBUTE theme;      -- 'dark'
TRACE    assistant FOR user_id = 42 ATTRIBUTE theme;      -- why we believe it

Seventeen statements, covering writes, assembled reads, point lookups, lineage, removal, search, relations and maintenance.

Four design decisions matter more than the statement list:

Identity is enforced, not conventional. Memories belong to an identity you name. A caller cannot read across identities by forgetting a WHERE clause.

Conflicts resolve by authority, not recency. Users contradict themselves — dark mode on Monday, light mode on Friday. A vector store returns both and lets the model pick. Here a system_of_record outranks a verified user statement, which outranks an inference, and TRACE shows you which source won. If your agent makes decisions anyone will question later, that's the part you'll care about.

Confidence and relevance are different fields. Evidence strength is not retrieval score. Collapsing them into one number is how a strongly-held but irrelevant fact ends up looking like a great match.

Consolidation runs in the engine. No prompt, no tool call, no application callback. Correctness doesn't depend on your app remembering to ask.

It's available to every agent in the engine, over the REST API, and to external agents through MCP.

Five more bugs we found the same way

Building it wasn't the hard part. Running it was.

  • REMEMBER accepted two options it silently never honoured
  • Two error codes existed that no code path could return
  • Two routing gaps that compiled cleanly and were invisible to every test
  • Interactive logins were never pinned to an identity, so credential-scoped access was enforced everywhere and populated nowhere

None of these were caught by the type checker or the test suite. All of them were caught by running the feature against a live engine and reading what actually came back.

Cloud LLM providers worked over REST but not from SQL

An operator reported that setting provider = "anthropic" — exactly as the shipped config comments describe — logged Unknown AI provider: anthropic and made GENERATE() return an empty string.

They were right, and the providers were not missing. The engine has two places that turn a provider name into a live client: one for the /v1/ai/* REST endpoints, one for every SQL AI function. Only the REST one knew about anthropic and gemini. SQL fell through to a stub.

Fixing the wiring exposed three more defects stacked behind it, each hidden by the one in front:

  1. Every Anthropic model ID we shipped had been retired — including the default, the health check, and the native vision default. All would have returned 404.
  2. The engine sent a sampling parameter that current models reject, so every completion returned HTTP 400.
  3. The response parser predated thinking blocks, so a successful completion was discarded with a parse error.

Only the first was findable by reading code. The other two required calling the real API with a real key. Both dispatchers now delegate to one shared builder, so they cannot drift apart again.

[query.ai_service]
provider        = "anthropic"
model           = "claude-opus-5"
embedding_model = "all-minilm"   # Anthropic has no embeddings endpoint —
                                  # EMBED() routes to the bundled local model

What we checked before shipping

Gate Result
Feature validator, against the published artifact 200 / 200
Recipe certification, pristine data directory 162 / 162
Non-AVX-512 canary (i5-10400F) boot + EMBED + GENERATE, zero SIGILL
Live cloud provider, real API key GENERATE / EMBED / RECALL end to end
Unit 133 memory · 11 routing · 7 provider

The canary matters more than it sounds. The binary contains AVX-512 instructions — 2,018 of them — and a naive check would fail the release on that alone. They're confined to a runtime-dispatched BLAKE3 hash variant; the inference paths contain none, and the binary was verified executing on hardware without AVX-512 before we shipped it.

Known issues, stated plainly

  • The deterministic extractor doesn't catch every phrasing. "my favorite color is blue" populates structured state; "I prefer dark mode" does not. The episode is still stored and RECALL still retrieves it — but CURRENT ... ATTRIBUTE won't have it. We're widening the pattern coverage.
  • GENERATE() in a WHERE clause still isn't cancelled when a client disconnects. Projection-position GENERATE() cancels correctly. The WHERE-clause path pushes the predicate into the storage scan, below every layer that can observe a dropped connection — a fix belongs in the scan itself, and we'd rather do that carefully than quickly.

Upgrading

Drop-in. No migration, no schema change, no config change. Existing agents, recipes and queries are unaffected; CREATE MEMORY objects are created on demand.

docker pull synapcores/community:latest