cd /news/ai-agents/agent-memory-that-commits-the-write-… · home topics ai-agents article
[ARTICLE · art-124700] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Agent memory that commits the write before it tries to embed

A developer released hinterland, a zero-dependency agent memory library for Node.js that commits writes to SQLite before attempting to embed them, ensuring durability on unreliable connections. The library offers lexical fallback search when embeddings are unavailable and a backfill operation to catch up on missing vectors, addressing a common assumption in agent memory systems that embedding APIs are always reachable.

by read3 min views1 publishedSep 9, 2026

I build on a connection that drops for hours at a time. That exposed an assumption nearly every agent memory library makes without stating it.

In most of them, remember() calls an embedding API, so the write fails when the connection does.

It is an ordering decision, made once, that everything else follows from.

// The row lands first, unconditionally.
this.db.prepare(`INSERT INTO memories (...) VALUES (...)`).run(...);

// Then we try. This is allowed to fail.
await this.#tryEmbed([{ id: memoryId, content }]);

The write is durable before anything touches the network. If the embedder is unreachable, unconfigured or slow, the memory is still there. It gets its vector later, whenever there is a link.

Committing the write is half of it. If recall then requires vectors, the failure has moved rather than gone.

So retrieval runs whatever it can:

// Lexical always runs. It needs nothing.
if (mode !== 'lexical') {
  const vectors = await this.embedder.embed([query]);
  if (vectors && vectors[0]) {
    // Semantic joins in when it can.
  }
}

With embeddings present you get semantic search fused with full text search. With none, you get full text search alone. That is worse, and it is the same API returning the same shape.

So your code has no if (hasVectors) in it. That property matters more than the retrieval quality, because a fallback you handle explicitly is a second code path, and second code paths drift from the first.

Memories written offline need to catch up, so that is a named operation:

const { embedded, remaining } = await memory.backfill();

It embeds everything without a vector for the current model, in batches, and reports what is left. memory.pending() tells you how many are waiting.

Two details worth stealing.

Backfill checks availability first and returns a reason instead of throwing. Being offline is the expected state, so { embedded: 0, remaining: 42, reason: 'no embedder' } is a normal answer.

Changing a memory's content deletes its vector immediately:

this.db.prepare('DELETE FROM embeddings WHERE memory_id = ?').run(memoryId);

An embedding describes text. If the text changed, the vector describes something that no longer exists, and nothing will ever tell you it is wrong. Deleting it puts the memory back in the backfill queue.

Local search on npm is not an unfilled gap. Orama is a complete in-process search engine and RAG pipeline. The sqlite-vec bindings put vector search inside SQLite directly. Both are good and both are more capable than this at what they are for.

The difference is what they assume about the embedding. They treat it as present, or as somebody else's problem. This one is built for the case where it is neither.

With reliable connectivity you probably do not need this. If you have had a write fail because a model was unreachable, you know what it is for.

npm install hinterland

Zero dependencies, one SQLite file, Node 22.5+. hinterland.

The storage side has its own article, because node:sqlite removes almost all of the usual setup.

── more in #ai-agents 4 stories · sorted by recency
── more on @hinterland 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/agent-memory-that-co…] indexed:0 read:3min 2026-09-09 ·