@furongjun1999/dsh-memory 0.7.4 → 0.7.5

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.
Files changed (30) hide show
  1. package/README.md +631 -630
  2. package/codebuddy/CODEBUDDY.md +5 -5
  3. package/docs/discipline/templates/zcode-user.md.tmpl +4 -1
  4. package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +1 -1
  5. package/docs/eval//345/217/221/345/270/20313_/344/270/212/344/270/213/346/226/207/350/207/252/347/256/241/347/220/206/346/234/272/345/210/266_v1.0.md +114 -0
  6. package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +80 -1
  7. package/docs/plans//347/201/265/346/236/242/350/272/253/344/275/223/303/227/350/204/221_/344/270/226/347/225/214/346/250/241/345/236/213/345/257/271/346/216/245/350/256/276/350/256/241_v0.1.md +76 -0
  8. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +461 -461
  9. package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.2.md +121 -0
  10. package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.3.md +117 -0
  11. package/lib/hooks.d.ts +34 -0
  12. package/lib/hooks.js +165 -0
  13. package/lib/index.js +22 -1
  14. package/lib/lib/mdcg_client.d.ts +37 -0
  15. package/lib/lib/mdcg_client.js +44 -0
  16. package/md_cg/audit.py +7 -1
  17. package/md_cg/bench_governance.py +4 -1
  18. package/md_cg/bench_locomo_zh.py +12 -2
  19. package/md_cg/bench_zh_mad.py +12 -2
  20. package/md_cg/interop.py +25 -9
  21. package/md_cg/sleep.py +223 -103
  22. package/md_cg/sustain.py +150 -4
  23. package/md_cg/test_sustain_bounded.py +624 -0
  24. package/md_cg/units.py +4 -1
  25. package/package.json +1 -1
  26. package/skills/plugin.json +1 -1
  27. package/src/hooks.ts +177 -0
  28. package/src/index.ts +22 -1
  29. package/src/lib/mdcg_client.ts +48 -0
  30. package/zcode/AGENTS.md +5 -5
package/src/hooks.ts CHANGED
@@ -24,10 +24,29 @@
24
24
  * `delegationDepth=0` 真实在写);**仍未观测**的是 `origin='subagent'` /
25
25
  * `delegationDepth>0` / `form='relay'` 的真实出现(见 installMemoryHooks 内注释)。
26
26
  *
27
+ * contextWindow(滑动窗口,2026-10-06):**短期记忆 = 运行态事件窗口**。
28
+ * 写侧把每条 user/assistant 消息(经既有 sanitize 的原文)追加进灵枢的
29
+ * `_recent.jsonl` 滚动窗口(`cg(op=recent, action=add)`);注入侧在每次
30
+ * system-prompt/assemble 时注入独立的「【本会话近期对话】」块
31
+ * (`cg(op=session, action=recall)` 的 recent 段)——宿主压缩(retained 置空
32
+ * 后重新投影)时该块随既有块一起自然重现,即「上下文满后早期对话的接续锚」。
33
+ *
34
+ * ⚠️ **两轨关系(勿混)**:
35
+ * · 知识面轨:`opts.userMessage` / `opts.assistantMessage` 管「消息沉淀成
36
+ * 记忆节点」(role:user/assistant,进检索正排);使用者 2026-09-27 的
37
+ * `userMessage=false` 决策关的是**这一轨**(消息不自动进知识面)。
38
+ * · 窗口轨:`opts.contextWindow`(enabled/turns)管「消息进运行态窗口」
39
+ * (`_recent.jsonl`:滚动淘汰、**不占知识层、不进检索正排、不是知识节点**)。
40
+ * 两轨**独立开关、互不替代**:知识面关掉时窗口照常工作(这正是本机制存在的
41
+ * 意义——压缩后的续接锚不依赖自动记忆的写入开关)。
42
+ *
27
43
  * autoRecall:通过 system-prompt/assemble 事件(waterfall,异步允许)在每次
