@agentmemory/agentmemory 0.7.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 (259) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.github/workflows/ci.yml +22 -0
  3. package/.github/workflows/publish.yml +28 -0
  4. package/AGENTS.md +113 -0
  5. package/LICENSE +190 -0
  6. package/README.md +828 -0
  7. package/assets/banner.png +0 -0
  8. package/assets/demo.gif +0 -0
  9. package/assets/demo.mp4 +0 -0
  10. package/benchmark/QUALITY.md +73 -0
  11. package/benchmark/REAL-EMBEDDINGS.md +67 -0
  12. package/benchmark/SCALE.md +110 -0
  13. package/benchmark/dataset.ts +293 -0
  14. package/benchmark/quality-eval.ts +643 -0
  15. package/benchmark/real-embeddings-eval.ts +405 -0
  16. package/benchmark/scale-eval.ts +398 -0
  17. package/dist/cli.d.mts +1 -0
  18. package/dist/cli.mjs +137 -0
  19. package/dist/cli.mjs.map +1 -0
  20. package/dist/docker-compose.yml +14 -0
  21. package/dist/hooks/notification.d.mts +1 -0
  22. package/dist/hooks/notification.mjs +45 -0
  23. package/dist/hooks/notification.mjs.map +1 -0
  24. package/dist/hooks/post-tool-failure.d.mts +1 -0
  25. package/dist/hooks/post-tool-failure.mjs +45 -0
  26. package/dist/hooks/post-tool-failure.mjs.map +1 -0
  27. package/dist/hooks/post-tool-use.d.mts +1 -0
  28. package/dist/hooks/post-tool-use.mjs +53 -0
  29. package/dist/hooks/post-tool-use.mjs.map +1 -0
  30. package/dist/hooks/pre-compact.d.mts +1 -0
  31. package/dist/hooks/pre-compact.mjs +50 -0
  32. package/dist/hooks/pre-compact.mjs.map +1 -0
  33. package/dist/hooks/pre-tool-use.d.mts +1 -0
  34. package/dist/hooks/pre-tool-use.mjs +69 -0
  35. package/dist/hooks/pre-tool-use.mjs.map +1 -0
  36. package/dist/hooks/prompt-submit.d.mts +1 -0
  37. package/dist/hooks/prompt-submit.mjs +40 -0
  38. package/dist/hooks/prompt-submit.mjs.map +1 -0
  39. package/dist/hooks/session-end.d.mts +1 -0
  40. package/dist/hooks/session-end.mjs +61 -0
  41. package/dist/hooks/session-end.mjs.map +1 -0
  42. package/dist/hooks/session-start.d.mts +1 -0
  43. package/dist/hooks/session-start.mjs +42 -0
  44. package/dist/hooks/session-start.mjs.map +1 -0
  45. package/dist/hooks/stop.d.mts +1 -0
  46. package/dist/hooks/stop.mjs +33 -0
  47. package/dist/hooks/stop.mjs.map +1 -0
  48. package/dist/hooks/subagent-start.d.mts +1 -0
  49. package/dist/hooks/subagent-start.mjs +43 -0
  50. package/dist/hooks/subagent-start.mjs.map +1 -0
  51. package/dist/hooks/subagent-stop.d.mts +1 -0
  52. package/dist/hooks/subagent-stop.mjs +45 -0
  53. package/dist/hooks/subagent-stop.mjs.map +1 -0
  54. package/dist/hooks/task-completed.d.mts +1 -0
  55. package/dist/hooks/task-completed.mjs +46 -0
  56. package/dist/hooks/task-completed.mjs.map +1 -0
  57. package/dist/iii-config.yaml +51 -0
  58. package/dist/index.d.mts +2 -0
  59. package/dist/index.mjs +13776 -0
  60. package/dist/index.mjs.map +1 -0
  61. package/dist/src-QxitMPfJ.mjs +13775 -0
  62. package/dist/src-QxitMPfJ.mjs.map +1 -0
  63. package/dist/standalone.d.mts +1 -0
  64. package/dist/standalone.mjs +1155 -0
  65. package/dist/standalone.mjs.map +1 -0
  66. package/dist/transformers-BX_tgxdO.mjs +38684 -0
  67. package/dist/transformers-BX_tgxdO.mjs.map +1 -0
  68. package/dist/transformers-KMm1i9no.mjs +38683 -0
  69. package/dist/transformers-KMm1i9no.mjs.map +1 -0
  70. package/docker-compose.yml +14 -0
  71. package/iii-config.yaml +51 -0
  72. package/package.json +59 -0
  73. package/plugin/.claude-plugin/plugin.json +10 -0
  74. package/plugin/hooks/hooks.json +77 -0
  75. package/plugin/scripts/diagnostics.mjs +551 -0
  76. package/plugin/scripts/notification.mjs +45 -0
  77. package/plugin/scripts/post-tool-failure.mjs +45 -0
  78. package/plugin/scripts/post-tool-use.mjs +53 -0
  79. package/plugin/scripts/pre-compact.mjs +50 -0
  80. package/plugin/scripts/pre-tool-use.mjs +69 -0
  81. package/plugin/scripts/prompt-submit.mjs +40 -0
  82. package/plugin/scripts/session-end.mjs +61 -0
  83. package/plugin/scripts/session-start.mjs +42 -0
  84. package/plugin/scripts/stop.mjs +33 -0
  85. package/plugin/scripts/subagent-start.mjs +43 -0
  86. package/plugin/scripts/subagent-stop.mjs +45 -0
  87. package/plugin/scripts/task-completed.mjs +46 -0
  88. package/plugin/skills/forget/SKILL.md +32 -0
  89. package/plugin/skills/recall/SKILL.md +18 -0
  90. package/plugin/skills/remember/SKILL.md +25 -0
  91. package/plugin/skills/session-history/SKILL.md +17 -0
  92. package/src/auth.ts +12 -0
  93. package/src/cli.ts +159 -0
  94. package/src/config.ts +221 -0
  95. package/src/eval/metrics-store.ts +65 -0
  96. package/src/eval/quality.ts +51 -0
  97. package/src/eval/schemas.ts +124 -0
  98. package/src/eval/self-correct.ts +28 -0
  99. package/src/eval/validator.ts +31 -0
  100. package/src/functions/actions.ts +288 -0
  101. package/src/functions/audit.ts +61 -0
  102. package/src/functions/auto-forget.ts +169 -0
  103. package/src/functions/branch-aware.ts +169 -0
  104. package/src/functions/cascade.ts +80 -0
  105. package/src/functions/checkpoints.ts +209 -0
  106. package/src/functions/claude-bridge.ts +161 -0
  107. package/src/functions/compress.ts +194 -0
  108. package/src/functions/consolidate.ts +212 -0
  109. package/src/functions/consolidation-pipeline.ts +258 -0
  110. package/src/functions/context.ts +169 -0
  111. package/src/functions/crystallize.ts +293 -0
  112. package/src/functions/dedup.ts +57 -0
  113. package/src/functions/diagnostics.ts +785 -0
  114. package/src/functions/enrich.ts +132 -0
  115. package/src/functions/evict.ts +163 -0
  116. package/src/functions/export-import.ts +508 -0
  117. package/src/functions/facets.ts +248 -0
  118. package/src/functions/file-index.ts +106 -0
  119. package/src/functions/flow-compress.ts +214 -0
  120. package/src/functions/frontier.ts +196 -0
  121. package/src/functions/governance.ts +131 -0
  122. package/src/functions/graph-retrieval.ts +277 -0
  123. package/src/functions/graph.ts +275 -0
  124. package/src/functions/leases.ts +216 -0
  125. package/src/functions/lessons.ts +253 -0
  126. package/src/functions/mesh.ts +434 -0
  127. package/src/functions/migrate.ts +165 -0
  128. package/src/functions/observe.ts +144 -0
  129. package/src/functions/obsidian-export.ts +310 -0
  130. package/src/functions/patterns.ts +138 -0
  131. package/src/functions/privacy.ts +39 -0
  132. package/src/functions/profile.ts +155 -0
  133. package/src/functions/query-expansion.ts +186 -0
  134. package/src/functions/relations.ts +237 -0
  135. package/src/functions/remember.ts +162 -0
  136. package/src/functions/retention.ts +235 -0
  137. package/src/functions/routines.ts +289 -0
  138. package/src/functions/search.ts +80 -0
  139. package/src/functions/sentinels.ts +417 -0
  140. package/src/functions/signals.ts +186 -0
  141. package/src/functions/sketches.ts +274 -0
  142. package/src/functions/sliding-window.ts +257 -0
  143. package/src/functions/smart-search.ts +115 -0
  144. package/src/functions/snapshot.ts +219 -0
  145. package/src/functions/summarize.ts +155 -0
  146. package/src/functions/team.ts +147 -0
  147. package/src/functions/temporal-graph.ts +476 -0
  148. package/src/functions/timeline.ts +138 -0
  149. package/src/functions/verify.ts +117 -0
  150. package/src/health/monitor.ts +110 -0
  151. package/src/health/thresholds.ts +73 -0
  152. package/src/hooks/notification.ts +52 -0
  153. package/src/hooks/post-tool-failure.ts +58 -0
  154. package/src/hooks/post-tool-use.ts +62 -0
  155. package/src/hooks/pre-compact.ts +60 -0
  156. package/src/hooks/pre-tool-use.ts +72 -0
  157. package/src/hooks/prompt-submit.ts +46 -0
  158. package/src/hooks/session-end.ts +71 -0
  159. package/src/hooks/session-start.ts +48 -0
  160. package/src/hooks/stop.ts +39 -0
  161. package/src/hooks/subagent-start.ts +49 -0
  162. package/src/hooks/subagent-stop.ts +54 -0
  163. package/src/hooks/task-completed.ts +54 -0
  164. package/src/index.ts +342 -0
  165. package/src/mcp/in-memory-kv.ts +61 -0
  166. package/src/mcp/server.ts +1455 -0
  167. package/src/mcp/standalone.ts +177 -0
  168. package/src/mcp/tools-registry.ts +769 -0
  169. package/src/mcp/transport.ts +91 -0
  170. package/src/prompts/compression.ts +67 -0
  171. package/src/prompts/consolidation.ts +48 -0
  172. package/src/prompts/graph-extraction.ts +35 -0
  173. package/src/prompts/summary.ts +38 -0
  174. package/src/prompts/xml.ts +26 -0
  175. package/src/providers/agent-sdk.ts +34 -0
  176. package/src/providers/anthropic.ts +35 -0
  177. package/src/providers/circuit-breaker.ts +82 -0
  178. package/src/providers/embedding/cohere.ts +46 -0
  179. package/src/providers/embedding/gemini.ts +54 -0
  180. package/src/providers/embedding/index.ts +39 -0
  181. package/src/providers/embedding/local.ts +52 -0
  182. package/src/providers/embedding/openai.ts +45 -0
  183. package/src/providers/embedding/openrouter.ts +51 -0
  184. package/src/providers/embedding/voyage.ts +46 -0
  185. package/src/providers/fallback-chain.ts +31 -0
  186. package/src/providers/index.ts +84 -0
  187. package/src/providers/openrouter.ts +71 -0
  188. package/src/providers/resilient.ts +37 -0
  189. package/src/state/hybrid-search.ts +295 -0
  190. package/src/state/index-persistence.ts +63 -0
  191. package/src/state/keyed-mutex.ts +18 -0
  192. package/src/state/kv.ts +33 -0
  193. package/src/state/schema.ts +71 -0
  194. package/src/state/search-index.ts +245 -0
  195. package/src/state/stemmer.ts +104 -0
  196. package/src/state/synonyms.ts +63 -0
  197. package/src/state/vector-index.ts +130 -0
  198. package/src/telemetry/setup.ts +116 -0
  199. package/src/triggers/api.ts +1904 -0
  200. package/src/triggers/events.ts +71 -0
  201. package/src/types.ts +769 -0
  202. package/src/version.ts +1 -0
  203. package/src/viewer/index.html +2497 -0
  204. package/src/viewer/server.ts +207 -0
  205. package/src/xenova.d.ts +3 -0
  206. package/test/actions.test.ts +490 -0
  207. package/test/audit.test.ts +108 -0
  208. package/test/auto-forget.test.ts +188 -0
  209. package/test/cascade.test.ts +277 -0
  210. package/test/checkpoints.test.ts +493 -0
  211. package/test/circuit-breaker.test.ts +107 -0
  212. package/test/claude-bridge.test.ts +178 -0
  213. package/test/confidence.test.ts +247 -0
  214. package/test/consistency.test.ts +61 -0
  215. package/test/consolidation-pipeline.test.ts +251 -0
  216. package/test/crystallize.test.ts +521 -0
  217. package/test/diagnostics.test.ts +638 -0
  218. package/test/embedding-provider.test.ts +49 -0
  219. package/test/enrich.test.ts +209 -0
  220. package/test/eval.test.ts +300 -0
  221. package/test/export-import.test.ts +251 -0
  222. package/test/facets.test.ts +448 -0
  223. package/test/fallback-chain.test.ts +93 -0
  224. package/test/frontier.test.ts +485 -0
  225. package/test/governance.test.ts +147 -0
  226. package/test/graph-retrieval.test.ts +186 -0
  227. package/test/graph.test.ts +160 -0
  228. package/test/helpers/mocks.ts +40 -0
  229. package/test/hybrid-search.test.ts +145 -0
  230. package/test/index-persistence.test.ts +124 -0
  231. package/test/integration.test.ts +265 -0
  232. package/test/leases.test.ts +399 -0
  233. package/test/mcp-prompts.test.ts +218 -0
  234. package/test/mcp-resources.test.ts +286 -0
  235. package/test/mcp-standalone.test.ts +113 -0
  236. package/test/mesh.test.ts +700 -0
  237. package/test/privacy.test.ts +87 -0
  238. package/test/profile.test.ts +161 -0
  239. package/test/query-expansion.test.ts +154 -0
  240. package/test/relations.test.ts +198 -0
  241. package/test/retention.test.ts +245 -0
  242. package/test/routines.test.ts +497 -0
  243. package/test/schema-fingerprint.test.ts +81 -0
  244. package/test/schema.test.ts +42 -0
  245. package/test/search-index.test.ts +128 -0
  246. package/test/sentinels.test.ts +626 -0
  247. package/test/signals.test.ts +410 -0
  248. package/test/sketches.test.ts +549 -0
  249. package/test/sliding-window.test.ts +199 -0
  250. package/test/smart-search.test.ts +169 -0
  251. package/test/snapshot.test.ts +165 -0
  252. package/test/team.test.ts +156 -0
  253. package/test/temporal-graph.test.ts +378 -0
  254. package/test/timeline.test.ts +148 -0
  255. package/test/vector-index.test.ts +79 -0
  256. package/test/verify.test.ts +209 -0
  257. package/test/xml.test.ts +65 -0
  258. package/tsconfig.json +22 -0
  259. package/tsdown.config.ts +62 -0
