@agentmemory/agentmemory 0.7.5 → 0.7.6

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.
@@ -1182,12 +1182,66 @@ function extractEntitiesFromQuery(query) {
1182
1182
  return [...new Set(entities)];
1183
1183
  }
1184
1184
 
1185
+ //#endregion
1186
+ //#region src/state/reranker.ts
1187
+ let pipeline = null;
1188
+ let pipelineLoading = null;
1189
+ let pipelineUnavailable = false;
1190
+ async function loadPipeline() {
1191
+ if (pipelineUnavailable) return null;
1192
+ if (pipeline) return pipeline;
1193
+ if (pipelineLoading) return pipelineLoading;
1194
+ pipelineLoading = (async () => {
1195
+ try {
1196
+ const { pipeline: createPipeline } = await import("./transformers-KMm1i9no.mjs");
1197
+ pipeline = await createPipeline("text-classification", "Xenova/ms-marco-MiniLM-L-6-v2", { quantized: true });
1198
+ return pipeline;
1199
+ } catch {
1200
+ pipeline = null;
1201
+ pipelineUnavailable = true;
1202
+ return null;
1203
+ } finally {
1204
+ pipelineLoading = null;
1205
+ }
1206
+ })();
1207
+ return pipelineLoading;
1208
+ }
1209
+ async function rerank(query, results, topK = 20) {
1210
+ if (results.length <= 1) return results;
1211
+ const reranker = await loadPipeline();
1212
+ if (!reranker) return results;
1213
+ const pairs = results.slice(0, Math.min(results.length, topK)).map((r) => ({
1214
+ text: `${query} [SEP] ${r.observation.title || ""} ${r.observation.narrative || ""}`.slice(0, 512),
1215
+ result: r
1216
+ }));
1217
+ const scores = [];
1218
+ for (const pair of pairs) try {
1219
+ const output = await reranker(pair.text);
1220
+ const score = Array.isArray(output) ? output[0]?.score ?? 0 : 0;
1221
+ scores.push({
1222
+ result: pair.result,
1223
+ rerankScore: score
1224
+ });
1225
+ } catch {
1226
+ scores.push({
1227
+ result: pair.result,
1228
+ rerankScore: pair.result.combinedScore
1229
+ });
1230
+ }
1231
+ scores.sort((a, b) => b.rerankScore - a.rerankScore);
1232
+ return scores.map((s, i) => ({
1233
+ ...s.result,
1234
+ combinedScore: s.rerankScore,
1235
+ rerankPosition: i + 1
1236
+ }));
1237
+ }
1238
+
1185
1239
  //#endregion
1186
1240
  //#region src/state/hybrid-search.ts
1187
1241
  const RRF_K = 60;
