← Blog

Making agent recall involuntary: the architecture, and the wrong turns that shaped it

· Vectros Team
  • engineering
  • dogfooding
  • agent memory

The last post ended on a claim: recall has to be involuntary. Telling an agent to check its memory first is a rule it follows right up until the moment it matters, then skips. This post is what we built instead.

We did not design the final shape and then build it. We built a plainer version, turned it on in our own work, and its mistakes corrected us into the architecture we run now. So this post is two things at once: the architecture, which turned out to differ from the memory systems we had studied, and the wrong turns that produced it. The wrong turns are the more useful half.

Why involuntary

The design has a single origin. A "recall from the knowledge base first" rule sat in our operating manual and in the agent's own prompt, and got skipped anyway during a debugging push. Thirty minutes lost to a log hunt whose answer a single query returned at the top of the results. Nobody heading confidently down a path stops to ask "has this already been solved?" That is exactly when recall is worth the most, and exactly when a rule you have to remember fails.

So the goal was never to build a memory store. It was to make recall a step that happens on its own, not one the agent has to remember.

What already exists, and what we wanted

When we surveyed how agent-memory systems handle recall, almost all of them do one of two things. Either they retrieve on every turn and inject whatever they find, or they leave it to the model to call a search tool when it decides it needs one.

Each has a gap. Always injecting fills the context with results whether or not any of them answer the question, because search always returns its top matches and has no way to say "I have nothing." Leaving it to the model puts you back where you started: the model decides to look only when it remembers to, which is not when it matters. And neither approach watches the agent's own reasoning as it works. The richest signal in a long agent session is not the user's prompt. It is what the agent is figuring out mid-task, and that is precisely the window both patterns ignore.

We wanted the cheap always-on baseline, plus something that tracks the work in progress, plus the model's own judgment when it wants to dig, and we wanted capture to stop depending on the agent's memory of itself.

The architecture we run now

Recall happens in three places, none of which the agent has to trigger.

  • Before each step. On every prompt, a fast search runs over the agent's own memory and the shared knowledge base in one pass, and the top few results are placed in the context the model is about to read. It is ranked, it can only ever see that agent's own private notes, and it fails open: if the store is slow or down, it adds nothing and never blocks the turn.
  • While the agent works. A background pass watches the running task and, when it looks like there is something to recall, runs a second small model to check whether the results it found actually answer the need. Only what passes gets injected at the next step. This is the part the systems we studied do not have, and it exists because of a mistake we cover below: search cannot tell you it found nothing, so something has to judge the answer, not just ask the question. It runs off to the side, so it costs the agent no waiting.
  • When the agent decides to dig. The agent is itself a capable model, so when it judges it needs more, it calls the search tools directly, with real reasoning. This is the layer the other approaches lead with; here it sits on top of the two cheaper ones.

Capture, the writing-down half, works the same way: on its own, not on the agent's discipline. At a turn boundary, a small, cheap model reads what is new in the transcript and proposes candidate memories. It does not save them. The capable agent reviews and commits them, in the same session, because it is the one holding the full context and the tools to check a questionable claim.

That split, a cheap model proposes and the capable one commits, is the single most important decision in the system. We did not plan it. We arrived at it by getting burned, which is the rest of this post.

First, it worked

Two things happened on the first real day, neither staged.

We opened a new work stream to unify how we store credentials. We wrote the issue, weighed the options, picked a library, and wrote a kickoff, all from first principles, carefully, over an hour. Then recall fired on that kickoff and returned, at the top of the results, a decision we had written months earlier that already held the whole analysis: the same library chosen, the same alternative rejected, the same constraints. We had re-argued a settled question in ignorance of our own answer, and recall caught it, on the very work its own rollout had started.

Later that day, while testing the recall hooks, the background pass surfaced a rule of ours: check the real entry point, not just the test harness. We were, at that exact moment, testing the hooks against a stand-in harness instead of a real payload, which is how the worst bug of the day had survived. It surfaced the rule we were breaking, in the turn where we broke it.

Neither would have happened if recall were opt-in. That is the whole argument, shown on ourselves.

The wrong turns that shaped it

Each of these started as the obvious design. Each one broke in a way that rewrote a piece of the architecture above.

