fix: three setup-run bugs — degenerate causal edges, SQLite grooming surface, legacy memory import - #163
Merged
Merged
Conversation
…e unpack crash Symptom: 'ValueError: not enough values to unpack (expected 2, got 1)' at causal_graph.py:166 (a, b = sorted(edge)) during a real setup run on a 23k-entity store. Root cause: entities.name carries no UNIQUE constraint (pg_schema.py / sqlite_schema.py), so cls.py's row-to-name flattening can hand discover_causal_edges the same name twice. pc_skeleton seeds the complete graph with frozenset((a, b)) over combinations(variables, 2); a duplicated name collapses to a degenerate 1-element frozenset that survives skeleton learning (X vs X is never marginally independent) and crashes the 2-tuple unpack in the consumption loop. Duplicates also hand orient_v_structures fake unshielded (X, X, Z) triples. Fix at the source: establish the PC distinct-variables precondition once, at the discover_causal_edges boundary (order-preserving dict.fromkeys), before either pc_skeleton or orient_v_structures sees the list — no guard at the unpack site. pc_skeleton's docstring now states the precondition. Repro test: duplicated name over an exact-independence grid (the config that lets the degenerate edge survive to the unpack) + a regression guard that dedup keeps genuine edges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ite backend Symptom: 'AttributeError: SqliteMemoryStore has no attribute get_grooming_ages' on a real 30k-memory setup run — get_grooming_ages existed only on PgStatsMixin, so on the SQLite backend (the plugin default) memory_stats crashed outright and get_grooming_health crashed twice (ages, then the PG-only jsonb promotion count against the compat connection). Fix: - sqlite_store_grooming.py (new): SqliteGroomingMixin.get_grooming_ages, same contract as the PG twin. distillation/promotion are the honest json_each translation of the jsonb tag queries; wiki is a documented degradation (always None — the tended index, wiki.pages, is PG-only and the SQLite schema has no wiki table; None already means never-recorded to every consumer). - sqlite_store_lesson_promotion.py (new): SQLite twin of the promotion eligibility count (jsonb operators have no mechanical translation in sqlite_compat.py, so the dialect needs its own module). - get_grooming_health.py: composition root now dispatches the promotion count on store type instead of running the PG SQL unconditionally. - sqlite_store.py: wire the mixin; correct the stale 'all 89 methods' docstring claim with measured numbers (117 shared / 18 PG-only, inspect diff 2026-07-22). Call-site status: memory_stats and get_grooming_health now work end-to-end on SQLite; hooks/session_start needed no change (its SQLite banner path deliberately skips grooming staleness, and the PG path already degrades to [] on error). Repro tests: memory_stats handler on an in-memory SqliteMemoryStore (pre-fix AttributeError) + grooming-age/count contract tests + handler dispatch test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es unreadable Symptom: '[methodology-agent] Failed to read <...>/memory/<file>.md: tuple indices must be integers or slices, not str' for every legacy memory .md file during setup/import. Root cause: shared/yaml_parser.parse_yaml_frontmatter returns a FrontmatterResult NamedTuple, but scanner._parse_memory_file indexed it with string keys (parsed["meta"] / parsed["body"] — drift from the Node.js port, where that was object access). Every parse raised TypeError; discover_all_memories' per-file except swallowed it to stderr, so setup runs silently imported zero memory files. The other consumer (wiki_purge) already uses attribute access. Fix at the consumption site to honor the declared NamedTuple contract: parsed.meta / parsed.body. Repro tests: synthetic legacy memory fixture under projects/<p>/memory/ with and without YAML frontmatter — pre-fix both return [] (error swallowed), post-fix the parsed record round-trips name/description/type/body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e_store docstring shift The 03e8ff6 docstring correction added 6 lines above the three known heat_base UPDATE sites in sqlite_store.py; the writers are unchanged, only their coordinates moved (441/471/535 -> 447/477/541). Verified the new lines are the same bump_heat_raw / update_memories_heat_batch statements the ADR-cited allowlist covers. Full invariants suite: 39 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 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.
Three bugs hit by a real setup run on a large store (30k memories / 23k entities), each fixed at root cause with a repro test that failed pre-fix:
1.
causal_graph.pyunpack crash —entities.namehas no UNIQUE constraint, so duplicate names reachpc_skeleton, wherefrozenset((a, b))collapses a duplicated pair to a 1-element edge; X-vs-X is never independent so the degenerate edge survives toa, b = sorted(edge). Fixed at the source:discover_causal_edgesestablishes PC's distinct-variables precondition (order-preserving dedup) before skeleton learning — duplicates were also fabricating unshielded (X, X, Z) triples for collider orientation.2.
SqliteMemoryStoregrooming surface —get_grooming_agesexisted only on the PG stats mixin; on the (now default) SQLite backendmemory_statsandget_grooming_healthcrashed. New SQLite implementations translate the jsonb tag queries viajson_each; wiki age is an honest documented degradation (alwaysNone— SQLite has no wiki table, andNonealready means "never recorded" to every consumer). Thesqlite_store.pydocstring falsely claiming full PG parity is corrected with the measured 18-method gap inventory.3. Legacy memory import silently broken —
scanner._parse_memory_fileindexed theFrontmatterResultNamedTuple as a dict (parsed["meta"]), a Node.js-port drift: everyprojects/*/memory/*.mdparse raised TypeError, swallowed per-file, so setup imported zero legacy memory files while appearing to succeed.Known follow-up (out of scope, inventoried):
lesson_promotionhandler still PG-only on SQLite.Test plan
🤖 Generated with Claude Code