diff --git a/internal/http/knowledge_graph.go b/internal/http/knowledge_graph.go index 9cb79496..7333f851 100644 --- a/internal/http/knowledge_graph.go +++ b/internal/http/knowledge_graph.go @@ -49,5 +49,11 @@ func (h *KnowledgeGraphHandler) RegisterRoutes(mux *http.ServeMux) { } func (h *KnowledgeGraphHandler) auth(next http.HandlerFunc) http.HandlerFunc { - return requireAuth("", next) + return requireAuth("", func(w http.ResponseWriter, r *http.Request) { + // KG management endpoints serve the admin UI — use shared KG context + // so queries don't require exact user_id match. Tenant isolation is + // still enforced via scopeClause (tenant_id filter). + ctx := store.WithSharedKG(r.Context()) + next(w, r.WithContext(ctx)) + }) } diff --git a/internal/http/knowledge_graph_handlers.go b/internal/http/knowledge_graph_handlers.go index 28a33883..139f5cc8 100644 --- a/internal/http/knowledge_graph_handlers.go +++ b/internal/http/knowledge_graph_handlers.go @@ -67,6 +67,7 @@ func (h *KnowledgeGraphHandler) handleGetEntity(w http.ResponseWriter, r *http.R relations, err := h.store.ListRelations(r.Context(), agentID, userID, entityID) if err != nil { + slog.Warn("kg.list_relations failed", "agent_id", agentID, "entity_id", entityID, "user_id", userID, "error", err) relations = []store.Relation{} } if relations == nil { diff --git a/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts b/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts index 5ac1d33d..3ce37f41 100644 --- a/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts +++ b/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts @@ -268,5 +268,7 @@ export function useKGTraversal(agentId: string) { [http, agentId], ); - return { results, traversing, traverse }; + const reset = useCallback(() => setResults([]), []); + + return { results, traversing, traverse, reset }; } diff --git a/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx b/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx index 325928b5..639cbdd6 100644 --- a/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx +++ b/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx @@ -26,7 +26,13 @@ export function KGEntityDetailDialog({ open, onOpenChange, agentId, entity, getE const { t } = useTranslation("memory"); const [relations, setRelations] = useState([]); const [loadingRels, setLoadingRels] = useState(false); - const { results: traversalResults, traversing, traverse } = useKGTraversal(agentId); + const { results: traversalResults, traversing, traverse, reset: resetTraversal } = useKGTraversal(agentId); + + // Reset state when entity changes to prevent stale data from previous entity + useEffect(() => { + setRelations([]); + resetTraversal(); + }, [entity?.id, resetTraversal]); const loadRelations = useCallback(async () => { if (!entity) return; @@ -52,9 +58,9 @@ export function KGEntityDetailDialog({ open, onOpenChange, agentId, entity, getE traverse(entity.id, entity.user_id, 3); }, [entity, traverse]); - // Auto-traverse on open + // Auto-traverse on open — always traverse since state is reset on entity change useEffect(() => { - if (open && entity && traversalResults.length === 0 && !traversing) { + if (open && entity && !traversing) { traverse(entity.id, entity.user_id, 3); } }, [open, entity]); // eslint-disable-line react-hooks/exhaustive-deps