28
44
  * 模型请求组装 system prompt 时自动注入灵枢最近记忆
29
45
  * (`stg(op=timeline)`,最近记忆节点时间线),让记忆"自动可用"而不只依赖
30
46
  * Agent 主动调用 recall/think 工具。失败静默(不影响请求)。
47
+ * contextWindow 的注入面是**独立第二块**(`lingshu:session-window`)——既有块
48
+ * (timeline / `lingshu:auto-recall`)的行为一字不动;第二块的注入门控 =
49
+ * 注入面总开关 `opts.autoRecall` × 本机制开关 `contextWindow.enabled`。
31
50
  * ⚠️ 该注入块的**稳定性**决定宿主是否新追加快照:内容没变时也必须照旧 push
32
51
  * (宿主按渲染后的整段文本去重);跳过 push 反而会各追加一份「有块/无块」的快照
33
52
  * —— 详见 installMemoryHooks 里的长注释。
@@ -76,6 +95,18 @@ export interface MemoryHooksOptions {
76
95
  * apply 探针同目录同惯例),供测试与定制注入;审计自身失败静默降级,
77
96
  * 绝不冒泡进记忆路径。不改任何既有选项语义。 */
78
97
  auditPath?: string
98
+ /** **短期会话窗口**(滑动窗口,2026-10-06):enabled(缺省 true)控制整条
99
+ * 机制(写侧 + 注入侧同时静默);turns(缺省 10)= 窗口取数条数
100
+ * (`session_recall` 的 `recent_limit`,语义是**条**不是轮)。
101
+ *
102
+ * ⚠️ **两轨关系(勿混,头注有详述)**:`userMessage` / `assistantMessage`
103
+ * 管**知识面**(消息沉淀成记忆节点,进检索正排);本组管**运行态窗口**
104
+ * (`_recent.jsonl`:滚动淘汰、不占知识层、不进正排、不是知识节点)。
105
+ * 两轨独立开关、互不替代——知识面开关关掉时窗口照常工作。
106
+ *
107
+ * 可选(缺省视为 `{ enabled: true, turns: 10 }`):既有调用方不传本项时
108
+ * 行为与缺省一致,不改变任何既有选项语义。 */
109
+ contextWindow?: { enabled: boolean; turns: number }
79
110
  }
80
111
 
81
112
  /** 从 ContentBlock[] 提取纯文本。 */
@@ -323,6 +354,57 @@ function formatTimelineDecayed(payload: unknown): string {
323
354
  return out.join('\n').slice(0, RECALL_MAX_CHARS)
324
355
  }
325
356
 
357
+ // ---------------------------------------------------------------- 会话窗口渲染
358
+ // 「短期会话窗口」(contextWindow)的取数/渲染常量。与上方 RECALL_* 同款纪律:
359
+ // 常量写死在此处(而非 config schema——未知键会被 schema 剥离)。
360
+ /** 窗口块取数的整包 token 预算(服务端 `session_recall` 的 budget_tokens)。
361
+ *
362
+ * ⚠️ 这是**整包**预算,不是 recent 段的独立预算:notes/goals/tasks/self_state
363
+ * 与 recent 共享(服务端裁剪循环交替丢 recent / notes 尾部,md_cg/mdcos.py:
364
+ * 3351-3362)。取 600 是**有意保守**——本块定位是「存在性锚点 / 接续提示」,
365
+ * 宁可少注入几条,也不挤占宿主上下文。实测(空库 + 10 条窗口条目):
366
+ * budget_tokens=600 → recent 段 8 条;1200 → 10 条。 */
367
+ const WINDOW_BUDGET_TOKENS = 600
368
+ /** 单条窗口条目预览上限(字符)。 */
369
+ const WINDOW_ITEM_CHARS = 120
370
+ /** 窗口块总长上限(字符)。 */
371
+ const WINDOW_MAX_CHARS = 800
372
+
373
+ /** 会话窗口载荷 → 注入文本(限幅沿 RECALL 分级渲染的风格:单条 ≤120 字、
374
+ * 整块 ≤800 字)。
375
+ *
376
+ * `cg(op=session, action=recall)` 的 `recent` 段 = `{role, text, t}` 列表,
377
+ * 按**新→旧**排列(服务端 `recent_events` 的 newest_first)。渲染取**旧→新**
378
+ * (对话流水的自然阅读序),但**裁剪保最新**:先按服务端序(新→旧)逐条
379
+ * 试放入上限(放不下就**停在更旧的条目上**,整条不放入),最后整体反转
380
+ * ——总长受限时丢掉的是**最旧**条目(近因优先),且**不切条目中间**
381
+ * (逐条整放/整弃;最后才 slice 是错的——那会把最新一条切掉半截)。
382
+ *
383
+ * 空载荷 / 无 recent 段 / 全空条目 → 返回空串(调用方据此**不 push** 第二块)。 */
384
+ function formatSessionWindow(payload: unknown, limit: number): string {
385
+ const items = (payload && typeof payload === 'object'
386
+ && Array.isArray((payload as { recent?: unknown }).recent))
387
+ ? (payload as { recent: Array<Record<string, unknown>> }).recent
388
+ : []
389
+ const rows: string[] = []
390
+ const max = Math.max(1, Math.floor(limit) || 10)
391
+ let used = 0
392
+ for (const it of items.slice(0, max)) {
393
+ const role = String(it['role'] ?? '').trim() || 'user'
394
+ const preview = String(it['text'] ?? '').replace(/\s+/g, ' ').trim()
395
+ if (!preview) continue
396
+ const body = preview.length > WINDOW_ITEM_CHARS
397
+ ? preview.slice(0, WINDOW_ITEM_CHARS) + '…'
398
+ : preview
399
+ const row = `[${role}] ${body}`
400
+ const next = used === 0 ? row.length : used + 1 + row.length
401
+ if (used > 0 && next > WINDOW_MAX_CHARS) break
402
+ rows.push(row)
403
+ used = next
404
+ }
405
+ return rows.reverse().join('\n')
406
+ }
407
+
326
408
  /** 取宿主会话标识(只用于**归因/隔离**,不参与任何权限判断)。
327
409
  *
328
410
  * 动机:记忆写入必须带会话身份才能区分不同会话;读取默认只看本会话(防串台),
@@ -410,6 +492,62 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
410
492
  * 绝不冒泡进记忆路径、不改任何写入/过滤判定。 */
411
493
  const audit = new HookAuditRecorder(opts.auditPath)
412
494
 
495
+ // ── 短期会话窗口(contextWindow):与知识面写入**独立成轨**(见文件头)──
496
+ // 缺省开启({ enabled: true, turns: 10 });enabled=false → 写侧与注入侧
497
+ // **同时静默**。turns = 窗口取数条数(session_recall 的 recent_limit 语义是
498
+ // **条**不是轮);clamp 到 1~50(与既有 recallLimit 同款纪律,防配置失手)。
499
+ const cw = opts.contextWindow ?? { enabled: true, turns: 10 }
500
+ const cwEnabled = cw.enabled !== false
501
+ const cwTurns = Math.max(1, Math.min(50, Math.floor(cw.turns || 10)))
502
+
503
+ /** 窗口条目判定(contextWindow 写侧):这条事件是否值得进「近期对话」窗口。
504
+ * 返回 null = 不写。
505
+ *
506
+ * 过滤面与知识面**同源**(复用同一组谓词函数,故两处口径不会各自漂移):
507
+ * · user/message:`form==='relay'`(H1 消息级)与 `kind!=='user'`(插件注入 /
508
+ * 系统上下文)不写——与自动记忆同一判定;有文本才写;
509
+ * · assistant/message:有文本即写;
510
+ * · 其它事件类型(tool/result 等):一律不写(窗口是**对话**记录)。
511
+ *
512
+ * ⚠️ 两处**有意不同门**(这是设计,不是遗漏):本判定**不看**
513
+ * `opts.userMessage` / `opts.assistantMessage`——那两个开关管知识面(消息沉淀
514
+ * 成记忆节点),本机制由 `contextWindow.enabled` 管(运行态窗口)。若把窗口写
515
+ * 也挂到那两个开关上,使用者既有的 `userMessage=false` 就会连带关掉窗口,
516
+ * 「知识面关、窗口开」的独立轨道即不成立(两轨关系见文件头)。
517
+ *
518
+ * ⚠️ 子代理会话(H1 会话级)由调用点**更早**拦回(在取 sid 之前),不在此重判
519
+ * ——与自动记忆同口径:委派指令不进真人窗口。 */
520
+ const windowEntry = (event: SessionEvent): { role: 'user' | 'assistant'; text: string } | null => {
521
+ if (event.type === 'user/message') {
522
+ if (isRelayedMessage(event.data.source)) return null
523
+ if (event.data.source?.kind !== 'user') return null
524
+ const text = extractText(event.data.content)
525
+ return text ? { role: 'user', text } : null
526
+ }
527
+ if (event.type === 'assistant/message') {
528
+ const text = extractText(event.data.message.content)
529
+ return text ? { role: 'assistant', text } : null
530
+ }
531
+ return null
532
+ }
533
+
534
+ /** 窗口写入(fire-and-forget):失败只记 warn,**绝不炸会话流**(沿 memorize
535
+ * 的 catch 风格)。桥未就绪静默跳过(窗口是运行态面,不阻塞对话;「未就绪」
536
+ * 的告警已由 memorize 路径负责,不在此重复刷屏)。
537
+ *
538
+ * ⚠️ 同步抛出也必须被吞(catch 两段):真实部署下 graph 是 MdcgClient 全量
539
+ * 实现;但桥替换实现 / 降级替身缺该方法时,抛错同样不得越过会话流边界。 */
540
+ const noteRecent = (role: 'user' | 'assistant', text: string,
541
+ meta: Record<string, unknown>): void => {
542
+ if (!graph.isReady()) return
543
+ try {
544
+ void graph.recentAdd(role, text, meta, ['dsh', 'recent-window'])
545
+ .catch((err: Error) => ctx.logger.warn(`dsh-memory: 短期窗口写入失败: ${err.message}`))
546
+ } catch (err) {
547
+ ctx.logger.warn(`dsh-memory: 短期窗口写入失败: ${(err as Error).message}`)
548
+ }
549
+ }
550
+
413
551
  /** 本实例是否曾观测到会话(B 治本批)。
414
552
  *
415
553
  * 会话状态本体是**进程级单点**(lib/session_state.ts;hooks 面观测、工具面
@@ -507,6 +645,29 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
507
645
  `【灵枢最近记忆】\n${text.slice(0, RECALL_MAX_CHARS)}`)),
508
646
  })
509
647
  }
648
+ // ── 独立第二块:「【本会话近期对话】」(contextWindow 注入面)──
649
+ // 与上方 timeline 块**块名/取数/开关各自独立**(既有块一字未动)。
650
+ // 稳定性口径与既有块同等:**每步都 push**(内容随新轮增长属预期——
651
+ // 宿主对内容变化追加快照的既有行为不变;窗口不变时两块逐字节相同)。
652
+ // fail-soft:取数/渲染失败 → 静默、不 push 第二块,绝不抛——且因
653
+ // 上方既有块已先 push,本块的失败**不影响**既有块(反之亦然)。
654
+ // 门控 = 注入面总开关 autoRecall(本 handler 的注册条件)× 本机制开关
655
+ // contextWindow.enabled。
656
+ if (cwEnabled) {
657
+ try {
658
+ const win = await graph.sessionRecall(sid, cwTurns, WINDOW_BUDGET_TOKENS)
659
+ const winText = formatSessionWindow(win, cwTurns)
660
+ if (winText) {
661
+ // 注入边界同规(文件头硬约束):不可信内容边界 + `{{` 转义,
662
+ // 都只改注入副本——窗口原文在库内保真。
663
+ assembly.contexts.push({
664
+ name: 'lingshu:session-window',
665
+ text: escapePromptBraces(renderUntrustedMemoryBlock(
666
+ `【本会话近期对话】\n${winText}`)),
667
+ })
668
+ }
669
+ } catch { /* 静默:窗口取数失败不影响请求,也不影响既有块 */ }
670
+ }
510
671
  }
