opencode-graphiti 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,12 +22,16 @@ reminded of recent project context — regardless of what survived the summary.
22
22
 
23
23
  This plugin connects to a Graphiti MCP server and:
24
24
 
25
- - Injects relevant memories into the first user message of each session
26
- - Periodically re-injects project memories as the conversation progresses
25
+ - Searches Graphiti for relevant facts and entities on each user message
26
+ - Injects memories into the system prompt via
27
+ `experimental.chat.system.transform`, keeping session titles clean
28
+ - Detects context drift using Jaccard similarity and re-injects when the
29
+ conversation topic shifts
27
30
  - Buffers user and assistant messages, flushing them to Graphiti on idle or
28
31
  before compaction
29
32
  - Preserves key facts during context compaction
30
33
  - Saves compaction summaries as episodes so knowledge survives across boundaries
34
+ - Annotates stale facts and filters expired ones automatically
31
35
  - Scopes memories per project (and per user) using directory-based group IDs
32
36
 
33
37
  ## Prerequisites
@@ -104,8 +108,12 @@ Create a config file at `~/.config/opencode/graphiti.jsonc`:
104
108
  // Prefix for project group IDs (e.g. "opencode-my-project")
105
109
  "groupIdPrefix": "opencode",
106
110
 
107
- // Number of user messages between memory re-injections (0 = disabled)
108
- "injectionInterval": 10
111
+ // Jaccard similarity threshold (0–1) below which memory is re-injected
112
+ // Lower values mean the topic must drift further before re-injection
113
+ "driftThreshold": 0.5,
114
+
115
+ // Number of days after which facts are annotated as stale
116
+ "factStaleDays": 30
109
117
  }
