Commit 35307d15b38c4452b9c3ca4db602ec2555ef2b06

Authored by yibo
1 parent d279c17f

docs(29-dream): dream 设计草案 —— 四条取舍已拍板,附实查到的原料现状

28-followups §1.1 一直是「方向已定、设计未定」。这份把取舍摆出来并记下拍板结果。

## 先看清原料:它现在是脏的(全部实查,不是推测)

· **写进 memory 的不是「事实」,是截断的日志行**。appendToMemoryMd 的全部逻辑是
  「取第一行、砍到 80/100 字符」。cto/_global 的实际内容里,三条「关键事实」是
  `🔧 [TOOL_CALL] Bash (ID: toolu_01RsQ...)` —— 工具调用 id。
  §5.6b 点名的错误版本是「自动把原文写进共享 SKILL.md」;**现在这套已经是那个错误的
  单公司版**,只是还没跨公司共享,所以还没造成泄漏。
· 长期记忆是一个 3000 字符的滚动窗口,没有按重要性保留的机制。
· 96 个「公司态目录」里混着 train/ workspace/ 和**带时间戳的测试残留**
  (tmp-w7-*-1786986291739-*、qroute-co-13/14)。dream 第一步「枚举所有公司」
  现在没有可靠答案 —— 可靠的答案在 company_agents 表里,目录只能用来定位文件。
· 75 份 memory.md 里只有 7 份有非模板内容。**dream 现在几乎没有原料。**

## 四条拍板(2026-08-20)

A 去敏 = 独立判定 agent + 写作要求。失效方向是「少沉淀」不是「泄漏」——
  两种错代价不对称时判据偏向贵的那侧,与 P3B §八 FAILING_VERDICTS 同一条经验。
B 落点 = memory/ 与 SKILL.md 都写,**必要时迭代 soul.md / agent.md**。
C 触发 = 定时每日一次。
D 顺序 = 上游修复与 dream 一起做。

## B 把范围扩大了,随之而来的约束单列一节

「本体只有一份」意味着写 agent.md / soul.md 影响**所有雇了它的公司**,
而不是提炼出这条经验的那一家。所以四个落点按可逆性分层,并记了四条硬约束:
· agent.md 有一处 2000 字符截断 —— **作用点要说准**:那是给规划 agent 看的
  「角色定义摘录」(graph-engine.js:915),agent 自己那条装配链不截断。
  判据因此是「保证前 2000 字符仍然自足」,不是「不许追加」。
  (初稿我按记忆写成「装配时只读前 2000 字」,查了代码才改对。)
· 这四个里三个是 git 跟踪的**定义**,不是运行态 —— dream 改它们会进 git status。
· 毕业闸门读这些文件,写坏会让本体雇不进任何公司。
· 必须能分别回滚:经验写坏只影响上下文,agent.md 写坏影响所有公司。

## 还没定的

决策 D(提炼质量怎么评);B.2 里「定义文件直接写还是走提案」——
我建议走提案并写进了推进步骤,但那是建议不是拍板。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/todo/system-design/29-dream-design.md 0 → 100644
  1 +# 29-DREAM · 经验汇流(dream)—— 设计草案
  2 +
  3 +> **所属**: [28 Agent 注册表统一](../../done/system-design/28-agent-registry-unification.md) §5.6b 锁住的那条约束的下游设计;未确认项来源 [28-followups §1.1](./28-followups.md)。
  4 +> **日期**: 2026-08-20
  5 +> **状态**: 📝 **四条取舍已由用户拍板(2026-08-20,见 §三),实现未开始。**
  6 +> 标 ✅ 的是已生效的约束或有实测证据的事实;标 🔒 的是本轮拍板的决定。
  7 +
  8 +---
  9 +
  10 +## 〇、一句话
  11 +
  12 +dream 是设想中定时跑的一套逻辑:整理**所有 agent 在所有公司**的 history 与 memory,
  13 +**显式提炼**后沉淀到 skill memory 上,让经验跨公司汇流,而**不泄漏任何一家公司的具体信息**。
  14 +
  15 +---
  16 +
  17 +## 一、开工前先看清原料:它现在是脏的
  18 +
  19 +> 这一节全是**实查 2026-08-20 的仓库状态**得到的,不是推测。
  20 +> 结论很直接:**在原料变干净之前,做提炼是没有意义的**——垃圾进,垃圾出。
  21 +
  22 +### 1.1 现在写进 memory 的不是「事实」,是截断的日志行
  23 +
  24 +`memory-service.appendToMemoryMd()` 的全部逻辑是:
  25 +
  26 +```js
  27 +const taskOneLine = (task || '').split('\n')[0].substring(0, 80);
  28 +const resultOneLine = (result || '').split('\n')[0].substring(0, 100);
  29 +const entry = `- [${timestamp}] ${taskOneLine} → ${resultOneLine}`;
  30 +```
  31 +
  32 +**取第一行、砍到 80/100 字符**。这不是提炼,是搬运 + 截断。
  33 +`cto/_global/memory/memory.md` 的实际内容:
  34 +
  35 +```
  36 +## 关键事实
  37 +- [2026-08-20] [TASK from ceo] → ..._global/` 下不新增 `callback-*` 目录。反向验证过:临时把 `isTestEnv` 改成 `() => false`,该用例 FAIL。
  38 +- [2026-08-20] [SYSTEM HEARTBEAT] → 🔧 [TOOL_CALL] Bash (ID: toolu_01RsQ3gh5niCFyUZMFZj2hxF)
  39 +- [2026-08-20] [SYSTEM HEARTBEAT] → 🔧 [TOOL_CALL] Bash (ID: toolu_01MG6RqDgqcRCi5ageGk84Kt)
  40 +```
  41 +
  42 +后三条是**工具调用 id**。它们作为「关键事实」被写下来、被注入下一轮提示词、
  43 +并且会成为 dream 的原料。第一条更糟:它是一句话的**中段**(开头被截掉了),
  44 +读起来像事实,其实缺主语。
  45 +
  46 +> §5.6b 说的错误版本是「每轮对话自动把原文写进共享 SKILL.md」。
  47 +> **现在这套已经是那个错误版本的单公司版了** —— 只是还没跨公司共享,所以还没泄漏。
  48 +> dream 如果直接消费它,等于把这个错误放大到跨公司。
  49 +
  50 +### 1.2 长期记忆是一个 3000 字符的滚动窗口
  51 +
  52 +`MAX_MEMORY_MD_SIZE = 3000`。超了就截。所以「长期记忆」实际是**最近若干条截断日志**,
  53 +没有任何按重要性保留的机制。
  54 +
  55 +### 1.3 「公司态目录」里有不是公司的东西
  56 +
  57 +`system/agents/{aid}/{cid}/` 是约定,不是校验。实查 96 个二级目录里混着:
  58 +
  59 +| 形态 | 例子 | 是什么 |
  60 +|---|---|---|
  61 +| 真公司 | `e2e-demo` / `mycompany` / `idearun` | ✅ dream 的原料 |
  62 +| 保留段 | `_global` | 全局 agent 的落点,算不算「一家公司」需要定 ❓ |
  63 +| ArenaSK 时代的运行态 | `train/` `workspace/` | ❌ 不是公司,是训练 / 工作目录 |
  64 +| **测试残留** | `tmp-w7-bootstrap-1786986291739-9n2xv`、`qroute-co-13/14`、`zz-*` | ❌ 夹具,且**带时间戳**,会无限增长 |
  65 +
  66 +**dream 的第一步「枚举所有公司」现在没有可靠答案。**
  67 +可靠的答案在库里:`company_agents` 表(28 批次 4 起「这家公司雇了谁」的唯一真相源)。
  68 +目录只能用来定位文件,不能用来枚举公司。
  69 +
  70 +### 1.4 绝大多数 memory 是空模板
  71 +
  72 +75 份 `memory.md` 里,**只有 7 份**有非模板内容(`innovator-evolver` / `master` /
  73 +`org-innovator` / `innovator-digest` / `innovator-hunt` / `coo` / `qa-judge`),
  74 +其余全是 `_(刚入职,尚无记忆)_`。
  75 +
  76 +> 这条对设计有直接影响:**dream 现在跑起来几乎没有原料**。
  77 +> 它的价值随「真跑的量」增长,不是上线即有收益的东西。
  78 +
  79 +---
  80 +
  81 +## 二、已经生效的约束(不随设计变化)
  82 +
  83 +✅ **公司态目录是 dream 的唯一原料,不许被任何清理策略当垃圾扫掉**(§5.6b)。
  84 +任务态(`{aid}/{cid}/{taskId}/`)可清,公司态(`{aid}/{cid}/memory/` 与 `conversations/`)不可清。
  85 +
  86 +✅ **沉淀必须是提炼后的抽象,不是自动搬运原文。**
  87 +错误版本真的发生过(28 号第一轮验收挖出的最严重 blocker):直接灌原始对话
  88 += 把 A 公司的机密喂给 B 公司。
  89 +
  90 +✅ **目录布局本身就是 dream 的主查询索引**:
  91 +`某个 agent 在所有公司的全部记忆 == glob: system/agents/{aid}/*/memory/`,一个 glob 就够。
  92 +(前提是 1.3 那个「二级目录不都是公司」的问题被解决。)
  93 +
  94 +---
  95 +
  96 +## 三、需要定的取舍
  97 +
  98 +### 🔒 决策 A —— 去敏的判据:**独立判定 agent + 写作要求**(2026-08-20 拍板)
  99 +
  100 +**定案 = A3 + A1 组合**:提炼 agent 按 A1 的写作要求产出,产出后由**另一个只负责判
  101 +「这段话能不能给别家公司看」的 agent** 过闸门,判不准就拒绝沉淀。
  102 +
  103 +理由(也是本仓库反复验证过的一条):**两种错的代价不对称时,判据要偏向贵的那一侧**
  104 +(P3B §八 关于 `FAILING_VERDICTS` 的那段)。「少沉淀一条经验」与「泄漏一次机密」显然不对称。
  105 +失效方向因此是「少沉淀」,不是「泄漏」。
  106 +
  107 +下面三种形态的对比留档,说明为什么选它。
  108 +
  109 +### 决策 A 的备选(留档)
  110 +
  111 +这是**安全边界**,也是整个设计里唯一「做错了会造成真实损害」的一条。三种可选形态:
  112 +
  113 +| 方案 | 判据 | 代价 |
  114 +|---|---|---|
  115 +| **A1 白名单式**:只允许提炼成「不含任何专有名词的方法论」 | 产出里不许出现公司 id、人名、产品名、URL、文件路径 | 最安全;但也最容易把有用的经验一起滤掉(「给 X 公司做官网时发现静态站点要先定验收脚本」→ 只剩「要先定验收脚本」) |
  116 +| **A2 黑名单式**:过滤已知敏感模式后放行 | 正则 / LLM 扫一遍公司 id、密钥形态、路径 | 保留信息量更多;**漏一个模式就是一次泄漏**,而且是静默的 |
  117 +| **A3 二次判定**:提炼 agent 产出后,由**另一个** agent 只负责判「这段话能不能给别家公司看」 | 判定是一次独立的 agent 调用,双方不共享上下文 | 成本翻倍;但与本仓库既有的 validator / triager 模式同构,且**失效方向可控**(判不准就拒绝沉淀,宁可少沉淀) |
  118 +
  119 +### 🔒 决策 B —— 沉淀落点:**四个都要,按「改动的可逆性」分层**(2026-08-20 拍板)
  120 +
  121 +用户定的是:`system/agents/{aid}/memory/` **和** `SKILL.md` 都要写,
  122 +**必要时还要迭代 `soul.md` / `agent.md`**。
  123 +
  124 +这比原来三选一的范围大得多,**因为它引入了「agent 改写自己的定义」这件事**。
  125 +下面这一节是这个决定的直接后果,不是可选的补充说明。
  126 +
  127 +#### B.1 四个落点不是一回事,风险差三个数量级
  128 +
  129 +| 落点 | 是什么 | 谁受影响 | 可逆性 |
  130 +|---|---|---|---|
  131 +| `{aid}/memory/`(本体根) | **经验积累**,跨公司那一份 | 只影响这个 agent 读到的上下文 | 删掉即可,无副作用 |
  132 +| `SKILL.md` | **能力定义**:这个 agent 会什么 | 同上,但会进 Skill 市场的语义 | 覆盖前留旧版即可回滚 |
  133 +| `soul.md` | **人格**:它怎么说话、怎么取舍 | 所有雇了这个本体的公司 | 改坏了表现为「行为漂了」,**不报错** |
  134 +| `agent.md` | **行为契约**:它必须做什么、禁止做什么 | 所有雇了这个本体的公司 | 改坏了可能让整条链路静默失效 |
  135 +
  136 +**28 号的核心裁决是「本体只有一份」** —— 所以写 `agent.md` / `soul.md` 的影响面是
  137 +**所有雇了它的公司**,而不是提炼出这条经验的那一家。这恰恰是 §5.6b 担心的那种扩散,
  138 +只不过现在是**有意为之**。因此它必须比前两个落点多两道东西:
  139 +
  140 +#### B.2 写定义文件的硬约束(这几条不是建议)
  141 +
  142 +1. ✅ **`agent.md` 有一处 2000 字符的截断,但作用点要说准**:
  143 + `graph-engine.js:915` 给**规划 agent** 看的「角色定义摘录」只取
  144 + `agent.md` 的**前 2000 字符**(`readFileSync(f).slice(0, 2000)`),
  145 + 超出部分静默丢弃。agent 自己那条装配链(`prompt-builder`)**不截断**,读全文。
  146 + → 后果是:dream 往 `agent.md` **末尾追加**内容,规划侧读到的画像不会变;
  147 + 但如果追加把重要约束**顶到 2000 之后**(例如重写时调整了顺序),规划侧会无声地少看到那一段。
  148 + → 判据:写 `agent.md` 时**保证前 2000 字符仍然自足**(职能说清楚),
  149 + 新增内容往后放;改动后应当有一条检查盯住这个窗口。
  150 +2. ✅ **这四个文件里有三个是 git 跟踪的定义**(`agent.md` / `soul.md` / `SKILL.md`),
  151 + 不是运行态。dream 改它们 = 改仓库里的文件 → 会被边界守卫看见,也会进 `git status`。
  152 + → 需要明确:dream 的产出是**直接落盘**还是**提案 + 人工合并**。
  153 +3. ✅ **毕业闸门读这些文件**(`missingBodyDefinition` / `promoteDraftIfComplete`)。
  154 + 把 `agent.md` 写空 / 写坏会让本体退回「定义没填满」,**雇不进任何公司**。
  155 +4. ✅ **必须可回滚且能分别回滚**:经验沉淀写坏了只影响上下文,`agent.md` 写坏了影响所有公司。
  156 + 两者混在同一次提交里,回滚粒度就没了。
  157 +
  158 +> **建议(仍需你定)**:`memory/` 与 `SKILL.md` 由 dream 直接写;
  159 +> `soul.md` / `agent.md` 只产出**提案**(写到 `{aid}/memory/proposals/` 下),由人过一眼再合并。
  160 +> 理由是上面第 1、3 条:这两个文件写坏的失效方向都是**静默**的,而 dream 的产出目前
  161 +> 没有任何质量评价手段(决策 D 未定)。
  162 +
  163 +### 决策 B 的备选(留档)
  164 +
  165 +「skill memory」目前**没有对应的实体**。候选落点:
  166 +
  167 +| 落点 | 形态 | 问题 |
  168 +|---|---|---|
  169 +| **B1** `system/agents/{aid}/memory/`(本体根,跨公司那一份) | 与公司态并列,一个 agent 一份跨公司经验 | 目前只有 `zz-scaffold-readonly-fresh` 有这个目录,等于新建一层;**但它天然与 §5.6b 的 glob 对齐**,读的时候本体根 + 各公司态一起加载 |
  170 +| **B2** 本体的 `SKILL.md` | 复用现有概念(5 个本体已有 SKILL.md) | **这正是 §5.6b 点名的错误版本的落点**;SKILL.md 是「能力定义」,不是「经验积累」,混在一起之后没法分别回滚 |
  171 +| **B3** `<repo>/../data/skills/` 那 27,059 条 Skill 仓库 | 与 ArenaSK 的 Skill 市场打通 | 那是**面向分发**的仓库,写进去等于对外发布;且跨了两个 data 根 |
  172 +
  173 +(拍板结果见上:B1 与 B2 都要,另加 `soul.md` / `agent.md` 两个定义落点。)
  174 +
  175 +### 🔒 决策 C —— 触发时机:**定时,每日一次**(2026-08-20 拍板)
  176 +
  177 +备选留档如下。
  178 +
  179 +> ⚠️ **与 §1.4 的冲突要知道**:实查 75 份 `memory.md` 里只有 7 份有非模板内容,
  180 +> 所以每日跑起来**绝大多数 agent 会是空转**。这不是反对定时,是要求实现时做到:
  181 +> **没有新原料就不起 agent 调用**(判据:上次 dream 之后有没有新的 session 摘要),
  182 +> 否则每天会有几十次「读完发现没东西」的 LLM 调用在烧钱,而且日志里全是噪声。
  183 +
  184 +| 方案 | 说明 |
  185 +|---|---|
  186 +| **C1 定时**(如每日一次) | 简单;但没有真跑的日子会空转,有大量真跑的日子又攒得太多 |
  187 +| **C2 按量**(某 agent 新增 N 条 session 摘要后触发) | 与实际产出量对齐;需要一个计数落点 |
  188 +| **C3 手动**(控制台一个按钮 + API) | 先手动跑一段时间看提炼质量,稳定了再自动 |
  189 +
  190 +### ❓ 决策 D —— 提炼质量怎么评 / 失败怎么办
  191 +
  192 +这一条**可以先不定**,但要知道它没定:
  193 +
  194 +- 没有评价手段时,dream 的产出**无法验证对错**——它和 FDE 那边
  195 + 「LLM 自己汇报分数」的问题是同一个(见 [architecture_todo_list](../future/architecture_todo_list.md) §1)。
  196 +- 一个便宜的起步:dream 每次产出都**保留原料指针**(哪几条 session 提炼出这一条),
  197 + 人抽查时能顺着回去看。**可追溯**比「可评分」便宜得多,也更早有用。
  198 +
  199 +---
  200 +
  201 +## 四、推进顺序 🔒 **上游与 dream 一起做**(2026-08-20 拍板)
  202 +
  203 +用户定的是两件一起做,不分先后。拆成可验收的步骤:
  204 +
  205 +| 步 | 内容 | 为什么不能省 |
  206 +|---|---|---|
  207 +| 1 | **`appendToMemoryMd` 停止截断搬运**(§1.1) | 它现在把工具调用 id 当「关键事实」写进 `memory.md` 并注入**每一轮**提示词。dream 直接消费它 = 把这个错误放大到跨公司 |
  208 +| 2 | **「有哪些公司」改查 `company_agents` 表**(§1.3) | 目录里混着 `train/` `workspace/` 和带时间戳的测试残留,拿目录枚举公司必然把它们当原料 |
  209 +| 3 | **dream v0**:枚举 → 提炼 agent → 去敏判定 agent(决策 A)→ 写 `memory/` + `SKILL.md` | 主体 |
  210 +| 4 | **定义文件走提案**(决策 B.2 建议):`soul.md` / `agent.md` 只产出提案 | 两者写坏的失效方向都是静默的,而质量评价(决策 D)还没定 |
  211 +| 5 | **可追溯**:每条沉淀记住它是从哪几条 session 提炼来的(决策 D 的便宜起步) | 没有它,dream 的产出无法验证对错 —— 与 FDE 那边「LLM 自己汇报分数」是同一个问题 |
  212 +
  213 +**空转保护**(决策 C 的直接要求):没有新原料就不起 agent 调用。
  214 +
  215 +> 本文档仍**不是实现规格**。决策 D(质量怎么评)未定;B.2 里「定义文件直接写还是走提案」
  216 +> 也还需要你点头 —— 我按「走提案」写在了步骤 4,但那是我的建议,不是你拍的板。