511
672
  }
512
673
  catch { /* 静默:召回失败不影响请求 */ }
@@ -595,5 +756,21 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
595
756
  ...sessionTag,
596
757
  }))
597
758
  }
759
+
760
+ // ── 短期窗口写侧(contextWindow):与上方知识面写入**独立成轨** ──
761
+ // 位置在既有分支链**之外**:不受 opts.userMessage / opts.assistantMessage
762
+ // 门控(那两个开关管知识面;本机制由 contextWindow.enabled 管——两轨关系
763
+ // 见文件头与 windowEntry 注释)。
764
+ // 过滤面与知识面同源:H1 会话级(子代理整条会话)已在函数首拦回;此处经
765
+ // windowEntry 复用同一组谓词(relay / kind),再经**同一个** sanitize 脱敏
766
+ // ——纯凭据消息(sanitize 返回 null)同样不写(不把明文凭据引进窗口)。
767
+ // 失败只记 warn(noteRecent),绝不冒泡进会话流。
768
+ if (cwEnabled) {
769
+ const entry = windowEntry(event)
770
+ if (entry) {
771
+ const safe = sanitize(entry.text)
772
+ if (safe !== null) noteRecent(entry.role, safe, { ...sessionTag })
773
+ }
774
+ }
598
775
  })
599
776
  }
package/src/index.ts CHANGED
@@ -169,8 +169,29 @@ export const Config: z<Config> = z.object({
169
169
  autoRecall: z.boolean().default(true),
170
170
  autoRecallLimit: z.number().default(4),
171
171
  desensitize: z.boolean().default(true),
172
+ /** **短期会话窗口**(滑动窗口,2026-10-06)——「短期保留近 N 条对话记录」
173
+ * 的机制实现(长期记忆仍走灵枢的显式调用)。
174
+ *
175
+ * enabled(缺省 true)控制**整条机制**(写侧 + 注入侧同时静默);
176
+ * turns(缺省 10)=窗口取数条数(服务端 `session_recall` 的
177
+ * `recent_limit`,语义是**条**不是轮)。
178
+ *
179
+ * ⚠️ **两轨关系(勿混,机制头注见 src/hooks.ts / mdcg_client.ts)**:
180
+ * · 知识面轨:`userMessage` / `assistantMessage` 管「消息沉淀成记忆
181
+ * 节点」(role:user/assistant,进检索正排)。使用者 2026-09-27 的
182
+ * `userMessage=false` 决策关的是**这一轨**——消息不自动进知识面;
183
+ * · 窗口轨:本组管「消息进**运行态窗口**」(`_recent.jsonl`:滚动淘汰、
184
+ * 不占知识层、不进检索正排、**不是知识节点**)。
185
+ * 两轨独立开关、互不替代:知识面关掉时窗口照常工作(这正是本机制存在
186
+ * 的意义——上下文压缩后的续接锚不依赖自动记忆的写入开关)。 */
187
+ contextWindow: z
188
+ .object({
189
+ enabled: z.boolean().default(true),
190
+ turns: z.number().default(10),
191
+ })
192
+ .default({ enabled: true, turns: 10 }),
172
193
  })
173
- .default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6, autoRecall: true, autoRecallLimit: 4, desensitize: true }),
194
+ .default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6, autoRecall: true, autoRecallLimit: 4, desensitize: true, contextWindow: { enabled: true, turns: 10 } }),
174
195
  toolCallTimeoutMs: z.number().default(60_000),
