How it works
Contradictions
Two claims that cannot both be true, stored as a pair with an actionable question — and pinned into every prompt until a human answers it.
What one is
A contradiction is two claims that cannot both be true, stored as a pair with an actionable question. Not a flag on either memory, and not a resolution — a third thing, with its own identity and its own lifecycle.
The question is the part that matters. “These memories disagree” is not actionable; “Which is it?” is. Every tension carries a question phrased so it can be answered in a sentence, because the answer usually arrives as an ordinary user message and the thing doing the asking is a prompt.
### Unresolved Contradictions- [CRITICAL] "We deploy on Fridays" conflicts with "We never deploy on Fridays". Ask: Which is it?Why it is always injected
A tension is pinned into every context build until it is resolved, and it is the one thing always injected in full regardless of budget — because the whole point is that the agent sees both sides rather than picking one.
This is the concrete case for the exemption L0 gets from the budget. A tension that got truncated away is indistinguishable from a tension that was never detected, and both look exactly like a working system right up until the agent acts on the wrong premise.
The severity tag on a tension — CRITICAL above — is not severity of the disagreement but of what the agent might do while it is unresolved. It is what makes triage possible when there are more open tensions than anyone has time to read.
How one is detected
Detection is deliberately conservative, because a false tension is expensive in a way a missed one is not. A tension that does not exist pins two claims into every prompt forever and trains the user to ignore the section; a missed one is a single bad turn.
Two paths produce one. A merge candidate that cannot satisfy the merge rule while sharing enough structure to be about the same thing is a tension, not two memories. Separately, capture compares a new statement against what is held, and an explicit negation of a stored claim — “we never deploy on Fridays”, against a stored “we deploy on Fridays” — is the clearest signal there is.
And a human can always record one directly, which matters more than it sounds: the deterministic paths only see claims that were both stored. “These two things you told me cannot both be true” is something a person notices and the service cannot.
await memory.tensions.create({ claimA: "We deploy on Fridays", claimB: "We never deploy on Fridays", actionableQuestion: "Which is it?", impact: "critical"})Resolving one
Resolving keeps the resolution, including the reusable pattern it revealed. Deleting it would mean rediscovering the same contradiction next month.
That pattern is the genuinely valuable output. A resolved tension is evidence about how this project actually works — that the Fridays rule was a deployment-window constraint rather than a general one, say — and it is stored as a fact that participates in tiering like any other.
| Operation | What it does |
|---|---|
| Open | Pinned into every prompt, listed in the dashboard by status. |
| Resolved | Marks the answer, stores the pattern it revealed, unpins the pair. The resolution is kept. |
| Reopened | Pins the pair again. Used when the answer turns out to have been situational. |
| Deleted | Removes it entirely. Discards the pattern too. |
Reopen and delete are separate because the second is destructive and the first is not. A resolution that was right for one deploy cycle is common enough that conflating it with deletion loses the pattern.
Where to look
Tensions lists them by status with resolve and reopen, and the overview counts the open ones, because an unresolved tension is the single most likely thing to need a human.
They also show up in GET /v1/stats and in GET /v1/tensions?status=open, so a build can surface the count without reading prose. The API reference has both.