Skip to content

Appendix A: Ebbinghaus-Roynard Bootstrap

A complete, ready-to-use knowledge-layer scoring configuration implementing the Ebbinghaus-Roynard four-layer decomposition.

On database open, if config.Memory.DecayEnabled is true and the target namespace has no existing knowledge-policy schema entries at all, NornicDB seeds that namespace with this built-in bootstrap. It does not overwrite or merge with an existing knowledge-policy schema: if any decay bundles, decay bindings, promotion profiles, or promotion policies already exist, bootstrap is skipped. The built-in bootstrap covers the Ebbinghaus-Roynard defaults excluding the canonical-graph-ledger layer; that layer should be installed separately with a dedicated CGL bootstrap tailored to Roynard's model.

Overview

This bootstrap implements the Ebbinghaus-Roynard model described in "The Missing Knowledge Layer in Cognitive Architectures for AI Agents" (Roynard, 2026; arXiv:2604.11364). The paper identifies a category error in applying uniform cognitive decay to all content types and proposes a four-layer decomposition with distinct persistence semantics:

Layer Content Type Persistence Semantic NornicDB Implementation
Knowledge Facts, claims, entities Supersession, not forgetting :KnowledgeFactdecayEnabled: false; superseded via :SUPERSEDES edges
Memory Experiences, episodes, sessions Ebbinghaus forgetting + consolidation :MemoryEpisode — exponential decay, 7-day half-life, scoreFrom: 'VERSION'
Wisdom Behavioral directives, patterns Evidence-gated revision with stability tiers :WisdomDirectivedecayEnabled: false; stability tiers via promotion
Intelligence The model itself Frozen in weights The LLM — its effects persist only through writes to the other three layers

NornicDB implements the three persistence layers (Knowledge, Memory, Wisdom) as database-native primitives. The Intelligence layer is the model itself — it leaves no trace in storage and its effects persist only through writes to the other three layers. This is a fundamental architectural distinction, not a gap: there is nothing to configure because the model's weights are outside the persistence boundary.

This bootstrap provides the complete DDL to configure all three persistence layers, their edges, promotion tiers, and multi-agent session gating. Replace the label names with your domain's terminology.

Quick Start

For a brand new decay-enabled database, these defaults are already installed automatically. Run the DDL below only when you want to inspect, customize, or reproduce the built-in bootstrap manually via the NornicDB shell or Bolt client.

nornicdb shell --data-dir ./data

Step 1: Decay Profile Bundles

Knowledge layer — no decay

Knowledge facts are permanent. They are superseded by newer evidence through graph operations, not by time-based decay. The Ebbinghaus curve does not apply here.

CREATE DECAY PROFILE knowledge_fact_retention OPTIONS {
  decayEnabled: false,
  visibilityThreshold: 0.0,
  function: 'none',
  scoreFrom: 'CREATED'
}

Memory layer — Ebbinghaus exponential decay (7-day half-life)

Memory episodes decay according to the Ebbinghaus forgetting curve: score(t) = e^(-ln(2) × t / 604800). Episodes that are not accessed or consolidated within this window lose score and eventually become invisible. Suppressed episodes are deindexed but remain accessible through reveal().

CREATE DECAY PROFILE memory_episode_retention OPTIONS {
  halfLifeSeconds: 604800,
  function: 'exponential',
  visibilityThreshold: 0.10,
  scoreFrom: 'VERSION'
}

Memory summary — slower decay for summarized content

Episode summaries retain value even as the raw experience fades. A 14-day half-life lets the curve fade, and scoreFloor set to match visibilityThreshold keeps the summary visible forever once the curve drops to that level — the floor and threshold are independent levers, so this works only because they are equal here. With scoreFloor: 0.0 the summary would disappear once the curve crossed 0.10; with scoreFloor: 0.10 (matching threshold) it plateaus at 0.10, visible but ranked at the bottom.

CREATE DECAY PROFILE session_summary OPTIONS {
  halfLifeSeconds: 1209600,
  function: 'exponential',
  visibilityThreshold: 0.10,
  scoreFloor: 0.10        -- equals threshold  "visible but ranked lowest after fade"
}

Wisdom layer — no time-based decay

Wisdom directives are revised by evidence-gated graph operations, not by time. Stability tiers (managed through promotion policies) gate revision.

CREATE DECAY PROFILE wisdom_directive_retention OPTIONS {
  decayEnabled: false,
  visibilityThreshold: 0.0,
  function: 'none',
  scoreFrom: 'CREATED'
}

Evidence edges — 30-day decay

The relevance of supporting evidence fades, even though the Knowledge fact it supports does not. A 30-day half-life balances recency and retention.

CREATE DECAY PROFILE evidence_decay OPTIONS {
  halfLifeSeconds: 2592000,
  function: 'exponential',
  visibilityThreshold: 0.10,
  scoreFrom: 'CREATED'
}

