holo-antimemory
A memory where negation is a first-class object.
Most memory systems store presence. "This fact is known" is the only state they can represent. This tool stores presence and absence separately. Query returns one of four verdicts:
| Verdict | Meaning |
|---|---|
YES |
X is asserted |
NO |
X is denied |
UNKNOWN |
neither asserted nor denied (or both below threshold) |
AMBIGUOUS |
both asserted and denied with comparable weight |
The store is a single complex vector space of dimension D. Two independent traces hold positive and negative assertions:
P_yes = sum_i w_i * bind(item_i, item_i)
P_no = sum_i v_i * bind(item_i, item_i)
Query with item X reads both traces and returns a four-way verdict with the raw weights visible to the caller. Contradictions are not resolved silently β they are the fourth verdict, and they are queryable.
What it does
Store a fact. Deny a fact. Store and deny the same fact. Query any of them and get back a verdict that distinguishes all four states.
mem.store("paris_capital_france")
mem.deny("berlin_capital_france")
mem.store("water_is_wet")
mem.deny("water_is_wet")
mem.query("paris_capital_france") # YES
mem.query("berlin_capital_france") # NO
mem.query("water_is_wet") # AMBIGUOUS
mem.query("tokyo_capital_japan") # UNKNOWN
The AMBIGUOUS state is not an error. It is the store reporting that
it holds both signals and that neither dominates. Contradiction is a
first-class object.
Why this matters
Standard memory stores are sets. "X is in the store" is the only question they can answer. This means they cannot represent "X is not the case" as a fact β only as the absence of the fact. The two states are conflated.
The anti-memory separates them. An item's positive weight and negative weight are stored independently. A fact can be both asserted and denied at the same time, and the store reports the conflict rather than silently choosing one side. This is useful whenever the caller wants to reason about contradictions rather than resolve them:
- Knowledge bases. Two sources disagree about the same fact. The store records both, and the query reports the conflict.
- Reasoning over conflicting evidence. An agent receives contradictory signals. The anti-memory preserves both, and the agent's policy decides what to do with them.
- Change over time. A fact was asserted in one session and denied in another. The store keeps the history rather than collapsing it.
- Explicit negative knowledge. "I know that X is false" is different from "I don't know about X." The anti-memory distinguishes them.
Installation
pip install numpy # required
pip install matplotlib # optional, for the summary plot
Usage
Python
from holo_antimemory import AntiMemory
mem = AntiMemory(d=2048, margin=0.15)
mem.store("paris_capital_france")
mem.deny("berlin_capital_france")
mem.store("water_is_wet")
mem.deny("water_is_wet")
r = mem.query("water_is_wet")
print(r.verdict) # "AMBIGUOUS"
print(r.raw_yes) # ~1.00
print(r.raw_no) # ~1.00
print(r.margin) # ~0.00
CLI
python holo_antimemory.py
python holo_antimemory.py --output results/
python holo_antimemory.py --margin 0.25
Runs ten demonstrations and writes output to a directory.
Results
All results from a standard run at D=2048 with margin=0.15. The self-test verifies that cosine is scale-invariant while raw projection is not, which is the property that makes weight readout possible.
1. Verdict taxonomy
| Item | Weight (yes/no) | Verdict | raw_yes | raw_no | margin |
|---|---|---|---|---|---|
asserted_only |
1.0 / 0.0 | YES | +1.026 | +0.003 | +1.024 |
denied_only |
0.0 / 1.0 | NO | -0.001 | +1.023 | -1.024 |
both_sides |
1.0 / 1.0 | AMBIGUOUS | +1.026 | +1.023 | +0.004 |
never_seen |
0.0 / 0.0 | UNKNOWN | 0.000 | 0.000 | 0.000 |
The four verdicts are clean. A perfectly balanced assertion and denial returns AMBIGUOUS with margin 0.004. A missing item returns UNKNOWN.
2. Contradiction detection
Items with weight on both sides are detected and ranked by conflict score (1.0 = perfectly balanced, 0.0 = one side dominates):
| Item | Weight (yes/no) | Conflict |
|---|---|---|
fact_a |
1.00 / 1.00 | 1.000 |
fact_b |
1.00 / 1.00 | 1.000 |
fact_d |
1.00 / 0.30 | 0.462 |
fact_c (1.00 / 0.00) is not a contradiction and does not appear.
3. Weighted assertions
The margin parameter controls when two weights are "close enough" to be a contradiction. At margin=0.15:
| Item | Weight (yes/no) | raw_yes | raw_no | margin | Verdict |
|---|---|---|---|---|---|
common_belief |
1.00 / 0.30 | +0.982 | +0.267 | +0.714 | YES |
balanced_debate |
1.00 / 1.00 | +0.981 | +0.954 | +0.027 | AMBIGUOUS |
weak_assertion |
0.20 / 0.20 | +0.249 | +0.224 | +0.025 | AMBIGUOUS |
strong_denial |
0.00 / 5.00 | -0.012 | +4.990 | -5.002 | NO |
The 10:3 ratio resolves to YES. The 1:1 ratio is a contradiction. The 5:0 denial is a NO. The raw projection recovers the stored weights to two decimal places.
4. Repeated store and deny
Storing the same fact five times accumulates weight. often_asserted
(5 stores) shows raw_yes = +5.03. sometimes_denied (2 denials) shows
raw_no = +2.00. A skewed item (1.0 vs 0.2) resolves to YES at
margin=0.15 because the raw gap (0.886) exceeds the margin.
5. Entity-level denial
Denying all four facts about a person produces four perfectly balanced contradictions:
alice_lives_in_paris: AMBIGUOUS (raw_yes=+1.000, raw_no=+1.000)
alice_works_at_acme: AMBIGUOUS (raw_yes=+0.990, raw_no=+0.990)
alice_has_a_dog: AMBIGUOUS (raw_yes=+1.048, raw_no=+1.048)
alice_speaks_french: AMBIGUOUS (raw_yes=+0.987, raw_no=+0.987)
The entity-level operation (deny_many) applies to the whole set in
one call and produces contradictions per fact.
6. Margin sweep
Three items, each with both positive and negative weight, queried with the margin parameter varying from 0.0 to 0.8:
| margin | balanced | skewed_2_to_1 | skewed_5_to_1 |
|---|---|---|---|
| 0.00 | YES | YES | YES |
| 0.10 | AMBIGUOUS | YES | YES |
| 0.20 | AMBIGUOUS | YES | YES |
| 0.30 | AMBIGUOUS | YES | YES |
| 0.50 | AMBIGUOUS | YES | YES |
| 0.80 | AMBIGUOUS | AMBIGUOUS | YES |
Reference margins (the true weight differences):
balanced: +0.009skewed_2_to_1: +0.518skewed_5_to_1: +0.811
A balanced item stays AMBIGUOUS at every margin. A 2:1 skewed item stays YES until margin exceeds its gap of 0.518. A 5:1 skewed item stays YES through margin 0.80. The margin parameter is the user's choice of how close two sides must be to be called a contradiction.
7. Query noise robustness
Store is fixed. Query keys are noisy. Mean raw_yes across 20 items:
| Query noise | YES ok | NO ok | AMBIG ok | mean raw_yes |
|---|---|---|---|---|
| 0.00 | 20/20 | 20/20 | 1/1 | 1.006 |
| 0.10 | 20/20 | 20/20 | 1/1 | 1.005 |
| 0.30 | 20/20 | 20/20 | 1/1 | 0.985 |
| 0.50 | 20/20 | 20/20 | 1/1 | 0.930 |
| 0.80 | 20/20 | 20/20 | 1/1 | 0.808 |
| 1.20 | 20/20 | 20/20 | 1/1 | 0.629 |
| 2.00 | 20/20 | 20/20 | 1/1 | 0.416 |
Verdicts stay correct because the threshold (0.05) is well below the noise-degraded raw weight (0.416 at noise 2.0). The confidence degrades smoothly. A user who wants the store to refuse low-confidence retrievals raises the threshold; a user who wants maximum recall keeps it low.
8. Capacity of the two-trace store
Stored N positive items and N negative items simultaneously. Verdicts queried at the end of the run:
| D | N | YES correct | NO correct | UNKNOWN correct |
|---|---|---|---|---|
| 1024 | 100 | 100/100 | 100/100 | 100/100 |
| 1024 | 200 | 200/200 | 200/200 | 200/200 |
| 2048 | 200 | 200/200 | 200/200 | 200/200 |
| 4096 | 200 | 200/200 | 200/200 | 200/200 |
The two-trace store holds both sides at D=1024 with N=200. The raw projection readout is more robust than cosine here because cosine divides out the scale, which is where the weight information lives.
API reference
AntiMemory
AntiMemory(d=2048, seed=0, threshold=0.05, margin=0.15)
Storage
store(item, weight=1.0)β add positive weight for item.deny(item, weight=1.0)β add negative weight for item.store_many(items, weight=1.0)β positive for a list of items.deny_many(items, weight=1.0)β negative for a list of items.
Query
query(item) -> QueryResultβ four-way verdict with raw weights.query_many(items) -> [QueryResult]β batch query.contradictions() -> [(item, w_yes, w_no)]β all items with weight on both sides.contradictions_with_conflict() -> [(item, w_yes, w_no, conflict)]β ranked by conflict score.
Maintenance
forget(item)β remove all trace of item from both traces.reset()β clear the store.
Diagnostics
stats() -> dictβ counts, magnitudes, contradiction count.save_json(path)β full state as JSON.plot(path)β bar chart of positive and negative weights per item.
QueryResult
@dataclass
class QueryResult:
item: str
verdict: str # YES / NO / UNKNOWN / AMBIGUOUS
raw_yes: float # raw weight read from positive trace
raw_no: float # raw weight read from negative trace
weight_yes: float # total positive weight stored for this item
weight_no: float # total negative weight stored for this item
confidence: float # magnitude of the winning signal
margin: float # raw_yes - raw_no (positive means YES dominates)
note: str # explanatory note for the verdict
Design notes
Why two traces
A single signed trace would collapse balanced contradictions to zero.
If X is asserted with weight 1 and denied with weight 1, a signed trace
P = bind(X,X) - bind(X,X) = 0 carries no information about either
side. The two-trace structure keeps both signals intact, which is what
makes the AMBIGUOUS verdict possible.
Why raw projection, not cosine
Cosine is scale-invariant: cos(w*v, v) = 1.0 for any positive w.
If the query readout uses cosine, it cannot see the stored weight. The
raw projection vdot(u, v) / ||v||^2 returns the weight directly.
For u = w*v, it returns w. For u = v + noise, it returns
1 + (noise projected onto v).
This is verified in the self-test:
cos(0.3*a) = 1.0000, cos(1.5*a) = 1.0000 (both equal)
proj(0.3*a) = 0.3000, proj(1.5*a) = 1.5000 (different)
Why the margin parameter
Two items with weights (1.0, 0.3) are not the same kind of thing as two items with weights (1.0, 1.0). The first is a mostly-asserted fact with a small counter-signal. The second is a genuine disagreement.
The margin parameter is the threshold between these two cases. An item whose weight difference exceeds the margin is resolved to the dominant side. An item whose weight difference is below the margin is called AMBIGUOUS. The user chooses the margin based on their application.
Limitations
No automatic resolution policy. The store reports AMBIGUOUS but does not decide what to do. The caller must decide: refuse to answer, return both sides, or apply a policy. This is deliberate β deciding how to handle contradictions is application-specific.
No provenance. The store tracks weights, not sources. Two sources that disagree about the same fact will produce an AMBIGUOUS verdict with no indication of which source contributed which weight. Adding a provenance role is possible but not implemented.
Weights do not decay. Storing the same fact 100 times produces
weight 100, which may dominate other items. If decay is wanted,
forget(item) removes all trace and the user can re-store with a
smaller weight.
Repeated store and deny at capacity. At D=1024 with N=200, the store still separates all verdicts. Beyond that, the raw projection readout degrades faster than the pure item store because each item participates in both traces.
No truth maintenance. If a fact is stored and then denied, the store keeps both. It does not perform non-monotonic reasoning to retract dependents of the asserted fact. The AMBIGUOUS verdict is available; the caller decides what it implies.
Known limitations of the demonstration
EXP 7 noise sweep is verdict-insensitive at the default threshold.
Mean raw_yes drops from 1.006 to 0.416 as noise increases from 0 to
2.0, but the verdict count stays 20/20/1 because the threshold (0.05)
is well below even the degraded raw weight. This is not a bug β it is
the store correctly reporting that the signal is above threshold even
under heavy noise. To force verdict degradation, raise the threshold
or raise the noise. The demo uses the default threshold so the reader
can see the smooth degradation of confidence without the verdicts
flipping.
Citation
@misc{holo-antimemory2026,
title = {holo-antimemory: A memory where negation is a
first-class object},
author = {zeechimp},
year = {2026},
note = {Four-way verdict memory with independent positive
and negative traces.}
}
References
- Plate, T. A. "Holographic Reduced Representations." IEEE Transactions on Neural Networks 6:3 (1995), 623β641.
- Kanerva, P. "Hyperdimensional Computing." Cognitive Computation 1:2 (2009), 139β159.
- Gayler, R. W. "Vector Symbolic Architectures Answer Jackendoff's Challenges." ICCS/ASCS (2003).
License
Apache 2.0