Skip to content
GitHub
Decisions

ADR-058: World Agent memory storage and cross-agent memory primitives

Status: Accepted (2026-04-25) Supersedes: ADR-018 on the tier vocabulary for World Agent memory (the three-tier framing for Spectral Agent stands; ADR-018’s “world signal path” framing stands)

Context

The World Agent is the internal explorer/proposer resident in each world model (per Codex system-design/world-model-system/world-agent.mdx). It is not customer-facing; it reasons over the canonical rule corpus, surfaces coverage gaps, proposes rule candidates, and answers operator questions via the Operations Agent’s ask_world_agent tool.

This ADR lands the World Agent’s memory storage. During disposition the scope expanded upward — the three Spectral agents had memory specs that diverged in incompatible ways (Spectral Agent’s three-tier model from ADR-018; World Agent’s draft 4-tier with version-spanning persistent; Ops Agent’s draft three-tier from SPEC-268 S11 with operator/operator-session/operator-persistent vocabulary). The divergence was structural noise, not principled difference. Disposition produced a new Codex page (agent-memory-primitives.mdx) defining the cross-cutting axes any agent memory must answer; World Agent memory then parameterizes the primitives.

The primitives doc fixes vocabulary all three agents share:

  • Universal interaction / session / persistent lifecycle — tiers carry durability, not domain. Cycle/run/conversation are agent-domain event scopes within sessions, not tiers.
  • Memory typology (episodic / semantic / procedural) — orthogonal to tier; carries behavioral defaults (lifecycle, decay, write policy, promotion, conflict resolution, consolidation).
  • Joint tier + typology discriminator — single table per agent with both columns; partial indexes per (tier, typology).
  • Reference-only invariant — agent memory carries reasoning + references; never canonical state.
  • Append-only audit posture — universal default; no hard DELETE.

This ADR captures the World Agent parameterization plus the primitives that future agents (TA-13 Ops Agent in ADR-059, future agents) inherit from.

Decision

D1 — Universal three-tier memory lifecycle: interaction / session / persistent

Tiers are universal across all Spectral agents; per-agent parameterization is the anchor entity, the session-end rules, and which domain events accumulate observations during sessions. Cycle/run/conversation are agent-domain event scopes, not tiers. This supersedes the prior 4-tier Codex draft for World Agent (cycle/run/workspace=version-scoped/version-spanning) and the Operator-session/Operator-persistent vocabulary in SPEC-268 S11. ADR-018’s three-tier framing for Spectral Agent stands; only the World Agent draft is replaced.

D2 — Anchor entity for World Agent

  • Persistent: world_id (durable root across world model versions per the rule logical-identity model)
  • Session: agent_session_id (UUID)
  • Interaction: agent_interaction_id (NULL at session/persistent tiers)

D3 — Single table per agent with joint tier + typology discriminators

tier enum(interaction, session, persistent) carries lifecycle; typology enum(episodic, semantic, procedural) carries behavioral defaults. Pattern applies across all Spectral agents (TA-13 Ops Agent inherits in ADR-059). Partial HNSW indexes per (tier, typology) keep per-class retrieval lean.

D4 — Memory typology distribution

  • Transient tiers (interaction / session): episodic
  • Persistent tier: semantic + procedural about the world’s domain
  • Persistent-episodic is not produced by current agent design (no use case). Schema retains episodic at the persistent tier so the cell exists; doctrine + repository wrapper enforce the distribution, no CHECK constraint.

D5 — world_version as contextual provenance, not version pin

A nullable column on memory rows captures “the world model version active when the agent recorded this.” Useful for provenance review (“what world model state was the agent reasoning against when it said this?”). Not a version pin — rules survive across world model versions per the worlds.rules logical-identity model.

D6 — Rule references via junction table

worlds.world_agent_memory_rule_refs (ref_id, memory_id, rule_id, world_version, created_at) with real FKs and ON DELETE RESTRICT. No uuid[] arrays; no inline FK on the memory row. Standard relational answer; aligns with CQRS read-model discipline.

D7 — Contradictions as a first-class concept

