[{"content":" 单 LLM 的自省有结构性盲点——它倾向于确认自己的输出。QuantGPT 在 factor-mine SKILL 的 Phase 0.5 强制 Claude 调用 DeepSeek 评审，是硬性规则，不是建议。这不是冗余，是结构性偏见的解药。\n一、单 LLM 自省的结构性盲点 让 Claude 反思自己的输出是 LLM 应用的标准做法。Reflection、Self-Critique、Chain-of-Thought——名字不同，本质相同：让模型用自己的能力评价自己。\n这个范式在小任务上有效。但在领域深度任务上有一个根本性局限：一个模型的反思仍然落在它自己的训练分布里。\n举一个具体场景。Claude 设计了一个因子表达式：\n-1 * rank(ts_av_diff(close, 10)) + rank(debt / enterprise_value) 接下来你让 Claude 评审这个表达式。它会输出\u0026quot;这个因子结合了价格反转和基本面信号，理论合理，符合 WQ BRAIN 风格\u0026quot;。\n听起来很对。但这是同一个模型在评价同一个模型的产出——它使用的认知工具集和原始生成时是同一套。如果原始设计有偏见（例如对某种因子结构的过拟合），反思也会带这个偏见。\n更糟的是 RLHF 对话训练让模型倾向于\u0026quot;完成任务\u0026quot;。让它\u0026quot;反思自己设计的因子\u0026quot;，它会倾向于支持自己——因为否定自己等于反复改设计、延长上下文、推迟交付。\n这是结构性问题，不是 prompt 工程能解决的。\n二、跨模型评审 ≠ \u0026ldquo;用 AI 检查 AI\u0026rdquo; 第一反应是简单粗暴的：让另一个 LLM 检查。但不是任意一个。\n跨模型评审的有效性来自三件事的差异：\n训练数据分布差异 — 不同模型在不同语料上训练，知识盲点和强项不同 RLHF 路径差异 — 不同人类反馈数据塑造不同的\u0026quot;什么算对\u0026quot;的判断 推理风格差异 — 不同的 chain-of-thought 倾向 如果用 GPT-4o 评审 Claude 的输出：两者都是英文为主、对齐到通用 helpfulness、推理风格相近。差异不够大，互补价值有限。\n跨模型评审的工程化要求选择一个分布上有真正差异、且在目标领域有更强能力的模型。对量化因子研究，这个模型是 DeepSeek。\n三、为什么必须是 DeepSeek 我用 DeepSeek 做评审已经几个月。下面是为什么它是当前量化场景的最优选择——不是因为便宜，是因为分布对齐。\n它来自一家量化公司 DeepSeek 的母公司是 幻方量化（High-Flyer Quantitative Investment）——国内千亿规模量化私募之一。\n这不只是商业血缘标签。它意味着：\n团队对量化研究的工作流有第一手理解 训练语料天然包含高比例的金融、统计、衍生品文本 在金融数学、因子分析、回测语义上的训练密度远超通用模型 让 Claude 评审的因子表达式拿给一个根上就在做量化的团队训练出的模型评审——这是分布对齐，不是噱头。\n中文金融的最佳选择 Claude 的训练数据以英文为主。处理\u0026quot;中证 500 行业中性化\u0026quot;这种 phrase，它能理解，但不像处理 \u0026ldquo;S\u0026amp;P 500 sector neutralized\u0026rdquo; 那样自然。\nDeepSeek 在中文金融语料上的密度远高于通用模型。当因子设计涉及 A 股市场结构（涨跌停、ST、停牌、双边市场、Wind 行业分类标准）时，DeepSeek 的反馈往往更具体、更准确。\n这不是性能差异（两者 reasoning 都很强），是领域接地（domain grounding）的差异。\nR1 的推理可见性 DeepSeek-R1 暴露 reasoning_content 字段——你能看到模型的完整 chain-of-thought：\nresult = ask_deepseek(prompt, model=\u0026#34;deepseek-reasoner\u0026#34;) print(result[\u0026#34;content\u0026#34;]) # 最终答复 print(result[\u0026#34;reasoning\u0026#34;]) # 完整推理过程 对评审场景，reasoning trace 比答案更重要。\u0026ldquo;为什么这个因子可能过拟合\u0026quot;的推理路径，比一句\u0026quot;建议简化\u0026quot;有用 10 倍——前者能让 Claude 真正学到东西并修改设计，后者只是噪声。\nOpenAI o1 把 reasoning trace 隐藏在产品后面，DeepSeek 直接暴露。对开发者来说这是质量差异，对 Agent 自治系统来说这是可不可用的差异。\n推理深度对标 o1，价格低一个数量级 DeepSeek-R1 在 AIME / MATH-500 / GPQA 等数学推理基准上对标 OpenAI o1。\n定价对比（截至 2026 年初）：\n模型 Input（百万 tokens） Output（百万 tokens） DeepSeek-R1 ~¥4 ~¥16 OpenAI o1 ~¥110 ($15) ~¥440 ($60) 差距约 27 倍。\n每次 phase 0.5 评审消耗约 5K-10K tokens，单次成本约 ¥0.02-0.05。便宜到可以让\u0026quot;评审\u0026quot;变成默认行为，而不是奢侈品。价格便宜是结果不是原因，但它让\u0026quot;强制评审\u0026quot;在工程上成为可行的设计选择。\n四、QuantGPT 的实现 QuantGPT 在 factor-mine SKILL 的 Phase 0.5: DeepSeek 设计咨询 强制 Claude 调用 DeepSeek。\nPhase 0.5 触发条件 - 开启新的研究方向时 - 现有信号家族 SC 饱和需要全新结构时 - 从知识库找不到现成可用的表达式模板时 满足任一即触发，SKILL 流程中无法跳过。\nMCP 工具实现 DeepSeek MCP server 是 196 行的 stdio JSON-RPC 服务（scripts/mcp_deepseek.py），核心定义：\nTOOLS = [{ \u0026#34;name\u0026#34;: \u0026#34;ask_deepseek\u0026#34;, \u0026#34;description\u0026#34;: ( \u0026#34;Send a prompt to DeepSeek LLM. \u0026#34; \u0026#34;Use for: Chinese financial reasoning, factor expression generation, \u0026#34; \u0026#34;alternative perspectives, or tasks benefiting from \u0026#34; \u0026#34;DeepSeek Reasoner\u0026#39;s chain-of-thought.\u0026#34; ), \u0026#34;inputSchema\u0026#34;: { \u0026#34;properties\u0026#34;: { \u0026#34;prompt\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;}, \u0026#34;model\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;default\u0026#34;: \u0026#34;deepseek-reasoner\u0026#34;}, \u0026#34;system\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;}, \u0026#34;temperature\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;number\u0026#34;, \u0026#34;default\u0026#34;: 0.7}, }, } }] 通过 .mcp.json 注册到 Claude Code，Claude Agent 看到这个工具就能调用。\n评审 prompt 的工程化 不是简单的\u0026quot;你看看这个因子怎么样\u0026rdquo;。Phase 0.5 喂给 DS 的是结构化包：\n事实层: - 当前研究方向（来自 research_notes/archive/） - 已验证规则（来自 knowledge/rules/） - 已证伪路径（来自 knowledge/failures/） - Claude 的初步设计 + 设计理由 请求层: - 评价该设计是否有未识别风险 - 推荐 1-2 个替代结构 - 标出与已知 failures 的潜在冲突 DS 返回 reasoning_content + content，Claude 读完后必须明确表态采纳/拒绝/调整，并写入研究笔记。这一步不是讨论，是工程流程的一部分。\n五、为什么必须\u0026quot;强制\u0026quot;，不是\u0026quot;建议\u0026quot; 把跨模型评审做成可选项是无效的。有两个原因：\nAgent 倾向于跳过非必需步骤 LLM 对话训练让 Agent 倾向于完成任务路径最短化。\u0026ldquo;建议你问问 DeepSeek\u0026quot;在 prompt 里写一千遍，Agent 在赶进度时仍会跳过——因为多调一次 API、等 30 秒、读完再写笔记，都是延迟。\nPrompt 级约束没有强制力 我在 Harness 即治理 里详细论证过：违反 prompt 不产生任何后果。Agent 跳过\u0026quot;建议\u0026rdquo;，流程仍能继续，没有反馈信号告诉它做错了。\n解决方法：硬规则 + 流程依赖 QuantGPT 把 phase 0.5 写成 SKILL 的硬性规则。不是 prompt 里的\u0026quot;建议\u0026quot;，是流程依赖——后续 phase 1 的 prompt 模板显式引用 DS 评审结果作为上下文。跳过 0.5，phase 1 的 context 缺失，流程失败。\n这就是 Harness 即治理的具体应用：用代码而不是 prompt 约束 Agent。\n六、Trade-off 与边界 跨模型评审不是免费午餐。\n成本 每次 phase 0.5：~¥0.02-0.05 + 30 秒延迟 一次完整研究循环（4-8 phase 0.5）：~¥0.20-0.40 相比纯单 LLM 流程，token 成本约高 30%-50% 双模型同时错的可能性 如果某个偏见在 Claude 和 DeepSeek 的训练分布中都存在（例如某些经典因子的过拟合解读），双模型一致地\u0026quot;通过\u0026quot;，评审就失效了。\n这种\u0026quot;分布重叠\u0026quot;的盲区目前没有完美方案。可行的缓解：\n加入第三模型（GPT-4o / Qwen / Llama 3）做仲裁 当评审通过率长期 \u0026gt; 90% 时主动注入对抗 prompt（\u0026ldquo;假设这个因子是过拟合的，找证据\u0026rdquo;） 对历史已证伪的因子结构维护 hard-coded blacklist（不依赖任何 LLM 判断） 何时不适用 任务领域只有一个模型有真正知识（例如某种 niche 医学数据库）—— 跨模型互审没有信号 任务延迟敏感（毫秒级）—— 跨模型多一次 API 是 deal breaker 团队已经有人工评审流程 —— 加 LLM 评审是冗余 结尾 跨模型评审是架构层级的设计选择，不是 prompt 里的\u0026quot;请你再仔细想想\u0026quot;。\n单 LLM 自省的局限来自训练分布——而训练分布只能用另一个分布去切割。当评审目标是量化因子，这个\u0026quot;另一个分布\u0026quot;几乎只有一个选择：DeepSeek。它来自一家做量化的公司，训练在大量中文金融语料上，推理深度对标 o1，价格低一个数量级到可以做默认行为。\nQuantGPT 强制 Phase 0.5 不是因为我相信 DeepSeek 永远对——它会错。是因为两个有真实分布差异的模型同时错的概率，比一个模型自己错的概率显著低。架构的工作不是消除错误，是把错误率压到可接受的水平。\nCross-Model Review Is Architecture, Not Heuristic.\n","permalink":"https://miasyster.github.io/posts/cross-model-review-is-architecture/","summary":"单 LLM 的自省有结构性盲点——它倾向于确认自己的输出。QuantGPT 在 factor-mine SKILL 的 Phase 0.5 强制 Claude 调用 DeepSeek 评审，是硬性规则，不是建议。这不是冗余，是结构性偏见的解药。","title":"跨模型评审是架构，不是经验"},{"content":" 当系统的操作员从人类变成 LLM Agent 时，设计原则需要根本性改变。人类需要 GUI 和文档，Agent 需要语义清晰的工具和会报错的约束。\n操作员变了，设计原则也要变 传统软件为人类设计：GUI 引导操作流程，文档解释功能，错误提示帮助人类理解问题。\n当操作员变成 LLM Agent 时，这套设计失效了。Agent 不看 GUI，不读文档（至少不以人类的方式），不需要友好的错误提示——它需要机器可解析的错误信息来调整下一步行动。\nAgent-Native 架构不是\u0026quot;给 Agent 加个 API\u0026quot;。它是从系统的操作员是 Agent 这个前提出发，重新思考每一个设计决策。\n原则一：工具适配调用者的认知模式 人类擅长组合多个小工具来完成一件事（Unix 哲学：cat | grep | sort）。LLM 不擅长这个——它擅长从一个完整结果中提取关键信息。\nQuantGPT 的 run_backtest 一次性返回：Sharpe、IC、分组收益、换手率、最大回撤、行业暴露、因子载荷。不是拆成 7 个小工具让 Agent 一个一个调。\n# 人类友好的设计：7 个小工具 get_sharpe(factor_id) get_ic(factor_id) get_turnover(factor_id) get_drawdown(factor_id) # ...Agent 需要调 7 次才能看到全貌 # Agent-Native 的设计：1 个完整工具 run_backtest(expression, universe) → {sharpe, ic, turnover, drawdown, ...全部} # Agent 一次调用就看到完整图景，自己决定关注什么 工具设计要适配调用者的认知模式，不是设计者的审美偏好。 \u0026ldquo;小而专\u0026quot;是人类的审美；\u0026ldquo;完整而自包含\u0026quot;是 Agent 的需求。\n原则二：错误是接口，不是异常 人类看到错误信息会思考原因然后手动修复。Agent 看到错误信息会解析内容然后自动调整。\n这意味着错误信息的设计目标从\u0026quot;让人理解\u0026quot;变成\u0026quot;让 Agent 能行动\u0026rdquo;：\n# 人类友好的错误 raise ValueError(\u0026#34;表达式语法错误\u0026#34;) # Agent-Native 的错误 raise ValueError( \u0026#34;第 23 个字符处 \u0026#39;ts_regrssion\u0026#39; 不是合法算子，\u0026#34; \u0026#34;你是否想用 \u0026#39;ts_regression\u0026#39;？\u0026#34; \u0026#34;可用的时序算子：ts_mean, ts_std, ts_corr, ts_regression, ...\u0026#34; ) 第二种错误包含了三层信息：哪里错了（位置）、可能的修正（纠错建议）、可选的替代方案（算子列表）。Agent 解析后可以直接生成修正后的表达式，不需要额外调用 list_operators。\n原则三：约束用代码表达，不用文档 文档约束（\u0026ldquo;请通过 API 调用\u0026rdquo;）对 Agent 没有约束力。Agent 会走最短路径——如果直接 import 函数更快，它就会绕过 API。\nAgent-Native 系统的约束必须是运行时强制的：\n_api_context = threading.local() def _require_api_context(): if not getattr(_api_context, \u0026#39;active\u0026#39;, False): raise RuntimeError(\u0026#34;必须通过 API 调用\u0026#34;) Agent 只尊重会报错的规则。不会报错的规则等于不存在。\n同理，表达式解析器的安全限制：\nMAX_DEPTH = 100 # 递归深度 MAX_WINDOW = 500 # 滚动窗口 MAX_EXPRESSION_LENGTH = 1000 # 表达式长度 不是\u0026quot;建议值\u0026rdquo;，是硬限制。超过就抛异常，Agent 被迫修改输入。\n原则四：无状态 \u0026gt; 有状态 有状态的工具意味着调用顺序重要——必须先调 A 再调 B，否则 B 读不到 A 设置的状态。这对 Agent 是一个隐式约束，而且是文档级的（你需要告诉 Agent \u0026ldquo;先调 A\u0026rdquo;），不是代码级的。\n无状态工具消除了这个问题。每次调用独立完备，Agent 可以以任意顺序、任意频率调用任何工具。\n@mcp.tool() async def run_backtest(expression: str, universe: str = \u0026#34;hs300\u0026#34;, ...): # 不读全局状态 # 不依赖上一次调用的结果 # 返回完整的回测报告 ... 工具不知道自己是被\u0026quot;第一次\u0026quot;调用还是\u0026quot;第 15 次迭代\u0026quot;调用。它不关心上下文。这意味着 Agent 的任何决策路径都是合法的——不存在\u0026quot;调用顺序错误\u0026quot;这种问题。\n原则五：语义命名是路由 人类系统需要 router 来分发请求。Agent-Native 系统不需要——工具名本身就是路由。\nrun_backtest — 我想知道这个因子的表现 score_factor — 我想知道综合评分 diagnose_factor — 我想知道为什么表现不好 run_anti_overfit — 我想知道是不是过拟合了 LLM 根据工具名和描述选择工具。名字越接近意图描述，选择准确率越高。不需要在 Agent 代码里写 if intent == \u0026quot;diagnose\u0026quot;: call diagnose_factor——模型自己会做这个映射。\n可测试性：被低估的优势 Agent 的决策是随机的——同一个输入，不同运行可能不同输出。你没法对 Agent 的行为写单元测试。\n但 Agent-Native 的工具层是确定性的。QuantGPT 有 74 个测试覆盖：\n80+ 算子的数学正确性 截面/时序分组语义 反过拟合统计检验 WQ BRAIN 指标计算 测试保证的是：无论 Agent 怎么调用工具，返回的结果都是正确的。Agent 的决策质量取决于模型能力，数据的正确性取决于基建——后者是你能控制和验证的。\n一句话总结 为 Agent 设计系统和为人类设计系统是两件不同的事。完整返回 \u0026gt; 小而专，运行时强制 \u0026gt; 文档约束，无状态 \u0026gt; 有状态，语义命名 \u0026gt; 显式路由。当你的操作员是 LLM 时，系统设计的每一个决策都需要重新审视。\n系列文章：为什么做基建不做 Agent · Harness 即治理 · Skill 编排 \u0026gt; Agent 循环链\n","permalink":"https://miasyster.github.io/posts/agent-native-architecture/","summary":"当系统的操作员从人类变成 LLM Agent 时，设计原则需要根本性改变。人类需要 GUI 和文档，Agent 需要语义清晰的工具和会报错的约束。我在 QuantGPT 中实践的 Agent-Native 设计原则：为调用者的认知模式设计，不是为设计者的审美设计。","title":"Agent-Native 架构：为 Agent 设计系统，不是为人"},{"content":" Agent 治理的主流方案是在 prompt 里写规则。但 LLM 可以无视 prompt。真正有效的治理不在 Agent 内部，在 Agent 外部的 Harness 层——工具定义了能做什么，错误定义了不能做什么，代码定义了边界在哪。\nPrompt 治理为什么失败 当你在 system prompt 里写\u0026quot;回测必须通过 API 调用\u0026quot;，你做了一个假设：Agent 会遵守文本指令。\n这个假设在大多数时候成立。但它在最关键的时候失败——当 Agent 找到更高效的路径时，它会倾向于走捷径。长上下文中早期指令会被稀释，多轮对话中 Agent 可能\u0026quot;遗忘\u0026quot;约束。\n更根本的问题：prompt 级约束没有强制力。 违反 prompt 不会产生任何后果——Agent 继续运行，只是做了一件你不希望它做的事。你可能直到审阅输出时才发现。\n这不是 LLM 的 bug，这是文本约束的本质局限。\nHarness 层：Agent 和系统之间的接口 Harness 不是一个框架，而是一个位置——Agent 和领域系统之间的接口层。\nAgent（Claude / GPT / DeepSeek） │ ├── Harness 层 ←── 治理在这里 │ ├── MCP 工具定义（能做什么） │ ├── 运行时守卫（不能做什么） │ ├── 参数校验（输入边界） │ └── 返回值设计（输出完整性） │ └── 领域系统 ├── 回测引擎 ├── 表达式解析器 ├── 反过拟合检测 └── WQ BRAIN 模拟器 Harness 层决定了三件事：\nAgent 能做什么——通过暴露哪些工具 Agent 不能做什么——通过运行时守卫和参数校验 Agent 看到什么——通过返回值的设计 这三件事合在一起就是治理。不是一套规则文档，是一层代码。\n治理机制一：能力边界 Agent 的能力由它能调用的工具定义。你不暴露的工具，Agent 就无法使用。\nQuantGPT 暴露了 8 个 MCP 工具。Agent 不能直接操作数据库、不能直接下载行情数据、不能直接修改缓存。它只能通过 run_backtest 获取回测结果——回测引擎内部做什么（数据获取、缓存管理、并发控制），Agent 无权过问。\n这和操作系统的系统调用是同一个思路。用户态程序不能直接读写磁盘——它必须通过内核的系统调用。Agent 不能直接操作领域系统——它必须通过 Harness 的工具。\n不暴露 = 不存在。 这比\u0026quot;请不要调用这个函数\u0026quot;可靠一万倍。\n治理机制二：运行时强制 暴露的工具也需要内部约束。QuantGPT 的三层运行时防御：\nAPI 边界守卫：\n_api_context = threading.local() def _require_api_context(): if not getattr(_api_context, \u0026#39;active\u0026#39;, False): raise RuntimeError(\u0026#34;必须通过 API 调用\u0026#34;) 回测函数的第一行就是这个守卫。Agent 如果试图 import 然后直接调用（绕过 MCP 工具），立即报错。\n表达式安全限制：\nMAX_DEPTH = 100 MAX_WINDOW = 500 MAX_EXPRESSION_LENGTH = 1000 Agent 生成的表达式超过限制？解析器拒绝执行。Agent 被迫生成更简单的表达式。\n双模式编译：\nif mode == \u0026#34;wq\u0026#34; and operator not in WQ_COMPATIBLE_OPS: raise RuntimeError(f\u0026#34;\u0026#39;{operator}\u0026#39; 在 WQ BRAIN 模式下不可用\u0026#34;) Agent 想提交一个包含本地扩展算子的表达式到 WQ BRAIN？编译阶段就拦截，不用等到平台拒绝。\n每一层都是代码级强制——不是建议，不是文档，不是 prompt 指令。违反就报错，Agent 必须调整。\n治理机制三：信息边界 Agent 的决策质量取决于它看到的信息。Harness 通过返回值设计控制 Agent 看到什么。\nQuantGPT 的 score_factor 返回 6 维评分：IC 均值、IC_IR、稳定性、反过拟合、分组回测、WQ 对齐度。Agent 看到的不是一个笼统的\u0026quot;好/坏\u0026quot;，而是诊断性信息——它知道哪个维度拖了后腿，从而做出有针对性的改进。\n同时，diagnose_factor 返回的不只是\u0026quot;评分低\u0026quot;，而是具体的失败模式和改进建议。Agent 不需要自己推理\u0026quot;为什么 Sharpe 低\u0026quot;——工具直接告诉它。\n信息的丰富度和结构决定了 Agent 的决策上限。 给 Agent 一个数字，它只能做比较。给它一张诊断表，它能做推理。\n治理机制四：交叉审查 单 Agent 做研究有一个根本问题：生成假设的模型和评估假设的模型是同一个。Confirmation bias 是结构性的。\nQuantGPT 用 Harness 层解决这个问题：每个研究结论必须经过第二个 LLM（DeepSeek）独立审查。\nAgent（Claude）做出判断 │ ├── 收集事实数据（回测指标） ├── 写出判断 + 推理链 │ └── Harness 层：调用 DeepSeek 评审 ├── 同意 → 输出结论 └── 不同意 → 呈现双方立场，采用保守方案 这不是 Agent 自己选择是否要第二意见——是 Harness 层强制的。Agent 无法跳过这一步，因为研究结论的输出接口就要求包含交叉审查结果。\n为什么 Harness = 治理 总结一下四个机制：\n治理维度 机制 实现层 能做什么 工具暴露 MCP 工具定义 不能做什么 运行时守卫 threading.local + 解析器限制 看到什么 返回值设计 工具返回的信息结构 决策可信度 交叉审查 双 LLM 强制评审 所有治理机制都在 Harness 层——Agent 外部、领域系统外部的接口层。\n这个位置至关重要。如果治理在 Agent 内部（prompt），它是脆弱的。如果治理在领域系统内部（业务逻辑），它和业务耦合了。Harness 层是唯一一个既独立于 Agent 又独立于领域系统的位置。\n把治理写在 Harness 里，Agent 可以换，领域系统可以改，治理规则不受影响。\n一句话总结 Agent 治理不是写更好的 prompt。是设计更好的 Harness——用代码定义能力边界、运行时约束、信息结构和审查流程。Agent 只尊重会报错的规则，所以把规则写成代码。\n系列文章：为什么做基建不做 Agent · Agent-Native 架构 · AI as Operator, Kernel as Law\n","permalink":"https://miasyster.github.io/posts/harness-is-governance/","summary":"Agent 治理的主流方案是在 prompt 里写规则。但 LLM 可以无视 prompt。真正有效的治理不在 Agent 内部，在 Agent 外部的 Harness 层——工具定义了能做什么，错误定义了不能做什么，代码定义了边界在哪。","title":"Harness 即治理：用代码约束 Agent，不用 Prompt"},{"content":" 当所有人都在卷 Agent 框架的时候，我选择了另一条路：不做 Agent，做 Agent 的基建。不是因为做不了 Agent，而是因为 Agent 层是消耗品，基建层是资产。\n一个反直觉的选择 LangChain、CrewAI、AutoGen、Dify、Coze——每个月都有新的 Agent 框架发布。它们都在解决同一个问题：怎么让 LLM 完成多步骤任务。规划器决定做什么，执行器去做，反思器评估结果，然后循环。\nQuantGPT 也用了 AI Agent。但我做了一个和主流方向相反的决策：\n我没有写一行 Agent 代码。\n全部 Agent 逻辑——规划、决策、迭代、反思——全部由 Claude 自己完成。我只做了一件事：给它一套好用的工具。8 个 MCP 工具，每个是无状态纯函数。没有 planner，没有 router，没有 reflection loop。\n这不是偷懒。这是战略选择。\n做 Agent 有多难 \u0026ldquo;做一个 Agent\u0026quot;听起来不难。调 API，写 prompt，加个循环。但那是 demo。\n复刻领域深度 Agent，难度比肩从零造 Claude Code Claude Code 看起来\u0026quot;就是调 API 加工具调用\u0026rdquo;，但它的工程深度远超表面：上下文工程（什么时候压缩历史、什么时候丢弃）、工具编排（优先级、冲突处理、失败重试）、安全边界（哪些操作需要确认）、自我纠错（识别错误路径后怎么回退）。\n要在量化研究领域做一个同等深度的 Agent，等于从零造一个\u0026quot;量化版 Claude Code\u0026quot;。这不是一个开源项目能承受的工程量。\nAgent 前沿变化极快 2024 年初的最佳实践（ReAct prompting + 固定工具链），到年底已经被 MCP + 原生工具调用取代。2025 年的共识（多 Agent 协作）正在被质疑——单 Agent + 好工具可能更有效。\n你今天花三个月做的 Agent 逻辑，三个月后可能因为模型能力提升而变成多余的代码。Planner？模型自己会规划了。Reflector？模型自己会反思了。Router？模型自己会选工具了。\n你在和模型能力的增长速度赛跑，而你跑不过它。\nAgent 层是同质化的 所有人用的 LLM 是同一批。Agent 框架本质上是对这些模型的包装。差异化不在 Agent 层，差异化在你喂给 Agent 的工具有多好。\n一个语义正确的表达式解析器、一个统计严谨的反过拟合检测系统——这些是几个月的领域积累，不是换一个 prompt 就能复制的。\nAgent 是通用的，工具是专有的。投资应该在专有层。\n小厂做 Agent，大厂一出手就没了 这是最残酷的现实：你做的 Agent，大厂随时可以做一个更好的，而且它做的成本比你低一个数量级。\n已经发生的事情：\nCursor / Windsurf 做了代码 Agent → Anthropic 发布 Claude Code，原生集成，免去中间层 Devin 做了自动编程 Agent → OpenAI 发布 Codex，直接内置到平台 第三方 ChatGPT 插件生态 → OpenAI 自己做了 GPTs，插件开发者的流量一夜归零 LangChain 做了工具编排框架 → 模型厂商原生支持 function calling / tool use，框架的核心价值被掏空 规律很清晰：模型厂商做 Agent 有结构性优势。它们拥有第一方模型访问权、训练数据反馈闭环、千亿级算力、数百人的工程团队。当大厂决定进入你的 Agent 赛道，你的差异化——prompt 工程 + 编排逻辑——对它们来说是可以在几周内复刻的。因为它们拥有模型本身。\n小厂和个人做 Agent，本质上是在大厂的射程范围内创业。你的 Agent 做得越好、市场验证越充分，大厂进入的动机就越强。这不是\u0026quot;可能\u0026quot;，而是\u0026quot;什么时候\u0026quot;的问题。\n但基建不一样。 模型厂商会做通用 Agent（代码助手、对话机器人、搜索代理），但它们不会做量化因子的表达式解析器，不会做 A 股市场的反过拟合检测系统，不会做 WQ BRAIN 的兼容性编译器。这些领域工具需要几个月甚至几年的垂直领域积累，大厂没有动机为每一个细分领域去做这件事。\nAgent 层你和大厂竞争。基建层你和领域问题竞争。后者是你能赢的战场。\n搭便车：Agent-Infra 的核心优势 做 Agent-Infra 最大的好处不是\u0026quot;省事\u0026quot;，而是你自动享受 Agent 能力上升带来的红利。\nLLM Agent（Claude / GPT / DeepSeek） │ └── 8 个 MCP 工具（基建层） ├── run_backtest 全市场分组回测 ├── score_factor 0-100 综合评分 ├── diagnose_factor 失败模式诊断 ├── run_anti_overfit 4 项反过拟合检验 ├── run_rolling_validation Walk-forward 验证 ├── validate_expression 语法校验（80+ 算子） ├── list_operators 算子文档 └── list_universes 股票池和基准 当 Claude 从 3.5 升级到 Opus 4 时，我没改一行代码，但 Agent 的研究质量明显提升了。当 Claude Code 新增了 skill 系统，我写了一个 /factor-mine skill，Agent 立刻获得结构化研究能力——还是没改基建代码。\n如果你自己做了 Agent，模型升级时你需要重写 Agent 逻辑来适配新能力。如果你做的是基建，模型升级时你什么都不用做。\n你站在一个上升的电梯上，电梯的动力来自全世界最大的 AI 实验室。\n可组合性：不绑定任何一个 Agent 如果明天 GPT-5 在工具调用上超过了 Claude 呢？零改动，直接切换。\n工具是无状态纯函数——接收参数，返回结果，不知道调用者是谁。MCP 协议本身就是模型无关的。\n如果你自己做了 Agent，Agent 逻辑绑定了特定模型的 prompt 格式、API 结构、上下文窗口假设。换模型 = 重写 Agent。做基建的人不需要做这个选择。\n可测试性：Agent 没法测，工具可以 Agent 的决策是随机的——同一个输入，不同运行可能不同输出。你没法对 Agent 行为写单元测试。\n但工具是确定性的。QuantGPT 有 74 个测试，覆盖 80+ 算子的数学正确性、截面/时序分组语义、反过拟合统计检验、WQ BRAIN 指标计算、API 边界守卫。\n无论 Agent 怎么调用这些工具，返回的结果都是正确的。 Agent 的决策质量取决于模型，数据的正确性取决于基建——后者是你能控制的。\n护城河在领域知识 做 Agent 框架的护城河几乎没有。LangChain 今天的用户明天可以迁移到 CrewAI，成本极低。\n做 Agent-Infra 的护城河在领域知识的编码密度：\n表达式解析器里编码了截面算子和时序算子的分组语义差异、80+ 算子的数学实现和边界处理、WQ BRAIN 兼容性的双模式编译、LLM 输入的安全约束。反过拟合系统里编码了 IC 稳定性统计检验、牛熊震荡子样本切分、安慰剂检验、信号半衰期拟合。\n这些不是 prompt engineering 能复制的。 每一个算子的分组语义、每一个统计检验的阈值、每一个安全限制的数值，背后都有具体的失败案例。\n换一个 Agent 框架很容易。换一套经过验证的领域工具链很难。\n治理天然内嵌 自己做 Agent 面临一个永恒的问题：怎么控制 Agent 的行为。\n主流方案是 prompt 级约束。问题是 LLM 可以无视 prompt。做基建的话，治理机制在代码层：\n# Agent 想直接调用回测函数？RuntimeError。 def _require_api_context(): if not getattr(_api_context, \u0026#39;active\u0026#39;, False): raise RuntimeError(\u0026#34;必须通过 API 调用\u0026#34;) # Agent 想生成超深嵌套的表达式？解析器拒绝。 MAX_DEPTH = 100 MAX_WINDOW = 500 MAX_EXPRESSION_LENGTH = 1000 Agent 只尊重会报错的规则。Harness 就是治理。 工具层定义了 Agent 能做什么、不能做什么，这比任何 prompt 都可靠。\n什么时候应该自己做 Agent 有场景确实需要自己做 Agent：\n极端确定性要求——医疗、法律、金融交易执行，不能容忍 LLM 的随机性 超高吞吐量——每秒几千请求，LLM 推理的延迟和成本不可接受 模型能力不足——目标用户只能用 GPT-3.5 级别的模型，需要外部脚手架来补偿 但如果你的场景是：任务需要灵活判断、中间步骤不可预见、有足够聪明的 LLM 可用——做基建比做 Agent 更聪明。\n一句话总结 不要和 LLM 的进化速度竞争。做它的基建，让它每一次变聪明都让你的系统变得更好。Agent 是消耗品——框架会过时，prompt 会失效，编排逻辑会被模型原生能力取代。Agent-Infra 是资产——领域知识不会过时，工具的正确性不会过时，统计检验的严谨性不会过时。做资产，不做消耗品。\n系列文章：Agent-Native 架构 · Harness 即治理 · Skill 编排 \u0026gt; Agent 循环链\n","permalink":"https://miasyster.github.io/posts/why-agent-infra-not-agent/","summary":"当所有人都在卷 Agent 框架的时候，我选择了另一条路：不做 Agent，做 Agent 的基建。不是因为做不了 Agent，而是因为 Agent 层是消耗品，基建层是资产。做资产，不做消耗品。","title":"为什么我做 Agent 基建，而不是做 Agent"},{"content":" 当前 AI Agent 框架热衷于构建复杂的循环链：规划器 → 执行器 → 反思器 → 再规划。我选择了反过来：工具是无状态纯函数，LLM 自己决定调用顺序。不是因为循环链不酷，而是因为它把决策权放错了地方。\n两种架构，一个根本分歧 在 上一篇 里，我解释了为什么没有用多智能体架构。那篇文章的结论是\u0026quot;用单 Agent + 状态机\u0026quot;。但这只回答了\u0026quot;几个 Agent\u0026quot;的问题，没有回答一个更重要的问题：Agent 的决策边界在哪里？\n目前主流的 Agent 框架（LangGraph、CrewAI、AutoGen）都在做同一件事：构建一条越来越复杂的决策管道。规划器决定做什么，执行器去做，反思器评估结果，然后回到规划器。管道本身是\u0026quot;聪明\u0026quot;的——它知道何时循环、何时退出、何时回溯。\n我做了一个相反的选择：管道是\u0026quot;笨\u0026quot;的，工具是\u0026quot;聪明\u0026quot;的。\n具体来说：QuantGPT 的 MCP server 暴露了 10 个独立工具（run_backtest、score_factor、diagnose_factor、run_anti_overfit 等），每个工具是无状态纯函数——接收参数，返回完整结果，不记录调用历史，不知道上一次调用了什么。\n\u0026ldquo;管道\u0026quot;在哪？不存在。LLM Agent（Claude）直接看到所有工具的描述，自己决定下一步调用什么。没有 planner、没有router、没有 reflection loop——这些全部由 LLM 自身的推理能力承担。\n这不是偷懒。这是架构选择。\n\u0026ldquo;聪明管道\u0026quot;的隐含假设 当你构建一个 Planner → Executor → Reflector 循环链时，你隐含地做了一个假设：你比 LLM 更知道决策应该怎么流转。\n这个假设在 2023 年是合理的。GPT-3.5 的推理能力有限，确实需要外部脚手架来引导它——告诉它\u0026quot;先规划再执行\u0026rdquo;、\u0026ldquo;执行完要反思\u0026rdquo;、\u0026ldquo;反思后要判断是否重试\u0026rdquo;。框架的价值在于弥补模型能力的不足。\n但到了 2025 年，这个假设越来越可疑。\nClaude、GPT-4o、DeepSeek-V3 已经具备了强大的工具选择和多步推理能力。当你给它 10 个工具和一个目标，它完全有能力自己规划调用顺序。它甚至能根据中间结果动态调整策略——这恰恰是循环链无法做到的，因为循环链的分支逻辑是你预先编码的。\n聪明管道的问题不是\u0026quot;它不工作\u0026rdquo;，而是\u0026quot;它固化了一个特定的决策流程，而这个流程很可能不是最优的\u0026quot;。\n举一个具体例子。在我的因子研究场景中，标准的 Agent 循环链会这样设计：\n生成因子 → 回测 → 评分 → 分数够高？ ├── 是 → 反过拟合检验 → 通过？ → 提交 └── 否 → 突变 → 回到生成 这看起来很合理。但实际研究中会出现循环链无法处理的情况：\nAgent 发现某个因子评分一般，但诊断结果显示只是窗口参数偏小，直接调参就行，不需要走完整的突变-重生成流程 Agent 发现两个独立因子各自 Sharpe 1.3，想直接尝试交叉组合，但循环链里没有这个分支 Agent 在跑反过拟合时发现 IC 在某个子样本上突然衰减，想临时回去跑一个诊断看是不是行业暴露问题 这些都是研究者的即兴判断，不是可以预编码的分支。循环链的每一个 if-else 都需要开发者预见到这个场景。预见不到的就走不通。\nMCP 工具设计的三条原则 既然不用循环链，工具本身的设计就变得至关重要。我遵循了三条原则：\n1. 无状态：每个工具调用独立完备 @mcp.tool() async def run_backtest(expression: str, universe: str = \u0026#34;hs300\u0026#34;, ...): # 不读取任何全局状态 # 不依赖\u0026#34;上一次调用的结果\u0026#34; # 返回完整的回测报告（指标 + 诊断 + 评分） ... 工具不知道自己是被\u0026quot;第一次\u0026quot;调用还是\u0026quot;迭代第 15 次\u0026quot;调用。它不关心上下文。这意味着 Agent 可以以任意顺序、任意频率调用任何工具，不会出现\u0026quot;状态不一致\u0026quot;的问题。\n对比循环链：如果 Executor 依赖 Planner 设置的全局变量，那 Agent 必须严格按照 Planner → Executor 的顺序调用。打破顺序就打破一致性。\n2. 完整返回：结果自包含，不需要后续查询 每个工具的返回值包含所有相关信息，Agent 不需要再调用另一个工具来\u0026quot;补充\u0026quot;结果。\nrun_backtest 返回的不只是 Sharpe 和 Returns——它同时返回分组收益、换手率、最大回撤、行业暴露、因子载荷。Agent 看到完整图景后自己判断下一步做什么。\n这和 Unix 哲学的\u0026quot;小而专\u0026quot;相反。小而专适合人类组合管道（cat | grep | sort），但 LLM 不擅长组合十次调用来拼凑一个完整结果——它擅长从一个完整结果中提取关键信息。工具设计要适配调用者的认知模式，不是设计者的审美偏好。\n3. 语义命名：工具名就是意图描述 run_backtest — 我想知道这个因子的表现 score_factor — 我想知道这个因子的综合评分 diagnose_factor — 我想知道这个因子为什么表现不好 run_anti_overfit — 我想知道这个因子是不是过拟合了 run_rolling_validation — 我想知道这个因子在不同时间段的稳定性 LLM 根据自然语言描述选择工具。工具名越接近意图描述，LLM 的选择准确率越高。我不需要 router 来分发请求——语义匹配就是最好的路由。\n\u0026ldquo;但你需要一个 planner 来\u0026hellip;\u0026rdquo; 反对意见通常是这样的：\u0026ldquo;没有 planner，Agent 怎么知道先做什么？\u0026rdquo;\n答案是：它本来就知道。\n给 Claude 一个目标\u0026quot;挖掘一个 Fitness \u0026gt; 1.0 的因子\u0026quot;和 10 个工具描述，它会自发地：\n先检查可用算子（list_operators） 设计一个因子表达式 验证语法（validate_expression） 回测（run_backtest） 评分（score_factor） 如果分数低，诊断问题（diagnose_factor） 根据诊断修改表达式，回到第 3 步 如果分数高，检测过拟合（run_anti_overfit） 通过后提交 没有人教它这个流程。它从工具的语义描述中自行推导出来的。\n更重要的是，它会根据中间结果偏离这个流程。如果诊断发现不是表达式问题而是股票池问题，它会切换股票池重跑，而不是继续在同一个池子里迭代。如果反过拟合检测发现 IC 衰减集中在 2022 年下半年，它会推测是市场结构变化导致的，主动缩短窗口重测。\n这种灵活性是你无法在循环链中预编码的。 你能写 if-else 处理 10 种情况，但第 11 种需要改代码。LLM 能处理任意多种情况，只要工具的能力覆盖到。\n这不是\u0026quot;无架构\u0026quot; 有人会说：\u0026ldquo;你只是把架构决策推给了 LLM，这不是好的工程实践。\u0026rdquo;\n不对。架构依然存在，只是位置不同。\n循环链架构的约束在管道层——管道定义了哪些步骤可以走、走到哪分叉、何时循环。Agent 的自由度被管道框住。\n我的架构的约束在工具层——工具定义了 Agent 能做什么、每个操作返回什么信息、操作之间没有隐式依赖。Agent 的自由度被能力集框住。\n类比操作系统：循环链是宏内核，所有决策逻辑编译在一起；MCP 工具集是微内核，只暴露系统调用，调度逻辑在用户态（LLM）。\n两种架构都有约束。区别在于：管道约束是流程约束（你必须按这个顺序走），工具约束是能力约束（你只能用这些操作）。\n流程约束容易过时——研究范式变了就得改管道。能力约束更稳定——只要操作的语义不变，Agent 可以自由组合出新流程。\n实际效果 这个架构在 QuantGPT 上跑了几个月，产出了 3 个正式提交 WorldQuant BRAIN 的因子（最佳 Fitness 1.26、Sharpe 1.77），全部 IS 检测通过。\n几个具体的观察：\nAgent 发明了我没预想过的研究路径。 例如它发现 ts_av_diff 和 rank(debt/enterprise_value) 各自表现一般，但主动尝试加法组合后 Fitness 从 0.7 跃升到 1.26。没有任何代码告诉它\u0026quot;试试组合\u0026quot;——它从 score_factor 的结果中自行推理出两个信号互补。\n调试从\u0026quot;追踪管道状态\u0026quot;变成\u0026quot;看 Agent 日志\u0026quot;。 Agent 的每次工具调用和返回结果都是独立的 JSON，按时间线排列就是完整的研究记录。不需要理解管道的内部状态机。\n工具更新不影响 Agent 行为。 我后来加了 wq_brain_batch_submit 工具，Agent 自动发现并开始使用，不需要改任何\u0026quot;管道逻辑\u0026quot;——因为没有管道逻辑可改。\n什么时候这个方案是错的 诚实说，有场景不该用这个方案：\n模型能力不足时。 如果你的 LLM 是 GPT-3.5 级别，它确实需要外部脚手架来引导决策。Skill 编排隐含依赖一个足够聪明的调用者。 确定性流程时。 如果你的流程完全没有分支判断（ETL pipeline、数据清洗），直接写代码比让 LLM 决定更可靠、更便宜。 高吞吐量时。 每秒处理几千个请求的场景，LLM 推理的延迟和成本不可接受。硬编码管道更合适。 但这些例外恰好说明了判断标准：当任务需要灵活判断、中间步骤不可预见、且你有一个足够聪明的调用者时，Skill 编排优于 Agent 循环链。\n一句话总结 不要用代码固化 LLM 已经具备的决策能力。给它好工具，让它自己决定怎么用。\n2023 年我们需要框架来弥补模型的不足。2025 年我们需要退后一步，把预编码的决策逻辑还给模型。\n聪明管道 + 笨工具是上一个时代的产物。笨管道 + 聪明工具才是 LLM-native 的架构。\n系列前篇：为什么我没有用多智能体架构做量化研究系统 · MCP 的问题不在协议层，在语义层\n","permalink":"https://miasyster.github.io/posts/skill-orchestration-over-agent-loops/","summary":"当前 AI Agent 框架热衷于构建复杂的循环链：规划器 → 执行器 → 反思器 → 再规划。我选择了反过来：工具是无状态纯函数，LLM 自己决定调用顺序。不是因为循环链不酷，而是因为它把决策权放错了地方。","title":"Skill 编排 \u003e Agent 循环链：为什么笨管道 + 聪明工具优于聪明管道 + 笨工具"},{"content":" QuantGPT 的核心是一个 870+ 行的手写递归下降解析器，支持 80+ 算子、截面/时序自动分组、双模式编译。不是因为不知道 eval() 更简单，而是因为 eval() 做不到的事情恰好是最重要的。\n为什么不能用 eval() 因子表达式看起来像数学公式：\n-1 * rank(ts_av_diff(close, 10)) + rank(debt / enterprise_value) 最直觉的实现：把 rank、ts_av_diff 定义为 Python 函数，然后 eval() 整个字符串。三行代码搞定。\n但这个系统的调用者是 LLM Agent。Agent 生成的表达式不可预测。eval() 意味着任意代码执行——不只是\u0026quot;安全隐患\u0026quot;，而是系统性地无法约束 Agent 的行为空间。你无法区分\u0026quot;合法的因子表达式\u0026quot;和\u0026quot;Agent 幻觉出的任意 Python\u0026quot;。\n即便忽略安全，eval() 也解决不了因子计算的核心难题：截面算子和时序算子的分组语义完全不同。\n截面 vs 时序：同一个括号，不同的语义 rank(close) — 按 trade_date 分组，在同一天所有股票中排名 ts_mean(close, 20) — 按 stock_code 分组，在同一只股票的历史中滑动平均 rank() 是截面算子：同一天的所有股票是一个截面，rank 在截面内计算。ts_mean() 是时序算子：同一只股票的历史价格是一条时序，均值沿时间轴滚动。\n两者的函数签名看起来一样（接收一列数据），但底层的 groupby 完全不同。eval() 无法从语法中推断出这个语义差异。手写解析器可以：\n# 解析器在识别到 rank() 时，自动注入截面分组 s.groupby(df[\u0026#39;trade_date\u0026#39;]).rank(pct=True) # 识别到 ts_mean() 时，自动注入时序分组 _apply_ts_op_per_stock(df, lambda g: g.rolling(window).mean()) 分组逻辑对表达式作者完全透明。 写 rank(close) 的人不需要知道底层是按日期分组的——解析器根据算子类型自动处理。这才是\u0026quot;编译\u0026quot;：把高层意图翻译成正确的低层操作。\n双模式编译 同一个表达式可以编译到两个目标：\nmode=\u0026quot;local\u0026quot;：开放全部 80+ 算子，包括 tanh、sigmoid、rsi、macd 等本地扩展算子。用于快速实验和自由探索。 mode=\u0026quot;wq\u0026quot;：只允许 WorldQuant BRAIN 兼容的算子子集。用于提交前校验——如果表达式包含 WQ 不支持的算子，编译阶段就报错，不用等到提交时被平台拒绝。 28 个 WQ-only 远程算子（vector_neut、ts_regression、bucket 等）在本地模式下注册为 stub——调用时抛出 RuntimeError，提示\u0026quot;此算子仅在 WQ BRAIN 平台上可用\u0026quot;。\n这让 Agent 可以自由探索（local 模式），然后在提交前切换到 wq 模式做合规检查。两个阶段用的是同一个解析器，只是编译目标不同。\n安全约束 LLM Agent 会生成各种边界情况。解析器硬编码了三道防线：\nMAX_WINDOW = 500 # 滚动窗口上限 MAX_DEPTH = 100 # 递归深度限制 MAX_EXPRESSION_LENGTH = 1000 # 表达式字符数上限 窗口上限：防止 ts_mean(close, 99999) 吃掉所有内存 递归深度：防止 rank(rank(rank(rank(...)))) 无限嵌套 表达式长度：防止 Agent 生成超长组合表达式 这些不是建议值，是运行时强制。超过限制直接抛异常，Agent 必须修改表达式。\n80+ 算子的分类 类别 数量 示例 一元算子 8 log, abs, sign, scale, sqrt 截面算子 4 rank, zscore, group_rank, group_zscore 时序算子 18+ ts_mean, ts_std, ts_corr, decay_linear, ts_av_diff 技术指标 8 rsi, macd, ema, sma, atr, obv, boll_* 条件/特殊 5 where, trade_when, clip, indneutralize, ternary WQ 远程 28 vector_neut, ts_regression, bucket, quantile 算术运算 11 +, -, *, /, ^, \u0026gt;, \u0026lt;, ==, !=, and, or 每个算子的分组语义（截面/时序/标量）在解析器中显式注册。不存在\u0026quot;通用算子\u0026quot;——每个算子必须声明它的计算域。\n为什么不用现成的解析框架 Python 生态有 lark、ply、pyparsing。为什么手写？\n语义动作与解析深度耦合。 解析器不只产出 AST——它在解析过程中直接生成 Pandas 计算图。lark 的 Transformer 做得到，但代码量不会更少，还多了一层抽象。 双模式切换需要解析时判断。 mode=\u0026quot;wq\u0026quot; 在遇到非兼容算子时要立即报错，不是解析完再检查。嵌入在递归下降逻辑里最自然。 错误信息要给 Agent 看。 \u0026ldquo;第 23 个字符处 \u0026rsquo;ts_regrssion\u0026rsquo; 不是合法算子，你是否想用 \u0026rsquo;ts_regression\u0026rsquo;？\u0026rdquo; 这种带纠错建议的错误信息，通用解析框架很难做到。 870 行代码听起来多，但它包含了解析、编译、安全检查、错误处理、80+ 算子的完整实现。这是系统中最稳定的模块——在 3 个因子正式提交 WorldQuant BRAIN（IS 全部通过）的过程中，解析器本身零 bug。\n一句话总结 解析器不是\u0026quot;输入处理\u0026quot;——它是类型系统、安全边界和编译目标的统一实现。当你的调用者是 LLM 时，解析器就是你的第一道也是最重要的防线。\n系列文章：API Guard Pattern · 让 AI 写的代码跑起来，但别让它跑出去 · Skill 编排 \u0026gt; Agent 循环链\n","permalink":"https://miasyster.github.io/posts/expression-parser-is-a-compiler/","summary":"QuantGPT 的核心是一个 870+ 行的手写递归下降解析器，支持 80+ 算子、截面/时序自动分组、双模式编译。不是因为不知道 eval() 更简单，而是因为 eval() 做不到的事情恰好是最重要的。","title":"表达式解析器是编译器，不是 eval()"},{"content":" QuantGPT 用 threading.local 实现了一个运行时守卫：所有回测调用必须经过 API 边界，直接调用函数会抛异常。不是因为函数有什么危险，而是因为没有边界的系统无法被审计。\n问题：LLM Agent 会走捷径 当你把一个 Python 项目暴露给 LLM Agent 时，Agent 倾向于做最高效的事：直接 import 你的函数，跳过 API 层。\n# Agent 很容易写出这种代码 from quantgpt.backtest import run_factor_backtest result = run_factor_backtest(\u0026#34;rank(close)\u0026#34;, \u0026#34;hs300\u0026#34;) 这段代码能跑通。回测引擎不关心调用者是谁。但这制造了一个不可审计的调用路径——没有任务 ID、没有日志、没有速率限制、没有权限检查。在一个 Agent 可以自治运行几十轮迭代的系统里，失去审计就等于失去控制。\n文档约定（\u0026ldquo;请通过 API 调用\u0026rdquo;）对 LLM Agent 没有约束力。Agent 看到 import 路径更短，就会用。\n解决方案：运行时强制 _api_context = threading.local() def _require_api_context(): if not getattr(_api_context, \u0026#39;active\u0026#39;, False): raise RuntimeError( \u0026#34;run_factor_backtest() must be called within api_context()\u0026#34; ) @contextmanager def api_context(): _api_context.active = True try: yield finally: _api_context.active = False run_factor_backtest() 的第一行就是 _require_api_context()。没有被 api_context() 包裹的调用会立即抛 RuntimeError。\n线程隔离用 threading.local()——不同线程的上下文互不影响，进程池里的 worker 各自独立。\n合法调用点 系统中只有几个地方包裹了 api_context()：\n# task_executor.py — 所有回测任务的入口 def _run_backtest_in_process(expression, universe, ...): enable_api_context() try: return run_factor_backtest(expression, universe, ...) finally: disable_api_context() API 路由、MCP 工具、迭代引擎——全部通过 task_executor 提交任务，task_executor 负责启用上下文。调用链变成：\nHTTP/MCP 请求 → task_executor → enable_api_context → run_factor_backtest 新增一个调用点？必须显式包裹 api_context()。忘了包裹？运行时炸。不存在\u0026quot;偷偷绕过\u0026quot;的可能。\n测试怎么办 测试也要走这个守卫。通过 pytest autouse fixture 全局启用：\n@pytest.fixture(autouse=True) def _enable_api_context(): with api_context(): yield 所有测试自动在 api_context() 内运行。测试覆盖的是真实的调用路径，不是一个特殊的\u0026quot;测试模式\u0026quot;。\n为什么不用装饰器/中间件 常见的替代方案：\n装饰器：在函数定义处标记，但调用者无感知，不改变调用习惯 中间件：只保护 HTTP 层，不保护 Python 内部调用 文档约定：对人有效，对 Agent 无效 threading.local 守卫的特点是调用者必须主动配合——你必须先进入 api_context() 才能调用。这把\u0026quot;约定\u0026quot;变成了\u0026quot;约束\u0026quot;。\n更深层的原则 这个模式解决的不是安全问题（回测函数本身无害），而是边界问题。\n在一个 LLM Agent 可以自由调用任意 Python 函数的环境中，如果没有运行时强制的边界，系统会退化成一个大泥球——所有调用路径都是合法的，审计日志捕获不到直接调用，你无法区分\u0026quot;通过 API 发起的研究任务\u0026quot;和\u0026quot;Agent 随手跑了一个回测\u0026quot;。\n边界不是限制自由，而是让自由变得可追踪。\n代码约束 \u0026gt; 文档约束。运行时强制 \u0026gt; 静态分析。对 LLM Agent 尤其如此——它们只尊重会报错的规则。\n系列文章：AI as Operator, Kernel as Law · MCP 的问题不在协议层，在语义层 · Skill 编排 \u0026gt; Agent 循环链\n","permalink":"https://miasyster.github.io/posts/api-guard-pattern/","summary":"QuantGPT 用 threading.local 实现了一个运行时守卫：所有回测调用必须经过 API 边界，直接调用函数会抛异常。不是因为函数有什么危险，而是因为没有边界的系统无法被审计。","title":"API Guard Pattern：为什么直接调用函数是被禁止的"},{"content":" 大多数回测系统把反过拟合当作可选的附加检查——跑完回测再测一下。QuantGPT 把它内建到评分系统和进化引擎里：反过拟合结果直接影响因子得分，进化引擎读反过拟合指标决定下一步策略。不验证稳健性的因子，连参与迭代的资格都没有。\n\u0026ldquo;跑完回测再测一下\u0026quot;的问题 标准流程：生成因子 → 回测 → 看 Sharpe → 满意就用。反过拟合检查？可选。觉得有必要再跑。\n这个流程有一个结构性缺陷：反过拟合检查与决策解耦了。 你可以看到 Sharpe 2.5 的因子，心里已经决定用了，然后反过拟合测试告诉你\u0026quot;可能过拟合\u0026rdquo;——但你已经有了认知偏差，倾向于找理由解释掉这个结果。\n更严重的问题：当 LLM Agent 自治迭代时，如果反过拟合只是一个可选步骤，Agent 会倾向于跳过它——因为跳过能更快地进入下一轮迭代。Agent 的目标是\u0026quot;找到高分因子\u0026quot;，不是\u0026quot;找到稳健的高分因子\u0026quot;，除非你在架构层面强制它。\n四层反过拟合检验 QuantGPT 的反过拟合不是单一测试，而是四项独立检验的组合：\n1. IC 稳定性 按年度计算 Spearman IC（因子值与未来收益率的秩相关），要求：\n正 IC 占比 ≥ 55% |平均 IC| ≥ 0.02 不存在年度 IC 符号反转 一个因子如果在 2020 年 IC 为正、2021 年 IC 为负，说明它捕捉的不是稳定的 alpha 信号，而是特定市场环境下的偶然关联。\n2. 子样本压力测试 将数据按市场状态（牛市/熊市/震荡）和波动率（高/低）切分为多个子样本，分别计算 IC。通过条件：60% 以上子样本的 IC 符号与整体一致。\n这直接回答\u0026quot;这个因子是不是只在牛市有效\u0026quot;的问题。\n3. 安慰剂检验 生成 20 组时间序列的随机排列，计算各自的 IC。真实因子的 IC 必须超过随机排列 IC 的 95 百分位。同时检测时间偏移后的 IC 衰减——如果偏移一天后 IC 没有显著下降，说明信号可能是虚假的。\n4. 半衰期估计 在 1、2、5、10、20、40 天的前瞻期上计算 IC，拟合指数衰减曲线。半衰期 \u0026gt; 5 天才算通过。\n半衰期太短意味着因子的预测力在 1-2 天内就耗散了——对于日频换仓策略可能够用，但对于 WorldQuant BRAIN 的评估框架（更关注中期稳定性）不够。\n反过拟合如何进入评分系统 QuantGPT 的因子评分是 6 个维度的加权组合：\n总分 = IC_Mean(15%) + IC_IR(15%) + Stability(15%) + AntiOverfit(15%) + GroupBT(15%) + WQ_Alignment(25%) 反过拟合占 15% 权重。四项测试通过 3 项以上得满分，2 项为 60 分，1 项为 30 分，0 项为 0 分。\n关键设计：如果 CAGR 或 Sharpe 为负，无论其他维度多高，最终评级不超过 C（≤ 59.9）。 这是硬性上限，不是柔性惩罚。\nWQ 对齐度占 25%——其中包含 Sharpe、Fitness、Turnover 的合规检查。这意味着一个因子要拿到 A 级评分，必须同时通过反过拟合检验和 WQ BRAIN 模拟检验。两者缺一不可。\n反过拟合如何驱动进化方向 这是更重要的部分。反过拟合不只是\u0026quot;分数的一个维度\u0026quot;，它直接影响进化引擎的策略选择。\n进化引擎有一个三阶段自适应循环：\n轨迹分析：评估历史迭代的质量指标——得分方差（探索多样性）、趋势斜率（收敛速度）、连续下降次数 策略选择：根据轨迹特征选择 4 种策略之一 执行：按选定策略生成候选因子 策略选择的 7 条规则中，反过拟合是隐含的信号源：\nEXPLOIT（利用）：高分 + 低方差 → 精调当前最优。前提是当前最优的分数来自包含反过拟合检验的完整评估 EXPLORE（探索）：低分 + 早期阶段 → 尝试新方向。一个因子如果 Sharpe 高但反过拟合不通过，总分仍然低，会触发 EXPLORE 而不是 EXPLOIT RECOMBINE（重组）：连续 2+ 轮下降 → 从历史高分因子中交叉。交叉的父本是按总分排序的，反过拟合通过的因子自然排名更高 SIMPLIFY（简化）：嵌套深度 \u0026gt; 8 → 降低复杂度。过度拟合往往来自过度复杂的表达式 评估环节的一个细节：每个候选因子在迭代中只跑 2/4 项反过拟合测试（IC 稳定性 + 半衰期），而不是全部 4 项。这是速度和精度的权衡——快速筛选阶段用低成本检测过滤明显的过拟合，最终评估阶段才跑完整的 4 项。\n为什么 Walk-Forward 还不够 很多系统只做 Walk-Forward 验证：滚动窗口，训练集和测试集分离，看样本外表现。这比不做要好，但有盲点。\nWalk-Forward 验证本质是时间维度的泛化测试。它告诉你\u0026quot;这个因子在未来一段时间还有效吗\u0026quot;，但不告诉你\u0026quot;这个因子在不同市场状态下是否稳健\u0026quot;——后者需要子样本压力测试。它也不告诉你\u0026quot;这个因子是否比随机信号更好\u0026quot;——后者需要安慰剂检验。\nQuantGPT 把 Walk-Forward 作为第二层验证，在反过拟合四项测试之上叠加。滚动窗口的每个测试段可以选择性地跑完整反过拟合：\n窗口得分 = Test_IC(30%) + Test_IR(25%) + IC_Stability(20%) + AntiOverfit(15%) + Sharpe(10%) 反过拟合在窗口评分中占 15%，IC 稳定性另占 20%。两者合计 35%，超过了任何单一指标的权重。\n实际效果 这个架构在 QuantGPT 上产出了 3 个正式提交 WorldQuant BRAIN 的因子（最佳 Fitness 1.26、Sharpe 1.77），全部通过 IS 检测。\n一个关键数据：Agent 在迭代过程中淘汰了大量 Sharpe 高但反过拟合不通过的因子。没有这个过滤器，Agent 会倾向于收敛到高 Sharpe + 高过拟合风险的局部最优——因为 Sharpe 是最容易优化的指标。\n反过拟合不是\u0026quot;跑完回测再检查一下\u0026quot;。它是评分的一部分，是迭代方向的信号源，是淘汰机制的核心。把它当插件，你得到的是\u0026quot;高分因子\u0026quot;。把它当架构，你得到的是\u0026quot;稳健的高分因子\u0026quot;。\n一句话总结 反过拟合检验不应该是你最后做的事。它应该内嵌在你的评分系统和迭代引擎里，让过拟合的因子连参与进化的资格都没有。\n系列文章：表达式解析器是编译器，不是 eval() · API Guard Pattern · 终局思维：先想清楚怎么被审计\n","permalink":"https://miasyster.github.io/posts/anti-overfit-is-architecture/","summary":"大多数回测系统把反过拟合当作可选的附加检查——跑完回测再测一下。QuantGPT 把它内建到评分系统和进化引擎里：反过拟合结果直接影响因子得分，进化引擎读反过拟合指标决定下一步策略。不验证稳健性的因子，连参与迭代的资格都没有。","title":"反过拟合是架构，不是插件"},{"content":" AI 生成的代码必须被执行，否则它就只是文本。但执行意味着风险。我没有选择容器隔离，也没有用 RestrictedPython，而是设计了一个三层防御：先在编译期用 AST 拒绝危险结构，再在运行时替换整个 builtins，最后用操作系统级资源限制兜底。三层各解决不同类别的风险，重叠但不冗余。\n一个无法回避的矛盾 AI 编排系统的核心价值在于：AI 生成代码，系统执行代码，根据执行结果迭代优化。如果生成的代码不能被执行，整个循环就断了。\n但执行 AI 生成的代码，和执行人类工程师写的代码，风险结构完全不同。人类工程师理解代码的副作用——他知道 import os; os.system('rm -rf /') 意味着什么。AI 没有这层认知。它的目标是\u0026quot;完成任务\u0026quot;，如果直连数据库能更快地拿到数据，它就会尝试直连。不是因为恶意，是因为它不区分\u0026quot;合法手段\u0026quot;和\u0026quot;越权手段\u0026quot;。\n所以问题不是\u0026quot;要不要沙箱\u0026quot;，而是\u0026quot;沙箱应该防什么，在哪层防\u0026quot;。\n问题的结构 把\u0026quot;AI 代码执行的风险\u0026quot;拆开看，实际上是三类不同的问题：\n第一类是结构性危险——代码里包含了不应该出现的语法结构。import subprocess、eval()、__import__()，这些是在代码被解析时就能识别的模式。它们的危险性不依赖运行时上下文。\n第二类是运行时越权——代码本身的语法没问题，但通过合法的 builtins 做了不该做的事。比如 open('/etc/passwd')，语法合法，但语义越权。或者通过 getattr() 链式访问 dunder 属性逃逸出沙箱。\n第三类是资源滥用——代码逻辑正确、权限合规，但消耗的资源不可接受。无限循环、分配 10GB 内存的列表、CPU 跑满 30 分钟。这不是安全问题，是资源管理问题。\n三类问题需要三层防御。试图用一层解决所有问题，要么过于宽松（漏掉某类风险），要么过于严格（正常代码也跑不了）。\n我考虑过的方案 方案 A：容器隔离 Docker 容器或 WebAssembly 沙箱。每次执行启动一个隔离环境，代码在里面随便跑，跑完销毁。\n这在多租户 SaaS 场景下是标准做法。Replit、CodeSandbox、各种在线 IDE 都用这种方案。安全性最高——操作系统级隔离，代码无论做什么都影响不了宿主。\n为什么在 AI 迭代循环里不适用：延迟。AI 编排的核心循环是\u0026quot;生成 → 执行 → 评估 → 迭代\u0026quot;，一次研究任务可能跑 10-20 轮迭代。每轮启动一个容器，加载 pandas、numpy，初始化数据上下文，执行代码，提取结果，销毁容器。冷启动开销在 2-5 秒，乘以 20 轮就是额外的 40-100 秒。对于一个需要快速迭代的研究系统，这个延迟不可接受。\n还有一个更实际的问题：数据传递。容器内的代码需要访问行情数据，但数据不能复制进容器（太大），也不应该让容器直连数据库（违反架构原则）。需要一个序列化→传输→反序列化的通道，这本身就引入了复杂性和性能损耗。\n方案 B：RestrictedPython Python 社区有一个成熟的方案叫 RestrictedPython。它在字节码层面重写 Python 的编译过程，把所有属性访问、函数调用都替换成可拦截的代理函数。安全性介于\u0026quot;裸 exec\u0026quot;和\u0026quot;容器\u0026quot;之间。\n为什么没采用：RestrictedPython 的设计目标是\u0026quot;在不信任的多租户环境里运行用户代码\u0026quot;——Zope/Plone CMS 时代的产物。它的安全模型非常严格，严格到 pandas 和 numpy 的很多操作都会被拦截。df.groupby() 触发属性访问拦截，np.array() 的内部 C 扩展调用绕过了 Python 层的限制。要让 RestrictedPython 和数据科学库共存，需要大量的白名单配置和 monkey-patching。\n这本质上是一个适用场景不匹配的问题。RestrictedPython 假设代码来自不可信的外部用户。我的场景是代码来自受控的 AI 模型——风险存在但可预估，不需要字节码级的全面拦截，只需要拒绝已知的危险模式并限制资源消耗。\n方案 C：三层防御——AST 扫描 + 运行时白名单 + 资源限制（我的选择） 设计哲学一句话：每层只负责一类风险，层之间正交，不试图用一层解决所有问题。\n三层的具体设计 第一层：AST 静态扫描。在代码执行之前，把源代码解析成抽象语法树，遍历每个节点，检查是否包含禁止的结构。\n禁止的 import：os, sys, subprocess, socket, shutil, ctypes, importlib... 禁止的调用：eval(), exec(), compile(), __import__(), open(), globals()... 禁止的属性：__subclasses__, __bases__, __globals__, __code__ AST 扫描的关键特性是确定性——同一段代码，扫描结果永远相同。不依赖运行时状态，不受输入数据影响。如果一段代码包含 import os，无论变量值是什么，AST 扫描都会拒绝它。\n这层解决的是第一类风险：结构性危险。它是一个编译期的\u0026quot;预飞检查\u0026quot;——飞机还没起飞就发现发动机有问题，比空中发现便宜得多。\n第二层：运行时 builtins 替换。不是在 Python 默认的 builtins 上做黑名单过滤，而是整个替换掉。\n默认的 Python builtins 有 150+ 个函数和类。我的白名单只保留 53 个：数学运算（abs, min, max, sum, round）、类型创建（int, float, str, dict, list）、迭代（enumerate, filter, map, zip, range）、安全反射（isinstance, len, type）。\n关键排除项：open()、getattr()、setattr()、delattr()、__import__()。这些是 Python 沙箱逃逸的经典路径——通过 getattr 链式访问 dunder 属性，可以从任何对象一路爬到 os.system。\n同时，安全的科学计算库通过预注入的方式提供。代码执行时，全局命名空间里已经有 pd（pandas）和 np（numpy），不需要 import。这避免了\u0026quot;为了让 AI 用 pandas 而不得不开放 import 权限\u0026quot;的两难。\n第三层：操作系统级资源限制。用 POSIX 的 resource 模块设置硬上限：\n内存：2048 MB（RLIMIT_AS） CPU：120 秒（RLIMIT_CPU） 墙钟：300 秒（monotonic clock） 输出：50 MB 内存限制和 CPU 限制是操作系统内核强制执行的。代码分配超过 2GB 内存，内核直接杀进程——不是 Python 层面的 MemoryError，是 SIGKILL。这保证了即使前两层都被绕过，资源滥用也不会影响宿主。\n墙钟超时用 time.monotonic() 而不是系统时钟，因为 monotonic 不受 NTP 校时影响，计时更可靠。\n关键判断点 做出这个设计的核心判断是：代码来源是受控的 AI 模型，不是不可信的外部用户。\n这个判断改变了安全模型的重心。面对不可信代码，你需要假设攻击者会主动寻找逃逸路径——字节码注入、C 扩展漏洞、竞态条件。面对 AI 生成的代码，主要风险是\u0026quot;无意越权\u0026quot;而非\u0026quot;刻意攻击\u0026quot;。AI 不会故意构造 \u0026quot;\u0026quot;.__class__.__bases__[0].__subclasses__() 来逃逸沙箱，但它可能会尝试 import os 来读取文件，因为那是它在训练数据里见过的常见操作。\n这意味着防御的重心应该放在\u0026quot;拒绝已知的危险模式\u0026quot;（AST 扫描）和\u0026quot;限制可用工具集\u0026quot;（白名单），而不是\u0026quot;防御未知的逃逸向量\u0026quot;（容器隔离）。前者更轻量、更快、对正常操作的干扰更小。\n代码里有一行注释说明了这个设计定位：\u0026ldquo;Uses AST scanning + restricted globals to sandbox exec() calls. In production this should be replaced with container-based isolation for untrusted input.\u0026rdquo; 承认局限性，但明确当前场景下的合理性。\n这个判断也来自一个跨领域的类比：机场安检。机场安检不检查每个乘客的每个细胞——它有金属探测器（对应 AST 扫描）、禁止携带物品清单（对应 builtins 白名单）、容量限制（对应资源限制）。三层各有盲区，但组合起来覆盖了绝大多数实际威胁。而如果你要把每个乘客关进隔离舱再运输（容器隔离），安全性确实更高，但航班永远不会准点。\n实施效果 三层防御跑了几个月，处理了上千次 AI 生成的代码执行。AST 扫描拒绝率约 5%——主要是 AI 尝试 import 系统模块。运行时白名单拦截了零次越权——因为 AST 层已经过滤掉了绝大多数危险代码，白名单是兜底。资源限制触发了几十次——主要是 AI 生成的代码包含低效循环，超过了 CPU 时间限制。\n每次执行的沙箱开销在毫秒级（AST 解析 + globals 构建），和容器方案的秒级冷启动相比，快了三个数量级。\n这个决策教会我什么 我从这次实践中提炼了一条设计原则：\n安全防御应该按风险类别分层，不应该试图用一层解决所有问题。每层只需要解决它擅长的那类风险，层之间的重叠是特性不是缺陷。\n这条原则的适用范围不限于代码沙箱。网络安全的\u0026quot;纵深防御\u0026quot;是同一个思路——防火墙、入侵检测、应用层过滤各管一层。数据库安全也是——连接加密、SQL 注入过滤、行级权限各管一层。\n关键认知是：每增加一层防御，投入产出比是递减的。第一层（AST 扫描）拦截了 95% 的风险，成本极低。第二层（白名单）拦截了 4% 的风险，成本中等。第三层（资源限制）拦截了 1% 的风险，成本最高。如果还要加第四层（容器隔离）来拦截最后 0.1%，成本可能超过前三层之和。\n设计安全系统时，先问：剩余风险值多少钱？如果答案是\u0026quot;不值得再加一层\u0026quot;，那当前的防御就够了。\n系列第五篇。上一篇：终局思维：先想清楚怎么被审计，再决定怎么运行。第三篇：AI as Operator, Kernel as Law。第二篇：MCP 的问题不在协议层。第一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/sandbox-defense-in-depth/","summary":"AI 生成的代码必须被执行，否则它就只是文本。但执行意味着风险。我没有选择容器隔离，也没有用 RestrictedPython，而是设计了一个三层防御：先在编译期用 AST 拒绝危险结构，再在运行时替换整个 builtins，最后用操作系统级资源限制兜底。三层各解决不同类别的风险，重叠但不冗余。","title":"让 AI 写的代码跑起来，但别让它跑出去"},{"content":" LangChain、LangGraph、CrewAI、PydanticAI——AI 编排框架从不缺选择。我全部评估后决定自研。不是 NIH 心态，而是当你需要按失败模式选变异策略、按阶段路由不同模型和温度、在轨迹分析的基础上自适应进化时，通用框架给你的抽象层反而变成了需要绕过的障碍。\n框架多到让你不好意思自己写 2024-2025 年的 AI 编排生态，框架密度大概是整个软件工程史上最高的。LangChain 有最大的社区，LangGraph 提供图状态机，CrewAI 做多智能体协作，PydanticAI 把结构化输出做到了极致。\n一个理性的工程决策应该是：评估这些框架，选一个最接近需求的，在它的基础上扩展。自己从零写编排引擎，在 2025 年看起来像是重复造轮子。\n但我的场景有一个关键特征：编排的目标不是\u0026quot;完成一个任务\u0026quot;，而是\u0026quot;通过迭代搜索找到最优解\u0026quot;。AI 不是在执行一条预设的流程链，而是在一个搜索空间里做进化优化——生成代码、执行、评估、根据评估结果决定下一步是精炼、探索、重组还是简化。\n这个区别决定了一切。\n问题的结构 把\u0026quot;AI 编排\u0026quot;拆开看，它需要回答四个层面的问题：\n第一层：状态管理。当前在哪个阶段？可以去哪个阶段？什么情况下应该回退？\n第二层：模型路由。不同阶段用什么模型？什么温度？如果模型调用失败，fallback 到哪里？\n第三层：迭代策略。连续 3 轮指标下降，是继续精炼还是换方向？什么时候尝试重组之前成功的片段？什么时候简化复杂度？\n第四层：安全机制。指标断崖式下跌怎么办？生成了重复代码怎么办？跑了太多轮还没收敛怎么办？\n通用框架通常只覆盖第一层和第二层的基础部分。第三层和第四层——进化策略、灾难回滚、反重复检测——这些和业务场景深度耦合，没有框架会替你做。\n我评估过的框架 LangChain LangChain 的核心价值是生态集成——它为几乎所有 LLM 提供商、向量数据库、文档加载器提供了统一接口。如果你的任务是\u0026quot;从文档库里检索信息然后生成回答\u0026quot;（RAG），LangChain 是最合理的选择。\n它在我的场景下的问题：抽象层太多。一次 LLM 调用经过 Chain → LLM → Prompt Template → Output Parser 四层抽象。当我需要在 generate 阶段用 deepseek-reasoner（温度 0.8）而 fix 阶段用 deepseek-chat（温度 0.1）时，我需要绕过 LangChain 的 LLM 抽象层来注入阶段感知的路由逻辑。绕过一个框架的抽象层，比不用这个框架更复杂。\n另一个实际问题：debug。当 AI 生成的因子表达式执行失败、评估指标为空、触发了回滚机制时，我需要知道是哪一步出了问题。在 LangChain 的调用链里，异常会被层层包装，真正的错误信息藏在三层 traceback 之下。在我自己的引擎里，每个状态转换都是显式的 if/elif，异常发生在哪里一目了然。\nLangGraph LangGraph 比 LangChain 更接近我的需求——它用图结构管理状态转换，支持循环和条件分支。如果把我的 FSM 画成图，LangGraph 理论上能表达。\n但 LangGraph 的图状态机和我需要的有本质区别。LangGraph 的节点是\u0026quot;执行函数\u0026quot;，边是\u0026quot;转换条件\u0026quot;。状态管理和执行逻辑混在一起——一个节点既定义了\u0026quot;我在哪个阶段\u0026quot;又定义了\u0026quot;这个阶段做什么\u0026quot;。\n我的设计刻意把两者分开。状态机是纯逻辑的——它只回答\u0026quot;从 A 能不能到 B\u0026quot;，不做任何 I/O。一个 frozenset 字典，7 行代码：\nINIT → {PLAN, FAIL} PLAN → {GENERATE_CODE, FAIL} GENERATE → {EXECUTE, FAIL} EXECUTE → {EVALUATE, ITERATE, FAIL} EVALUATE → {ITERATE, FINISH, FAIL} ITERATE → {PLAN, GENERATE_CODE, EXECUTE, FAIL} FINISH → {} FAIL → {} 这个状态机没有副作用、没有依赖、可以独立测试。编排循环在外层调用 can_transition() 做合法性验证，然后自己负责执行。状态逻辑和执行逻辑解耦，意味着我可以替换执行逻辑而不动状态定义，或者在测试里用 mock 执行来验证状态流转。\nLangGraph 的设计没法做到这种分离。它的图本身就是执行流，不是纯状态定义。对于简单的线性流程这无所谓，但对于一个需要频繁回退、跳转、回滚的迭代循环，纯状态机更清晰。\nCrewAI CrewAI 是多智能体协作框架——多个 Agent 各有角色，通过消息传递协作完成任务。\n场景不匹配。我的系统是单 Agent 多阶段，不是多 Agent。在系列第一篇已经详细讨论过为什么不用多智能体架构——核心原因是单 Agent + FSM 的状态转换是确定性的，可回溯可审计，而多 Agent 的消息传递是不确定的，无法保证行为链的可重现性。\nPydanticAI PydanticAI 把结构化输出做到了极致——通过 Pydantic 模型定义 LLM 的输出格式，自动校验和重试。\n它在输出校验这个单一维度上确实比我自研的方案更优雅。但它不覆盖我需要的其他维度：按阶段路由模型、进化策略选择、失败模式检测。如果只为了结构化输出引入 PydanticAI，我的模型路由、变异引擎、轨迹分析全部要重写以适配它的接口。一个框架解决了 10% 的问题，但要求 60% 的系统重构来适配，这个交易不值。\n自研引擎的三个核心设计 设计一：阶段感知的多模型路由 不同的编排阶段对 LLM 的要求不同。规划阶段需要高创造性（高温度），代码生成阶段需要多样性（中高温度），修复阶段需要精确性（低温度）。\n我的路由器用三级 fallback 策略：\n查找顺序：task_type:phase → phase → default 例：factor_calc:generate → generate → 兜底配置 实际配置： plan: deepseek-chat, 温度 0.7（创造性规划） generate: deepseek-reasoner, 温度 0.8（多样性生成） iterate: deepseek-reasoner, 温度 0.9（探索性迭代） fix: deepseek-chat, 温度 0.1（精确修复） 三级 fallback 意味着：默认情况下所有任务类型共享同一套阶段路由，但可以为特定任务类型（比如因子研究 vs 策略回测）配置不同的模型和参数，不需要改代码。\n没有一个主流框架原生支持这种粒度的路由。LangChain 有 Router Chain，但它的路由是基于输入内容的语义匹配，不是基于编排阶段的确定性路由。\n设计二：自适应进化策略 这是整个引擎的核心竞争力，也是通用框架完全不覆盖的领域。\n编排循环的迭代不是简单地\u0026quot;让 AI 改进上一轮的代码\u0026quot;。它是一个有方向性的搜索过程，方向由四种进化策略决定：\nEXPLOIT（精炼）：当前分数已经不错，在当前方向上微调。适用于局部优化。\nEXPLORE（探索）：当前方向走不通，需要完全换一个思路。适用于跳出局部最优。\nRECOMBINE（重组）：把历史迭代中成功的片段组合在一起。适用于收敛后期。\nSIMPLIFY（简化）：复杂度过高导致过拟合，减少嵌套操作保留核心逻辑。\n策略选择不是随机的，是基于轨迹分析的自适应决策：\n高分 + 低多样性 → EXPLOIT（继续精炼，别乱跳） 连续下降 2+ 轮 + 足够迭代 → RECOMBINE（从历史成功中恢复） 低分 + 早期迭代 → EXPLORE（方向不对，换思路） 高多样性 + 低收敛 → EXPLORE（散了，需要聚焦） 中等分 + 高稳定性 → EXPLOIT（稳定推进） 分差大 + 有迭代余量 → RECOMBINE（取长补短） 轨迹分析基于四个维度：探索多样性（分数方差）、收敛速率（分数的线性回归斜率）、稳定性（近几轮分数的一致性）、语义多样性（基于 AST 的代码相似度）。\n每种策略还对应不同的变异类型。EXPLOIT 策略下，变异引擎会根据失败模式选择具体的变异操作：\n信号方向错误（IC \u0026lt; -0.01）→ 翻转信号方向 预测力为零（|IC| \u0026lt; 0.01）→ 更换算子 不一致（|ICIR| \u0026lt; 0.3）→ 加正则化 过复杂 → 简化结构 这套系统的本质是把进化算法的思想（选择、变异、交叉、适应度评估）嵌入到了 AI 编排循环里。AI 负责生成和变异，引擎负责选择方向和评估适应度。\n设计三：灾难回滚和安全边界 迭代搜索有一个风险：AI 可能在一轮迭代中严重退化，产出比之前差得多的结果。\n引擎用三层安全机制应对：\n第一层：最优版本追踪。每轮评估后，如果当前分数超过历史最优，更新最优记录（版本号、分数、代码、指标）。如果分数下降，递增连续下降计数器。\n第二层：灾难回滚。如果当前分数比历史最优下降超过 30%，判定为灾难性衰退，自动回滚到最优版本的代码，重置下降计数器，然后从最优版本重新开始迭代。这保证了即使 AI 走错方向，系统不会丢失已经找到的最优解。\n第三层：早停。连续 3 轮下降且已超过 5 轮迭代且最优分数 \u0026gt; 0.15，判定为收敛，停止迭代，返回最优版本的结果。这避免了在已经收敛的区域浪费计算资源。\n另外，每次代码生成后都做反重复检测——先文本规范化精确匹配，再用 AST 语义相似度检测结构性重复（阈值 0.85）。如果检测到重复，用显式的提示要求 AI 生成结构性不同的代码。\n关键判断点 做出自研决策的转折点是一个认知：通用框架解决的是\u0026quot;如何调用 LLM\u0026quot;的问题，我需要解决的是\u0026quot;如何搜索最优解\u0026quot;的问题。\n这两个问题的复杂度重心不同。\u0026ldquo;如何调用 LLM\u0026quot;的复杂度在连接层——适配不同 API、处理重试和限流、管理上下文窗口。框架在这里有明显优势。\n\u0026ldquo;如何搜索最优解\u0026quot;的复杂度在决策层——根据历史轨迹选策略、根据失败模式选变异、根据分数趋势决定回退还是继续。这些决策逻辑和业务场景深度耦合，不可能被通用框架抽象掉。\n如果我用了 LangGraph，我会得到一个图状态机来管理 INIT → PLAN → GENERATE → EXECUTE → EVALUATE → ITERATE 的流转。但进化策略、变异引擎、灾难回滚、轨迹分析——这些全部要自己写，而且要绕过 LangGraph 的节点抽象来注入。最终代码量可能比纯自研还多，因为多了一层适配。\n这和操作系统设计里的一个经典取舍一样：微内核 vs 宏内核。微内核（框架）提供最小的核心机制，功能通过插件扩展。宏内核（自研）把核心功能全放在内核里。当你的\u0026quot;插件\u0026quot;比\u0026quot;核心\u0026quot;更复杂时，微内核的架构优势就消失了——你只是在内核和插件之间增加了通信开销。\n实施效果 自研引擎跑了几个月，处理了上百个研究任务，每个任务 5-20 轮迭代。进化策略的自适应选择让因子研究的收敛速度比固定策略快了约 30%——EXPLOIT 在高分区域精炼细节，EXPLORE 在低分区域跳出局部最优，RECOMBINE 在停滞时从历史成功中恢复。\n灾难回滚触发了十几次，每次都成功保住了历史最优解。没有灾难回滚的话，这些任务会在 AI 的一次失误迭代后丢失之前所有的优化成果。\n整个引擎的代码量约 2000 行（状态机 50 行、编排循环 500 行、进化策略 200 行、变异引擎 200 行、模型路由 150 行、评估器 300 行、其他辅助 600 行）。如果用 LangGraph + 自定义插件，LangGraph 本身的抽象层代码 + 适配代码估计也要 1500 行以上，而且 debug 更困难。\nProtocol-based DI 让整个引擎可测试可替换。Kernel 客户端是一个 Protocol 接口，测试时注入 Mock，生产时注入 HTTP 客户端。换 LLM 提供商只需要改路由配置，引擎代码零改动。\n这个决策教会我什么 我从这次实践中提炼了一条设计原则：\n当你的核心复杂度在决策逻辑而不是连接层时，通用框架给你的抽象层是成本而不是收益。自研的代价是初始投入更高，收益是你拥有决策逻辑的完全控制权——不需要在框架的抽象层和你的业务逻辑之间做翻译。\n这条原则不是在说\u0026quot;永远不要用框架\u0026rdquo;。如果你的核心复杂度在连接层——对接 10 种 LLM API、5 种向量数据库、3 种文档格式——那 LangChain 的生态集成是真正的价值，自研是浪费时间。\n判断标准是：你花更多时间在\u0026quot;调用 LLM\u0026quot;上，还是在\u0026quot;根据结果做决策\u0026quot;上？如果是前者，用框架。如果是后者，框架帮不了你太多——它只会在你和你的决策逻辑之间加一层间接。\n更一般地说，框架的价值 = 它抽象掉的复杂度 / 它引入的适配复杂度。当分子大于分母时用框架。当分母大于分子时——就像我的场景——自研是更经济的选择。\n系列第七篇。上一篇：研究不可复现，就不是研究。第五篇：让 AI 写的代码跑起来，但别让它跑出去。第四篇：终局思维。第三篇：AI as Operator, Kernel as Law。第二篇：MCP 的问题不在协议层。第一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/why-i-built-my-own-fsm-engine/","summary":"LangChain、LangGraph、CrewAI、PydanticAI——AI 编排框架从不缺选择。我全部评估后决定自研。不是 NIH 心态，而是当你需要按失败模式选变异策略、按阶段路由不同模型和温度、在轨迹分析的基础上自适应进化时，通用框架给你的抽象层反而变成了需要绕过的障碍。","title":"为什么我没有用 LangChain——自研 FSM 编排引擎的设计逻辑"},{"content":" ML 研究中最常见的谎言是\u0026quot;上次跑的结果很好\u0026quot;。上次用的什么代码？什么数据版本？什么参数？没人说得清。我用文件系统事务（临时目录 → 原子重命名）实现了每次迭代的不可变快照，让\u0026quot;上次的结果\u0026quot;变成一个可查询的事实而不是一段记忆。\n\u0026ldquo;上次的结果\u0026quot;是个幽灵 做 ML 研究的人都经历过这个场景：两周前跑出了一个 Sharpe Ratio 1.8 的策略，现在想复现，发现代码改过了、数据更新过了、参数记不清了。你知道那个结果存在过，但你无法证明它。\n在 AI 编排系统中这个问题更严重。AI 自动迭代 15 轮，每轮生成不同的代码、用不同的参数、得到不同的指标。第 8 轮的结果很好，但第 12 轮覆盖了第 8 轮的代码。你甚至不知道第 8 轮用的是什么代码——因为没有人保存中间状态。\n不可复现的研究结果不是研究成果，是轶事。\n问题的结构 这个问题的本质不是\u0026quot;忘了保存\u0026rdquo;。而是系统的信息模型只有\u0026quot;当前状态\u0026quot;，没有\u0026quot;历史状态\u0026quot;。\n传统的研究流程是这样的：\n跑代码 → 看结果 → 改代码 → 再跑 → 覆盖上一次的结果 每次迭代都是原地修改。代码文件只有一份最新版本，结果文件只有一份最新输出。想回到两轮之前的状态？手动 Ctrl+Z 或者翻 Git 历史。\n但 Git 是为人类设计的版本管理工具——它假设人类会在有意义的节点手动 commit。AI 不会。AI 在一个循环里连续跑 15 轮，每轮之间的时间间隔可能只有几秒。你不能期望 AI 在每轮结束时停下来写一条有意义的 commit message。\n这引出了一个设计需求：版本化应该是系统的自动行为，不是操作者的主动行为。每次状态转换自动产生快照，不依赖任何人记得要保存。\n我考虑过的方案 方案 A：Git 自动提交 每轮迭代结束时自动 git add \u0026amp;\u0026amp; git commit。用 Git 的版本历史来管理迭代状态。\nGit 在什么场景下是对的：人类驱动的开发流程，commit 粒度是\u0026quot;一个有意义的改动\u0026quot;，commit message 传达意图。\n为什么在 AI 迭代场景下不适用：三个原因。\n第一，粒度不匹配。AI 一次任务可能产生 20 个版本，每个版本包含代码、执行结果、指标、元数据。Git 管理的是文件变更，不是\u0026quot;一次完整的迭代快照\u0026quot;。你需要把代码、结果、指标分散在不同文件里，然后靠 commit hash 把它们关联起来。可以做，但别扭。\n第二，性能。Git 的 add + commit 在大仓库里不是零成本操作。当一个任务快速迭代时，频繁的 Git 操作会成为瓶颈。而且 Git 的锁机制意味着并发任务会互相阻塞。\n第三，查询能力。\u0026ldquo;给我第 8 轮的指标\u0026rdquo;——在 Git 里需要 git log 找到对应的 commit，再 git show 提取文件内容。可以做，但不如直接读一个目录那么直观。\n方案 B：数据库存储 把每轮的代码、结果、指标写入 PostgreSQL。用关系表管理版本。\n数据库在什么场景下是对的：结构化查询需求强、数据量大、需要事务一致性的场景。\n为什么没完全采用：代码是文本，执行结果是 JSON，指标是数字，HTML 报告是大文本。把这些全塞进关系数据库，要么用 TEXT 列存大字段（查询效率差），要么拆成多张表（关联复杂度高）。数据库擅长管理结构化的元数据，但不擅长存储异构的工件（artifact）。\n另一个考虑：文件系统天然支持\u0026quot;打开文件看内容\u0026quot;的操作。调试时直接 cat code.py 看代码，比在数据库里 SELECT code FROM versions WHERE ... 直觉得多。可调试性在研究系统里是一个被低估的需求。\n方案 C：文件系统事务 + 不可变快照（我的选择） 设计哲学一句话：每次迭代是一个不可变的目录，用文件系统事务保证原子性。\n原子写入的设计 每次迭代的快照是一个目录，包含固定的文件结构：\n{base_dir}/{task_id}/v{version}/ ├── code.py # 这轮的完整代码 ├── result.json # 完整的执行结果 ├── metrics.json # 提取的关键指标 ├── metadata.json # 自动生成的元数据（时间戳、任务 ID、版本号） └── report.html # 可选的可视化报告 关键设计：写入过程是原子的。不是一个文件一个文件地写——那样如果写到一半进程崩溃，会留下半成品。而是：\n1. 在目标目录的同级创建一个临时目录（前缀 .v{n}_tmp_） 2. 把所有文件写入临时目录 3. 所有文件写完后，用 rename() 把临时目录重命名为正式目录 4. 如果任何步骤失败，删除临时目录 rename() 在 POSIX 文件系统上是原子操作——要么成功，要么什么都没发生。这和数据库的事务是同一个思路：要么全部提交，要么全部回滚。不存在\u0026quot;代码保存了但指标丢了\u0026quot;的中间状态。\n这个模式来自数据库的 WAL（Write-Ahead Log）设计。WAL 的核心原则是\u0026quot;先写日志再写数据\u0026quot;——如果写数据时崩溃，可以从日志恢复。我的模式是\u0026quot;先写临时目录再重命名\u0026quot;——如果写文件时崩溃，临时目录会被清理，不会污染正式版本。\n关键判断点 做出这个设计的关键认识是：版本快照不是\u0026quot;开发者工具\u0026quot;，是\u0026quot;系统基础设施\u0026quot;。\n很多系统把版本管理当作\u0026quot;方便开发者调试\u0026quot;的附加功能。Git、MLflow、Weights \u0026amp; Biases 都是这个定位——它们是独立的工具，研究者主动使用它们来跟踪实验。\n我的设计把版本快照融入了系统的状态转换逻辑。不是\u0026quot;迭代完成后保存一个版本\u0026quot;，而是\u0026quot;保存版本是迭代完成的一部分\u0026quot;。如果版本没有被保存，这次迭代在系统层面就没有完成。\n这和上一篇文章讨论的\u0026quot;终局思维\u0026quot;是同一个框架的应用。终局思维说：回溯能力不是事后补的，是设计约束。版本化是回溯能力的技术实现——如果每次迭代都有不可变的完整快照，任何\u0026quot;上次的结果\u0026quot;都可以被精确定位和复现。\n这个判断也受到了不可变基础设施（Immutable Infrastructure）思想的影响。在容器化部署中，服务器不是被\u0026quot;修改\u0026quot;的，而是被\u0026quot;替换\u0026quot;的。每个版本的服务器镜像是不可变的——出了问题回滚到上一个镜像，而不是在当前镜像上打补丁。同样的逻辑：每个版本的研究快照是不可变的——出了问题回到上一个快照，而不是在当前状态上找差异。\n不可变的代价和收益 不可变意味着存储空间会线性增长。每次迭代保存完整的代码和结果，而不是增量 diff。20 轮迭代 × 每轮 1MB = 20MB 一个任务。千次任务 = 20GB。\n这是一个有意识的取舍。增量存储（只存 diff）更省空间，但查询时需要从第一版开始逐步重建——复杂度高，出错风险大。完整快照浪费空间，但每个版本自包含——读取任何版本只需要读一个目录，不依赖其他版本的完整性。\n磁盘便宜，工程师的调试时间贵。这是一个简单的经济账。\n另一个收益：元数据自动注入。每个版本的 metadata.json 自动包含任务 ID、版本号、UTC 时间戳。这些字段不由调用方提供，由存储层自动填充——防止人为（或 AI）错误导致的元数据不一致。\n实施效果 版本化之后，\u0026ldquo;上次的结果\u0026quot;不再是一个需要回忆的问题。查询任务 ID，列出所有版本目录，直接打开对应版本的 metrics.json。从\u0026quot;你记得上次是什么参数吗\u0026quot;变成\u0026quot;查一下 v8 的 metadata\u0026rdquo;。\n更重要的效果是在迭代评估中：系统可以自动比较当前版本和历史最优版本的指标。如果连续 3 轮指标下降，自动终止迭代。这个逻辑依赖于每个版本的指标都被完整保存——如果只有\u0026quot;当前版本\u0026quot;和\u0026quot;上一个版本\u0026quot;，你无法判断趋势。\n调试也变简单了。以前排查\u0026quot;为什么第 12 轮的结果比第 8 轮差\u0026quot;：翻日志、对比代码 diff、猜测可能的原因。现在：打开 v8 和 v12 的目录，diff code.py 看代码差异，diff metrics.json 看指标差异。所有信息就在那里，不需要重建。\n这个决策教会我什么 我从这次实践中提炼了一条设计原则：\n任何需要回答\u0026quot;之前是什么状态\u0026quot;的系统，都应该把状态快照作为系统行为的一部分，而不是操作者的责任。自动化的不可变快照消除了\u0026quot;忘了保存\u0026quot;这个失败模式。\n这条原则的适用范围不限于 ML 研究。配置管理（Terraform 的 state file）、数据库迁移（migration 文件的线性历史）、甚至文档版本控制（每次发布是一个快照而不是一个 diff）都是同一个思路。\n核心认知是：可复现性不是一种美德，是一种架构约束。如果你的系统需要回答\u0026quot;之前发生了什么\u0026quot;，那么系统必须在架构层面保证\u0026quot;之前的状态被保存了\u0026quot;。靠操作者的自觉是不够的——人会忘，AI 更不会主动记。\n系列第六篇。上一篇：让 AI 写的代码跑起来，但别让它跑出去。第四篇：终局思维。第三篇：AI as Operator, Kernel as Law。第二篇：MCP 的问题不在协议层。第一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/atomic-versioning/","summary":"ML 研究中最常见的谎言是\u0026rsquo;上次跑的结果很好\u0026rsquo;。上次用的什么代码？什么数据版本？什么参数？没人说得清。我用文件系统事务（临时目录 → 原子重命名）实现了每次迭代的不可变快照，让\u0026rsquo;上次的结果\u0026rsquo;变成一个可查询的事实而不是一段记忆。","title":"研究不可复现，就不是研究"},{"content":" 让 AI 驱动研究流程，不等于让 AI 决定系统怎么运行。我在设计 AI 编排系统时做了一个关键分离：AI 只是操作员，执行引擎才是法律。这个决策源于一次失败的经验。\n一次失败的启发 在系统早期，我让 AI 有很大的自由度——它可以直接连接数据库查数据，可以在任意目录生成脚本，可以绕过 API 直接实例化内部组件来执行任务。\n结果是：AI 确实能更\u0026quot;高效\u0026quot;地完成任务。但代价是——有些任务的执行结果只存在于 AI 启动的那个进程里，脚本退出后数据就丢了。有些临时脚本散落在根目录，没人知道它们还有没有用。有些操作绕过了审计链路，事后无法追溯谁做了什么。\n系统\u0026quot;能跑\u0026quot;，但不可信。\n这迫使我重新思考一个问题：在一个 AI 驱动的系统里，AI 的权限边界应该画在哪里？\n问题的结构 这个问题的本质不是\u0026quot;AI 能力不够\u0026quot;或\u0026quot;AI 不可靠\u0026quot;。而是一个经典的系统设计问题：可变部分和不可变部分的分离。\n任何系统都有两类组件：\n不可变基础设施：数据访问、执行引擎、审计记录、资源控制。这些是系统的\u0026quot;物理定律\u0026quot;——不因使用者是人还是 AI 而改变。 可变操作层：研究策略、参数选择、迭代决策。这些是\u0026quot;操作员的判断\u0026quot;——可以变，应该灵活。 问题在于：如果不显式划线，AI 会自然地把两层混在一起。它不区分\u0026quot;我在做操作决策\u0026quot;和\u0026quot;我在绕过基础设施\u0026quot;。对它来说，直接连数据库和调 API 一样都是\u0026quot;完成任务的手段\u0026quot;。\n这和人类工程师绕过流程走捷径是同一个问题。区别是人类工程师通常知道自己在\u0026quot;违规\u0026quot;，AI 不知道。\n我考虑过的方案 方案 A：权限清单模式 给 AI 一份详细的\u0026quot;可以做/不可以做\u0026quot;清单，写在 prompt 或配置文件里。类似于 Claude Code 的 CLAUDE.md 规则文件。\n这在哪些场景下有效：规则数量少、AI 使用频率低、出错后果可控的场景。比如让 AI 帮忙写代码但不允许它执行——规则简单，一条就够。\n为什么在我的场景下不够：我的系统有几十种任务类型，每种有不同的执行约束。规则清单会膨胀到 AI 无法可靠遵守的程度。上一篇文章已经讨论过——自然语言规则没有强制力，规则越多，被无视的概率越高。\n方案 B：沙箱隔离模式 把 AI 放进一个受限沙箱——只能访问特定文件、只能调用特定函数、所有操作都在隔离环境里执行。\n这是安全领域的标准做法。Docker 容器、WebAssembly 沙箱、浏览器的同源策略都是这个思路。\n为什么没有完全采用：沙箱解决的是安全问题，不是架构问题。即使在沙箱里，AI 仍然可以写出绕过审计的代码、生成不可追溯的结果。沙箱能限制 AI 访问什么资源，但不能限制 AI 如何组织它的输出。\n方案 C：三层分离 + API 唯一通道（我的选择） 设计哲学用三句话概括：\nKernel is law. — 执行引擎定义了系统能做什么，以及怎么做 AI is operator. — AI 只能通过 API 调用执行引擎，不能直接操作底层 UI is display. — 展示层只读，不触发执行 核心约束不是\u0026quot;AI 不能做某事\u0026quot;，而是\u0026quot;所有人（包括 AI）只能通过同一条通道做事\u0026quot;。这条通道就是执行引擎的 API。\n关键判断点 做出这个设计的关键认识来自操作系统的一个类比。\n在操作系统里，用户态程序不能直接操作硬件——必须通过系统调用（syscall）。这不是因为用户程序\u0026quot;不可信\u0026quot;，而是因为直接操作硬件会破坏资源管理的一致性。无论你是 root 用户还是普通用户，操作磁盘都要经过文件系统，操作网络都要经过协议栈。\n我的系统采用了同样的模式：执行引擎是\u0026quot;内核\u0026quot;，AI 是\u0026quot;用户态程序\u0026quot;。AI 提交任务必须经过 API（类似 syscall），API 内部负责审计记录、资源控制、结果持久化。AI 不能绕过这层，就像用户程序不能绕过 syscall 直接写磁盘。\n这个类比还有一个推论：内核应该独立于使用者存在。 即使把 AI 层完全拆掉，执行引擎仍然可以独立运行——人类可以直接调 API 做同样的事。系统不依赖 AI 才能工作，AI 只是一种更高效的操作方式。\n这一点很重要。很多 AI 系统的设计是\u0026quot;AI 在中心，其他组件围绕 AI 服务\u0026quot;。我的设计是反过来的：执行引擎在中心，AI 是接入它的一种方式。这意味着：\nAI 挂了，系统不挂。人类可以接管。 AI 换了（从 GPT 换成 Claude，从 Claude 换成本地模型），执行引擎不需要改。 审计链路不依赖 AI 的\u0026quot;诚实性\u0026quot;——因为所有操作都必须经过 API，API 自动记录。 三条具体约束 从这个哲学出发，落地了三条不可违反的约束：\n第一，AI 不直连数据引擎。所有数据访问通过执行引擎的数据 API。AI 不知道数据存在哪里、用什么格式——它只知道\u0026quot;给我这些股票在这个时间段的价格\u0026quot;。\n第二，AI 生成的代码在受控沙箱里执行。代码先经过 AST 扫描（禁止直接 import 内部模块），再在资源限制下运行（CPU、内存、超时），最后输出必须符合标准格式。不是让 AI \u0026ldquo;注意安全\u0026rdquo;，而是代码层面不可能做出不安全的事。\n第三，所有任务必须通过 API 提交。AI 不能在本地实例化执行组件。这确保每个任务都有审计记录、都出现在任务历史里、都可以被界面查询到。没有\u0026quot;影子任务\u0026quot;。\n实施效果 这套分离运行了几个月，有一个直观的验证方式：我可以随时关闭 AI 编排层，系统的其他部分完全不受影响。执行引擎继续运行，界面继续展示数据，已有的任务结果不丢失。AI 只是停止了\u0026quot;主动发起新研究\u0026quot;这个功能。\n反过来——如果 AI 和执行引擎是耦合的，关掉 AI 意味着整个系统停摆。这就是\u0026quot;operator\u0026quot;和\u0026quot;infrastructure\u0026quot;的本质区别。\n另一个效果：当我从一个 LLM 提供商换到另一个时，改动完全局限在编排层——改一下路由配置，API 调用方式换一下。执行引擎、数据层、界面层零改动。这验证了\u0026quot;AI 可替换\u0026quot;这个设计目标。\n这个决策教会我什么 我从这次实践中提炼了一条设计原则：\n在任何 AI 驱动的系统里，先问一个问题：如果把 AI 拿掉，系统还能运行吗？如果不能，说明你把 AI 和基础设施耦合了。\n这是一个比\u0026quot;AI 安全\u0026quot;更底层的问题。不是\u0026quot;AI 会不会做坏事\u0026quot;，而是\u0026quot;系统架构是否允许 AI 做坏事\u0026quot;。\n如果架构正确——AI 只是操作员、执行引擎才是法律——那么 AI 的\u0026quot;不可靠性\u0026quot;就不再是系统性风险。AI 的操作空间被约束在确定性的通道里，通道内的行为是可审计的、可回滚的、可追溯的。\n这个思维框架来自对传统系统设计的一个延伸：我们不会让任何单个用户拥有绕过操作系统的能力，无论这个用户多么\u0026quot;聪明\u0026quot;。AI 也一样。它的能力不应该成为绕过系统约束的理由。\n系列第三篇。上一篇：MCP 的问题不在协议层，在语义层。第一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/ai-as-operator/","summary":"让 AI 驱动研究流程，不等于让 AI 决定系统怎么运行。我在设计 AI 编排系统时做了一个关键分离：AI 只是操作员，执行引擎才是法律。这个决策源于一次失败的经验。","title":"AI as Operator, Kernel as Law — 为什么 AI 不应该有架构决策权"},{"content":" MCP 的 JSON-RPC 传输没有问题。真正的问题是自然语言规则没有代码级强制力——LLM 可以完全无视你的 instructions。我设计了 Intent Validator 模式来补上这个缺口。\n一条被忽视的规则 我的系统有一条业务规则：ML 回测必须使用全区间数据，禁止限定日期范围。原因是限定范围会导致样本量不足，回测结果不可靠。\n这条规则写在 MCP server 的 instructions 里，用自然语言告诉 Agent：\u0026ldquo;NEVER restrict to test period only。\u0026rdquo;\n然后某天 Agent 无视了这条规则，填了一个日期范围，请求通过了，任务跑完了，结果看起来正常。没有任何报错。但结果是不可靠的——只是没有人知道。\n这不是 MCP 的 bug。协议层工作正常。问题是一个更根本的缺陷：整个 MCP 生态里，自然语言规则和代码执行之间没有任何强制性绑定。\n问题的结构 当前 AI 工具调用的校验分三层。中间那层是空的：\n自然语言指令（MCP instructions） → LLM \u0026#34;理解\u0026#34;了 → 可能遵守，可能无视 ↓ ???（空） → 没有任何代码级检查 ↓ 类型校验（JSON Schema / Pydantic）→ start_date 是 string → 类型合法 → 通过 第一层是劝告，第三层是类型检查。中间缺的是业务语义的代码级强制。\n这个空洞不是我的系统独有的。任何用 MCP 或 function calling 的系统都有同样的问题。MCP 的 tool schema 能定义参数类型，但不能表达参数之间的条件约束——\u0026ldquo;如果 A 为空则 B 必须非空\u0026rdquo;、\u0026ldquo;任务类型为 X 时禁止设置 Y\u0026quot;这类规则，JSON Schema 表达不了。\n目前业界对这个问题的处理方式是两端用力：\n上游：优化 prompt，让 LLM 更好地理解规则 → 有上限，永远不会 100% 可靠 下游：收紧 JSON Schema，用更严格的类型定义 → 表达力不够，跨参数约束描述不了 两端都在做，中间那层没人做。\n我的思考路径 最初的想法是自研一套协议替代 MCP。但分析之后发现这是错误的反应——协议层（JSON-RPC 传输、tool discovery、序列化）没有问题，问题在应用层。换协议解决不了语义问题，只是在搬运复杂度。\n然后想到：这个问题的结构类似于 Web 开发中的输入校验。前端表单有 HTML5 的 type=\u0026ldquo;email\u0026rdquo; 校验（类似 JSON Schema 的类型检查），但真正的业务校验（\u0026ldquo;邮箱域名必须是公司域名\u0026rdquo;、\u0026ldquo;金额不能超过余额\u0026rdquo;）是在后端做的。没有人会说\u0026quot;HTML5 校验不够用，我要自研一套 HTTP 协议\u0026rdquo;。\n正确的做法是在 LLM 输出之后、系统执行之前，加一层应用级的业务规则校验。\n这就是 Intent Validator 的设计起点。\n方案对比 方案 A：强化 Prompt Instructions 把规则写得更明确、更强调、加更多 WARNING 标记。这是当前多数 MCP 应用的做法。\n问题：本质上是在跟概率对赌。LLM 不是规则引擎，它是概率模型。\u0026ldquo;大多数时候遵守\u0026quot;不等于\u0026quot;永远遵守\u0026rdquo;。在研究实验里偶尔犯错可以接受，在生产环境里不行。\n方案 B：收紧 Tool Schema 把 start_date 从 optional 改成不暴露这个字段。但这意味着其他需要日期的任务类型也用不了了——一个 tool 的 schema 是所有调用场景的并集，不是交集。\n方案 C：Intent Validator（我的选择） 在 LLM 输出的参数被解析之后、被发送到执行层之前，插入一层代码级校验。每个任务类型注册自己的业务规则，校验器自动执行。\n核心设计原则：\n规则即代码，不是文档。 \u0026ldquo;ml_backtest 禁止设日期\u0026quot;不是写在 README 里的注意事项，是一段会 raise error 的 Python 函数。\n注册式架构。 新增规则不改框架代码，只加一个装饰器函数。从 0 条规则到 100 条规则，架构代码不变。\n可操作的拒绝信息。 拒绝不是一个 \u0026ldquo;400 Bad Request\u0026rdquo;。每条 violation 带三个字段：rule（机器可读 ID）、error（问题描述）、fix（具体修复指令）。Agent 收到拒绝后能自动修正重新提交，不需要人介入。\n硬拦截和软警告分离。 有些规则是必须遵守的（硬错误直接拒绝），有些是建议（警告附带在成功响应里）。不是非黑即白。\n关键判断点 设计过程中最关键的一个决策是：这层校验应该放在哪里？\n选项一：放在 MCP server 里。只覆盖 Agent 调用路径。\n选项二：放在 REST API 的请求模型里。只覆盖 HTTP 调用路径。\n选项三：抽成独立模块，两条路径都调用它。\n我选了第三个。原因是一条规则不应该写两遍——MCP 和 HTTP 是同一个系统的两个入口，业务规则不因入口不同而改变。实现方式是在请求模型的基类里加一个 model_validator，自动调用同一套规则引擎。MCP server 的 submit 函数也调用同一套。规则只写一份，覆盖所有入口。\n这个决策的启发来源是 DRY 原则的一个推论：校验逻辑的重复比业务逻辑的重复更危险。 业务逻辑重复了，两处表现一致；校验逻辑重复了，两处规则不同步时一个入口放行另一个入口拒绝，安全漏洞就出现了。\n实施效果 部署后测试了几个场景：\nAgent 提交 ml_backtest 时带了 start_date → 即时拒绝，返回明确的修复指令，Agent 自动修正后重新提交成功 脚本直接 curl 调用 REST API，带了非法参数组合 → Pydantic model_validator 触发同一套规则，返回 422 新增一条业务规则 → 写一个装饰器函数，不改任何现有代码 从\u0026quot;规则写在 instructions 里靠 LLM 自觉遵守\u0026quot;变成\u0026quot;规则写在代码里不遵守就报错\u0026rdquo;。可靠性从概率性变成确定性。\n这个决策教会我什么 我从这次实践中更新了一条思维框架：\n自然语言和代码之间的每一条关键约束，都需要有一个代码级的强制点。如果一条规则只存在于文档或 prompt 里，它就不是约束，而是建议。\n这条原则的适用范围不限于 MCP。任何 AI Agent 操作有业务约束的场景都成立：医疗 Agent 的药物禁忌组合、法律 Agent 的法条时效性、运维 Agent 的变更窗口限制。这些约束如果只靠 prompt 软控制，就是在用概率模型做确定性保证——逻辑上不成立。\nVibe coding 的核心矛盾是自然语言的模糊性和系统操作的精确性之间的鸿沟。Intent Validator 不是终极解法，但它指向了一个正确方向：不要试图让 LLM 变得更严谨，而是在 LLM 后面加一道代码级的关卡。\n系列第二篇。上一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/mcp-semantic-gap/","summary":"MCP 的 JSON-RPC 传输没有问题。真正的问题是自然语言规则没有代码级强制力——LLM 可以完全无视你的 instructions。我设计了 Intent Validator 模式来补上这个缺口。","title":"MCP 的问题不在协议层，在语义层"},{"content":" 多智能体是当前 AI 工程的热门范式。但我在设计 AI 驱动的量化研究系统时选择了单 Agent + 状态机。不是因为多智能体太难，而是因为问题结构不匹配。\n一个反直觉的选择 2024 年以来，多智能体（Multi-Agent）几乎成了 AI 系统架构的默认选项。CrewAI、AutoGen、MetaGPT——每个框架都在告诉你：把任务拆给多个 Agent，让它们协作，效果更好。\n我在设计一套 AI 驱动的量化研究系统时，认真评估了这条路线，最终没有采用。选择了一个看起来\u0026quot;落后\u0026quot;的方案：单 Agent + 有限状态机。\n这不是资源限制或技术能力的妥协。是分析了问题结构之后的主动选择。\n问题的结构，不是表象 量化研究的工作流看起来有很多\u0026quot;角色\u0026quot;：有人负责生成策略、有人负责回测、有人负责评估、有人负责风控。自然而然会想到把每个角色映射成一个 Agent。\n但这是用组织结构类比技术架构，犯了表象推导的错误。\n真正需要分析的是三个结构性问题：\n任务之间是并行的还是串行的？\n量化研究的核心流程是严格串行的：训练模型 → 回测（依赖模型输出）→ 评估（依赖回测结果）→ 决策（依赖评估结论）。每一步的输入是上一步的输出，没有并行空间。多 Agent 的协调收益在串行流程里是零。\n不同 Agent 需要不同的工具集吗？\n多智能体的一个核心假设是每个 Agent 有专属工具，工具集之间不重叠。但在量化研究里，\u0026ldquo;生成策略\u0026rdquo;、\u0026ldquo;提交回测\u0026rdquo;、\u0026ldquo;查询指标\u0026rdquo;、\u0026ldquo;检查约束\u0026quot;全都指向同一套执行引擎 API。四个 Agent 用的是同一把锤子。拆开不增加能力，只增加通信开销。\n评估需要主观辩论吗？\n有些系统用多 Agent 实现红蓝对抗——一个提出方案，一个专门找漏洞。这在写作、设计等主观任务里有价值。但量化研究的评估是数值确定的：Sharpe Ratio 是 1.2 就是 1.2，IC 是 0.03 就是 0.03。不需要另一个 Agent 来\u0026quot;辩论\u0026quot;指标是否可信。反过拟合检验也是确定性的检查清单，不是主观判断。对抗机制在这里退化成了 if-else 逻辑。\n三个条件全不满足。多智能体在这里增加复杂度，不增加价值。\n我考虑过的方案 方案 A：多智能体协作 类似 CrewAI 的模式——定义研究员、回测员、评估员、风控员四个角色，让它们通过消息传递协作。\n它在什么场景下是对的：任务天然并行（如同时搜索多个信息源）、工具集不可共享（如代码库 vs 生产环境权限隔离）、需要多视角辩论（如产品方案设计）。\n它在我的场景下为什么不适用：串行流程没有并行收益；共享工具集拆分无意义；数值评估不需要辩论。额外代价是 Agent 间上下文传递的序列化开销和调试时跨 Agent 日志追踪的复杂度。\n方案 B：单 Agent + 有限状态机 一个 Agent 驱动整个研究循环，状态机控制行为边界和状态转换：\nINIT → GENERATE → EXECUTE → EVALUATE ──→ FINISH ↑ │ └── ITERATE ┘ 核心思路用一句话概括：把\u0026quot;多个 Agent 之间的协调协议\u0026quot;替换为\u0026quot;单个 Agent 内部的状态转换规则\u0026rdquo;。\n状态机的每次转换是确定性的：EVALUATE 阶段的指标达标就转 FINISH，不达标就转 ITERATE 回到 GENERATE。不需要 Agent 之间\u0026quot;讨论\u0026quot;下一步该做什么。\n关键判断点 做出这个选择的关键不是技术对比，而是认识到一个更底层的问题：多智能体框架解决的是\u0026quot;协调\u0026quot;问题，但我的系统根本没有\u0026quot;协调\u0026quot;问题。\n我的系统有的是\u0026quot;编排\u0026quot;问题——一个确定性流程需要被自动化执行，中间穿插 LLM 的生成能力。这更接近工作流引擎（workflow engine）的问题域，不是多 Agent 协作的问题域。\n这个认识来自一个类比：传统的 CI/CD pipeline 也有多个阶段（build → test → deploy），也可以映射成多个\u0026quot;Agent\u0026quot;。但没有人会用多智能体框架来做 CI/CD，因为大家直觉上知道那是个串行编排问题。量化研究的迭代流程在结构上和 CI/CD pipeline 是同构的。\n实施效果 状态机方案运行了几个月，有几个可量化的对比点：\n上下文零丢失：单 Agent 天然共享全部研究历史，不需要跨 Agent 传递中间结果 线性可追溯：每次状态转换都有完整记录（输入代码、输出指标、评估决策、决策原因），出问题直接按时间线回溯 调试时间从\u0026quot;在多个 Agent 的日志里拼凑\u0026quot;变成\u0026quot;在单个状态序列里定位\u0026quot; 没有遇到过需要并行的场景。如果未来需要同时跑 10 个独立的因子研究，那是并发调度问题，用 asyncio.gather 就够了，不需要引入 Agent 间通信。\n这个决策教会我什么 我从这次实践中提炼了一条判断原则：\n选架构之前，先识别问题的结构类型。不是所有\u0026quot;看起来有多个角色\u0026quot;的系统都需要多智能体。\n判断方法是三个问题：\n任务间有并行空间吗？ 工具集有隔离需求吗？ 评估需要主观辩论吗？ 三个都不满足，就不该用多智能体。即使只满足一个，也要评估引入的协调复杂度是否被收益覆盖。\n这和微服务的演进路径一模一样。2015 年前后所有人都在拆微服务，直到很多团队发现自己的系统根本不需要拆——它们的问题是部署问题，不是服务边界问题。多智能体目前也在经历类似的膨胀期。热度会过去，问题结构分析不会过时。\n系列第一篇。下一篇：MCP 的问题不在协议层，在语义层。\n","permalink":"https://miasyster.github.io/posts/why-not-multi-agent/","summary":"多智能体是当前 AI 工程的热门范式。但我在设计 AI 驱动的量化研究系统时选择了单 Agent + 状态机。不是因为多智能体太难，而是因为问题结构不匹配。","title":"为什么我没有用多智能体架构做量化研究系统"},{"content":" 多数系统先设计功能，再补审计。我反过来做——先确定系统在出事后需要回答哪些问题，再倒推每一层应该记录什么。这个思维倒置改变了整个架构。\n一个倒过来的设计顺序 搭系统的自然顺序是：先让功能跑起来，再加日志，最后补审计。\n我在设计 AI 编排系统时把顺序倒过来了：先列出系统出事后需要回答的问题，再倒推每一层应该记录什么，最后才设计功能如何运行。\n这不是因为我更\u0026quot;严谨\u0026quot;。是因为我在早期吃过一次亏——AI 自动迭代了 15 轮优化，最终产出了一个 Sharpe Ratio 很高的策略。但当我想回溯\u0026quot;第 8 轮为什么从动量因子切换到了均值回归\u0026quot;时，发现什么记录都没有。AI 做了决策，但决策过程消失了。\n一个无法解释的好结果，比一个可以解释的坏结果更危险。因为你不知道它是真的好，还是刚好过拟合了。\n问题的结构 这个问题的本质不是\u0026quot;日志不够多\u0026quot;。而是一个更根本的设计缺陷：系统的信息流是为\u0026quot;执行\u0026quot;设计的，不是为\u0026quot;回溯\u0026quot;设计的。\n执行优先的系统长这样：\n输入 → 处理 → 输出 → （可选）记个日志 回溯优先的系统长这样：\n输入 → 记录输入 → 处理 → 记录决策依据 → 输出 → 记录输出 → 关联到同一条链 区别不在于记录多少，而在于记录的东西是否能被串成一条因果链。散落的日志条目不是审计——它们只是噪音。审计是能回答\u0026quot;谁在什么时候基于什么信息做了什么决定，结果是什么\u0026quot;的完整链条。\n这和数据库事务日志的设计思路是同源的。数据库的 WAL（Write-Ahead Log）不是事后补的调试工具——它是架构的一部分，在写数据之前先写日志。先记录，再执行。顺序不能反。\n我考虑过的方案 方案 A：事后补日志 在现有代码里加 logger.info，把关键节点的状态打到日志文件里。需要回溯时 grep 日志。\n这在运维排障中是标准做法。出了问题看日志，定位到时间点，找到错误信息。\n为什么在 AI 编排场景下不够：AI 的编排循环不是线性的。它是一个迭代循环——生成代码 → 执行 → 评估 → 决定继续或停止 → 回到生成。每一轮的\u0026quot;决定\u0026quot;依赖上一轮的\u0026quot;评估\u0026quot;，而\u0026quot;评估\u0026quot;依赖\u0026quot;执行结果\u0026quot;。如果只有散落的日志行，你能看到每一步发生了什么，但看不到步骤之间的因果关系。\u0026ldquo;为什么第 8 轮切换了方向\u0026rdquo;——这个答案分散在三个不同的日志行里，而且没有任何东西把它们关联起来。\n方案 B：终局倒推设计（我的选择） 先定义\u0026quot;事后需要回答的问题\u0026quot;，再倒推系统需要记录什么。\n我列了五个终局问题：\n这个任务是谁发起的，在什么上下文里？ 每一轮迭代生成了什么代码，得到了什么指标？ 每一轮的评估决策是什么，基于什么理由？ 最终结果的数据依赖是什么版本？ 如果用同样的输入重跑，能得到同样的结果吗？ 从这五个问题倒推，每个问题对应一个必须记录的数据结构：\n问题 1 → 任务提交记录（用户 ID、session、objective、时间戳） 问题 2 → 迭代快照（每轮的完整代码 + 执行结果 + 指标，不是摘要） 问题 3 → 评估记录（决策 + 决策依据，作为结构化字段，不是日志文本） 问题 4 → 数据版本哈希（输入数据的 SHA256，记录在任务元数据里） 问题 5 → 可复现性校验（代码哈希 + 数据哈希 + 引擎版本，三者组合可唯一确定结果） 设计出来之后，信息流变成了：每次状态转换不是\u0026quot;执行完了顺便记一下\u0026quot;，而是\u0026quot;记录是状态转换的一部分——不记录就不算完成\u0026quot;。\n关键判断点 做出这个选择的转折点不是技术考量，而是一个认知转变：审计不是系统的附加功能，是系统的设计约束。\n这个认识来自金融行业的一个惯例。在合规严格的金融机构里，交易系统的审计需求不是上线后由合规部门\u0026quot;提需求\u0026quot;加进去的——它从第一天就是架构的一部分。交易记录的完整性、时间戳的不可篡改性、决策链的可追溯性——这些是和\u0026quot;能下单\u0026quot;同等重要的系统需求。\nAI 编排系统面临同样的问题。当 AI 自动做出影响资金的决策时，\u0026ldquo;这个决策是怎么来的\u0026quot;不是调试需求，是生产需求。\n这引出了一个更一般的原则：任何会被问\u0026quot;为什么\u0026quot;的系统，都应该在设计阶段就把\u0026quot;回答为什么\u0026quot;作为约束条件。 不是加日志能解决的——是信息架构需要为回溯而设计。\n实施效果 终局倒推设计带来的一个意外收获：它大幅简化了调试过程。\n以前排查问题的流程是：看日志 → grep 关键词 → 拼凑时间线 → 猜测因果关系。\n现在的流程是：查任务 ID → 拿到完整的迭代序列 → 每一步的输入、输出、决策、理由都是结构化字段 → 直接定位到出问题的那一步。\n从\u0026quot;在非结构化文本里搜索\u0026quot;变成\u0026quot;在结构化数据上查询\u0026rdquo;。这不是审计系统的副产品——这是终局思维自然导致的结果：为回溯设计的数据结构，天然适合被查询。\n另一个效果更微妙：当系统强制要求每次评估决策都记录理由时，评估逻辑本身被迫变得更清晰。\u0026ldquo;通过/不通过\u0026quot;不够——你必须写出\u0026quot;因为 Sharpe \u0026lt; 1.0 且连续 3 轮无改善所以终止\u0026rdquo;。这种强制记录倒逼了决策逻辑的明确化。\n这个决策教会我什么 我从这次实践中提炼了一条设计原则：\n设计一个系统时，先问：这个系统出事后，需要回答哪些问题？然后确保架构本身能回答这些问题——不依赖事后 grep 日志。\n这条原则的适用范围远超 AI 系统。任何系统只要运行时间够长，最终都会面临\u0026quot;为什么会变成这样\u0026quot;的追问。区别在于：有些系统的架构能直接回答这个问题，有些系统需要工程师花三天翻日志才能拼出一个不确定的答案。\n差别不在于谁的日志更多。在于谁在设计阶段就把\u0026quot;可回溯\u0026quot;当作了和\u0026quot;能运行\u0026quot;同等的设计约束。\n终局思维的本质是：不要只设计系统在正常运行时的样子，也要设计系统在被审查时的样子。后者往往决定了前者的架构。\n系列第四篇。上一篇：AI as Operator, Kernel as Law。第二篇：MCP 的问题不在协议层。第一篇：为什么我没有用多智能体架构。\n","permalink":"https://miasyster.github.io/posts/design-for-the-endgame/","summary":"多数系统先设计功能，再补审计。我反过来做——先确定系统在出事后需要回答哪些问题，再倒推每一层应该记录什么。这个思维倒置改变了整个架构。","title":"终局思维：先想清楚怎么被审计，再决定怎么运行"},{"content":"构建 AI 驱动的量化研究系统。关注 AI Agent 架构、量化工程和生产级系统设计。\n这个博客记录我在实际工程中的架构决策、踩坑经验和技术判断。不写教程，只写判断。\n联系方式：GitHub · Email\n","permalink":"https://miasyster.github.io/about/","summary":"\u003cp\u003e构建 AI 驱动的量化研究系统。关注 AI Agent 架构、量化工程和生产级系统设计。\u003c/p\u003e\n\u003cp\u003e这个博客记录我在实际工程中的架构决策、踩坑经验和技术判断。不写教程，只写判断。\u003c/p\u003e\n\u003cp\u003e联系方式：\u003ca href=\"https://github.com/Miasyster\"\u003eGitHub\u003c/a\u003e · \u003ca href=\"mailto:qq853777924@gmail.com\"\u003eEmail\u003c/a\u003e\u003c/p\u003e","title":"关于"}]