## 背景 讨论 #164 的「第三腿触发」:dream 自动整理与查询按需综合之外,缺一个 agent 自己决定调用的整理入口——长段工作收尾时,agent 发起「整理当前上下文、与已有记忆比对去重、沉淀 document」(document 型见姊妹 issue)。 ## 形态(#164 已对齐) 1. **dryRun**:返回比对报告(候选新条 vs 库内相似条),不写库; 2. agent 判断报告后调 **apply** 落库;筛除项进归档不删; 3. 调用全程留审计回执——复用 dream_runs 的 receipt 语义,不新建审计面。 ## 边界 - 现有 9 工具与命令面均无此入口,不重复造:新工具一个(dryRun/apply 两档)或一个工具带 mode 参数,倾向后者; - opt-in 默认关(新行为开关惯例),settings.js 白名单注册 + lightMode 考虑; - 上下文大幅精简前的「关键连续性」整理是其主场景(对照 [2608.22752](https://arxiv.org/abs/2608.22752) 压缩悬崖:任务状态是蒸馏管线覆盖不到的部分)。
Activity
heptaspirit commentedon Sep 19, 2026
对照 #249 的提案时意识到一个问题:单独实现该主动整理接口依然不能解决“AI为什么什么时候调用它”的问题,至少需要在 #249 中的能力说明完善后,通过注入形式教会 AI 使用。
所以对该 Feature 提出几个新建议:
modusensus commentedon Sep 19, 2026
@heptaspirit 顺着 #230 问一句 #231(agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply)的归属。
这两条是你在 #164 里提的同一份设计的两个半边——「产物分两型」与「触发加第三条腿」。你认领了 #230,所以想确认 #231 是你打算一起做,还是留给我们这边接?
如果由我们接,会按你在 #164 里定的口径走(先 dryRun 出比对报告、agent 判断后再 apply、调用全留审计),实现前把接口形态贴上来给你过一遍。如果由你一起做,我们就不动,只在 review 时配合。
heptaspirit commentedon Sep 19, 2026
@modusensus 本 Issue 我们认领,接在 #230 之后开发。
需要对的一个口径是,我们目前的开发是打算为 #249 铺路,如果你对 #249 还有什么建议或者方向调整也请提出,这样在本 Issue 开发前能有一个比较清晰的路径。
modusensus commentedon Sep 19, 2026
@heptaspirit #231 归你们,欢迎 —— 三条建议我这边都同意,补一句实施口径:
关于 #249,先说结论:方向同意,P0 + P1 先做(能力说明进工具描述是纯加法零成本;分池是全篇证据最强的一条)。逐条给几条调整,都带核对过的依据:
1. §10 的父开关默认关会翻掉现有默认
src/config.js现在是autoInject: z.boolean().default(true)。如果新的「自动注入总闸」默认关,存量用户升级后会静默失去注入——这与仓库纪律的方向相反:那条纪律是「新功能不打扰老用户」,不是「把老功能关掉」。建议:
autoInject保留原语义与default(true);分组只在面板呈现层做(把现有开关与新子项放进同一组),不引入第二个总闸。这样「父关 = 全不生效」的语义由既有的autoInject天然承担,验收里的「父关零注入」也随之成立。2. B2「摘要 + 指针」目前没有摘要可读
service.toApiList产出的 DTO 是id / type / title / content / tags / importance / source / created_at / updated_at(外加条件展开的 scope 三键与 occurred_at/sensitivity)——没有摘要字段。现有注入本来就是「截断头(injectContentMaxChars,默认 300 字)+ id」。建议第一版把 B2 明确写成「截断头 + 显式 id 指针」:零新增字段、零新增 LLM 调用,与 §3 的非目标一致。真正的「摘要」留给 #230 / document 那条线,避免 B2 偷偷长出一条摘要管线。
3. §7 那条「需确认追加 API 形态」已核实:可用
宿主
dsh-agent自己的实现就是范例(lib/index.js:160):agent/pre-step回调签名是async ({ agent, messages, signal, step }, next),await next()拿到 decision,返回{ ...decision, messages: [...decision.messages, notice] }即可追加;还支持{ prepend: true }控制顺序。agent/turn-stopping、agent/request-error、agent/session-start也都存在。所以 N3 的「写库 + 末尾注入」双落点不必先降级为持久规则,可以直接按主路径实现;「宿主不提供压缩前时机」的降级分支保留着即可(§4.4 的写法没问题)。
4. 分池默认开:建议第一版仍默认关
你的论证我认同(默认配置不该保留一个已实测的缺陷)。但本项目的行为变更纪律是「先能回滚、再谈默认」:建议第一版仍 opt-in 默认关,同时在 README 与面板文案里给出推荐配置(甚至一键套用),等真实负载数据到位再单独一次「翻默认」的提交。这样把「我们相信这个修正」与「我们替你改行为」分成两步。
5. 面板分组不要挤进 P0/P1
lib/client.js是 4300+ 行手写、无构建步骤的文件,改它本身要单独排期。P0/P1 新增的配置键会自动出现在现有 flag 列表里(无需改面板即可用),所以建议:P0 → P1 → 面板分组 → P2 → P3(只计量),分组 UI 作为独立 PR。6. 一处分工确认
P0(工具描述文案 + 库可见性行)与 P1(分池)我们这边可以直接接;你们手上是 #230 + #231。如果你们想连 P1 一起做(它动的是注入排序与轮换的既有契约,与你们 #230/#231 的注入形态同源),我们就不动,只在 review 配合——你们定。
feat(memory): document 型记忆——注册校验/摘要+指针落库/supersede 记账(#230)
modusensus commentedon Sep 19, 2026
@heptaspirit 认领与三条口径(不进工具列表、不做独立 opt-in、注入形式与 #249 同批实现)都同意,按此开工。#249 的方向表态见那边的回复——第一批(能力说明 + constraint/preference 分池保真)与 #231 并行不冲突,你说的「为 #249 铺路」的路径成立。
fix(organize): apply 只重放 dryRun 校验过的快照,回执与数据同事务(slow-stack#231)
feat(organize): agent 主动整理接口 dryRun -> apply,复用 dream_runs 回执(slow-st…
fix(organize): apply 只重放 dryRun 校验过的快照,回执与数据同事务(slow-stack#231)
fix(organize): 新增文件的两条 biome 告警——可选链与未用绑定(slow-stack#231)
heptaspirit commentedon Sep 21, 2026
@modusensus
CodeRabbit 在 #267 上把「报告判定与落库判定是两套实现」标成了 Major。这条我在 9-19 的 PR 评论(#267 comment)里已经交底过它的 A.1 与 A.3,当时问的是「放这个 PR 还是单独一个改动」。PR 评论你多半看不到,所以搬到这里问一句。
分歧有三处,都在
src/organize.js的 dryRun 与src/service.js的saveWithDedupe之间:title.trim()全等 → 标题只差标点时,报告exact、apply 新建。type === "summary" && source === "dream"按来源去重、对 narrative 按tags[0]去重;dryRun 只比标题 → 报告new、apply 并入既有行并改写它的标题。limit: null(全量),写路径limit: 100→ 同类型超过 100 行且重复行排在窗口外时,报告exact、apply 新建。后果是报告会领先于写路径,而 agent 是照着报告的
match.id去归档旧行的,库里可能留下一条没合并的新行。根因是 organize 自己重写了一遍写路径的匹配逻辑。收口办法是从
saveWithDedupe里把「这个候选会并进哪一行」提成一个导出函数(写路径行为不变),dryRun 直接调它——这样这类分歧在结构上不会再出现。要不要做、放 #267 还是后续单独一个 PR,你说一句我照办。#267 那边我不再推代码,只加评论。
另外补一句配套的:organize 现在只有 service 层入口(
src/tools.js的 10 个工具里没有它),这是照你定的口径留在「与 #249 同批暴露」。等下一步定了,把「注册工具 + 注入指引」接上,这套整理能力就能交给 AI 调用。在那之前只需要收口一处:dryRun 的 scope 默认值来自调用方 payload(src/organize.js:130),当它变成模型可调用的工具时,这个值应当改由 tools 层注入当前 scope——与register_document的 strictScope 同口径(src/document.js:87-88:调用者 scope 看不见的行按「不存在」处理,不得为跨 scope id 建立引用)。modusensus commentedon Sep 21, 2026
@heptaspirit 三处分歧我逐条对过 main 上的代码,全部属实,且根因正如你说的:organize 自己重写了一遍写路径的匹配逻辑。
title.trim()全等limit: null全量limit: 100报告领先于写路径的后果链也成立:agent 照报告的
match.id归档旧行,库里留一条没合并的新行——这正是 CodeRabbit 那个 Major 的真实危害,不是纸面分歧。拍板:收口单独一个 PR,不进 #267。
理由三条:
收口办法本身赞成:从
saveWithDedupe把「这个候选会并进哪一行」提成导出函数,dryRun 直接调它——匹配逻辑单一来源,这类分歧在结构上不会再出现,这是根治不是补丁。谁来写:你提的方案、你更熟 organize 侧,你来开,我们 review。scope 注入点(organize.js:130)——同意,记入工具化批次。 与 #249 同批暴露时,「dryRun 默认 scope 改由 tools 层注入当前 scope」与
register_document的 strictScope 同口径(document.js:87-88:调用者 scope 看不见的行按「不存在」处理)。这条我认领进批次清单,不是现在改。关键判断:#231 拍板 = 收口单独 PR 由你开(不进 #267),scope 注入点随工具化批次处理;三处分歧核实全部属实。
Merge pull request #267 from heptaspirit/feat/231-organize
heptaspirit commentedon Sep 24, 2026
@modusensus
补一条关于「整理什么时候才会发生」的机制观察,是照这个 issue 的形态在自己库上跑了一遍之后落的结论。
现状:整理是拉取式,长碎片那条路是推送式。
organize的 dryRun 要吃调用方给的候选(src/organize.js的payload.candidates,一次上限 50 条),apply 按 agent 的判断落库,整条链要有人主动来调。而长碎片那条路是自动的:会话蒸馏逐条走saveWithDedupe,去重键是(type, title, agent_scope, workspace_scope, sensitivity),标题不同就新建。两边一比,结果就是同一个议题在库里长成几十条各自独立的事实,而库里没有任何一处会产生「这个话题该整理了」的信号。所以缺的是触发,不是动作。 我倾向触发信号走确定性遥测,动作留给 agent。
为什么不放在 dream 做。 dream 是后台无人任务,适合判定可复算、结果可回滚的机械动作(精确重复、近重复对、撤销归档后的向量清理),它现在的候选阈值
dreamCandidateMinSim也是这个用法。但把一族碎片收敛成一条时间线是判断活:哪条是旧口径、哪些矛盾是真的、某一步当时为什么这么定,这些因果只在工作上下文里。反过来看合并也说明同一件事:merge能把几十条拍成一条,得到的是「一坨」而不是时间线,而且那一坨里会同时留着旧读数与新口径,读的人分不出哪个还算数。dream 在这里能做的是发信号,不是写。触发时机建议保守。 长段工作收尾、上下文接近压缩边缘,加上话题锚水位(同一话题新建到了 N 行)与归档净增速率。不要每轮都建议。阈值按 #254 定的「先记录不拦」同口径走:先只发信号只记数,等遥测有真实分布再定,别在没数据时拍一个数。
实体层已经有一部分现成的整理素材,不用重新造。 抽取开着的时候,
entity_attrs本身就是一条按实体版本的属性时间线(valid_from/valid_until,当前值 =valid_until IS NULL),entity_relations是边,两张表都带memory_id。store.getLinkedMemoryIds(entityIds)把实体 id 集合反查成Map<memory_id, "attr" | "relation">(attr 档 = 当前有效属性行直挂的记忆,relation 档 = 关系行挂的记忆,同一记忆两条边都有时取 attr)。这跟给去重候选集定的「按结构化锚召回先收窄成本、不做全扫」是同一口径,dryRun 的候选可以由它推出来,不必只等调用方给。mention_count、last_seen与它的有效属性行数,本身就是「这个东西被反复写」的计数,不用新增埋点。但实体层现在与整理对不上,这一点得先说清。
entityRecall会把归档行与遗忘行直接 continue 掉,而registerDocument全程不碰实体表(document.js里没有 attrs / relations 的引用);对比decisions.js在合并记忆时会调migrateAttrsToMemory,把 loser 的属性行迁到 keeper。也就是一次合并会把实体关联带走,一次升格吸收不会:被吸收进 document 的记忆归档之后,实体召回静默丢掉它们,属性行与关系行仍指着归档的记忆。整理要变成常规动作,这里需要一个口径,是吸收时把entity_attrs.memory_id迁到 document 行,还是保留指向归档行、只在检索面用includeArchived取。有一点我还没想清楚,想听你的。整理动作的产物(document 行)只在
documentMemoryEnabled打开时才进注入(injectCandidates的INJECT_TYPES_WITH_DOCUMENT门),而它吸收掉的 evidence 原子已经归档,也就是整理要成立得先确认这个开关是开的,否则一族会从注入面消失。这个耦合该在整理侧声明,还是该让 document 行无条件进注入、只受预算管?heptaspirit commentedon Sep 24, 2026
@modusensus
承接上一条,把可执行的那半段补上,免得只停在「缺触发」。
阶段一只落水位,不改行为。 把三类数放进状态面的同一处:话题锚的重复计数与间隔(G2 已在记)、实体侧的
mention_count/last_seen/ 有效属性行数、归档侧的净增速率与可压掉行数(#275 第五指标)。这一阶段只记数、只在状态面显示,不启动整理;阈值等遥测有真实分布再定,与 #254 第一阶段同口径。阶段二让候选可由锚推出,动作仍留给 agent。 dryRun 的候选除调用方传入之外,允许按话题锚与
store.getLinkedMemoryIds推;报告里带一族的版本序列(entity_attrs的valid_from/valid_until,当前值 =valid_until IS NULL)与entity_relations的边;apply 走 #230 的 document 与吸收归档。dream 那边不承担写 document,理由同上一条(它适合判定可复算、结果可回滚的机械动作,dreamCandidateMinSim就是那个用法)。阈值那条想问一句。 近重复现在基本不开火:
summarizeDedupeMinSim0.92、searchSemanticDedupThreshold0.95、dreamCandidateMinSim0.85,都设在「几乎完全相同」那一侧,而一族碎片之间的关系常常不是重复,是同一件事的不同侧面、或不同时间的口径。是给同义改写留个口子,还是去重门保持现在的严口径、这类收敛一律归整理?我倾向前者不动:去重门管的是穿透,收敛管的是粒度,两件事别混在一个阈值里。这条不涉及。 不改去重键、不动拦截行为、不自动 VACUUM;物理删除与归档区的周期处理仍属 #275 第二批;整理也不采用「把多行合并成一行」那种追加式合并,得到的是一条越来越长的记录,不是可读的时间线。
modusensus commentedon Sep 24, 2026
@heptaspirit 两件都拍板,另有一个 PR 侧的决定一并说。
#254 穿透口:这批不加,等阶段二。 理由是阶段一没有拦截——豁免参数的消费者不存在,先立参数等于给没有的门装把手。而且你说的张力是真的:agent 自报「这是用户要求的」恰好是我们要在阶段二用确定性遥测去核的那个主张,现在把自报通道立起来,遥测就脏了。第二阶段接
confirm时把这个口径(自报豁免在审计里怎么标、怎么跟水位对照)带着真实数据一起定。本批保持三条信号干净收口。#231 两阶段 + 去重门口径:都同意。
合并侧:#311 已合,#312 update-branch 后跟着合,第五指标读的 content_hash 列已进 main。
modusensus commentedon Oct 20, 2026
现状(2026-10-21 对照
main实测)核心机制(dryRun → apply,全留审计)已经实现在
src/organize.js里,但——它没有任何调用入口。已落地
dsh-mneme/src/organize.js(24,555 B)完整实现,模块头:2-3写明「agent 主动整理接口(issue [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231,口径基线 = 聊几个我觉得值得做的方向 #164 的『第三腿触发』):dryRun 出比对报告 → agent 判断 → apply 落库;筛除项进归档不删;调用全程留审计回执」。organize({ mode: "dryRun"|"apply", ... }, opts)(organize.js:63)。dream_runs审计(run_type='organize',:196);apply 必须引用一次真实 dryRun 的run_id,且只重放校验过的快照(:254-264、5284428)。service.js:2557const { organize } = createOrganizer({...}),:2573挂到 service 上。test/organize.test.js存在。真正缺的:出口
dsh-mneme/src/tools.js注册的十个工具是memory_save/memory_search/memory_list/memory_get/memory_update/memory_delete/memory_forget/memory_archive/memory_register_document/memory_runtime—— 没有 organize(tools.js:127/207/266/314/352/411/433/468/581/689)。api.js里organize0 命中;api-standalone.js里 0 命中。.organize(调用点 0 处。也就是说:引擎造好了,油门没接上。 issue 要求的「新工具一个(dryRun/apply 两档)或一个工具带 mode 参数」正好就是缺的那一步。
issue 里另外两条边界
config.js里organizeEnabled0 命中)。还缺什么
tools.js注册一个memory_organize工具(mode: dryRun|apply),description 写明「apply 必须引用真实 dryRun 的 run_id」;organize.enabled(默认 false)+settings.js白名单成对注册 + 旗标计数锁;memory_organize(dryRun)→ 拿到run_id→memory_organize(apply, run_id)→ 断言落库 +dream_runs有回执。接手建议
这是整个 open issue 里最「差一步」的一条:24 KB 的实现、测试都在,只差一个工具注册 + 一个配置键。工作量大概半天,而且不碰任何既有语义(默认关 = 零行为变化)。
modusensus commentedon Oct 21, 2026
认领 #231 剩下的那一步:给已造好的引擎接上调用入口。
src/organize.js本体一行不动(四条硬规则、快照重放、回执语义全保持),只补工具注册 + 配置面 + 测试。计划
organizeEnabled,默认 false。settings.js的FEATURE_FLAG_BOOLEANS;扁平键,因此不需要api.js的NESTED_FLAG_PATHS条目([Feature] 写入准入(non-write 判定):把「这条该不该进库」前移到 LLM 之前 #254 那类漏登记坑在这里不适用);LIGHT_MODE_OFF:轻量档默认关,用户显式勾选仍然赢(合并顺序「用户开关 > 轻量预设 > bundle」,同continuityRescueEnabled/distillRecords.enabled口径)。理由:dryRun→apply 是两步协议,弱模型漏run_id或跳过报告的概率高,而一次 dryRun 的代价是嵌入调用 + 有界全库扫描。这比我先前设想的「轻量档直接不出这个工具」更准——预设关,但不剥夺用户显式开启的权利。tools.js注册memory_organize(mode: dryRun|apply,缺省dryRun),复用既有hiddenTools闸门:organizeEnabled !== true时不注册(模型看不到就不会调,比在描述里劝它少调用可靠——v0.8.5 工具暴露闸的原话定性)。描述按set_scope_state([feature] 作用域级可逆闸门:让单路记忆能被单独按下与松开,而不必改配置重启 #471)的写法:写清「apply 必须引用一次真实 dryRun 的run_id」「筛除=归档不删」「开关默认关,没开时工具根本不存在」。opts.hiddenIds:organize.js:251收{ hiddenIds },但全仓零生产者(工具没接,自然没人传)。工具侧按调用者 scope 计算:报告里被引用的行过service.isVisibleInScope,看不见的按「不存在」处理——不让整理接口成为第二条跨 scope 改库的通道。tools.test.js四条名单锁(默认关不含新工具)+ 新增「默认关不注册 / 开启后注册」两例;[Test] 全工具矩阵的「DTO 键集 ⊆ output schema」系统性断言——防 #184 类漂移 #195 矩阵TOOL_NAMES补memory_organize与 dryRun/apply 分支;端到端一例dryRun → run_id → apply断言落库且dream_runs有回执;api.test.js旗标锁 103→104;docs-config-keys守卫要求docs/CONFIGURATION.md补organizeEnabled(含 lightMode 清单那一节)。[Unreleased]。本 PR 不做(各归各处)
run_type='organize'的行就会进dream-status的最近 5 条列表——正是 PR feat(organize): agent 主动整理接口 dryRun -> apply(issue #231) #267 备注 1 预留、说「等注册工具那批再决定」的那件事。要不要过滤或标注是面板侧口径,单开 issue,不在这里碰lib/client.js(面板线同一时刻只许一个写入者,分叉的 UI 车道还没收)。验收:
npm run sync+npm test全绿,check-sync 平价。fix(organize): filter dryRun match targets by caller visibility (#231 …
docs(readme): the tool table was missing two tools, and nothing could…
feat(organize): expose the organizer to agents as memory_organize (sl…
docs: repair the stale tool count, map the modules that were never li…