1188
1242
  var HybridSearch = class {
1189
1243
  graphRetrieval;
1190
- constructor(bm25, vector, embeddingProvider, kv, bm25Weight = .4, vectorWeight = .6, graphWeight = .3) {
1244
+ constructor(bm25, vector, embeddingProvider, kv, bm25Weight = .4, vectorWeight = .6, graphWeight = .3, rerankEnabled = process.env.RERANK_ENABLED === "true") {
1191
1245
  this.bm25 = bm25;
1192
1246
  this.vector = vector;
1193
1247
  this.embeddingProvider = embeddingProvider;
@@ -1195,6 +1249,7 @@ var HybridSearch = class {
1195
1249
  this.bm25Weight = bm25Weight;
1196
1250
  this.vectorWeight = vectorWeight;
1197
1251
  this.graphWeight = graphWeight;
1252
+ this.rerankEnabled = rerankEnabled;
1198
1253
  this.graphRetrieval = new GraphRetrieval(kv);
1199
1254
  }
1200
1255
  async search(query, limit = 20) {
@@ -1298,8 +1353,18 @@ var HybridSearch = class {
1298
1353
  combinedScore: effectiveBm25W * (1 / (RRF_K + s.bm25Rank)) + effectiveVectorW * (1 / (RRF_K + s.vectorRank)) + effectiveGraphW * (1 / (RRF_K + s.graphRank))
1299
1354
  }));
1300
1355
  combined.sort((a, b) => b.combinedScore - a.combinedScore);
1301
- const diversified = this.diversifyBySession(combined, limit);
1302
- return this.enrichResults(diversified, limit);
1356
+ const retrievalDepth = Math.max(limit, 20);
1357
+ const rerankWindow = 20;
1358
+ const diversified = this.diversifyBySession(combined, retrievalDepth);
1359
+ const enriched = await this.enrichResults(diversified, retrievalDepth);
1360
+ if (this.rerankEnabled && enriched.length > 1) try {
1361
+ const head = enriched.slice(0, rerankWindow);
1362
+ const tail = enriched.slice(rerankWindow);
1363
+ return (await rerank(query, head, rerankWindow)).concat(tail).slice(0, limit);
1364
+ } catch {
1365
+ return enriched.slice(0, limit);
1366
+ }
1367
+ return enriched.slice(0, limit);
1303
1368
  }
1304
1369
  diversifyBySession(results, limit, maxPerSession = 3) {
1305
1370
  const selected = [];
@@ -2413,7 +2478,7 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
2413
2478
 
2414
2479
  //#endregion
2415
2480
  //#region src/functions/context.ts
2416
- function estimateTokens(text) {
2481
+ function estimateTokens$1(text) {
2417
2482
  return Math.ceil(text.length / 3);
2418
2483
  }
2419
2484
  function escapeXmlAttr(s) {
@@ -2439,7 +2504,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
2439
2504
  blocks.push({
2440
2505
  type: "memory",
2441
2506
  content: profileContent,
2442
- tokens: estimateTokens(profileContent),
2507
+ tokens: estimateTokens$1(profileContent),
2443
2508
  recency: new Date(profile.updatedAt).getTime()
2444
2509
  });
2445
2510
  }
@@ -2454,7 +2519,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
2454
2519
  blocks.push({
2455
2520
  type: "summary",
2456
2521
  content,
2457
- tokens: estimateTokens(content),
2522
+ tokens: estimateTokens$1(content),
2458
2523
  recency: new Date(summary.createdAt).getTime()
2459
2524
  });
2460
2525
  } else sessionsNeedingObs.push(i);
@@ -2469,7 +2534,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
2469
2534
  blocks.push({
2470
2535
  type: "observation",
2471
2536
  content,
2472
- tokens: estimateTokens(content),
2537
+ tokens: estimateTokens$1(content),
2473
2538
  recency: new Date(sessions[i].startedAt).getTime()
2474
2539
  });
2475
2540
  }
@@ -2479,7 +2544,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
2479
2544
  const selected = [];
2480
2545
  const header = `<agentmemory-context project="${escapeXmlAttr(data.project)}">`;
2481
2546
  const footer = `</agentmemory-context>`;
2482
- usedTokens += estimateTokens(header) + estimateTokens(footer);
2547
+ usedTokens += estimateTokens$1(header) + estimateTokens$1(footer);
2483
2548
  for (const block of blocks) {
2484
2549
  if (usedTokens + block.tokens > budget) break;
2485
2550
  selected.push(block.content);
@@ -3739,7 +3804,7 @@ function registerAutoForgetFunction(sdk, kv) {
3739
3804
 
3740
3805
  //#endregion
3741
3806
  //#region src/version.ts
3742
- const VERSION = "0.7.5";
3807
+ const VERSION = "0.7.6";
3743
3808
 
3744
3809
  //#endregion
3745
3810
  //#region src/functions/export-import.ts
@@ -3841,7 +3906,8 @@ function registerExportImportFunction(sdk, kv) {
3841
3906
  "0.7.2",
3842
3907
  "0.7.3",
3843
3908
  "0.7.4",
3844
- "0.7.5"
3909
+ "0.7.5",
3910
+ "0.7.6"
3845
3911
  ]).has(importData.version)) return {
3846
3912
  success: false,
3847
3913
  error: `Unsupported export version: ${importData.version}`
@@ -8743,6 +8809,407 @@ function registerReflectFunctions(sdk, kv, provider) {
8743
8809
  });
8744
8810
  }
