Rensei docs
Graph

Graph Feedback

EMA weights production wire-up.

Graph feedback closes the learning loop: when an agent session completes with a known outcome (accepted, rework, or rejected), feedbackWeight on every node and edge that was surfaced during context injection is updated via an Exponential Moving Average (EMA). Over time, frequently-useful entities rank higher in future queries; entities that led to rework or rejection rank lower.

Architecture

EMA update formula

The feedback weight update uses a standard EMA:

new_weight = alpha * new_score + (1 - alpha) * current_weight

Where:

  • alpha defaults to 0.1 (slow decay - the graph is meant to be stable)
  • new_score is derived from the outcome: accepted → 1.0, rework → 0.5, rejected → 0.0
  • current_weight is the current feedbackWeight on the node or edge (default 0.5)

Both node and edge feedbackWeight columns are updated independently. The edge weight adjustment influences triplet scoring for future queries via the feedbackInfluence parameter (default 0.3).

Production wire-up

The production entry point is applyFeedback in graph-feedback.ts. It:

  1. Reads context_injection_logs to find the node IDs and edge keys that were surfaced to the agent during context injection
  2. Resolves the project ID via Redis session-project mapping
  3. Constructs PgGraphStore.withScope({ orgId, projectId }) to bind tenant scope
  4. Constructs PgFeedbackHistoryStore for graph_feedback_history writes
  5. Creates a GraphFeedbackService with the Voyage document embedder (when VOYAGE_API_KEY is set) for triplet embedding updates
  6. Delegates to GraphFeedbackService.applyFeedback
import { applyFeedback } from '@/lib/memory/graph-feedback'

// Called from session-lifecycle-hooks.ts after outcome resolution
await applyFeedback({
  orgId: 'org_...',
  sessionId: 'sess_...',
  outcome: 'accepted',  // 'accepted' | 'rework' | 'rejected'
})

Fallthrough cases

The function exits cleanly without throwing when:

  • orgId is undefined
  • No context_injection_logs row exists for the session (context injection skipped)
  • Both graphNodeIds and graphEdgeKeys arrays are empty (graph was disabled or returned no triplets)
  • resolveProjectIdForSession returns null (Redis state expired; a structured warning is logged)

All fallthrough cases are expected during normal operation - not every session injects graph context, and not every org has graph enabled.

Triplet embeddings

When a VOYAGE_API_KEY is configured and embeddings are enabled, the feedback service also embeds the triplet string "sourceName → relationshipName → targetName" and writes it to graph_triplet_embeddings via store.upsertTripletEmbedding. These vectors power triplet-level cosine retrieval (the <=> operator on the HNSW index) in addition to node-level similarity.

When no embedder is available, the feedback path still runs fully - node and edge feedbackWeight updates apply regardless. The triplet embedding step is silently skipped.

Feedback history

Every feedback update appends a row to graph_feedback_history via PgFeedbackHistoryStore. The history table powers the trend chart in the memory analytics dashboard (feedback impact sub-view) and is the data source for the graph_feedback audit event.

Audit trail

Each applyFeedback call emits a graph.feedback event to the hash-chained audit log. The event includes the session ID, outcome, scope, and counts of node/edge updates.

GraphFeedbackService API (advanced)

For direct service use (e.g. the graph_improve MCP tool):

import { GraphFeedbackService } from '@/lib/graph/feedback/service'

const service = new GraphFeedbackService({
  store: pgGraphStore.withScope({ orgId, projectId }),
  history: pgFeedbackHistoryStore,
  audit: graphFeedbackAuditHook,
  embedder: optionalEmbedder,  // (text) => Promise<number[] | null>
})

const result = await service.applyFeedback({
  sessionId,
  outcome: 'rework',
  usedNodeIds: ['node-uuid-1', 'node-uuid-2'],
  usedEdgeKeys: [
    { sourceId: 'node-uuid-1', targetId: 'node-uuid-2', relationshipName: 'depends_on' }
  ],
  scope: { orgId, projectId },
  alpha: 0.1,   // optional, default 0.1
})
// result: { nodeUpdates, edgeUpdates, tripletEmbeddings, durationMs }

On this page