Two architectures for teaching agents to remember decisions. One stores traces and retrieves by similarity. The other learns a compressed prototype that scores in O(1) — no retrieval, no LLM call.
What is Judgment Memory?
Same decision stream. 4 categories, 8 factors, 4 actions. JM scores before it learns. k-NN: distance-weighted retrieval, k=5, 400-chunk cap, FIFO eviction.
Two complete implementations. Read them side by side.
class AgenticMemory:
"""k-NN retrieval memory. The pattern behind LangChain, Mem0,
and most agentic frameworks."""
def __init__(self, k=5, cap=200):
self.store = [] # flat list, all categories mixed
self.k = k # neighbors to retrieve
self.cap = cap # max traces before eviction
def score(self, factors, category='general'):
"""O(n*K) — scans entire store, every call."""
if len(self.store) < self.k:
return None, 0.0
dists = [
(euclidean(factors, t['factors']), t)
for t in self.store
]
dists.sort(key=lambda x: x[0])
neighbors = dists[:self.k]
votes = {}
for d, t in neighbors:
w = 1.0 / (d + 1e-8)
votes[t['action']] = votes.get(t['action'], 0) + w
best = max(votes, key=votes.get)
return best, votes[best] / sum(votes.values())
# → THEN: 1 LLM call to synthesize retrieved context
def learn(self, factors, action, **kwargs):
"""Append. No compression. No gate. No quality signal."""
self.store.append({
'factors': factors,
'action': action,
'ts': now()
})
if len(self.store) > self.cap:
self.store.pop(0) # FIFO eviction
# No drift protection
# No outcome-quality handling
# kwargs silently ignored
class JudgmentMemory:
"""Centroid learning. O(1) inference, conservation-gated updates."""
def __init__(self, tau=0.1, drift_thresh=0.4):
self.centroids = {} # {category: {action: [mu]}}
self.tau = tau # softmax temperature
self.drift_thresh = drift_thresh
self.gate_log = [] # every drift event recorded
def score(self, factors, category='general'):
"""O(A*K) — constant, category-scoped. Zero LLM calls."""
if category not in self.centroids:
return None, 0.0
C = self.centroids[category]
dists = {
a: sum((f - c)**2 for f, c in zip(factors, mu))
for a, mu in C.items()
}
exps = {a: exp(-d / self.tau) for a, d in dists.items()}
Z = sum(exps.values())
best = max(exps, key=exps.get)
return best, exps[best] / Z
# → Done. 0 LLM calls. <1ms.
def learn(self, factors, action, category,
confirmed=True, outcome=None):
"""Update centroid. Conservation gate checks drift."""
if outcome == '__incorrect__':
return {'skipped': True} # uncertain → skip
eta = 0.05 if confirmed else 0.01 # 5x differential
mu = self.centroids[category][action]
# Conservation gate
disp = norm(factors - mu)
if disp > self.drift_thresh:
eta *= 0.5 # dampen outlier
self.gate_log.append({'disp': disp, 'action': action})
for i in range(len(mu)):
mu[i] += eta * (factors[i] - mu[i]) # c += η·(x - c)
return {'gated': disp > self.drift_thresh,
'eta': eta, 'disp': disp}
Five things JudgmentMemory does that AgenticMemory cannot:
| Dimension | Agentic Memory | Judgment Memory |
|---|---|---|
| Inference complexity | O(n·K) — grows with store | O(A·K) — constant |
| Memory footprint | n traces × K factors each | A·K floats per category |
| LLM calls at inference | 1 (synthesis of retrieved context) | 0 |
| Drift protection | None (FIFO eviction only) | Conservation gate (3-layer) |
| Learning signal | Append only | Confirmed/override (η differentiated) |
| Latency (modeled) | ~340ms (retrieval + LLM) | <1ms |
| Category isolation | Single shared store | Scoped per category |
| Eval protocol | No separation of train/eval | Score-before-learn (strict online) |