DEV Community

Tom Jones
Tom Jones

Posted on

Crystals: agent memory that arrives before you act

Most agent memory is a search box. The model wonders something, a retriever runs, chunks come back. That design has one load-bearing hole: retrieval fires only when the agent already suspects it needs help. The expensive mistakes are the confident ones, and a confident agent never queries.

So we push instead. A note gets bound to an action, and it arrives in the moment before that action runs, whether or not anybody asked.

We call these crystals. The name earns its keep. A crystal has one axis, it formed under pressure, and what you get out is small and hard. One knowing per crystal. That constraint turns out to be the difference between this working and this quietly failing, for reasons I will get to.

Before writing this up I went looking for prior art, expecting to find none. Expecting to find none is usually the sign you have not looked hard enough, and sure enough, two of the four ideas here are already published. Rules that fire on the action rather than on a similarity score have been described, right down to injecting before a git commit under a hook time budget. The agent-memory evaluation literature also has a failure taxonomy that comes close. Mem2ActBench attributes memory-grounded errors to five types, two of which sound like the one below: Retrieved-but-Unused, "evidence is retrieved but not utilized", and Lossless Retention Failure, "long/structured values are corrupted (e.g., truncation or character-level errors)".

Read them closely, though, and both sit on the model's side of the boundary. Retrieved-but-Unused means the evidence reached the context window and the model ignored it. Lossless Retention Failure means the model mangled a long value on the way out. The failure I describe below happens earlier, in the plumbing: the note is correctly matched and then cut by the budget packer, so it never reaches the model at all. To the model it never existed; to the logs it was a hit.

The budget problem is named too. A 2026 survey of agent memory lists "curation under fixed budgets, including update, eviction" as an open direction, and asks for benchmarks that measure "memory quality as a function of token budget, storage cost, and latency, reflecting the bounded-memory conditions of real deployments."

Which is a fair description of what follows, so treat this as a field report against that request rather than a new idea. What I have that the papers mostly do not is a channel that has been running long enough to overflow, and the numbers from the far side of that. Here is the mechanism, the budget it collides with, and the two rules we did not know we needed.

Anatomy of a crystal

A crystal is a markdown file with a binding block at the top:

crystal:
  on: bash, write                              # which ACTS it fires on
  match: git reset --hard, git clean, rm -rf    # substrings in the act's text
  deliver: act                                 # the channel
  stale_after: 2027-03-25                      # after this, the claim is withheld
Enter fullscreen mode Exit fullscreen mode

and, further down, the part that actually gets delivered, fenced by two markers:

<!-- crystal:essence -->
⛔ A DESTRUCTIVE VERB ACTS ON A TREE YOU SHARE. `git reset --hard <ref>` also
discards TRACKED-but-uncommitted changes; only UNTRACKED files survive...
<!-- /crystal:essence -->
Enter fullscreen mode Exit fullscreen mode

The file around it can run three thousand words of history, rationale and dead ends. Only what sits between those markers reaches the model. So the crystal is the delivered sentence, and the file is where it came from. That split matters: the file is for whoever is arguing about the rule later, the essence is for whoever is about to break it in four seconds.

Hooks, and what an "act" is

A hook fires on the tool call before it executes. Which tool it was determines the act, and the act determines what a crystal can match against:

act fires what match: tests against
bash before a shell command the command line
write before a file write or edit the path, plus the content being written
delegate before briefing a subagent the brief
commit inside the git pre-commit hook the staged file list
prompt when a request arrives, before any tool runs the request text
boot at session start nothing yet

The third column is where the power sits. write can see content, so a crystal about pricing claims fires on the pricing claim itself and leaves your other edits alone. prompt fires earliest of all: the crystal lands before the first tool call, which is the only place you can still change the plan rather than the keystroke.

Matching stays deliberately dumb. match: is a comma list of plain substrings, tested case-insensitively, with no word boundaries and no embedding anywhere in the path. A substring test stays auditable at a glance and cannot drift, which is what you want from something firing three hundred times a day.

What it looks like when a crystal fires

Three real ones from a single day of work.

Before a shell command. I typed a git reset --hard. Before it ran:

⛔ A DESTRUCTIVE VERB ACTS ON A TREE YOU SHARE. git status --short first, and account for every line. Uncommitted work you did not make is an agent's.

Before a file write. I began writing a note that made a causal claim. Before the write landed:

⛔ If you are naming a CAUSE: write the RIVAL (what ELSE produces this exact observation?) and the DISCRIMINATOR (the one measurement that separates them). On 2026-07-19 four confident cause-claims were made in one evening; every one had evidence and every one was wrong.

Before a commit. In the pre-commit hook, matched against the staged file list:

⛔ FINISHING SOMETHING IS NOT THE SAME AS IT BEING RECORDED WHERE PEOPLE READ IT. Name in the commit message everything the commit changed, especially a flag touched while doing something else.

I asked for none of those. That is the entire point, because in each case I was not wondering.

Then you hit the budget

There are 300-odd crystals. The median action matches five of them, wanting 13,014 characters. The budget is 4,000 characters per action, and no single crystal may take more than 2,000 of it unless it wins outright.

93% of actions want more than the whole budget. Three times oversubscribed, at the median. So the channel behaves like an auction that runs a few hundred times a day, and on almost every run something loses.

That reframes the design question. Delivery was the easy half. The hard half is an admission rule, an eviction cost, and a way to degrade.

Fair scheduling beats relevance here

Selection goes least-served-first, then longest-unheard, then shortest as a packing tiebreak. A crystal that already fired this session steps aside for one that has not.

Relevance ranking sounds better and fails badly here. A handful of broad crystals win every auction while fifty go undelivered, which is what we measured before rotation existed.

A cheap tier that somebody wrote

When a crystal cannot fit, it emits a one-line summary instead. We call that its TELL, and the median TELL runs 81 characters.

We tested whether a TELL carries anything real. Take 20 crystals whose full text demonstrably changes what the agent does, and run a blind judge against three resolutions:

what the model receives median chars behaviour preserved
full essence 2,302 20 / 20
head, 750-char prefix 631 19 / 20
authored TELL 81 17 / 20
421-char prefix 421 12 / 20

Two things fall out, and I nearly published the second one backwards.

An authored 81 characters beat an arbitrary 421-character prefix, 17 against 12, at a fifth the size. A prefix stops wherever the character count happens to land, which is mid-argument more often than not.

But a longer arbitrary prefix does fine: 750 characters keeps 19 of 20. So the enemy here turns out to be brevity itself. Cut lightly and curation barely matters; cut deep and it starts to matter enormously. Zero items flipped the other way at any resolution, which argues against judge noise.

Where it still breaks

Head-first truncation is safe when a crystal carries one idea, because the claim leads. Give a crystal two ideas and the second one sits in the tail, where it silently vanishes.

Across our corpus that comes to 98,715 characters which are stored, correctly matched to an action, and withheld on any act where rotation does not elevate them. Of 102 crystals over the per-item cap, 88 withhold at least one marked claim in the tail. Treat that 88 as an upper bound. Some tails turn out to be supporting evidence for the head's claim, and I have not separated those by hand.

No retrieval metric has a name for this. The crystal is present, correctly selected, and cut: it logs like a hit and behaves like a miss. I found it by shipping the same bug twice in one session with the warning sitting one character-cap away from my eyes.

Match precision is the other open front. A blind two-judge study asked how often an admitted crystal actually changes the action, against random same-act controls. The controls scored 0 of 41 both times, so the matcher is doing real work (p=0.0001 and p=0.0054). The crystals themselves scored somewhere between 20% and 49%.

That range is embarrassing, and explaining it matters, because it turns out to be an artifact of my own apparatus. The 20% end was measured by showing the judge a 421-character excerpt, which the resolution study above had already identified as the worst representation tested. Shown fuller text, the same judge said 48.8%. I had measured relevance through a truncation, and I nearly published the truncation's score as the system's.

Either way, roughly half the admitted crystals fail to earn their slot. Substring keys are the reason. A two-letter key like pt matches nearly everything, and one loose key is how a single crystal loses the auction for everybody else.

The two rules we did not have

One knowing per crystal. This reads as style advice and turns out to be load-bearing. Head-first truncation is only safe when the claim leads, and we learned that by watching second claims disappear. A crystal that has grown two ideas wants splitting.

A crystal earns its binding by beating what it evicts. Truth alone will not buy a slot. At three times oversubscribed, every new binding forcibly removes one that already matched, so admission is always a comparison against the incumbent.

We put that to the test. I proposed binding eight orphaned notes. Two reviewers, briefed separately, independently came back with zero to two, and the admission test they both converged on was:

  1. Does it change this action?
  2. Can a tight key reach it without catching everything else?
  3. Is the act earlier than the next reliable check that would catch the same mistake?
  4. Is there no cheaper enforcer?
  5. Does it beat the incumbent it evicts?

Most candidates die on 4. A crystal about your own tooling usually wants to be an assert in the script instead.

If you build one

The core design holds. Pushing beats pulling for anything that must not be forgotten, precisely because the agent's sense of when it needs help is the faculty you cannot lean on.

What we underestimated is that a push channel is a budget, and a budget needs an economics. We built the delivery first and found the economics a month later, by measuring what never arrived.

Measure that early. The useful question about a crystal is what did this displace, and did anyone ever read the part that got cut.

Top comments (0)