8745
8811
 
8812
+ //#endregion
8813
+ //#region src/functions/working-memory.ts
8814
+ const CORE_SCOPE = "mem:core-memory";
8815
+ function estimateTokens(text) {
8816
+ return Math.ceil(text.length / 3);
8817
+ }
8818
+ function scoreEntry(entry, now) {
8819
+ const recencyScore = 1 / (1 + (now - new Date(entry.lastAccessedAt).getTime()) / (1e3 * 60 * 60 * 24) * .1);
8820
+ const accessScore = Math.log2(entry.accessCount + 1) / 10;
8821
+ return entry.importance / 10 * .5 + recencyScore * .3 + accessScore * .2;
8822
+ }
8823
+ function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
8824
+ sdk.registerFunction({
8825
+ id: "mem::core-add",
8826
+ description: "Add a fact to core memory (always included in context)"
8827
+ }, async (data) => {
8828
+ if (!data?.content?.trim()) return {
8829
+ success: false,
8830
+ error: "content is required"
8831
+ };
8832
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8833
+ const entry = {
8834
+ id: generateId("core"),
8835
+ content: data.content.trim(),
8836
+ importance: Math.min(10, Math.max(1, data.importance ?? 7)),
8837
+ pinned: data.pinned ?? false,
8838
+ accessCount: 0,
8839
+ lastAccessedAt: now,
8840
+ createdAt: now
8841
+ };
8842
+ await kv.set(CORE_SCOPE, entry.id, entry);
8843
+ try {
8844
+ await recordAudit(kv, "core_add", "mem::core-add", [entry.id], {
8845
+ content: entry.content.slice(0, 100),
8846
+ importance: entry.importance,
8847
+ pinned: entry.pinned
8848
+ });
8849
+ } catch {}
8850
+ return {
8851
+ success: true,
8852
+ id: entry.id
8853
+ };
8854
+ });
8855
+ sdk.registerFunction({
8856
+ id: "mem::core-remove",
8857
+ description: "Remove a fact from core memory"
8858
+ }, async (data) => {
8859
+ if (!data?.id) return {
8860
+ success: false,
8861
+ error: "id is required"
8862
+ };
8863
+ await kv.delete(CORE_SCOPE, data.id);
8864
+ try {
8865
+ await recordAudit(kv, "core_remove", "mem::core-remove", [data.id], {});
8866
+ } catch {}
8867
+ return { success: true };
8868
+ });
8869
+ sdk.registerFunction({
8870
+ id: "mem::core-list",
8871
+ description: "List all core memory entries"
8872
+ }, async () => {
8873
+ const entries = await kv.list(CORE_SCOPE);
8874
+ entries.sort((a, b) => b.importance - a.importance);
8875
+ return {
8876
+ success: true,
8877
+ entries,
8878
+ totalTokens: entries.reduce((sum, e) => sum + estimateTokens(e.content), 0)
8879
+ };
8880
+ });
8881
+ sdk.registerFunction({
8882
+ id: "mem::working-context",
8883
+ description: "Build working context: core memory (pinned) + paged archival (by relevance/recency)"
8884
+ }, async (data) => {
8885
+ const ctx = getContext();
8886
+ const budget = data.budget || tokenBudget;
8887
+ const now = Date.now();
8888
+ let usedTokens = 0;
8889
+ const coreEntries = await kv.list(CORE_SCOPE);
8890
+ const pinned = coreEntries.filter((e) => e.pinned);
8891
+ const unpinned = coreEntries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(b, now) - scoreEntry(a, now));
8892
+ const coreLines = [];
8893
+ const coreBudget = Math.floor(budget * .3);
8894
+ const accessUpdates = [];
8895
+ const accessTimestamp = (/* @__PURE__ */ new Date()).toISOString();
8896
+ for (const entry of [...pinned, ...unpinned]) {
8897
+ const tokens = estimateTokens(entry.content);
8898
+ if (usedTokens + tokens > coreBudget && !entry.pinned) continue;
8899
+ coreLines.push(`- ${entry.content}`);
8900
+ usedTokens += tokens;
8901
+ entry.accessCount++;
8902
+ entry.lastAccessedAt = accessTimestamp;
8903
+ accessUpdates.push({
8904
+ id: entry.id,
8905
+ entry
8906
+ });
8907
+ }
8908
+ Promise.allSettled(accessUpdates.map(({ id, entry }) => kv.set(CORE_SCOPE, id, entry))).catch(() => {});
8909
+ const archivalLines = [];
8910
+ const active = (await kv.list(KV.memories)).filter((m) => m.isLatest !== false).sort((a, b) => {
8911
+ const strengthDiff = b.strength - a.strength;
8912
+ if (Math.abs(strengthDiff) > .2) return strengthDiff;
8913
+ return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
8914
+ });
8915
+ for (const mem of active) {
8916
+ const tokens = estimateTokens(mem.content);
8917
+ if (usedTokens + tokens > budget) continue;
8918
+ archivalLines.push(`- [${mem.type}] ${mem.title}: ${mem.content}`);
8919
+ usedTokens += tokens;
8920
+ }
8921
+ const pagedOut = active.length - archivalLines.length;
8922
+ const sections = [];
8923
+ if (coreLines.length > 0) sections.push(`## Core Memory\n${coreLines.join("\n")}`);
8924
+ if (archivalLines.length > 0) sections.push(`## Archival Memory\n${archivalLines.join("\n")}`);
8925
+ if (pagedOut > 0) sections.push(`_${pagedOut} memories paged to archival (use mem::search to retrieve)_`);
8926
+ const context = sections.join("\n\n");
8927
+ ctx.logger.info("Working context built", {
8928
+ coreEntries: coreLines.length,
8929
+ archivalEntries: archivalLines.length,
8930
+ pagedOut,
8931
+ tokens: usedTokens,
8932
+ budget
8933
+ });
8934
+ return {
8935
+ success: true,
8936
+ context,
8937
+ coreEntries: coreLines.length,
8938
+ archivalEntries: archivalLines.length,
8939
+ pagedOut,
8940
+ tokens: usedTokens,
8941
+ budget
8942
+ };
8943
+ });
8944
+ sdk.registerFunction({
8945
+ id: "mem::auto-page",
8946
+ description: "Automatically page low-value core memory entries to archival when over budget"
8947
+ }, async (data) => {
8948
+ const budget = data?.budget || tokenBudget;
8949
+ const coreBudget = Math.floor(budget * .3);
8950
+ const entries = await kv.list(CORE_SCOPE);
8951
+ let totalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
8952
+ if (totalTokens <= coreBudget) return {
8953
+ success: true,
8954
+ paged: 0,
8955
+ totalTokens,
8956
+ budget: coreBudget
8957
+ };
8958
+ const now = Date.now();
8959
+ const unpinned = entries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(a, now) - scoreEntry(b, now));
8960
+ let paged = 0;
8961
+ const pagedIds = [];
8962
+ for (const entry of unpinned) {
8963
+ if (totalTokens <= coreBudget) break;
8964
+ const tokens = estimateTokens(entry.content);
8965
+ const archivalMemory = {
8966
+ id: generateId("mem"),
8967
+ createdAt: entry.createdAt,
8968
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8969
+ type: "fact",
8970
+ title: entry.content.slice(0, 80),
8971
+ content: entry.content,
8972
+ concepts: [],
8973
+ files: [],
8974
+ sessionIds: [],
8975
+ strength: entry.importance / 10,
8976
+ version: 1,
8977
+ isLatest: true
8978
+ };
8979
+ await kv.set(KV.memories, archivalMemory.id, archivalMemory);
8980
+ await kv.delete(CORE_SCOPE, entry.id);
8981
+ totalTokens -= tokens;
8982
+ paged++;
8983
+ pagedIds.push(entry.id);
8984
+ }
8985
+ if (paged > 0) try {
8986
+ await recordAudit(kv, "auto_page", "mem::auto-page", pagedIds, {
8987
+ paged,
8988
+ budget: coreBudget
8989
+ });
8990
+ } catch {}
8991
+ return {
8992
+ success: true,
8993
+ paged,
8994
+ totalTokens,
8995
+ budget: coreBudget
8996
+ };
8997
+ });
8998
+ }
8999
+
9000
+ //#endregion
9001
+ //#region src/functions/skill-extract.ts
9002
+ const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
9003
+
9004
+ Output format:
9005
+ <skill>
9006
+ <trigger>When the agent encounters [specific situation/pattern]</trigger>
9007
+ <title>Short skill title</title>
9008
+ <steps>
9009
+ <step>First concrete action</step>
9010
+ <step>Second concrete action</step>
9011
+ </steps>
9012
+ <expected_outcome>What success looks like</expected_outcome>
9013
+ <tags>comma,separated,tags</tags>
9014
+ </skill>
9015
+
9016
+ Rules:
9017
+ - Extract ONLY if the session shows a clear multi-step procedure that succeeded
9018
+ - Steps must be concrete and actionable, not vague
9019
+ - The trigger should describe WHEN to apply this skill
9020
+ - If the session is exploratory with no clear procedure, output <no-skill/>
9021
+ - Maximum 10 steps per skill`;
9022
+ function buildSkillPrompt(summary, observations) {
9023
+ const obsText = observations.filter((o) => o.importance >= 4).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()).slice(0, 30).map((o) => `[${o.type}] ${o.title}${o.narrative ? ": " + o.narrative : ""}`).join("\n");
9024
+ return `## Session Summary
9025
+ Title: ${summary.title}
9026
+ Narrative: ${summary.narrative}
9027
+ Key Decisions: ${summary.keyDecisions.join("; ")}
9028
+ Files Modified: ${summary.filesModified.join(", ")}
9029
+ Concepts: ${summary.concepts.join(", ")}
9030
+
9031
+ ## Observations (${observations.length} total, showing top by importance)
9032
+ ${obsText}`;
9033
+ }
9034
+ function parseSkillXml(xml) {
9035
+ if (xml.includes("<no-skill/>")) return null;
9036
+ const triggerMatch = xml.match(/<trigger>([\s\S]*?)<\/trigger>/);
9037
+ const titleMatch = xml.match(/<title>([\s\S]*?)<\/title>/);
9038
+ const stepsMatch = xml.match(/<steps>([\s\S]*?)<\/steps>/);
9039
+ const outcomeMatch = xml.match(/<expected_outcome>([\s\S]*?)<\/expected_outcome>/);
9040
+ const tagsMatch = xml.match(/<tags>([\s\S]*?)<\/tags>/);
9041
+ if (!triggerMatch || !titleMatch || !stepsMatch) return null;
9042
+ const stepRegex = /<step>([\s\S]*?)<\/step>/g;
9043
+ const steps = [];
9044
+ let match;
9045
+ while ((match = stepRegex.exec(stepsMatch[1])) !== null) {
9046
+ const step = match[1].trim();
9047
+ if (step) steps.push(step);
9048
+ }
9049
+ if (steps.length < 2) return null;
9050
+ return {
9051
+ trigger: triggerMatch[1].trim(),
9052
+ title: titleMatch[1].trim(),
9053
+ steps,
9054
+ expectedOutcome: outcomeMatch?.[1]?.trim() || "",
9055
+ tags: tagsMatch?.[1]?.split(",").map((t) => t.trim()).filter(Boolean) || []
9056
+ };
9057
+ }
9058
+ function registerSkillExtractFunctions(sdk, kv, provider) {
9059
+ sdk.registerFunction({
9060
+ id: "mem::skill-extract",
9061
+ description: "Extract a reusable skill document from a completed session"
9062
+ }, async (data) => {
9063
+ const ctx = getContext();
9064
+ if (!data?.sessionId) return {
9065
+ success: false,
9066
+ error: "sessionId is required"
9067
+ };
9068
+ const session = await kv.get(KV.sessions, data.sessionId).catch(() => null);
9069
+ if (!session) return {
9070
+ success: false,
9071
+ error: "session not found"
9072
+ };
9073
+ if (session.status !== "completed") return {
9074
+ success: false,
9075
+ error: "session must be completed before skill extraction"
9076
+ };
9077
+ const [summary, observations] = await Promise.all([kv.get(KV.summaries, data.sessionId).catch(() => null), kv.list(KV.observations(data.sessionId)).catch(() => [])]);
9078
+ if (!summary) return {
9079
+ success: false,
9080
+ error: "no summary — run mem::summarize first"
9081
+ };
9082
+ if (observations.length < 3) return {
9083
+ success: false,
9084
+ error: "too few observations for skill extraction"
9085
+ };
9086
+ try {
9087
+ const prompt = buildSkillPrompt(summary, observations);
9088
+ const parsed = parseSkillXml(await provider.summarize(SKILL_EXTRACT_SYSTEM, prompt));
9089
+ if (!parsed) {
9090
+ ctx.logger.info("No skill extracted — session was exploratory", { sessionId: data.sessionId });
9091
+ return {
9092
+ success: true,
9093
+ extracted: false,
9094
+ reason: "no clear procedure found"
9095
+ };
9096
+ }
9097
+ const fp = fingerprintId("skill", JSON.stringify({
9098
+ title: parsed.title.toLowerCase(),
9099
+ trigger: parsed.trigger.toLowerCase(),
9100
+ steps: parsed.steps.map((s) => s.toLowerCase().trim())
9101
+ }));
9102
+ const existing = await kv.get(KV.procedural, fp).catch(() => null);
9103
+ if (existing) {
9104
+ if (!existing.sourceSessionIds.includes(data.sessionId)) {
9105
+ existing.strength = Math.min(1, existing.strength + .15);
9106
+ existing.frequency++;
9107
+ existing.sourceSessionIds = [...existing.sourceSessionIds, data.sessionId];
9108
+ }
9109
+ existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9110
+ await kv.set(KV.procedural, existing.id, existing);
9111
+ try {
9112
+ await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
9113
+ skillId: existing.id,
9114
+ reinforced: true,
9115
+ sessionId: data.sessionId
9116
+ });
9117
+ } catch {}
9118
+ ctx.logger.info("Skill reinforced", {
9119
+ id: existing.id,
9120
+ name: parsed.title
9121
+ });
9122
+ return {
9123
+ success: true,
9124
+ extracted: true,
9125
+ reinforced: true,
9126
+ skill: existing
9127
+ };
9128
+ }
9129
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9130
+ const skill = {
9131
+ id: fp,
9132
+ name: parsed.title,
9133
+ triggerCondition: parsed.trigger,
9134
+ steps: parsed.steps,
9135
+ expectedOutcome: parsed.expectedOutcome,
9136
+ strength: .6,
9137
+ frequency: 1,
9138
+ tags: parsed.tags,
9139
+ concepts: summary.concepts,
9140
+ sourceSessionIds: [data.sessionId],
9141
+ sourceObservationIds: observations.slice(0, 10).map((o) => o.id),
9142
+ createdAt: now,
9143
+ updatedAt: now
9144
+ };
9145
+ await kv.set(KV.procedural, skill.id, skill);
9146
+ try {
9147
+ await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
9148
+ skillId: skill.id,
9149
+ title: parsed.title,
9150
+ steps: parsed.steps.length,
9151
+ sessionId: data.sessionId
9152
+ });
9153
+ } catch {}
9154
+ ctx.logger.info("Skill extracted", {
9155
+ id: skill.id,
9156
+ title: parsed.title,
9157
+ steps: parsed.steps.length
9158
+ });
9159
+ return {
9160
+ success: true,
9161
+ extracted: true,
9162
+ reinforced: false,
9163
+ skill
9164
+ };
9165
+ } catch (err) {
9166
+ const msg = err instanceof Error ? err.message : String(err);
9167
+ ctx.logger.error("Skill extraction failed", { error: msg });
9168
+ return {
9169
+ success: false,
9170
+ error: msg
9171
+ };
9172
+ }
9173
+ });
9174
+ sdk.registerFunction({
9175
+ id: "mem::skill-list",
9176
+ description: "List extracted procedural skills"
9177
+ }, async (data) => {
9178
+ const limit = data?.limit ?? 50;
9179
+ const sorted = (await kv.list(KV.procedural)).sort((a, b) => b.strength - a.strength);
9180
+ return {
9181
+ success: true,
9182
+ skills: sorted.slice(0, limit),
9183
+ total: sorted.length
9184
+ };
9185
+ });
9186
+ sdk.registerFunction({
9187
+ id: "mem::skill-match",
9188
+ description: "Find skills relevant to a given task description"
9189
+ }, async (data) => {
9190
+ if (!data?.query?.trim()) return {
9191
+ success: false,
9192
+ error: "query is required"
9193
+ };
9194
+ const limit = data.limit ?? 5;
9195
+ const terms = data.query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
9196
+ const scored = (await kv.list(KV.procedural)).map((skill) => {
9197
+ const text = `${skill.name} ${skill.triggerCondition} ${(skill.tags || []).join(" ")} ${skill.steps.join(" ")}`.toLowerCase();
9198
+ const matchCount = terms.filter((t) => text.includes(t)).length;
9199
+ if (matchCount === 0) return null;
9200
+ return {
9201
+ skill,
9202
+ score: matchCount / terms.length * skill.strength
9203
+ };
9204
+ }).filter(Boolean);
9205
+ scored.sort((a, b) => b.score - a.score);
9206
+ return {
9207
+ success: true,
9208
+ matches: scored.slice(0, limit)
9209
+ };
9210
+ });
9211
+ }
9212
+
8746
9213
  //#endregion
8747
9214
  //#region src/functions/sliding-window.ts
8748
9215
  const SLIDING_WINDOW_SYSTEM = `You are a contextual enrichment engine. Given a primary observation and its surrounding context window (previous and next observations from the same session), produce an enriched version.
@@ -14157,6 +14624,8 @@ async function main() {
14157
14624
  registerLessonsFunctions(sdk, kv);
14158
14625
  registerObsidianExportFunction(sdk, kv);
14159
14626
  registerReflectFunctions(sdk, kv, provider);
14627
+ registerWorkingMemoryFunctions(sdk, kv, config.tokenBudget);
14628
+ registerSkillExtractFunctions(sdk, kv, provider);
14160
14629
  registerCascadeFunction(sdk, kv);
14161
14630
  registerSlidingWindowFunction(sdk, kv, provider);
14162
14631
  registerQueryExpansionFunction(sdk, provider);
@@ -14256,4 +14725,4 @@ main().catch((err) => {
14256
14725
 
14257
14726
  //#endregion
14258
14727
  export { };
14259
- //# sourceMappingURL=src-Dflj3k63.mjs.map
14728
+ //# sourceMappingURL=src-CxKzBty4.mjs.map