Home/Blog/The rule nobody typed
Engineering · One week in May

The rule nobody typed.

On Wednesday, May 6, a rule showed up on the Desklight brand page that nobody had typed. Allie's memory wrote it, from something I'd said in a chat, and cited the conversation. It was the moment the whole memory layer exists for, and I nearly took it at face value. Two days later I traced it back through every layer it should have passed through, found six places where a write said "saved" and saved nothing, and then watched the first honest run tell me something about myself I hadn't said out loud.

A solo operator at a dim late-evening studio workstation, leaning forward to examine multiple terminal windows on dark monitors. A single overhead architect's lamp casts a warm amber pool of light across their hands and the desk surface; the rest of the room recedes into emerald-blue near-black darkness. Their expression is focused and contemplative, the moment of noticing something subtle that wasn't obvious a second earlier.
Friday afternoon, roughly. The desk and the person are generated. What was on my actual screen: three queries, one brand rule, and a zero with a good excuse.

A rule appeared.

Allie has memory. Not the chat history inside one conversation, which is gone when the conversation ends, but a layer that lasts across months of work. It has three levels. Raw observations pulled from email and chat (level 0). Heuristics compressed from clusters of those observations (level 1). Principles synthesized across the heuristics (level 2). The shape comes from the skill-acquisition and memory-consolidation literature. What makes it move on its own is a set of crons, and those shipped on Tuesday night: a daily pass that fades memories nobody has reinforced, a daily sweep that compresses ripe clusters, a weekly synthesis for the principles, and a daily writeback that lifts high-confidence corrections into brand rules you can revert with one click.

The writeback ran for the first time at 5:30 UTC on Wednesday, which is 2:30 in the morning here. When I opened the Desklight brand page, the panel called What Allie's learned had something in it:

Default to measured, understated tone over dynamic or motivational energy — let calm carry the message. From a correction: For desklight: avoid high-energy or intense messaging. Client explicitly requested 'something calmer' — tone should be peaceful, measured, and understated rather than dynamic or motivational. Auto-applied 2 days ago · revertible

I had said that. In a chat, once: something calmer. Allie had filed it as a correction, and the writeback had turned it into a rule, with a citation back to the conversation and a revert button beside it. I read it and thought: she actually learned something. I almost moved on.

That's exactly where a customer would stop, and believe it. It's where the audit had to start.

Three queries.

"Do the tables have rows?" is the wrong question. Every busy, broken system answers yes. The question that can't be faked is whether this one rule, the one on the screen, can be followed backward through every layer the architecture diagram says it passed through. Find the audit row. Dereference the memory it cites. Read the metadata. Three queries.

It held. The audit row was real, two days old, active and revertible. Its source pointer landed on a level-0 correction tagged for the right brand, at the top confidence score, reinforced four times. The source was a chat thread, not an email, which closed a question that had been sitting on my list: whether the chat side of extraction had parity with the email side. It did.

One rule, end to end. The chain worked.

Then the harder question: how often?

A zero with a good excuse.

The dashboard said 386 raw memories and 259 heuristics. Level 2, the layer that reads across the heuristics and writes principles, said zero. Not zero this week. Zero ever.

The zero had an alibi. The weekly synthesis fires on Sundays at 5:00 UTC. The cron had landed on Tuesday. It was Friday. Its first Sunday was two days away, so of course it had never written a row. Not a bug. I could have closed the laptop on that.

Except the dashboard had no way to tell "scheduled, hasn't fired yet" from "fired and produced nothing." Both are a zero. The only way to know which zero I'd be looking at on Sunday was to run the synthesis by hand on Friday and watch. So I did, and the honest answer turned out to be: on Sunday it would have logged a success and written nothing. Claude and I spent the rest of the afternoon pulling on that thread.

Six writes that said they worked.

Six, between the density check and the table. Each one returned cleanly, printed something cheerful in the log, and left no row behind. None of them threw. Here they are, with what fixed each one.

