Skip to content

Semantic memories — user-stated facts and preferences#34

Merged
emp3thy merged 6 commits into
mainfrom
semantic-memories
May 4, 2026
Merged

Semantic memories — user-stated facts and preferences#34
emp3thy merged 6 commits into
mainfrom
semantic-memories

Conversation

@emp3thy
Copy link
Copy Markdown
Owner

@emp3thy emp3thy commented May 4, 2026

Summary

  • New top-level concept in better-memory: semantic_memories for user-asserted facts/preferences. Distinct from observations (episodic) and reflections (LLM-distilled).
  • Same 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.
  • Three implementation commits: migration 0008 (schema + 7 tests) → SemanticMemoryService (~150 LOC, 14 tests) → 4 MCP tools (10 tests). Plus a docs commit with the spec + plan.

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

  • Migration 0008 schema/index/CHECK tests — 7 tests
  • Service-level CRUD tests including invalid scope, empty content, missing id, scope merging, ordering — 14 tests
  • MCP tool registration + scope-null defense regression test — 10 tests
  • Full pytest green: 660+ passed (was 629 baseline post-PR-25)
  • `uv run pyright` clean (0 errors)
  • Manual smoke through the four MCP tools

Commits

  • `ba9ff17` docs(superpowers): semantic memories spec + plan
  • `a4d0017` feat(db): migration 0008 — semantic_memories table
  • `17883d6` feat(semantic): SemanticMemoryService for user-stated facts/preferences
  • `8ce9de8` feat(mcp): memory.semantic_observe / _retrieve / _update / _delete tools

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

emp3thy and others added 5 commits May 4, 2026 13:08
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>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Comment thread better_memory/services/semantic.py
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>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.

@emp3thy emp3thy merged commit aa34ad1 into main May 4, 2026
3 checks passed
@emp3thy emp3thy deleted the semantic-memories branch May 8, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant