@agentmemory/agentmemory 0.7.5 → 0.7.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.
@@ -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.7";
3743
3808
 
3744
3809
  //#endregion
3745
3810
  //#region src/functions/export-import.ts
@@ -3841,7 +3906,9 @@ 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",
3911
+ "0.7.7"
3845
3912
  ]).has(importData.version)) return {
3846
3913
  success: false,
3847
3914
  error: `Unsupported export version: ${importData.version}`
@@ -8743,6 +8810,407 @@ function registerReflectFunctions(sdk, kv, provider) {
8743
8810
  });
8744
8811
  }
8745
8812
 
8813
+ //#endregion
8814
+ //#region src/functions/working-memory.ts
8815
+ const CORE_SCOPE = "mem:core-memory";
8816
+ function estimateTokens(text) {
8817
+ return Math.ceil(text.length / 3);
8818
+ }
8819
+ function scoreEntry(entry, now) {
8820
+ const recencyScore = 1 / (1 + (now - new Date(entry.lastAccessedAt).getTime()) / (1e3 * 60 * 60 * 24) * .1);
8821
+ const accessScore = Math.log2(entry.accessCount + 1) / 10;
8822
+ return entry.importance / 10 * .5 + recencyScore * .3 + accessScore * .2;
8823
+ }
8824
+ function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
8825
+ sdk.registerFunction({
8826
+ id: "mem::core-add",
8827
+ description: "Add a fact to core memory (always included in context)"
8828
+ }, async (data) => {
8829
+ if (!data?.content?.trim()) return {
8830
+ success: false,
8831
+ error: "content is required"
8832
+ };
8833
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8834
+ const entry = {
8835
+ id: generateId("core"),
8836
+ content: data.content.trim(),
8837
+ importance: Math.min(10, Math.max(1, data.importance ?? 7)),
8838
+ pinned: data.pinned ?? false,
8839
+ accessCount: 0,
8840
+ lastAccessedAt: now,
8841
+ createdAt: now
8842
+ };
8843
+ await kv.set(CORE_SCOPE, entry.id, entry);
8844
+ try {
8845
+ await recordAudit(kv, "core_add", "mem::core-add", [entry.id], {
8846
+ content: entry.content.slice(0, 100),
8847
+ importance: entry.importance,
8848
+ pinned: entry.pinned
8849
+ });
8850
+ } catch {}
8851
+ return {
8852
+ success: true,
8853
+ id: entry.id
8854
+ };
8855
+ });
8856
+ sdk.registerFunction({
8857
+ id: "mem::core-remove",
8858
+ description: "Remove a fact from core memory"
8859
+ }, async (data) => {
8860
+ if (!data?.id) return {
8861
+ success: false,
8862
+ error: "id is required"
8863
+ };
8864
+ await kv.delete(CORE_SCOPE, data.id);
8865
+ try {
8866
+ await recordAudit(kv, "core_remove", "mem::core-remove", [data.id], {});
8867
+ } catch {}
8868
+ return { success: true };
8869
+ });
8870
+ sdk.registerFunction({
8871
+ id: "mem::core-list",
8872
+ description: "List all core memory entries"
8873
+ }, async () => {
8874
+ const entries = await kv.list(CORE_SCOPE);
8875
+ entries.sort((a, b) => b.importance - a.importance);
8876
+ return {
8877
+ success: true,
8878
+ entries,
8879
+ totalTokens: entries.reduce((sum, e) => sum + estimateTokens(e.content), 0)
8880
+ };
8881
+ });
8882
+ sdk.registerFunction({
8883
+ id: "mem::working-context",
8884
+ description: "Build working context: core memory (pinned) + paged archival (by relevance/recency)"
8885
+ }, async (data) => {
8886
+ const ctx = getContext();
8887
+ const budget = data.budget || tokenBudget;
8888
+ const now = Date.now();
8889
+ let usedTokens = 0;
8890
+ const coreEntries = await kv.list(CORE_SCOPE);
8891
+ const pinned = coreEntries.filter((e) => e.pinned);
8892
+ const unpinned = coreEntries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(b, now) - scoreEntry(a, now));
8893
+ const coreLines = [];
8894
+ const coreBudget = Math.floor(budget * .3);
8895
+ const accessUpdates = [];
8896
+ const accessTimestamp = (/* @__PURE__ */ new Date()).toISOString();
8897
+ for (const entry of [...pinned, ...unpinned]) {
8898
+ const tokens = estimateTokens(entry.content);
8899
+ if (usedTokens + tokens > coreBudget && !entry.pinned) continue;
8900
+ coreLines.push(`- ${entry.content}`);
8901
+ usedTokens += tokens;
8902
+ entry.accessCount++;
8903
+ entry.lastAccessedAt = accessTimestamp;
8904
+ accessUpdates.push({
8905
+ id: entry.id,
8906
+ entry
8907
+ });
8908
+ }
8909
+ Promise.allSettled(accessUpdates.map(({ id, entry }) => kv.set(CORE_SCOPE, id, entry))).catch(() => {});
8910
+ const archivalLines = [];
8911
+ const active = (await kv.list(KV.memories)).filter((m) => m.isLatest !== false).sort((a, b) => {
8912
+ const strengthDiff = b.strength - a.strength;
8913
+ if (Math.abs(strengthDiff) > .2) return strengthDiff;
8914
+ return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
8915
+ });
8916
+ for (const mem of active) {
8917
+ const tokens = estimateTokens(mem.content);
8918
+ if (usedTokens + tokens > budget) continue;
8919
+ archivalLines.push(`- [${mem.type}] ${mem.title}: ${mem.content}`);
8920
+ usedTokens += tokens;
8921
+ }
8922
+ const pagedOut = active.length - archivalLines.length;
8923
+ const sections = [];
8924
+ if (coreLines.length > 0) sections.push(`## Core Memory\n${coreLines.join("\n")}`);
8925
+ if (archivalLines.length > 0) sections.push(`## Archival Memory\n${archivalLines.join("\n")}`);
8926
+ if (pagedOut > 0) sections.push(`_${pagedOut} memories paged to archival (use mem::search to retrieve)_`);
8927
+ const context = sections.join("\n\n");
8928
+ ctx.logger.info("Working context built", {
8929
+ coreEntries: coreLines.length,
8930
+ archivalEntries: archivalLines.length,
8931
+ pagedOut,
8932
+ tokens: usedTokens,
8933
+ budget
8934
+ });
8935
+ return {
8936
+ success: true,
8937
+ context,
8938
+ coreEntries: coreLines.length,
8939
+ archivalEntries: archivalLines.length,
8940
+ pagedOut,
8941
+ tokens: usedTokens,
8942
+ budget
8943
+ };
8944
+ });
8945
+ sdk.registerFunction({
8946
+ id: "mem::auto-page",
8947
+ description: "Automatically page low-value core memory entries to archival when over budget"
8948
+ }, async (data) => {
8949
+ const budget = data?.budget || tokenBudget;
8950
+ const coreBudget = Math.floor(budget * .3);
8951
+ const entries = await kv.list(CORE_SCOPE);
8952
+ let totalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
8953
+ if (totalTokens <= coreBudget) return {
8954
+ success: true,
8955
+ paged: 0,
8956
+ totalTokens,
8957
+ budget: coreBudget
8958
+ };
8959
+ const now = Date.now();
8960
+ const unpinned = entries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(a, now) - scoreEntry(b, now));
8961
+ let paged = 0;
8962
+ const pagedIds = [];
8963
+ for (const entry of unpinned) {
8964
+ if (totalTokens <= coreBudget) break;
8965
+ const tokens = estimateTokens(entry.content);
8966
+ const archivalMemory = {
8967
+ id: generateId("mem"),
8968
+ createdAt: entry.createdAt,
8969
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8970
+ type: "fact",
8971
+ title: entry.content.slice(0, 80),
8972
+ content: entry.content,
8973
+ concepts: [],
8974
+ files: [],
8975
+ sessionIds: [],
8976
+ strength: entry.importance / 10,
8977
+ version: 1,
8978
+ isLatest: true
8979
+ };
8980
+ await kv.set(KV.memories, archivalMemory.id, archivalMemory);
8981
+ await kv.delete(CORE_SCOPE, entry.id);
8982
+ totalTokens -= tokens;
8983
+ paged++;
8984
+ pagedIds.push(entry.id);
8985
+ }
8986
+ if (paged > 0) try {
8987
+ await recordAudit(kv, "auto_page", "mem::auto-page", pagedIds, {
8988
+ paged,
8989
+ budget: coreBudget
8990
+ });
8991
+ } catch {}
8992
+ return {
8993
+ success: true,
8994
+ paged,
8995
+ totalTokens,
8996
+ budget: coreBudget
8997
+ };
8998
+ });
8999
+ }
9000
+
9001
+ //#endregion
9002
+ //#region src/functions/skill-extract.ts
9003
+ const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
9004
+
9005
+ Output format:
9006
+ <skill>
9007
+ <trigger>When the agent encounters [specific situation/pattern]</trigger>
9008
+ <title>Short skill title</title>
9009
+ <steps>
9010
+ <step>First concrete action</step>
9011
+ <step>Second concrete action</step>
9012
+ </steps>
9013
+ <expected_outcome>What success looks like</expected_outcome>
9014
+ <tags>comma,separated,tags</tags>
9015
+ </skill>
9016
+
9017
+ Rules:
9018
+ - Extract ONLY if the session shows a clear multi-step procedure that succeeded
9019
+ - Steps must be concrete and actionable, not vague
9020
+ - The trigger should describe WHEN to apply this skill
9021
+ - If the session is exploratory with no clear procedure, output <no-skill/>
9022
+ - Maximum 10 steps per skill`;
9023
+ function buildSkillPrompt(summary, observations) {
9024
+ 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");
9025
+ return `## Session Summary
9026
+ Title: ${summary.title}
9027
+ Narrative: ${summary.narrative}
9028
+ Key Decisions: ${summary.keyDecisions.join("; ")}
9029
+ Files Modified: ${summary.filesModified.join(", ")}
9030
+ Concepts: ${summary.concepts.join(", ")}
9031
+
9032
+ ## Observations (${observations.length} total, showing top by importance)
9033
+ ${obsText}`;
9034
+ }
9035
+ function parseSkillXml(xml) {
9036
+ if (xml.includes("<no-skill/>")) return null;
9037
+ const triggerMatch = xml.match(/<trigger>([\s\S]*?)<\/trigger>/);
9038
+ const titleMatch = xml.match(/<title>([\s\S]*?)<\/title>/);
9039
+ const stepsMatch = xml.match(/<steps>([\s\S]*?)<\/steps>/);
9040
+ const outcomeMatch = xml.match(/<expected_outcome>([\s\S]*?)<\/expected_outcome>/);
9041
+ const tagsMatch = xml.match(/<tags>([\s\S]*?)<\/tags>/);
9042
+ if (!triggerMatch || !titleMatch || !stepsMatch) return null;
9043
+ const stepRegex = /<step>([\s\S]*?)<\/step>/g;
9044
+ const steps = [];
9045
+ let match;
9046
+ while ((match = stepRegex.exec(stepsMatch[1])) !== null) {
9047
+ const step = match[1].trim();
9048
+ if (step) steps.push(step);
9049
+ }
9050
+ if (steps.length < 2) return null;
9051
+ return {
9052
+ trigger: triggerMatch[1].trim(),
9053
+ title: titleMatch[1].trim(),
9054
+ steps,
9055
+ expectedOutcome: outcomeMatch?.[1]?.trim() || "",
9056
+ tags: tagsMatch?.[1]?.split(",").map((t) => t.trim()).filter(Boolean) || []
9057
+ };
9058
+ }
9059
+ function registerSkillExtractFunctions(sdk, kv, provider) {
9060
+ sdk.registerFunction({
9061
+ id: "mem::skill-extract",
9062
+ description: "Extract a reusable skill document from a completed session"
9063
+ }, async (data) => {
9064
+ const ctx = getContext();
9065
+ if (!data?.sessionId) return {
9066
+ success: false,
9067
+ error: "sessionId is required"
9068
+ };
9069
+ const session = await kv.get(KV.sessions, data.sessionId).catch(() => null);
9070
+ if (!session) return {
9071
+ success: false,
9072
+ error: "session not found"
9073
+ };
9074
+ if (session.status !== "completed") return {
9075
+ success: false,
9076
+ error: "session must be completed before skill extraction"
9077
+ };
9078
+ const [summary, observations] = await Promise.all([kv.get(KV.summaries, data.sessionId).catch(() => null), kv.list(KV.observations(data.sessionId)).catch(() => [])]);
9079
+ if (!summary) return {
9080
+ success: false,
9081
+ error: "no summary — run mem::summarize first"
9082
+ };
9083
+ if (observations.length < 3) return {
9084
+ success: false,
9085
+ error: "too few observations for skill extraction"
9086
+ };
9087
+ try {
9088
+ const prompt = buildSkillPrompt(summary, observations);
9089
+ const parsed = parseSkillXml(await provider.summarize(SKILL_EXTRACT_SYSTEM, prompt));
9090
+ if (!parsed) {
9091
+ ctx.logger.info("No skill extracted — session was exploratory", { sessionId: data.sessionId });
9092
+ return {
9093
+ success: true,
9094
+ extracted: false,
9095
+ reason: "no clear procedure found"
9096
+ };
9097
+ }
9098
+ const fp = fingerprintId("skill", JSON.stringify({
9099
+ title: parsed.title.toLowerCase(),
9100
+ trigger: parsed.trigger.toLowerCase(),
9101
+ steps: parsed.steps.map((s) => s.toLowerCase().trim())
9102
+ }));
9103
+ const existing = await kv.get(KV.procedural, fp).catch(() => null);
9104
+ if (existing) {
9105
+ if (!existing.sourceSessionIds.includes(data.sessionId)) {
9106
+ existing.strength = Math.min(1, existing.strength + .15);
9107
+ existing.frequency++;
9108
+ existing.sourceSessionIds = [...existing.sourceSessionIds, data.sessionId];
9109
+ }
9110
+ existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9111
+ await kv.set(KV.procedural, existing.id, existing);
9112
+ try {
9113
+ await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
9114
+ skillId: existing.id,
9115
+ reinforced: true,
9116
+ sessionId: data.sessionId
9117
+ });
9118
+ } catch {}
9119
+ ctx.logger.info("Skill reinforced", {
9120
+ id: existing.id,
9121
+ name: parsed.title
9122
+ });
9123
+ return {
9124
+ success: true,
9125
+ extracted: true,
9126
+ reinforced: true,
9127
+ skill: existing
9128
+ };
9129
+ }
9130
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9131
+ const skill = {
9132
+ id: fp,
9133
+ name: parsed.title,
9134
+ triggerCondition: parsed.trigger,
9135
+ steps: parsed.steps,
9136
+ expectedOutcome: parsed.expectedOutcome,
9137
+ strength: .6,
9138
+ frequency: 1,
9139
+ tags: parsed.tags,
9140
+ concepts: summary.concepts,
9141
+ sourceSessionIds: [data.sessionId],
9142
+ sourceObservationIds: observations.slice(0, 10).map((o) => o.id),
9143
+ createdAt: now,
9144
+ updatedAt: now
9145
+ };
9146
+ await kv.set(KV.procedural, skill.id, skill);
9147
+ try {
9148
+ await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
9149
+ skillId: skill.id,
9150
+ title: parsed.title,
9151
+ steps: parsed.steps.length,
9152
+ sessionId: data.sessionId
9153
+ });
9154
+ } catch {}
9155
+ ctx.logger.info("Skill extracted", {
9156
+ id: skill.id,
9157
+ title: parsed.title,
9158
+ steps: parsed.steps.length
9159
+ });
9160
+ return {
9161
+ success: true,
9162
+ extracted: true,
9163
+ reinforced: false,
9164
+ skill
9165
+ };
9166
+ } catch (err) {
9167
+ const msg = err instanceof Error ? err.message : String(err);
9168
+ ctx.logger.error("Skill extraction failed", { error: msg });
9169
+ return {
9170
+ success: false,
9171
+ error: msg
9172
+ };
9173
+ }
9174
+ });
9175
+ sdk.registerFunction({
9176
+ id: "mem::skill-list",
9177
+ description: "List extracted procedural skills"
9178
+ }, async (data) => {
9179
+ const limit = data?.limit ?? 50;
9180
+ const sorted = (await kv.list(KV.procedural)).sort((a, b) => b.strength - a.strength);
9181
+ return {
9182
+ success: true,
9183
+ skills: sorted.slice(0, limit),
9184
+ total: sorted.length
9185
+ };
9186
+ });
9187
+ sdk.registerFunction({
9188
+ id: "mem::skill-match",
9189
+ description: "Find skills relevant to a given task description"
9190
+ }, async (data) => {
9191
+ if (!data?.query?.trim()) return {
9192
+ success: false,
9193
+ error: "query is required"
9194
+ };
9195
+ const limit = data.limit ?? 5;
9196
+ const terms = data.query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
9197
+ const scored = (await kv.list(KV.procedural)).map((skill) => {
9198
+ const text = `${skill.name} ${skill.triggerCondition} ${(skill.tags || []).join(" ")} ${skill.steps.join(" ")}`.toLowerCase();
9199
+ const matchCount = terms.filter((t) => text.includes(t)).length;
9200
+ if (matchCount === 0) return null;
9201
+ return {
9202
+ skill,
9203
+ score: matchCount / terms.length * skill.strength
9204
+ };
9205
+ }).filter(Boolean);
9206
+ scored.sort((a, b) => b.score - a.score);
9207
+ return {
9208
+ success: true,
9209
+ matches: scored.slice(0, limit)
9210
+ };
9211
+ });
9212
+ }
9213
+
8746
9214
  //#endregion
8747
9215
  //#region src/functions/sliding-window.ts
8748
9216
  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 +14625,8 @@ async function main() {
14157
14625
  registerLessonsFunctions(sdk, kv);
14158
14626
  registerObsidianExportFunction(sdk, kv);
14159
14627
  registerReflectFunctions(sdk, kv, provider);
14628
+ registerWorkingMemoryFunctions(sdk, kv, config.tokenBudget);
14629
+ registerSkillExtractFunctions(sdk, kv, provider);
14160
14630
  registerCascadeFunction(sdk, kv);
14161
14631
  registerSlidingWindowFunction(sdk, kv, provider);
14162
14632
  registerQueryExpansionFunction(sdk, provider);
@@ -14256,4 +14726,4 @@ main().catch((err) => {
14256
14726
 
14257
14727
  //#endregion
14258
14728
  export { };
14259
- //# sourceMappingURL=src-Dflj3k63.mjs.map
14729
+ //# sourceMappingURL=src-C_TC9frp.mjs.map