Step 2: Decay Profile Bindings

Memory Episodes — full Ebbinghaus decay with bi-temporal properties

CREATE DECAY PROFILE memory_episode_retention_binding
FOR (n:MemoryEpisode)
APPLY {
  DECAY PROFILE 'memory_episode_retention'
  DECAY VISIBILITY THRESHOLD 0.10
  n.tenantId NO DECAY
  n.agentId NO DECAY
  n.sessionId NO DECAY
  n.system_created_at NO DECAY
  n.system_expired_at NO DECAY
  n.valid_from NO DECAY
  n.valid_to NO DECAY
  n.summary DECAY HALF LIFE 1209600
  n.summary DECAY FLOOR 0.10
  n.ephemeralContext DECAY HALF LIFE 86400
}

Property-level NO DECAY on a decaying node is meaningful — it marks structural properties that remain fully visible even as the node's content decays. Properties like tenantId, sessionId, and bi-temporal timestamps (system_created_at, valid_from, etc.) are query infrastructure, not decaying content. A NO DECAY property also acts as a suppression anchor: a node with at least one NO DECAY property cannot be fully suppressed because that property remains visible at score 1.0.

Note: property-level NO DECAY is only useful when the parent node does decay. On a decayEnabled: false node (like :KnowledgeFact or :WisdomDirective), property-level NO DECAY is redundant — the entire node is already permanent.

Knowledge Facts — supersession model

CREATE DECAY PROFILE knowledge_fact_retention_binding
FOR (n:KnowledgeFact)
APPLY {
  DECAY PROFILE 'knowledge_fact_retention'
}

The referenced profile has decayEnabled: false — the entire node is permanent. Property-level NO DECAY would be redundant here. Properties are protected by supersession, not by decay anchors.

Wisdom Directives — evidence-gated revision

CREATE DECAY PROFILE wisdom_directive_retention_binding
FOR (n:WisdomDirective)
APPLY {
  DECAY PROFILE 'wisdom_directive_retention'
}

Same as Knowledge: the referenced profile disables decay entirely. Wisdom directives are revised through evidence-gated graph operations (:REVISES edges), not by time.

Evidence edges — 30-day decay with permanent provenance

The evidence edge decays with a 30-day half-life, but r.sourceId is declared NO DECAY — it is a structural property that must remain visible for provenance queries even as the edge's relevance fades.

CREATE DECAY PROFILE evidence_edge_retention_binding
FOR ()-[r:EVIDENCES]-()
APPLY {
  DECAY PROFILE 'evidence_decay'
  DECAY VISIBILITY THRESHOLD 0.10
  r.sourceId NO DECAY
}

Supersession edges — permanent provenance

Every :SUPERSEDES edge is a permanent record of the Knowledge-layer supersession chain. Entity-level NO DECAY makes the entire edge permanent — property-level NO DECAY would be redundant.

CREATE DECAY PROFILE supersession_edge_retention
FOR ()-[r:SUPERSEDES]-()
APPLY {
  NO DECAY
}
CREATE DECAY PROFILE consolidation_edge_retention
FOR ()-[r:CONSOLIDATES_TO]-()
APPLY {
  NO DECAY
}

Revision edges — permanent wisdom provenance

CREATE DECAY PROFILE revision_edge_retention
FOR ()-[r:REVISES]-()
APPLY {
  NO DECAY
}

Derived-from edges — permanent lineage

CREATE DECAY PROFILE derivation_edge_retention
FOR ()-[r:DERIVED_FROM]-()
APPLY {
  NO DECAY
}

Step 3: Promotion Profiles

Note: a promotion profile's scoreFloor is a different field from a decay bundle's scoreFloor. The promotion floor is applied before the cap inside the promoted-curve computation (max(promoFloor, base * multiplier)); the decay floor is the final clamp on the resulting score. Both clamp upward; the promotion floor only matters when the matching WHEN predicate fires. Neither is a visibility cutoff — that role belongs to the decay bundle's visibilityThreshold.

Memory Consolidation Tiers

CREATE PROMOTION PROFILE memory_reinforced OPTIONS {
  multiplier: 1.25,
  scoreFloor: 0.0,
  scoreCap: 1.0
}
CREATE PROMOTION PROFILE consolidation_candidate OPTIONS {
  multiplier: 1.50,
  scoreFloor: 0.80,
  scoreCap: 1.0
}

Wisdom Stability Tiers

CREATE PROMOTION PROFILE wisdom_provisional OPTIONS {
  multiplier: 1.0,
  scoreFloor: 0.0,
  scoreCap: 1.0
}
CREATE PROMOTION PROFILE wisdom_established OPTIONS {
  multiplier: 1.0,
  scoreFloor: 0.50,
  scoreCap: 1.0
}
CREATE PROMOTION PROFILE wisdom_canonical OPTIONS {
  multiplier: 1.0,
  scoreFloor: 0.90,
  scoreCap: 1.0
}

