Most takes on AI memory are about remembering more. I think that is the wrong target. The question is not how much your agent remembers. It is what stays present when it stops looking relevant.
Here is the failure I kept hitting. The thing I most needed the agent not to forget, a killed option or a hard constraint, was usually the thing that looked least relevant to the task in front of it. So any system that surfaces context by relevance would quietly down-weight it, right when it would have saved me. You only notice when the agent cheerfully re-proposes the exact approach you ruled out three sessions ago.
Pull-by-relevance is not push-by-state
This crystallized in a back-and-forth on dev.to with Daniel Nevoigt, who builds Bastra Recall, a local-first memory engine. He named the cut better than I had, so I will use his framing.
A recall engine is pull-by-relevance: the model asks, a ranker answers with what looks relevant to this turn. It is good, and getting a real ranker right is the hard part, because a full context window degrades (lost-in-the-middle is well documented). You are not just storing memory, you are rationing the context window.
An operating surface is push-by-state: the process decides what must be present no matter what the current turn thinks it needs. A killed option, an open decision, a constraint. These have a job precisely when the turn does not think it needs them. One ranker cannot serve both objectives, so you want both layers, not one pretending to be the other.
The thing you most need to not forget is often what looks least relevant to the happy-path turn.
The hard job is unpinning
The obvious fix is to pin the important things so they always load. That is half right. Pinning is easy. The hard job is unpinning.
A pinned set that only grows becomes a second always-loaded blob, and you have rebuilt the context-window problem one level up. So the part that matters is the lifecycle: pin when a decision opens, demote it back to relevance when it settles, retire it when a constraint dies. And you do not want a hand-maintained pin list. Membership should fall out of state you already track. Open becomes settled. Active becomes superseded. The set curates itself off decision events, not off a flag someone remembers to toggle.
A decision is pinned while it is open, demoted when it settles, retired when it is superseded. The set curates itself off status, not a manual list.
Which is why, to me, two different things survive a session, not one:
- The settled conclusion: what we decided, the fact, the chosen approach. A recall engine handles this well.
- The open loop: what is still undecided, what changed since last session, what the next step is. This has no settled truth to retrieve, because it is the unfinished part. It rots first, and a relevance ranker has nothing to grab onto, because nothing in the work references it yet.
What I actually do
I keep the open loop in plain Markdown files the agent reads at the start of a task and rewrites at the end. Decisions carry a status, open questions live in their own file, and every task ends with a small write-back step so the set stays current.
## decisions/decisions_log.md
- 2026-06-20 Launch channel = blog first, then dev.to. status: accepted
- 2026-06-21 Call the repo "cowork-os". status: locked
- 2026-06-22 Drop the second pricing tier for now. status: open
That status field is the whole trick. open means it stays in front of the agent. locked means it can drop back to relevance, the engine will surface it if a turn needs it. I can open the file and see exactly what the system believes is true, and fix the line if it is wrong.
This is not a memory engine and it does not replace one. Native memory exists, I use it. Recall engines like Bastra are real and good at the fact layer. This is the layer on top: the operating state you read and govern, sitting alongside a recall engine, not against it.
I packaged it as an open-source kit for Claude Cowork called cowork-os: the folder structure, the instructions that enforce the write-back habit, and the recurring routines. MIT, one command. If it saves you from re-explaining your own decisions to your agent, a star helps other people find it.
I run my whole company Yempik on it
One question if you have built anything in this space: when your agent re-proposes something you already ruled out, is that a retrieval miss, or did the killed option never have a home that was not relevance-gated?

Top comments (3)
To your closing question, I think the honest answer is neither, exactly. Not a retrieval miss in the usual sense, because the killed option does not exist as a fact to retrieve. Not "no home" either, because some systems do try to give it one. What I see fail in practice is a third shape: the killed option had a home, but the home itself was relevance-gated. The reason the option died was attached to a project that has since moved on, the surrounding context no longer triggers the ranker, and the killed-ness drops out with everything else from that project. Which means push-by-state has to carry the supersession reason as a first-class field, not just the status flag. Re-proposing X happens less when the system can surface "X was killed because Y" than when it can only surface "X was killed."
The push-by-state versus pull-by-relevance split does the load-bearing work that "memory" alone cannot, and the unpinning lifecycle is the half I see most attempts skip. The three lifecycle states (open, accepted, locked) cover most of what I carry too. The one I have ended up adding is "superseded by," because the most useful signal at re-proposal time is what replaced the killed option, not just that it died. A locked decision with a clean supersession pointer is also easier to retire later, because the chain is auditable from the current state backward.
Your second paragraph names something I think most writing in this space steps over: the thing the agent most needs not to forget is often the thing that looks least relevant to the happy-path turn. That is also why ranker-only memory feels right in demos and wrong in week three.
One question back, since you asked one: in your write-back step, what is the smallest piece of state you have found has to be re-asserted every session, even when nothing about it changed, just because the relevance gate cannot be trusted to surface it on its own? My instinct is "the active constraint set," but I am curious what survives in your usage.
Honest answer from my own usage: not the active constraint set. Constraints at least bind something the current turn touches, so a ranker catches them part of the time. The thing I re-assert verbatim every session, unchanged, is the set of open loops, the decisions still marked open. A settled fact has a relevance hook and gets surfaced when it matches the turn; an open question has nothing to match yet, it is waiting, so similarity scoring is structurally blind to it. Openness is not a similarity signal, which is exactly why ranker-only memory drops it, and it stays tiny because it is just the loops, not their content.
Your "superseded by" pointer maps onto this cleanly. When an open loop closes I do not flip a status flag, I attach "resolved by [decision id]" so the chain reads backward from current state, the same auditability you described for killed options. The open to settled transition earns a pointer the same way killed to replaced does.
Question back: do "killed with reason" and "still open" ride the same ranker for you, or do you surface still-open on a separate path? Killed-because-Y at least gets a relevance hook once someone touches that area again; still-open has none until it is re-touched, and that gap is exactly where I keep having to push state instead of trusting pull.
"Openness is not a similarity signal" is the atom worth stealing forward, and you just named the structural reason ranker-only memory drops what it most needs to keep. Negative-space state (what has not been decided, what has not been observed, what has not been resolved) is invisible to relevance scoring because there is nothing to score against. Push-by-state is the right mechanism specifically for negative-space artifacts, and the engine has no way to know it without being told.
To your concrete question: yes, they ride different paths in my current architecture, and I will be honest that the architecture is ~24 hours public so this is intent more than stress-tested usage. Killed-with-reason gets a relevance hook because the reason is content the ranker can match. Still-open gets surfaced via push because the decision id and topic are the only artifacts; there is no resolution content yet for similarity to grip on. Same engine, different paths.
The third class I have been adding, less sharp than your two: aged open loops. An open decision that has been open for three weeks is still openness-not-similarity, but the ranker also has no concept of "this should have closed by now." Time-since-opened becomes a separate push signal with a different urgency, because the failure mode is not "ranker missed it" but "it has gone quiet and nobody has noticed." Aging is push-by-time on top of push-by-state, and it produces different surface behavior.
Stealing "openness is not a similarity signal" as the line, same way you took consequence locality earlier. Better naming of why pull-by-relevance structurally fails on the loop class than I had.