Skip to main content

Decay Mechanics

Problem

Without decay, knowledge stores grow monotonically. Team standup notes from January sit alongside today's context. A machine proposal that was never reviewed but never rejected lingers with the same visibility as fresh insights. Over time:

  • Search quality degrades. Stale L2 context pollutes queries. A team's working memory from three sprints ago is noise.
  • Review fatigue. Governance reviewers face an ever-growing backlog of L3 proposals, many of which are outdated by the time they are reviewed.
  • Storage grows unbounded. Ephemeral context was never meant to live forever.

Deletion is not the answer — decayed entries may be referenced by evidence chains in L3 proposals or L4 canon. Destroying them breaks audit trails.

Solution

SOMA implements time-based decay with activity extension. Entries in L2 and L3 have a decay_at timestamp. When that timestamp passes, the Decay Processor moves the entry to L1 (the Execution Archive) with a decayed_from marker. The entry is not deleted — it sinks to the historical layer where it remains queryable but no longer surfaces in active searches.

Key design decisions:

  • Decay to L1, not deletion. Evidence chains remain intact.
  • Activity-based extension. Reading an entry resets its decay timer, keeping actively-referenced knowledge alive.
  • Promoted/rejected entries are exempt. Once a governance decision is made, the entry is permanent regardless of layer.
  • Reference updates on decay. Evidence links in L3/L4 entries are rewritten to point to the new L1 location.

Architecture

Code Examples

Creating the Decay Processor

import { createDecayProcessor } from './decay';
import { Vault } from './vault';

const vault = new Vault({ dir: '.soma/vault' });

const decayProcessor = createDecayProcessor(vault, {
l2DefaultDays: 14, // Team working memory TTL
l3DefaultDays: 90, // Emerging knowledge TTL
teamDecayDays: { // Per-team overrides
'backend': 7, // Backend team: faster decay
'security': 30, // Security team: slower decay
},
});

Processing decay

// Run the decay processor — checks all L2 and L3 entries
const result = await decayProcessor.processDecay();

// Example output:
// {
// decayed: [
// {
// id: 'standup-backend-20260307',
// from_layer: 'working',
// to_layer: 'archive',
// new_id: 'decayed-standup-backend-20260307',
// decayed_from: 'working',
// references_updated: 0
// },
// {
// id: 'proposal-old-pattern',
// from_layer: 'emerging',
// to_layer: 'archive',
// new_id: 'decayed-proposal-old-pattern',
// decayed_from: 'emerging',
// references_updated: 2 // Two L4 entries had evidence_links updated
// }
// ],
// skipped_promoted: 3,
// skipped_rejected: 1,
// total_checked: 47
// }

Activity-based extension

// When an entity is accessed, its decay timer resets
import { extendDecayOnAccess } from './decay';

// This happens automatically during queryByLayer and vault.get()
// But can also be called explicitly:
await extendDecayOnAccess(vault, 'proposal-retry-limit');

// The entry's decay_at is reset to now + configured TTL
// So an L3 entry accessed today won't decay for another 90 days

What a decayed entry looks like

---
type: insight
id: decayed-standup-backend-20260307
name: "Backend team standup notes (decayed)"
status: active
layer: archive
source_worker: decay
decayed_from: working
original_id: standup-backend-20260307
original_decay_at: "2026-03-21T00:00:00.000Z"
team_id: backend
tags: ["team-context", "decayed"]
created: "2026-03-07T10:00:00.000Z"
updated: "2026-03-21T00:05:00.000Z"
---

## Backend team standup notes (decayed)

Original team working memory entry, decayed to archive on 2026-03-21.

Reference update on decay

// Before decay: L4 canon entry references an L3 proposal
// {
// id: 'canon-retry-limit',
// evidence_links: ['exec-invoice-agent-001', 'proposal-old-pattern', 'exec-email-router-042']
// }

// After 'proposal-old-pattern' decays to L1:
// {
// id: 'canon-retry-limit',
// evidence_links: ['exec-invoice-agent-001', 'decayed-proposal-old-pattern', 'exec-email-router-042']
// }
// The link is updated, not broken.

CLI commands

# Show entries approaching expiry
soma decay status

# Example output:
# L2 Working Memory:
# standup-backend-20260318 expires in 3 days team: backend
# standup-security-20260310 expires in 1 day team: security
#
# L3 Emerging:
# proposal-payload-limit expires in 12 days
# proposal-logging-pattern expires in 67 days
#
# Exempt (promoted/rejected): 4 entries

Rules and Invariants

Decay rules by layer

LayerDecays?Default TTLConfigurable?Decay Target
L1 ArchiveNever---
L2 Working MemoryYes14 daysPer-team via teamDecayDaysL1
L3 Emerging (active)Yes90 daysGlobally via l3DefaultDaysL1
L3 Emerging (promoted)Never---
L3 Emerging (rejected)Never---
L4 CanonNever---

Decay exemptions

ConditionExempt?Reason
L3 entry with status: promotedYesGovernance decision is permanent
L3 entry with status: rejectedYesGovernance decision is permanent
L1 entry (any status)YesArchive is permanent by definition
L4 entry (any status)YesCanon is permanent by definition
L2/L3 entry accessed within TTLExtendedActivity resets the decay timer

Reference integrity on decay

When an entry decays, the processor performs these steps in order:

  1. Create a new L1 entity with decayed-{originalId} as the ID and decayed_from metadata.
  2. Scan all L3 entries for evidence_links containing the old ID. Update them to the new ID.
  3. Scan all L4 entries for evidence_links containing the old ID. Update them to the new ID.
  4. Mark the original entry as decayed (it is replaced by the L1 copy).

The checkDanglingReferences() function can be run independently to audit all evidence links across the vault and report any that point to non-existent entities.

Invariants

  1. L1 and L4 entries never have decay_at. The schema rejects it.
  2. Every L2 entry has both team_id and decay_at. Missing either fails validation.
  3. Decayed entries always land in L1. There is no decay path to L2, L3, or L4.
  4. Evidence references are updated atomically with decay. No operation leaves dangling links.
  5. Reading extends decay. Any access to an entry resets its decay_at to now + TTL.
  6. Promoted/rejected L3 entries never decay. The governance decision overrides the timer.
  7. Decay is non-destructive. The original content is preserved in L1 with full provenance metadata.