175
196
  maxRetryDelayMs: z.number().default(30_000),
176
197
  failOnStartupError: z.boolean().default(false),
@@ -19,6 +19,8 @@
19
19
  * 外部裁决回填 → MdcgClient.verify() → MCP cg(op=verify)
20
20
  * 最近记忆时间线 → MdcgClient.timeline() → MCP stg(op=timeline)
21
21
  * 近期事件窗口 → MdcgClient.recent() → MCP cg(op=recent)
22
+ * 窗口追加事件 → MdcgClient.recentAdd() → MCP cg(op=recent, action=add)
23
+ * 会话续接包 → MdcgClient.sessionRecall() → MCP cg(op=session, action=recall)
22
24
  * 身份读取 → MdcgClient.identity() → MCP cg(op=identity)
23
25
  * 白箱能力验证 → MdcgClient.whitebox() → MCP cg(op=whitebox)
24
26
  * 服务信息 → MdcgClient.serviceInfo()→ MCP cg(op=info)
@@ -307,6 +309,52 @@ export class MdcgClient {
307
309
  return this.cg({ op: 'recent', limit })
308
310
  }
309
311
 
312
+ /** 追加一条**近期事件窗口**记录(`cg(op=recent, action=add)` →
313
+ * `md_cg` 的 `remember_event(role, text, tags, meta, window)`)。
314
+ *
315
+ * 这是「滑动窗口」写侧:事件落 `_recent.jsonl`(**运行态面**),按窗口滚动
316
+ * 淘汰(服务端缺省 200 条,`mdcg.py:91 DEFAULT_RECENT_WINDOW`)——它**不是
317
+ * 知识节点**:不占 knowledge 层、不进检索正排,与 `remember()` 的知识面沉淀
318
+ * 是两条独立的轨道(见 src/hooks.ts 文件头的 contextWindow 头注)。
319
+ *
320
+ * ⚠️ **不注入 as_unit**(与 `write()` 同款理由的反面):本调用不写任何层的
321
+ * 节点(服务端 `remember_event` 不做层白名单校验),收窄单元无收益且可能压低
322
+ * 事件密级(缺省 internal);`meta.session` 由调用方显式给出(沿本文件
323
+ * `remember()` 的 sessionTag 口径)。
324
+ *
325
+ * ⚠️ 服务端对 `meta` 做 `setdefault`(tenant/session/harness/unit,见
326
+ * `mdcos.py:5057 remember_event`):调用方已写的键**不被覆盖**。 */
327
+ recentAdd(role: string, text: string,
328
+ meta: Record<string, unknown> = {},
329
+ tags: string[] = []): Promise<unknown> {
330
+ return this.cg({ op: 'recent', action: 'add', role, text, meta, tags })
331
+ }
332
+
333
+ /** **会话续接包**(`cg(op=session, action=recall)` → `md_cg` 的
334
+ * `session_recall`):一次取回 notes / goals / tasks / **recent 事件窗口** /
335
+ * unresolved / self_state,按 `budget_tokens` 整包裁剪(服务端
336
+ * `mdcos.py:3351-3362`:交替丢 recent / notes 尾部)。
337
+ *
338
+ * 本插件消费其中的 `recent` 段——滑动窗口的**注入面**(见 src/hooks.ts)。
339
+ * `recent_limit` 语义 = 近期事件**条数**(非「轮数」);服务端读取时会按
340
+ * `max(1, recent_limit or 10)` 归一(`mdcos.py:3309`)。
341
+ *
342
+ * ⚠️ 两条服务端事实(决定注入侧的可达性,勿据本方法名臆测):
343
+ * ① `recent` 段取 `recent_events(limit=recent_limit)` —— **不按 session
344
+ * 过滤**(近期事件是运行态滚动窗口,会话归属只写在每条事件的 meta 里);
345
+ * ② budget 是**整包**预算:库内 notes/tasks/self_state 占位越多,同样
346
+ * budget 下 recent 段被裁得越短(实测:空库 + 10 条窗口条目,
347
+ * budget_tokens=600 → recent 8 条;1200 → 10 条)。
348
+ *
349
+ * 只读调用,不注入 as_unit(同其它读路径:读无副作用,收窄只会压低 owner
350
+ * 的 private 读能力)。 */
351
+ sessionRecall(session: string, recentLimit: number, budgetTokens: number): Promise<unknown> {
352
+ return this.cg({
353
+ op: 'session', action: 'recall',
354
+ session, recent_limit: recentLimit, budget_tokens: budgetTokens,
355
+ })
356
+ }
357
+
310
358
  /** 最近记忆**时间线**(AEIS `timeline` 的对应物):`stg(op=timeline)` →
311
359
  * `{count, limit, items:[{id, layer, start, end, preview}]}`,按时间倒序。
312
360
  *
package/zcode/AGENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 本文件由 `scripts/render_discipline.py` 从真源
4
4
  > `docs/工作纪律_认知图条目_v1.1.json` 渲染生成,**请勿手改**;改真源后重跑渲染。
5
- > 真源指纹(SHA256 前16位):e8c979edba52de41
5
+ > 真源指纹(SHA256 前16位):9486668b0dadac39
6
6
 
7
7
  ## 概述
8
8
 
@@ -128,7 +128,7 @@
128
128
  ### 16. 任务收尾归档(记忆闭环)
129
129
 
130
130
  - **触发**:任务执行完成|修改落地后|交付后
131
- - **动作**:任务收尾→提炼核心修改(内容/原因/位置/验证结论四要素, 不写中间过程/试错/调试/重复确认)→按 CCG 六要素成文(# 功能名/# 生效条件/# 子功能/# 执行/# 验证方式/# 不适用条件 六行缺一不可, 与正文四要素并置)→写入灵枢记忆(认知图/MCP memory)——text 类缺要素即被写入闸门拒(REJECT, 当场返回缺失清单, 不是重试无用), 按清单补齐后重写即可→读回确认(写入后发起一次读取查询确认写入成功且可检索)→标注关联条目+更新 subgraph/depends_on
131
+ - **动作**:任务收尾→提炼核心修改(内容/原因/位置/验证结论四要素, 不写中间过程/试错/调试/重复确认)→按 CCG 六要素成文(# 功能名/# 生效条件/# 子功能/# 执行/# 验证方式/# 不适用条件 六行缺一不可, 与正文四要素并置)→写入灵枢记忆(认知图/MCP memory)——text 类缺要素即被写入闸门拒(REJECT, 当场返回缺失清单, 不是重试无用), 按清单补齐后重写即可→读回确认(写入后发起一次读取查询确认写入成功且可检索)→标注关联条目+更新 subgraph/depends_on; 长会话每10轮→同款间歇归档(写入+读回确认一次), 不等收尾
132
132
  - **不适用**:情感交互|闲聊|纯查询无改动
133
133
  - **声明**:按工作纪律第16条: 任务收尾归档——每次任务执行完只提炼核心修改(内容/原因/位置/验证结论)并按 CCG 六要素(功能名/生效条件/子功能/执行/验证方式/不适用条件)成文存入灵枢记忆(text 类六要素缺失会被写入闸门当场拒绝并返回缺失清单——补齐后重写即可, 不必等读回确认才发现), 写入后发起一次读取查询确认写入成功且可检索, 禁写中间过程/试错/调试等无效信息, 与第2条形成「查记忆→执行→写记忆→读回确认」闭环。
134
134
 
@@ -142,9 +142,9 @@
142
142
  ### 18. 工作区索引优先
143
143
 
144
144
  - **触发**:查找工作区文件|需要了解工作区结构|跨目录检索定位|长会话续接/上下文压缩后查找工作区文件
145
- - **动作**:定位工作区文件→先读 WORKSPACE_INDEX.md(仓根)→有则按表中职责/关键入口直接定位→无则先 python scripts/workspace_index.py --write 生成再读→守卫报陈化先重生成→不以重复全盘浏览代替; 新增顶层目录/根级文件须在脚本 DIR_ROLES/ROOT_FILES 登记后重生成; 上下文压缩/长会话续接后, 先 cg route『工作区索引』重建纪律视野(压缩会丢弃未留下执行痕迹的纪律, 只剩被声明过的条目——须主动召回)
145
+ - **动作**:定位工作区文件→先读 WORKSPACE_INDEX.md(仓根)→有则按表中职责/关键入口直接定位→无则先 python scripts/workspace_index.py --write 生成再读→守卫报陈化先重生成→不以重复全盘浏览代替; 新增顶层目录/根级文件须在脚本 DIR_ROLES/ROOT_FILES 登记后重生成; 上下文压缩/长会话续接后, 先 cg route『工作区索引』重建纪律视野(压缩会丢弃未留下执行痕迹的纪律, 只剩被声明过的条目——须主动召回); 压缩/续接后三步=route『工作区索引』→输出声明→回取本会话近10轮窗口重建上下文(cg(op=recent)/session_recall/会话 md 镜像)
146
146
  - **不适用**:已明确路径的单文件操作|纯会话内对话/问答(无文件查找)|仓外路径/系统路径(git 追踪面之外)
147
- - **声明**:按工作纪律第18条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。
147
+ - **声明**:按工作纪律第18条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。 压缩续接后并回取本会话窗口(最近10轮)重建上下文。
148
148
 
149
149
  ## 声明出口(`response.direct` 原文 · 未输出即未执行)
150
150
 
@@ -167,7 +167,7 @@
167
167
  | 15 | 按工作纪律第15条: 命令执行统一走python——argv列表+显式UTF-8+PYTHONUTF8=1, 不经Windows shell, 规避GBK解码异常。 另: 全仓文本与路径/文件名一律UTF-8(中文可进路径), 不做控制台兼容(取用走python, 控制台乱码属显示层)。 另: 公开面路径写相对路径——本机绝对路径以中性占位代(仓内 `<仓根>`/家目录 `~/`/临时 `%TEMP%`), 门禁 check_local_paths。 |
168
168
  | 16 | 按工作纪律第16条: 任务收尾归档——每次任务执行完只提炼核心修改(内容/原因/位置/验证结论)并按 CCG 六要素(功能名/生效条件/子功能/执行/验证方式/不适用条件)成文存入灵枢记忆(text 类六要素缺失会被写入闸门当场拒绝并返回缺失清单——补齐后重写即可, 不必等读回确认才发现), 写入后发起一次读取查询确认写入成功且可检索, 禁写中间过程/试错/调试等无效信息, 与第2条形成「查记忆→执行→写记忆→读回确认」闭环。 |
169
169
  | 17 | 按工作纪律第17条: 任务派发统一走蜂巢——任何执行性任务经蜂巢 spawn/submit 执行并留痕(spec/status/result), agent本体只做编排; 宿主自带subagent/team不是等价通道(zcode端的工作流工具为等效蜂巢工具——使用者2026-10-04裁定; 蜂巢功能完全完善之前, zcode端可优先使用zcode工作流处理), 兜底须声明。L1只读判定(产物落点=不改仓库/外部状态)可直跑, 须输出「L1 直跑:<命令> — 风险/频次/可逆性」留痕。 |
170
- | 18 | 按工作纪律第18条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。 |
170
+ | 18 | 按工作纪律第18条: 工作区索引优先——查工作区文件先读 WORKSPACE_INDEX.md(仓根, 管线生成); 无则先跑 scripts/workspace_index.py --write 生成再读, 不以重复全盘浏览代替。 压缩续接后并回取本会话窗口(最近10轮)重建上下文。 |
171
171
 
172
172
  ## 记忆接口速查(`cg` / `stg`)
173
173