dsh-layered-memory 0.7.0 → 0.8.0
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.en.md +118 -66
- package/README.md +131 -98
- package/assets/img/EmbeddingSource.png +0 -0
- package/assets/img/MemoryTools.png +0 -0
- package/assets/img/Modes.png +0 -0
- package/assets/img/ToolTrajectory.png +0 -0
- package/dist/client.js +11 -2
- package/dist/config.d.ts +26 -1
- package/dist/config.js +9 -3
- package/dist/hooks/recall.d.ts +11 -8
- package/dist/hooks/recall.js +70 -51
- package/dist/index.d.ts +16 -0
- package/dist/index.js +5 -1
- package/dist/llm.d.ts +13 -0
- package/dist/llm.js +17 -0
- package/dist/pipeline/l1.js +5 -4
- package/dist/pipeline/l2.js +7 -2
- package/dist/pipeline/l3.js +7 -2
- package/dist/pipeline/runner.d.ts +36 -5
- package/dist/pipeline/runner.js +148 -35
- package/dist/pipeline/trigger.d.ts +38 -0
- package/dist/pipeline/trigger.js +64 -0
- package/dist/runtime-package-lock.json +982 -0
- package/dist/store/bm25.js +2 -1
- package/dist/store/embedding.d.ts +12 -6
- package/dist/store/embedding.js +10 -7
- package/dist/store/l0.d.ts +2 -0
- package/dist/store/l0.js +11 -4
- package/dist/store/l1.d.ts +2 -0
- package/dist/store/l1.js +9 -6
- package/dist/store/pending.d.ts +27 -6
- package/dist/store/pending.js +49 -9
- package/dist/store/runtime-installer.d.ts +6 -1
- package/dist/store/runtime-installer.js +65 -26
- package/dist/store/search-utils.js +3 -2
- package/dist/store/session-modes.d.ts +4 -0
- package/dist/store/session-modes.js +16 -0
- package/dist/store/sqlite.d.ts +27 -0
- package/dist/store/sqlite.js +186 -6
- package/dist/util/recall-budget.d.ts +32 -0
- package/dist/util/recall-budget.js +85 -0
- package/dist/util/text.d.ts +8 -3
- package/dist/util/text.js +41 -15
- package/dist/util/tokenizer.d.ts +14 -0
- package/dist/util/tokenizer.js +53 -0
- package/package.json +2 -1
package/dist/config.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ export interface MemoryConfig {
|
|
|
19
19
|
};
|
|
20
20
|
extract: {
|
|
21
21
|
enabled: boolean;
|
|
22
|
-
/**
|
|
22
|
+
/** 稳态触发阈值:单会话攒够多少条新消息才跑一次 L1 抽取(省 token)。
|
|
23
|
+
* 起步阶段生效阈值从 1 翻倍爬坡到此值(渐进阈值,ADR-0003)。 */
|
|
23
24
|
minMessages: number;
|
|
25
|
+
/** 闲置兜底:会话静默多少秒后把未蒸馏切片落袋(接住"没攒够阈值用户就离开");0 = 关闭。 */
|
|
26
|
+
idleSeconds: number;
|
|
24
27
|
/** L1 抽取时的背景消息条数(供上下文推断,不参与提取)。 */
|
|
25
28
|
backgroundMessages: number;
|
|
26
29
|
/** 去重候选池大小(每条新记忆的相似候选数)。 */
|
|
@@ -44,6 +47,12 @@ export interface MemoryConfig {
|
|
|
44
47
|
enabled: boolean;
|
|
45
48
|
/** 每步自动召回注入的 L1 条数。 */
|
|
46
49
|
maxResults: number;
|
|
50
|
+
/** 单条注入记忆的字符上限(超限截断并提示用工具查全文);0 = 不限。 */
|
|
51
|
+
maxCharsPerMemory: number;
|
|
52
|
+
/** 整轮注入总字符上限(超限按相关性丢尾部);0 = 不限。 */
|
|
53
|
+
maxTotalRecallChars: number;
|
|
54
|
+
/** 召回总预算(ms):超时跳过本轮注入、不阻塞对话;0 = 不限时。 */
|
|
55
|
+
timeoutMs: number;
|
|
47
56
|
includePersona: boolean;
|
|
48
57
|
includeSceneNav: boolean;
|
|
49
58
|
/** 检索策略:keyword(FTS5 BM25)| embedding(向量)| hybrid(双路 + RRF 融合)。 */
|
|
@@ -102,11 +111,13 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
102
111
|
extract: Schema<Schemastery.ObjectS<{
|
|
103
112
|
enabled: Schema<boolean, boolean>;
|
|
104
113
|
minMessages: Schema<number, number>;
|
|
114
|
+
idleSeconds: Schema<number, number>;
|
|
105
115
|
backgroundMessages: Schema<number, number>;
|
|
106
116
|
candidatePool: Schema<number, number>;
|
|
107
117
|
}>, Schemastery.ObjectT<{
|
|
108
118
|
enabled: Schema<boolean, boolean>;
|
|
109
119
|
minMessages: Schema<number, number>;
|
|
120
|
+
idleSeconds: Schema<number, number>;
|
|
110
121
|
backgroundMessages: Schema<number, number>;
|
|
111
122
|
candidatePool: Schema<number, number>;
|
|
112
123
|
}>>;
|
|
@@ -131,6 +142,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
131
142
|
recall: Schema<Schemastery.ObjectS<{
|
|
132
143
|
enabled: Schema<boolean, boolean>;
|
|
133
144
|
maxResults: Schema<number, number>;
|
|
145
|
+
maxCharsPerMemory: Schema<number, number>;
|
|
146
|
+
maxTotalRecallChars: Schema<number, number>;
|
|
147
|
+
timeoutMs: Schema<number, number>;
|
|
134
148
|
includePersona: Schema<boolean, boolean>;
|
|
135
149
|
includeSceneNav: Schema<boolean, boolean>;
|
|
136
150
|
strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -138,6 +152,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
138
152
|
}>, Schemastery.ObjectT<{
|
|
139
153
|
enabled: Schema<boolean, boolean>;
|
|
140
154
|
maxResults: Schema<number, number>;
|
|
155
|
+
maxCharsPerMemory: Schema<number, number>;
|
|
156
|
+
maxTotalRecallChars: Schema<number, number>;
|
|
157
|
+
timeoutMs: Schema<number, number>;
|
|
141
158
|
includePersona: Schema<boolean, boolean>;
|
|
142
159
|
includeSceneNav: Schema<boolean, boolean>;
|
|
143
160
|
strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -197,11 +214,13 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
197
214
|
extract: Schema<Schemastery.ObjectS<{
|
|
198
215
|
enabled: Schema<boolean, boolean>;
|
|
199
216
|
minMessages: Schema<number, number>;
|
|
217
|
+
idleSeconds: Schema<number, number>;
|
|
200
218
|
backgroundMessages: Schema<number, number>;
|
|
201
219
|
candidatePool: Schema<number, number>;
|
|
202
220
|
}>, Schemastery.ObjectT<{
|
|
203
221
|
enabled: Schema<boolean, boolean>;
|
|
204
222
|
minMessages: Schema<number, number>;
|
|
223
|
+
idleSeconds: Schema<number, number>;
|
|
205
224
|
backgroundMessages: Schema<number, number>;
|
|
206
225
|
candidatePool: Schema<number, number>;
|
|
207
226
|
}>>;
|
|
@@ -226,6 +245,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
226
245
|
recall: Schema<Schemastery.ObjectS<{
|
|
227
246
|
enabled: Schema<boolean, boolean>;
|
|
228
247
|
maxResults: Schema<number, number>;
|
|
248
|
+
maxCharsPerMemory: Schema<number, number>;
|
|
249
|
+
maxTotalRecallChars: Schema<number, number>;
|
|
250
|
+
timeoutMs: Schema<number, number>;
|
|
229
251
|
includePersona: Schema<boolean, boolean>;
|
|
230
252
|
includeSceneNav: Schema<boolean, boolean>;
|
|
231
253
|
strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -233,6 +255,9 @@ export declare const memorySchema: Schema<Schemastery.ObjectS<{
|
|
|
233
255
|
}>, Schemastery.ObjectT<{
|
|
234
256
|
enabled: Schema<boolean, boolean>;
|
|
235
257
|
maxResults: Schema<number, number>;
|
|
258
|
+
maxCharsPerMemory: Schema<number, number>;
|
|
259
|
+
maxTotalRecallChars: Schema<number, number>;
|
|
260
|
+
timeoutMs: Schema<number, number>;
|
|
236
261
|
includePersona: Schema<boolean, boolean>;
|
|
237
262
|
includeSceneNav: Schema<boolean, boolean>;
|
|
238
263
|
strategy: Schema<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
package/dist/config.js
CHANGED
|
@@ -15,7 +15,8 @@ export const memorySchema = Schema.object({
|
|
|
15
15
|
}),
|
|
16
16
|
extract: Schema.object({
|
|
17
17
|
enabled: Schema.boolean().default(true),
|
|
18
|
-
minMessages: Schema.number().min(1).max(100).default(
|
|
18
|
+
minMessages: Schema.number().min(1).max(100).default(6),
|
|
19
|
+
idleSeconds: Schema.number().min(0).max(86_400).default(300),
|
|
19
20
|
backgroundMessages: Schema.number().min(0).max(50).default(10),
|
|
20
21
|
candidatePool: Schema.number().min(1).max(20).default(5),
|
|
21
22
|
}),
|
|
@@ -32,6 +33,10 @@ export const memorySchema = Schema.object({
|
|
|
32
33
|
recall: Schema.object({
|
|
33
34
|
enabled: Schema.boolean().default(true),
|
|
34
35
|
maxResults: Schema.number().min(1).max(20).default(5),
|
|
36
|
+
// 预算与超时(ADR-0001 / 规格 A 节):截断是引流——工具路径返回全文
|
|
37
|
+
maxCharsPerMemory: Schema.number().min(0).max(100_000).default(500),
|
|
38
|
+
maxTotalRecallChars: Schema.number().min(0).max(100_000).default(2000),
|
|
39
|
+
timeoutMs: Schema.number().min(0).max(60_000).default(5000),
|
|
35
40
|
includePersona: Schema.boolean().default(true),
|
|
36
41
|
includeSceneNav: Schema.boolean().default(true),
|
|
37
42
|
strategy: Schema.union(['keyword', 'embedding', 'hybrid']).default('hybrid'),
|
|
@@ -52,8 +57,9 @@ export const memorySchema = Schema.object({
|
|
|
52
57
|
llm: Schema.object({
|
|
53
58
|
provider: Schema.string().default(''),
|
|
54
59
|
model: Schema.string().default(''),
|
|
55
|
-
// 推理模型(如 v4-flash)的 reasoning 计入输出预算:预算不足会被思考吃光导致正文 0
|
|
56
|
-
|
|
60
|
+
// 推理模型(如 v4-flash)的 reasoning 计入输出预算:预算不足会被思考吃光导致正文 0 字符。
|
|
61
|
+
// 0.8.0 起各蒸馏层显式传分层预算(见 llm.ts LAYER_MAX_TOKENS_*),本值为未分层调用的兜底总闸
|
|
62
|
+
maxTokens: Schema.number().min(1024).max(1_000_000).default(65_536),
|
|
57
63
|
// 蒸馏是结构化抽取任务,默认关思考(off):v4-flash 默认 high 档的思考可把任意 maxTokens
|
|
58
64
|
// 预算全部吃光导致正文 0 字符;非推理模型不认识 effort 时会报 UNSUPPORTED_REASONING_EFFORT,设空串跳过
|
|
59
65
|
reasoningEffort: Schema.union(['', 'off', 'high', 'max']).default('off'),
|
package/dist/hooks/recall.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 召回 Hook
|
|
3
|
-
* 1. agent/pre-step(waterfall
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* 召回 Hook(消息侧注入版,ADR-0001):
|
|
3
|
+
* 1. agent/pre-step(waterfall,prepend 注册):有新的用户来源消息到达的步骤
|
|
4
|
+
* (轮首 claim 或 steering 插话)→ 检索 L1 → 以合成消息形式注入到用户消息之前。
|
|
5
|
+
* 注入消息携带插件来源(form: 'recall'),对用户可见、随会话历史持久累积——
|
|
6
|
+
* 这就是"记忆生效"的显式提示(dsh-time-context 同款官方范式)。
|
|
7
|
+
* 纯工具步透传;召回超时(recall.timeoutMs 总预算)跳过本轮,绝不阻塞对话。
|
|
8
|
+
* 2. agent/created:在 agent 作用域注册动态上下文 provider(L3 画像 + L2 场景导航 +
|
|
9
|
+
* 工具指南——系统提示只保留稳定内容;指南三条件门控:工具开启 && 有内容)。
|
|
6
10
|
*
|
|
7
|
-
* 注入内容使用 <relevant-memories>
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 分域(子块只出现在外层标签内部,随外层一起被 sanitize 剥离,无泄漏风险)。
|
|
11
|
+
* 注入内容使用 <relevant-memories> 标签包裹(模型侧语义边界 + 助手回显剥离锚点),
|
|
12
|
+
* 召回预算(单条/整轮)超限截断并引导模型用记忆工具查全文。
|
|
13
|
+
* L0 防污染零成本:capture 侧只收 source.kind === 'user' 的消息,注入消息天然被排除。
|
|
11
14
|
*
|
|
12
15
|
* 注意:PromptContext.text 是**同步**函数,画像/场景导航这类异步文件读取必须走内存缓存,
|
|
13
16
|
* 由定时刷新 + 管线更新后的主动失效来更新。
|
package/dist/hooks/recall.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { applyRecallBudget, raceRecallTimeout, RECALL_EMBED_CAP_MS } from '../util/recall-budget.js';
|
|
3
|
+
import { errDetail } from '../util/filelog.js';
|
|
1
4
|
import { blocksToText } from '../util/text.js';
|
|
2
5
|
const PROFILE_TTL = 60_000;
|
|
3
6
|
/** 召回查询只取会话末尾 N 条消息(长会话每步把全史拼进 FTS MATCH 会让检索成本线性上涨)。 */
|
|
@@ -28,9 +31,13 @@ const MEMORY_TOOLS_GUIDE = `<memory-tools-guide>
|
|
|
28
31
|
每轮对话中,memory_search 和 conversation_search **合计最多调用 3 次**。
|
|
29
32
|
- 首次搜索无结果时,可换关键词或换工具重试,但总调用次数不要超过 3 次。
|
|
30
33
|
- 若 3 次搜索后仍无结果,说明该信息不在记忆中,请直接根据已有信息回复用户。
|
|
34
|
+
|
|
35
|
+
注:若当前环境限制直接调用工具(如仅允许代码执行入口),请经由该环境的工具调用机制
|
|
36
|
+
(如 run_code 程序内)使用以上记忆工具。
|
|
31
37
|
</memory-tools-guide>`;
|
|
32
38
|
export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
33
|
-
|
|
39
|
+
/** 每 agent 最近一次注入的命中条数(工具指南门控的"本轮召回命中"信号)。 */
|
|
40
|
+
const recallHits = new Map();
|
|
34
41
|
// 画像/场景导航按族缓存(分族隔离:注入时按会话档位选族)
|
|
35
42
|
const profileCache = {
|
|
36
43
|
chat: { persona: '', nav: '' },
|
|
@@ -46,7 +53,7 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
|
46
53
|
profileCache.work = work;
|
|
47
54
|
}
|
|
48
55
|
catch (err) {
|
|
49
|
-
logger.warn(`[memory] 画像/场景缓存刷新失败: ${
|
|
56
|
+
logger.warn(`[memory] 画像/场景缓存刷新失败: ${errDetail(err)}`);
|
|
50
57
|
}
|
|
51
58
|
};
|
|
52
59
|
// 初始刷新 + 定时刷新(TTL)
|
|
@@ -58,43 +65,72 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
|
58
65
|
const invalidateProfile = () => {
|
|
59
66
|
void refreshProfile();
|
|
60
67
|
};
|
|
61
|
-
// agent
|
|
68
|
+
// agent 销毁时清掉召回信号槽
|
|
62
69
|
ctx.on('agent/disposed', (payload) => {
|
|
63
|
-
|
|
70
|
+
recallHits.delete(payload.agent.id);
|
|
64
71
|
});
|
|
65
|
-
// ── 1. pre-step
|
|
72
|
+
// ── 1. pre-step 消息侧注入:记忆先行于每一条新的用户输入(ADR-0001) ──
|
|
73
|
+
// prepend 注册 + 先 next() 再改写:不劫持其他监听器(dsh-time-context 官方范式)。
|
|
66
74
|
if (cfg.recall.enabled) {
|
|
67
75
|
ctx.on('agent/pre-step', async (payload, next) => {
|
|
76
|
+
const decision = await next();
|
|
77
|
+
if (decision.kind === 'reject' || payload.signal.aborted)
|
|
78
|
+
return decision;
|
|
68
79
|
try {
|
|
69
80
|
const s = live.get();
|
|
70
81
|
const mode = modes.get(payload.agent.id);
|
|
71
|
-
if (!s.enabled || !s.recall || mode === 'off')
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
if (!s.enabled || !s.recall || mode === 'off')
|
|
83
|
+
return decision;
|
|
84
|
+
// 只在有新的用户来源消息的步骤注入(轮首 claim 或 steering 插话);纯工具步透传
|
|
85
|
+
const hasNewUserMessage = decision.messages.some((m) => m.source?.kind === 'user');
|
|
86
|
+
if (!hasNewUserMessage)
|
|
87
|
+
return decision;
|
|
75
88
|
const query = buildRecallQuery(payload.messages);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
l1Cache.set(payload.agent.id, []);
|
|
89
|
+
recallHits.set(payload.agent.id, 0);
|
|
90
|
+
if (!query)
|
|
91
|
+
return decision;
|
|
92
|
+
const hits = await raceRecallTimeout(stores.l1.search(query, cfg.recall.maxResults, {
|
|
93
|
+
scoreThreshold: cfg.recall.scoreThreshold,
|
|
94
|
+
family: mode === 'auto' ? undefined : mode,
|
|
95
|
+
// 远程嵌入 fetch 内层钳制:给 FTS 降级留出总预算内的时间(本地推理不钳)
|
|
96
|
+
embeddingTimeoutMs: RECALL_EMBED_CAP_MS,
|
|
97
|
+
}), cfg.recall.timeoutMs);
|
|
98
|
+
if (hits === undefined) {
|
|
99
|
+
logger.warn('[memory] 召回超时,跳过本轮注入(不阻塞对话)');
|
|
100
|
+
return decision;
|
|
89
101
|
}
|
|
102
|
+
recallHits.set(payload.agent.id, hits.length);
|
|
103
|
+
if (hits.length === 0)
|
|
104
|
+
return decision;
|
|
105
|
+
const lines = applyRecallBudget(hits.map((h) => `- [${h.scene_name ? `${h.type}|${h.scene_name}` : h.type}] ${h.content}`), { maxCharsPerMemory: cfg.recall.maxCharsPerMemory, maxTotalRecallChars: cfg.recall.maxTotalRecallChars });
|
|
106
|
+
if (lines.length === 0)
|
|
107
|
+
return decision;
|
|
108
|
+
const text = [
|
|
109
|
+
'<relevant-memories>',
|
|
110
|
+
'以下是当前对话召回的相关记忆,不代表当前任务进程,仅作为参考:',
|
|
111
|
+
'',
|
|
112
|
+
...lines,
|
|
113
|
+
'',
|
|
114
|
+
'</relevant-memories>',
|
|
115
|
+
].join('\n');
|
|
116
|
+
logger.info(`[memory] 召回注入 ${lines.length} 条 L1(mode=${mode},query="${query.slice(0, 30).replace(/\n/g, ' ')}…",agent=${payload.agent.id},消息侧)`);
|
|
117
|
+
const injection = createUserMessage({
|
|
118
|
+
content: [{ type: 'text', text }],
|
|
119
|
+
// plugin 字段是宿主 UI 的署名后缀("上下文注入 · memory")——用展示友好的
|
|
120
|
+
// 子系统名,不用 cordis id(dsh-memory);kind:'plugin' 的标题恒为通用
|
|
121
|
+
// "上下文注入"(专用"跨会话召回"标题仅留给 session-reference 来源)
|
|
122
|
+
source: { kind: 'plugin', plugin: 'memory', form: 'recall' },
|
|
123
|
+
});
|
|
124
|
+
// 注入消息排在用户新消息之前(原版 prepend 语义:先线索后问题)
|
|
125
|
+
return { kind: 'enter', messages: [injection, ...decision.messages] };
|
|
90
126
|
}
|
|
91
127
|
catch (err) {
|
|
92
|
-
logger.warn(`[memory]
|
|
128
|
+
logger.warn(`[memory] 召回注入失败(跳过本轮): ${errDetail(err)}`);
|
|
129
|
+
return decision;
|
|
93
130
|
}
|
|
94
|
-
|
|
95
|
-
});
|
|
131
|
+
}, { prepend: true });
|
|
96
132
|
}
|
|
97
|
-
// ── 2. agent 作用域上下文 provider ──
|
|
133
|
+
// ── 2. agent 作用域上下文 provider(系统提示稳定区:画像 + 导航 + 门控指南) ──
|
|
98
134
|
// 插件可能在默认 agent 创建之后才加载(组合顺序由依赖决定),
|
|
99
135
|
// 因此除了监听 agent/created,还要给已存在的 agent 补注册。
|
|
100
136
|
const registered = new WeakSet();
|
|
@@ -107,16 +143,6 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
|
107
143
|
registered.add(agent);
|
|
108
144
|
try {
|
|
109
145
|
contextDisposers.push(agent.ctx.systemPrompt.context({
|
|
110
|
-
name: 'memory:recall',
|
|
111
|
-
order: 500,
|
|
112
|
-
text: () => {
|
|
113
|
-
const s = live.get();
|
|
114
|
-
if (!s.enabled || !s.recall)
|
|
115
|
-
return '';
|
|
116
|
-
const hits = l1Cache.get(agent.id) ?? [];
|
|
117
|
-
return formatL1Memories(hits);
|
|
118
|
-
},
|
|
119
|
-
}), agent.ctx.systemPrompt.context({
|
|
120
146
|
name: 'memory:profile',
|
|
121
147
|
order: 510,
|
|
122
148
|
text: () => {
|
|
@@ -130,9 +156,14 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
|
130
156
|
const body = mode === 'auto'
|
|
131
157
|
? formatProfileAuto(profileCache.chat, profileCache.work)
|
|
132
158
|
: formatProfileSingle(profileCache[mode]);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
const hasRecallHit = (recallHits.get(agent.id) ?? 0) > 0;
|
|
160
|
+
// 指南三条件门控:工具已注册(cfg.tools)&&(稳定内容 ∥ 本轮召回命中)——
|
|
161
|
+
// 空库用户与关闭工具的用户不付这份固定 token(原版 auto-recall 同款语义)
|
|
162
|
+
if (!cfg.tools)
|
|
163
|
+
return body;
|
|
164
|
+
if (!body && !hasRecallHit)
|
|
165
|
+
return '';
|
|
166
|
+
return body ? `${body}\n\n${MEMORY_TOOLS_GUIDE}` : MEMORY_TOOLS_GUIDE;
|
|
136
167
|
},
|
|
137
168
|
}));
|
|
138
169
|
}
|
|
@@ -160,18 +191,6 @@ export function registerRecall(ctx, cfg, stores, logger, live, modes) {
|
|
|
160
191
|
});
|
|
161
192
|
return { invalidateProfile };
|
|
162
193
|
}
|
|
163
|
-
function formatL1Memories(items) {
|
|
164
|
-
if (items.length === 0)
|
|
165
|
-
return '';
|
|
166
|
-
const lines = ['<relevant-memories>', ''];
|
|
167
|
-
for (const item of items) {
|
|
168
|
-
// [type|scene] 标签 + 内容(官方召回行格式)
|
|
169
|
-
const tag = item.scene_name ? `${item.type}|${item.scene_name}` : item.type;
|
|
170
|
-
lines.push(`- [${tag}] ${item.content}`);
|
|
171
|
-
}
|
|
172
|
-
lines.push('', '</relevant-memories>');
|
|
173
|
-
return lines.join('\n');
|
|
174
|
-
}
|
|
175
194
|
/** auto 档 <user-persona> 内的域说明:让模型理解分块结构与两域的独立性。 */
|
|
176
195
|
const DOMAIN_HINT = '以下内容按记忆域分块:chat=用户个人画像(User Narrative Profile),work=团队工作准则(Team Operating Doctrine)。' +
|
|
177
196
|
'两域独立蒸馏与更新,请按当前对话语境参考对应域,不要把一域的内容当作另一域的事实。';
|
package/dist/index.d.ts
CHANGED
|
@@ -23,11 +23,13 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
23
23
|
extract: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
24
24
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
25
25
|
minMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
26
|
+
idleSeconds: import("@deepseek-ai/schemastery").default<number, number>;
|
|
26
27
|
backgroundMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
27
28
|
candidatePool: import("@deepseek-ai/schemastery").default<number, number>;
|
|
28
29
|
}>, Schemastery.ObjectT<{
|
|
29
30
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
30
31
|
minMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
32
|
+
idleSeconds: import("@deepseek-ai/schemastery").default<number, number>;
|
|
31
33
|
backgroundMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
32
34
|
candidatePool: import("@deepseek-ai/schemastery").default<number, number>;
|
|
33
35
|
}>>;
|
|
@@ -52,6 +54,9 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
52
54
|
recall: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
53
55
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
54
56
|
maxResults: import("@deepseek-ai/schemastery").default<number, number>;
|
|
57
|
+
maxCharsPerMemory: import("@deepseek-ai/schemastery").default<number, number>;
|
|
58
|
+
maxTotalRecallChars: import("@deepseek-ai/schemastery").default<number, number>;
|
|
59
|
+
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
55
60
|
includePersona: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
56
61
|
includeSceneNav: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
57
62
|
strategy: import("@deepseek-ai/schemastery").default<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -59,6 +64,9 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
59
64
|
}>, Schemastery.ObjectT<{
|
|
60
65
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
61
66
|
maxResults: import("@deepseek-ai/schemastery").default<number, number>;
|
|
67
|
+
maxCharsPerMemory: import("@deepseek-ai/schemastery").default<number, number>;
|
|
68
|
+
maxTotalRecallChars: import("@deepseek-ai/schemastery").default<number, number>;
|
|
69
|
+
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
62
70
|
includePersona: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
63
71
|
includeSceneNav: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
64
72
|
strategy: import("@deepseek-ai/schemastery").default<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -118,11 +126,13 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
118
126
|
extract: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
119
127
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
120
128
|
minMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
129
|
+
idleSeconds: import("@deepseek-ai/schemastery").default<number, number>;
|
|
121
130
|
backgroundMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
122
131
|
candidatePool: import("@deepseek-ai/schemastery").default<number, number>;
|
|
123
132
|
}>, Schemastery.ObjectT<{
|
|
124
133
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
125
134
|
minMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
135
|
+
idleSeconds: import("@deepseek-ai/schemastery").default<number, number>;
|
|
126
136
|
backgroundMessages: import("@deepseek-ai/schemastery").default<number, number>;
|
|
127
137
|
candidatePool: import("@deepseek-ai/schemastery").default<number, number>;
|
|
128
138
|
}>>;
|
|
@@ -147,6 +157,9 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
147
157
|
recall: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<{
|
|
148
158
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
149
159
|
maxResults: import("@deepseek-ai/schemastery").default<number, number>;
|
|
160
|
+
maxCharsPerMemory: import("@deepseek-ai/schemastery").default<number, number>;
|
|
161
|
+
maxTotalRecallChars: import("@deepseek-ai/schemastery").default<number, number>;
|
|
162
|
+
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
150
163
|
includePersona: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
151
164
|
includeSceneNav: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
152
165
|
strategy: import("@deepseek-ai/schemastery").default<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
|
@@ -154,6 +167,9 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
|
|
|
154
167
|
}>, Schemastery.ObjectT<{
|
|
155
168
|
enabled: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
156
169
|
maxResults: import("@deepseek-ai/schemastery").default<number, number>;
|
|
170
|
+
maxCharsPerMemory: import("@deepseek-ai/schemastery").default<number, number>;
|
|
171
|
+
maxTotalRecallChars: import("@deepseek-ai/schemastery").default<number, number>;
|
|
172
|
+
timeoutMs: import("@deepseek-ai/schemastery").default<number, number>;
|
|
157
173
|
includePersona: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
158
174
|
includeSceneNav: import("@deepseek-ai/schemastery").default<boolean, boolean>;
|
|
159
175
|
strategy: import("@deepseek-ai/schemastery").default<"keyword" | "embedding" | "hybrid", "keyword" | "embedding" | "hybrid">;
|
package/dist/index.js
CHANGED
|
@@ -269,8 +269,12 @@ export async function apply(ctx, config) {
|
|
|
269
269
|
})
|
|
270
270
|
: undefined;
|
|
271
271
|
embedManagerRef = embedManager;
|
|
272
|
-
const runner = new MemoryRunner(ctx, config, stores, logger, live);
|
|
272
|
+
const runner = new MemoryRunner(ctx, config, stores, logger, live, modes);
|
|
273
273
|
await runner.init();
|
|
274
|
+
// 档位切换同步(ADR-0003):切走按捕获档位落袋 / 切 off 挂起 / 切回清挂起
|
|
275
|
+
modes.setModeChangeHandler((sessionId, oldMode, newMode) => runner.onModeChange(sessionId, oldMode, newMode));
|
|
276
|
+
// 闲置兜底:静默达标会话的未蒸馏切片自动落袋(idleSeconds=0 关闭)
|
|
277
|
+
runner.startIdleTimer();
|
|
274
278
|
// 重建控制器(存储降级时不建——RPC 端点走 supported=false 分支)
|
|
275
279
|
const rebuild = storageOk && !db.isDegraded()
|
|
276
280
|
? new RebuildController(ctx, config, stores, db, runner, logger, live)
|
package/dist/llm.d.ts
CHANGED
|
@@ -13,6 +13,19 @@ export interface LlmCallOptions {
|
|
|
13
13
|
/** 诊断日志(缺失时静默,不影响调用) */
|
|
14
14
|
logger?: MemoryLogger;
|
|
15
15
|
}
|
|
16
|
+
/** L1 抽取(大输入块的 JSON 记忆数组输出)。 */
|
|
17
|
+
export declare const LAYER_MAX_TOKENS_EXTRACT = 16000;
|
|
18
|
+
/** L1 去重(合并决策数组,输出比抽取短)。 */
|
|
19
|
+
export declare const LAYER_MAX_TOKENS_DEDUP = 8000;
|
|
20
|
+
/** L2 场景整合(完整场景 Markdown 文件输出,输出最重的层)。 */
|
|
21
|
+
export declare const LAYER_MAX_TOKENS_L2 = 32000;
|
|
22
|
+
/** L3 画像(完整 persona 文档)。 */
|
|
23
|
+
export declare const LAYER_MAX_TOKENS_L3 = 16000;
|
|
24
|
+
/**
|
|
25
|
+
* 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
|
|
26
|
+
* 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
|
|
27
|
+
*/
|
|
28
|
+
export declare function layerMaxTokens(base: number, reasoningEffort: string): number;
|
|
16
29
|
/** 解析蒸馏用的 provider/model:配置优先,其次当前默认选择。 */
|
|
17
30
|
export declare function resolveModelRoute(ctx: Context, cfg: MemoryConfig): Promise<{
|
|
18
31
|
provider: string;
|
package/dist/llm.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { createUserMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
2
2
|
import { errDetail } from './util/filelog.js';
|
|
3
|
+
// ── 分层输出预算(规格 C 节):结构化蒸馏远用不到总闸级预算,逐层设护栏;
|
|
4
|
+
// 模型跑偏时单次损失有界。数值"先试跑"状态,按线上截断率调整。
|
|
5
|
+
/** L1 抽取(大输入块的 JSON 记忆数组输出)。 */
|
|
6
|
+
export const LAYER_MAX_TOKENS_EXTRACT = 16_000;
|
|
7
|
+
/** L1 去重(合并决策数组,输出比抽取短)。 */
|
|
8
|
+
export const LAYER_MAX_TOKENS_DEDUP = 8_000;
|
|
9
|
+
/** L2 场景整合(完整场景 Markdown 文件输出,输出最重的层)。 */
|
|
10
|
+
export const LAYER_MAX_TOKENS_L2 = 32_000;
|
|
11
|
+
/** L3 画像(完整 persona 文档)。 */
|
|
12
|
+
export const LAYER_MAX_TOKENS_L3 = 16_000;
|
|
13
|
+
/**
|
|
14
|
+
* 思考档预算放大:reasoning 计入输出预算(v4-flash 事故:high 思考可吃光全部
|
|
15
|
+
* 预算致正文 0 字符)——effort 为 high/max 时分层预算 ×4。
|
|
16
|
+
*/
|
|
17
|
+
export function layerMaxTokens(base, reasoningEffort) {
|
|
18
|
+
return reasoningEffort === 'high' || reasoningEffort === 'max' ? base * 4 : base;
|
|
19
|
+
}
|
|
3
20
|
/** 解析蒸馏用的 provider/model:配置优先,其次当前默认选择。 */
|
|
4
21
|
export async function resolveModelRoute(ctx, cfg) {
|
|
5
22
|
if (cfg.llm.provider && cfg.llm.model) {
|
package/dist/pipeline/l1.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 移植 MemoryCore 的 l1 runner 语义(抽取 prompt → batch 冲突检测 → apply)。
|
|
4
4
|
*/
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
|
-
import { callLLM, parseJsonLogged } from '../llm.js';
|
|
6
|
+
import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_DEDUP, LAYER_MAX_TOKENS_EXTRACT, parseJsonLogged } from '../llm.js';
|
|
7
7
|
import { formatExtractionPrompt, getExtractMemoriesSystemPrompt } from '../prompts/l1-extraction.js';
|
|
8
8
|
import { formatBatchConflictPrompt, getConflictDetectionSystemPrompt } from '../prompts/l1-dedup.js';
|
|
9
9
|
import { familyForType } from '../types.js';
|
|
@@ -37,9 +37,8 @@ export function chunkByCharBudget(messages, budgetChars) {
|
|
|
37
37
|
export async function runExtraction(ctx, cfg, store, states, pending, background, logger, mode) {
|
|
38
38
|
if (!cfg.extract.enabled)
|
|
39
39
|
return { stored: 0, skipped: true, sceneName: chainHead(states, mode), newRecords: [] };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
40
|
+
// 触发阈值(渐进爬坡 + 按会话切片计数)由 runner 判定(trigger.ts);
|
|
41
|
+
// 此处不再重复 gate——重建轮 force 与 warmup 早期轮次都会传入小于稳态值的切片。
|
|
43
42
|
// 纯档强制族 = 档位族;auto 档抽取后的记录族按 type 前缀判定
|
|
44
43
|
const forcedFamily = mode === 'auto' ? undefined : mode;
|
|
45
44
|
// 情境链锚点桶:纯档用本族;auto 用最近活跃的族(chainHead 同源)
|
|
@@ -64,6 +63,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
|
|
|
64
63
|
const raw = await callLLM(ctx, cfg, {
|
|
65
64
|
system: getExtractMemoriesSystemPrompt(mode),
|
|
66
65
|
user: userPrompt,
|
|
66
|
+
maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_EXTRACT, cfg.llm.reasoningEffort),
|
|
67
67
|
logger,
|
|
68
68
|
});
|
|
69
69
|
const scenes = parseJsonLogged(raw, 'L1 抽取', logger);
|
|
@@ -101,6 +101,7 @@ export async function runExtraction(ctx, cfg, store, states, pending, background
|
|
|
101
101
|
const dedupRaw = await callLLM(ctx, cfg, {
|
|
102
102
|
system: getConflictDetectionSystemPrompt(mode),
|
|
103
103
|
user: dedupPrompt,
|
|
104
|
+
maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_DEDUP, cfg.llm.reasoningEffort),
|
|
104
105
|
logger,
|
|
105
106
|
});
|
|
106
107
|
const decisions = parseJsonLogged(dedupRaw, 'L1 去重判定', logger);
|
package/dist/pipeline/l2.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callLLM, parseJsonLogged } from '../llm.js';
|
|
1
|
+
import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_L2, parseJsonLogged } from '../llm.js';
|
|
2
2
|
import { buildScenePrompt, formatSceneSummaries } from '../prompts/scene.js';
|
|
3
3
|
import { Bm25Index } from '../store/bm25.js';
|
|
4
4
|
const REQUEST_RE = /\[PERSONA_UPDATE_REQUEST\]\s*reason:\s*([\s\S]*?)\[\/PERSONA_UPDATE_REQUEST\]/;
|
|
@@ -27,7 +27,12 @@ export async function runSceneConsolidation(ctx, cfg, scenes, newMemories, logge
|
|
|
27
27
|
maxScenes: cfg.l2.maxScenes,
|
|
28
28
|
family,
|
|
29
29
|
});
|
|
30
|
-
const raw = await callLLM(ctx, cfg, {
|
|
30
|
+
const raw = await callLLM(ctx, cfg, {
|
|
31
|
+
system: systemPrompt,
|
|
32
|
+
user: userPrompt,
|
|
33
|
+
maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_L2, cfg.llm.reasoningEffort),
|
|
34
|
+
logger,
|
|
35
|
+
});
|
|
31
36
|
// ── 解析:先取 PERSONA_UPDATE_REQUEST 标记,再解析操作数组 ──
|
|
32
37
|
const reqMatch = REQUEST_RE.exec(raw);
|
|
33
38
|
const personaRequestedReason = reqMatch?.[1]?.trim() || undefined;
|
package/dist/pipeline/l3.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callLLM } from '../llm.js';
|
|
1
|
+
import { callLLM, layerMaxTokens, LAYER_MAX_TOKENS_L3 } from '../llm.js';
|
|
2
2
|
import { buildPersonaPrompt } from '../prompts/persona.js';
|
|
3
3
|
export async function runPersona(ctx, cfg, scenes, persona, state, logger, family) {
|
|
4
4
|
if (!cfg.l3.enabled)
|
|
@@ -48,7 +48,12 @@ export async function runPersona(ctx, cfg, scenes, persona, state, logger, famil
|
|
|
48
48
|
existingPersona,
|
|
49
49
|
triggerInfo: reason,
|
|
50
50
|
});
|
|
51
|
-
const raw = await callLLM(ctx, cfg, {
|
|
51
|
+
const raw = await callLLM(ctx, cfg, {
|
|
52
|
+
system: systemPrompt,
|
|
53
|
+
user: userPrompt,
|
|
54
|
+
maxTokens: layerMaxTokens(LAYER_MAX_TOKENS_L3, cfg.llm.reasoningEffort),
|
|
55
|
+
logger,
|
|
56
|
+
});
|
|
52
57
|
const body = unwrapFence(raw);
|
|
53
58
|
if (!body) {
|
|
54
59
|
logger.error(`[memory] L3 输出为空,原始输出前 400 字符: ${raw.slice(0, 400)}`);
|
|
@@ -46,32 +46,57 @@ export declare class MemoryRunner {
|
|
|
46
46
|
private readonly stores;
|
|
47
47
|
private readonly logger;
|
|
48
48
|
private readonly live;
|
|
49
|
+
/** 会话档位只读句柄(闲置扫描跳过 off 档会话;挂起语义)。 */
|
|
50
|
+
private readonly modes?;
|
|
49
51
|
private tasks;
|
|
50
52
|
private draining;
|
|
51
53
|
/** 停止标志(dispose 序置位):不再取新任务;进行中任务自然收尾,其 DB 写入失败由各层兜底捕获。 */
|
|
52
54
|
private stopped;
|
|
53
55
|
private pending;
|
|
56
|
+
/** 各档位桶渐进阈值(1 起步翻倍至稳态毕业;ADR-0003,随 pending.json 持久化)。 */
|
|
57
|
+
private warmup;
|
|
58
|
+
/** 每会话最后活动时间(闲置兜底判定用)。 */
|
|
59
|
+
private lastActivity;
|
|
54
60
|
private readonly pendingFile;
|
|
55
|
-
private background;
|
|
56
61
|
/** 分族 checkpoint(init 后可用;重建收尾也从这里读活引用)。 */
|
|
57
62
|
states: FamilyStates;
|
|
58
63
|
private afterRun;
|
|
59
|
-
constructor(ctx: Context, cfg: MemoryConfig, stores: MemoryStores, logger: MemoryLogger, live: LiveSettingsHandle
|
|
64
|
+
constructor(ctx: Context, cfg: MemoryConfig, stores: MemoryStores, logger: MemoryLogger, live: LiveSettingsHandle,
|
|
65
|
+
/** 会话档位只读句柄(闲置扫描跳过 off 档会话;挂起语义)。 */
|
|
66
|
+
modes?: {
|
|
67
|
+
get(sessionId: string): string;
|
|
68
|
+
} | undefined);
|
|
60
69
|
init(): Promise<void>;
|
|
61
|
-
/**
|
|
70
|
+
/** 启动补跑:对每个非空桶的每个会话切片入队一次蒸馏尝试(受 live 开关与
|
|
71
|
+
* 生效阈值约束;不足阈值的消息等用户继续或闲置兜底,失败不无限重试)。 */
|
|
62
72
|
private scheduleStartupRetry;
|
|
63
73
|
/** L1 抽取待重试的消息条数(状态面板用)。 */
|
|
64
74
|
get pendingCount(): number;
|
|
65
75
|
/** 管线跑完一轮后的回调(用于召回缓存失效)。 */
|
|
66
76
|
setAfterRun(fn: () => void): void;
|
|
67
77
|
/** 一轮对话结束后入队(L0 落盘由 capture 在 turn/end 即时完成,不排蒸馏队列)。 */
|
|
68
|
-
enqueue(sessionId: string, messages: ConversationMessage[], mode: ExtractMode
|
|
78
|
+
enqueue(sessionId: string, messages: ConversationMessage[], mode: ExtractMode, opts?: {
|
|
79
|
+
force?: boolean;
|
|
80
|
+
}): void;
|
|
69
81
|
/** 重建任务入队(低优先级:让位于正常轮次;由 RebuildController 分块驱动)。 */
|
|
70
82
|
enqueueRebuildTask(run: () => Promise<unknown>): void;
|
|
71
|
-
/** 重建蒸馏轮:统一 auto
|
|
83
|
+
/** 重建蒸馏轮:统一 auto 档,全量强制蒸馏(不受阈值约束——历史小会话也必须出记忆)、
|
|
84
|
+
* 不受缓冲 200 上限(历史会话全量入桶,由 char 预算分块)。 */
|
|
72
85
|
runRebuildTurn(sessionId: string, messages: ConversationMessage[]): Promise<number>;
|
|
73
86
|
/** 停止取新任务(插件 dispose 序调用;进行中任务照常跑完但不 await——LLM 慢调用不拖住宿主卸载)。 */
|
|
74
87
|
stop(): void;
|
|
88
|
+
/** 启动闲置兜底定时器(index.ts 装配;idleSeconds=0 关闭)。 */
|
|
89
|
+
startIdleTimer(): void;
|
|
90
|
+
/** 闲置扫描:静默达标且有切片的会话按捕获档位落袋(off 档会话挂起跳过)。 */
|
|
91
|
+
private flushIdleSlices;
|
|
92
|
+
/** 把某会话在各桶中的切片按捕获档位逐个入队强制蒸馏(闲置兜底 / 档位切换共用)。 */
|
|
93
|
+
private enqueueSessionSlices;
|
|
94
|
+
/**
|
|
95
|
+
* 档位切换同步(session-modes 的 set() 回调,ADR-0003):
|
|
96
|
+
* 非 off 间切换 → 该会话切片立即按捕获档位蒸馏(新档位从空切片起步);
|
|
97
|
+
* 切到 off → 挂起(切片留存,闲置扫描跳过);从 off 切回 → 挂起片按捕获档位落袋。
|
|
98
|
+
*/
|
|
99
|
+
onModeChange(sessionId: string, oldMode: string, newMode: string): void;
|
|
75
100
|
private pushTask;
|
|
76
101
|
private drain;
|
|
77
102
|
/** 缓冲落盘(每次蒸馏尝试后调用;失败只告警不阻断管线)。
|
|
@@ -79,4 +104,10 @@ export declare class MemoryRunner {
|
|
|
79
104
|
* 蒸馏尝试时反复整量序列化落盘(多 MB 级 IO);重建轮豁免维持。 */
|
|
80
105
|
private persistPending;
|
|
81
106
|
private runTurn;
|
|
107
|
+
/**
|
|
108
|
+
* 抽取并消费一个会话切片:成功才把切片移出桶并推进爬坡阈值;失败保留切片待重试。
|
|
109
|
+
* 调用方已保证切片达到生效阈值(或 force)。背景参考按会话从 L0 现查并剔除切片
|
|
110
|
+
* 自身(ADR-0003:会话间互不污染、重启不丢背景)。
|
|
111
|
+
*/
|
|
112
|
+
private extractSessionSlice;
|
|
82
113
|
}
|