@x-otto/memory 0.0.1-alpha.2 → 0.0.1-alpha.4
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/README.md +28 -19
- package/dist/index.d.ts +501 -53
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -92
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import { Message } from "@x-otto/interchange";
|
|
2
2
|
|
|
3
|
+
//#region src/compaction-truncation.d.ts
|
|
4
|
+
declare function formatMessageForSummary(msg: Message): string;
|
|
5
|
+
/**
|
|
6
|
+
* RFC-381 M3:压缩阶段 spill 端口(可选)。超大可再生 tool_result 在截断前先全文落盘,
|
|
7
|
+
* 返回路径字符串;返回 undefined = 无端口/失败(fail-open 退回内存截断)。
|
|
8
|
+
* 实现侧复用执行时 spill(tool-result-store),与 prune.ts 的 SPILL_MARKER 占位符同格式。
|
|
9
|
+
*/
|
|
10
|
+
type CompactionSpillSink = (toolCallId: string, text: string) => Promise<string | undefined>;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/coverage-governor.d.ts
|
|
13
|
+
/**
|
|
14
|
+
* coverage-governor.ts —— RFC-324 D3 M3:摘要覆盖率闭环状态(per-session)。
|
|
15
|
+
*
|
|
16
|
+
* 从 `compaction.ts` 模块级 Map 迁出(RFC-394 M1):coverage 递推状态归属实例,
|
|
17
|
+
* 消除模块级可变全局状态(多实例交叉污染隐患 + 自由函数隐藏状态依赖 + 测试需
|
|
18
|
+
* 手动清理全局 Map)。M2 将随 CompactionBackend 构造注入。
|
|
19
|
+
*
|
|
20
|
+
* 语义(RFC-324 D3 M3):上次压缩 coverage < warn/error 时,下次压缩调大批次上限
|
|
21
|
+
* (每批更小源内容 → 提高覆盖率)。**仅递推一次**(consume 后清零),避免持续
|
|
22
|
+
* 放大导致批次数无界。
|
|
23
|
+
*/
|
|
24
|
+
declare class CoverageGovernor {
|
|
25
|
+
/** per-session 调大批次数(正常值 0 = 不调整)。 */
|
|
26
|
+
private readonly boostedBySession;
|
|
27
|
+
/** 指定 session 的调大批次数(正常值 0 = 不调整)。 */
|
|
28
|
+
get(sessionId: string): number;
|
|
29
|
+
/**
|
|
30
|
+
* 记录本次压缩 coverage,供下次压缩调批次上限(RFC-324 D3 M3)。
|
|
31
|
+
* coverage < error → 双重调大(x2,下限 16);error..warn → 调大(+2);>= warn → 不干预。
|
|
32
|
+
*/
|
|
33
|
+
record(coverage: number, currentBatches: number, sessionId: string, errorThreshold: number, warnThreshold: number): void;
|
|
34
|
+
/** 读取并清零(仅递推一次)。delete 而非 set(0)——值归 0 时 key 永久残留(08-12 review W4)。 */
|
|
35
|
+
consume(sessionId: string): number;
|
|
36
|
+
/** 会话销毁时清理其条目(MemoryManager.forgetSession 调用)——覆盖「set 后未 consume 即 forget」的残留。 */
|
|
37
|
+
forget(sessionId: string): void;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
3
40
|
//#region src/types.d.ts
|
|
4
41
|
/**
|
|
5
42
|
* 触发阈值的三种表达。
|
|
@@ -36,10 +73,30 @@ interface PruneConfig {
|
|
|
36
73
|
* 裁掉=真实信息损失。空列表 = 不裁任何 tool_result。
|
|
37
74
|
*/
|
|
38
75
|
regenerableTools: string[];
|
|
76
|
+
/**
|
|
77
|
+
* RFC-437 D2:可再生名单的**惰性 getter**(可选)——每次 prune 实时读取,与静态
|
|
78
|
+
* `regenerableTools` 并集(getter 为工具声明真源,静态名单回退兜底 legacy 名)。
|
|
79
|
+
* 由装配侧注入 `() => toolRegistry.listRegenerableNames()`:内置 + 插件工具的
|
|
80
|
+
* `regenerableOutput: true` 声明即时生效(插件晚于 memory 构造装载也能被 prune
|
|
81
|
+
* 看见)。缺省未提供 = 现状行为逐字节不变。
|
|
82
|
+
*/
|
|
83
|
+
regenerableToolNames?: () => string[];
|
|
39
84
|
/** 二次保护:即便在 regenerableTools 内,列入此处的工具仍不裁(覆盖)。 */
|
|
40
85
|
protectedTools: string[];
|
|
41
86
|
truncateTools: string[];
|
|
42
87
|
truncateMaxLength: number;
|
|
88
|
+
/**
|
|
89
|
+
* RFC review D8:时间基 microcompact(可选,缺省 disabled = 零行为变化)。
|
|
90
|
+
* 参照 CC `timeBasedMCConfig.ts`:距最后一条 assistant 消息的 gap 超过阈值(默认 60min,
|
|
91
|
+
* 服务端 1h prompt cache 必过期)→ 下一轮请求前清旧 tool_result,保留最近 keepRecent 条。
|
|
92
|
+
* 与 token 触发正交(OR 关系)——时间触发不依赖上下文大小,cache 过期窗口内任何旧
|
|
93
|
+
* 工具输出重写都是纯收益。
|
|
94
|
+
*/
|
|
95
|
+
timeBased?: {
|
|
96
|
+
enabled?: boolean; /** gap 阈值(ms)。默认 60min。 */
|
|
97
|
+
gapThresholdMs?: number; /** 保留最近 N 条 compactable tool_result(更早的全部裁)。缺省 5。 */
|
|
98
|
+
keepRecent?: number;
|
|
99
|
+
};
|
|
43
100
|
}
|
|
44
101
|
interface PruneResult {
|
|
45
102
|
messages: Message[];
|
|
@@ -79,15 +136,66 @@ interface CompactionConfig {
|
|
|
79
136
|
*/
|
|
80
137
|
coverageErrorThreshold?: number;
|
|
81
138
|
/**
|
|
82
|
-
* RFC-340 D1
|
|
83
|
-
*
|
|
84
|
-
*
|
|
139
|
+
* RFC-340 D1:摘要覆盖率**硬下限**。低于此值时摘要正文前置"低保真"标注
|
|
140
|
+
* (`buildLowCoverageNotice`),如实告知模型这段摘要并不完整——边界照常建立。
|
|
141
|
+
* (一版设计"低于 floor 即不建边界"已被 RFC-321 M6/C1 回归测试证伪:不建边界 →
|
|
142
|
+
* escapeValveFailures 累加 → 放行 lossy 裸裁剪,比 6% 覆盖的摘要严格更差。)
|
|
143
|
+
* 缺省 `MEMORY_COVERAGE_FLOOR_THRESHOLD`(0.3)。
|
|
85
144
|
*
|
|
86
145
|
* 不变式 `floor ≤ error ≤ warn`——floor 是叠加在既有 warn/error 递推之上的**新档位**,
|
|
87
146
|
* 不替换它们(RFC-340 规则 4)。接线层负责钳制,防配置写反导致档位失效。
|
|
88
147
|
*/
|
|
89
148
|
coverageFloorThreshold?: number;
|
|
90
149
|
customInstructions?: string;
|
|
150
|
+
/**
|
|
151
|
+
* RFC-387 前缀复用开关(2026-08-14 M2b 方案 A 修订)。true 时 compact() 的全部
|
|
152
|
+
* summarize 调用点携带 `prefix`(单批带 sessionId+messages;map/merge/turn-prefix
|
|
153
|
+
* 仅 sessionId)——装配方据 sessionId 查 wire 快照、原样重放 system 分段 + tools +
|
|
154
|
+
* cacheRetention(消息仍在指令文本内)。缺省 false = 现状行为逐字节不变。
|
|
155
|
+
*/
|
|
156
|
+
prefixReuse?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* RFC-436 G2:压缩请求前缀的 cacheRetention 档位(TTL 决定前缀能否命中 provider
|
|
159
|
+
* KV 缓存)。**不随正常轮次快照**——压缩是低频大 prefill 操作,正常轮次非交互模式
|
|
160
|
+
* 用 'short'(5min TTL),压缩距上轮请求通常远超 5 分钟,随快照必然过期(前缀复用
|
|
161
|
+
* 形同虚设)。缺省 'long'(1h TTL,DEFAULT_COMPACTION_CONFIG 定义)。宿主可按场景
|
|
162
|
+
* 覆盖(如 provider 对 long 收费异常时临时切 'short')。
|
|
163
|
+
*/
|
|
164
|
+
cacheRetention?: CompactionCacheRetention;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* RFC-436 G2:压缩请求前缀 TTL 档位(与 provider 侧 CacheRetention 结构兼容,
|
|
168
|
+
* memory 是叶包不依赖 provider,本地定义联合类型)。
|
|
169
|
+
*/
|
|
170
|
+
type CompactionCacheRetention = 'none' | 'short' | 'long';
|
|
171
|
+
/**
|
|
172
|
+
* RFC-387 D1:压缩请求的可缓存前缀(wire 形态快照的子集)。
|
|
173
|
+
*
|
|
174
|
+
* - `messages`:待摘要消息原样重放——**不得改写任何消息内容**(RFC-142 R1 强化面);
|
|
175
|
+
* - `systemPromptSegments`/`tools`/`modelId`:由调用方(M2b 快照)在装配时合并,
|
|
176
|
+
* memory 侧只透传不消费(交接契约:快照的 wire 消息形态与 compact() 切点对齐是
|
|
177
|
+
* M2b 的字节门禁)。
|
|
178
|
+
*/
|
|
179
|
+
interface CompactionPrefix {
|
|
180
|
+
/**
|
|
181
|
+
* RFC-387 M2b(方案 A):压缩所属会话 id——装配方据此查 wire 快照
|
|
182
|
+
* (system 分段 + tools + cacheRetention)。缺省 = 不装配(现状行为)。
|
|
183
|
+
*/
|
|
184
|
+
sessionId?: string;
|
|
185
|
+
/**
|
|
186
|
+
* RFC-387 D5(消息级复用地基,M2b 方案 A 不消费):待摘要消息原样引用。
|
|
187
|
+
* 仅单批路径传递;多批各批内容不同,不传。
|
|
188
|
+
*/
|
|
189
|
+
messages?: readonly Message[];
|
|
190
|
+
/**
|
|
191
|
+
* RFC-436 G2:装配方用于压缩请求的 cacheRetention(缺省 'long')。memory 侧
|
|
192
|
+
* 从 CompactionConfig.cacheRetention 注入;不随快照(快照是正常轮次的 retention,
|
|
193
|
+
* 对低频压缩请求 TTL 过短)。
|
|
194
|
+
*/
|
|
195
|
+
cacheRetention?: CompactionCacheRetention;
|
|
196
|
+
systemPromptSegments?: readonly unknown[];
|
|
197
|
+
tools?: readonly unknown[];
|
|
198
|
+
modelId?: string;
|
|
91
199
|
}
|
|
92
200
|
interface CutPoint {
|
|
93
201
|
firstKeptIndex: number;
|
|
@@ -135,6 +243,17 @@ interface ArchiveStorage {
|
|
|
135
243
|
*/
|
|
136
244
|
forgetSession?(sessionId: string): void;
|
|
137
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* RFC-381 M1:跨进程 durable 压缩锁端口。
|
|
248
|
+
* 实现侧(runtime)写 session 树 `compaction-lock` entry(phase start/end + token,
|
|
249
|
+
* append-only 历史不可变);本接口是 memory 包与实现之间的薄契约。
|
|
250
|
+
* - acquire 返回 false = 当前持锁(含孤儿锁未超时)→ 调用方跳过本轮压缩(下轮重试);
|
|
251
|
+
* - release 幂等(end 已写/锁已被接管时 no-op 返回)。
|
|
252
|
+
*/
|
|
253
|
+
interface CompactionLockPort {
|
|
254
|
+
acquire(sessionId: string, token: string): Promise<boolean>;
|
|
255
|
+
release(sessionId: string, token: string): Promise<void>;
|
|
256
|
+
}
|
|
138
257
|
interface ArchiveEntry {
|
|
139
258
|
path: string;
|
|
140
259
|
timestamp: number;
|
|
@@ -172,6 +291,19 @@ interface ContextTokenEstimate {
|
|
|
172
291
|
}
|
|
173
292
|
interface MemoryManagerConfig {
|
|
174
293
|
sources?: MemorySource[];
|
|
294
|
+
/**
|
|
295
|
+
* 确定性时钟端口(M-2)。缺省 = 真实系统时钟(行为不变);replay/测试注入固定时钟,
|
|
296
|
+
* 使 prune gap 计算、压缩 token、归档时间戳等 Date.now() 派生路径可确定重放。
|
|
297
|
+
*/
|
|
298
|
+
clock?: {
|
|
299
|
+
now(): number;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* RFC-382 D1:索引条目文件 mtime 映射(key=`memory/<slug>.md`,与索引行括号内一致)。
|
|
303
|
+
* 由宿主(coding storage-wiring,有 fs 能力)在组装 sources 时 statSync 采集后注入;
|
|
304
|
+
* 缺省 undefined = 不折叠(remote/无本地 fs 场景 fail-open,见 buildMemoryInjection)。
|
|
305
|
+
*/
|
|
306
|
+
entryMtimes?: Map<string, number>;
|
|
175
307
|
memoryStore?: MemoryStore;
|
|
176
308
|
compaction?: Partial<CompactionConfig>;
|
|
177
309
|
prune?: Partial<PruneConfig>;
|
|
@@ -191,6 +323,11 @@ interface MemoryManagerConfig {
|
|
|
191
323
|
summarize: (prompt: string, options?: {
|
|
192
324
|
maxTokens?: number;
|
|
193
325
|
signal?: AbortSignal;
|
|
326
|
+
/**
|
|
327
|
+
* RFC-387 D1:可选可缓存前缀。缺省 = 现状(prompt 含全部内容,逐字节等价);
|
|
328
|
+
* 传入时实现方须把 `prefix` 原样置于 prompt 之前作为请求前缀(M2b 装配)。
|
|
329
|
+
*/
|
|
330
|
+
prefix?: CompactionPrefix;
|
|
194
331
|
}) => Promise<string>;
|
|
195
332
|
estimateTokens?: (text: string) => number;
|
|
196
333
|
model?: {
|
|
@@ -226,6 +363,20 @@ interface MemoryManagerConfig {
|
|
|
226
363
|
* 模式对齐同文件的 `getContextWindow`/`getEstimatedBytes`(实现侧注入避免跨包依赖)。
|
|
227
364
|
*/
|
|
228
365
|
getMaxContentBytes?: () => number | undefined;
|
|
366
|
+
/**
|
|
367
|
+
* RFC-381 M1:跨进程 durable 压缩锁(可选注入)。
|
|
368
|
+
* 有端口 → process() 压缩前 acquire(false = 他进程/本进程持锁,跳过本轮)、
|
|
369
|
+
* 压缩结束 finally release;无端口 → 退回进程内 Set(单进程向后兼容)。
|
|
370
|
+
* 实现侧(runtime)写 session 树 compaction-lock entry(start/end bracket + token),
|
|
371
|
+
* 崩溃后孤儿 start 可被检测接管。
|
|
372
|
+
*/
|
|
373
|
+
compactionLock?: CompactionLockPort;
|
|
374
|
+
/**
|
|
375
|
+
* RFC-381 M3:压缩阶段 spill 端口(可选)。P3 可再生 tool_result 截断前先全文落盘 +
|
|
376
|
+
* 占位符(read 可恢复),压缩从有损丢弃升级为无损移出上下文。未注入 → 退回既有
|
|
377
|
+
* 内存头尾截断(与改动前行为一致)。
|
|
378
|
+
*/
|
|
379
|
+
compactionSpill?: CompactionSpillSink;
|
|
229
380
|
/**
|
|
230
381
|
* RFC-324 D3:摘要覆盖率阈值的惰性读数。
|
|
231
382
|
*
|
|
@@ -244,6 +395,28 @@ interface MemoryManagerConfig {
|
|
|
244
395
|
* 读取器,跟随活跃会话)。与 `maxContentBytes` 配对注入,缺一不启用字节因子。
|
|
245
396
|
*/
|
|
246
397
|
getEstimatedBytes?: () => number | undefined;
|
|
398
|
+
/**
|
|
399
|
+
* RFC-390 D2:provider 真实 input token 读数(惰性,跟随最近一轮 usage)。
|
|
400
|
+
*
|
|
401
|
+
* 注入后 `needsCompaction` 在 `estimateContextTokens` 的混合口径之外,
|
|
402
|
+
* 额外参考真实 input tokens——当真实 input(total 口径)已达软触发线 90%
|
|
403
|
+
* 时提前触发压缩,不等启发式估算追上。解决空响应轮 `record()` 跳过导致
|
|
404
|
+
* `lastTokenUsage` 滞后的盲区(模型返回空响应时 usage 不更新,估算可能仍判
|
|
405
|
+
* "不需压缩",上下文继续膨胀到模型退化)。
|
|
406
|
+
*
|
|
407
|
+
* **坐标系(RFC-339 B1 教训)**:返回值是 **total 口径**(含 cache 前缀),
|
|
408
|
+
* 与 `estimateContextTokens` 的 `total` 同维度。软触发线 `triggerTokens`
|
|
409
|
+
* 是 bodyAfterPrefix 口径——使用时须换算:`realInput >= triggerTokens * 0.9`
|
|
410
|
+
* 是保守近似(realInput 含前缀,triggerTokens 不含——保守侧误差,不会误触发)。
|
|
411
|
+
* 未注入时回退纯估算(向后兼容)。
|
|
412
|
+
*/
|
|
413
|
+
getRealInputTokens?: () => number | undefined;
|
|
414
|
+
/**
|
|
415
|
+
* RFC-394 M1:coverage 闭环状态(可选注入)。缺省 MemoryManager 自建实例。
|
|
416
|
+
* 注入面供测试读取 per-session boost 值(替代已删除的 `__getCoverageBoostedBatchesForTest`
|
|
417
|
+
* 测试后门)——测试创建 governor 传入并断言其状态;生产无需注入。
|
|
418
|
+
*/
|
|
419
|
+
coverageGovernor?: CoverageGovernor;
|
|
247
420
|
}
|
|
248
421
|
interface MemoryDefaults {
|
|
249
422
|
compaction: CompactionConfig;
|
|
@@ -254,13 +427,11 @@ interface MemoryDefaults {
|
|
|
254
427
|
declare class MemoryManager {
|
|
255
428
|
private readonly persistent;
|
|
256
429
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
430
|
+
* RFC-394 M4:归档源——统一接口(替代原 archiveStorage + loadSessionEntries +
|
|
431
|
+
* messageFromEntry 三字段双路径 try/fallback)。构造期决定用哪个实现
|
|
432
|
+
* (SessionEntriesArchiveSource / InMemoryArchiveSource),不再运行时分支。
|
|
259
433
|
*/
|
|
260
|
-
private readonly
|
|
261
|
-
private readonly loadSessionEntries?;
|
|
262
|
-
private readonly messageFromEntry?;
|
|
263
|
-
private readonly summarize;
|
|
434
|
+
private readonly archiveSource;
|
|
264
435
|
private readonly estimateTokens;
|
|
265
436
|
/** 构造时保留原始配置副本,供 reconfigure 用新窗口重算阈值。 */
|
|
266
437
|
private readonly rawConfig;
|
|
@@ -274,9 +445,9 @@ declare class MemoryManager {
|
|
|
274
445
|
/** RFC-321 M3:生效字节预算惰性读数(跟随 ResidencyGovernor 收紧档位)。 */
|
|
275
446
|
private readonly getMaxContentBytes?;
|
|
276
447
|
private readonly getEstimatedBytes?;
|
|
448
|
+
/** RFC-390 D2:provider 真实 input token 读数(惰性)。 */
|
|
449
|
+
private readonly getRealInputTokens?;
|
|
277
450
|
private readonly previousSummaries;
|
|
278
|
-
/** archive Path 单调发号——每会话内独立(archive:{sessionId}:{1..N})。 */
|
|
279
|
-
private readonly archiveIdCounter;
|
|
280
451
|
/**
|
|
281
452
|
* 终局架构 review 建议优化项:并发 process() 门闩(per-session)。
|
|
282
453
|
*
|
|
@@ -289,6 +460,25 @@ declare class MemoryManager {
|
|
|
289
460
|
* 延后一轮),finally 保证异常路径也会复位。
|
|
290
461
|
*/
|
|
291
462
|
private readonly compactionInFlight;
|
|
463
|
+
/** RFC-381 M1:跨进程 durable 压缩锁端口(可选;缺省退回进程内 Set)。 */
|
|
464
|
+
private readonly compactionLock?;
|
|
465
|
+
/**
|
|
466
|
+
* RFC-381 M3:压缩阶段 spill 端口(可选;缺省退回既有内存截断)。
|
|
467
|
+
* **公开只读**——生产链透传回归门(coding m13-storage-wiring.test.ts)断言此字段:
|
|
468
|
+
* App 组合根注入的 spill 必须真到达 MemoryManager(2026-08-14 曾发生断链:
|
|
469
|
+
* buildDefaultMemory 解构/传参漏了 compactionSpill,spill 静默失效)。
|
|
470
|
+
*/
|
|
471
|
+
readonly compactionSpill?: CompactionSpillSink;
|
|
472
|
+
/** RFC-394 M1:coverage 闭环状态——实例归属(config 可注入供测试读取,缺省自建)。 */
|
|
473
|
+
private readonly coverageGovernor;
|
|
474
|
+
/** RFC-394 M2:压缩执行后端(策略与编排分离——本类只编排,执行归 backend)。 */
|
|
475
|
+
private readonly compactionBackend;
|
|
476
|
+
/** RFC-394 M3:压缩触发策略列表(OR 合并)。 */
|
|
477
|
+
private readonly compactionPolicies;
|
|
478
|
+
/** RFC-394 M3:裁剪触发策略列表(OR 合并)。 */
|
|
479
|
+
private readonly prunePolicies;
|
|
480
|
+
/** M-2:确定性时钟端口(缺省真实系统时钟)。 */
|
|
481
|
+
private readonly clock;
|
|
292
482
|
constructor(config: MemoryManagerConfig);
|
|
293
483
|
/**
|
|
294
484
|
* `/clear`——驱逐本会话的滚动压缩摘要桶(L2 工作记忆)。L1 持久知识(persistent)
|
|
@@ -297,7 +487,7 @@ declare class MemoryManager {
|
|
|
297
487
|
clearSession(sessionId?: string): void;
|
|
298
488
|
/**
|
|
299
489
|
* 审计修复:会话销毁/驱逐时遗忘其全部 per-session 状态——防三个 Map 在长跑进程中无界增长。
|
|
300
|
-
* 与 clearSession 不同:此处清
|
|
490
|
+
* 与 clearSession 不同:此处清 archive 会话终态数据(无再压缩可能)。
|
|
301
491
|
*/
|
|
302
492
|
forgetSession(sessionId: string): void;
|
|
303
493
|
/**
|
|
@@ -313,6 +503,14 @@ declare class MemoryManager {
|
|
|
313
503
|
* prune/compaction 一切触发线均按此计算,避免构造期 model 未知导致的 200K 默认锁死。
|
|
314
504
|
*/
|
|
315
505
|
private effectiveContextWindow;
|
|
506
|
+
/**
|
|
507
|
+
* 生效压缩配置——CompactionBackend 经 getConfig 惰性读取。
|
|
508
|
+
* 装配面:reconfigure 更新 + 生效窗口 + coverage 阈值惰性读(settings 在 App
|
|
509
|
+
* 构造后才 load,静态快照会永远是 undefined;未注入或字段缺省时回退 memory 侧常量)。
|
|
510
|
+
* RFC-394 M2:原 compact() 调用处的内联合并逻辑迁入此处,backend 只做
|
|
511
|
+
* `{ ...DEFAULT, ...getConfig() }` 兜底。
|
|
512
|
+
*/
|
|
513
|
+
private effectiveCompactionConfig;
|
|
316
514
|
/**
|
|
317
515
|
* 主动检查当前消息量是否超过压缩触发线(基于 reconfigure 后的新窗口);
|
|
318
516
|
* 超过则 force-compact。用于模型切换时预防下一轮 413。
|
|
@@ -335,16 +533,22 @@ declare class MemoryManager {
|
|
|
335
533
|
private pruneWith;
|
|
336
534
|
needsCompaction(messages: readonly Message[]): boolean;
|
|
337
535
|
/**
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*/
|
|
347
|
-
private
|
|
536
|
+
* 驻留(字节)压力是否活跃——供 process() 的 force keepRecent 钳制与日志归因用。
|
|
537
|
+
* RFC-394 M3:直接用 ResidencyPolicy 判定(策略对象复用,不内联逻辑)。
|
|
538
|
+
*/
|
|
539
|
+
private isResidencyPressureActive;
|
|
540
|
+
/**
|
|
541
|
+
* RFC-394 M3:触发判定上下文装配——一次性读取所有策略所需的状态(estimate /
|
|
542
|
+
* 生效窗口 / 配置 / 时钟 / realInputTokens / 字节预算 / 字节估算 / timeGapMs),
|
|
543
|
+
* 供所有策略共用,消除 needsCompaction 与 needsPruneWith 各自读状态的重复。
|
|
544
|
+
*/
|
|
545
|
+
private buildTriggerContext;
|
|
546
|
+
/**
|
|
547
|
+
* RFC review D8 / RFC-394 M3:距最后一条带时间戳消息(user/tool_result/
|
|
548
|
+
* system_notification——assistant 消息由模型生成无 timestamp)的 gap(ms)。
|
|
549
|
+
* 单一真源(timeGapTriggered 与 pruneWith 共用,消除重复倒序遍历)。
|
|
550
|
+
*/
|
|
551
|
+
private computeTimeGapMs;
|
|
348
552
|
isEligibleForManualCompact(messages: readonly Message[]): boolean;
|
|
349
553
|
/**
|
|
350
554
|
* @param options.force RFC-321 R10:强制压缩语义下 keepRecent 钳制到当前总量一半,
|
|
@@ -356,9 +560,8 @@ declare class MemoryManager {
|
|
|
356
560
|
}): CompactionPreparation | null;
|
|
357
561
|
compact(preparation: CompactionPreparation, sessionId?: string, signal?: AbortSignal): Promise<CompactionResult>;
|
|
358
562
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* (有重建器则归档按需从会话条目还原,archivePath 只作逻辑引用)。
|
|
563
|
+
* RFC-394 M4:归档记录委托给 ArchiveSource(SessionEntriesArchiveSource 或
|
|
564
|
+
* InMemoryArchiveSource)——MemoryManager 不再做形态分支。
|
|
362
565
|
*/
|
|
363
566
|
private recordCompactionArchive;
|
|
364
567
|
process(messages: readonly Message[], sessionId?: string, signal?: AbortSignal, options?: {
|
|
@@ -379,11 +582,7 @@ declare class MemoryManager {
|
|
|
379
582
|
};
|
|
380
583
|
}>;
|
|
381
584
|
listArchives(sessionId: string): Promise<ArchiveEntry[]>;
|
|
382
|
-
/** 从 session entries 的 compaction 节点枚举 ArchiveEntry 列表。 */
|
|
383
|
-
private listArchivesFromEntries;
|
|
384
585
|
readArchive(archivePath: string): Promise<string>;
|
|
385
|
-
/** 从 session entries 的 compaction 边界间重建归档全文。 */
|
|
386
|
-
private readArchiveFromEntries;
|
|
387
586
|
dispose(): void;
|
|
388
587
|
}
|
|
389
588
|
declare function createMemoryManager(config: MemoryManagerConfig): MemoryManager;
|
|
@@ -393,9 +592,13 @@ declare const DEFAULT_MEMORY_SOURCES: MemorySource[];
|
|
|
393
592
|
declare class PersistentMemory {
|
|
394
593
|
private readonly store;
|
|
395
594
|
private readonly sources;
|
|
595
|
+
/** RFC-382 D1:索引条目 mtime 映射(见 MemoryManagerConfig.entryMtimes)。 */
|
|
596
|
+
private readonly entryMtimes?;
|
|
396
597
|
private memories;
|
|
397
598
|
private unwatch?;
|
|
398
|
-
constructor(store: MemoryStore, sources?: MemorySource[]
|
|
599
|
+
constructor(store: MemoryStore, sources?: MemorySource[], /** RFC-382 D1:索引条目 mtime 映射(见 MemoryManagerConfig.entryMtimes)。 */
|
|
600
|
+
|
|
601
|
+
entryMtimes?: Map<string, number> | undefined);
|
|
399
602
|
load(): Promise<void>;
|
|
400
603
|
reload(): Promise<void>;
|
|
401
604
|
getInjection(): string;
|
|
@@ -702,7 +905,8 @@ declare function estimateMessagesTokens(messages: readonly Message[], estimator?
|
|
|
702
905
|
//#region src/prune.d.ts
|
|
703
906
|
declare function prune(messages: readonly Message[], contextWindow: number, config?: Partial<PruneConfig>, estimator?: typeof estimateTokens, opts?: {
|
|
704
907
|
currentTokens?: number;
|
|
705
|
-
force?: boolean;
|
|
908
|
+
force?: boolean; /** RFC review D8:距最后一条 assistant 消息的时间 gap(ms)——时间基 microcompact 触发输入。 */
|
|
909
|
+
timeGapMs?: number;
|
|
706
910
|
}): PruneResult;
|
|
707
911
|
//#endregion
|
|
708
912
|
//#region src/archive.d.ts
|
|
@@ -732,10 +936,56 @@ declare const DEFAULT_COMPACTION_CONFIG: CompactionConfig;
|
|
|
732
936
|
declare const DEFAULT_PRUNE_CONFIG: PruneConfig;
|
|
733
937
|
declare function resolveContextSize(size: ContextSize, contextWindow: number): number;
|
|
734
938
|
//#endregion
|
|
939
|
+
//#region src/memory-injection.d.ts
|
|
940
|
+
/**
|
|
941
|
+
* memory-injection.ts —— RFC-394 M6/P1-11:`<agent_memory>` 段注入逻辑。
|
|
942
|
+
*
|
|
943
|
+
* 从 `prompts.ts` 拆出——prompts.ts 只留 prompt 模板(摘要/抽取/turn-prefix),
|
|
944
|
+
* 本文件负责「多源记忆 → 带预算的 `<agent_memory>` 注入文本」:
|
|
945
|
+
* - token 预算记账(CJK 加权)
|
|
946
|
+
* - 二分截断到 code-point 边界
|
|
947
|
+
* - RFC-382 D1 索引行折叠(mtime 窗口)
|
|
948
|
+
* - `<memory_guidelines>` 尾部追加
|
|
949
|
+
*
|
|
950
|
+
* 公开导出不变(barrel re-export,消费方零破坏)。
|
|
951
|
+
*/
|
|
952
|
+
/**
|
|
953
|
+
* `<agent_memory>` 段的默认总预算(tokens,粗估)。
|
|
954
|
+
*
|
|
955
|
+
* 本仓正常场景(AGENTS.md 三源 + AutoMemory 索引)实测 ~3.4k tokens;monorepo
|
|
956
|
+
* `discoverAgentsSources` 理论上限是 10 文件 × 64KB = 640KB,若真的堆到那个量级会把
|
|
957
|
+
* system prompt 撑爆。这里的预算不是替代 discoverAgentsSources 的单文件/文件数上限
|
|
958
|
+
* (那层继续管),而是在全部来源(含它管不到的 writable AGENTS.md / AutoMemory 索引)
|
|
959
|
+
* 汇总之后再把一道总闸——`buildMemoryInjection` 是唯一收口点,别处管不到的这里管。
|
|
960
|
+
*
|
|
961
|
+
* 取 3.4k 实测值的 ~2.3 倍留余量,同时把极端场景压掉 95%+。
|
|
962
|
+
*/
|
|
963
|
+
declare const DEFAULT_MEMORY_INJECTION_BUDGET_TOKENS = 8000;
|
|
964
|
+
declare function buildMemoryInjection(memories: Map<string, string>, budgetTokens?: number, entryMtimes?: Map<string, number>, nowMs?: number): string;
|
|
965
|
+
//#endregion
|
|
735
966
|
//#region src/prompts.d.ts
|
|
736
|
-
|
|
967
|
+
/**
|
|
968
|
+
* RFC-394 M6/P1-5:SUMMARIZATION_PROMPT 收敛为壳 + SUMMARY_BODY_FORMAT(与其他变体一致,
|
|
969
|
+
* 消除双模板源的细微措辞漂移风险)。测试断言 `SUMMARIZATION_PROMPT` 含
|
|
970
|
+
* `'## All User Requests'` / `'EVERY distinct request'` —— SUMMARY_BODY_FORMAT 已含。
|
|
971
|
+
*/
|
|
972
|
+
declare const SUMMARIZATION_PROMPT = "You are a conversation summarizer for an AI coding assistant. Your task is to create a structured summary of the conversation that preserves all information needed to continue the work.\n\nCreate the summary in the following format:\n\n## Goal\n[What is the user trying to accomplish?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements the user mentioned]\n\n## All User Requests\n- [List EVERY distinct request/instruction the user made, in order, that is not a tool result. This is the authoritative record of user intent \u2014 multi-round compaction must never let an earlier request silently drop. Quote the user's own wording for each; do not paraphrase away specifics.]\n\n## Progress\n### Done\n- [x] [Completed work items]\n### In Progress\n- [ ] [Work in progress]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Planned next steps. For the immediate next action, include a VERBATIM quote of the most recent user message or your own last stated intent that it derives from \u2014 this anchors the task and prevents interpretation drift across compactions.]\n\n## File Operations\n### Read\n- [Files that were read]\n### Modified\n- [Files that were modified]\n\n## Critical Context\n- [Any critical context needed to continue the work]\n\nIMPORTANT:\n- Preserve exact file paths, function names, error messages, and code snippets\n- Keep technical details precise \u2014 do not generalize\n- Include all tool call results that affect the current state\n- Note any pending or failed operations";
|
|
737
973
|
declare const UPDATE_SUMMARIZATION_PROMPT = "You are updating an existing conversation summary with new information. The existing summary and new messages are provided below.\n\nProduce a single UPDATED summary that stays BOUNDED \u2014 a summary that grows without limit across rounds eventually gets hard-truncated by the output cap, silently losing whatever fell past the limit. Keep it tight by actively triaging, not by appending.\n\nKeep (never drop):\n- Every distinct user request/instruction (the \"All User Requests\" record is authoritative)\n- Unfinished, in-progress, or blocked work\n- Decisions not yet verified, and anything the user corrected you on\n- Exact file paths, function names, error messages, code snippets still in play\n\nDrop / compress (make room):\n- Work that is both completed AND verified \u2014 collapse to a one-line outcome\n- Superseded intermediate attempts and abandoned approaches\n- Redundant restatements already captured elsewhere in the summary\n\nRules:\n- Move completed \"In Progress\" items to \"Done\", then compress \"Done\" per the above\n- Merge new work items, decisions, and context; do not simply append\n- Keep the same structured format (including \"All User Requests\")\n- If any information conflicts, use the newer version";
|
|
738
974
|
declare const TURN_PREFIX_SUMMARIZATION_PROMPT = "You are summarizing the FIRST PART of an assistant turn that was split during conversation compaction. The remaining part of this turn is still in the conversation.\n\nCreate a brief summary in this format:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key actions completed in the prefix portion]\n\n## Context for Remaining Messages\n- [Information needed to understand the remaining suffix messages]\n\nKeep it concise \u2014 this will be prepended to the remaining messages of this turn.";
|
|
975
|
+
/**
|
|
976
|
+
* RFC-339 D3:游标感知提示词的三个变体(选择逻辑见 `pickVariant`)。
|
|
977
|
+
*
|
|
978
|
+
* 缺陷背景(RFC-339 §1.3 缺陷 D):`prepareCompaction` 早已算出 `preservedMessages`
|
|
979
|
+
* (切点之后原样保留、会拼在摘要之后一起送回模型的近期消息),但提示词从不告诉模型
|
|
980
|
+
* 这个边界的存在。后果:①模型把保留区内容也概括进摘要 → 重复占位、概括版与原文版
|
|
981
|
+
* 并存易矛盾;②模型无法产出「读懂保留区所需的前置信息」这种面向续作的摘要。
|
|
982
|
+
*
|
|
983
|
+
* - FULL:无保留区(全量折叠,罕见)。等价于原 SUMMARIZATION_PROMPT。
|
|
984
|
+
* - WITH_PRESERVED_TAIL:**主路径**。显式告知其后 N 条原样保留、不要概括它们,
|
|
985
|
+
* 并要求产出「保留区依赖的前置信息」段。
|
|
986
|
+
* - SEGMENT:分批(map-reduce)的 map 阶段单段摘要,显式告知「这是第 i/N 段、不是
|
|
987
|
+
* 全部对话」,禁止输出面向全局的 next-step 判断(那属于 reduce 合并阶段)。
|
|
988
|
+
*/
|
|
739
989
|
declare const WITH_PRESERVED_TAIL_PROMPT = "You are a conversation summarizer for an AI coding assistant. You are summarizing the EARLIER portion of a conversation. The most recent messages are kept verbatim and will follow your summary unchanged \u2014 they are shown to you below in <preserved_tail_context> FOR REFERENCE ONLY.\n\nYour summary and those preserved recent messages together become the assistant's full context going forward.\n\nCreate the summary in the following format:\n\n## Goal\n[What is the user trying to accomplish?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements the user mentioned]\n\n## All User Requests\n- [List EVERY distinct request/instruction the user made, in order, that is not a tool result. This is the authoritative record of user intent \u2014 multi-round compaction must never let an earlier request silently drop. Quote the user's own wording for each; do not paraphrase away specifics.]\n\n## Progress\n### Done\n- [x] [Completed work items]\n### In Progress\n- [ ] [Work in progress]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Planned next steps. For the immediate next action, include a VERBATIM quote of the most recent user message or your own last stated intent that it derives from \u2014 this anchors the task and prevents interpretation drift across compactions.]\n\n## File Operations\n### Read\n- [Files that were read]\n### Modified\n- [Files that were modified]\n\n## Critical Context\n- [Any critical context needed to continue the work]\n\n## Context the Preserved Tail Depends On\n- [Facts a reader MUST know to correctly understand the preserved recent messages shown in <preserved_tail_context>: decisions already made, file paths and their current state, conventions/constraints agreed earlier, mistakes already corrected, and any partially-done work those recent messages continue. Read the preserved tail, then look BACK through the earlier conversation for what it silently relies on. This section is the whole point of this summary \u2014 be specific.]\n\nIMPORTANT:\n- Preserve exact file paths, function names, error messages, and code snippets\n- Keep technical details precise \u2014 do not generalize\n- Include all tool call results that affect the current state\n- Note any pending or failed operations\n- Do NOT summarize the content of <preserved_tail_context> \u2014 it is kept verbatim after your summary and re-describing it wastes your budget. Use it ONLY to work out which parts of the EARLIER conversation it depends on.\n- Summarize ONLY the conversation inside <conversation>.";
|
|
740
990
|
declare const SEGMENT_SUMMARIZATION_PROMPT = "You are summarizing ONE SEGMENT of a longer conversation for an AI coding assistant. This is a partial view \u2014 you are NOT seeing the whole conversation, and other segments are being summarized separately. Your segment summary will later be merged with the others.\n\nCreate a concise summary of THIS SEGMENT in the following format:\n\n## Goal\n[What is the user trying to accomplish?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements the user mentioned]\n\n## All User Requests\n- [List EVERY distinct request/instruction the user made, in order, that is not a tool result. This is the authoritative record of user intent \u2014 multi-round compaction must never let an earlier request silently drop. Quote the user's own wording for each; do not paraphrase away specifics.]\n\n## Progress\n### Done\n- [x] [Completed work items]\n### In Progress\n- [ ] [Work in progress]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Planned next steps. For the immediate next action, include a VERBATIM quote of the most recent user message or your own last stated intent that it derives from \u2014 this anchors the task and prevents interpretation drift across compactions.]\n\n## File Operations\n### Read\n- [Files that were read]\n### Modified\n- [Files that were modified]\n\n## Critical Context\n- [Any critical context needed to continue the work]\n\nIMPORTANT:\n- Preserve exact file paths, function names, error messages, and code snippets\n- Keep technical details precise \u2014 do not generalize\n- Include all tool call results that affect the current state\n- Note any pending or failed operations\n- Do NOT emit conclusions that require seeing the whole conversation (e.g. a single global \"next step\"). Report only what THIS segment establishes; the merge step will reconcile across segments.\n- If <preserved_tail_context> is present, it holds the most recent messages, kept verbatim outside this compaction. Do NOT summarize it \u2014 use it ONLY to judge which parts of THIS segment it depends on, and make sure those parts survive into your summary. Summarize ONLY the content inside <conversation>.";
|
|
741
991
|
declare const SEGMENT_MERGE_PROMPT = "You are merging several segment summaries of one conversation into a single coherent summary for an AI coding assistant. Each input below is a summary of a different (chronological) segment of the same conversation.\n\nReconcile them into one summary in the following format:\n\n## Goal\n[What is the user trying to accomplish?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements the user mentioned]\n\n## All User Requests\n- [List EVERY distinct request/instruction the user made, in order, that is not a tool result. This is the authoritative record of user intent \u2014 multi-round compaction must never let an earlier request silently drop. Quote the user's own wording for each; do not paraphrase away specifics.]\n\n## Progress\n### Done\n- [x] [Completed work items]\n### In Progress\n- [ ] [Work in progress]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Planned next steps. For the immediate next action, include a VERBATIM quote of the most recent user message or your own last stated intent that it derives from \u2014 this anchors the task and prevents interpretation drift across compactions.]\n\n## File Operations\n### Read\n- [Files that were read]\n### Modified\n- [Files that were modified]\n\n## Critical Context\n- [Any critical context needed to continue the work]\n\nIMPORTANT:\n- Preserve exact file paths, function names, error messages, and code snippets\n- Keep technical details precise \u2014 do not generalize\n- Include all tool call results that affect the current state\n- Note any pending or failed operations\n- Resolve conflicts in favour of the LATER segment.\n- Merge duplicate items; do not simply concatenate.";
|
|
@@ -764,6 +1014,12 @@ interface SummarizationContext {
|
|
|
764
1014
|
* 缺省/空串 = 不注入标签,提示词与 RFC-358 之前逐字节等价(向后兼容)。
|
|
765
1015
|
*/
|
|
766
1016
|
preservedTailReference?: string;
|
|
1017
|
+
/**
|
|
1018
|
+
* RFC-387 D1:前缀复用模式——对话内容经 `prefix.messages` 原样重放,不进入指令文本。
|
|
1019
|
+
* true 时不追加 `<conversation>` 块(缺省 false = 现状字节等价)。
|
|
1020
|
+
* 注:M1 阶段仅单批路径单测启用;模板内对 `<conversation>` 位置的最小措辞修订属 M2b(D3 方案 a)。
|
|
1021
|
+
*/
|
|
1022
|
+
omitConversation?: boolean;
|
|
767
1023
|
}
|
|
768
1024
|
/**
|
|
769
1025
|
* 构造摘要提示词。RFC-339 D3:新增可选 `context` 参数使提示词感知切点/批次形态;
|
|
@@ -787,19 +1043,6 @@ declare function buildSummarizationPrompt(messages: string, previousSummary?: st
|
|
|
787
1043
|
*/
|
|
788
1044
|
declare function buildSegmentMergePrompt(segmentSummaries: string, previousSummary?: string, customInstructions?: string): string;
|
|
789
1045
|
declare function buildTurnPrefixPrompt(turnPrefixMessages: string): string;
|
|
790
|
-
/**
|
|
791
|
-
* `<agent_memory>` 段的默认总预算(tokens,粗估)。
|
|
792
|
-
*
|
|
793
|
-
* 本仓正常场景(AGENTS.md 三源 + AutoMemory 索引)实测 ~3.4k tokens;monorepo
|
|
794
|
-
* `discoverAgentsSources` 理论上限是 10 文件 × 64KB = 640KB,若真的堆到那个量级会把
|
|
795
|
-
* system prompt 撑爆。这里的预算不是替代 discoverAgentsSources 的单文件/文件数上限
|
|
796
|
-
* (那层继续管),而是在全部来源(含它管不到的 writable AGENTS.md / AutoMemory 索引)
|
|
797
|
-
* 汇总之后再把一道总闸——`buildMemoryInjection` 是唯一收口点,别处管不到的这里管。
|
|
798
|
-
*
|
|
799
|
-
* 取 3.4k 实测值的 ~2.3 倍留余量,同时把极端场景压掉 95%+。
|
|
800
|
-
*/
|
|
801
|
-
declare const DEFAULT_MEMORY_INJECTION_BUDGET_TOKENS = 8000;
|
|
802
|
-
declare function buildMemoryInjection(memories: Map<string, string>, budgetTokens?: number): string;
|
|
803
1046
|
/**
|
|
804
1047
|
* 跨会话经验抽取提示词。要求模型从一段会话切片里提炼可复用的「经验」
|
|
805
1048
|
* (trigger=何时适用 / insight=怎么做 / tags=召回关键词),严格输出 JSON 数组。
|
|
@@ -858,13 +1101,12 @@ interface Lesson {
|
|
|
858
1101
|
sourceSessionId: string;
|
|
859
1102
|
createdAt: number;
|
|
860
1103
|
/**
|
|
861
|
-
* @deprecated RFC-
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
*
|
|
865
|
-
* 读取时与 `relevantHitCount` 双向同步,不再作为新的判据来源。
|
|
1104
|
+
* @deprecated RFC-394 M5:改用 `relevantHitCount`。该字段是 v1 遗留——`save()` 不再
|
|
1105
|
+
* 写入它,`search()`/`recordRelevantHits()` 不再同步它。旧数据文件里遗留的值由
|
|
1106
|
+
* `hydrateLesson` 读取时一次性映射到 `relevantHitCount`(读旧写新,无需迁移脚本)。
|
|
1107
|
+
* 类型改为可选——新创建的 lesson 不赋此属性(`JSON.stringify` 不落盘 undefined 键)。
|
|
866
1108
|
*/
|
|
867
|
-
appliedCount
|
|
1109
|
+
appliedCount?: number;
|
|
868
1110
|
/** 语义类别。缺省视为未归类,由 M4 审计填充。 */
|
|
869
1111
|
class?: LessonClass;
|
|
870
1112
|
/** 生命周期状态。缺省视为 `active`。 */
|
|
@@ -894,6 +1136,15 @@ type LessonInput = Omit<Lesson, 'id' | 'createdAt' | 'appliedCount'>;
|
|
|
894
1136
|
interface LessonFilter {
|
|
895
1137
|
tags?: string[];
|
|
896
1138
|
query?: string;
|
|
1139
|
+
/**
|
|
1140
|
+
* RFC-284 T7:是否包含已出射(archived/tombstone)的 lesson。缺省 false——
|
|
1141
|
+
* `list()` 的主消费方是 stable prompt 注入(lesson-injection.ts),归档语义
|
|
1142
|
+
* 的全部意义就是「不再进 stable 注入」;审计/维护工具需要全量视图时显式传 true。
|
|
1143
|
+
* 注意 `search()`(相关性召回)刻意**不受**此过滤影响——archived 的经验仍可被
|
|
1144
|
+
* query 相关召回进 volatile hints(出 stable 注入 ≠ 出召回,比 DSH 冻结归档温和,
|
|
1145
|
+
* 适配 lesson「低频但可能突然有用」的数据性质)。
|
|
1146
|
+
*/
|
|
1147
|
+
includeArchived?: boolean;
|
|
897
1148
|
}
|
|
898
1149
|
interface SimilarLesson {
|
|
899
1150
|
lesson: Lesson;
|
|
@@ -937,6 +1188,12 @@ interface LearningStoreOptions {
|
|
|
937
1188
|
* 两个写盘分支于单点。由 §D1 白名单点 agent-services-setup 从 isShadowModeEnabled 派生。
|
|
938
1189
|
*/
|
|
939
1190
|
ephemeral?: boolean;
|
|
1191
|
+
/**
|
|
1192
|
+
* 确定性时钟端口(RFC-394 M5)。缺省 `() => Date.now()`(向后兼容);
|
|
1193
|
+
* 注入后 save 的 `createdAt` / search·recordRelevantHits 的 `lastAccessedAt` /
|
|
1194
|
+
* persist 的快照时间戳全部经注入时钟——确定性重放前提(CODE-STYLE §43)。
|
|
1195
|
+
*/
|
|
1196
|
+
now?: () => number;
|
|
940
1197
|
}
|
|
941
1198
|
/** 估算注入 token(CJK≈1/字,ASCII≈4 字符/token)。与 RFC-284 探针脚本同口径。 */
|
|
942
1199
|
declare function estimateLessonTokens(lesson: Pick<Lesson, 'tags' | 'trigger' | 'insight'>): number;
|
|
@@ -951,6 +1208,11 @@ declare class OperationalLearningStore {
|
|
|
951
1208
|
private lessons;
|
|
952
1209
|
private nextId;
|
|
953
1210
|
private loaded;
|
|
1211
|
+
/**
|
|
1212
|
+
* RFC-284 T7:本进程是否显式改过治理字段(archiveLessons)。为真时 `persist()` 跳过
|
|
1213
|
+
* 磁盘治理字段合并——否则本进程刚做的归档会被磁盘旧值(active)覆盖回去。
|
|
1214
|
+
*/
|
|
1215
|
+
private governanceDirty;
|
|
954
1216
|
private readonly snapshotId;
|
|
955
1217
|
private readonly persistence;
|
|
956
1218
|
/** RFC-164 M164-2 D5:MemoryStore 接入(可选)——提供时优先于 `persistence` 路径。 */
|
|
@@ -961,8 +1223,28 @@ declare class OperationalLearningStore {
|
|
|
961
1223
|
private readonly scope;
|
|
962
1224
|
/** RFC-345 §D3:影子态——persist() 短路(内存优先架构,写后读经 this.lessons Map 保持)。 */
|
|
963
1225
|
private readonly ephemeral;
|
|
1226
|
+
/** RFC-394 M5:确定性时钟端口(缺省 Date.now)。 */
|
|
1227
|
+
private readonly now;
|
|
964
1228
|
constructor(options?: LearningStoreOptions);
|
|
965
1229
|
save(input: LessonInput): Promise<Lesson>;
|
|
1230
|
+
/**
|
|
1231
|
+
* RFC-284 T7:归档写入通道——把指定 lesson 置为 `archived`(出 stable 注入面,
|
|
1232
|
+
* 数据保留、`search()` 相关性召回不受影响)。这是 archive-plan dry-run(T4a)
|
|
1233
|
+
* 的 apply 执行端。
|
|
1234
|
+
*
|
|
1235
|
+
* 保守语义(RFC-284 endgame-retention-design §4 Layer C):
|
|
1236
|
+
* - `pinned` / `state==='live'` 的 lesson **拒绝归档**(返回 skipped,不抛错)——
|
|
1237
|
+
* 与 dry-run checker 的保护项判据一致,双层防线(plan 侧 protected + store 侧硬闸)。
|
|
1238
|
+
* - 未知 id 计入 skipped(fail-soft:plan 与 store 可能有漂移,不让单条错拦整批)。
|
|
1239
|
+
* - 幂等:已 archived 的再归档计入 archived(状态不变,不重复写盘副作用)。
|
|
1240
|
+
*/
|
|
1241
|
+
archiveLessons(ids: readonly string[]): Promise<{
|
|
1242
|
+
archived: string[];
|
|
1243
|
+
skipped: Array<{
|
|
1244
|
+
id: string;
|
|
1245
|
+
reason: string;
|
|
1246
|
+
}>;
|
|
1247
|
+
}>;
|
|
966
1248
|
search(query: string, limit?: number): Promise<Lesson[]>;
|
|
967
1249
|
/**
|
|
968
1250
|
* RFC-036 M61(R4 去重前置):在写入前查找与 `(trigger, insight)` 近似重复的既有经验。
|
|
@@ -993,6 +1275,23 @@ declare class OperationalLearningStore {
|
|
|
993
1275
|
refresh(): Promise<void>;
|
|
994
1276
|
/** RFC-164 M164-2 D5:MemoryStore 路径的加载——store.load 返回原始 JSON 字符串,本地解析。 */
|
|
995
1277
|
private loadFromStore;
|
|
1278
|
+
/**
|
|
1279
|
+
* RFC-284 T7 修复(2026-08-15 验收发现的真实缺陷):写盘前合并磁盘上的**治理字段**。
|
|
1280
|
+
*
|
|
1281
|
+
* 事故:`scripts/rfc284-archive-plan.mjs --apply` 把 60 条 lesson 置为 `archived` 落盘后,
|
|
1282
|
+
* 40 分钟后被长驻 otto 进程整体抹回 `active`——`persist()` 用**进程启动时加载的内存 Map**
|
|
1283
|
+
* 全量覆写,完全不知道外部工具改过磁盘。归档在真机上直接失效(验收脚本抓到)。
|
|
1284
|
+
*
|
|
1285
|
+
* 所有权划分(与 auth-store「读盘-合并-写回」同款纹理):
|
|
1286
|
+
* - **运行时拥有**:trigger/insight/tags/relevantHitCount/lastAccessedAt 等自身产生的字段;
|
|
1287
|
+
* - **外部治理工具拥有**:`state`/`pinned`/`supersededBy`/`class`(archive-plan/governance-plan
|
|
1288
|
+
* 等脚本写入,运行时只读不产)。
|
|
1289
|
+
*
|
|
1290
|
+
* 合并规则:治理字段以**磁盘为准**(除非本进程显式改过它——`archiveLessons()` 走内存已改,
|
|
1291
|
+
* 此时内存值更新,用 `governanceDirty` 标记豁免)。磁盘读失败一律 fail-open 用内存值写出
|
|
1292
|
+
* (不让合并失败演变成写盘失败——丢新 lesson 比丢归档标记更严重)。
|
|
1293
|
+
*/
|
|
1294
|
+
private mergeGovernanceFieldsFromDisk;
|
|
996
1295
|
private persist;
|
|
997
1296
|
}
|
|
998
1297
|
//#endregion
|
|
@@ -1063,18 +1362,34 @@ interface MemoryAutoExtractConfig {
|
|
|
1063
1362
|
similarityThreshold?: number;
|
|
1064
1363
|
/** 抽取调用 token 上限,透传 extractLessons。 */
|
|
1065
1364
|
maxTokens?: number;
|
|
1365
|
+
/**
|
|
1366
|
+
* RFC-327 M3-05:相关召回低水位阈值(显式阈值)。
|
|
1367
|
+
*
|
|
1368
|
+
* 开启 auto-extract 时,若既有 lessons 的最大 relevantHitCount 低于此水位
|
|
1369
|
+
* (召回路径未激活/未被证明有效),跳过本次抽取——防"write-only"噪音积累
|
|
1370
|
+
* (抽取写入了、却从未被相关召回命中)。配合 `readRelevantHits` 端口使用:
|
|
1371
|
+
* 设置了本阈值但未注入读取端口时 fail-closed(不抽取),避免静默绕过配置意图。
|
|
1372
|
+
* 缺省不设(无门,向后兼容)。
|
|
1373
|
+
*/
|
|
1374
|
+
minRelevantHits?: number;
|
|
1066
1375
|
}
|
|
1067
1376
|
interface MemoryAutoExtractorDeps {
|
|
1068
1377
|
llm: ExtractLLM;
|
|
1069
1378
|
store: LessonWriteStore;
|
|
1070
1379
|
config: MemoryAutoExtractConfig;
|
|
1380
|
+
/**
|
|
1381
|
+
* RFC-327 M3-05:召回信号读取端口——返回既有 lessons 的**最大** relevantHitCount。
|
|
1382
|
+
* 由宿主注入(对齐 `store` 的注入纹理——extractor 不直接持有读取侧 store 依赖)。
|
|
1383
|
+
* 与 `config.minRelevantHits` 配对:设置了阈值但未注入端口时 fail-closed(不抽取)。
|
|
1384
|
+
*/
|
|
1385
|
+
readRelevantHits?: () => Promise<number> | number;
|
|
1071
1386
|
/**
|
|
1072
1387
|
* R5 不与主 agent 抢:判定某工具调用是否算"记忆写入"——若本批切片里主 agent 已写过记忆,
|
|
1073
1388
|
* 跳过本次抽取。默认认 `learn` 工具与名字含 `memory` 的工具。
|
|
1074
1389
|
*/
|
|
1075
1390
|
isMemoryWriteTool?: (toolName: string) => boolean;
|
|
1076
1391
|
}
|
|
1077
|
-
type AutoExtractReason = 'disabled' | 'no-new-messages' | 'throttled' | 'main-agent-wrote' | 'error';
|
|
1392
|
+
type AutoExtractReason = 'disabled' | 'no-new-messages' | 'throttled' | 'main-agent-wrote' | 'below-relevant-hits-low-water' | 'error';
|
|
1078
1393
|
interface AutoExtractOutcome {
|
|
1079
1394
|
ran: boolean;
|
|
1080
1395
|
reason?: AutoExtractReason;
|
|
@@ -1123,5 +1438,138 @@ declare class FileStateManager {
|
|
|
1123
1438
|
private checkRecentFiles;
|
|
1124
1439
|
}
|
|
1125
1440
|
//#endregion
|
|
1126
|
-
|
|
1441
|
+
//#region src/evolution-signal-store.d.ts
|
|
1442
|
+
/**
|
|
1443
|
+
* evolution-signal-store.ts —— RFC-327 M3-03:进化观测层的统一信号出口。
|
|
1444
|
+
*
|
|
1445
|
+
* 三回路(RFC-318 skill usage / RFC-287 capability_gap 缺口 / RFC-036 relevantHitCount)
|
|
1446
|
+
* 的进化信号在这里汇聚成 **append-only** 日志,供第③层内化决策与第④层训练语料筛选
|
|
1447
|
+
* 消费(两者均缓议——见 RFC-327 §5 R3':观测层只收信号不决策)。
|
|
1448
|
+
*
|
|
1449
|
+
* ## 单源裁决(R3')
|
|
1450
|
+
*
|
|
1451
|
+
* relevantHitCount 的**真源在 memory store**(`OperationalLearningStore.recordRelevantHits`
|
|
1452
|
+
* / `search`),本 store 只做**消费投影**——`appendRelevantHits` 只读 lesson 数据、绝不写回;
|
|
1453
|
+
* 「grep 防第二写入点」升级为模块边界单测(见 evolution-signal-store.test.ts)。
|
|
1454
|
+
*
|
|
1455
|
+
* ## 事件驱动写入(不轮询)
|
|
1456
|
+
*
|
|
1457
|
+
* 所有写入经 `append`(宿主在信号事件发生点调用,如 lesson-relevant-hints hook 在
|
|
1458
|
+
* `recordRelevantHits` 之后投影);本 store 不提供任何"定时同步/轮询"入口。
|
|
1459
|
+
*/
|
|
1460
|
+
type EvolutionSignalKind = 'skill.usage' | 'capability.gap' | 'relevant.hits' | 'internalize';
|
|
1461
|
+
/** append-only 信号日志中的一条记录。 */
|
|
1462
|
+
interface EvolutionSignalRecord {
|
|
1463
|
+
kind: EvolutionSignalKind;
|
|
1464
|
+
/** 信号归属 key(skill 名 / 缺口去重键 / lesson id)。 */
|
|
1465
|
+
key: string;
|
|
1466
|
+
/** 信号值(执行次数 / 上报次数 / 命中次数等)。 */
|
|
1467
|
+
value: number;
|
|
1468
|
+
/** 信号时刻——注入时钟刻度(确定性纪律,本层不直呼 Date.now)。 */
|
|
1469
|
+
at: number;
|
|
1470
|
+
/**
|
|
1471
|
+
* 会话归属(RFC-385 §11 授权扩展,RFC-327 M3 schema 向后兼容)——可选;
|
|
1472
|
+
* 不传 = 该信号无会话归属(旧写入点行为不变,全局聚合照旧)。第④层出口按此字段
|
|
1473
|
+
* 切片做 turn 级质量标注联结(trajectory-capture)。
|
|
1474
|
+
*/
|
|
1475
|
+
sessionId?: string;
|
|
1476
|
+
/** 本次事件的增量元数据(如 success/durationMs/deduped)。 */
|
|
1477
|
+
detail?: Record<string, unknown>;
|
|
1478
|
+
}
|
|
1479
|
+
/** 最小写入端口——消费方(如 lesson-injection)依赖此面,不依赖具体实现。 */
|
|
1480
|
+
interface EvolutionSignalSink {
|
|
1481
|
+
append(record: Omit<EvolutionSignalRecord, 'at'>): void;
|
|
1482
|
+
/**
|
|
1483
|
+
* relevantHitCount 消费投影(R3')——读真源数据派生信号记录并 append。
|
|
1484
|
+
* 实现必须只读、绝不写回 lesson 数据(单源写入点边界,见测试)。
|
|
1485
|
+
*/
|
|
1486
|
+
appendRelevantHits(lessons: ReadonlyArray<{
|
|
1487
|
+
id: string;
|
|
1488
|
+
relevantHitCount?: number;
|
|
1489
|
+
}>): void;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* 持久化端口(RFC-388 M1)——宿主注入 SignalLog;可选:不注入 = 纯内存(旧行为不变)。
|
|
1493
|
+
* `append` 返回 Promise(异步落盘);store 侧 fire-and-forget + .catch 保证无未处理 rejection。
|
|
1494
|
+
*/
|
|
1495
|
+
interface EvolutionSignalPersistPort {
|
|
1496
|
+
append(record: EvolutionSignalRecord): Promise<unknown>;
|
|
1497
|
+
load(): Promise<EvolutionSignalRecord[]>;
|
|
1498
|
+
close?(): Promise<void>;
|
|
1499
|
+
}
|
|
1500
|
+
interface EvolutionSignalStoreOptions {
|
|
1501
|
+
/** 时钟端口——信号时刻由注入时钟提供。缺省回退 Date.now(宿主未注入时向后兼容)。 */
|
|
1502
|
+
now?: () => number;
|
|
1503
|
+
/** RFC-388 M1:持久化端口(可选)。不注入 = 纯内存 store,行为与 RFC-327 原版一致。 */
|
|
1504
|
+
persist?: EvolutionSignalPersistPort;
|
|
1505
|
+
/** 落盘/恢复失败的告警端口(可选)。不注入 = 静默(仅吞错,不阻断信号)。 */
|
|
1506
|
+
logWarn?: (msg: string, err?: unknown) => void;
|
|
1507
|
+
}
|
|
1508
|
+
/** 按 kind:key 聚合的信号计数(快照视图,供进化决策读取)。 */
|
|
1509
|
+
interface EvolutionSignalCount {
|
|
1510
|
+
kind: EvolutionSignalKind;
|
|
1511
|
+
key: string;
|
|
1512
|
+
/** 信号值累计(如 total 执行次数 / 上报次数 / 命中次数)。 */
|
|
1513
|
+
total: number;
|
|
1514
|
+
/** 日志条数(事件次数)。 */
|
|
1515
|
+
records: number;
|
|
1516
|
+
/** 最近一次信号时刻。 */
|
|
1517
|
+
lastAt: number;
|
|
1518
|
+
}
|
|
1519
|
+
declare class EvolutionSignalStore implements EvolutionSignalSink {
|
|
1520
|
+
private readonly records;
|
|
1521
|
+
private readonly now;
|
|
1522
|
+
private readonly persist?;
|
|
1523
|
+
private readonly logWarn?;
|
|
1524
|
+
constructor(options?: EvolutionSignalStoreOptions);
|
|
1525
|
+
/** append-only 写入——事件驱动(宿主在信号事件发生点调用)。 */
|
|
1526
|
+
append(record: Omit<EvolutionSignalRecord, 'at'>): void;
|
|
1527
|
+
/**
|
|
1528
|
+
* 纯投影:从 memory store 数据派生 relevantHitCount 信号记录。
|
|
1529
|
+
*
|
|
1530
|
+
* **只读**——不 append、不改写入参;返回的记录可直接交给 `append` 或消费方检查。
|
|
1531
|
+
* 值来自 memory store 真源(R3'),本方法不提供任何写回 lesson 的路径。
|
|
1532
|
+
*/
|
|
1533
|
+
projectRelevantHits(lessons: ReadonlyArray<{
|
|
1534
|
+
id: string;
|
|
1535
|
+
relevantHitCount?: number;
|
|
1536
|
+
}>): Array<Omit<EvolutionSignalRecord, 'at'>>;
|
|
1537
|
+
/** 事件驱动投影写入:读 memory store 的 relevantHitCount,append 信号记录(R3' 消费投影)。 */
|
|
1538
|
+
appendRelevantHits(lessons: ReadonlyArray<{
|
|
1539
|
+
id: string;
|
|
1540
|
+
relevantHitCount?: number;
|
|
1541
|
+
}>): void;
|
|
1542
|
+
/** 全量日志(append-only:无更新/删除入口)。 */
|
|
1543
|
+
list(): readonly EvolutionSignalRecord[];
|
|
1544
|
+
/** 按 kind:key 聚合的快照(信号值累计 + 事件次数)。 */
|
|
1545
|
+
counts(): EvolutionSignalCount[];
|
|
1546
|
+
/**
|
|
1547
|
+
* RFC-388 M1:从持久化日志全量恢复(重放)进内存。append-only 无删改——
|
|
1548
|
+
* 重放后内存 at 序 = 文件 seq 序 = 追加序。宿主启动时调用一次。
|
|
1549
|
+
* fail-soft:文件损坏/读失败时告警并空启动(信号是观测数据可容忍),不抛。
|
|
1550
|
+
* 返回恢复条数(观测 count 用)。
|
|
1551
|
+
*
|
|
1552
|
+
* 时序约束(应用点 B 建议③):仅在启动早期调用(任何 signal 生产者就绪前)。
|
|
1553
|
+
* 恢复窗口内并发 append 会被 records.length=0 清空抹掉内存快照(该信号已
|
|
1554
|
+
* fire-and-forget 落盘、下次重启可恢复,非数据丢失)——运行期勿调用。
|
|
1555
|
+
*/
|
|
1556
|
+
restoreFromLog(): Promise<number>;
|
|
1557
|
+
/** RFC-388 M1:转发 close(flush-on-exit——AppendLog.close() 排空 writeQueue)。 */
|
|
1558
|
+
close(): Promise<void>;
|
|
1559
|
+
get size(): number;
|
|
1560
|
+
}
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region src/signal-log.d.ts
|
|
1563
|
+
/** 进化信号持久化端口(SignalLog 的最小领域面)。 */
|
|
1564
|
+
interface SignalLog {
|
|
1565
|
+
/** 追加一条;FileAppendLog 内部 writeQueue 串行,进程内落盘序 = 调用序。 */
|
|
1566
|
+
append(record: EvolutionSignalRecord): Promise<unknown>;
|
|
1567
|
+
/** 全量重放(seq 升序 = 追加序)。首启无文件 → 空数组,不抛。 */
|
|
1568
|
+
load(): Promise<EvolutionSignalRecord[]>;
|
|
1569
|
+
/** 排空在途写入队列并关闭句柄(flush-on-exit)。 */
|
|
1570
|
+
close(): Promise<void>;
|
|
1571
|
+
}
|
|
1572
|
+
declare function createSignalLog(dir: string): SignalLog;
|
|
1573
|
+
//#endregion
|
|
1574
|
+
export { type AgentsDiscoveryOptions, type ArchiveEntry, type ArchiveStorage, type AutoExtractOutcome, type AutoExtractReason, AutoMemory, type AutoMemoryEntry, type AutoMemoryOptions, type CompactionConfig, type CompactionLockPort, type CompactionPrefix, type CompactionPreparation, type CompactionResult, type CompactionSpillSink, type ContextSize, type ContextTokenEstimate, type CutPoint, DEFAULT_COMPACTION_CONFIG, DEFAULT_MEMORY_INJECTION_BUDGET_TOKENS, DEFAULT_MEMORY_SOURCES, DEFAULT_PRUNE_CONFIG, DEFAULT_SIMILARITY_THRESHOLD, type EvolutionSignalCount, type EvolutionSignalKind, type EvolutionSignalPersistPort, type EvolutionSignalRecord, type EvolutionSignalSink, EvolutionSignalStore, type EvolutionSignalStoreOptions, type ExtractLLM, type ExtractionOptions, type FileStateCapture, FileStateManager, type FileStateSnapshot, FileSystemMemoryStore, HttpMemoryStore, IMAGE_BLOCK_TOKEN_ESTIMATE, InMemoryArchiveStorage, InMemoryMemoryStore, LESSON_EXTRACTION_PROMPT, type LearningStoreOptions, type Lesson, type LessonClass, type LessonFilter, type LessonInput, type LessonState, type LessonWriteStore, type MemoryAutoExtractConfig, type MemoryAutoExtractorDeps, type MemoryDefaults, MemoryManager, type MemoryManagerConfig, type MemorySource, type MemoryStore, type MessageFromEntry, type MinimalEntry, OperationalLearningStore, PersistentMemory, type PruneConfig, type PruneResult, SEGMENT_MERGE_PROMPT, SEGMENT_SUMMARIZATION_PROMPT, SUMMARIZATION_PROMPT, type SaveExtractedOptions, type SaveExtractedResult, type SignalLog, type SimilarLesson, type StorageType, type SummarizationContext, type SummarizationVariant, TURN_PREFIX_SUMMARIZATION_PROMPT, UPDATE_SUMMARIZATION_PROMPT, WITH_PRESERVED_TAIL_PROMPT, buildArchiveReference, buildExtractionPrompt, buildLowCoverageNotice, buildMemoryInjection, buildSegmentMergePrompt, buildSummarizationPrompt, buildTurnPrefixPrompt, computeLessonContentHash, computeMemoryDefaults, createMemoryAutoExtractor, createMemoryManager, createOverlayMemoryStore, createSignalLog, discoverAgentsSources, ensureTokenizer, estimateLessonTokens, estimateMessageTokens, estimateMessagesTokens, estimateTokens, estimateTokensHeuristic, extractLessons, formatArchive, formatMessageForSummary, messageToText, prune, resolveContextSize, saveExtractedLessons };
|
|
1127
1575
|
//# sourceMappingURL=index.d.ts.map
|