Add smem --redundancy-check (tree inclusion)#579
Draft
kimjune01 wants to merge 8 commits intoSoarGroup:developmentfrom
Draft
Add smem --redundancy-check (tree inclusion)#579kimjune01 wants to merge 8 commits intoSoarGroup:developmentfrom
kimjune01 wants to merge 8 commits intoSoarGroup:developmentfrom
Conversation
Periodically scan episodic memory for stable WME structures and write
them to semantic memory as new LTIs. This implements the compose+test
framework (Casteigts et al., 2019) for automatic episodic-to-semantic
knowledge transfer — the operation Soar's long-term declarative stores
have been missing.
Algorithm:
compose — union of constant WMEs currently active in epmem
test — continuous presence >= consolidate-threshold episodes
write — create smem LTI with qualifying augmentations via CLI_add
New parameters (all under epmem):
consolidate on/off (default off)
consolidate-interval integer (default 100) — episodes between runs
consolidate-threshold integer (default 10) — min episode persistence
Deduplication via epmem_consolidated tracking table prevents repeated
writes across consolidation runs. Table is dropped on reinit alongside
other epmem graph tables.
Off by default — zero behavior change until explicitly enabled.
Limitations (deferred to follow-up):
- Only consolidates constant-valued WMEs, not identifier edges
- No back-invalidation across the WM/smem tier boundary
- last-consolidation stat does not persist across agent reinit
Motivation: Derbinsky & Laird (2013) proved forgetting is essential to
Soar's scaling but only built it for working and procedural memory.
Episodic and semantic memory have no eviction and no capacity bound.
This patch addresses the first half: automatic semantic learning from
episodic experience. With semantic entries derived from episodes,
episodic eviction becomes safe (merged episodes leave no reconstruction
debt), and R4's forgettable WME scope expands automatically.
Reference:
Casteigts et al. (2019), "Computing Parameters of Sequence-Based
Dynamic Graphs," Theory of Computing Systems.
Derbinsky & Laird (2013), "Effective and efficient forgetting of
learned knowledge in Soar's working and procedural memories,"
Cognitive Systems Research.
https://june.kim/prescription-soar — full prescription
After consolidation writes stable WMEs to smem, old episodes become redundant. Delete point entries and episode rows older than consolidate-evict-age episodes. This is safe: the consolidated knowledge is in smem, so there is no reconstruction debt. New parameter: consolidate-evict-age integer (default 0 = off) — min age before an episode is eligible for eviction Range and _now interval entries are preserved (they span multiple episodes). Only point entries and episode rows are removed. Reference: Derbinsky & Laird (2013), §5 — "forgotten working-memory knowledge may be recovered via deliberate reconstruction from semantic memory." Consolidation creates the semantic entries; eviction removes the source episodes that are no longer needed for reconstruction.
- Delete _range entries whose intervals end before the eviction cutoff (previously only _point entries were evicted, leaving dead weight) - Wrap all eviction DELETEs in BEGIN/COMMIT when lazy_commit is off for atomicity (when lazy_commit is on, already inside a transaction) Retrieval of evicted episodes is already safe: epmem_install_memory checks valid_episode and returns ^retrieved no-memory.
Implements Kilpeläinen-Mannila tree inclusion to detect which LTI entries are structurally dominated by others. Detection only — no eviction. Works at the raw hash level via web_expand to avoid Symbol allocation overhead.
…rror routing Codex review found four issues: 1. Greedy child matching could produce false negatives when first-fit blocks later required matches. Replaced with backtracking injective matcher. 2. Cycle detection conflated "currently exploring" with "proven to include". Split into separate active_pairs (recursion stack) and memo (proven results). Cycles now conservatively return false. 3. CLI_redundancy_check returned void, DoSMem always returned true. Changed to bool with SetError routing on failure. 4. help smem did not list --redundancy-check. Added.
Round 2 codex review found two remaining issues: 1. Per-attribute a_used vectors allowed two distinct B nodes to map to the same A node via different attributes. Fixed by threading a global b_to_a map (B node → A node assignment) through all recursion. Backtracking undoes assignments on failure. 2. Active-pair cycle check returned false (pessimistic), which missed valid cyclic equivalences like @1 ^next @1 vs @2 ^next @2. Changed to coinductive (optimistic): revisiting an active pair returns true. If the assumption is wrong, non-cyclic proof obligations will fail.
Round 3 codex review found two issues: 1. Failed speculative branches leaked descendant b_to_a bindings. Fixed by snapshotting b_to_a before each branch and restoring on failure. Also pre-bind b_child before recursion so descendants see the intended assignment. 2. Memoization keyed by (lti_a, lti_b) was unsound because results depend on the current b_to_a context. Dropped memo entirely — smem entries are shallow (depth 1-2) so re-evaluation is cheap.
Round 4 codex review found two issues: 1. Root of B was not pinned to root of A in the global assignment. Counterexample: @b ^next @b vs @A ^next @A1, @A1 ^next @A1 — B's root could map to A1 instead of A. Fixed by seeding b_to_a[lti_b] = lti_a before recursion. 2. smem --redundancy-check was missing from the runtime help screen in smem_settings.cpp. Added.
kimjune01
added a commit
to kimjune01/Soar
that referenced
this pull request
Mar 27, 2026
…ated LTIs Mark phase reuses tree inclusion detection from SoarGroup#579. Sweep phase: 1. R4 safety check: skip LTIs currently referenced in working memory 2. Dependency-safe deletion: disconnect_ltm, then delete from all smem tables (augmentations, activation history, aliases, lti) 3. Per-invocation budget via optional numeric argument Includes functional test proving full eviction: add 3 LTIs where @1 is structurally dominated by @2, sweep, verify @1 is gone and @2/@3 survive. Post-sweep redundancy check confirms no remaining dominated entries.
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Semantic memory grows without bound. Soar has no mechanism to detect when one smem entry's knowledge is fully contained in another's. After episodic-to-semantic consolidation (#578), merged entries may structurally subsume older entries, but those older entries persist indefinitely — actively retrieved, never evicted, slowing every subsequent spreading activation pass.
Derbinsky & Laird (2013) showed that without forgetting, a robot exceeded the 50ms decision-cycle threshold within an hour. They built forgetting for working memory and procedural memory. Smem and epmem still have none. This PR addresses the detection half of smem maintenance.
Full analysis: Diagnosis: Soar, SOAP Notes: Soar, Prescription: Soar.
What this PR does
Adds
smem --redundancy-check: a CLI command that scans semantic memory for structurally dominated entries via tree inclusion. Detection only — no eviction.LTI A dominates LTI B if B's augmentation graph embeds injectively into A's — every slot in B has a matching slot in A with a superset of values, child LTIs matched recursively.
Expected consequence
After consolidation (#578) merges N episodes into a semantic entry, this check identifies pre-existing smem entries that the merged result now subsumes. Without it, consolidation adds entries but never removes redundant ones — net store growth is monotonically positive. With it, the eviction path (future PR) can remove dominated entries, making net growth per consolidation cycle potentially negative. Over a 30-day deployment at 72,000 episodes/hour, this is the difference between smem growing without bound and smem staying bounded.
Dependencies
The three form a pipeline: consolidation creates entries → tree inclusion detects redundancy → eviction removes dominated entries. Each is independently useful but the full payoff requires all three.
Theory
Tree inclusion (Kilpeläinen & Mannila, 1995) defines structural containment on rooted labeled trees. This PR adapts it to smem's graph structures with three extensions:
Coinductive cycle handling. Smem graphs may contain cycles. We use coinductive semantics: revisiting an active pair optimistically assumes inclusion; if the assumption is wrong, non-cyclic proof obligations fail and reject it.
Global injective matching. A
b_to_amap threaded through all recursion enforces that two distinct B nodes cannot map to the same A node, even via different attributes. Root pinning ensures rooted inclusion.Backtracking child matching. Full backtracking over candidate assignments per attribute, with transactional
b_to_asnapshot/restore on failed branches.BLA (base-level activation) handles staleness — entries that haven't been accessed decay. Tree inclusion handles redundancy — entries whose knowledge is fully contained in a richer entry. They are orthogonal. See Prescription: Soar § Structural redundancy via tree inclusion.
This fills the "Tree × Dominance" cell in the parts bin taxonomy — structural dominance filtering on tree-shaped data. Background on the taxonomy and the blank cell: The Missing Parts.
Implementation
smem_inclusion.cpp(~400 lines)load_lti_augs()— loads augmentations viaweb_expandinto lightweight structs keyed by hash IDs (no Symbol allocation)smem_lti_includes_impl()— recursive inclusion with coinductive cycles, global injectivity, backtrackingCLI_redundancy_check()— iterates all LTI pairs, reports dominated entries and equivalence classessmem --redundancy-check(option'R', no arguments)web_expandandlti_allinfrastructureMethodology
Implementation by Claude Code (Opus 4.6) in an isolated git worktree. Reviewed through five rounds of adversarial volley with codex (GPT-5.4). Volley methodology: june.kim/volley.
References
Test plan
@1 (^name alice ^age 30)dominated by@2 (^name alice ^age 30 ^city boston)@1 ^next @1vs@2 ^next @2)