feat(db): expose relevance scores from match_agent_memories_hybrid#444
feat(db): expose relevance scores from match_agent_memories_hybrid#444gianpaj wants to merge 1 commit into
Conversation
Surface the cosine distance, fused RRF score, and per-branch vector/text ranks the hybrid retrieval RPC already computes but discarded, so the voice agent (sexycall observability) can log retrieval quality. Additive and backward compatible: memory_type/content stay the first two columns, the argument signature is unchanged, and ranking is identical (same 0.7/0.3 RRF weights, k=60, same ORDER BY/LIMIT) — only four trailing score columns are added. Adding columns to a RETURNS TABLE is a return-type change that CREATE OR REPLACE cannot do, so the migration drops and recreates only the function (the agent_memories table and its data are untouched) and re-grants EXECUTE. Includes a pgTAP test (supabase/tests) asserting order preservation, cosine_distance null/ascending behaviour, monotonic rrf_score, and text-only vs vector-only rank nullability. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XcFmiddpzKxYxkPbDnYfYv
|
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @gianpaj's task in 1m 31s —— View job Review: expose relevance scores from
|
There was a problem hiding this comment.
Code Review
This pull request introduces a migration to drop and recreate the public.match_agent_memories_hybrid function, exposing trailing relevance score columns (cosine distance, RRF score, vector rank, and text rank) to improve observability of retrieval quality. It also adds a comprehensive pgTAP test suite to verify the correctness of these new columns and ensure ranking behavior remains unchanged. The review feedback highlights a critical improvement opportunity: qualifying the vector type as extensions.vector in the DROP FUNCTION, CREATE FUNCTION, and GRANT statements to prevent potential schema-resolution failures when extensions is not in the active search_path.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| DROP FUNCTION IF EXISTS public.match_agent_memories_hybrid( | ||
| vector, text, uuid, text, int | ||
| ); |
There was a problem hiding this comment.
In Supabase, the vector extension and its associated types (like vector) are typically installed in the extensions schema. If the migration runner or session executing this SQL does not have extensions in its active search_path, referencing vector unqualified will fail with a type "vector" does not exist error. Qualifying it as extensions.vector ensures the migration is robust and schema-safe.
DROP FUNCTION IF EXISTS public.match_agent_memories_hybrid(
extensions.vector, text, uuid, text, int
);| CREATE OR REPLACE FUNCTION public.match_agent_memories_hybrid( | ||
| query_embedding vector(1536), | ||
| query_text text, | ||
| p_user_id uuid, | ||
| p_character_id text DEFAULT '__shared__', | ||
| match_count int DEFAULT 5 | ||
| ) |
There was a problem hiding this comment.
The SET search_path attribute on the function definition only applies during the execution of the function, not during its creation. During creation, PostgreSQL parses the argument types using the session's current search_path. If extensions is not in the session's search path, the CREATE FUNCTION statement will fail. Qualifying the argument type as extensions.vector(1536) prevents this failure.
CREATE OR REPLACE FUNCTION public.match_agent_memories_hybrid(
query_embedding extensions.vector(1536),
query_text text,
p_user_id uuid,
p_character_id text DEFAULT '__shared__',
match_count int DEFAULT 5
)| GRANT EXECUTE ON FUNCTION public.match_agent_memories_hybrid( | ||
| vector, text, uuid, text, int | ||
| ) TO authenticated, service_role; |
There was a problem hiding this comment.
Similar to the DROP FUNCTION statement, the GRANT statement is executed in the session context. If extensions is not in the session's active search_path, referencing vector unqualified will fail. Qualifying it as extensions.vector ensures the grant is applied successfully.
GRANT EXECUTE ON FUNCTION public.match_agent_memories_hybrid(
extensions.vector, text, uuid, text, int
) TO authenticated, service_role;There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 740df31383
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| WITH vector_ranked AS ( | ||
| SELECT m.id, | ||
| row_number() OVER (ORDER BY m.embedding <=> query_embedding ASC) AS rank | ||
| FROM public.agent_memories m |
There was a problem hiding this comment.
Add the missing agent_memories prerequisite migration
In the reviewed parent commit (12474fe), git ls-tree ... | rg 'agent_memories|match_agent_memories|20260626110000' finds no prerequisite migration or table definition, and a repo-wide search only finds this new migration/test. Because this SQL function body references public.agent_memories, a fresh Supabase migration run from main will reach this migration before the table/function introduced by the referenced #437 exists, so database resets/deploys fail instead of applying cleanly. Please include or correctly stack the prerequisite migration before this one.
Useful? React with 👍 / 👎.
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — recreates the public.match_agent_memories_hybrid RPC to expose four already-computed-but-discarded relevance columns, plus a standalone pgTAP test.
- Add four trailing output columns —
cosine_distance,rrf_score,vector_rank,text_rankare appended aftermemory_type/content; thefusedCTE now carriesv.rank/t.rankforward and the finalSELECTre-derives the cosine distance per surviving row. DROP+ recreate keyed on the exact arg signature — required because addingRETURNS TABLEcolumns changes the result type, whichCREATE OR REPLACE FUNCTIONrejects;EXECUTEis re-granted toauthenticated, service_roleand the security posture (language sql,security invoker,set search_path = public, extensions) is preserved.- Add pgTAP test (
match_agent_memories_hybrid_scores.test.sql) — 11 assertions covering row/order preservation,cosine_distancenull-vs-ascending behaviour,rrf_scoremonotonicity, and per-branch rank nullability; not wired into Vitest CI (no Postgres), run viasupabase test db.
I compared the recreated body against #437's current function definition (claude/supabase-memory): the two CTEs, the RRF score formula (0.7/(60+v.rank) + 0.3/(60+t.rank)), ORDER BY f.score DESC, and LIMIT greatest(match_count, 1) are byte-for-byte identical — ranking is genuinely unchanged, so the additive/backward-compatible claim holds. The RPC is absent from types.d.ts and unreferenced by any web-app .ts, so the new columns have no typed-surface impact.
ℹ️ Recreated body duplicates #437's function while both PRs are in flight
This migration hardcodes a full copy of match_agent_memories_hybrid and depends on #437 (claude/supabase-memory), which is still open and was already rebased once (its timestamps moved from 20260623… to 20260626…). The "additive only" guarantee rests on this copy staying a strict superset of #437's body, but nothing in the web-app CI verifies that — the pgTAP test isn't wired into Vitest (no Postgres), so a divergence would only surface when someone runs supabase test db manually.
Technical details
# Recreated body duplicates #437's function while both PRs are in flight
## Affected sites
- `apps/web/supabase/migrations/20260626130000_match_agent_memories_hybrid_scores.sql` — full recreate of the RPC, must remain a strict superset of #437's `20260626110000_create_agent_memories.sql` definition.
## Required outcome
- Before merging, re-confirm #437's final merged function body still matches the two CTEs / RRF formula / ORDER BY / LIMIT recreated here (verified identical at head 740df31 vs the current `claude/supabase-memory`).
- Merge order must be #437 first, then #444 — the migration's CREATE references `public.agent_memories` and will fail if applied on `main` before #437 lands. The author already documents this.
## Open questions for the human
- Is there appetite to wire `supabase test db` into CI later so this class of drift is caught automatically? Out of scope for this PR; noted for awareness.@v0 or keep the SHA fresh with Dependabot | View workflow run | Using Claude Opus | 𝕏

What
Adds a migration that surfaces relevance scores from the existing Postgres RPC
public.match_agent_memories_hybrid, plus a pgTAP test. This is additive and backward compatible: the RPC keepsmemory_typeandcontentas its first two columns and the same argument signature, ranking behaviour is unchanged (same RRF weights 0.7 vector / 0.3 text, k=60, sameORDER BY f.score DESC/LIMIT), and four trailing columns are added —cosine_distance,rrf_score,vector_rank,text_rank. It only exposes values the function already computed and discarded, so it unblocks retrieval-quality monitoring in sexycall (src/observability.py) without re-ranking anything. Callers that readmemory_type/content(e.g. sexycall'sformat_memories) are unaffected.Files
apps/web/supabase/migrations/20260626130000_match_agent_memories_hybrid_scores.sqlapps/web/supabase/tests/match_agent_memories_hybrid_scores.test.sqlNote on
DROPvsCREATE OR REPLACEThe task asked for
CREATE OR REPLACE, but adding columns to aRETURNS TABLEchanges the function's result type, and Postgres rejects that viaCREATE OR REPLACE FUNCTION(ERROR: cannot change return type of existing function). The migration thereforeDROPs and recreates only the function, keyed on its exact argument signature — theagent_memoriestable and its rows are never touched, the arguments stay identical, andEXECUTEis re-granted toauthenticated, service_role. Security posture is unchanged (language sql,security invoker,set search_path = public, extensions).Dependency / stacking
claude/supabase-memory), which introducesagent_memoriesand the original two-column function. The new migration's timestamp (20260626130000) sorts after #437's…110000_create_agent_memories.sqland…120000_prune_agent_memories.sql, so it applies in the correct order once both are merged. It will not apply onmainuntil #437 lands.Testing
CI (Vitest + Redis) has no Postgres, so the test is a standalone pgTAP script run via
supabase test db. Validated locally on Postgres 16 + pgvector + pgTAP by applying #437's migration then this one: the migration applies cleanly and all 11 pgTAP assertions pass. Coverage:contentstays the second columncosine_distancenon-null & ascending for embedded rows,NULLfor the null-embedding rowrrf_scorenon-null and monotonically non-increasing across the result setvector_rank IS NULL(with atext_rank) and a vector-only match hastext_rank IS NULL(with avector_rank)🤖 Generated with Claude Code
Generated by Claude Code