Evidence Traversal Tier

CREATE PROMOTION PROFILE reinforced_evidence OPTIONS {
  multiplier: 1.20,
  scoreFloor: 0.0,
  scoreCap: 1.0
}

Step 4: Promotion Policies

Memory Episode Consolidation

Tracks access patterns across sessions and agents. Uses Kalman filtering to smooth noisy behavioral signals and session gating to prevent within-session sycophancy loops.

Architectural boundary: the promotion policy determines when an entity is a candidate for consolidation (via WHEN predicates and promotion tiers). It does not perform the consolidation itself — creating a :KnowledgeFact node and linking it with a :CONSOLIDATES_TO edge is an application-layer concern. The Heimdall plugin (pkg/heimdall/plugin.go) serves as the reference implementation.

CREATE PROMOTION POLICY memory_episode_consolidation
FOR (n:MemoryEpisode)
APPLY {
  ON ACCESS {
    SET n.accessCount = coalesce(n.accessCount, 0) + 1
    SET n.lastAccessedAt = timestamp()
    SET n.accessIntervals = coalesce(n.accessIntervals, '') + ',' + toString(timestamp())
    WITH KALMAN SET n.crossSessionAccessRate =
      CASE WHEN n._lastSessionId <> $_session
        THEN coalesce(n.crossSessionAccessRate, 0) + 1
        ELSE n.crossSessionAccessRate
      END
    SET n._lastSessionId = $_session
  }

  WHEN n.accessCount >= 3
    APPLY PROFILE 'memory_reinforced'

  WHEN n.accessCount >= 5 AND n.sourceAgreement >= 0.80
    APPLY PROFILE 'consolidation_candidate'
}

Wisdom Directive Stability

Stability tiers gate revision: a provisional directive can be revised by any contradicting evidence; a canonical directive requires overwhelming counter-evidence. evidenceCount, contradictionRate, and crossSessionSupport in the WHEN predicates resolve from accessMeta first, falling back to stored node properties.

CREATE PROMOTION POLICY wisdom_directive_stability
FOR (n:WisdomDirective)
APPLY {
  ON ACCESS {
    SET n.evaluationCount = coalesce(n.evaluationCount, 0) + 1
    SET n.lastEvaluatedAt = timestamp()
  }

  WHEN n.evidenceCount < 3
    APPLY PROFILE 'wisdom_provisional'

  WHEN n.evidenceCount >= 3 AND n.contradictionRate < 0.20
    APPLY PROFILE 'wisdom_established'

  WHEN n.evidenceCount >= 10 AND n.contradictionRate < 0.05 AND n.crossSessionSupport >= 3
    APPLY PROFILE 'wisdom_canonical'
}

Evidence Edge Traversal

More frequently traversed evidence links carry higher weight in retrieval even as their base decay score drops.

CREATE PROMOTION POLICY evidence_traversal_tiering
FOR ()-[r:EVIDENCES]-()
APPLY {
  ON ACCESS {
    SET r.traversalCount = coalesce(r.traversalCount, 0) + 1
    SET r.lastTraversedAt = timestamp()
  }

  WHEN r.traversalCount >= 5
    APPLY PROFILE 'reinforced_evidence'
}

How It Works

Memory Layer — Ebbinghaus Forgetting with Spaced Repetition

A :MemoryEpisode created at time t₀ has a base score of:

score(t) = e^(-ln(2) × (t - t₀) / 604800)
Age Base Score With memory_reinforced (×1.25) With consolidation_candidate (floor 0.80)
0 days 1.000 1.000 1.000
7 days 0.500 0.625 0.800
14 days 0.250 0.313 0.800
21 days 0.125 0.156 0.800
30 days 0.045 0.056 0.800

The consolidation_candidate floor of 0.80 models the spaced-repetition finding: sufficiently rehearsed content resists forgetting indefinitely until consolidated into a :KnowledgeFact.

Knowledge Layer — Canonical Graph Ledger

:KnowledgeFact nodes never decay. When a fact is updated, the canonical graph ledger pattern applies:

  1. Create a new :KnowledgeFact node with the updated assertion
  2. Create a :SUPERSEDES edge from the new node to the old, carrying superseded_at, superseded_by_agent, evidence_source
  3. Move the :CURRENT pointer to the new version

The old fact remains in the graph as an immutable historical record. See Canonical Graph Ledger for the full pattern.

Wisdom Layer — Evidence-Gated Stability

:WisdomDirective nodes never decay. They progress through stability tiers based on accumulated evidence:

