VALORA
Tech Pitch · 2026 · 33 min

Teaching Machines
to Remember

A Biologically-Inspired Persistent Memory
for AI Agent Orchestration

Episodic · Semantic · Decisions Obsidian Vault · Zero Native Deps Ebbinghaus · Hebbian · Spreading Activation

01 — The Amnesia Problem

11 agents. Every session
starts from zero.

Valora orchestrates 11 specialised AI agents through multi-stage pipelines. Each is capable. But the moment a session ends, everything learned vanishes.

Repeated mistakes

An agent flags the same anti-pattern in sprint 1, sprint 5, sprint 12. Each time a fresh discovery.

Rediscovered patterns

Conventions emerge in the codebase. Agents reverse-engineer them on every run instead of building on accumulated knowledge.

Lost decisions

The team chooses an architectural direction. Agents propose already-rejected alternatives. The knowledge-base/ directory was empty.

11Agents
0Persistent memories
Repeated discoveries

02 — Neuroscience Inspiration

"
Without mental repetition, the newly formed connections in the brain are subject to a process of forgetting which, following a regular course, is most rapid immediately after learning.
Hermann Ebbinghaus — Über das Gedächtnis, 1885
The Forgetting Curve 141 years old · still the specification

02 — Forgetting Curve

Three categories.
Three timescales.

Each memory category has a half-life tuned to its nature. Observations fade fast. Patterns persist. Decisions outlive both.

episodic/7d
decisions/21d
semantic/30d

strength = 0.5^(elapsed / halfLife)

Ebbinghaus Forgetting Curve 1.0 0.5 0.0 0 7d 14d 21d 28d 30d ½ ½ @ 7d ½ @ 21d ½ @ 30d

Neuroscience

The biological model

Ebbinghaus Forgetting Curve

Memory strength decays exponentially without reinforcement.

Spaced Repetition

Retrieving a memory strengthens it, pushing the decay curve into the future.

Error Amplification (Amygdala)

Mistakes are tagged with extra durability. We remember embarrassments longer than trivia.

Hippocampal Consolidation

Episodic memories compress into semantic knowledge during low-activity periods (sleep).

v1 — Valora equivalent

The v1 agent model

strength = 0.5^(t / halfLife)

The specification taken directly from Ebbinghaus. Pure maths, zero abstractions.

halfLife += accessCount × 2d

Each retrieval adds 2 days to the effective half-life. Frequently queried patterns persist.

isError → halfLife × 2

Error entries get double the base half-life. Hard lessons outlive routine observations.

valora consolidate

Jaccard tag-merge promotes episodic observations into durable semantic patterns.

03 — v1 Architecture

Three JSON stores. One coherent model.

All memory lives under .valora/memory/ — gitignored by default, per-project, zero infrastructure required.

episodic.json7 days
7d
Default half-life
Raw timestamped observations written during pipeline execution. Decays quickly — noisy, context-specific, short-lived by design.
"no-console lint rule triggered in src/cli/index.ts — suppressed with eslint-disable"
decisions.json21 days
21d
Default half-life
Architectural choices with rationale. Middle decay rate — decisions age, but the reasoning behind them matters longer than observations.
"Auth middleware rewritten for compliance, not tech debt — session tokens stored differently"
semantic.json30 days
30d
Default half-life
Distilled, reusable patterns consolidated from multiple episodic observations. Validated knowledge. Survives the longest.
"Integration tests always use Testcontainers; mock-based integration tests are forbidden"
Zero new runtime dependencies Pure TypeScript · File I/O only Jaccard similarity only · flat isolated lists

03 — The Decay Engine

One formula governs all memory.

strength = 0.5 ^ ( elapsed / halfLife )
At t=0, strength is 1.0.
After one half-life, 0.5.
Asymptotic — no hard expiry cliffs.
decay.ts · preserved unchanged in v2
export function computeStrength(referenceAt: string, halfLifeDays: number, now = Date.now()): number {
  const elapsedDays = (now - new Date(referenceAt).getTime()) / MS_PER_DAY;
  return Math.pow(0.5, elapsedDays / halfLifeDays);  // injectable for deterministic tests
}

export function computeEffectiveHalfLife(
  base: number, accessCount: number, isError: boolean,
  retrievalBoostDays = 2, errorMultiplier = 2
): number {
  const b = isError ? base * errorMultiplier : base;   // amygdala effect
  return b + accessCount * retrievalBoostDays;          // spaced repetition
}
Error multiplier
+2d
Per retrieval
0.05
Prune threshold
pure fn
No side effects

03 — Memory Entry

Confidence tiers
drive injection order.

Every entry carries a confidence tier — unchanged across both v1 and v2. Agents weight memories accordingly.

memory.types.ts · preserved in v2
type ConfidenceTier =
  | 'verified'   // ← highest trust
  | 'observed'
  | 'inferred'
  | 'stale';    // ← never injected

// Injection: verified → observed → inferred
// Stale entries are always excluded.
Verified
Confirmed truth
Multiple agents or runs have validated this observation. Highest priority in prompt injection.
→ Injected first
Observed
Single-source fact
Recorded once by one agent. Reliable enough to surface, not yet corroborated. Second priority.
→ Injected second
Inferred
Derived pattern
Generated by cosine-clustering consolidation. A potential pattern, not yet confirmed. Third priority.
→ Injected last
Stale
Git-invalidated
Downgraded when relatedPaths intersect with files changed since last consolidation.
→ Never injected

04 — Where v1 Fell Short

Three gaps the flat model couldn't bridge.

v1 faithfully translated Ebbinghaus. But biology isn't a filing cabinet — the brain is a network, and the network is the memory.

#

Jaccard tag overlap is coarse

Synonyms never match. Antonyms might. Tag selection is arbitrary — two memories about the same concept with different labels will never consolidate. ADR-011 acknowledged this weakness explicitly.

∩(tags) / ∪(tags) ≥ 0.6

Memories are isolated islands

No links between related entries. "Auth rewrite" (decisions.json) and "session-token format" (episodic.json) live in separate JSON arrays with no pointer between them. Every connection existed only in a developer's head.

episodic.json ⟵╌╌ decisions.json

No hippocampal index

Every recall loaded the full bucket, scored every entry, and sorted. O(n) per query with no structural way to skip irrelevant categories. The brain doesn't scan its entire cortex to recall breakfast.

query → sort(all entries) → top-k
v1 limitations → v2 motivation ADR-011 acknowledged all three

05 — The Graph Insight

Neurons that fire
together, wire together.

Donald Hebb's rule (1949) describes how synaptic strength grows with co-activation — the same mechanism behind long-term potentiation in the hippocampus.

Hebbian co-access

Every co-retrieved pair gets its co_access[otherId] count incremented in frontmatter. Frequently paired memories develop strong associative links.

Spreading activation

A query activates cosine-similar seeds. Activation then propagates outward over related and co_accessed edges, decaying by γ=0.6 per hop.

Synaptic consolidation

Cosine clustering (≥0.82) promotes episodic clusters to semantic memories — the cortical transfer that happens during biological sleep.

Recall graph QUERY embed() k=1 ANN k=2 ANN k=3 ANN cosine [[related]] γ = 0.6 k=12 seeds depth 2 cosine ANN (k=12) BFS spreading activation
Hebbian plasticity Spreading activation Synaptic consolidation

06 — v2 Architecture

One Markdown file per memory. A vault, not a database.

Per-memory .md files with YAML frontmatter and [[id|kind]] wikilinks. The vault is openable in Obsidian — no extra tooling required.

vault directory tree
.valora/memory/
├── version
├── meta.json
├── embeddings.bin          ← packed Float32Array
├── embeddings.index.json   ← id → offset map
├── episodic/
│   └── mem-abc123def456.md
├── semantic/
│   └── mem-xyz789pqr012.md
├── decisions/
│   └── mem-def456ghi789.md
└── .obsidian/               ← Obsidian compat
mem-abc123def456.md
---
id: mem-abc123def456
category: episodic
created_at: "2026-04-28T10:23:00Z"
last_accessed_at: "2026-04-30T09:10:00Z"
agent_role: code-reviewer
confidence: observed
tags: ["typescript", "testing"]
half_life_days: 7
access_count: 3
is_error: false
co_access: {"mem-xyz789pqr012": 2}
---

Found a flaky test in the auth suite — timeout
too short for the CI environment.

[[mem-xyz789pqr012|related]]
One Markdown file per memory linked by [[id|kind]] openable in Obsidian atomic writes · tmp → rename

06 — Recall

Five steps from query to ranked memories.

The vault recall pipeline replaces bucket scans with an embedding-seeded graph traversal — then wires co-retrieved pairs closer together.

STEP 1
Embed
embed(query) via LLMProvider. Ollama with nomic-embed-text. Graceful lexical fallback when no embedder is configured — zero code-path changes.
EmbedderPort.embed()
STEP 2
ANN Seeds
Cosine nearest-neighbour search over embeddings.bin (packed Float32Array). Top k=12 seeds selected as activation starting points.
k = 12 · cosine
STEP 3
BFS Spread
Bidirectional BFS over related and co_accessed edges to depth 2. Activation decays by γ=0.6 per hop times edge weight.
depth 2 · γ = 0.6
STEP 4
Score
Final score: activation × decayStrength × confidenceWeight. Weights: verified=1.0 · observed=0.7 · inferred=0.4 · stale=0.1.
activation × decay × conf
STEP 5
Hebbian
Strengthen each retrieved entry (accessCount++ · halfLife bump). Increment co_access[otherId] in frontmatter for every co-retrieved pair — the synaptic weight update.
co_access[id]++
Zero new runtime deps lexical fallback when no embedder ADR-013 §5