worlds.world_agent_rule_contradictions (contradiction_id, memory_id, rule_id, world_version, observed_at, status, resolution_notes) with status enum(open, dismissed, surfaced_to_operator, promoted_to_revision_signal). Separate from rule references because contradictions are not just provenance — they are rule-revision signals that flow to operator surfaces and connect to the TA-9 failure-cluster pipeline as the agent matures. ref_role is not added to the rule-refs junction; classification roles like “derived_from” are structurally hollow.

D8 — body_text rule-shadow defense via trigram similarity trigger

BEFORE INSERT OR UPDATE OF body_text trigger on worlds.world_agent_memory:

  • Skip if length(body_text) < 100
  • pg_trgm similarity check against worlds.rules.body_text scoped by world_id
  • Reject if max similarity > 0.85 (errcode 23514)

A world-scoped GIN/GIST trigram index on worlds.rules.body_text supports the lookup. Threshold and floor are contracts, not config. Calibration trigger: first false-positive blocking legitimate reasoning OR first observed shadow that slips through. Repository wrapper handles rejection as a clean error with structlog + Sentry surface.

Justification: shadow content in injected memory creates context drift on rule changes (especially reversals); harm is asymmetric and hard to detect downstream. For the World Agent the trigger is the primary defense (the agent reasons OVER rules to produce semantic claims); for the Ops Agent (ADR-059) the same trigger is a backstop with workshop discipline as primary defense — per-agent leak-defense calibration is itself a primitive.

D9 — Corroboration via junction table

worlds.world_agent_memory_corroborations (corroboration_id, memory_id, source_type, source_ref, corroborated_at, notes) with source_type enum(observation_event, rule_change, cross_version_carry, operator_input, agent_inference). source_ref is a uuid pointer (per-source-type target table documented in code). No denormalized last_corroborated_at on the memory row; derive via MAX(corroborated_at) from the junction. Tracks source, not just timestamp — provenance review is bidirectional.

D10 — Append-only audit posture

Per the primitives universal default. No hard DELETE on memory rows. Soft-supersession via archived_at, superseded_at, superseded_by state columns. Reasoning chains preserved across all persistent memory tiers.

Refinement (per ADR-059 D9): “no hard DELETE” applies inherently to persistent-tier memory and to any-tier memory load-bearing for audit (action-linkage). Transient-tier memory without action-linkage can be removed when its scope ends. Action-linkage examples: rule promotion, change-set acceptance, published decision.

D11 — Embedding via TA-11 D9 retrievable-table convention

worlds.world_agent_memory_embeddings is a separate retrievable table joined via embedding_id. BGE-small-en-v1.5 per TA-11 D2. embedding_id is NULLABLE on the memory row — interaction-tier rows may live and die without indexing.

D12 — Retention via TA-4 RetentionPolicy registration

Persistent tier registers a RetentionPolicy with typology-driven defaults from primitives (semantic = validity-window decay; procedural = decay-exempt). Domain owner = worlds.

Transient tiers (interaction, session) inherit lifecycle from their enclosing scope per ADR-059 D9 — they do not register a literal TTL with TA-4. The earlier framing (“transient tiers don’t register — ephemeral by construction”) is replaced by the durability-vs-scope-inheritance framing in the primitives doc; this ADR carries the corrected framing.

D13 — No inter-context role grants for worlds.world_agent_memory

Operator access flows through ask_world_agent per SPEC-267 S10. The mechanism is in-process DI through the workers-entrypoint composition seam (per ADR-060 / ADR-063), with WorldAgentRunner as the protocol surface. There is no worlds_world_agent_memory_reader SQL role.

D14 — Reference-only invariant for World Agent memory

Memory carries reasoning + references; never canonical state. Non-mirror list:

  • World-model rule content (enforced by D8 trigger)
  • Spectral memory at any tier
  • Canonical scan artifacts

Schema implementation (D6, D7, D8) realizes the invariant.

D15 — Schema implementation deferred to consumer epic

Per the precedent set by ADR-043 (TA-14): application schemas land in the implementation epic that consumes them, not in the disposition ADR. This ADR’s contract is the doctrine (D1–D14) plus the primitives Codex page. The implementation epic lands the migration creating the five tables (memory + rule_refs + contradictions + corroborations + embeddings retrievable), the trigram trigger + index, the repository gateway, retention policy registration entries, and per-(tier, typology) partial HNSW indexes.

Alternatives considered

