@psiclawops/hypermem 0.6.2 → 0.8.0

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.
Files changed (98) hide show
  1. package/ARCHITECTURE.md +31 -39
  2. package/README.md +20 -14
  3. package/bin/hypermem-status.mjs +1 -1
  4. package/dist/background-indexer.d.ts +14 -3
  5. package/dist/background-indexer.d.ts.map +1 -1
  6. package/dist/background-indexer.js +135 -27
  7. package/dist/budget-policy.d.ts +22 -0
  8. package/dist/budget-policy.d.ts.map +1 -0
  9. package/dist/budget-policy.js +27 -0
  10. package/dist/cache.d.ts +11 -0
  11. package/dist/cache.d.ts.map +1 -1
  12. package/dist/compositor-utils.d.ts +31 -0
  13. package/dist/compositor-utils.d.ts.map +1 -0
  14. package/dist/compositor-utils.js +47 -0
  15. package/dist/compositor.d.ts +163 -1
  16. package/dist/compositor.d.ts.map +1 -1
  17. package/dist/compositor.js +862 -130
  18. package/dist/content-hash.d.ts +43 -0
  19. package/dist/content-hash.d.ts.map +1 -0
  20. package/dist/content-hash.js +75 -0
  21. package/dist/context-store.d.ts +54 -0
  22. package/dist/context-store.d.ts.map +1 -1
  23. package/dist/context-store.js +102 -0
  24. package/dist/contradiction-audit-store.d.ts +54 -0
  25. package/dist/contradiction-audit-store.d.ts.map +1 -0
  26. package/dist/contradiction-audit-store.js +88 -0
  27. package/dist/contradiction-detector.d.ts +78 -0
  28. package/dist/contradiction-detector.d.ts.map +1 -0
  29. package/dist/contradiction-detector.js +362 -0
  30. package/dist/contradiction-resolution-policy.d.ts +21 -0
  31. package/dist/contradiction-resolution-policy.d.ts.map +1 -0
  32. package/dist/contradiction-resolution-policy.js +17 -0
  33. package/dist/cross-agent.d.ts +1 -1
  34. package/dist/cross-agent.js +17 -17
  35. package/dist/degradation.d.ts +102 -0
  36. package/dist/degradation.d.ts.map +1 -0
  37. package/dist/degradation.js +141 -0
  38. package/dist/dreaming-promoter.d.ts +39 -1
  39. package/dist/dreaming-promoter.d.ts.map +1 -1
  40. package/dist/dreaming-promoter.js +70 -4
  41. package/dist/expertise-store.d.ts +129 -0
  42. package/dist/expertise-store.d.ts.map +1 -0
  43. package/dist/expertise-store.js +342 -0
  44. package/dist/fact-store.d.ts +15 -0
  45. package/dist/fact-store.d.ts.map +1 -1
  46. package/dist/fact-store.js +52 -5
  47. package/dist/index.d.ts +74 -8
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +407 -29
  50. package/dist/knowledge-lint.d.ts +2 -0
  51. package/dist/knowledge-lint.d.ts.map +1 -1
  52. package/dist/knowledge-lint.js +40 -1
  53. package/dist/library-schema.d.ts +7 -2
  54. package/dist/library-schema.d.ts.map +1 -1
  55. package/dist/library-schema.js +307 -2
  56. package/dist/message-store.d.ts +64 -1
  57. package/dist/message-store.d.ts.map +1 -1
  58. package/dist/message-store.js +137 -1
  59. package/dist/proactive-pass.d.ts +2 -2
  60. package/dist/proactive-pass.d.ts.map +1 -1
  61. package/dist/proactive-pass.js +66 -12
  62. package/dist/replay-recovery.d.ts +29 -0
  63. package/dist/replay-recovery.d.ts.map +1 -0
  64. package/dist/replay-recovery.js +82 -0
  65. package/dist/reranker.d.ts +95 -0
  66. package/dist/reranker.d.ts.map +1 -0
  67. package/dist/reranker.js +308 -0
  68. package/dist/schema.d.ts +1 -1
  69. package/dist/schema.d.ts.map +1 -1
  70. package/dist/schema.js +46 -1
  71. package/dist/seed.d.ts +1 -1
  72. package/dist/seed.js +1 -1
  73. package/dist/session-flusher.d.ts +4 -4
  74. package/dist/session-flusher.d.ts.map +1 -1
  75. package/dist/session-flusher.js +3 -3
  76. package/dist/spawn-context.d.ts +1 -1
  77. package/dist/spawn-context.js +1 -1
  78. package/dist/temporal-store.d.ts +1 -0
  79. package/dist/temporal-store.d.ts.map +1 -1
  80. package/dist/tool-artifact-store.d.ts +98 -0
  81. package/dist/tool-artifact-store.d.ts.map +1 -0
  82. package/dist/tool-artifact-store.js +244 -0
  83. package/dist/topic-detector.js +2 -2
  84. package/dist/topic-store.d.ts +6 -0
  85. package/dist/topic-store.d.ts.map +1 -1
  86. package/dist/topic-store.js +39 -0
  87. package/dist/topic-synthesizer.js +1 -1
  88. package/dist/trigger-registry.d.ts +1 -1
  89. package/dist/trigger-registry.js +4 -4
  90. package/dist/types.d.ts +239 -3
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/vector-store.d.ts +2 -1
  93. package/dist/vector-store.d.ts.map +1 -1
  94. package/dist/vector-store.js +3 -0
  95. package/dist/version.d.ts +10 -10
  96. package/dist/version.d.ts.map +1 -1
  97. package/dist/version.js +10 -10
  98. package/package.json +6 -4
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Tool Artifact Store
3
+ *
4
+ * Durable, addressable storage for full tool result payloads. Schema v9.
5
+ *
6
+ * Why: the wave-guard at ingest time replaces oversized tool result payloads
7
+ * with a small stub for pressure relief. Before this store existed, the full
8
+ * payload was discarded. Now the stub in the transcript carries an
9
+ * `artifactId` pointing at the durable copy here, and hydration is an
10
+ * explicit decision, not an automatic transcript rewrite.
11
+ *
12
+ * Retention is deliberately independent of transcript eviction. Artifacts
13
+ * outlive the messages that referenced them, and GC is a separate concern
14
+ * (Phase 2).
15
+ *
16
+ * See: specs/TOOL_ARTIFACT_STORE.md
17
+ */
18
+ import { createHash, randomBytes } from 'node:crypto';
19
+ /**
20
+ * Simple char/4 heuristic matching src/compositor.ts estimateTokens().
21
+ * Kept local to avoid a cyclic import and keep the store self-contained.
22
+ */
23
+ function estimateTokens(text) {
24
+ if (!text)
25
+ return 0;
26
+ return Math.ceil(text.length / 4);
27
+ }
28
+ function nowIso() {
29
+ return new Date().toISOString();
30
+ }
31
+ function newArtifactId() {
32
+ // 24-char lowercase id. Deliberately NOT a UUID — shorter, fits the stub
33
+ // length cap in src/degradation.ts (DEGRADATION_LIMITS.artifactId = 64).
34
+ return 'art_' + randomBytes(10).toString('hex');
35
+ }
36
+ function sha256Hex(s) {
37
+ return createHash('sha256').update(s).digest('hex');
38
+ }
39
+ function rowToRecord(row) {
40
+ return {
41
+ id: row.id,
42
+ contentHash: row.content_hash,
43
+ agentId: row.agent_id,
44
+ sessionKey: row.session_key,
45
+ conversationId: row.conversation_id ?? null,
46
+ messageId: row.message_id ?? null,
47
+ turnId: row.turn_id ?? null,
48
+ toolCallId: row.tool_call_id ?? null,
49
+ toolName: row.tool_name,
50
+ isError: row.is_error === 1,
51
+ contentType: row.content_type,
52
+ sizeBytes: row.size_bytes,
53
+ tokenEstimate: row.token_estimate,
54
+ payload: row.payload,
55
+ summary: row.summary ?? null,
56
+ createdAt: row.created_at,
57
+ lastUsedAt: row.last_used_at,
58
+ refCount: row.ref_count,
59
+ isSensitive: row.is_sensitive === 1,
60
+ };
61
+ }
62
+ export class ToolArtifactStore {
63
+ db;
64
+ constructor(db) {
65
+ this.db = db;
66
+ }
67
+ /**
68
+ * Insert a new artifact, or dedupe against an existing one in the same
69
+ * (agentId, sessionKey) scope. Dedupe is scoped to a session so distinct
70
+ * sessions can't leak artifact ids across each other even when payload
71
+ * content is identical.
72
+ */
73
+ put(input) {
74
+ const payload = input.payload ?? '';
75
+ const contentHash = sha256Hex(payload);
76
+ const sizeBytes = Buffer.byteLength(payload, 'utf8');
77
+ const tokenEstimate = estimateTokens(payload);
78
+ const contentType = input.contentType ?? 'text/plain';
79
+ const isError = input.isError ? 1 : 0;
80
+ const isSensitive = input.isSensitive ? 1 : 0;
81
+ // Dedupe within (agentId, sessionKey) — same hash bumps ref_count and
82
+ // updates last_used_at, returning the existing record.
83
+ const existing = this.db
84
+ .prepare(`SELECT * FROM tool_artifacts
85
+ WHERE agent_id = ? AND session_key = ? AND content_hash = ?
86
+ LIMIT 1`)
87
+ .get(input.agentId, input.sessionKey, contentHash);
88
+ if (existing) {
89
+ const ts = nowIso();
90
+ this.db
91
+ .prepare(`UPDATE tool_artifacts
92
+ SET ref_count = ref_count + 1,
93
+ last_used_at = ?
94
+ WHERE id = ?`)
95
+ .run(ts, existing.id);
96
+ return rowToRecord({ ...existing, ref_count: existing.ref_count + 1, last_used_at: ts, is_sensitive: existing.is_sensitive ?? 0 });
97
+ }
98
+ const id = newArtifactId();
99
+ const ts = nowIso();
100
+ this.db
101
+ .prepare(`INSERT INTO tool_artifacts (
102
+ id, content_hash, agent_id, session_key,
103
+ conversation_id, message_id, turn_id, tool_call_id,
104
+ tool_name, is_error, content_type,
105
+ size_bytes, token_estimate, payload, summary,
106
+ created_at, last_used_at, ref_count, is_sensitive
107
+ ) VALUES (?, ?, ?, ?,
108
+ ?, ?, ?, ?,
109
+ ?, ?, ?,
110
+ ?, ?, ?, ?,
111
+ ?, ?, 1, ?)`)
112
+ .run(id, contentHash, input.agentId, input.sessionKey, input.conversationId ?? null, input.messageId ?? null, input.turnId ?? null, input.toolCallId ?? null, input.toolName, isError, contentType, sizeBytes, tokenEstimate, payload, input.summary ?? null, ts, ts, isSensitive);
113
+ return {
114
+ id,
115
+ contentHash,
116
+ agentId: input.agentId,
117
+ sessionKey: input.sessionKey,
118
+ conversationId: input.conversationId ?? null,
119
+ messageId: input.messageId ?? null,
120
+ turnId: input.turnId ?? null,
121
+ toolCallId: input.toolCallId ?? null,
122
+ toolName: input.toolName,
123
+ isError: input.isError ?? false,
124
+ contentType,
125
+ sizeBytes,
126
+ tokenEstimate,
127
+ payload,
128
+ summary: input.summary ?? null,
129
+ createdAt: ts,
130
+ lastUsedAt: ts,
131
+ refCount: 1,
132
+ isSensitive: input.isSensitive ?? false,
133
+ };
134
+ }
135
+ /** Alias for get(id) — explicit name used by the compositor hydration pass. */
136
+ getById(id) {
137
+ return this.get(id);
138
+ }
139
+ get(id) {
140
+ const row = this.db
141
+ .prepare('SELECT * FROM tool_artifacts WHERE id = ? LIMIT 1')
142
+ .get(id);
143
+ return row ? rowToRecord(row) : null;
144
+ }
145
+ getByHash(agentId, sessionKey, contentHash) {
146
+ const row = this.db
147
+ .prepare(`SELECT * FROM tool_artifacts
148
+ WHERE agent_id = ? AND session_key = ? AND content_hash = ?
149
+ ORDER BY created_at DESC
150
+ LIMIT 1`)
151
+ .get(agentId, sessionKey, contentHash);
152
+ return row ? rowToRecord(row) : null;
153
+ }
154
+ listByTurn(sessionKey, turnId) {
155
+ const rows = this.db
156
+ .prepare(`SELECT * FROM tool_artifacts
157
+ WHERE session_key = ? AND turn_id = ?
158
+ ORDER BY created_at ASC`)
159
+ .all(sessionKey, turnId);
160
+ return rows.map(rowToRecord);
161
+ }
162
+ listByToolCall(toolCallId) {
163
+ const rows = this.db
164
+ .prepare(`SELECT * FROM tool_artifacts
165
+ WHERE tool_call_id = ?
166
+ ORDER BY created_at ASC`)
167
+ .all(toolCallId);
168
+ return rows.map(rowToRecord);
169
+ }
170
+ listRecent(agentId, sessionKey, limit = 20) {
171
+ const rows = this.db
172
+ .prepare(`SELECT * FROM tool_artifacts
173
+ WHERE agent_id = ? AND session_key = ?
174
+ ORDER BY created_at DESC
175
+ LIMIT ?`)
176
+ .all(agentId, sessionKey, limit);
177
+ return rows.map(rowToRecord);
178
+ }
179
+ /** Update last_used_at — call this when hydration actually surfaces the payload. */
180
+ touch(id) {
181
+ this.db
182
+ .prepare('UPDATE tool_artifacts SET last_used_at = ? WHERE id = ?')
183
+ .run(nowIso(), id);
184
+ }
185
+ /**
186
+ * GC sweep: delete artifacts that exceed their TTL or per-session count cap.
187
+ * Returns total rows deleted.
188
+ *
189
+ * Sensitive artifacts use sensitiveTtlMs (shorter); standard artifacts use
190
+ * standardTtlMs. Optional maxPerSession bounds row count per (agent, session)
191
+ * using a ROW_NUMBER() window query — oldest last_used_at removed first.
192
+ */
193
+ sweep(policy) {
194
+ const now = Date.now();
195
+ const standardCutoff = new Date(now - policy.standardTtlMs).toISOString();
196
+ const sensitiveCutoff = new Date(now - policy.sensitiveTtlMs).toISOString();
197
+ const ttlResult = this.db
198
+ .prepare(`DELETE FROM tool_artifacts
199
+ WHERE (is_sensitive = 0 AND last_used_at < ?)
200
+ OR (is_sensitive = 1 AND last_used_at < ?)`)
201
+ .run(standardCutoff, sensitiveCutoff);
202
+ let deleted = ttlResult.changes ?? 0;
203
+ if (policy.maxPerSession != null) {
204
+ const boundResult = this.db
205
+ .prepare(`DELETE FROM tool_artifacts
206
+ WHERE id IN (
207
+ SELECT id FROM (
208
+ SELECT id,
209
+ ROW_NUMBER() OVER (
210
+ PARTITION BY agent_id, session_key
211
+ ORDER BY last_used_at DESC
212
+ ) AS rn
213
+ FROM tool_artifacts
214
+ )
215
+ WHERE rn > ?
216
+ )`)
217
+ .run(policy.maxPerSession);
218
+ deleted += boundResult.changes ?? 0;
219
+ }
220
+ return deleted;
221
+ }
222
+ /**
223
+ * Delete artifacts whose last_used_at is older than the ISO cutoff.
224
+ * Returns the number of rows deleted. Phase 1: manual invocation only.
225
+ */
226
+ deleteOlderThan(isoCutoff) {
227
+ const result = this.db
228
+ .prepare('DELETE FROM tool_artifacts WHERE last_used_at < ?')
229
+ .run(isoCutoff);
230
+ return Number(result.changes ?? 0);
231
+ }
232
+ /** Debug / ops: row count across a session. */
233
+ count(agentId, sessionKey) {
234
+ if (agentId && sessionKey) {
235
+ const row = this.db
236
+ .prepare('SELECT COUNT(*) AS n FROM tool_artifacts WHERE agent_id = ? AND session_key = ?')
237
+ .get(agentId, sessionKey);
238
+ return row?.n ?? 0;
239
+ }
240
+ const row = this.db.prepare('SELECT COUNT(*) AS n FROM tool_artifacts').get();
241
+ return row?.n ?? 0;
242
+ }
243
+ }
244
+ //# sourceMappingURL=tool-artifact-store.js.map
@@ -234,11 +234,11 @@ export function detectTopicShift(incomingMessage, recentMessages, currentTopicId
234
234
  const contextText = contextWindow
235
235
  .map(m => m.textContent ?? '')
236
236
  .join(' ');
237
- if (incomingEntities.size > 0 && contextText.length > 0) {
237
+ if (incomingEntities.size >= 2 && contextText.length > 0) {
238
238
  const contextEntities = extractEntities(contextText);
239
239
  const similarity = jaccardSimilarity(incomingEntities, contextEntities);
240
240
  // Low similarity + no continuation marker → likely a new topic
241
- if (similarity < 0.1) {
241
+ if (similarity < 0.15) {
242
242
  const name = extractTopicName(text);
243
243
  return { topicId: null, isNewTopic: true, confidence: 0.6, topicName: name };
244
244
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { DatabaseSync } from 'node:sqlite';
9
9
  import type { Topic, TopicStatus } from './types.js';
10
+ export declare function normalizeTopicName(name: string): string;
10
11
  export declare class TopicStore {
11
12
  private readonly db;
12
13
  constructor(db: DatabaseSync);
@@ -14,6 +15,11 @@ export declare class TopicStore {
14
15
  * Create a new topic.
15
16
  */
16
17
  create(agentId: string, name: string, description?: string, visibility?: string): Topic;
18
+ /**
19
+ * Find an existing topic by name (case-insensitive) or create a new one.
20
+ * Prevents duplicate topics for the same logical concept.
21
+ */
22
+ findOrCreate(agentId: string, rawName: string, description?: string, visibility?: string): Topic;
17
23
  /**
18
24
  * Touch a topic — update activity tracking.
19
25
  */
@@ -1 +1 @@
1
- {"version":3,"file":"topic-store.d.ts","sourceRoot":"","sources":["../src/topic-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAqBrD,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK;IAwBvF;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,GAAE,MAAU,GAAG,IAAI;IAY3E;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,KAAK,EAAE;IAWvD;;OAEG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,KAAK,EAAE;IAoBV;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,KAAK,EAAE;IAanE;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAE,MAAW,GAAG,MAAM;IAYpE;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,GAAE,MAAU,GAAG,MAAM;CAWnE"}
1
+ {"version":3,"file":"topic-store.d.ts","sourceRoot":"","sources":["../src/topic-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmBrD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAqBD,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK;IAwBvF;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK;IAkBhG;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,GAAE,MAAU,GAAG,IAAI;IAY3E;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,KAAK,EAAE;IAWvD;;OAEG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,KAAK,EAAE;IAoBV;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,KAAK,EAAE;IAanE;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAE,MAAW,GAAG,MAAM;IAYpE;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,GAAE,MAAU,GAAG,MAAM;CAWnE"}
@@ -5,6 +5,26 @@
5
5
  * can span multiple sessions and channels.
6
6
  * Lives in the central library DB.
7
7
  */
8
+ /**
9
+ * Normalize a topic name for dedup purposes.
10
+ * Preserves casing of known product names; lowercases everything else.
11
+ */
12
+ const KNOWN_NAMES = {
13
+ hypermem: 'HyperMem',
14
+ hyperbuilder: 'HyperBuilder',
15
+ clawcanvas: 'canvas',
16
+ clawdash: 'dashboard',
17
+ clawdispatch: 'dispatch',
18
+ clawtext: 'ClawText',
19
+ clawtomation: 'automation',
20
+ clawcouncil: 'council',
21
+ openclaw: 'OpenClaw',
22
+ clawhub: 'ClawHub',
23
+ };
24
+ export function normalizeTopicName(name) {
25
+ const lower = name.trim().toLowerCase();
26
+ return KNOWN_NAMES[lower] ?? name.trim();
27
+ }
8
28
  function nowIso() {
9
29
  return new Date().toISOString();
10
30
  }
@@ -50,6 +70,25 @@ export class TopicStore {
50
70
  updatedAt: now,
51
71
  };
52
72
  }
73
+ /**
74
+ * Find an existing topic by name (case-insensitive) or create a new one.
75
+ * Prevents duplicate topics for the same logical concept.
76
+ */
77
+ findOrCreate(agentId, rawName, description, visibility) {
78
+ const name = normalizeTopicName(rawName);
79
+ const existing = this.db.prepare(`
80
+ SELECT * FROM topics
81
+ WHERE agent_id = ?
82
+ AND lower(name) = lower(?)
83
+ AND status != 'closed'
84
+ ORDER BY updated_at DESC
85
+ LIMIT 1
86
+ `).get(agentId, name);
87
+ if (existing) {
88
+ return parseTopicRow(existing);
89
+ }
90
+ return this.create(agentId, name, description, visibility);
91
+ }
53
92
  /**
54
93
  * Touch a topic — update activity tracking.
55
94
  */
@@ -35,7 +35,7 @@ function keystoneScore(msg) {
35
35
  const backtickMatches = text.match(/`[^`]+`/g) || [];
36
36
  score += backtickMatches.length * 0.2;
37
37
  // Agent mentions (known patterns)
38
- const agentMentions = text.match(/\b(agent1|agent2|agent4|agent3|agent5|agent6|director1|director2|director7|specialist2|specialist1)\b/gi) || [];
38
+ const agentMentions = text.match(/\b(alice|bob|agent4|dave|oscar|carol|director1|director2|director7|specialist2|specialist1)\b/gi) || [];
39
39
  score += agentMentions.length * 0.25;
40
40
  // Quoted content
41
41
  const quotedMatches = text.match(/"[^"]{10,}"/g) || [];
@@ -39,7 +39,7 @@ export interface CollectionTrigger {
39
39
  export declare const TRIGGER_REGISTRY_VERSION = "1.0.0";
40
40
  /**
41
41
  * Default trigger registry for standard ACA collections.
42
- * Covers the core ACA offload use case from agent6's spec.
42
+ * Covers the core ACA offload use case from carol's spec.
43
43
  */
44
44
  export declare const TRIGGER_REGISTRY: CollectionTrigger[];
45
45
  /** Backward-compat alias — same reference as TRIGGER_REGISTRY */
@@ -13,7 +13,7 @@ import { createHash } from 'node:crypto';
13
13
  export const TRIGGER_REGISTRY_VERSION = '1.0.0';
14
14
  /**
15
15
  * Default trigger registry for standard ACA collections.
16
- * Covers the core ACA offload use case from agent6's spec.
16
+ * Covers the core ACA offload use case from carol's spec.
17
17
  */
18
18
  export const TRIGGER_REGISTRY = [
19
19
  {
@@ -61,7 +61,7 @@ export const TRIGGER_REGISTRY = [
61
61
  ],
62
62
  maxTokens: 800,
63
63
  maxChunks: 2,
64
- owner: 'agent1',
64
+ owner: 'alice',
65
65
  category: 'operations',
66
66
  description: 'Agent operational procedures: boot sequence, heartbeat, work queue, session startup',
67
67
  },
@@ -98,7 +98,7 @@ export const TRIGGER_REGISTRY = [
98
98
  ],
99
99
  maxTokens: 1500,
100
100
  maxChunks: 4,
101
- owner: 'agent1',
101
+ owner: 'alice',
102
102
  category: 'memory',
103
103
  description: 'Decision history: past choices, previously agreed approaches, recalled context',
104
104
  },
@@ -124,7 +124,7 @@ export const TRIGGER_REGISTRY = [
124
124
  ],
125
125
  maxTokens: 1200,
126
126
  maxChunks: 3,
127
- owner: 'agent1',
127
+ owner: 'alice',
128
128
  category: 'operations',
129
129
  description: 'Agent tooling reference: CLI commands, config paths, deployment procedures, quick reference',
130
130
  },