@@ -0,0 +1,80 @@
1
+ import type { ISdk } from "iii-sdk";
2
+ import type { StateKV } from "../state/kv.js";
3
+ import { KV } from "../state/schema.js";
4
+ import type { Memory, GraphNode, GraphEdge } from "../types.js";
5
+
6
+ export function registerCascadeFunction(sdk: ISdk, kv: StateKV): void {
7
+ sdk.registerFunction(
8
+ { id: "mem::cascade-update" },
9
+ async (data: { supersededMemoryId: string }) => {
10
+ if (!data.supersededMemoryId || typeof data.supersededMemoryId !== "string") {
11
+ return { success: false, error: "supersededMemoryId is required" };
12
+ }
13
+
14
+ const superseded = await kv.get<Memory>(KV.memories, data.supersededMemoryId);
15
+ if (!superseded) {
16
+ return { success: false, error: "superseded memory not found" };
17
+ }
18
+
19
+ let flaggedNodes = 0;
20
+ let flaggedEdges = 0;
21
+ let flaggedMemories = 0;
22
+
23
+ const obsIds = new Set(superseded.sourceObservationIds || []);
24
+
25
+ if (obsIds.size > 0) {
26
+ const now = new Date().toISOString();
27
+ const nodes = await kv.list<GraphNode>(KV.graphNodes);
28
+ for (const node of nodes) {
29
+ if (node.stale) continue;
30
+ const overlap = (node.sourceObservationIds ?? []).some((id) => obsIds.has(id));
31
+ if (overlap) {
32
+ node.stale = true;
33
+ node.updatedAt = now;
34
+ await kv.set(KV.graphNodes, node.id, node);
35
+ flaggedNodes++;
36
+ }
37
+ }
38
+
39
+ const edges = await kv.list<GraphEdge>(KV.graphEdges);
40
+ for (const edge of edges) {
41
+ if (edge.stale) continue;
42
+ const overlap = (edge.sourceObservationIds ?? []).some((id) => obsIds.has(id));
43
+ if (overlap) {
44
+ edge.stale = true;
45
+ await kv.set(KV.graphEdges, edge.id, edge);
46
+ flaggedEdges++;
47
+ }
48
+ }
49
+ }
50
+
51
+ const supersededConcepts = new Set(
52
+ (superseded.concepts ?? []).map((c) => c.toLowerCase()),
53
+ );
54
+ if (supersededConcepts.size >= 2) {
55
+ const allMemories = await kv.list<Memory>(KV.memories);
56
+ for (const mem of allMemories) {
57
+ if (mem.id === data.supersededMemoryId) continue;
58
+ if (!mem.isLatest) continue;
59
+
60
+ const sharedCount = (mem.concepts ?? []).filter((c) =>
61
+ supersededConcepts.has(c.toLowerCase()),
62
+ ).length;
63
+ if (sharedCount >= 2) {
64
+ flaggedMemories++;
65
+ }
66
+ }
67
+ }
68
+
69
+ return {
70
+ success: true,
71
+ flagged: {
72
+ nodes: flaggedNodes,
73
+ edges: flaggedEdges,
74
+ siblingMemories: flaggedMemories,
75
+ },
76
+ total: flaggedNodes + flaggedEdges + flaggedMemories,
77
+ };
78
+ },
79
+ );
80
+ }
@@ -0,0 +1,209 @@
1
+ import type { ISdk } from "iii-sdk";
2
+ import type { StateKV } from "../state/kv.js";
3
+ import { KV, generateId } from "../state/schema.js";
4
+ import { withKeyedLock } from "../state/keyed-mutex.js";
5
+ import type { Action, ActionEdge, Checkpoint } from "../types.js";
6
+
7
+ export function registerCheckpointsFunction(sdk: ISdk, kv: StateKV): void {
8
+ sdk.registerFunction(
9
+ { id: "mem::checkpoint-create" },
10
+ async (data: {
11
+ name: string;
12
+ description?: string;
13
+ type?: Checkpoint["type"];
14
+ linkedActionIds?: string[];
15
+ expiresInMs?: number;
16
+ }) => {
17
+ if (!data.name) {
18
+ return { success: false, error: "name is required" };
19
+ }
20
+
21
+ const validTypes: Checkpoint["type"][] = ["ci", "approval", "deploy", "external", "timer"];
22
+ if (data.type && !validTypes.includes(data.type)) {
23
+ return { success: false, error: `invalid checkpoint type: ${data.type}. Must be one of: ${validTypes.join(", ")}` };
24
+ }
25
+
26
+ const now = new Date();
27
+ const checkpoint: Checkpoint = {
28
+ id: generateId("ckpt"),
29
+ name: data.name.trim(),
30
+ description: (data.description || "").trim(),
31
+ status: "pending",
32
+ type: data.type || "external",
33
+ createdAt: now.toISOString(),
34
+ linkedActionIds: data.linkedActionIds || [],
35
+ expiresAt: data.expiresInMs
36
+ ? new Date(now.getTime() + data.expiresInMs).toISOString()
37
+ : undefined,
38
+ };
39
+
40
+ if (data.linkedActionIds && data.linkedActionIds.length > 0) {
41
+ for (const actionId of data.linkedActionIds) {
42
+ const action = await kv.get<Action>(KV.actions, actionId);
43
+ if (!action) {
44
+ return { success: false, error: `linked action not found: ${actionId}` };
45
+ }
46
+ }
47
+ }
48
+
49
+ await kv.set(KV.checkpoints, checkpoint.id, checkpoint);
50
+
51
+ if (data.linkedActionIds && data.linkedActionIds.length > 0) {
52
+ for (const actionId of data.linkedActionIds) {
53
+ const edge: ActionEdge = {
54
+ id: generateId("ae"),
55
+ type: "gated_by",
56
+ sourceActionId: actionId,
57
+ targetActionId: checkpoint.id,
58
+ createdAt: now.toISOString(),
59
+ };
60
+ await kv.set(KV.actionEdges, edge.id, edge);
61
+
62
+ const action = await kv.get<Action>(KV.actions, actionId);
63
+ if (action && action.status === "pending") {
64
+ action.status = "blocked";
65
+ action.updatedAt = now.toISOString();
66
+ await kv.set(KV.actions, action.id, action);
67
+ }
68
+ }
69
+ }
70
+
71
+ return { success: true, checkpoint };
72
+ },
73
+ );
74
+
75
+ sdk.registerFunction(
76
+ { id: "mem::checkpoint-resolve" },
77
+ async (data: {
78
+ checkpointId: string;
79
+ status: "passed" | "failed";
80
+ resolvedBy?: string;
81
+ result?: unknown;
82
+ }) => {
83
+ if (!data.checkpointId || !data.status) {
84
+ return {
85
+ success: false,
86
+ error: "checkpointId and status are required",
87
+ };
88
+ }
89
+
90
+ return withKeyedLock(
91
+ `mem:checkpoint:${data.checkpointId}`,
92
+ async () => {
93
+ const checkpoint = await kv.get<Checkpoint>(
94
+ KV.checkpoints,
95
+ data.checkpointId,
96
+ );
97
+ if (!checkpoint) {
98
+ return { success: false, error: "checkpoint not found" };
99
+ }
100
+ if (checkpoint.status !== "pending") {
101
+ return {
102
+ success: false,
103
+ error: `checkpoint already ${checkpoint.status}`,
104
+ };
105
+ }
106
+
107
+ checkpoint.status = data.status;
108
+ checkpoint.resolvedAt = new Date().toISOString();
109
+ checkpoint.resolvedBy = data.resolvedBy;
110
+ checkpoint.result = data.result;
111
+
112
+ await kv.set(KV.checkpoints, checkpoint.id, checkpoint);
113
+
114
+ let unblockedCount = 0;
115
+ if (data.status === "passed" && checkpoint.linkedActionIds.length > 0) {
116
+ const allEdges = await kv.list<ActionEdge>(KV.actionEdges);
117
+ const allCheckpoints = await kv.list<Checkpoint>(KV.checkpoints);
118
+ const cpMap = new Map(allCheckpoints.map((c) => [c.id, c]));
119
+
120
+ for (const actionId of checkpoint.linkedActionIds) {
121
+ await withKeyedLock(`mem:action:${actionId}`, async () => {
122
+ const action = await kv.get<Action>(KV.actions, actionId);
123
+ if (action && action.status === "blocked") {
124
+ const gates = allEdges.filter(
125
+ (e) => e.sourceActionId === actionId && e.type === "gated_by",
126
+ );
127
+ const allGatesPassed = gates.every((g) => {
128
+ const cp = cpMap.get(g.targetActionId);
129
+ return cp && cp.status === "passed";
130
+ });
131
+ const requires = allEdges.filter(
132
+ (e) => e.sourceActionId === actionId && e.type === "requires",
133
+ );
134
+ const allActions = await kv.list<Action>(KV.actions);
135
+ const actionMap = new Map(allActions.map((a) => [a.id, a]));
136
+ const allRequiresMet = requires.every((r) => {
137
+ const dep = actionMap.get(r.targetActionId);
138
+ return dep && dep.status === "done";
139
+ });
140
+ if (allGatesPassed && allRequiresMet) {
141
+ action.status = "pending";
142
+ action.updatedAt = new Date().toISOString();
143
+ await kv.set(KV.actions, action.id, action);
144
+ unblockedCount++;
145
+ }
146
+ }
147
+ });
148
+ }
149
+ }
150
+
151
+ return { success: true, checkpoint, unblockedCount };
152
+ },
153
+ );
154
+ },
155
+ );
156
+
157
+ sdk.registerFunction(
158
+ { id: "mem::checkpoint-list" },
159
+ async (data: { status?: string; type?: string }) => {
160
+ let checkpoints = await kv.list<Checkpoint>(KV.checkpoints);
161
+
162
+ if (data.status) {
163
+ checkpoints = checkpoints.filter((c) => c.status === data.status);
164
+ }
165
+ if (data.type) {
166
+ checkpoints = checkpoints.filter((c) => c.type === data.type);
167
+ }
168
+
169
+ checkpoints.sort(
170
+ (a, b) =>
171
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
172
+ );
173
+
174
+ return { success: true, checkpoints };
175
+ },
176
+ );
177
+
178
+ sdk.registerFunction(
179
+ { id: "mem::checkpoint-expire" },
180
+ async () => {
181
+ const checkpoints = await kv.list<Checkpoint>(KV.checkpoints);
182
+ const now = Date.now();
183
+ let expired = 0;
184
+
185
+ for (const cp of checkpoints) {
186
+ if (
187
+ cp.status === "pending" &&
188
+ cp.expiresAt &&
189
+ new Date(cp.expiresAt).getTime() <= now
190
+ ) {
191
+ const didExpire = await withKeyedLock(
192
+ `mem:checkpoint:${cp.id}`,
193
+ async () => {
194
+ const fresh = await kv.get<Checkpoint>(KV.checkpoints, cp.id);
195
+ if (!fresh || fresh.status !== "pending") return false;
196
+ fresh.status = "expired";
197
+ fresh.resolvedAt = new Date().toISOString();
198
+ await kv.set(KV.checkpoints, fresh.id, fresh);
199
+ return true;
200
+ },
201
+ );
202
+ if (didExpire) expired++;
203
+ }
204
+ }
205
+
206
+ return { success: true, expired };
207
+ },
208
+ );
209
+ }
@@ -0,0 +1,161 @@
1
+ import type { ISdk } from "iii-sdk";
2
+ import { getContext } from "iii-sdk";
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
4
+ import { dirname } from "node:path";
5
+ import type { Memory, ClaudeBridgeConfig } from "../types.js";
6
+ import { KV } from "../state/schema.js";
7
+ import type { StateKV } from "../state/kv.js";
8
+ import { recordAudit } from "./audit.js";
9
+
10
+ function parseMemoryMd(content: string): {
11
+ sections: Map<string, string>;
12
+ raw: string;
13
+ } {
14
+ const sections = new Map<string, string>();
15
+ let currentSection = "";
16
+ let currentContent: string[] = [];
17
+
18
+ for (const line of content.split("\n")) {
19
+ if (line.startsWith("## ")) {
20
+ if (currentSection) {
21
+ sections.set(currentSection, currentContent.join("\n").trim());
22
+ }
23
+ currentSection = line.slice(3).trim();
24
+ currentContent = [];
25
+ } else {
26
+ currentContent.push(line);
27
+ }
28
+ }
29
+ if (currentSection) {
30
+ sections.set(currentSection, currentContent.join("\n").trim());
31
+ }
32
+
33
+ return { sections, raw: content };
34
+ }
35
+
36
+ function serializeToMemoryMd(
37
+ memories: Memory[],
38
+ projectSummary: string,
39
+ lineBudget: number,
40
+ ): string {
41
+ const lines: string[] = [];
42
+ lines.push("# Agent Memory (auto-synced by agentmemory)");
43
+ lines.push("");
44
+
45
+ if (projectSummary) {
46
+ lines.push("## Project Summary");
47
+ lines.push(projectSummary);
48
+ lines.push("");
49
+ }
50
+
51
+ lines.push("## Key Memories");
52
+ lines.push("");
53
+
54
+ const sorted = [...memories]
55
+ .filter((m) => m.isLatest)
56
+ .sort((a, b) => b.strength - a.strength);
57
+
58
+ for (const mem of sorted) {
59
+ if (lines.length >= lineBudget - 2) break;
60
+ lines.push(`### ${mem.title}`);
61
+ const contentLines = mem.content.split("\n");
62
+ for (const cl of contentLines) {
63
+ if (lines.length >= lineBudget - 1) break;
64
+ lines.push(cl);
65
+ }
66
+ lines.push("");
67
+ }
68
+
69
+ return lines.slice(0, lineBudget).join("\n");
70
+ }
71
+
72
+ export function registerClaudeBridgeFunction(
73
+ sdk: ISdk,
74
+ kv: StateKV,
75
+ config: ClaudeBridgeConfig,
76
+ ): void {
77
+ sdk.registerFunction(
78
+ { id: "mem::claude-bridge-read" },
79
+ async () => {
80
+ const ctx = getContext();
81
+ if (!config.enabled || !config.memoryFilePath) {
82
+ return { success: false, error: "Claude bridge not configured" };
83
+ }
84
+
85
+ try {
86
+ if (!existsSync(config.memoryFilePath)) {
87
+ return { success: true, content: "", parsed: false };
88
+ }
89
+ const content = readFileSync(config.memoryFilePath, "utf-8");
90
+ const { sections } = parseMemoryMd(content);
91
+
92
+ await kv.set(KV.claudeBridge, "last-read", {
93
+ timestamp: new Date().toISOString(),
94
+ sections: Object.fromEntries(sections),
95
+ lineCount: content.split("\n").length,
96
+ });
97
+
98
+ ctx.logger.info("Claude bridge: read MEMORY.md", {
99
+ path: config.memoryFilePath,
100
+ lines: content.split("\n").length,
101
+ });
102
+ return { success: true, content, sections: Object.fromEntries(sections) };
103
+ } catch (err) {
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ ctx.logger.error("Claude bridge read failed", { error: msg });
106
+ return { success: false, error: msg };
107
+ }
108
+ },
109
+ );
110
+
111
+ sdk.registerFunction(
112
+ { id: "mem::claude-bridge-sync" },
113
+ async () => {
114
+ const ctx = getContext();
115
+ if (!config.enabled || !config.memoryFilePath) {
116
+ return { success: false, error: "Claude bridge not configured" };
117
+ }
118
+
119
+ try {
120
+ const memories = await kv.list<Memory>(KV.memories);
121
+ const latestMemories = memories.filter((m) => m.isLatest);
122
+
123
+ let projectSummary = "";
124
+ if (config.projectPath) {
125
+ const profile = await kv
126
+ .get<{ summary?: string }>(KV.profiles, config.projectPath)
127
+ .catch(() => null);
128
+ projectSummary = profile?.summary || "";
129
+ }
130
+
131
+ const md = serializeToMemoryMd(
132
+ latestMemories,
133
+ projectSummary,
134
+ config.lineBudget,
135
+ );
136
+
137
+ const dir = dirname(config.memoryFilePath);
138
+ if (!existsSync(dir)) {
139
+ mkdirSync(dir, { recursive: true });
140
+ }
141
+ writeFileSync(config.memoryFilePath, md, "utf-8");
142
+
143
+ await recordAudit(kv, "export", "mem::claude-bridge-sync", [], {
144
+ path: config.memoryFilePath,
145
+ memoryCount: latestMemories.length,
146
+ lines: md.split("\n").length,
147
+ });
148
+
149
+ ctx.logger.info("Claude bridge: synced to MEMORY.md", {
150
+ path: config.memoryFilePath,
151
+ memories: latestMemories.length,
152
+ });
153
+ return { success: true, path: config.memoryFilePath, lines: md.split("\n").length };
154
+ } catch (err) {
155
+ const msg = err instanceof Error ? err.message : String(err);
156
+ ctx.logger.error("Claude bridge sync failed", { error: msg });
157
+ return { success: false, error: msg };
158
+ }
159
+ },
160
+ );
161
+ }
@@ -0,0 +1,194 @@
1
+ import type { ISdk } from "iii-sdk";
2
+ import { getContext } from "iii-sdk";
3
+ import type {
4
+ RawObservation,
5
+ CompressedObservation,
6
+ ObservationType,
7
+ MemoryProvider,
8
+ } from "../types.js";
9
+ import { KV, STREAM } from "../state/schema.js";
10
+ import { StateKV } from "../state/kv.js";
11
+ import {
12
+ COMPRESSION_SYSTEM,
13
+ buildCompressionPrompt,
14
+ } from "../prompts/compression.js";
15
+ import { getXmlTag, getXmlChildren } from "../prompts/xml.js";
16
+ import { getSearchIndex } from "./search.js";
17
+ import { CompressOutputSchema } from "../eval/schemas.js";
18
+ import { validateOutput } from "../eval/validator.js";
19
+ import { scoreCompression } from "../eval/quality.js";
20
+ import { compressWithRetry } from "../eval/self-correct.js";
21
+ import type { MetricsStore } from "../eval/metrics-store.js";
22
+
23
+ const VALID_TYPES = new Set<string>([
24
+ "file_read",
25
+ "file_write",
26
+ "file_edit",
27
+ "command_run",
28
+ "search",
29
+ "web_fetch",
30
+ "conversation",
31
+ "error",
32
+ "decision",
33
+ "discovery",
34
+ "subagent",
35
+ "notification",
36
+ "task",
37
+ "other",
38
+ ]);
39
+
40
+ function parseCompressionXml(
41
+ xml: string,
42
+ ): Omit<CompressedObservation, "id" | "sessionId" | "timestamp"> | null {
43
+ const rawType = getXmlTag(xml, "type");
44
+ const title = getXmlTag(xml, "title");
45
+ if (!rawType || !title) return null;
46
+ const type = VALID_TYPES.has(rawType) ? rawType : "other";
47
+
48
+ return {
49
+ type: type as ObservationType,
50
+ title,
51
+ subtitle: getXmlTag(xml, "subtitle") || undefined,
52
+ facts: getXmlChildren(xml, "facts", "fact"),
53
+ narrative: getXmlTag(xml, "narrative"),
54
+ concepts: getXmlChildren(xml, "concepts", "concept"),
55
+ files: getXmlChildren(xml, "files", "file"),
56
+ importance: Math.max(
57
+ 1,
58
+ Math.min(10, parseInt(getXmlTag(xml, "importance") || "5", 10) || 5),
59
+ ),
60
+ };
61
+ }
62
+
63
+ export function registerCompressFunction(
64
+ sdk: ISdk,
65
+ kv: StateKV,
66
+ provider: MemoryProvider,
67
+ metricsStore?: MetricsStore,
68
+ ): void {
69
+ sdk.registerFunction(
70
+ {
71
+ id: "mem::compress",
72
+ description: "Compress a raw observation using LLM",
73
+ },
74
+ async (data: {
75
+ observationId: string;
76
+ sessionId: string;
77
+ raw: RawObservation;
78
+ }) => {
79
+ const ctx = getContext();
80
+ const startMs = Date.now();
81
+ const prompt = buildCompressionPrompt({
82
+ hookType: data.raw.hookType,
83
+ toolName: data.raw.toolName,
84
+ toolInput: data.raw.toolInput,
85
+ toolOutput: data.raw.toolOutput,
86
+ userPrompt: data.raw.userPrompt,
87
+ timestamp: data.raw.timestamp,
88
+ });
89
+
90
+ try {
91
+ const validator = (response: string) => {
92
+ const parsed = parseCompressionXml(response);
93
+ if (!parsed) return { valid: false, errors: ["xml_parse_failed"] };
94
+ const result = validateOutput(
95
+ CompressOutputSchema,
96
+ parsed,
97
+ "mem::compress",
98
+ );
99
+ return result.valid
100
+ ? { valid: true }
101
+ : { valid: false, errors: result.result.errors };
102
+ };
103
+
104
+ const { response, retried } = await compressWithRetry(
105
+ provider,
106
+ COMPRESSION_SYSTEM,
107
+ prompt,
108
+ validator,
109
+ 1,
110
+ );
111
+
112
+ const parsed = parseCompressionXml(response);
113
+ if (!parsed) {
114
+ const latencyMs = Date.now() - startMs;
115
+ if (metricsStore) {
116
+ await metricsStore.record("mem::compress", latencyMs, false);
117
+ }
118
+ ctx.logger.warn("Failed to parse compression XML", {
119
+ obsId: data.observationId,
120
+ retried,
121
+ });
122
+ return { success: false, error: "parse_failed" };
123
+ }
124
+
125
+ const qualityScore = scoreCompression(parsed);
126
+
127
+ const compressed: CompressedObservation = {
128
+ id: data.observationId,
129
+ sessionId: data.sessionId,
130
+ timestamp: data.raw.timestamp,
131
+ ...parsed,
132
+ confidence: qualityScore / 100,
133
+ };
134
+
135
+ await kv.set(
136
+ KV.observations(data.sessionId),
137
+ data.observationId,
138
+ compressed,
139
+ );
140
+
141
+ getSearchIndex().add(compressed);
142
+
143
+ sdk.triggerVoid("stream::set", {
144
+ stream_name: STREAM.name,
145
+ group_id: STREAM.group(data.sessionId),
146
+ item_id: data.observationId,
147
+ data: { type: "compressed", observation: compressed },
148
+ });
149
+
150
+ sdk.triggerVoid("stream::set", {
151
+ stream_name: STREAM.name,
152
+ group_id: STREAM.viewerGroup,
153
+ item_id: data.observationId,
154
+ data: {
155
+ type: "compressed",
156
+ observation: compressed,
157
+ sessionId: data.sessionId,
158
+ },
159
+ });
160
+
161
+ const latencyMs = Date.now() - startMs;
162
+ if (metricsStore) {
163
+ await metricsStore.record(
164
+ "mem::compress",
165
+ latencyMs,
166
+ true,
167
+ qualityScore,
168
+ );
169
+ }
170
+
171
+ ctx.logger.info("Observation compressed", {
172
+ obsId: data.observationId,
173
+ type: compressed.type,
174
+ importance: compressed.importance,
175
+ qualityScore,
176
+ retried,
177
+ });
178
+
179
+ return { success: true, compressed, qualityScore };
180
+ } catch (err) {
181
+ const msg = err instanceof Error ? err.message : String(err);
182
+ const latencyMs = Date.now() - startMs;
183
+ if (metricsStore) {
184
+ await metricsStore.record("mem::compress", latencyMs, false);
185
+ }
186
+ ctx.logger.error("Compression failed", {
187
+ obsId: data.observationId,
188
+ error: msg,
189
+ });
190
+ return { success: false, error: "compression_failed" };
191
+ }
192
+ },
193
+ );
194
+ }