Skip to content

fix(memory): fold tags into the FTS vector so memory_search matches tags - #229

Open
prellr wants to merge 1 commit into
mainfrom
fix/memory-search-index-tags
Open

prellr wants to merge 1 commit into
mainfrom
fix/memory-search-index-tags

Conversation

@prellr

@prellr prellr commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Problem

memory_search (the SearchMemoryArtifacts query) is advertised as searching "titles, content, and tags", but content_tsv (migration 068) is generated from only title + content. So a full-text search for a tag returns nothing — e.g. searching planning-doc-migration found 0 rows even though many artifacts carry that tag. (Discovered while bulk-migrating planning docs with a shared tag and finding them unsearchable by it.)

Fix — migration 127

Rebuild content_tsv to fold tags into the vector:

to_tsvector('english',
  coalesce(title,'') || ' ' || coalesce(content,'') || ' ' ||
  coalesce(array_to_string(tags, ' '), ''))

A generated column's expression can't be ALTERed in place, so the migration drops the idx_memory_tsv GIN index, drops + re-adds content_tsv with the new expression, and recreates the index. Re-adding the STORED generated column recomputes it for every existing row, so all current artifacts become tag-searchable immediately (memory_artifact is small — the rewrite is quick). .down.sql reverts to title+content only.

No behavior change beyond the fix

  • No query change — SearchMemoryArtifacts/CountSearchMemoryArtifacts already match on content_tsv; they now match tag terms too.
  • sqlc regen is benign — re-adding the column moves content_tsv (an opaque tsvector, scanned by name) to the end of the RETURNING * / SELECT * column lists. That's the entire generated diff.

Verification

  • make sqlc clean · go build ./pkg/db/... ./cmd/multica/ ok.
  • FTS expression is correct by construction: to_tsvector folds the tag lexemes in, and websearch_to_tsquery tokenizes the query the same way, so @@ matches. (Couldn't run a live DB proof locally — no Docker here — so the end-to-end check happens on make migrate-up / CI.)

Note for reviewer

Applying this on deploy (make migrate-up) rewrites the content_tsv for existing rows once. If you'd rather keep tags out of relevance weighting, an alternative is setweight(...) with a low weight for the tags portion — happy to switch to that if preferred.

🤖 Generated with Claude Code

The memory_search MCP tool advertises search over "titles, content, and tags",
but content_tsv (migration 068) only covered title + content — so searching a
tag (e.g. `planning-doc-migration`) returned zero rows despite matching artifacts.

Migration 127 rebuilds content_tsv to fold in `array_to_string(tags, ' ')`. A
generated column's expression can't be ALTERed in place, so it drops the GIN
index, drops + re-adds the column, and recreates the index; re-adding the STORED
generated column recomputes it for every existing row, so all current artifacts
become tag-searchable immediately (memory_artifact is small — quick rewrite).

No query change: SearchMemoryArtifacts already selects on content_tsv. The sqlc
regen is a benign reorder — re-adding the column moves content_tsv (an opaque
tsvector, name-scanned) to the end of the RETURNING/SELECT column lists.

Verified: `make sqlc` clean, `go build` ok. The FTS expression is correct by
construction (to_tsvector folds the tag tokens in; websearch_to_tsquery tokenizes
the query identically). Live end-to-end proof runs on `make migrate-up` / CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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