01
The cron was younger than its first window. Level-2 synthesis fires weekly, on Sunday. The cron shipped on a Tuesday; I was probing on a Friday. Zero rows was the correct answer. The problem was that the status panel showed the same zero for "hasn't fired yet" and "fired and saved nothing," and I would have trusted either one. A count of zero is ambiguous on its own. Put the next-run time beside it.
02
The fast model was inventing identifiers. Level-1 compression hands a fast model a cluster of memories, asks for one heuristic, and stores it with the IDs of the memories it came from. The IDs are typed, and the prompt never gave the model the real ones, so it made some up: asset_ref_speed_isnt_the_point, correction-1. Plausible, well formed, wrong. The database rejected the insert with a type error. The code never read the error field. The log printed compressed! anyway. On other runs the model returned an empty list, the insert went through, and the heuristic saved with no sources at all. The log looked identical either way. Check every ID the model returns against the cluster it was handed. If any are invented, use the real set. Read the error field on every write.
03
The strategic synthesizer was inventing them too. Level 2 gives a stronger model the full list of heuristics and asks for cross-cutting principles, drift flags and prune candidates, each pointing at a heuristic by ID. Same gap, worse consequence: the prompt carried no IDs, so the model wrote labels like clarify-platform-angle-variants-x15. The prune step then ran "set inactive where id equals that string" against a column of real identifiers. Zero rows matched. Nothing errored. It would have reported every prune as done. Put the real ID in front of every item in the prompt. Tell the model to copy it verbatim. Reject anything that comes back that wasn't in the input.
04
The synthesizer ran out of room. With about 250 heuristics going in, the model's response hit its output limit mid-string. The JSON parser threw. The catch swallowed it. No insight saved, no row, and nothing you'd notice without re-running it with verbose logging on. Give the model room to finish. The budget went from 2,048 tokens to 8,192; the prompt already caps each insight at 40 tokens, so the headroom costs almost nothing.
05
Nothing stopped a cluster from being compressed twice. The density check knows when a cluster is dense enough to compress. It has no idea whether the cluster has already been compressed. So every nightly tick compressed the same memories again and saved a new, slightly different sentence. Eight active versions of "clarify platform unless the client signals creative autonomy," the same twenty or so source memories, the same reinforcement count, different word order. Retrieval pulls the top few by similarity, so the copies crowded out heuristics that actually said something else. Before inserting, look for an active heuristic (or one pruned in the last week) in the same domain and brand that already covers 60% of the same source memories. If there is one, reinforce it instead. One query at write time beats a filter on every read.
06
The brand tag was case-split. Memories carry a tag for the brand they're about, and the cluster filter compared tags exactly. A tag like Forma would not cluster with forma; VOLT would not meet volt either. Seven rows had split tags in all, which sounds like nothing, except that a split cluster is two thin clusters, and thin clusters never get dense enough to compress. Lowercase and trim at the moment of writing, at every site that writes. One backfill for the rows already there.
0
Strategic insights, Friday afternoon
Level 2 had never written a row. Its first Sunday was two days away, which is what made the zero look innocent.
6
Writes that said they worked
Each returned cleanly, logged "compressed!" or "saved!", and left no row. None threw. None showed up anywhere a person looks.
43
Heuristics pruned, first real run
220 kept, 43 retired, six strategic insights written where there had been none, and four drifts flagged for me to read.
Macro photograph of a vintage analog oscilloscope's CRT phosphor screen. Two waveforms run horizontally across the same fine etched grid, one glowing in saturated amber, the other in cool teal-cyan, tracing nearly the same sinusoidal shape but drifting slowly out of phase, with their crossing points and divergences creating soft moiré interference and faint colour-blend halos through the centre of the screen. Curved-glass corners and faint dust catch the screen light; the screen edges fall off into deep emerald-blue near-black.
Drift isn't decay. It's the older default and the newer one running on the same line, both reinforced, both active, slowly out of phase. The screen is generated; the two lines are a fair picture of what the synthesis found.

The first run that wrote anything.

With the six fixed, I ran the consolidation by hand for the Desklight tenant. It kept 220 heuristics, retired 43, wrote six strategic insights where there had been none, and flagged four drifts for me to look at. One afternoon's run did more than every run before it combined, which is a low bar, since every run before it had done nothing.