Tier Trigger Score Floor Revision Requirement
Provisional evidenceCount < 3 0.0 Any contradicting evidence
Established evidenceCount >= 3, contradictionRate < 0.20 0.50 Multiple independent sources
Canonical evidenceCount >= 10, contradictionRate < 0.05, crossSessionSupport >= 3 0.90 Overwhelming counter-evidence; flagged for human review

When evidence gates are met for revision, the operation creates a new :WisdomDirective (status: active), sets the old one to status: 'retired', and links them via a :REVISES edge with provenance.

Edge Decay

Edge Type Behavior Purpose
:EVIDENCES 30-day half-life, reinforced at 5+ traversals Supporting evidence fades; the fact it supports does not
:SUPERSEDES No decay Permanent provenance: old facts linked to successors
:CONSOLIDATES_TO No decay Permanent link from consolidated episode to fact
:REVISES No decay Permanent wisdom revision history
:DERIVED_FROM No decay Permanent lineage

Multi-Agent Session Gating

The $_session and $_agent variables are passed from HTTP headers and prevent within-session access inflation:

Header Variable Purpose
X-Query-Session $_session Same-session deduplication
X-Query-Agent $_agent Per-agent tracking
X-Query-Tenant $_tenant Multi-tenant isolation

The WITH KALMAN SET n.crossSessionAccessRate = CASE WHEN ... pattern gates the counter so that 50 accesses from one session count as one observation. The Kalman filter then smooths genuine cross-session signals.


Verification

After running all DDL statements:

SHOW DECAY PROFILES

Expected: 5 bundles and 8 bindings.

SHOW PROMOTION POLICIES

Expected: 6 promotion profiles and 3 promotion policies.

CLI Maintenance

nornicdb decay recalculate --data-dir ./data
nornicdb suppress --data-dir ./data --threshold 0.10
nornicdb decay stats --data-dir ./data

Customization

  1. Replace node labels: :MemoryEpisode → your ephemeral data label, :KnowledgeFact → your persistent fact label, :WisdomDirective → your policy/rule label
  2. Adjust half-lives: 604800 (7 days) works for conversational memory; adjust for your domain's natural forgetting rate
  3. Tune promotion thresholds: accessCount >= 3 and sourceAgreement >= 0.80 are starting points; tune based on your multi-agent setup
  4. Add property rules: Extend APPLY blocks with domain-specific properties that need NO DECAY or custom half-lives

Optional: Inverted Memory Layer (Consolidation)

The default Memory profile above implements the classic Ebbinghaus forgetting curve — episodes start strong and fade unless reinforced. Roynard's full model also covers the inverse dynamic: a memory that is not retrieved gains strength as it consolidates, while frequent retrieval (interference) erodes it. This is supported as a first-class profile by giving halfLifeSeconds a negative value and anchoring on LAST_ACCESSED. The scoreFloor choice matters more on the inverted curve than on the forward one because the inverted curve evaluates to exactly 0.0 immediately after access:

CREATE DECAY PROFILE memory_consolidation OPTIONS {
  halfLifeSeconds: -86400,        -- negative  invert the curve
  function: 'exponential',
  scoreFrom: 'LAST_ACCESSED',     -- age = time since last retrieval
  visibilityThreshold: 0.10,
  scoreFloor: 0.10                -- equals threshold  visible right after access
}

A node bound to this profile scores 0.10 (the floor) immediately after access — visible because suppression uses a strict < check — and strengthens monotonically toward 1.0 as it sits idle. The next access drops the score back to 0.10, never below.

Setting scoreFloor: 0.0 here would produce a different behavior: every access drops the score to strict zero, the entity is suppressed and deindexed instantly, and stays hidden until ~3.3 hours of idle time pass for the curve to climb back over the threshold. Choose 0.0 only when you explicitly want a "cooldown" memory that disappears between retrievals.

Pair the consolidation profile with a dampening promotion profile to model interference:

CREATE PROMOTION PROFILE retrieval_interference OPTIONS {
  multiplier: 0.5,
  scoreFloor: 0.0,
  scoreCap: 1.0
}

CREATE PROMOTION POLICY consolidation_interference
FOR (n:MemoryEpisode)
APPLY {
  ON ACCESS {
    SET n.accessCount = coalesce(n.accessCount, 0) + 1
    SET n.lastAccessedAt = timestamp()
  }
  WHEN n.accessCount >= 5
    APPLY PROFILE 'retrieval_interference'
}

Net behavior — opposite of the default forgetting layer:

  • Idle episodes consolidate (score rises toward 1.0).
  • Each retrieval resets the consolidation clock and bumps accessCount.
  • Once accessCount crosses the interference threshold, the dampener pins the multiplier at 0.5 and frequent retrievals never reach the asymptote.

Use this layer when modeling skills/procedures (which strengthen with rest) instead of episodic recall (which strengthens with rehearsal). The two layers can coexist on different label sets within the same database.