dsh-prime-memory 0.14.0-beta.5 → 0.14.0-beta.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.
package/dist/pipeline/l1.js
CHANGED
|
@@ -272,6 +272,14 @@ anchorMap) {
|
|
|
272
272
|
relatedIds.add(c.id);
|
|
273
273
|
}
|
|
274
274
|
const byId = new Map(store.getByIds([...relatedIds]).map((r) => [r.id, r]));
|
|
275
|
+
/**
|
|
276
|
+
* 本批次**全部**新记忆的 record_id。两个用途:
|
|
277
|
+
* ① 作为 `validateConflictPair` 的对手集之一 —— 同批次两条新记忆互相矛盾时,
|
|
278
|
+
* 对方的 id 不在候选池里(本轮刚生成、尚未入库),没有这个集合就**必然被判
|
|
279
|
+
* 不成对而回落 store**(2026-09-18 取证确认,见 findings R6 / Agent A);
|
|
280
|
+
* ② 队满自动了结的护栏 —— 败方若属本批新记忆,不得自动退场(见下方分支)。
|
|
281
|
+
*/
|
|
282
|
+
const batchIds = new Set(extracted.map((e) => e.record_id));
|
|
275
283
|
/**
|
|
276
284
|
* update/merge 取代掉的旧记录 → **取代它的**新记录 id。
|
|
277
285
|
* 用 Map 而非 Set:退场标记要带 `by`,否则"被谁取代"只能靠时间猜。
|
|
@@ -299,7 +307,7 @@ anchorMap) {
|
|
|
299
307
|
// 一个显式声明了"拿不准"的决策,绝不能被静默当成"新记忆更优"去覆盖旧记忆。
|
|
300
308
|
if (action === 'conflict') {
|
|
301
309
|
const pair = freezeEnabled
|
|
302
|
-
? validateConflictPair(m.record_id, decision.winner, decision.loser, new Set(byId.keys()))
|
|
310
|
+
? validateConflictPair(m.record_id, decision.winner, decision.loser, new Set(byId.keys()), batchIds)
|
|
303
311
|
: null;
|
|
304
312
|
if (pair) {
|
|
305
313
|
added.push(toStoreRecord(m, now, ts, anchorMap));
|
|
@@ -313,6 +321,16 @@ anchorMap) {
|
|
|
313
321
|
// 判据含 frozen 中本轮已停放的未裁决数,否则同一轮内多条冲突会一起越界。
|
|
314
322
|
const pendingNow = store.countConflictPendingUnresolved() + frozen.filter((p) => p.resolvedAt === '').length;
|
|
315
323
|
if (pendingNow >= maxPending) {
|
|
324
|
+
// 护栏(2026-09-18 随同批次冻结一起加):**败方是本轮新记忆时不做自动了结**。
|
|
325
|
+
// 自动了结 = `retire(loser)`,而本轮的 `added` 里刚把这条新记忆写入 ——
|
|
326
|
+
// 那等于"刚抽取出来的产出立刻退场",且没有任何人被告知。
|
|
327
|
+
// 改为不停放这一对(该条已在上方 added 中照常入库,记忆不丢),
|
|
328
|
+
// 只放弃这条裁决请求;队列有界性因此仍然成立。
|
|
329
|
+
if (batchIds.has(built.loserId)) {
|
|
330
|
+
logger.warn(`[memory] 矛盾冻结:队列已满(${pendingNow}/${maxPending}),且该对的败方是本轮新记忆` +
|
|
331
|
+
`(${built.loserId})——不做自动了结(避免新记忆立即退场),改为不停放、照常入库`);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
316
334
|
built.resolvedAt = new Date(now).toISOString();
|
|
317
335
|
built.resolution = 'auto';
|
|
318
336
|
autoLosers.add(pair.loserId);
|
|
@@ -18,7 +18,7 @@ export declare const ALL_CONFLICT_DETECTION_SYSTEM_PROMPT = "\u4F60\u662F\u8BB0\
|
|
|
18
18
|
* 语义承自 mneme(dream layer **不**自动裁决 winner/loser):检测到矛盾后
|
|
19
19
|
* 不是"拦住写入",而是"不自动裁决"——把冲突对停放到待审区。
|
|
20
20
|
*/
|
|
21
|
-
export declare const CONFLICT_ACTION_CLAUSE = "## \u77DB\u76FE\u51BB\u7ED3\u52A8\u4F5C\uFF08\"conflict\"\uFF09\n\n\u4E0A\u9762\u56DB\u6761\u52A8\u4F5C\u5728**\u5224\u5B9A\u51B2\u7A81**\u65F6\u90FD\u4E0D\u53EF\u7528\u2014\u2014\"update\" \u4E0E \"merge\" \u90FD\u4F1A\u7531\u4F60\u76F4\u63A5\u6539\u5199\u8BB0\u5FC6\uFF0C**\u6CA1\u6709\"\u505C\u4E0B\u6765\u7B49\u4EBA\u88C1\u51B3\"\u8FD9\u4E2A\u9009\u9879**\u3002\u6545\u65B0\u589E\u7B2C\u4E94\u6761\u52A8\u4F5C\uFF1A\n\n- \"conflict\"\uFF1A\u65B0\u8BB0\u5FC6\u4E0E\u5019\u9009\u6C60\u4E2D\
|
|
21
|
+
export declare const CONFLICT_ACTION_CLAUSE = "## \u77DB\u76FE\u51BB\u7ED3\u52A8\u4F5C\uFF08\"conflict\"\uFF09\n\n\u4E0A\u9762\u56DB\u6761\u52A8\u4F5C\u5728**\u5224\u5B9A\u51B2\u7A81**\u65F6\u90FD\u4E0D\u53EF\u7528\u2014\u2014\"update\" \u4E0E \"merge\" \u90FD\u4F1A\u7531\u4F60\u76F4\u63A5\u6539\u5199\u8BB0\u5FC6\uFF0C**\u6CA1\u6709\"\u505C\u4E0B\u6765\u7B49\u4EBA\u88C1\u51B3\"\u8FD9\u4E2A\u9009\u9879**\u3002\u6545\u65B0\u589E\u7B2C\u4E94\u6761\u52A8\u4F5C\uFF1A\n\n- \"conflict\"\uFF1A\u65B0\u8BB0\u5FC6\u4E0E**\u53E6\u4E00\u6761\u8BB0\u5FC6**\u2014\u2014\u5019\u9009\u6C60\u4E2D\u7684\u5DF2\u6709\u8BB0\u5FC6\uFF0C**\u6216\u540C\u4E00\u6279\u6B21\u91CC\u7684\u53E6\u4E00\u6761\u65B0\u8BB0\u5FC6**\uFF08\u5B83\u7684 record_id \u540C\u6837\u5370\u5728\u3010\u65B0\u8BB0\u5FC6\u3011\u5217\u8868\u91CC\uFF09\u2014\u2014**\u63CF\u8FF0\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6/\u5DE5\u4F5C\u5BF9\u8C61\uFF0C\u4F46\u5185\u5BB9\u4E92\u76F8\u77DB\u76FE**\uFF0C\u4E14\u4F60**\u65E0\u6CD5\u4F9D\u636E\u73B0\u6709\u4FE1\u606F\u5224\u5B9A\u54EA\u4E00\u65B9\u66F4\u53EF\u4FE1**\u65F6\u4F7F\u7528\u3002**\u4E0D\u8986\u76D6\u3001\u4E0D\u5408\u5E76**\uFF1A\u8BE5\u6761\u65B0\u8BB0\u5FC6\u7167\u5E38\u5199\u5165\uFF0C\u4E0E\u5BF9\u65B9\u4F5C\u4E3A**\u4E00\u5BF9**\u505C\u653E\u5230\u5F85\u4EBA\u5DE5\u88C1\u51B3\u533A\uFF0C\u53CC\u65B9\u5185\u5BB9\u90FD\u4E0D\u88AB\u6539\u5199\u3002\n\n### conflict \u7684\u8FFD\u52A0\u8F93\u51FA\u5B57\u6BB5\n\n{\n \"record_id\": \"\u672C\u6761\u65B0\u8BB0\u5FC6\u7684 record_id\",\n \"action\": \"conflict\",\n \"winner\": \"\u5176\u4E2D\u4E00\u65B9\u7684 record_id\",\n \"loser\": \"\u53E6\u4E00\u65B9\u7684 record_id\"\n}\n\n- \"winner\" / \"loser\"\uFF1A**\u4E8C\u8005\u5FC5\u987B\u4E0D\u540C**\uFF0C\u4E14**\u6070\u6709\u4E00\u65B9\u5FC5\u987B\u662F\u672C\u6761\u65B0\u8BB0\u5FC6\u7684 record_id**\uFF1B\u53E6\u4E00\u65B9\u53D6\u81EA\u300C\u5019\u9009\u6C60\u4E2D\u7684\u5DF2\u6709\u8BB0\u5FC6 record_id\u300D\u6216\u300C\u540C\u4E00\u6279\u6B21\u5176\u5B83\u65B0\u8BB0\u5FC6\u7684 record_id\u300D\u3002\u6307\u5411\u672C\u6279\u6B21\u4E4B\u5916\u7684\u3001\u4E0D\u5728\u5019\u9009\u6C60\u91CC\u7684 id \u4E00\u5F8B\u65E0\u6548\u3002\n- \"winner\" \u53EA\u8868\u793A\u8FDB\u5165\u5F85\u88C1\u51B3\u5BF9\u65F6\u7684\u6392\u5E8F\u4F4D\uFF0C**\u4E0D\u4EE3\u8868\u6700\u7EC8\u7ED3\u8BBA**\uFF1B\u6700\u7EC8\u7ED3\u8BBA\u7531\u4EBA\u5DE5\u88C1\u51B3\u5199\u5165\u3002\n- action \u4E3A \"conflict\" \u65F6**\u4E0D\u8981**\u8F93\u51FA merged_content / merged_type / merged_priority / merged_timestamps\u2014\u2014\u5B83\u4EEC\u53EA\u5C5E\u4E8E update / merge\u3002\n\n### \u4EC0\u4E48\u65F6\u5019**\u4E0D**\u7528 conflict\n\n- \u65B0\u8BB0\u5FC6\u66F4\u5177\u4F53\u3001\u66F4\u65B0\u3001\u66F4\u6743\u5A01\uFF0C\u6216\u80FD\u660E\u786E\u7EA0\u6B63\u65E7\u8BB0\u5FC6\u7684\u9519\u8BEF \u2192 \u4ECD\u7528 \"update\"\u3002\n- \u65B0\u65E7\u8BB0\u5FC6\u4FE1\u606F\u4E92\u8865\u4E14**\u4E0D\u77DB\u76FE** \u2192 \u4ECD\u7528 \"merge\"\u3002\n- \u53EA\u662F\u540C\u5C5E\u4E00\u4E2A\u4E3B\u9898\u4F46\u63CF\u8FF0\u5BF9\u8C61\u4E0D\u540C \u2192 \u4ECD\u7528 \"store\"\u3002\n- \u540C\u6279\u6B21\u4E24\u6761\u65B0\u8BB0\u5FC6\u53EA\u662F**\u540C\u4E00\u4E8B\u5B9E\u7684\u4E0D\u540C\u8868\u8FF0**\uFF08\u4E92\u8865\u3001\u7C92\u5EA6\u4E0D\u540C\u3001\u4E0D\u4E92\u65A5\uFF09\u2192 \u4ECD\u5404\u81EA \"store\"\u3002**\u53EA\u6709\u4E92\u76F8\u77DB\u76FE\u624D\u7B97 conflict\u3002**\n\nconflict \u53EA\u7559\u7ED9\"\u4E24\u8FB9\u90FD\u50CF\u662F\u5BF9\u7684\u3001\u673A\u5668\u5224\u4E0D\u4E86\"\u7684\u60C5\u51B5\u2014\u2014\u5B83\u6D88\u8017\u4EBA\u7684\u6CE8\u610F\u529B\uFF0C\u4E0D\u53EF\u6EE5\u7528\u3002";
|
|
22
22
|
export interface DedupPromptOptions {
|
|
23
23
|
/** §C 矛盾冻结总开关。**默认关**:关闭时 prompt 与改动前逐字一致。 */
|
|
24
24
|
conflictFreeze?: boolean;
|
package/dist/prompts/l1-dedup.js
CHANGED
|
@@ -208,7 +208,7 @@ export const CONFLICT_ACTION_CLAUSE = `## 矛盾冻结动作("conflict")
|
|
|
208
208
|
|
|
209
209
|
上面四条动作在**判定冲突**时都不可用——"update" 与 "merge" 都会由你直接改写记忆,**没有"停下来等人裁决"这个选项**。故新增第五条动作:
|
|
210
210
|
|
|
211
|
-
- "conflict"
|
|
211
|
+
- "conflict":新记忆与**另一条记忆**——候选池中的已有记忆,**或同一批次里的另一条新记忆**(它的 record_id 同样印在【新记忆】列表里)——**描述同一事实/事件/工作对象,但内容互相矛盾**,且你**无法依据现有信息判定哪一方更可信**时使用。**不覆盖、不合并**:该条新记忆照常写入,与对方作为**一对**停放到待人工裁决区,双方内容都不被改写。
|
|
212
212
|
|
|
213
213
|
### conflict 的追加输出字段
|
|
214
214
|
|
|
@@ -219,7 +219,7 @@ export const CONFLICT_ACTION_CLAUSE = `## 矛盾冻结动作("conflict")
|
|
|
219
219
|
"loser": "另一方的 record_id"
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
- "winner" / "loser"
|
|
222
|
+
- "winner" / "loser":**二者必须不同**,且**恰有一方必须是本条新记忆的 record_id**;另一方取自「候选池中的已有记忆 record_id」或「同一批次其它新记忆的 record_id」。指向本批次之外的、不在候选池里的 id 一律无效。
|
|
223
223
|
- "winner" 只表示进入待裁决对时的排序位,**不代表最终结论**;最终结论由人工裁决写入。
|
|
224
224
|
- action 为 "conflict" 时**不要**输出 merged_content / merged_type / merged_priority / merged_timestamps——它们只属于 update / merge。
|
|
225
225
|
|
|
@@ -228,6 +228,7 @@ export const CONFLICT_ACTION_CLAUSE = `## 矛盾冻结动作("conflict")
|
|
|
228
228
|
- 新记忆更具体、更新、更权威,或能明确纠正旧记忆的错误 → 仍用 "update"。
|
|
229
229
|
- 新旧记忆信息互补且**不矛盾** → 仍用 "merge"。
|
|
230
230
|
- 只是同属一个主题但描述对象不同 → 仍用 "store"。
|
|
231
|
+
- 同批次两条新记忆只是**同一事实的不同表述**(互补、粒度不同、不互斥)→ 仍各自 "store"。**只有互相矛盾才算 conflict。**
|
|
231
232
|
|
|
232
233
|
conflict 只留给"两边都像是对的、机器判不了"的情况——它消耗人的注意力,不可滥用。`;
|
|
233
234
|
/**
|
|
@@ -50,13 +50,24 @@ export declare function buildConflictPair(input: ConflictPairInput): ConflictPai
|
|
|
50
50
|
* 三条都必需,缺一即无法停放,调用方须回落 `store`(信息绝不丢):
|
|
51
51
|
* ① winner / loser 都是非空 id;
|
|
52
52
|
* ② 二者**不同**——指向同一条记录是无效输出(承自 mneme 的 `validateDecisions`);
|
|
53
|
-
* ③
|
|
54
|
-
* (
|
|
55
|
-
*
|
|
53
|
+
* ③ **恰有一方是本条新记忆**(`recordId`),另一方是**可核实的对手**:
|
|
54
|
+
* 候选池里的已有记录(`knownIds`),**或同一批次里的另一条新记忆**(`batchIds`)。
|
|
55
|
+
*
|
|
56
|
+
* 关于 `batchIds`(2026-09-18 取证后放宽,见 findings R6 / Agent A):
|
|
57
|
+
* 此前另一方只认 `knownIds`(候选池 ∪ target_ids),而**同批次新记忆的 id 不在其中**
|
|
58
|
+
* ——它们是本轮刚生成的、尚未入库。于是"本轮两条新记忆互相矛盾"这种最典型的
|
|
59
|
+
* "机器判不了"情形,模型即便正确 emit 了 `conflict`,也**必然被判不成对而回落 store**。
|
|
60
|
+
* 实测:模型在生产 prompt 下对同批次矛盾 2/2 会 emit,但那一跳从未落库。
|
|
61
|
+
*
|
|
62
|
+
* 为何仍要求"恰有一方是 `recordId`":`conflict` 是**挂在某一条新记忆名下的决策**,
|
|
63
|
+
* 若允许"另外两条新记忆"配对,同一对会被每个兄弟重复申报一次。锁定一方为本条,
|
|
64
|
+
* 配对就唯一。
|
|
56
65
|
*
|
|
57
66
|
* 返回规范化后的 `{ winnerId, loserId }`,或 `null`(表示不构成冻结对)。
|
|
58
67
|
*/
|
|
59
|
-
export declare function validateConflictPair(recordId: string, winner: unknown, loser: unknown, knownIds: ReadonlySet<string
|
|
68
|
+
export declare function validateConflictPair(recordId: string, winner: unknown, loser: unknown, knownIds: ReadonlySet<string>,
|
|
69
|
+
/** 本批次其它新记忆的 record_id(同批次互斥也可冻结)。不传 = 维持旧行为。 */
|
|
70
|
+
batchIds?: ReadonlySet<string>): {
|
|
60
71
|
winnerId: string;
|
|
61
72
|
loserId: string;
|
|
62
73
|
} | null;
|
package/dist/store/conflicts.js
CHANGED
|
@@ -45,25 +45,35 @@ export function buildConflictPair(input) {
|
|
|
45
45
|
* 三条都必需,缺一即无法停放,调用方须回落 `store`(信息绝不丢):
|
|
46
46
|
* ① winner / loser 都是非空 id;
|
|
47
47
|
* ② 二者**不同**——指向同一条记录是无效输出(承自 mneme 的 `validateDecisions`);
|
|
48
|
-
* ③
|
|
49
|
-
* (
|
|
50
|
-
*
|
|
48
|
+
* ③ **恰有一方是本条新记忆**(`recordId`),另一方是**可核实的对手**:
|
|
49
|
+
* 候选池里的已有记录(`knownIds`),**或同一批次里的另一条新记忆**(`batchIds`)。
|
|
50
|
+
*
|
|
51
|
+
* 关于 `batchIds`(2026-09-18 取证后放宽,见 findings R6 / Agent A):
|
|
52
|
+
* 此前另一方只认 `knownIds`(候选池 ∪ target_ids),而**同批次新记忆的 id 不在其中**
|
|
53
|
+
* ——它们是本轮刚生成的、尚未入库。于是"本轮两条新记忆互相矛盾"这种最典型的
|
|
54
|
+
* "机器判不了"情形,模型即便正确 emit 了 `conflict`,也**必然被判不成对而回落 store**。
|
|
55
|
+
* 实测:模型在生产 prompt 下对同批次矛盾 2/2 会 emit,但那一跳从未落库。
|
|
56
|
+
*
|
|
57
|
+
* 为何仍要求"恰有一方是 `recordId`":`conflict` 是**挂在某一条新记忆名下的决策**,
|
|
58
|
+
* 若允许"另外两条新记忆"配对,同一对会被每个兄弟重复申报一次。锁定一方为本条,
|
|
59
|
+
* 配对就唯一。
|
|
51
60
|
*
|
|
52
61
|
* 返回规范化后的 `{ winnerId, loserId }`,或 `null`(表示不构成冻结对)。
|
|
53
62
|
*/
|
|
54
|
-
export function validateConflictPair(recordId, winner, loser, knownIds
|
|
63
|
+
export function validateConflictPair(recordId, winner, loser, knownIds,
|
|
64
|
+
/** 本批次其它新记忆的 record_id(同批次互斥也可冻结)。不传 = 维持旧行为。 */
|
|
65
|
+
batchIds) {
|
|
55
66
|
if (typeof winner !== 'string' || typeof loser !== 'string')
|
|
56
67
|
return null;
|
|
57
68
|
const w = winner.trim();
|
|
58
69
|
const l = loser.trim();
|
|
59
70
|
if (!w || !l || w === l)
|
|
60
71
|
return null;
|
|
61
|
-
// ③
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
72
|
+
// ③ 恰有一方是本条新记忆(另一方必然是别的 id,因为 w !== l)
|
|
73
|
+
const other = w === recordId ? l : l === recordId ? w : '';
|
|
74
|
+
if (!other)
|
|
64
75
|
return null;
|
|
65
|
-
|
|
66
|
-
if (!knownIds.has(other))
|
|
76
|
+
if (!knownIds.has(other) && !batchIds?.has(other))
|
|
67
77
|
return null;
|
|
68
78
|
return { winnerId: w, loserId: l };
|
|
69
79
|
}
|