The drift was the part I didn't expect. Allie's oldest heuristics, the ones with the most reinforcement behind them, the strongest reinforced about 133 times, say: clarify the platform first, then execute. Her newest ones say the opposite: execute, and only ask when something is genuinely ambiguous. The synthesis step put the two side by side and called it what it was. A migration.

It was right, and it was about me. As I trusted her more, I'd stopped wanting to be asked. I'd corrected her for asking a few times without ever thinking of it as a policy, and the corrections compounded into a new majority rule. I hadn't said that out loud to anyone. The layer that compares heuristics across time noticed before I did.

That's where the question changed, from "is the memory working?" to "what is it telling me?" A dashboard of counts would never have shown it. A filter at retrieval time would have averaged it away. The synthesis step caught it because it looks at the population, not the individual.

Five things I'd carry to any agent with a memory.

Trace one artifact end to end before you believe the dashboard.

Counts answer "are there rows?", and a broken system answers yes. One visible output, followed back through every layer, is faster, harder to fool, and tends to find the broken link in the first ten minutes.

Treat every identifier a model returns as untrusted.

Models invent IDs the same way they invent anything they weren't given: confidently, well formed, in the exact shape the schema checks for. Validate against the set you passed in. If anything is invented, fall back to the real set.

Read the error field on every write.

Most database clients return errors rather than throw them. await client.insert(...) resolves either way. If nothing in your code looks at the error, you don't have writes. You have a tidy log of writes that didn't happen.

Dedup when you write, not when you read.

If retrieval pulls the top few by similarity, six rephrasings of one rule retrieve six times and crowd out the rules that differ. The guard at insert is one query. The filter at read time is every query.

Tell "didn't run" apart from "ran and wrote nothing."

Both are a zero on the dashboard, and the fixes have nothing in common: one is a calendar, one is broken code, one is a model doing what models do. Make the difference visible where a person actually looks.

5:00 UTC.

A memory doesn't have to be perfect to be alive. It has to be honest: fail where you can see it, keep a citation for everything it learned, and prune what turned out to be wrong. An agent having memory isn't the point. The point is that she uses it, and that you can check her work without redoing it.

Allie keeps eligible corrections. Brand rules that are applied carry their source conversation and a revert. Promotion depends on confidence, reinforcement, eligibility and the nightly run. A correction does not automatically become a rule for the next post.

The first scheduled synthesis is Sunday at 5:00 UTC. If it writes nothing, it will say so now.

Questions.

What is an agent's memory layer?

It's what the agent keeps between sessions, separate from the chat history inside any one conversation. Allie's has three levels: raw observations pulled from email and chat (level 0), heuristics compressed from clusters of those observations (level 1), and principles synthesized across the heuristics (level 2). The shape comes from the skill-acquisition and memory-consolidation literature, with drift detection added so the layer can flag when the agent's own defaults are changing.

How does layered memory differ from chat history?

Chat history is working memory for one conversation: the last few turns of a single thread, gone when the thread ends. Layered memory is what carries across conversations: preferences, corrections and patterns observed over months of work. It's the difference between an agent that has been told something once and an agent that knows it.

What is a silent failure in an LLM-augmented system?

An operation that returns successfully, with no exception, no error and no log line saying anything broke, and doesn't do what it claims. In systems built from models, databases and crons they cluster in three places: model responses that look valid but contain invented identifiers, database writes whose errors are returned rather than thrown and never read, and updates where matching zero rows looks exactly like matching one.

How do you verify an agent's memory is actually working?

Not by counting rows. Take one visible artifact, a rule the agent says it learned, and trace it backward through every layer: find the audit row, dereference the memory it cites, check the confidence, reinforcement count and source thread. One artifact end to end is faster than a dashboard and much harder to fool.

Can a memory layer detect changes in its own behavior?

Yes, and it's one of the most useful things it does. Allie's level-2 synthesis compares heuristics across time. When older heuristics with high reinforcement contradict newer ones whose reinforcement is lower but rising, it flags drift. The first time it ran on production data it surfaced the migration from "clarify before executing" to "execute immediately", a real change in how I worked with her that I hadn't put into words. The system noticed before I did.

An agent that gets better the longer you work with it.

Brand rules that are applied carry the conversation they came from and a revert. Desklight is live by invitation. Already invited? Set up your brand and start with 500 credits.