110
118
  ```
111
119
 
@@ -114,27 +122,36 @@ values.
114
122
 
115
123
  ## How It Works
116
124
 
117
- ### Memory Injection (`chat.message`)
125
+ ### Memory Search and Caching (`chat.message`)
118
126
 
119
- On the first user message in a session, the plugin searches Graphiti for facts
120
- and entities relevant to the message content. Results are split into project and
121
- user scopes (70% / 30% budget split), formatted in XML-style `<memory>` blocks
122
- with explicit de-emphasis instructions, and injected into the conversation.
127
+ On each user message the plugin searches Graphiti for facts and entities
128
+ relevant to the message content. Results are split into project and user scopes
129
+ (70% / 30% budget), deduplicated, filtered for validity, annotated with
130
+ staleness if older than `factStaleDays`, and formatted as Markdown. The
131
+ formatted context is cached on the session state for the system prompt hook to
132
+ pick up.
123
133
 
124
- Memory is injected via `output.message.system` (system-level instruction) when
125
- available, which prevents memory from influencing session titles. If the system
126
- field is unavailable, the plugin falls back to prepending synthetic parts.
134
+ On the very first message of a session, the plugin also loads the most recent
135
+ session snapshot episode to prime the conversation with prior context.
127
136
 
128
137
  The injection budget is calculated dynamically: 5% of the model's context limit
129
138
  (resolved from the provider list) multiplied by 4 characters per token.
130
139
 
131
- ### Re-injection (`chat.message`)
140
+ ### System Prompt Injection (`experimental.chat.system.transform`)
141
+
142
+ A separate hook reads the cached memory context and appends it to the system
143
+ prompt array. This approach keeps memory out of user message parts, which
144
+ prevents it from influencing session titles or being treated as user
145
+ instructions. The cache is cleared after injection so stale context is not
146
+ re-injected on subsequent LLM calls within the same turn.
147
+
148
+ ### Drift-Based Re-injection (`chat.message`)
132
149
 
133
- When `injectionInterval` is greater than 0, the plugin periodically re-injects
134
- project-scoped memories as the conversation progresses. After every N user
135
- messages (where N is `injectionInterval`), stale synthetic memory parts are
136
- replaced with fresh results. Re-injection uses the full character budget for
137
- project memories only (no user scope).
150
+ After the first injection, the plugin monitors for context drift on every user
151
+ message. It searches Graphiti for the current message and compares the returned
152
+ fact UUIDs against the previously injected set using Jaccard similarity. When
153
+ similarity drops below `driftThreshold` (default 0.5), the memory cache is
154
+ refreshed with project-scoped results only (no user scope).
138
155
 
139
156
  ### Message Buffering (`event`)
140
157
 
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IA+IvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBAqEzE"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IAiJvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA6EzE"}
@@ -96,24 +96,26 @@ export function createChatHandler(deps) {
96
96
  .slice(0, characterBudget);
97
97
  if (!memoryContext)
98
98
  return;
99
- const factUuids = seedFactUuids ??
100
- new Set([
101
- ...projectContext.facts.map((fact) => fact.uuid),
102
- ...userContext.facts.map((fact) => fact.uuid),
103
- ]);
99
+ const allFactUuids = [
100
+ ...projectContext.facts.map((fact) => fact.uuid),
101
+ ...userContext.facts.map((fact) => fact.uuid),
102
+ ];
103
+ const factUuids = seedFactUuids ?? Array.from(new Set(allFactUuids));
104
104
  state.cachedMemoryContext = memoryContext;
105
105
  logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
106
106
  state.lastInjectionFactUuids = factUuids;
107
107
  };
108
108
  const computeJaccardSimilarity = (left, right) => {
109
- if (left.size === 0 && right.size === 0)
109
+ if (left.length === 0 && right.length === 0)
110
110
  return 1;
111
+ const leftSet = new Set(left);
112
+ const rightSet = new Set(right);
111
113
  let intersection = 0;
112
- for (const value of left) {
113
- if (right.has(value))
114
+ for (const value of leftSet) {
115
+ if (rightSet.has(value))
114
116
  intersection += 1;
115
117
  }
116
- const union = left.size + right.size - intersection;
118
+ const union = leftSet.size + rightSet.size - intersection;
117
119
  return union === 0 ? 1 : intersection / union;
118
120
  };
119
121
  return async ({ sessionID }, output) => {
@@ -144,18 +146,27 @@ export function createChatHandler(deps) {
144
146
  let shouldReinject = false;
145
147
  let currentFactUuids = null;
146
148
  if (!shouldInjectOnFirst) {
147
- const driftFacts = await client.searchFacts({
148
- query: messageText,
149
- groupIds: [state.groupId],
150
- maxFacts: 20,
151
- });
152
- currentFactUuids = new Set(driftFacts.map((fact) => fact.uuid));
153
- const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
154
- shouldReinject = similarity < driftThreshold;
155
- if (!shouldReinject) {
156
- logger.debug("Skipping reinjection; drift above threshold", {
149
+ try {
150
+ const driftFacts = await client.searchFacts({
151
+ query: messageText,
152
+ groupIds: [state.groupId],
153
+ maxFacts: 20,
154
+ });
155
+ currentFactUuids = driftFacts.map((fact) => fact.uuid);
156
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
157
+ shouldReinject = similarity < driftThreshold;
158
+ if (!shouldReinject) {
159
+ logger.debug("Skipping reinjection; similarity above threshold", {
160
+ sessionID,
161
+ similarity,
162
+ });
163
+ return;
164
+ }
165
+ }
166
+ catch (err) {
167
+ logger.error("Failed to check topic drift, skipping reinjection", {
157
168
  sessionID,
158
- similarity,
169
+ err,
159
170
  });
160
171
  return;
161
172
  }
@@ -59,7 +59,7 @@ export function createEventHandler(deps) {
59
59
  groupId: defaultGroupId,
60
60
  userGroupId: defaultUserGroupId,
61
61
  injectedMemories: false,
62
- lastInjectionFactUuids: new Set(),
62
+ lastInjectionFactUuids: [],
63
63
  messageCount: 0,
64
64
  pendingMessages: [],
65
65
  contextLimit: 200_000,
@@ -11,7 +11,7 @@ export type SessionState = {
11
11
  /** Whether memories have been injected into this session yet. */
12
12
  injectedMemories: boolean;
13
13
  /** Fact UUIDs included in the last memory injection. */
14
- lastInjectionFactUuids: Set<string>;
14
+ lastInjectionFactUuids: string[];
15
15
  /** Cached formatted memory context for system prompt injection. */
16
16
  cachedMemoryContext?: string;
17
17
  /** Count of messages observed in this session. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
@@ -106,7 +106,7 @@ export class SessionManager {
106
106
  groupId: this.defaultGroupId,
107
107
  userGroupId: this.defaultUserGroupId,
108
108
  injectedMemories: false,
109
- lastInjectionFactUuids: new Set(),
109
+ lastInjectionFactUuids: [],
110
110
  cachedMemoryContext: undefined,
111
111
  messageCount: 0,
112
112
  pendingMessages: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-graphiti",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
5
5
  "keywords": [
6
6
  "opencode",
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IA+IvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBAqEzE"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IAiJvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA6EzE"}
@@ -99,24 +99,26 @@ function createChatHandler(deps) {
99
99
  .slice(0, characterBudget);
100
100
  if (!memoryContext)
101
101
  return;
102
- const factUuids = seedFactUuids ??
103
- new Set([
104
- ...projectContext.facts.map((fact) => fact.uuid),
105
- ...userContext.facts.map((fact) => fact.uuid),
106
- ]);
102
+ const allFactUuids = [
103
+ ...projectContext.facts.map((fact) => fact.uuid),
104
+ ...userContext.facts.map((fact) => fact.uuid),
105
+ ];
106
+ const factUuids = seedFactUuids ?? Array.from(new Set(allFactUuids));
107
107
  state.cachedMemoryContext = memoryContext;
108
108
  logger_js_1.logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
109
109
  state.lastInjectionFactUuids = factUuids;
110
110
  };
111
111
  const computeJaccardSimilarity = (left, right) => {
112
- if (left.size === 0 && right.size === 0)
112
+ if (left.length === 0 && right.length === 0)
113
113
  return 1;
114
+ const leftSet = new Set(left);
115
+ const rightSet = new Set(right);
114
116
  let intersection = 0;
115
- for (const value of left) {
116
- if (right.has(value))
117
+ for (const value of leftSet) {
118
+ if (rightSet.has(value))
117
119
  intersection += 1;
118
120
  }
119
- const union = left.size + right.size - intersection;
121
+ const union = leftSet.size + rightSet.size - intersection;
120
122
  return union === 0 ? 1 : intersection / union;
121
123
  };
122
124
  return async ({ sessionID }, output) => {
@@ -147,18 +149,27 @@ function createChatHandler(deps) {
147
149
  let shouldReinject = false;
148
150
  let currentFactUuids = null;
149
151
  if (!shouldInjectOnFirst) {
150
- const driftFacts = await client.searchFacts({
151
- query: messageText,
152
- groupIds: [state.groupId],
153
- maxFacts: 20,
154
- });
155
- currentFactUuids = new Set(driftFacts.map((fact) => fact.uuid));
156
- const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
157
- shouldReinject = similarity < driftThreshold;
158
- if (!shouldReinject) {
159
- logger_js_1.logger.debug("Skipping reinjection; drift above threshold", {
152
+ try {
153
+ const driftFacts = await client.searchFacts({
154
+ query: messageText,
155
+ groupIds: [state.groupId],
156
+ maxFacts: 20,
157
+ });
158
+ currentFactUuids = driftFacts.map((fact) => fact.uuid);
159
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
160
+ shouldReinject = similarity < driftThreshold;
161
+ if (!shouldReinject) {
162
+ logger_js_1.logger.debug("Skipping reinjection; similarity above threshold", {
163
+ sessionID,
164
+ similarity,
165
+ });
166
+ return;
167
+ }
168
+ }
169
+ catch (err) {
170
+ logger_js_1.logger.error("Failed to check topic drift, skipping reinjection", {
160
171
  sessionID,
161
- similarity,
172
+ err,
162
173
  });
163
174
  return;
164
175
  }
@@ -62,7 +62,7 @@ function createEventHandler(deps) {
62
62
  groupId: defaultGroupId,
63
63
  userGroupId: defaultUserGroupId,
64
64
  injectedMemories: false,
65
- lastInjectionFactUuids: new Set(),
65
+ lastInjectionFactUuids: [],
66
66
  messageCount: 0,
67
67
  pendingMessages: [],
68
68
  contextLimit: 200_000,
@@ -11,7 +11,7 @@ export type SessionState = {
11
11
  /** Whether memories have been injected into this session yet. */
12
12
  injectedMemories: boolean;
13
13
  /** Fact UUIDs included in the last memory injection. */
14
- lastInjectionFactUuids: Set<string>;
14
+ lastInjectionFactUuids: string[];
15
15
  /** Cached formatted memory context for system prompt injection. */
16
16
  cachedMemoryContext?: string;
17
17
  /** Count of messages observed in this session. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
@@ -109,7 +109,7 @@ class SessionManager {
109
109
  groupId: this.defaultGroupId,
110
110
  userGroupId: this.defaultUserGroupId,
111
111
  injectedMemories: false,
112
- lastInjectionFactUuids: new Set(),
112
+ lastInjectionFactUuids: [],
113
113
  cachedMemoryContext: undefined,
114
114
  messageCount: 0,
115
115
  pendingMessages: [],