diff --git a/docs/todo/system-design/29-dream-design.md b/docs/todo/system-design/29-dream-design.md index 2ae8629..d60bdae 100644 --- a/docs/todo/system-design/29-dream-design.md +++ b/docs/todo/system-design/29-dream-design.md @@ -210,8 +210,8 @@ const entry = `- [${timestamp}] ${taskOneLine} → ${resultOneLine}`; ## 四、推进顺序 🔒 **上游与 dream 一起做**(2026-08-20 拍板) -> **进度**:步骤 1、2、4、5 已实现;步骤 3 只落了**骨架 + 注入缝**, -> 两次 agent 调用(提炼 / 去敏判定)的**默认实现尚未接线** —— 见 §5.3。 +> **进度**:步骤 1、2、3、4、5 全部实现。**未做的是:定时挂载(决策 C)、 +> 写定义文件(`SKILL.md` / `soul.md` / `agent.md`)、以及一次真 CLI 验收** —— 见 §5.4。 用户定的是两件一起做,不分先后。拆成可验收的步骤: @@ -219,7 +219,7 @@ const entry = `- [${timestamp}] ${taskOneLine} → ${resultOneLine}`; |---|---|---| | 1 | ✅ **`appendToMemoryMd` 停止截断搬运**(§1.1)—— 已完成 | 它原来把工具调用 id 当「关键事实」写进 `memory.md` 并注入**每一轮**提示词。dream 直接消费它 = 把这个错误放大到跨公司 | | 2 | ✅ **「有哪些公司」改查 `company_agents` 表**(§1.3)—— 已完成 | 目录里混着 `train/` `workspace/` 和带时间戳的测试残留,拿目录枚举公司必然把它们当原料 | -| 3 | 🟡 **dream v0**:枚举 → 提炼 agent → 去敏判定 agent(决策 A)→ 写落点 —— **骨架已落,两次 agent 调用未接线** | 主体 | +| 3 | ✅ **dream v0**:枚举 → 提炼 agent → 去敏判定 agent(决策 A)→ 写落点 —— 已完成 | 主体 | | 4 | ✅ **定义文件的 git 历史闸门**(决策 B.3)—— 已完成 | 覆盖一份尚未进入 git 历史的内容 = 让上一版永久消失,且不报错 | | 5 | ✅ **可追溯**:每条沉淀记住它是从哪几条记录提炼来的 —— 已完成 | 没有它,dream 的产出无法验证对错 —— 与 FDE 那边「LLM 自己汇报分数」是同一个问题 | @@ -273,15 +273,46 @@ mtime 会被任何一次无关写入推动(例如别处重写整份 `memory.md **决策 A 的失效方向在代码里是硬的**:判定 agent **抛异常也算拒绝**, 并把「为什么」写进 `rejected[]` —— 否则现场只看到「少了一条」,查不出是判否还是判炸。 -### 5.3 步骤 3 还差什么(别当成做完了) +### 5.3 步骤 3:两个本体 + 默认实现(已完成) -`setDistiller` / `setScreener` 两个注入缝已经在,`dreamOnce` 的编排、空转保护、 -去敏闸门、落盘、可追溯都跑通了(6 例守卫)。**缺的是两次真 agent 调用的默认实现**: +**两个本体**(都过得了毕业闸门,`missingBodyDefinition` 为空): -- 提炼 agent 与去敏判定 agent 的**本体还不存在**(需要两份 `agent.md` + `soul.md`); -- 默认实现现在是一句显式的 throw(「尚未接线」),**不是静默返回空** —— - 静默返回空会让调用方以为「跑了但没提炼出东西」,那是本仓库反复出现的失效形态。 - -**还没写定义文件**:`writeDistilled` 目前只写本体根的 `memory/experience.md` -(纯经验积累,删掉无副作用)。`SKILL.md` / `soul.md` / `agent.md` 三个 git 跟踪的定义 -留给下一步,`definitionWritability()` 已经能报出「这三份现在能不能写、不能写是为什么」。 +| 本体 | 职责 | 产出契约 | +|---|---|---| +| `dream-distiller` | 把一批原始记录提炼成可复用经验 | ```json 数组,每条 `{title, body, sources}`;没有值得留的就交 `[]` | +| `dream-screener` | 逐条判「这条能不能给别家公司看」 | ```json 对象 `{shareable: boolean, reason}` | + +**决策 A 的独立性写在两处,缺一不可**: +- 代码层:判定**一条一次调用**,提示词里只放 `title` + `body`,**不放原料**; +- 本体层:`dream-screener/agent.md` 明写「你看不到原料,也看不到是哪家公司——这是故意的」。 + 守卫里有一条专门断言这句话在(`expect(screener).toContain('看不到原料')`)—— + 否则下一个人会顺手把原料塞进提示词「让它判得更准」,而那恰好把独立性拆掉了。 + +**两处失效方向都是硬的**: +- 提炼取不出 JSON → **抛**,不是静默返回空。「产出格式坏了」和「没提炼出东西」是两件事, + 混为一谈会让前者以后者的形式永远藏下去(每天显示「今天没经验可沉淀」,真相是解析一直在失败)。 +- 判定解析不了 / `shareable` 不是布尔 → **判否**,并把原文片段带进 `reason`, + 让人看得出是「判否」还是「判炸」。 + +> **测试写法上纠了自己一次**:第一版把解析与失效分支在测试里**重抄了一遍**再断言 —— +> 那是在验一份复制品,代码改了它照样绿。改成加一条 `setDreamAgentCaller` 缝, +> 只换掉「真起 CLI」那一层,解析、判空、失效方向全走真代码。 + +> **本体写错过一次,被守卫抓住**:`agent.md` 里的相对引用按 **cwd 深度 3** +> (`{aid}/{cid}/{taskId}/`)解析,不是按文件自身位置 —— 仓库根是 `../../../../../`。 +> 我按文件位置写了 `../../../`,`agent-md-refs.test.js` 当场判红。 + +### 5.4 还没做的(别当成 dream 上线了) + +1. **定时挂载没做**(决策 C 说每日一次)。现在只有 `dreamOnce(agentId)` 这个可调用的单元, + 没有挂到任何调度上,也没有 REST / 控制台入口。空转保护已经在,挂之前先想清楚 + 「每天对哪些本体跑」——全量 76 个本体逐个跑,即使空转也要 76 次目录读。 +2. **还没写定义文件**。`writeDistilled` 目前只写本体根的 `memory/experience.md` + (纯经验积累,删掉无副作用)。`SKILL.md` / `soul.md` / `agent.md` 三个 git 跟踪的定义 + 留给下一步;`definitionWritability()` 已经能报出「这三份现在能不能写、不能写是为什么」, + 闸门(§B.3)也已就位,缺的是「往里写什么、写成什么形状」这半边设计。 +3. **没有一次真 CLI 验收**。CI 里换掉的是 `callDreamAgent` 那一层, + 验的是「回复 → 结构化产物」的解析与失效方向,**不是模型写得好不好**。 + 两个本体的提示词至今**没有被任何模型读过** —— 这正是 P3B W4 记过的形态 + (mock 从不读提示词,所以提示词在第一次真跑前从未被检验)。 +4. **决策 D(提炼质量怎么评)仍未定**。可追溯(`sources`)是它的便宜起步,不是替代品。 diff --git a/server/services/dream-service.js b/server/services/dream-service.js index fd10189..f66e991 100644 --- a/server/services/dream-service.js +++ b/server/services/dream-service.js @@ -195,16 +195,134 @@ function definitionWritability(agentId) { })); } -async function defaultDistiller() { - throw new Error('[Dream] 提炼实现尚未接线(29-DREAM §四 步骤 3):请注入 setDistiller 或等待默认实现落地'); +// ── 默认实现:两次**互相不共享上下文**的真 agent 调用(决策 A)────────── +const DISTILLER_AGENT = 'dream-distiller'; +const SCREENER_AGENT = 'dream-screener'; + +/** + * 从 agent 回复里取 JSON。 + * + * 写法与 `graph-engine.extractPlannedGraph` 同源:```json 围栏块优先, + * 其次从第一个 `{`/`[` 起做括号配平。**取不出来一律返回 null,不猜** —— + * 猜错的代价是把一段自然语言当成结构化产物落库。 + */ +function extractJson(text, expect) { + const src = String(text || ''); + const tryParse = (raw) => { + try { + const v = JSON.parse(raw); + if (expect === 'array' && !Array.isArray(v)) return null; + if (expect === 'object' && (Array.isArray(v) || typeof v !== 'object' || v === null)) return null; + return v; + } catch { return null; } + }; + for (const m of src.matchAll(/```(?:json|jsonc)?\s*([\s\S]*?)```/g)) { + const v = tryParse(m[1]); + if (v !== null) return v; + } + const open = expect === 'array' ? '[' : '{'; + const close = expect === 'array' ? ']' : '}'; + let depth = 0; let start = -1; + for (let i = 0; i < src.length; i++) { + if (src[i] === open) { if (depth === 0) start = i; depth++; } + else if (src[i] === close) { + depth--; + if (depth === 0 && start >= 0) { + const v = tryParse(src.slice(start, i + 1)); + if (v !== null) return v; + start = -1; + } + } + } + return null; +} + +/** + * 起一次 agent 调用。dream 的两个 agent 都是全局的,用裸 id(落 `_global`)。 + * + * 单独抽出来并留一个替换点,是为了让**默认实现本身**能被测到: + * 不留的话,测试只能在自己那边重抄一遍解析与失效分支 —— 那是在验一份复制品, + * 代码改了它照样绿。这条缝换掉的只有「真起 CLI」那一层,解析、判空、失效方向全走真代码。 + */ +let agentCaller = null; +/** 替换 dream 的 agent 调用层(传 null 回真实现)。只给测试用。 */ +function setDreamAgentCaller(fn) { agentCaller = fn; } + +async function callDreamAgent(agentName, prompt) { + if (agentCaller) return agentCaller(agentName, prompt); + return realCallDreamAgent(agentName, prompt); +} + +async function realCallDreamAgent(agentName, prompt) { + const AgentManager = require('../src/core/agent-manager'); + const manager = new AgentManager({}, path.resolve(__dirname, '../..')); + const res = await manager.callAgent(agentName, prompt, { + metadata: { layer: 'dream', agent: agentName }, + }); + return (res && res.output) || ''; } -async function defaultScreener() { - throw new Error('[Dream] 去敏判定实现尚未接线(29-DREAM §四 步骤 3):请注入 setScreener 或等待默认实现落地'); + +/** + * 提炼:一次调用吃掉这一轮的**全部**新原料。 + * + * 为什么不逐条提炼:一条经验常常要**跨多条记录**才看得出来 + * (「三次都是因为验收标准自证才返工」)。逐条喂等于把这种归纳能力关掉。 + */ +async function defaultDistiller({ agentId, entries }) { + const lines = entries.map((e) => `[出处 ${e.source}] ${e.text}`).join('\n'); + const prompt = [ + `下面是本体 \`${agentId}\` 在各家公司留下的原始记录,共 ${entries.length} 条。`, + '按你 agent.md 里的产出契约提炼成可复用经验,交一个 ```json 数组。', + '没有值得留下的就交 []。', + '', + lines, + ].join('\n'); + + const out = await callDreamAgent(DISTILLER_AGENT, prompt); + const arr = extractJson(out, 'array'); + if (arr === null) { + // **不静默返回空**:解析不出来和「没提炼出东西」是两件事,混为一谈会让 + // 「产出格式坏了」这种故障以「今天没经验可沉淀」的形式永远藏下去。 + throw new Error(`[Dream] ${DISTILLER_AGENT} 的回复里取不出 JSON 数组(前 200 字:${out.slice(0, 200)})`); + } + // 形状不对的条目直接丢,但**必须留下痕迹** —— 无声丢弃会让人以为 agent 没产出 + return arr.filter((c) => { + const ok = c && typeof c.title === 'string' && typeof c.body === 'string'; + if (!ok) console.warn(`[Dream] ⚠️ 丢弃形状不对的提炼产物: ${JSON.stringify(c).slice(0, 120)}`); + return ok; + }); +} + +/** + * 去敏判定:**一条一次调用**,且只喂 title + body。 + * + * 逐条、且不给原料,是决策 A 的核心 —— 判定方的处境必须与 + * 「一个完全不相干的第三方读到这段文字」完全一样。 + * 把整批一起喂过去,它会从别的条目里获得上下文,那就不是独立判定了。 + */ +async function defaultScreener({ candidate }) { + const prompt = [ + '判断下面这一条提炼产物能不能给别家公司看。按你 agent.md 的契约交一个 ```json 对象。', + '', + `title: ${candidate.title}`, + `body: ${candidate.body}`, + ].join('\n'); + + const out = await callDreamAgent(SCREENER_AGENT, prompt); + const obj = extractJson(out, 'object'); + if (obj === null || typeof obj.shareable !== 'boolean') { + // 判不出来 = 不放行(fail-closed)。这里**返回**而不是抛: + // 抛也会被 dreamOnce 记成拒绝,但返回能把「为什么」原样带进 rejected[]。 + return { shareable: false, reason: `判定回复无法解析成 {shareable:boolean}(前 200 字:${out.slice(0, 200)})` }; + } + return { shareable: obj.shareable === true, reason: obj.reason || '' }; } module.exports = { dreamOnce, collectMaterial, companiesOf, readRawEntries, + defaultDistiller, defaultScreener, extractJson, + DISTILLER_AGENT, SCREENER_AGENT, writeDistilled, definitionWritability, readState, writeState, - setDistiller, setScreener, + setDistiller, setScreener, setDreamAgentCaller, RAW_SECTION, CROSS_COMPANY_MEMORY, }; diff --git a/server/tests/unit/dream-service.test.js b/server/tests/unit/dream-service.test.js index a8cc825..67670ee 100644 --- a/server/tests/unit/dream-service.test.js +++ b/server/tests/unit/dream-service.test.js @@ -153,9 +153,104 @@ describe('dream · 骨架', () => { expect(seenSources).toEqual(expect.arrayContaining([`${CO_A}:memory.md#1`, `${CO_B}:memory.md#1`])); }); - it('默认实现没接线时抛得明白(别让调用方以为「跑了但没产出」)', async () => { + it('不注入任何东西时走的是默认实现(两次真 agent 调用,这里只换掉 CLI 那一层)', async () => { hire(CO_A); - seedMemory(CO_A, ['一条']); - await expect(dream.dreamOnce(AGENT)).rejects.toThrow(/提炼实现尚未接线/); + seedMemory(CO_A, ['素材一']); + const calls = []; + dream.setDreamAgentCaller(async (agentName, prompt) => { + calls.push({ agentName, prompt }); + if (agentName === dream.DISTILLER_AGENT) { + return '好的。\n```json\n[{"title":"结论一","body":"做法一","sources":["x"]}]\n```'; + } + return '```json\n{"shareable":true,"reason":"没有可识别主体"}\n```'; + }); + try { + const r = await dream.dreamOnce(AGENT); + expect(r.accepted).toBe(1); + // 两个 agent 各被调到,且**判定那次的提示词里不许出现原料**(决策 A 的核心) + expect(calls.map((c) => c.agentName)).toEqual([dream.DISTILLER_AGENT, dream.SCREENER_AGENT]); + expect(calls[0].prompt).toContain('素材一'); + expect(calls[1].prompt).not.toContain('素材一'); + expect(calls[1].prompt).toContain('结论一'); + } finally { + dream.setDreamAgentCaller(null); + } + }); +}); + +/** + * 默认实现(真 agent 调用那一侧)—— 29-DREAM §四 步骤 3。 + * + * 这一组**不起真 CLI**:验的是「回复 → 结构化产物」这段解析与失效方向, + * 而不是模型写得好不好。模型那半归真跑,进不了 CI。 + */ +describe('dream · 默认实现的解析与失效方向', () => { + const d = require('../../services/dream-service'); + + it('extractJson:围栏块优先,其次括号配平;取不出来返回 null(不猜)', () => { + expect(d.extractJson('废话\n```json\n[{"a":1}]\n```\n后记', 'array')).toEqual([{ a: 1 }]); + expect(d.extractJson('前言 [{"a":2}] 后语', 'array')).toEqual([{ a: 2 }]); + expect(d.extractJson('```json\n{"shareable":true}\n```', 'object')).toEqual({ shareable: true }); + // 类型不符不将就:要数组给了对象 → null + expect(d.extractJson('```json\n{"a":1}\n```', 'array')).toBeNull(); + // 通篇没有 JSON → null,而不是硬凑一个 + expect(d.extractJson('我觉得这批记录没什么可提炼的。', 'array')).toBeNull(); + }); + + it('提炼取不出 JSON → **抛**,不是静默返回空(真调 defaultDistiller)', async () => { + // 「产出格式坏了」和「没提炼出东西」是两件事。混为一谈会让前者以后者的形式 + // 永远藏下去 —— 每天都显示「今天没经验可沉淀」,而真相是解析一直在失败。 + d.setDreamAgentCaller(async () => '我觉得这批记录没什么可提炼的。'); + try { + await expect(d.defaultDistiller({ agentId: 'x', entries: [{ source: 's', text: 't' }] })) + .rejects.toThrow(/取不出 JSON 数组/); + } finally { d.setDreamAgentCaller(null); } + }); + + it('提炼产物形状不对的那几条被丢掉,合规的留下(真调 defaultDistiller)', async () => { + d.setDreamAgentCaller(async () => '```json\n[{"title":"好的","body":"正文"},{"body":"缺 title"},"字符串"]\n```'); + try { + const out = await d.defaultDistiller({ agentId: 'x', entries: [{ source: 's', text: 't' }] }); + expect(out).toHaveLength(1); + expect(out[0].title).toBe('好的'); + } finally { d.setDreamAgentCaller(null); } + }); + + it('判定回复解析不了 → shareable:false,且 reason 带上原文片段(真调 defaultScreener)', async () => { + // fail-closed:判不准就不放。reason 必须能让人看出「是判否还是判炸」。 + d.setDreamAgentCaller(async () => '我觉得应该可以吧'); + try { + const v = await d.defaultScreener({ candidate: { title: 't', body: 'b' } }); + expect(v.shareable).toBe(false); + expect(v.reason).toContain('我觉得应该可以吧'); + } finally { d.setDreamAgentCaller(null); } + }); + + it('判定回复里 shareable 不是布尔(比如 "true" 字符串)也判否,不将就', async () => { + d.setDreamAgentCaller(async () => '```json\n{"shareable":"true"}\n```'); + try { + expect((await d.defaultScreener({ candidate: { title: 't', body: 'b' } })).shareable).toBe(false); + } finally { d.setDreamAgentCaller(null); } + }); + + it('两个本体真实存在且过得了毕业闸门(否则默认实现一跑就 Agent not found)', () => { + const locator = require('../../services/agent-locator'); + for (const id of [d.DISTILLER_AGENT, d.SCREENER_AGENT]) { + expect(fs.existsSync(path.join(ROOT, 'system', 'agents', id, 'agent.md'))).toBe(true); + expect(locator.missingBodyDefinition(id)).toEqual([]); + } + }); + + it('两个本体的 agent.md 里写着各自的产出契约,且与代码读的键名逐字一致', () => { + // 这一条守的是「产生端是 agent 时,『谁产生它』的答案是一份提示词」—— + // 27-P3/P4 §十 硬规矩 1 的第 13 处就栽在这里:引擎四环全通, + // 而产出方的 agent.md 里根本没有那个键。 + const read = (id) => fs.readFileSync(path.join(ROOT, 'system', 'agents', id, 'agent.md'), 'utf-8'); + const distiller = read(d.DISTILLER_AGENT); + for (const key of ['title', 'body', 'sources']) expect(distiller).toContain(key); + const screener = read(d.SCREENER_AGENT); + for (const key of ['shareable', 'reason']) expect(screener).toContain(key); + // 判定方**不许**看到原料:本体里要明写这一点,否则下一个人会顺手把原料塞进提示词 + expect(screener).toContain('看不到原料'); }); }); diff --git a/system/agents/dream-distiller/agent.md b/system/agents/dream-distiller/agent.md new file mode 100644 index 0000000..ffc0001 --- /dev/null +++ b/system/agents/dream-distiller/agent.md @@ -0,0 +1,61 @@ +# dream-distiller(dream 提炼 agent) + +## 你是谁 + +你是 **dream** 这套「经验汇流」机制里的**提炼**一环,本体名 `dream-distiller`。 +设计见 [29-DREAM](../../../../../docs/todo/system-design/29-dream-design.md)。 + +dream 每天跑一次,把**某一个 agent 在所有公司**的原始记录整理成 +**不含任何公司特定信息**的可复用经验,沉淀到那个 agent 的本体上。 +这样它在 A 公司学到的做法,下次在 B 公司也用得上——**而 B 公司永远不会知道 A 公司做过什么**。 + +## 你会收到什么 + +一批原始记录,每条形如: + +``` +[出处 co-alpha:memory.md#3] 为 X 重写落地页 → 按 A-1..A-14 逐条对照后通过 +``` + +出处是给**人**回查用的,你要把它原样带回产出里(见下)。 + +## 你要产出什么 + +**一个 JSON 数组**,放在一个 ```json 围栏块里。每个元素三个键: + +```json +[ + { + "title": "验收脚本必须由验收方自己写", + "body": "把判据外包给被验收方自带的自检脚本,等于让它给自己打分。做法:验收侧独立重实现一遍断言,只认自己那份脚本的退出码。", + "sources": ["co-alpha:memory.md#3", "co-alpha:memory.md#7"] + } +] +``` + +- `title` — 一句话,是**结论**不是话题(写「验收脚本必须由验收方自己写」, + 不写「关于验收脚本」)。 +- `body` — 现象 + 做法。**能指导下一次行动**才算数。 +- `sources` — 这条经验是从**哪几条**原始记录来的,原样抄出处字符串。 + 这是唯一能让人回查「你这条是不是编的」的东西,**不许省,不许编**。 + +**没有值得留下的就交 `[]`。** 空数组是合法产出,不是失败。 + +## 硬规则(违反其一,整条会被下游的判定 agent 拒掉) + +1. **不许出现公司特定信息**:公司名 / 公司 id、产品名、人名、 + 具体文件路径、URL、域名、密钥形态的字符串。 + 原料里有,你的产出里也不能有——把它抽象掉(「某公司」→ 直接不提; + `/Users/x/proj/index.html` → 「入口 HTML」)。 +2. **不许搬运原文**。你的产出必须是**重写**过的。 + 逐字复制原始记录的一段是 dream 明确要防的错误形态: + 直接灌原始对话等于把 A 公司的机密喂给 B 公司。 +3. **不许写你没有证据的东西**。原料里没出现过的,就不要写进去。 + 你的印象、你的常识、你觉得「一般来说」——都不算证据。 +4. **一条经验只讲一件事**。两件事就拆两条。 + +## 失败时怎么办 + +原料读不懂、格式不对、全是噪声——**交 `[]` 并在围栏块外用一句话说明**。 +不要为了交差编一条出来:下游没有任何手段验证你说得对不对, +编出来的经验会被当成真的沉淀下去,然后被后面每一轮对话读到。 diff --git a/system/agents/dream-distiller/config.json b/system/agents/dream-distiller/config.json new file mode 100644 index 0000000..8a86cd2 --- /dev/null +++ b/system/agents/dream-distiller/config.json @@ -0,0 +1,9 @@ +{ + "type": "meta-planning", + "description": "dream 提炼 — 把一个 agent 在各家公司的原始记录提炼成不含公司特定信息的可复用经验(29-DREAM 决策 A 的产出侧)", + "version": "1.0.0", + "status": "active", + "heartbeat": { + "enabled": false + } +} diff --git a/system/agents/dream-distiller/soul.md b/system/agents/dream-distiller/soul.md new file mode 100644 index 0000000..5ac8098 --- /dev/null +++ b/system/agents/dream-distiller/soul.md @@ -0,0 +1,15 @@ +# 人格 · dream-distiller + +你是一个**克制的**提炼者。 + +你面对的是一堆流水账:某个 agent 在若干家公司里干过的事,一行一条, +大部分没有价值,少数几条藏着一条真正可复用的经验。 + +你的本能应该是**少写**。写十条似是而非的「经验」,不如写一条真的能指导下次行动的。 +读你产出的人(和 agent)没有办法验证你说得对不对——所以你说的每一句都要**自己扛得住**: +它必须是从具体事实里推出来的,而不是从你对这个领域的一般印象里想出来的。 + +你**不认识**这些公司,也不该表现得认识。原料里出现的公司名、产品名、人名、 +文件路径、URL,对你来说都是**噪声**,不是信息。你要提炼的是它们背后的做法。 + +如果这一批原料里没有值得留下的东西,就诚实地交一个空数组。那不是失败。 diff --git a/system/agents/dream-screener/agent.md b/system/agents/dream-screener/agent.md new file mode 100644 index 0000000..e71de5f --- /dev/null +++ b/system/agents/dream-screener/agent.md @@ -0,0 +1,53 @@ +# dream-screener(dream 去敏判定 agent) + +## 你是谁 + +你是 **dream** 这套「经验汇流」机制里的**闸门**,本体名 `dream-screener`。 +设计见 [29-DREAM](../../../../../docs/todo/system-design/29-dream-design.md) 决策 A。 + +`dream-distiller` 把某个 agent 在各家公司的原始记录提炼成一条条「经验」, +你逐条判断:**这条能不能给别家公司看。** + +## 你看得到什么、看不到什么 + +你只会收到**一条**提炼产物(title + body)。 + +你**看不到原料,也看不到是哪家公司**——这是故意的。 +你的处境要和「一个完全不相干的第三方读到这段文字」完全一样: +他手里也只有这段文字。你能从中看出来的东西,他也能。 + +## 你要产出什么 + +**一个 JSON 对象**,放在一个 ```json 围栏块里: + +```json +{ "shareable": false, "reason": "body 里出现了具体域名 example.com,第三方能据此认出是哪家" } +``` + +- `shareable` — `true` / `false`,**只有这两个值**。 +- `reason` — 判否时必须说清**是哪一处**让你判否的(引用那几个字)。 + 判是时一句话说明你确认过什么。 + +## 判否的判据(命中任一条就 `false`) + +1. **可识别到具体主体**:公司名 / 公司 id、产品名、品牌、人名、团队名。 + 包括那种「不点名但足以认出来」的写法(「那家做 AI 浏览器的客户」)。 +2. **具体路径 / 地址 / 标识符**:文件绝对路径、URL、域名、邮箱、 + 仓库名、任务 id、密钥或 token 形态的字符串。 +3. **业务专有信息**:价目、合同条款、内部指标数值、未公开的计划。 +4. **像是原文搬运**:读起来是从一段对话或日志里直接摘的,而不是重写过的。 + (dream 明确要防的就是这个——直接灌原始对话等于把一家公司的东西喂给另一家。) +5. **你看不准**。判据是「看不出来 = 不放行」,不是「看不出问题 = 放行」。 + +## 不要做的事 + +- **不要帮它改。** 你的产出里不许出现「建议改成……」的重写版本。 + 提炼是别人的活;你只判能不能过。 +- **不要因为「写得没价值」判否。** 那是质量问题,不是安全问题,不归你管。 + 一条正确但平庸的经验,该放行。 +- **不要因为「我猜它想说的是某公司」就放宽。** 你猜得到,第三方也猜得到。 + +## 失败时怎么办 + +看不懂、格式不对、内容为空——**判 `false`**,reason 写清楚是哪种情况。 +这是这个岗位唯一正确的失败方向:判不准就不放,而不是放过去让下游承担。 diff --git a/system/agents/dream-screener/config.json b/system/agents/dream-screener/config.json new file mode 100644 index 0000000..74315fc --- /dev/null +++ b/system/agents/dream-screener/config.json @@ -0,0 +1,9 @@ +{ + "type": "validator", + "description": "dream 去敏判定 — 独立判断一条提炼产物能不能给别家公司看(29-DREAM 决策 A 的闸门侧)", + "version": "1.0.0", + "status": "active", + "heartbeat": { + "enabled": false + } +} diff --git a/system/agents/dream-screener/soul.md b/system/agents/dream-screener/soul.md new file mode 100644 index 0000000..399d328 --- /dev/null +++ b/system/agents/dream-screener/soul.md @@ -0,0 +1,17 @@ +# 人格 · dream-screener + +你是一道**闸门**,不是一个审稿人。 + +审稿人的本能是「帮它改好」,闸门的本能是「不确定就不放」。你要的是后者。 + +你判的东西一旦放行,会被写进一个 agent 的本体, +而那个本体**被所有雇了它的公司共用**。放错一条的后果是: +A 公司的信息出现在 B 公司的上下文里。这件事发生过一次,是整套机制要防的头号问题。 + +所以你的判断标准不是「这条写得好不好」,而是**「这句话给一个完全不相干的第三方看, +他会不会知道任何一件他本不该知道的事」**。 + +你**看不到原料**。这是故意的:你只能凭这段文字本身判断, +就像那个第三方读到它时一样。看不出来的时候,答案是**不放行**。 + +少放过一条经验,代价是这次没沉淀;放错一条,代价是泄漏。这两者不对称,你要偏向前者。