Search cannot tell you it found nothing, so we added a step that judges the answer. The first version searched, took the top five results, and injected them. The problem is that the score our search returns is a rank, not a measure of similarity. The same value comes back for a perfect match and a total miss, so no cutoff on it can separate the two. And search always returns its top results. On a question with no real answer it returns the five closest things it has, which read exactly like an answer. We watched one narrow question come back with five real, confident, useless results, and an agent reading them would conclude the topic was covered. The fix was not a better cutoff. It was a second model that reads the results and decides whether any of them actually answer the question, and injects nothing when the answer is no. That is the mid-run judge in the architecture above.

"Reversible" is not "safe," so the cheap model lost the ability to write. The first design let the cheap proposer write memories directly, reasoning that a private write is reversible and therefore low stakes. That reasoning is wrong. During a test run, the proposer wrote a made-up statistic, a confident number about our own system that was never true, three hours before the session itself corrected the claim. A wrong memory does not sit quietly waiting to be fixed. It gets served back into later sessions as established fact, and the undo never happens, because undoing it means already knowing it is wrong, which is the very thing the memory prevents. So the write moved to the agent, and the proposer was left with no way to write anything at all. It cannot corrupt the store because it cannot reach it.

A fail-open component has to keep receipts, because we could not tell it was dead. Every hook here fails open by design: a broken hook must never break your turn. That is correct, and it has a hard corollary we had not named. The very first layer, the recall that runs before each step, read one field name wrong from its input and gave up on its first line, every time. It was wired up, deployed, described as working, and silently doing nothing in every session since it was written. Two things hid it. Every test fed it stand-in input carrying the same wrong field the code expected, so a test written from the code's own assumptions could not fail. And when a component fails open, "never ran," "ran and did nothing," and "ran and errored" all look the same from outside unless it writes down what it did. It did not, so a person noticed before any tooling could. A fail-open component has to say what it did.

Shared state in a plain file does not survive concurrency. Several hooks read and write one small state file per session. A counter that should only ever go up was seen going backwards, from thirty-seven to nine. The cause is that the ordinary way to write a file is not atomic: it empties the file, then writes, and any reader that lands in that gap gets nothing and falls back to a blank slate. Under normal load a third of reads were torn. The textbook fix, write a temporary file and rename it into place, then quietly dropped more than half its writes on one common operating system, because that system will not replace a file another process is holding open, and the readers hold it open constantly. It failed more than the bug it replaced, while looking healthier. The lasting lesson outlived both attempts: state that must never move backwards does not belong in a file you overwrite. It belongs in a log you only ever append to.

The lesson under all of them

Across the days we built this, roughly seven confident self-checks turned out to be wrong: an optimization whose headline number was backwards, a health check that reported all clear while files were broken, a test that confirmed an assumption instead of a requirement, and the dead field name above, among others. Every one was caught by re-checking a result already reported as good.

That is not a confession, it is the finding, and it is the same shape as every bug above: a signal that cannot see the thing it claims to report. A search score that is the same for a hit and a miss cannot rank relevance. A counter that resets on a bad read cannot report a bad read. And an agent's own check on its own work cannot be trusted, because it re-derives from a stale picture, trusts the derivation, and reports success. Which is the argument for involuntary recall, one level up. The value of a step that fires on its own is that it does not depend on the agent noticing it needs help, and noticing is the thing that fails first.

Where this honestly stands

This runs in our own work today: recall before each step, the background pass while the agent works, and capture that proposes candidates the agent commits. All of the local model calls run on a subscription, not billed per call, and at the rate we run them they cost a few percent of the main model's spend. In a pilot, recall returned the right result at the top for five of eight realistic situations and in the top five for seven of eight, and the always-loaded set did not leak into unrelated questions.

What we are careful not to claim: the cheap model proposes, an agent writes. The thresholds are sensible defaults, not a tuned system. And this specific machinery is what we run, not yet something you install in one command. What ships today is the memory tier itself, in the agentic-SDLC blueprint. The loop on top of it is what we run on our own work, on the platform we sell.

The next post is the part we were building toward from the start: moving our own agent's memory onto that tier for good, and the thing the move turned up in our own documentation.