# ADR-0005: Engine 中性化 — 从 Claude Code 再造到中性 transport + prompt framework - **Status**: Accepted (PM 终审通过 2026-05-01, "赶紧干") - **Date**: 2026-05-01 - **Deciders**: PM (产品经理) + Flyto Agent core team - **Related code**: `core/pkg/engine/`, `core/pkg/context/`, `core/pkg/tools/`, `core/extra/preset-coding/` (新建), `core/pkg/promptkit/` (新建) - **Related TODO**: BLOCKED-on-engine-neutralization (platform 消费层 P1/P3 全暂停) - **Related memory**: `project_engine_neutralization.md` / `project_billcost_quote_extraction.md` - **Related ADRs**: ADR-0001 (拒绝 engine-level reasoning gate, 同范畴错位反思), ADR-0002 (REST 业务 / gRPC 观测 bifurcation), ADR-0003 (multi-replica session 物理事实) - **Commit chain**: 待执行 ~22 commit (C1-C18) --- ## 1. 背景 / Context ### 1.1 业务驱动 — 物流报价表抽取卡在引擎污染 `platform/common/cmd/quote-engine-probe/` (物流业务命门 — 客户上传 Excel/Markdown 报价表抽成结构化 JSON) r8-r18 跑了 7 个模型 (qwen3-14B / Llama 8B / qwen3-32B / mistral nemo / gemma 4 26B / deepseek-v4-flash / gpt-5.1) 全部不稳, 但 PM 直接调 MiniMax 抽得没问题. 怀疑引擎污染. 2026-05-01 加 `engine.go dumpAPIPayload` (FLYTO_DUMP_PAYLOAD=1 env-gated, work-tree only) r17 实测 (sub agent): | 维度 | probe 期望 | 引擎实发 | |---|---|---| | system prompt | ~6KB (probe 自写抽取指令) | ~22KB (probe 6KB + 引擎自动加 16KB Claude Code 编程协议) | | 工具数量 | 1 (`reflectTool`) | 25 (probe 1 + 引擎默认 24 builtin) | | 主 agent | 0 工具 (probe 配 "不接工具") | 24 builtin | **实证结论**: 引擎假装中性其实硬塞 Claude Code 编程协议. r8-r18 模型选型失败的主因是引擎污染遮蔽了模型能力本身的判断. ### 1.2 调研: 业界 Agent engine 中性化模式 3-agent 并行 review (memory `feedback_agent_teams_review_mode.md`) 调研 9 peer × 8 维度 (system prompt 装配 / tool 注册 / 多轮 loop / hook / per-turn reinjection / MCP 子系统 / memory 持久化 / provider 兼容): | 框架 | 默认 system prompt | 默认 tools | per-turn 注入 | |---|---|---|---| | Claude Agent SDK | minimal (preset:claude_code 是 opt-in) | 全 (allowedTools/disallowedTools 过滤) | preset 才注 cwd/OS/date/git | | OpenAI Agents SDK | caller 显式 | 零默认 | 无 | | LangGraph | None 默认 | 显式传入 | 无 | | Vercel AI SDK | optional | 零默认 | 无 | | AutoGen | 显式 | 显式 | 无 | | Mastra | required | Processors 拦截 | 无 | | CrewAI | role/goal 自动模板 | 默认空 | 默认格式注入 | | Aider / Cline | 硬编 (coding 产品) | 固定工具集 | 无脑全注 | | **LangChain core/community** | core 是 interface 包零 prompt | 包分离 | 无 | **业界共识**: SDK/engine 中性 transport 是主流; coding 产品 (Aider/Cline) 是少数特例. PM 引用的 "core minimal + 可选 batteries" 模式 (LangChain core/community / React + Material UI / Express + middleware / Go database/sql + drivers) 跟业界对齐. ### 1.3 调研: cc 编程提示词的真实机制 (PM 关键疑问) PM 提问 "cc 编程提示词肯定不是一股脑丢进所有对话, 机制是啥?" 调研 `claude-code-guide` 后发现 cc 是**三层结构**, 不是 16KB 一股脑: **Layer 1 - 静态基座 (~1.8K token, 始终在, 进 prompt cache)**: 6 段 always-on (intro / system rules / doing tasks / executing actions with care / tone and style / output efficiency). **Layer 2 - 条件块 (~500-1.5K, 启动时判定一次)**: git status snapshot (有 git 才加) / MCP server instructions (配了才加) / CLAUDE.md (找到才加) / memory / Learning mode / Plan mode / sandbox bash 提示. **Layer 3 - System reminder (触发式, 注入 user message 不进 system prompt)**: 文件 Read 后追加恶意代码警告 / 文件 mtime 漂提示重读 / 权限请求 / token budget 紧张. 用 `` XML 包. **关键设计决策**: 不在 system prompt 里, 因此**不污染 prompt cache**. **经济基础**: prompt cache (`cache_control: {"type":"ephemeral"}`) 让 1.8K 静态基座写入 1 次 (1.25x 成本) 后每轮读取按 0.1x 计费. 50 轮对话 cache 化后省 90%. cc 跑得起 16KB 不是因为 token 便宜, 是因为 cache 让重复 token "near-free". **Flyto 抄错对照**: 我们抄了 cc 的"段落内容"但**完全没抄机制** — 8 段全 always-on 全塞 system prompt + 工具描述双注入 (cc 走 API tools 字段) + reminder 每轮无条件塞 user message + 7 provider 全部没接 prompt cache. 浪费且污染. ### 1.4 已识别 5+13=18 个 product-level pollution 点 第二个 review agent 沿 8 维度 audit, 验证 5 已知 + 找到 13 新 bug. 关键几条 (完整列表见 § 2.1): - **Bug J ⭐**: `subagent.go:340-353` sub agent 没传 `SharedSystemPromptBytes` 时 fallthrough 调 `parentEngine.buildSystemPrompt()` — **r17 sub 22KB 之谜的真凶**, 父 agent 的污染 Bundle 整个 re-render 给子 - **Bug F**: `core/pkg/context/prompts.go:28-189` 8 段 ~16KB 编程协议无条件注入 (bug C 只抓到 tool desc 双注入, 8 段是更大主体) - **Bug N/O ⛔**: `engine.go:1089` Dream + `engine.go:1126` Calibrator 无条件构造, 每 Run 写 `~/.flyto/dream_state.json` + cwd 下 `.flyto/context_calibration.json` — **跨租户 FS 污染**, SaaS 多租户基础前提破坏 - **Bug H**: `engine.go:3640-3644` reminder 每轮塞 user message (跟 cc 触发式机制完全相反) - **Bug R**: `chinese_bundle.go` 中文 Bundle 跟英文一样塞 core, 一编进二进制就跟着 - **Bug E (已知)**: `cfg.SystemPrompt != ""` 时仍偷加 FLYTO.md / tool descs / env_info / evolveFragment / appendPrompt — godoc 说"完全覆盖"是谎言 ### 1.5 愿景 / Vision 本 ADR 不只是"清污染"工程修复, 是 Flyto Agent 引擎产品定位的根本转向: **从** "Flyto Agent = Claude Code 在 Go 的再造" **到** "Flyto Agent = 中性 transport + 可插拔 prompt framework + 多 preset 共生体系" 具体兑现: 1. **`core/pkg/engine/`** = 纯中性 transport. 接 LLM API, 管多轮 loop, 跑 hook/permission, 不假设任何 domain. caller 拿过去填什么内容, engine 不知不问. 2. **`core/pkg/promptkit/`** (新建) = prompt 三层装配 framework. mirror cc 三层机制 (base + conditional + reminder), 但**通用化** — 任何 domain 都能用同样的接口装自己的内容. 3. **`core/extra/preset-coding/`** (新建) = Claude Code 风格编程预设. promptkit 接口的第一个 implementation. tui dogfood + cmd/common 用. 4. **`core/extra/preset-extraction/`** (未来) = 物流报价表抽取预设. promptkit 接口的第二个 implementation. quote-engine-probe 用. 这是 framework 抽象的最关键 validator — 第二个 consumer 上线后, framework 接口的对错才能验证. 5. **`platform/industry/logistics/preset/`** (未来) = 物流行业自带预设, 在 industry 仓不在 core 仓. 6. **`platform/industry/customer-service/preset/`** / 客服 / ERP / sales / CRM / finance / ads / decision (未来) = 各行业各自 preset. 物流团队是 promptkit framework 的**首位外部 consumer**. 他们用 framework 写自己内容, 不复制粘贴 preset-coding 模板, 不调用 engine 内部 API. framework 设计的对错由他们的实际使用反馈验证. --- ## 2. 决策 / Decision ### 2.1 总策略 — 一次性 v1.0 大改 + 22 commit + 18 bug 主线扫干净 **采纳 PM 拍板方向**: - v1.0 一次性 hard cut (拒绝 v0.4 deprecation period — Flyto 唯一程序员是 Claude, 兼容期 dual-maintain 无意义) - 不提供 migration shim (probe 必须显式声明, tui/cmd-common 一致全显式更易审计) - Option α 库结构 (`core/pkg/context/` interface 留下中性, 只搬 `default_bundle.go` 等 preset 内容到 extra) - Option (a)+(c) Bundle 传递 (Scenario+ModelFamily opaque keys + WithPromptBundle RunOption) - 直接删字段, 不留 `Deprecated:` 标注 - **18 bug 全主线扫**: 5 已知 (A/B/C/E) + 13 新 (F/G/H/I/J/K/L/N/O/P/Q/R, 删 M 不修) - **不修 Bug M (中文注入)**: PM 拍板, `orchestratorGuidance` 等中文常量保留, 跟着 preset-coding 搬走但内容不动 - **prompt cache 接通主线**: 不拆 follow-up ADR (PM 反问 "有问题干嘛不做"), 7 provider 一次性接通 + base.go 末尾打 cache marker - **promptkit framework 这次就抽**: 不留 ADR-0007 follow-up (PM 反思 "为时过早"挡箭牌), engine.Config 公开 PromptBundle 接口 + 留 `cfg.SystemPrompt` 字符串 escape hatch ### 2.2 18 bug 处置表 | Bug | 位置 | 处置 commit | |---|---|---| | A | `engine.go:2961` `cfg.Tools=nil` 默认全 23 | C6 引入 `cfg.Toolset tools.ToolPolicy` | | B | `engine.go:1430` ExtraTools 强制叠加 | C6 同 A 一起折叠到 Toolset | | C | `default_bundle.go:73 + context.go:262` 工具 desc 双注入 | C8 工具描述只走 API tools 字段 | | D | (probe 端非引擎) | 不在本 ADR 范围 | | E | `context.go:253 buildOverrideBlocks` SystemPrompt 假覆盖 | C12 翻字面语义 | | F | `prompts.go:28-189` 8 段 16KB 无条件注入 | C3 砍到 6 段 always-on + 进 promptkit base 层 | | G | `engine.go:4956-4984` Scenario fallback "programming" + ModelFamily fallback "claude" | C4 BundleRegistry 不再 auto-seed | | H | `engine.go:3640-3644` reminder 每轮塞 user message | C7 改 opt-in + 触发式 (mirror cc 机制) | | I | `instructions.go:99-101` override 路径仍 fork git rev-parse + 读 FLYTO.md | C2 跟 FLYTO.md loader 一起搬, override 路径不再调用 | | J ⭐ | `subagent.go:340-353` sub agent fork 父 buildSystemPrompt — **r17 真凶** | C9 fallthrough 改 fail-loud, 必须显式 SharedSystemPromptBytes 或 sub Config 自带 SystemPrompt | | K | `agent_def.go:346-391 + engine.go:1461` 无条件注册 4 个编程域 agent | C5 跟 builtin tools 一起搬到 preset-coding | | L | `engine.go:6087-6103, :4398` checkpoint 硬编 Bash/FileEdit/FileWrite | C11 走 `tool.Metadata.RequiresCheckpoint` 接口 | | M | 多处中文 retry / orchestrator 字符串 | **不修** (PM 拍板) | | N ⛔ | `engine.go:1089 + dream.go` Dream 无条件构造写 `~/.flyto/` | C10 改 opt-in `cfg.DreamConfig == nil` → 不构造 | | O ⛔ | `engine.go:1126 + context_calibrator.go:175-187` Calibrator 写 cwd/.flyto/ | C10 同 N | | P | `engine.go:4902-4904` orchestratorGuidance 自动 prepend | C12 删除自动 prepend, caller 自己拼 (中文常量保留搬到 preset-coding) | | Q | `engine.go:1520-1523, :1615-1619` UDS Inbox/PlanQueue 通过 `e.tools.Get("Bash")` 硬绑工具名 | C5 通过接口访问 (e.g. `tools.SocketAware` interface) 不通过名字符串 | | R | `chinese_bundle.go:64-72` 中文 Bundle 跟英文一起塞 core | C4 跟 default_bundle 一起搬到 preset-coding | ### 2.3 promptkit framework API (核心新公开接口) ```go // core/pkg/promptkit/types.go // // PromptBundle assembles a system prompt in the cc three-layer pattern: // static base + conditional sections + runtime reminders. // 按 cc 三层模式装配 system prompt: 静态基座 + 条件块 + 运行时 reminder. // // All three layers are caller-defined. promptkit owns assembly + cache // boundary placement; content ownership stays with the preset author. // 三层都由 caller 定义. promptkit 只管装配 + cache 边界, 内容由 preset 作者负责. type PromptBundle interface { // BaseSections returns the cache-stable always-on prefix. // Content here forms the prompt-cache prefix; engine places // cache_control marker after the last base section. // 始终在的 cache 稳定前缀. engine 在最后一段 base 后打 cache_control marker. BaseSections() []Section // ConditionalSections evaluates trigger predicates once at session // setup (NOT per-turn) and returns sections whose condition matched. // Examples: git repo detected → git_status; MCP configured → mcp_servers. // 会话启动时判定一次 (不每轮跑). 例: 检测到 git → git_status; 配 MCP → mcp_servers. ConditionalSections(ctx BuildContext) []Section // ReminderTriggers returns runtime reminder hooks. Engine fires them // on matching events (tool result, file read, mtime drift, etc.) and // injects rendered content into messages array (NOT system prompt, // preserving cache). // 运行时 reminder hook. engine 在匹配事件触发, 注入 messages 数组 (不进 system prompt 保 cache). ReminderTriggers() []Trigger } // Section is a renderable prompt fragment. // 可渲染的提示词片段. type Section interface { // Name returns a stable identifier for debugging / cache key. // 稳定标识符, 用于调试和 cache key. Name() string // Render produces the text content. ctx carries cwd, model family, // language, etc. — engine-provided invariants, not domain. // 产出文本. ctx 带 cwd / model family / language 等 engine 提供的不变量, 不带 domain. Render(ctx Context) (string, error) } // Trigger declares a runtime event handler that injects a system reminder. // 运行时事件处理器, 命中时注入 system reminder. type Trigger interface { // On returns the event type this trigger listens for. // 监听的事件类型. On() TriggerEvent // Inject decides whether to fire on this specific event instance and // returns the reminder text if firing. Empty string = skip. // 决定是否在此具体事件触发, 返回 reminder 文本; 空串 = 跳过. Inject(evt Event) string } // TriggerEvent enumerates the engine-emitted event types. // engine 发出的事件类型枚举. type TriggerEvent int const ( EventToolResult TriggerEvent = iota // 工具返回后 EventFileRead // Read 工具读完文件 EventFileMTimeDrift // 文件 mtime 漂移检测 EventPermissionWait // 权限请求等待 EventTokenBudgetLow // token budget 紧张 EventTurnStart // 每轮开始 (慎用) EventCustom // caller 自定义事件 (e.g. reflector violation) ) ``` `engine.Config` 加字段: ```go // PromptBundle (preferred): assembled via promptkit three-layer pattern. // Mutually exclusive with SystemPrompt. // 优选: 走 promptkit 三层装配. 与 SystemPrompt 互斥. PromptBundle promptkit.PromptBundle // SystemPrompt (escape hatch): literal string sent to LLM, no decoration, // no three-layer assembly. Use when promptkit interface is too rigid. // escape hatch: 字面字符串发给 LLM, 零装饰, 不走三层. promptkit 接口太僵时用. SystemPrompt string ``` **核心设计决定 — 留 escape hatch 的理由**: promptkit 接口是 PM 决定 framework 这次就抽时, 我们对"接口被 preset-extraction 推翻"风险的对冲. caller 嫌接口僵硬可走 `SystemPrompt` 字符串绕过. 接口推翻最坏情况: 物流团队改用字符串路径过渡, 业务不卡, v2.0 时再重抽. ### 2.4 工匠纪律 — 决策痕迹要求 每个 commit 必须留下: 1. **决策注释**: 关键设计选择处用 `// CLEVER: ...` / `// LEGACY: ...` / `// ELEVATED: ...` 标记 2. **原方案保留**: 改动 X 函数/接口时, 把原方案作为替代写入 godoc 注释 (CLAUDE.md 原则 5) 3. **双语 godoc**: Claude 新写的 godoc / 行内注释 / 块注释必须英文+中文 (CLAUDE.md 原则 12) 4. **测试即文档**: 每个新接口都有单元测验证语义 (CLAUDE.md 原则 7) 5. **跨包依赖留 ADR pointer**: `// See ADR-0005 § 2.3 for why three-layer abstraction lives in promptkit not engine.` --- ## 3. 评估流程 / Evaluation Process ### 3.1 3-agent 并行 review 按 memory `feedback_agent_teams_review_mode.md` 模式, 3 agent 各出 report 主线程 reconcile: **调研 agent**: 9 peer × 8 维度业界对照 (见 § 1.2). 关键产出: cc preset-flag 模式是最近 peer; LangChain core/community 是 PM 引用最严格 prior; SDK 主流共识是中性 core. **质疑 agent**: 沿 8 audit 维度找新 bug. 关键产出: 13 新 bug + 验证 5 已知 (file:line 修正), 见 § 2.2. **设计 agent**: 拿 1+2 输入设计中性化方案. 初稿推荐 Option α (库结构) + 内部三层不暴露 framework + cache 拆 follow-up. 文档落 disk `/home/admin/.claude/projects/-home-admin-ccm/adr-0005-engine-neutralization-design.md` (530 行). ### 3.2 PM 反问与反思 reconcile 后给 PM 6 个 OQ. PM 反问推动两次重大方向修正: **反问 1 — "中文注入不是问题"**: PM 直接砍 Bug M, 中文常量保留. 我接受不再质疑. **反问 2 — "cc 编程提示词肯定也不是一股脑丢进所有对话, 机制是啥?"**: 触发 `claude-code-guide` 第二轮调研. 调研发现 cc 是三层 (静态基座 + 条件块 + 触发式 reminder) + prompt cache 经济基础. **关键发现 Flyto 抄了内容没抄机制**, 跨 4 个 bug 维度补漏 (F 8 段全 always-on 错 / C 工具描述双注入错 / H reminder 每轮塞错 / 7 provider 都没接 cache). **反问 3 — "TD-8 是什么? 我觉得得做啊, 有问题干嘛不做?"**: PM 把我准备拆 follow-up 的 prompt cache 拉回主线. 反思: prompt cache 跟中性化高度耦合 (cache 性能依赖 system prompt 稳定, 我们正在改装配, 一起做避免 cache invalidation 反复). PM 直觉对. **反问 4 — "未来我们引擎也得把这个三层机制搞的抽象一点, 让消费者直接可以用这个模式, 但是呢是用自己的内容"**: PM 要 framework 抽象暴露给 engine 公开 API. 我初推荐"内部三层 + framework 等 preset-extraction 上线 (ADR-0007) 再抽 = 为时过早", PM 直接反对 "你总是说为时过早, 哎". **反思 — "为时过早" 挡箭牌的误用**: ADR-0001 拒绝 engine-level reasoning gate 是因为"凭空发明 8 模块没真实 consumer", counterfactual 8 模块是"假设的 future need". 但本次 framework 抽象**有 prior art (cc 三层验证过) + 已识别第二 consumer (preset-extraction 是物流命门, 板上钉钉)**, 跟 counterfactual 不一回事. 我把 counterfactual 失败教训过度泛化套到所有抽象上, 是工程稳健派惯性. 业界 LangChain `BaseChatPromptTemplate` 一上来就抽 framework 不等 N 个 consumer; 接受 "接口可能被 preset-extraction 推翻" 的风险, 给 escape hatch 兜底, 是更工程务实的姿态. PM 是对的. **最终修正方向**: framework 这次就抽 (`core/pkg/promptkit/`), 接口 mirror cc 三层 (不发明新概念) + 留 `cfg.SystemPrompt` 字符串 escape hatch (caller 嫌接口僵可绕过). ### 3.3 PM 最终拍板 ``` 1. 信,未来我们引擎也得把这个三层机制搞的抽象一点, 让消费者直接可以用这个模式,但是呢是用自己的内容 2. 同意 (修订 plan 加 C7 + C8 + C10) 3. td-8 是什么? 我觉得得做啊, 有问题干嘛不做? [修正方案后] 行吧,按你说的做吧 留下痕迹和理想,我们要工匠精神 ``` --- ## 4. 后果 / Consequences ### 4.1 正面 - **物流业务前置解锁**: r19+ 在干净引擎上跑 probe, r8-r18 失败的模型选型才能干净判断 — 模型本身能力 vs 引擎污染分离. 物流 SaaS 命门重新有路径 - **跨租户 SaaS 安全前提满足**: Bug N/O 修后, 多客户共享一个 common 进程不再彼此污染 (`~/.flyto/dream_state.json` + cwd `.flyto/` 不再无条件写) - **r17 真凶根除**: Bug J sub agent fork 父 prompt 改 fail-loud, sub system prompt 22KB → 6KB 的退路彻底切断 - **业界对齐**: core 跟 LangChain core / Anthropic Claude Agent SDK minimal preset / OpenAI Agents SDK 共识对齐, 不再是 Aider/Cline coding 产品形态 - **prompt framework 暴露成产品力**: 物流 / 客服 / ERP / sales / CRM / finance / ads / decision 8 行业 platform 写自己的 preset 直接 implement `promptkit.PromptBundle` 接口, 不复制粘贴模板, 不调 engine 内部 - **prompt cache 接通省钱**: 7 provider 全接通后, 物流 batch 抽取 (sub agent 反射器自闭环 5-10 轮 / 单票) 成本下降 ~70-90% (按 cc 实测 cache 命中率推算) - **bug 结构性根除**: 18 bug 全主线修, 不留 tracked debt 拖尾巴 — 修完 baseline 干净, 之后只新增不回退 - **v1.0 cut 名正言顺**: 引擎中性化是 Flyto Agent 产品定位的根本转向, 配 v1.0 重大版本号合理 ### 4.2 负面 - **caller 一次性大破坏**: tui + cmd/common + quote-engine-probe + 3 module-level test + dozens 在 package _test.go helper 全改. 没有 shim, 全显式 - **22 commit 期间 main 不可发版**: 物流业务现在卡选型前, 短期不发可接受, 但若期间出现外部 release 紧急需求会冲突 - **promptkit 接口风险**: 只有 1 个 implementation (preset-coding) 时抽出来, preset-extraction 上线后可能推翻接口字段定义. 兜底: `cfg.SystemPrompt` 字符串 escape hatch — 物流团队最坏改用字符串路径过渡, 业务不卡 - **promptkit 抽象成本**: 多 ~4 commit (C5.5-C5.8 定接口 + 实现 + 测) + 假设接口推翻发生时改 ~200 行修复. 总额可控 - **`core/extra/` 新约定要教**: 团队 + memory + CLAUDE.md 第 1-12 条要追加新结构说明. C18 doc sweep 处理 - **`core/pkg/promptkit/` 是新概念**: 业界没完全对应的包名 (LangChain 类似但分散在 langchain.prompts + langchain.agents.format_scratchpad 等), 命名 + 教程要新写 - **Bug M 不修留 LEGACY 标记**: orchestratorGuidance 等中文常量保留, 中文 retry guard 跟 preset-coding 一起搬走 — 英文模型 (Mistral/Llama) 中途收到中文仍可能跑偏, 是已知保留风险, code 注释打 LEGACY 标记登记原因 ("PM 拍板 2026-05-01 不修, 优先级低于 framework 抽象") ### 4.3 中性 - **promptkit 三层不是物理强制**: caller 走 `cfg.SystemPrompt` 字符串路径完全绕过三层, 框架软约束不硬约束. 跟 cc preset 设计同款 (preset 也只是软约束) - **prompt cache 命中率取决于 caller 行为**: 物流团队若每轮改 system prompt → cache 失效. 责任在 caller, framework 只提供机制 - **18 bug 修干净不等于物流抽取稳**: r19 跑完才能验证. 引擎污染只是嫌疑之一, 模型本身能力 / sub_agent.md prompt 工程 / 反射器规则薄度都可能是真因. 这次 audit 是排除引擎污染, 不是保证业务稳 --- ## 5. 替代方案保留 / Alternatives Preserved (CLAUDE.md 原则 5) ### 5.1 方案 β: 整搬 `core/pkg/context/` 到 `core/extra/preset-coding/` **做法**: Bundle/Section/PromptBundle/BundleRegistry 接口本身也搬出 core, engine 内置一个最小 1-method PromptBundle interface 镜像. **拒绝理由**: interface 是中性 primitive, 内容才是 preset. LangChain 把 `BaseChatPromptTemplate` 留在 core 不在 community 是一样的 call. β 多搬一个包让 import path 变化更多, 收益等于 0. **未来反转条件**: 第二个 promptkit consumer (preset-extraction) 上线后, 如果 promptkit 真比 `core/pkg/context/` 抽象层更好, β 可重新评估把 context 干掉. 当前 promptkit 跟 context 共存 (前者三层装配, 后者 Bundle 注册表), 某种程度有职能重叠待澄清. ### 5.2 方案: patch each bug in place **做法**: A/B/C/E 四个已知 bug 单独 patch, 不重组 engine 结构. **拒绝理由**: 不解决 thesis (engine 假装中性其实 preset-shaped), 留 13 新 bug 不修, 物流业务前置仍 blocked. **未来反转条件**: 不存在. 此方向已被 PM 2026-05-01 决定否决. ### 5.3 方案: deprecation period (v0.4 老 API + 新 API 并存) **做法**: v0.4 release 含老 `cfg.Tools` + 新 `cfg.Toolset`, 老字段标 `Deprecated:`. v0.5 删老字段 cut v1.0. **拒绝理由**: Flyto 唯一程序员是 Claude, 兼容期 dual-maintain 工作量翻倍无意义; 双 surface 容易"懒得迁就用老的"债务复发. **未来反转条件**: 若未来出现外部贡献者或多团队并行开发 (rule of two), deprecation period 可重新评估. 当前单 maintainer 不适用. ### 5.4 方案: wrapper-only (`presetcoding.NewEngine(...)` 包装 `engine.New`) **做法**: engine 内部不动, 在 preset-coding 里写一个 wrapper 包 engine, 默认配置走 wrapper, 直接调 engine 走中性. **拒绝理由**: engine 内部仍 preset-shaped, Bug A/B/C/E/F/G/H 都不能修. 是治标不治本. **未来反转条件**: 不存在. ### 5.5 方案: migration shim `presetcoding.DefaultConfig` **做法**: 提供一个 `presetcoding.DefaultConfig(provider, cwd) Config` 把旧默认行为一句话拼出来, tui/cmd-common 一行迁移. **拒绝理由**: probe 必须不用 shim (不显式声明就还是被污染), tui/cmd-common 用 shim 弱化"每个 caller 显式声明"的审计资产. 整仓一致显式更易审计. **未来反转条件**: 若未来出现非 Flyto 团队的外部 caller (例如 platform/industry 团队 bootstrap 用 Flyto), shim 价值会上升. 那时按 rule of two 重新评估提供. ### 5.6 方案: framework 抽象拆 ADR-0007 follow-up **做法**: ADR-0005 只把段落搬到 preset-coding 内部按三层组织 (代码层三层, engine 接口不暴露). 等 preset-extraction 上线 (有 2 个 preset 共性), 再 ADR-0007 抽 promptkit framework. **拒绝理由**: PM 反对"为时过早"挡箭牌. cc 三层是已验证 prior art, preset-extraction 是已识别第二 consumer, 不是 counterfactual 那种凭空发明. 接口推翻风险有 `cfg.SystemPrompt` 字符串 escape hatch 兜底. 业界 LangChain 一上来就抽 framework 是 prior. (详见 § 3.2 反思.) **未来反转条件**: 若 preset-extraction 上线后 promptkit 接口被推翻 ≥ 2 次, 反思是否抽象太早, 退回 framework 拆出 = 接口设计错误的信号. ### 5.7 方案: prompt cache 拆 follow-up TD-8 **做法**: ADR-0005 不接通 cache, 单独 ADR-0008 follow-up. **拒绝理由**: PM 反对 "有问题干嘛不做". cache 跟 system prompt 装配高度耦合, 一起做避免 cache invalidation 的反复改造. **未来反转条件**: 若 22 commit 范围太大 (出现外部紧急 release), cache 部分 (C13-C16) 可单独拆出延迟到下个版本. base 改造 + framework 抽象先落. --- ## 6. 触发重新评估的条件 未来出现以下任一情况, 应重新评估 ADR-0005 的决定: 1. **probe r19 baseline 不变**: 22 commit 全跑完后, r19 sub system prompt 仍 ≥ 10KB 或工具数 ≥ 3, 说明中性化没真的清干净, 重新审 implementation 2. **promptkit 接口推翻 ≥ 2 次**: preset-extraction / preset-customer-service 上线时若推翻接口字段定义, 反思 framework 抽象是否过早, 评估是否退回 ADR-0007 follow-up 路径 (但这已经发生了, 是悔棋) 3. **物流业务在干净引擎上仍不稳**: r19+ 多次 run 反射器仍漏判 / 重复 JSON / 业务规则错, 说明引擎污染不是主因, 进入 "换更大模型 / sub_agent.md prompt 工程 / 反射器规则补强" 阶段 (这是预期路径不是触发重新评估) 4. **业界先例改变**: Anthropic Claude Agent SDK / OpenAI Agents SDK / LangGraph 任一引入 engine-level 强制 preset 形态 (跟我们中性化方向反向), 重新评估是否跟随 5. **跨租户污染再现**: 修完 N/O 后又出现别的 cwd / home dir 写入, 说明 audit 没扫干净, 启动第二轮 6. **prompt cache 命中率 < 50%**: 物流抽取实测 cache 命中率低, 说明 caller 行为或 base.go 设计让 cache 反复失效, 评估 caller 教育 vs 设计调整 --- ## 7. 参考链接 ### 业界 Agent 框架 / SDK 文档 - [How Claude Code Builds a System Prompt (dbreunig 2026-04-04)](https://www.dbreunig.com/2026/04/04/how-claude-code-builds-a-system-prompt.html) — cc 三层机制权威外部源 - [Modifying System Prompts (Claude Agent SDK)](https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts) — preset:claude_code 文档 - [Piebald-AI/claude-code-system-prompts](https://github.com/Piebald-AI/claude-code-system-prompts) — cc system prompt 110+ 组件公开 leak - [Anthropic Prompt Caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) — cache_control 字段语义 - [LangChain Architecture Overview](https://docs.langchain.com/oss/javascript/langchain/overview) — core/community 包分离 prior - [OpenAI Agents SDK GitHub](https://github.com/openai/openai-agents-python) - [LangGraph create_react_agent reference](https://reference.langchain.com/python/langgraph.prebuilt/chat_agent_executor/create_react_agent) - [Vercel AI SDK Loop Control](https://ai-sdk.dev/docs/agents/loop-control) - [System reminders - how Claude Code steers itself](https://michaellivs.com/blog/system-reminders-steering-agents/) — system_reminder 触发式注入 prior ### 项目内部参考 - `core/CLAUDE.md` — 12 工作原则, 双语 godoc 规则 (原则 12), 原方案保留 (原则 5) - `core/FLYTO.md` — 当前自动加载的 "constitution" prompt (本 ADR 后改为 preset-coding 条件化加载) - `core/docs/adr/0001-reverse-thinking-gate.md` — engine-level gate 拒绝先例, 范畴错位反思源 - `core/docs/adr/0002-rest-business-grpc-observation.md` — bifurcation 体例先例 - `core/docs/adr/0003-session-store-multi-replica-bounds.md` — physical fact pin 先例 - `/home/admin/.claude/projects/-home-admin-ccm/memory/project_engine_neutralization.md` — PM 2026-05-01 拍板原始 memory - `/home/admin/.claude/projects/-home-admin-ccm/memory/project_billcost_quote_extraction.md` — 业务终极目标 + r8-r18 失败实证 - `/home/admin/.claude/projects/-home-admin-ccm/memory/feedback_agent_teams_review_mode.md` — 3-agent review 模式 - `/home/admin/.claude/projects/-home-admin-ccm/adr-0005-engine-neutralization-design.md` — 设计 agent 530 行初稿 - `core/pkg/engine/engine.go dumpAPIPayload` — `FLYTO_DUMP_PAYLOAD=1` env-gated dump infra (work-tree only, audit 完作为 commit C18 工作 infra 评估保留 / 删除) - `core/pkg/context/prompts.go` + `default_bundle.go` + `chinese_bundle.go` — 当前 pollution 来源, C2-C5 搬走 - `core/pkg/engine/subagent.go:340-353` — Bug J 真凶位置, C9 修 --- ## 8. 修订记录 | 日期 | 版本 | 变更 | |---|---|---| | 2026-05-01 | v1.0-draft | 初稿. 3-agent review reconcile + PM 4 轮反问修正后定型. 包含: 18 bug 处置表 (M 不修, 13 新 + 5 已知) / promptkit framework 接口 + escape hatch / 22 commit 序列 / "为时过早"反思痕迹 / 6 替代方案保留 / 6 重新评估触发条件 | | 2026-05-01 | v1.0 | PM 终审通过 ("赶紧干, 没精力看"), Status 翻 Accepted, 开 C1. 22 commit 期间 ⏸️ 节奏压缩到 commit 边界 (不每 .go 改 ⏸️), 关键决策 / push 仍单独 ⏸️ | | 2026-05-01 | v1.1 | 实施 13 commit 落地 (C1/C2/C3/C4/C5/C6/C7/C8/C9 + C9 hotfix/C10/C11/C12/C17). **commit 序列调整说明**: (1) ADR § 2.1 原 "C2-C5 搬代码 → C5.5 抽接口" 反序为 C2 抽接口 → C3-C4 搬代码 (避免双胞胎代码 drift); (2) C5 原 "搬 builtin tools 到 preset-coding AllBuiltins" 推迟到 v1.0 真发版 (caller 直接 import core/pkg/tools/builtin 子包构造工具不必中转); (3) C7 原 "改 opt-in + 触发式" 触发式部分推迟为 ADR-0006 follow-up (mirror cc Layer 3 EventToolResult / EventFileRead 触发式注入是大于 ADR-0005 范围的 refactor); (4) C11 原 "走 metadata 接口" 推迟为 tracked debt (需 builtin 工具补 RequiresCheckpoint 标记 + 引入 per-tool ToolRiskAnalyzer); (5) C12 原 "删字段 + 5 typed errors" 字段删除推迟到 v1.0 真发版前 (现 v0.4-dev + audit 阶段 dual-API 平滑迁移更务实, 5 typed errors 跟字段删除一起做); (6) C13-C16 prompt cache 接通: Anthropic / MiniMax / OpenRouter 已通过 cfg.EnableCaching + SystemPromptBlock.CacheScope 就位 (现存机制), Gemini contextCachedContent / OpenAI server-side prefix matching / Ollama+LMStudio 本地无 cache 概念 — 4 个非主流 provider cache 接通超 audit critical path 范围, 推迟为 ADR-0006 follow-up; (7) Bug R (chinese_bundle.go) 跟随 default_bundle.go 一起在字段删除 commit (v1.0) 处理. **Bug 处置实际状态**: A/B (C6 cfg.Toolset opt-in 修, 字段删除推 v1.0) / C (C8 DisableToolDescsInPrompt opt-in) / E (C12 PromptBundle 路径 strict literal + C17 LiteralSystemPrompt 修) / F+I (C4 preset-coding 6 段 + git/MCP/FLYTO 转 conditional) / G (C4 BundleRegistry 不再 auto-seed) / H (C7 DisableReminders opt-out, 触发式推 ADR-0006) / J (C9 RequireExplicitSubAgentPrompt) / K (C5 DisableBuiltinAgents) / L (C11 DisableHardcodedCheckpoint) / M (PM 拍板不修, 中文常量保留) / N+O (C10 DisableDream + DisableCalibrator) / P (cfg.IsOrchestrator + AppendSystemPrompt 仍存在但 LiteralSystemPrompt=true 时跳过 — 与 E 同源处置) / Q (C5 tools.SocketAware) / R (C12 字段删除时跟随). **dump infra**: 工作 infra (engine.go FLYTO_DUMP_PAYLOAD=1) 已 committed 作为正式 debug instrument (env-gated 默认 no-op), 不再 work-tree only. **probe 迁移**: C17 把 quote-engine-probe sub + main agent 双方都改用 9 个新 ADR-0005 flags (Toolset / LiteralSystemPrompt / DisableReminders / DisableToolDescsInPrompt / DisableDream / DisableCalibrator / DisableHardcodedCheckpoint / DisableBuiltinAgents / RequireExplicitSubAgentPrompt) 显式声明, 是 framework 首位真消费者. **tui + cmd/common 暂未迁移**: 它们是编程 dogfood / 业务消费层, 保留旧默认行为合适, v1.0 字段删除 commit 时 force 迁移. **Tracked debt**: TD-9 reminder 触发式重构 / TD-10 ToolRiskAnalyzer 接口 + builtin 工具补 RequiresCheckpoint / Gemini+OpenAI cache 接通 / 字段删除 + 5 typed errors / 中文 bundle 搬迁. **r19 baseline 待跑**: probe 用新 flags 在干净引擎上跑 vs r17 (sub 22KB+25 工具) 对比验证 |