Semantic memories — user-stated facts and preferences#34
Conversation
When working in a git worktree under .worktrees/, the main worktree's pyright was scanning the worktree's source files but resolving imports against main's modules — producing false-positive "unknown attribute" errors for symbols on the worktree's branch that don't yet exist on main. Adds **/.worktrees/** and **/__pycache__ to pyright exclude so each worktree's own 'uv run pyright' is the canonical type-check for that branch. Also confirms the IDE's pyright respects the project config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec: docs/superpowers/specs/2026-05-04-semantic-memories-design.md Plan: docs/superpowers/plans/2026-05-04-semantic-memories.md Brainstormed and approved 2026-05-04. Three commits follow: - migration 0008 (semantic_memories table) - SemanticMemoryService - 4 MCP tools (memory.semantic_observe / _retrieve / _update / _delete) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-stated facts/preferences. Distinct from observations (episodic, recorded as work happens) and reflections (LLM-distilled). Same scope model as PR #25's reflections — 'project' rows are per-project; 'general' rows surface in every project's retrieval. Schema: - semantic_memories(id, content, project, scope DEFAULT 'project' CHECK IN ('project','general'), created_at, updated_at) - idx_semantic_memories_project for the per-project read path - partial idx_semantic_memories_general WHERE scope='general' Service + MCP tools follow in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Service for managing semantic memories — user-asserted current truths, distinct from observations (episodic) and reflections (LLM-distilled). API: - create(*, content, project, scope='project') -> id - update_text(*, id, content) — bumps updated_at; raises if id absent - delete(*, id) — idempotent (no error on missing id) - list_for_project(*, project) -> list[SemanticMemory] Returns project rows + general-scope rows from any project, ordered newest-first by created_at. Validation: - scope must be 'project' or 'general' (ValueError before DB hit; CHECK constraint is the backstop) - content must not be empty/whitespace (ValueError) MCP wiring follows in next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire SemanticMemoryService into the MCP server with four new tools:
- memory.semantic_observe(content, scope='project') -> {id}
Records a user-stated fact/preference. scope='general' surfaces it
in every project's startup retrieval.
- memory.semantic_retrieve(project?) -> [memories...]
Returns project rows + general-scope rows from any project,
ordered newest-first. Flat list — they're facts, not lessons.
- memory.semantic_update(id, content) -> {ok}
Edits content in place; bumps updated_at. Raises if id absent.
- memory.semantic_delete(id) -> {ok}
Idempotent — no error if id absent.
Both write tools accepting scope use `args.get("scope") or "project"`
to defend against {"scope": null} from MCP clients (PR #25 BugBot
finding).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔴 Claude BugBot Analysis
Found 1 potential bug in this PR.
medium: 1
One medium-severity bug: update_text raises ValueError after executing a DML statement without rolling back the implicitly-opened write transaction, leaving the WAL write lock held until the next commit on the shared connection — inconsistent with the established pattern in observation.py:435.
BugBot finding (medium): SemanticMemoryService.update_text raised ValueError after the UPDATE found no rows, without first calling self._conn.rollback(). Python's sqlite3 with default isolation_level opens an implicit BEGIN before any DML, so the UPDATE held the WAL write lock until the next commit() — blocking other writers for up to busy_timeout (5 s). Mirrors the existing ObservationService.set_outcome pattern at better_memory/services/observation.py:435. Adds a regression test that asserts conn.in_transaction is False after the failed update. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Claude BugBot Analysis
No bugs found in the changes. The migration, SemanticMemoryService, and MCP handlers are all correctly implemented: scope and content validation fire before any DB access, update_text properly rolls back the implicit SQLite transaction before raising on a missing ID, the list_for_project OR-predicate returns each row exactly once (no duplicate general-scope rows), and the shared-connection pattern matches the existing services in the codebase.
No bugs were detected in this PR.
Summary
semantic_memoriesfor user-asserted facts/preferences. Distinct from observations (episodic) and reflections (LLM-distilled).scope = ('project'|'general')model as PR Episodic synthesis (per-episode LLM call) + general-scope reflections #25's reflections — general-scope rows surface in every project's startup retrieval.Why
Workflow rules and other project-agnostic preferences need a first-class structured place that surfaces in every session. PR #25 added cross-project scope to reflections; this PR extends the same model to a new user-driven (not LLM-distilled) memory layer. Examples that would land here: "I prefer terse responses" (general), "this codebase uses pytest, never unittest" (project), "always assign per-step confidence to a superpowers plan" (general).
Spec: `docs/superpowers/specs/2026-05-04-semantic-memories-design.md`
Plan: `docs/superpowers/plans/2026-05-04-semantic-memories.md`
API
```
memory.semantic_observe(content, scope='project') -> {id}
memory.semantic_retrieve(project?) -> [memories...]
memory.semantic_update(id, content) -> {ok}
memory.semantic_delete(id) -> {ok}
```
Test Plan
Commits
Follow-up (out of this PR)
Edit `~/.claude/CLAUDE.md` to add `memory_semantic_retrieve` to the mandatory startup retrieval flow. Exact text in the spec under "CLAUDE.md addendum".
🤖 Generated with Claude Code