fix(memory): skip user_id filter in shared memory queries

When share_memory is enabled, memory queries now remove the user_id
filter entirely instead of filtering by user_id IS NULL. This matches
the existing KG shared behavior and allows all users to see all memory
documents (global + per-user from everyone).
This commit is contained in:
viettranx committed 2026-04-03 14:34:29 +07:00
1 parent 1f85c73a39
commit 9472819334
2 files changed
+73 -10

No files matched your search

+40 -4
View File
@@ -51,7 +51,16 @@ func (s *PGMemoryStore) GetDocument(ctx context.Context, agentID, userID, path s
var content string
var err error
if userID == "" {
if store.IsSharedMemory(ctx) {
// Shared: no user_id filter
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return "", tcErr
}
err = s.db.QueryRowContext(ctx,
"SELECT content FROM memory_documents WHERE agent_id = $1 AND path = $2"+tc+" ORDER BY updated_at DESC LIMIT 1",
append([]any{aid, path}, tcArgs...)...).Scan(&content)
} else if userID == "" {
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return "", tcErr
@@ -100,7 +109,16 @@ func (s *PGMemoryStore) DeleteDocument(ctx context.Context, agentID, userID, pat
aid := mustParseUUID(agentID)
var res sql.Result
var err error
if userID == "" {
if store.IsSharedMemory(ctx) {
// Shared: delete any matching doc regardless of user_id
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return tcErr
}
res, err = s.db.ExecContext(ctx,
"DELETE FROM memory_documents WHERE agent_id = $1 AND path = $2"+tc,
append([]any{aid, path}, tcArgs...)...)
} else if userID == "" {
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return tcErr
@@ -132,7 +150,16 @@ func (s *PGMemoryStore) ListDocuments(ctx context.Context, agentID, userID strin
var rows *sql.Rows
var err error
if userID == "" {
if store.IsSharedMemory(ctx) {
// Shared: list ALL docs for agent (global + per-user from all users)
tc, tcArgs, _, tcErr := scopeClause(ctx, 2)
if tcErr != nil {
return nil, tcErr
}
rows, err = s.db.QueryContext(ctx,
"SELECT path, hash, user_id, updated_at FROM memory_documents WHERE agent_id = $1"+tc,
append([]any{aid}, tcArgs...)...)
} else if userID == "" {
tc, tcArgs, _, tcErr := scopeClause(ctx, 2)
if tcErr != nil {
return nil, tcErr
@@ -187,7 +214,16 @@ func (s *PGMemoryStore) IndexDocument(ctx context.Context, agentID, userID, path
// Get document ID
var docID uuid.UUID
if userID == "" {
if store.IsSharedMemory(ctx) {
// Shared: no user_id filter
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return tcErr
}
err = s.db.QueryRowContext(ctx,
"SELECT id FROM memory_documents WHERE agent_id = $1 AND path = $2"+tc+" ORDER BY updated_at DESC LIMIT 1",
append([]any{aid, path}, tcArgs...)...).Scan(&docID)
} else if userID == "" {
tc, tcArgs, _, tcErr := scopeClause(ctx, 3)
if tcErr != nil {
return tcErr
+33 -6
View File
@@ -81,9 +81,22 @@ func (s *PGMemoryStore) ftsSearch(ctx context.Context, query string, agentID any
var q string
var args []any
if userID != "" {
if store.IsSharedMemory(ctx) {
// Shared: no user_id filter — search ALL chunks for agent
tc, tcArgs, _, err := scopeClause(ctx, 4)
if err != nil {
return nil, err
}
limitN := 4 + len(tcArgs)
q = fmt.Sprintf(`SELECT path, start_line, end_line, text, user_id,
ts_rank(tsv, plainto_tsquery('simple', $1)) AS score
FROM memory_chunks
WHERE agent_id = $2 AND tsv @@ plainto_tsquery('simple', $3)%s
ORDER BY score DESC LIMIT $%d`, tc, limitN)
args = append([]any{query, agentID, query}, tcArgs...)
args = append(args, limit)
} else if userID != "" {
// fixed params: $1=query, $2=agentID, $3=query, $4=userID
// tenant clause appended at $5 (if filtered), then LIMIT at $5 or $6
tc, tcArgs, _, err := scopeClause(ctx, 5)
if err != nil {
return nil, err
@@ -99,7 +112,6 @@ func (s *PGMemoryStore) ftsSearch(ctx context.Context, query string, agentID any
args = append(args, limit)
} else {
// fixed params: $1=query, $2=agentID, $3=query
// tenant clause at $4 (if filtered), then LIMIT at $4 or $5
tc, tcArgs, _, err := scopeClause(ctx, 4)
if err != nil {
return nil, err
@@ -136,9 +148,23 @@ func (s *PGMemoryStore) vectorSearch(ctx context.Context, embedding []float32, a
var q string
var args []any
if userID != "" {
if store.IsSharedMemory(ctx) {
// Shared: no user_id filter — search ALL chunks for agent
tc, tcArgs, _, err := scopeClause(ctx, 3)
if err != nil {
return nil, err
}
orderN := 3 + len(tcArgs)
limitN := orderN + 1
q = fmt.Sprintf(`SELECT path, start_line, end_line, text, user_id,
1 - (embedding <=> $1::vector) AS score
FROM memory_chunks
WHERE agent_id = $2 AND embedding IS NOT NULL%s
ORDER BY embedding <=> $%d::vector LIMIT $%d`, tc, orderN, limitN)
args = append([]any{vecStr, agentID}, tcArgs...)
args = append(args, vecStr, limit)
} else if userID != "" {
// fixed params: $1=vec, $2=agentID, $3=userID
// tenant clause at $4, then ORDER vec at $4+len(tcArgs), LIMIT after
tc, tcArgs, _, err := scopeClause(ctx, 4)
if err != nil {
return nil, err
@@ -155,7 +181,6 @@ func (s *PGMemoryStore) vectorSearch(ctx context.Context, embedding []float32, a
args = append(args, vecStr, limit)
} else {
// fixed params: $1=vec, $2=agentID
// tenant clause at $3, then ORDER vec at $3+len(tcArgs), LIMIT after
tc, tcArgs, _, err := scopeClause(ctx, 3)
if err != nil {
return nil, err
@@ -189,6 +214,8 @@ func (s *PGMemoryStore) vectorSearch(ctx context.Context, embedding []float32, a
// hybridMerge combines FTS and vector results with weighted scoring.
// Per-user results get a 1.2x boost. Deduplication: user copy wins over global.
// NOTE: when shared memory is active, the 1.2x personal boost still applies —
// consider removing it in shared mode if all docs should be treated equally.
func hybridMerge(fts, vec []scoredChunk, textWeight, vectorWeight float64, currentUserID string) []store.MemorySearchResult {
type key struct {
Path string