dsh-daoing-memory 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -698,6 +698,40 @@ var MemoryStore = class {
698
698
  countExperiencesSince(ts) {
699
699
  return this.db.prepare("SELECT COUNT(*) AS n FROM experiences WHERE deleted = 0 AND created_at > ?").get(ts).n;
700
700
  }
701
+ /** Last deletion-feedback summarization ts (0 = never). Stored in memory_meta. */
702
+ lastDeletionFeedbackTs() {
703
+ const row = this.db.prepare("SELECT value FROM memory_meta WHERE key = 'last_deletion_feedback_ts'").get();
704
+ return row ? Number(row.value) : 0;
705
+ }
706
+ /** Record the timestamp of a deletion-feedback summarization run. */
707
+ setLastDeletionFeedbackTs(ts) {
708
+ this.db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES ('last_deletion_feedback_ts', ?)").run(String(ts));
709
+ }
710
+ /** Count experience deletions (ledger op='delete', objectType='experience') since a given ts. */
711
+ countDeletionsSince(ts) {
712
+ return this.db.prepare("SELECT COUNT(*) AS n FROM ledger WHERE op = 'delete' AND object_type = 'experience' AND ts > ?").get(ts).n;
713
+ }
714
+ /** Get the deletion ledger blocks since a given ts (for LLM summarization input). */
715
+ getDeletionRecordsSince(ts, limit = 50) {
716
+ return this.db.prepare("SELECT seq, ts, object_id, reason, payload FROM ledger WHERE op = 'delete' AND object_type = 'experience' AND ts > ? ORDER BY ts DESC LIMIT ?").all(ts, limit).map((r) => {
717
+ let gist = "";
718
+ try {
719
+ gist = JSON.parse(r.payload).gist ?? "";
720
+ } catch {}
721
+ return {
722
+ seq: r.seq,
723
+ ts: r.ts,
724
+ objectId: r.object_id,
725
+ reason: r.reason ?? "",
726
+ gist
727
+ };
728
+ });
729
+ }
730
+ /** Find the active (non-deleted) experience by family name. Returns the latest revision. */
731
+ findExperienceByFamily(family) {
732
+ const row = this.db.prepare("SELECT * FROM experiences WHERE family = ? AND deleted = 0 ORDER BY revision DESC LIMIT 1").get(family);
733
+ return row ? this.rowToExperience(row) : void 0;
734
+ }
701
735
  /** Archive a set of experiences by family_id (leave recall; recoverable — never hard-deleted autonomously). */
702
736
  archiveExperienceIds(ids, ts) {
703
737
  const stmt = this.db.prepare(`UPDATE experiences SET status = ?, updated_at = ? WHERE family_id = ? AND deleted = 0 AND status IN ('candidate','live','challenged')`);
@@ -888,7 +922,9 @@ const DEFAULT_CORE_CONFIG = {
888
922
  candidateTrialTopK: 3,
889
923
  candidateTrialFloorScore: .18,
890
924
  consolidateEveryNew: 6,
891
- consolidateIntervalHours: 24
925
+ consolidateIntervalHours: 24,
926
+ deletionFeedbackIntervalHours: 24,
927
+ deletionFeedbackMinDeletions: 3
892
928
  };
893
929
  /** Objective-evidence markers overriding a claimed experience-attributed failure. */
894
930
  const ENVIRONMENT_EVIDENCE_PATTERN = /network|timeout|timed out|EPERM|EACCES|ECONNREFUSED|ENOTFOUND|429|503|quota|rate.?limit|unauthorized|dns|proxy|网络|超时|断网|权限|配额|限流|服务不可用|服务繁忙/i;
@@ -931,7 +967,7 @@ function tokenOverlap(a, b) {
931
967
  return hits / Math.sqrt(a.size * b.size);
932
968
  }
933
969
  /** The memory core: stateless over the injected store. */
934
- var MemoryCore = class {
970
+ var MemoryCore = class MemoryCore {
935
971
  store;
936
972
  config;
937
973
  /** @param store - the durable SQLite store. */
@@ -1562,6 +1598,8 @@ var MemoryCore = class {
1562
1598
  rejected: [],
1563
1599
  appliedConcerns: 0
1564
1600
  };
1601
+ const feedback = this.getDeletionFeedback();
1602
+ if (feedback !== void 0) result.deletionFeedback = feedback.reasoning;
1565
1603
  const now = Date.now();
1566
1604
  const consumedDiary = /* @__PURE__ */ new Set();
1567
1605
  for (const proposal of request.proposals) {
@@ -1882,6 +1920,100 @@ var MemoryCore = class {
1882
1920
  }
1883
1921
  return result;
1884
1922
  }
1923
+ /** Family id for the auto-generated extraction-feedback experience. */
1924
+ static DELETION_FEEDBACK_FAMILY = "extraction-feedback";
1925
+ /**
1926
+ * Check whether a deletion-feedback summarization run is due.
1927
+ * Conditions: enough new deletions since last run AND enough time elapsed.
1928
+ * @returns { due, lastTs, newDeletions, hoursSince }
1929
+ */
1930
+ deletionFeedbackDue() {
1931
+ const lastTs = this.store.lastDeletionFeedbackTs();
1932
+ const hoursSince = lastTs === 0 ? Infinity : (Date.now() - lastTs) / (3600 * 1e3);
1933
+ const newDeletions = this.store.countDeletionsSince(lastTs);
1934
+ const enoughDeletions = newDeletions >= this.config.deletionFeedbackMinDeletions;
1935
+ const intervalPassed = hoursSince >= this.config.deletionFeedbackIntervalHours;
1936
+ return {
1937
+ due: enoughDeletions && intervalPassed,
1938
+ lastTs,
1939
+ newDeletions,
1940
+ hoursSince
1941
+ };
1942
+ }
1943
+ /**
1944
+ * Apply an LLM-generated deletion-feedback summary as an extraction-feedback
1945
+ * experience. If one already exists, it is revised in place (new revision);
1946
+ * otherwise a new candidate experience is created.
1947
+ * @param summary - the LLM-generated summary text (gist + reasoning).
1948
+ * @param actor - who triggered the summarization ('system').
1949
+ * @returns the upserted experience snapshot.
1950
+ */
1951
+ applyDeletionFeedback(summary, actor) {
1952
+ const now = Date.now();
1953
+ const existing = this.store.findExperienceByFamily(MemoryCore.DELETION_FEEDBACK_FAMILY);
1954
+ if (existing !== void 0) {
1955
+ const next = {
1956
+ ...existing,
1957
+ revision: existing.revision + 1,
1958
+ gist: summary.slice(0, 200),
1959
+ reasoning: summary,
1960
+ status: "live",
1961
+ updatedAt: now
1962
+ };
1963
+ this.store.upsertExperience(next);
1964
+ this.ledger("revise", "experience", next.id, actor, {
1965
+ revision: next.revision,
1966
+ previousRevision: existing.revision
1967
+ }, "deletion-feedback summarization");
1968
+ return next;
1969
+ }
1970
+ const id = crypto.randomUUID();
1971
+ const exp = {
1972
+ id,
1973
+ familyId: id,
1974
+ revision: 1,
1975
+ kind: "positive",
1976
+ source: "system",
1977
+ family: MemoryCore.DELETION_FEEDBACK_FAMILY,
1978
+ gist: summary.slice(0, 200),
1979
+ situation: ["memory_extract 提炼新记忆时,参考此经验避免重复提取已被用户否定的类型"],
1980
+ path: [{
1981
+ order: 1,
1982
+ action: "读取此经验的 reasoning 字段,了解哪些类型的记忆曾被用户删除及原因"
1983
+ }, {
1984
+ order: 2,
1985
+ action: "提炼时避开这些类型,优先提取用户认可的记忆模式"
1986
+ }],
1987
+ reasoning: summary,
1988
+ limits: ["此经验由系统自动生成,禁止人工删除(只能归档)", "内容会随删除记录增多而定期更新"],
1989
+ status: "live",
1990
+ alpha: 5,
1991
+ beta: 2,
1992
+ pinned: false,
1993
+ tokensSaved: 0,
1994
+ tokensSpent: 0,
1995
+ context: "",
1996
+ globalFlag: true,
1997
+ verifiedCount: 0,
1998
+ rejectCount: 0,
1999
+ createdAt: now,
2000
+ updatedAt: now
2001
+ };
2002
+ this.store.upsertExperience(exp);
2003
+ this.ledger("refine", "experience", exp.id, actor, {
2004
+ family: exp.family,
2005
+ status: exp.status
2006
+ }, "deletion-feedback summarization (initial)");
2007
+ this.store.setLastDeletionFeedbackTs(now);
2008
+ return exp;
2009
+ }
2010
+ /**
2011
+ * Get the current extraction-feedback experience (if any) for injection into
2012
+ * the extraction prompt.
2013
+ */
2014
+ getDeletionFeedback() {
2015
+ return this.store.findExperienceByFamily(MemoryCore.DELETION_FEEDBACK_FAMILY);
2016
+ }
1885
2017
  /** Human pin/unpin: pinned cards keep the trust floor and escape budgets. */
1886
2018
  humanPin(request, actor) {
1887
2019
  const current = this.store.getActiveRevision(request.id);
@@ -2341,6 +2473,8 @@ let MemoryService = (() => {
2341
2473
  let _family_decorators;
2342
2474
  let _appendDiary_decorators;
2343
2475
  let _extract_decorators;
2476
+ let _runDeletionFeedback_decorators;
2477
+ let _getDeletionFeedback_decorators;
2344
2478
  let _listDiary_decorators;
2345
2479
  let _getDiaryByIds_decorators;
2346
2480
  let _listFacts_decorators;
@@ -2387,6 +2521,8 @@ let MemoryService = (() => {
2387
2521
  _family_decorators = [Remote("family")];
2388
2522
  _appendDiary_decorators = [Remote("appendDiary")];
2389
2523
  _extract_decorators = [Remote("extract")];
2524
+ _runDeletionFeedback_decorators = [Remote("runDeletionFeedback")];
2525
+ _getDeletionFeedback_decorators = [Remote("getDeletionFeedback")];
2390
2526
  _listDiary_decorators = [Remote("listDiary")];
2391
2527
  _getDiaryByIds_decorators = [Remote("getDiaryByIds")];
2392
2528
  _listFacts_decorators = [Remote("listFacts")];
@@ -2550,6 +2686,28 @@ let MemoryService = (() => {
2550
2686
  },
2551
2687
  metadata: _metadata
2552
2688
  }, null, _instanceExtraInitializers);
2689
+ __esDecorate(this, null, _runDeletionFeedback_decorators, {
2690
+ kind: "method",
2691
+ name: "runDeletionFeedback",
2692
+ static: false,
2693
+ private: false,
2694
+ access: {
2695
+ has: (obj) => "runDeletionFeedback" in obj,
2696
+ get: (obj) => obj.runDeletionFeedback
2697
+ },
2698
+ metadata: _metadata
2699
+ }, null, _instanceExtraInitializers);
2700
+ __esDecorate(this, null, _getDeletionFeedback_decorators, {
2701
+ kind: "method",
2702
+ name: "getDeletionFeedback",
2703
+ static: false,
2704
+ private: false,
2705
+ access: {
2706
+ has: (obj) => "getDeletionFeedback" in obj,
2707
+ get: (obj) => obj.getDeletionFeedback
2708
+ },
2709
+ metadata: _metadata
2710
+ }, null, _instanceExtraInitializers);
2553
2711
  __esDecorate(this, null, _listDiary_decorators, {
2554
2712
  kind: "method",
2555
2713
  name: "listDiary",
@@ -2953,9 +3111,95 @@ let MemoryService = (() => {
2953
3111
  return this.core.appendDiary(request, actorOf(agent));
2954
3112
  }
2955
3113
  /** 上升通道: apply extracted facts over the pending diary window. */
2956
- extract(agent, request) {
3114
+ async extract(agent, request) {
3115
+ await this.maybeRunDeletionFeedbackSummarization(agent);
2957
3116
  return this.core.extract(request, actorOf(agent), "manual");
2958
3117
  }
3118
+ /**
3119
+ * Check if deletion-feedback summarization is due and run it if so.
3120
+ * Uses the agent's current model route for the LLM call.
3121
+ */
3122
+ async maybeRunDeletionFeedbackSummarization(agent) {
3123
+ const check = this.core.deletionFeedbackDue();
3124
+ if (!check.due) return;
3125
+ try {
3126
+ const records = this.core["store"].getDeletionRecordsSince(check.lastTs, 50);
3127
+ if (records.length === 0) return;
3128
+ const summary = await this.summarizeDeletionsWithLlm(agent, records);
3129
+ if (summary.length > 0) this.core.applyDeletionFeedback(summary, "system");
3130
+ } catch {}
3131
+ }
3132
+ /**
3133
+ * Call the LLM to summarize deletion records into extraction feedback rules.
3134
+ * Uses ctx.llm.stream() with the agent's current model route.
3135
+ */
3136
+ async summarizeDeletionsWithLlm(agent, records) {
3137
+ const header = agent.session.requestHeader()?.config;
3138
+ if (header === void 0) return "";
3139
+ const provider = header.provider;
3140
+ const model = header.model;
3141
+ if (provider === void 0 || model === void 0) return "";
3142
+ const deletionList = records.map((r, i) => `${i + 1}. [${new Date(r.ts).toLocaleDateString("zh-CN")}] 删除的经验:「${r.gist.slice(0, 80)}」\n 原因:${r.reason || "(未填写)"}`).join("\n\n");
3143
+ const systemPrompt = `你是一个记忆系统的反馈分析器。你的任务是分析用户删除记忆时填写的原因,总结出"什么类型的记忆不值得提取"的规则。
3144
+
3145
+ 输出要求:
3146
+ 1. 用简洁的中文列出 3-5 条规则
3147
+ 2. 每条规则说明:什么类型的记忆应该避免提取,以及为什么
3148
+ 3. 规则要具体可操作,不要泛泛而谈
3149
+ 4. 如果删除原因都很相似,合并为更精炼的规则`;
3150
+ const messages = [{
3151
+ role: "user",
3152
+ content: [{
3153
+ type: "text",
3154
+ text: `以下是近期被用户删除的记忆及其删除原因:
3155
+
3156
+ ${deletionList}
3157
+
3158
+ 请总结成提取反馈规则。`
3159
+ }]
3160
+ }];
3161
+ let text = "";
3162
+ const options = {
3163
+ provider,
3164
+ model,
3165
+ messages,
3166
+ system: systemPrompt,
3167
+ maxTokens: 1500,
3168
+ sessionId: agent.session.id
3169
+ };
3170
+ for await (const chunk of this.ctx.llm.stream(options)) if (chunk.type === "text-delta") text += chunk.text;
3171
+ return text.trim();
3172
+ }
3173
+ /**
3174
+ * Remote: manually trigger deletion-feedback summarization (for testing/debugging).
3175
+ */
3176
+ async runDeletionFeedback(agent) {
3177
+ const check = this.core.deletionFeedbackDue();
3178
+ const records = this.core["store"].getDeletionRecordsSince(check.lastTs, 50);
3179
+ if (records.length === 0) return { ran: false };
3180
+ try {
3181
+ const summary = await this.summarizeDeletionsWithLlm(agent, records);
3182
+ if (summary.length > 0) {
3183
+ this.core.applyDeletionFeedback(summary, "system");
3184
+ return {
3185
+ ran: true,
3186
+ summary
3187
+ };
3188
+ }
3189
+ return { ran: false };
3190
+ } catch (e) {
3191
+ return {
3192
+ ran: false,
3193
+ summary: String(e)
3194
+ };
3195
+ }
3196
+ }
3197
+ /**
3198
+ * Remote: get the current deletion-feedback experience (for debugging).
3199
+ */
3200
+ getDeletionFeedback(agent) {
3201
+ return this.core.getDeletionFeedback() ?? null;
3202
+ }
2959
3203
  /** Diary timeline for the workbench (007 §2: server-side pagination, newest first). */
2960
3204
  listDiary(agent, limit, offset, onlyUnextracted) {
2961
3205
  return this.core.listDiary(limit, offset, onlyUnextracted);
@@ -3129,7 +3373,9 @@ function resolveConfig(config) {
3129
3373
  "complexityStepGate",
3130
3374
  "duplicateOverlapGate",
3131
3375
  "recallFloorScore",
3132
- "shadowPassRate"
3376
+ "shadowPassRate",
3377
+ "deletionFeedbackIntervalHours",
3378
+ "deletionFeedbackMinDeletions"
3133
3379
  ];
3134
3380
  const unknown = Object.keys(config).filter((key) => !known.includes(key));
3135
3381
  if (unknown.length > 0) throw new Error(`memory: unknown config key(s) ${unknown.join(", ")}`);
@@ -3149,7 +3395,9 @@ function resolveConfig(config) {
3149
3395
  complexityStepGate: config.complexityStepGate ?? DEFAULT_CORE_CONFIG.complexityStepGate,
3150
3396
  duplicateOverlapGate: config.duplicateOverlapGate ?? DEFAULT_CORE_CONFIG.duplicateOverlapGate,
3151
3397
  recallFloorScore: config.recallFloorScore ?? DEFAULT_CORE_CONFIG.recallFloorScore,
3152
- shadowPassRate: config.shadowPassRate ?? DEFAULT_CORE_CONFIG.shadowPassRate
3398
+ shadowPassRate: config.shadowPassRate ?? DEFAULT_CORE_CONFIG.shadowPassRate,
3399
+ deletionFeedbackIntervalHours: config.deletionFeedbackIntervalHours ?? DEFAULT_CORE_CONFIG.deletionFeedbackIntervalHours,
3400
+ deletionFeedbackMinDeletions: config.deletionFeedbackMinDeletions ?? DEFAULT_CORE_CONFIG.deletionFeedbackMinDeletions
3153
3401
  };
3154
3402
  }
3155
3403
  /**
@@ -3174,7 +3422,9 @@ function apply(ctx, config = {}) {
3174
3422
  complexityStepGate: resolved.complexityStepGate,
3175
3423
  duplicateOverlapGate: resolved.duplicateOverlapGate,
3176
3424
  recallFloorScore: resolved.recallFloorScore,
3177
- shadowPassRate: resolved.shadowPassRate
3425
+ shadowPassRate: resolved.shadowPassRate,
3426
+ deletionFeedbackIntervalHours: resolved.deletionFeedbackIntervalHours,
3427
+ deletionFeedbackMinDeletions: resolved.deletionFeedbackMinDeletions
3178
3428
  };
3179
3429
  mkdirSync(join(resolved.databasePath, ".."), { recursive: true });
3180
3430
  const db = new DatabaseSync(resolved.databasePath);