4-tier (Codex draft) — version-anchored persistent + version-spanning tier. Rejected. Both tiers carry the same typology mix (semantic + procedural) with different anchors; one persistent table with world_version NULLABLE and view-based cross-version semantics is cleaner. Adversarial research confirmed the dumping-ground risk for an under-justified version-spanning tier.

Per-tier tables. Rejected per cross-agent pattern. Tiers differ in lifecycle, not column shape. Partial indexes cover the performance case.

uuid[] array for rule refs. Rejected. RI loss; no junction-time metadata; pgvector and array FKs do not compose cleanly.

ref_role enum on rule_refs (referenced/contradicts/derives_from). Rejected. derives_from is structurally hollow; contradicts deserves its own first-class table with lifecycle (D7); referenced is implicit.

Pure doctrine for body_text leak defense. Rejected per the asymmetric-harm argument: shadow content in injected memory creates context drift on rule changes, especially reversals; the harm is asymmetric and hard to detect downstream. Trigger cost is acceptable; doctrine alone is not.

CHECK enforcement of the (persistent, episodic) invariant. Rejected. Doctrine + repository wrapper sufficient; lift to enforcement on observed violation per TA-14 / TA-2 pattern.

Inter-context SQL grant for operator read. Rejected per SPEC-267 S10 + framework-layer composition discipline. ADR-063 is the canonical statement.

Persistent-episodic as a third typology cell with forcing-function decay. Designed-for in schema (typology enum includes episodic at persistent-tier), not produced by current agent design. Forward-compatible without committing.

Consequences

  • Universal agent-memory lifecycle established. TA-13 Ops Agent (ADR-059) inherits the pattern; future agents follow.
  • Codex primitives doc gives every agent’s memory a parameterization template — explicit choices, justified deviations. Page landed at commit dabfa49.
  • ADR-018 partially superseded on the tier vocabulary for World Agent. The three-tier framing for Spectral Agent in ADR-018 stands; the world signal path framing stands; only the per-agent tier draft for World Agent is replaced by the universal lifecycle.
  • TA-13 (ADR-059) inherits the schema shape with parameterization for operator_id anchor, procedural-dominant persistent tier, no contradictions/corroboration tables, scope-inheritance retention.
  • Codex page rewrites needed at close-pass:
    • system-design/world-model-system/world-agent.mdx — retire 4-tier framing for universal interaction/session/persistent; semantic + procedural at persistent; reference-only invariant precision; runtime placement (workers); WorldAgentRunner two modes.
    • system-design/agents/memory-system.mdx — cycle/run reframed as scan-event scoping; tier names → universal lifecycle.
    • system-design/operations/operations-agent.mdx — tier-name standardization (Conversation → Interaction; Operator-session → Session; Operator-persistent → Persistent).
    • system-design/agents/agent-memory-primitives.mdx — D12 edits per ADR-059 D12 (Primitive 6 lifecycle inheritance; audit posture action-linkage scope; per-agent leak-defense calibration sub-section).
  • TA-27 (SPEC-331) lifted as deserving-its-own-treatment during disposition rather than buried in close-pass. Resolved canonically in ADR-063.
  • Persistent-episodic is design-for-future without current use case. Acceptable; schema cost is one enum value.
  • Trigram trigger has per-write cost. Acceptable at alpha; revisit if write rate climbs.
  • worlds.rules schema (lands in SPEC-238) must include body_text for D8 trigger to function — documented in close-pass for SPEC-238 AC.

References

  • ADR-018 — partially superseded by this ADR on the World Agent tier vocabulary
  • ADR-065spectral.core admission discipline
  • ADR-031 — single-library + worlds context location
  • ADR-059 — TA-13 Ops Agent memory (inherits this pattern)
  • ADR-060 — TA-15 (D6 WorldAgentRunner inter-context composition reference)
  • ADR-063 — inter-context access pattern (D13 alignment)
  • TA-12 disposition — SPEC-315 comment 8592f0ef
  • TA-12 verification — SPEC-315 comment b97db299
  • Codex system-design/agents/agent-memory-primitives.mdx (commit dabfa49) — primitives source-of-truth
  • SPEC-238 — WorldAgent epic (schema + trigger + gateway + repository)
  • SPEC-242 — Spectral Agent / memory system rebuild (T3 schema target named via TA-12 bookkeeping)