07 — Synaptic Consolidation

Five nightly steps. Cosine replaces Jaccard.

valora consolidate — the hippocampal transfer that happens during biological sleep. Episodic observations cluster into durable semantic patterns.

STEP 1
Prune
Delete entries whose decay strength has fallen below 0.05. Memory that has not been accessed or reinforced fades to nothing — no manual curation needed.
strength < 0.05 → delete
STEP 2
Git-Invalidate
Entries whose related_paths intersect with files changed since the last consolidation are downgraded to stale. Code moves; memories must track it.
relatedPaths ∩ changed → stale
STEP 3
Cosine Cluster
Embed all surviving episodic entries and build clusters where cosine(a,b) ≥ 0.82. Replaces Jaccard tag overlap — Jaccard remains as offline fallback when no embedder is configured.
cosine ≥ 0.82
STEP 4
Promote
Cluster centroid becomes a new semantic/*.md entry with confidence inferred. Each member gets a [[centroidId|decays_from]] wikilink.
semantic/*.md + [[|decays_from]]
STEP 5
Strengthen
Surviving episodic entries that contributed to a cluster get their accessCount incremented and half-life extended — spaced repetition for patterns that proved durable.
accessCount++ · halfLife bump
valora consolidate cosine clustering replaces Jaccard Jaccard retained as offline fallback

08 — Context Injection

Memories reach the
agent via system prompt.

Every pipeline invocation prepends the AGENT MEMORY block — built from vault recall, capped at 2 000 tokens, ordered by confidence tier.

01
Spreading-activation seeds recalled from vault
new in v2
02
Sort: verified → observed → inferred (stale excluded)
03
Apply 2 000-token budget cap
04
Prepend AGENT MEMORY block to system message
buildSystemMessage()
05
Stale entries never surface — silently excluded
AGENT MEMORY block · system prompt
AGENT MEMORY  ·  vault recall  ·  2 000 tok budget
──────────────────────────────────────────────────
[VERIFIED · 0.97] Integration tests must use
Testcontainers; mock-based integration tests
are forbidden. (recalled 8×)

[OBSERVED · 0.72] Auth middleware rewritten for
compliance, not tech debt — session tokens
stored differently. (recalled 3×)

[INFERRED · 0.51] Pattern: no-console lint
suppression is consistently followed by an
eslint-disable comment explanation. (cluster)

09 — Design Decisions

What we kept. What we rejected.

Kept from v1

Decay
Exponential decay — unchanged; storage-agnostic pure function.
Errors
Error amplification (2×) — amygdala effect preserved.
Spacing
Spaced repetition (+2d per access) — unchanged.
Tiers
Confidence tiers (verified / observed / inferred / stale) — unchanged.
I/O
Atomic writes (tmp → rename). Gitignored. Zero runtime deps.

Rejected for v2

Kuzu
Native C++ binding; opaque binary; contradicts zero-dep goal.
sqlite-vec
SQLite extension; contradicts ADR-009 (no SQLite in Valora).
sql.js WASM
WASM overhead; unnecessary when flat-file BFS suffices.
graph.json
Single monolithic file; not Obsidian-compatible; merge conflicts.
Ext. API
External embedding API; contradicts ADR-005 (local LLM provider).
Pure TypeScript per-memory Markdown vault Hebbian + spreading activation ADR-005 · ADR-009 · ADR-013

10 — Results

Ship the vault. Add the graph. The rest is biology.

valora consolidate
$ valora consolidate Pruning entries below 0.05 strength... pruned 3 stale entries Git-invalidating changed paths... downgraded 2 → stale Embedding episodic entries... embedded 47 / 47 Cosine clustering (threshold 0.82)... merged 4 episodic clusters → 4 semantic Strengthening survivors... strengthened 43 episodic entries consolidation complete in 1.8s

Roadmap

Embedding-based similarity

Shipped in v2 — cosine ANN + spreading activation.

Pattern discovery

Cluster analysis to surface implicit conventions — agents discover architecture rules they never explicitly recorded.

valora memory reembed

Migrate embeddings when the model changes without touching memory content.

Kuzu graph export

Read-only analytics export for graph query and visualisation. Vault remains the write authority.

16Slides
33Minutes
v2Vault · shipped