Pi 背后的设计哲学:克制不是忍住不加,而是让功能有地方去

Posted on 三 19 8月 2026 in Tech

Abstract Pi 背后的设计哲学
Authors Walter Fan
Category learning note
Version v1.0
Updated 2026-08-19
License CC-BY-NC-ND 4.0

翻开 Pi 的 README,最下面有一节 Philosophy。我第一次读的时候有点意外,因为整节几乎全是"不做"——下面是原文的六个小标题,后面跟着它给的替代方案:

  • No MCP. Build CLI tools with READMEs, or build an extension that adds MCP support.
  • No sub-agents. Spawn pi instances via tmux, or build your own with extensions.
  • No permission popups. Run in a container, or build your own confirmation flow with extensions.
  • No plan mode. Write plans to files, or build it with extensions, or install a package.
  • No built-in to-dos. They confuse models. Use a TODO.md file, or build your own with extensions.
  • No background bash. Use tmux. Full observability, direct interaction.

一个 9.3 万 star 的项目,把竞品的核心卖点一条条列出来说"我不做",这胆子不小。但真正让我坐下来读代码的是另一个发现:这些被拒绝的功能,全都在这个仓库里躺着。 examples/extensions/ 下面有 subagent/(1195 行)、plan-mode/(558 行)、todo.ts(297 行)、permission-gate.ts(34 行)。作者不是不会做,也不是没做过。

所以"大道至简"的真相不是忍住不加。我读完这份代码库的判断是:Pi 的克制不靠作者意志力,靠三个架构支点撑住——只要这三个支点在,拒绝就是低成本的;缺任何一个,功能就会往内核里漏。 这篇先把这三个支点拆开讲,然后给一个把 Pi 当 Agent 引擎嵌进自己项目的完整例子,最后讲清它的代价和边界。

先交代背景。Pi 的主要作者是 Mario Zechner(GitHub 上的 badlogic),libGDX 的创造者——写过游戏引擎的人对"每一帧的开销"有本能的敏感,这点后面会体现得很明显。第二作者是 Armin Ronacher,Flask 和 Jinja2 的作者,也是托管这个项目的 Earendil 的创始人。仓库 2025 年 8 月建立,我拉下来这份代码时是 v0.84.2,6222 个 commit,Mario 一个人占了 3700 多个,Armin 600 多。


支点一:把"最小"的度量单位从行数换成 token

如果你拿 wc -l 去量 Pi,会得到一个很尴尬的结论:

行数(src/*.ts) 干什么的
pi-coding-agent 59,306 CLI、TUI 交互模式、会话管理、扩展加载
pi-ai 23,491 三十多家 provider 的统一 API
pi-tui 16,740 终端 UI 库,差分渲染
pi-agent-core 12,635 agent 运行时、工具调用、状态管理

将近 11 万行 TypeScript。这跟"minimal"两个字看起来是矛盾的,而且 CONTRIBUTING.md 里写得斩钉截铁:

pi's core is minimal. If your feature does not belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.

矛盾在哪?在于它压的从来不是代码行数,是模型看到的那个面

我把它的系统提示词模板抠出来数了一下:主模板 1352 个字符,按 4 字符 ≈ 1 token 粗算大约 340 token。默认四个工具(readwriteeditbash)的描述加参数 schema 合计 2443 字符,约 610 token。两项相加不到 1000 token——作者在博客里声称"系统提示词加工具定义合起来不到 1000 token",这个数量级在本地是能对上的。

对比一下他给出的另一组数字:Playwright 的 MCP server 挂上去是 21 个工具、13.7k token;Chrome DevTools 的 MCP 是 26 个工具、18k token。用他自己的话说:

That's 7-9% of your context window gone before you even start working.

这就是那个换算关系:在 agent 这一层,真正稀缺的资源不是硬盘也不是内存,是上下文窗口。 你多加一个内置工具,成本不是多几百行代码——代码放在硬盘上不要钱——成本是每一次请求都要为那段工具描述付费,而且它还会稀释模型的注意力。59306 行代码里绝大部分是 TUI 渲染、会话树、provider 适配,这些东西模型一个字都看不到。

想明白这一层,那份"不做"清单立刻就变得可解释了。它不是审美偏好,是预算约束下的排序。内置 todo 工具要占 schema、要模型维护状态;TODO.md 占零 token,模型用 readedit 就够了。作者的原话是:

In my experience, to-do lists generally confuse models more than they help. They add state that the model has to track and update, which introduces more opportunities for things to go wrong.

同样的算式也解释了为什么它不做 MCP。作者在另一篇文章里给了个更狠的对照:他把浏览器自动化做成四个 CLI 脚本加一个 README,README 一共 225 token。

instead of pulling in 13,000 to 18,000 tokens like the MCP servers mentioned above, this README has a whopping 225 tokens. This efficiency comes from the fact that models know how to write code and use Bash. I'm conserving context space by relying heavily on their existing knowledge.

最后半句是关键,也是我觉得最值得偷的一个思路:模型已经会用 bash 和 rg 了,你把这套知识重新包装成一套私有的 tool schema,等于花上下文买一份模型本来就有的能力。 这不是省钱,这是白扔钱。

推论:给 agent 加能力时,先问"这个能力能不能表达成模型已经会用的东西"。

能表达成 CLI + README 的,就不要做成 tool;能表达成文件的,就不要做成内置状态。

也正因为这条线画得清楚,packages/coding-agent/src/core/system-prompt.ts 里那段逻辑才有意义:工具描述不是一股脑塞进去的,而是按当前启用了哪些工具动态拼装,某个工具没提供一行简介就压根不出现在 Available tools 里。连 guidelines 都是按工具可用性挑的——只有在 bash 可用而 grep/find/ls 都不在的时候,才会加上那句"用 bash 做文件操作"。这种抠法,是写过游戏引擎的人的习惯。


支点二:给需求留泄压阀,拒绝才不得罪人

光有度量标准还不够。任何有用户的项目都会持续收到"能不能加个 X",光靠"不"字挡是挡不住的——挡久了用户就 fork 你,或者你自己心软了。

Pi 的解法是:把"能不能做到"和"要不要内置"彻底分开。 内核只保证一件事:任何人想做 X,都不必 fork 内核。

具体落在扩展 API 上。我数了 src/core/extensions/types.ts,一共 31 个事件钩子:

project_trust  resources_discover
session_start  session_info_changed  session_before_fork  session_before_tree
session_tree   session_compact  session_compact_failed  session_shutdown
context        input          user_bash
before_provider_headers  after_provider_response
before_agent_start  agent_start  agent_end  agent_settled
turn_start     turn_end
message_start  message_update  message_end
tool_call      tool_result
tool_execution_start  tool_execution_update  tool_execution_end
model_select   thinking_level_select

加上 ExtensionAPI 上二十来个方法(registerToolregisterCommandregisterShortcutregisterProviderregisterMessageRendererappendEntrysetActiveTools……)。最狠的一条是扩展可以用同名工具直接覆盖内置工具——examples/extensions/tool-override.ts 就是拿一个自己的 read 顶掉内置的 read,顺手加上审计日志和 .env/.ssh/.aws 的黑名单,然后再委托给原实现。

于是那份"不做"清单就有了下半句。它每一条的完整形态其实是"我不内置,但这是你自己做的入口":

拒绝的功能 泄压阀在哪 成本
权限弹窗 tool_call 事件返回 { block: true } permission-gate.ts 34 行
sub-agent registerTool + 拉起子进程 示例 1195 行
plan mode registerCommand + registerShortcut 示例 558 行
todo 列表 registerTool + 存进会话条目 示例 297 行
MCP 自己写扩展接 MCP 用户自理

那个 34 行的权限门是最有说服力的一个数字。别的 agent 把权限系统做成内核的一大块,Pi 只是保证"你能在工具执行前插一脚",剩下 34 行归你。

值得多说一句的是 examples/ 的性质。这个目录 15845 行 TypeScript,而且它是随 npm 包一起发布的——package.jsonfiles 字段里明确列了 docsexamples,系统提示词里还专门给出了它们的绝对路径,告诉模型"用户问 pi 自己的事情时去读这些"。

Pi documentation (read only when the user asks about pi itself, ...):
- Main documentation: <readmePath>
- Additional docs:    <docsPath>
- Examples:           <examplesPath>

这一步很妙。它等于把"我不做,你自己做"从一句甩锅的话,变成了一条可执行的路径——你跟 pi 说"给我加个权限确认",它会去读自己的示例,然后照着写一个。README 里那句 pi can create extensions. Ask it to build one for your use case. 不是俏皮话,是设计的一部分。

用一张图收口这套分流:

flowchart LR
    REQ["用户需求<br/>sub-agent / plan mode<br/>MCP / todo / 权限门"]
    GATE{"非得内核做吗?"}
    CORE["Core<br/>4 个默认工具 + agent loop<br/>模型可见面 &lt; 1000 token"]
    EXT["Extension<br/>31 个事件钩子<br/>可覆盖内置工具"]
    SKILL["Skill / Prompt 模板<br/>按需加载,只常驻描述"]
    PKG["Pi Package<br/>npm / git 分发给别人"]

    REQ --> GATE
    GATE -->|极少数| CORE
    GATE -->|大多数| EXT
    GATE -->|纯提示词就够| SKILL
    EXT --> PKG
    SKILL --> PKG

Skill 那条支线用的是同一套算式:启动时只把每个 skill 的名字和描述放进上下文,模型判断任务匹配了才用 read 去加载完整的 SKILL.md。文档里管这叫 progressive disclosure。说白了还是那句话——常驻的东西必须便宜,贵的东西必须按需。

顺带一提,这个分层在依赖上也守得住。pi-tui 这个 16740 行的终端 UI 库,运行时依赖只有两个:get-east-asian-widthmarkedpi-agent-core 六个。这不是运气,是分层真的分开了。


支点三:架构给了拒绝的能力,治理给了拒绝的执行力

前两个支点解决"拒绝之后用户怎么办"。但还有个更难的问题:谁来拒绝?

这是我觉得很多讲"最小内核"的文章会跳过的地方。架构上留好扩展点,只是让拒绝变得可能;真正把功能挡在门外,还得有人天天说不。而说不是件消耗人的事——尤其当项目有 9.3 万 star、1.1 万 fork 的时候。

Pi 的做法相当不客气。CONTRIBUTING.md 第一条规则叫 The One Rule

You must understand your code. If you cannot explain what your changes do and how they interact with the rest of the system, your PR will be closed.

Using AI to write code is fine. Submitting AI-generated slop without understanding it is not.

然后是默认关闭:所有新贡献者的 issue 和 PR 一律先自动关掉,维护者每天回看,觉得值得的再打开。想要豁免,得维护者在回复里写 lgtmi(以后你的 issue 不自动关)或者 lgtm(issue 和 PR 都不关)。周五到周日提的 issue 不保证被看。作者自己也没藏着:

as with all my open source projects, I tend to be dictatorial.

我知道这条会让不少人不舒服。但把它放回"最小内核"的语境里看,逻辑是闭合的:最小内核和开放贡献是天然对立的两件事。 一个内核想保持小,就必须有人有权力说"这个不进来",而且这个权力不能被"可是我已经写完了"稀释。auto-close 本质上是把"证明这个功能值得进内核"的举证责任交还给提出者。

同一套逻辑也写进了给 AI 的规则里。AGENTS.md 有两条我觉得任何团队都该抄:

  • Always ask before removing functionality or code that appears intentional.
  • Do not preserve backward compatibility unless the user asks for it.

第一条防 AI 手抖删东西,第二条防"为了兼容而积累复杂度"。还有一条更细的,专治 AI 写代码时的坏习惯:

Never hardcode key checks (e.g. matchesKey(keyData, "ctrl+x")). Add defaults to DEFAULT_EDITOR_KEYBINDINGS or DEFAULT_APP_KEYBINDINGS so they stay configurable.

这条特别值得玩味。它不是在管代码风格,是在用规则保护架构约束——快捷键必须走可配置的表,不许写死。AI 生成代码时最容易干的就是"这里直接判断一下得了",一次两次没事,攒够了架构就烂了。把这种约束写进 AGENTS.md,等于给架构上了个自动化的保险。

还有个数字挺能说明克制是有代价的:6222 个 commit 里有 19 个 Revert。翻一下最近的历史,Revert "feat(agent): expose provider context construction"Revert "feat(coding-agent): add cache-friendly compaction primitives"——都是自己人提的 feature,进去了又拉出来。能撤自己的提交,比能拒别人的 PR 更难。


实战:把 Pi 当 Agent 引擎嵌进自己的项目

前面都在读别人的代码。这一节换个角度:如果我要在自己项目里做一个 AI Agent,Pi 能不能当引擎用?

先说结论:能,而且这恰恰是它"最小内核"的直接红利——因为该拒绝的都拒绝了,剩下的部分足够薄,薄到你能整个塞进自己的进程里。它一共给了四条集成路径,选错了会很难受:

路径 怎么用 什么时候选
SDK import { createAgentSession } 你也是 Node/TS 进程,要类型安全和状态直读
RPC pi --mode rpc,stdin/stdout 收发 JSONL 宿主是 Java/Go/Python,要进程隔离
Print/JSON pi -ppi --mode json CI、脚本、一次性任务
Extension 在 pi 里注册工具和命令 你想扩的是 pi 本身,不是把 pi 嵌进别处

下面用 SDK 走一个完整例子。我挑的场景是代码库合规巡检——给定一个仓库,让 agent 自己找出违反团队规约的地方,产出结构化结果写进我们自己的系统。这个场景选得有私心:它同时用到只读工具收敛、自定义工具、事件流审计三样东西,而这三样正好是把 Pi 当引擎用的关键。

第一步:只给它该有的工具

import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";

const { session } = await createAgentSession({
  cwd: "/path/to/target-repo",
  // 巡检只需要读,不给 edit/write/bash——从工具层面就杜绝改代码
  tools: ["read", "grep", "find", "ls"],
  // 不落盘,巡检任务不需要会话历史
  sessionManager: SessionManager.inMemory("/path/to/target-repo"),
});

这三行就是我认为 Pi 最值钱的地方。默认工具是 read/write/edit/bash,一个 tools 数组就能砍到只读。在别的框架里"让 agent 只读"往往要靠提示词恳求模型别乱动,这里是工具压根不存在——模型想调也没得调。

顺带说一句,这比"加一个权限确认弹窗"干净得多。能力收敛应该发生在工具注册这一层,而不是等模型调用了再拦。

第二步:加一个自己的工具,把结果收进业务系统

巡检结果不能只打印在终端上,得进我们自己的库。这里用 defineTool

import { Type } from "typebox";
import { StringEnum } from "@earendil-works/pi-ai";
import { createAgentSession, defineTool, SessionManager } from "@earendil-works/pi-coding-agent";

interface Finding {
  file: string;
  line: number;
  rule: string;
  severity: "high" | "medium" | "low";
  detail: string;
}

const findings: Finding[] = [];

const reportFinding = defineTool({
  name: "report_finding",
  label: "Report Finding",
  description: "Report one compliance violation found in the codebase. Call once per violation.",
  // 不写 promptSnippet 的话,这个工具不会出现在系统提示词的 Available tools 一节
  promptSnippet: "Record one compliance violation",
  parameters: Type.Object({
    file: Type.String({ description: "Relative path of the offending file" }),
    line: Type.Number({ description: "Line number, 1-based" }),
    rule: Type.String({ description: "Rule id, e.g. LOG-001" }),
    // 用 StringEnum 而不是 Type.Union,理由见下文
    severity: StringEnum(["high", "medium", "low"] as const, { description: "Severity" }),
    detail: Type.String({ description: "One sentence explaining the violation" }),
  }),
  execute: async (_toolCallId, params) => {
    findings.push(params);
    return {
      content: [{ type: "text", text: `recorded ${params.rule} at ${params.file}:${params.line}` }],
      details: {},
    };
  },
});

const { session } = await createAgentSession({
  cwd: repoPath,
  tools: ["read", "grep", "find", "ls", "report_finding"],
  customTools: [reportFinding],
  sessionManager: SessionManager.inMemory(repoPath),
});

有两个坑我得单独标出来,都是读源码才确认的:

一、一旦你传了 tools 允许列表,自定义工具的名字也必须写进去,否则会被挡掉。_refreshToolRegistry() 里对内置工具和自定义工具用的是同一个 isAllowedTool() 过滤,customTools 并不豁免。我第一遍读文档时以为它是自动启用的。

二、自定义工具不写 promptSnippet,就不会出现在系统提示词的 Available tools 列表里。 工具本身能调用(schema 会发给模型),但那份清单里看不到它。这是前面讲的"按 token 计价"的延伸——默认不占系统提示词的位置,想占得自己申请。

还有个第三点,属于跨 provider 的兼容陷阱:枚举别用 Type.Union([Type.Literal(...)]),用 pi-ai 导出的 StringEnum 前者生成的 JSON Schema 是 anyOf,Google 的 API 不接受;StringEnum 生成的是 { type: "string", enum: [...] },各家都认。Pi 专门为此导出了这个 helper——你要是打算让用户随时切模型,这种差异必须提前吃掉。

为什么走"让模型调工具上报"而不是"让模型输出一段 JSON 然后我去解析"?因为工具调用的参数是有 schema 约束的——severity 只能是那三个值,line 必须是数字,Pi 在执行前会按 TypeBox schema 校验。让模型自由输出 JSON,你就得自己处理它偶尔多写一个字段、少一个逗号、把数字写成字符串。这不是省事,这是把校验责任放到了对的位置。

第三步:用事件流做审计和可观测

这是我觉得容易被忽略的一环。生产环境跑 agent,最怕的是"它到底干了什么不知道"。Pi 的事件流可以直接接到你现有的日志和 metrics 上:

const startedAt = Date.now();
let toolCalls = 0;

session.subscribe((event) => {
  switch (event.type) {
    case "tool_execution_start":
      toolCalls++;
      logger.info({ tool: event.toolName, args: event.args }, "agent tool call");
      break;
    case "tool_execution_end":
      if (event.isError) {
        logger.warn({ tool: event.toolName }, "agent tool failed");
      }
      break;
    case "turn_end":
      // 一轮 LLM 响应 + 工具调用结束,适合在这里做预算检查
      break;
    case "agent_end":
      logger.info({ ms: Date.now() - startedAt, toolCalls }, "agent finished");
      break;
  }
});

await session.prompt(
  "巡检本仓库的日志代码:找出所有把用户邮箱、手机号、身份证号直接写进日志的地方。" +
  "用 grep 和 read 定位,每发现一处调用一次 report_finding。全部检查完后停止。",
);

session.dispose();

// findings 现在是结构化结果,可以入库、发通知、生成报告
await saveToDatabase(findings);

session.dispose() 别忘了——官方示例全都是 try / finally 包起来调用的,长驻服务里漏掉会攒资源。

跑完之后 session.state.messages 里有完整的消息历史,session.state 直接暴露 agent 的内部状态(messagesmodeltoolssystemPrompt)。这种"状态直读"是同进程 SDK 相对 RPC 的主要优势。

整条链路

flowchart TD
    APP["你的业务服务<br/>Node / TypeScript"]
    CAS["createAgentSession()<br/>cwd / tools / customTools"]
    SESS["AgentSession<br/>prompt · subscribe · state"]
    TOOLS["只读内置工具<br/>read grep find ls"]
    MYTOOL["report_finding<br/>TypeBox schema 校验"]
    LLM["LLM Provider<br/>三十余家可切换"]
    SINK["业务落点<br/>数据库 / 告警 / 报表"]
    OBS["日志与 metrics<br/>来自事件流"]

    APP --> CAS --> SESS
    SESS <--> LLM
    SESS --> TOOLS
    SESS --> MYTOOL
    MYTOOL --> SINK
    SESS -.事件流.-> OBS

换成非 Node 宿主:走 RPC

如果你的服务是 Java 或 Go 写的,就别硬啃 SDK 了,起个子进程走 RPC:

pi --mode rpc --no-session

协议是 LF 分隔的 JSONL。文档里有一条警告值得抄进自己的实现注释:

RPC mode uses strict LF-delimited JSONL framing. Clients must split records on \n only. Do not use generic line readers like Node readline, which also split on Unicode separators inside JSON payloads.

按 Unicode 分隔符切行的通用 reader 会把 JSON 载荷本身切开。这种 bug 排查起来很折磨——数据里刚好出现某个字符才复现。

选型时我会问的三个问题

1. 模型可见面要多大? 这是从 Pi 学到的第一条,也适用于选型本身。你的 agent 真的需要二十个工具吗?我上面那个巡检 agent 是四个内置 + 一个自定义。工具越少,模型越不容易走偏,token 也越省。

2. 能力边界靠什么保证? 靠提示词说"请不要修改文件"是不设防的;靠 tools: ["read", "grep"] 是真的防住了。能在类型和注册层面表达的约束,不要放到提示词里表达。

3. 出了问题能不能查? 如果事件流没接到你的日志系统,那这个 agent 在生产环境就是个黑盒。先把 tool_execution_start 记下来再上线。

至于 Pi 适不适合当引擎,我的看法是:它适合"agent 逻辑简单、但要嵌得深"的场景——你要控工具集、要自定义工具落业务、要事件流可观测。反过来,如果你要的是多 agent 编排、复杂状态机、图式工作流,Pi 什么都不给你,那些得自己在上面搭。这是它"不做 sub-agent"的直接后果,不是缺陷,是它划的线。


代价:它把成本转给了你

到这里都是好话,得说说另一面。这套设计的代价是真实的,而且转给了使用者。

最明显的是安全。 Pi 没有沙箱,也没有权限系统,README 里写得很直白:

Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access. By default, it runs with the permissions of the user and process that launched it.

docs/security.md 给的理由我倒是认同:

A partial in-process sandbox would be easy to misunderstand as a security boundary while still depending on the host shell, filesystem, package managers, credentials, and extension code. Real isolation needs to come from the operating system or a virtualization/container boundary.

半个沙箱比没有沙箱更危险,因为它给人虚假的安全感——这个判断在安全领域是站得住的。但"所以真隔离交给容器"这句话,意味着你得自己会搭那套容器。它有个叫 project trust 的机制,但文档里也明确说了那只是"输入加载的闸门",防的是仓库偷偷改你的配置,不是防模型干坏事。

其次是开箱体验。 别人装完就有的东西,你得先装扩展或者自己写。pi install npm:@foo/pi-tools 这条路是通的,但生态得靠社区长出来,而扩展是能跑任意代码的——README 自己都在警告"安装第三方包前先读源码"。

再者是"minimal"这个词本身容易招误解。 我建议看这个项目的人分清两件事:模型可见面确实极小(不到 1000 token,四个工具),但代码库不小,交互模式那部分光 src/modes/ 就 19626 行。它是"给模型的接口最小",不是"实现最小"。如果你带着"代码少所以容易读懂"的期待进去,会失望。

最后,这套玩法有适用边界。 独裁式治理配上一个能力极强、一年下来平均每天十个 commit 的作者,是这套模式跑得动的前提。换成一个五人小队、没人有最终决定权的项目,auto-close 只会招骂而挡不住 bloat。架构可以抄,治理未必抄得动。


可以抄的部分

1. 先定"最小"的度量单位,再谈最小化。 Pi 量的是 token。你的项目量什么?可能是启动时间、API 表面积、配置项个数、新人上手时长。没有单位的"我们要保持简单"是句空话,因为它没法判定一个 PR 是变好还是变坏。 定完单位就把它写进贡献指南,让每个 PR 都能被这个尺子量。

2. 加功能前先问一句:这个能力能不能表达成用户已经会的东西。 Pi 用 CLI + README 换掉 MCP,用 TODO.md 换掉内置 todo,用 tmux 换掉后台任务。这不是偷懒,是不为已有能力重复付费。翻译到日常工作上:能用配置文件解决的不要做成后台管理页面,能用现有 CLI 拼出来的不要包一层新服务。

3. 拒绝之前,先把泄压阀修好。 只说"我们不做这个"会流失用户;说"我们不内置,但这是你自己做的入口"才站得住。所以顺序是:先把扩展点做对,再开始拒绝。 顺序倒了,拒绝就是耍横。

4. 让扩展点能覆盖内置实现,而不是只能追加。 只能 append 的插件系统会逼出"能不能给内核加个开关"的需求,一个个开关攒起来就是 bloat。允许同名覆盖,用户的需求就能自己消化掉。

5. 示例代码要当产品发布,不要当文档附件。 Pi 把 examples/ 打进 npm 包,还在系统提示词里告诉模型去哪儿读。"你自己写扩展"这句话的可信度,全靠这 15845 行示例撑着。

6. 把架构约束写成 AI 能执行的规则。 "不许写死快捷键,加到 DEFAULT_KEYBINDINGS 里"这种规则,放进 AGENTS.md 就是自动生效的架构护栏。AI 时代的架构腐化速度比人手写的时代快得多,靠 code review 兜不住,得靠规则前置。

7. 允许自己撤销。 19 个 Revert。功能进去了发现不对就拉出来,比在门口纠结三个月更健康。

8. 自己接 agent 时,把约束放在注册层而不是提示词里。 这条是从上面那个巡检例子里得到的:想让 agent 只读,tools: ["read", "grep"] 是真防住了,提示词里写"请勿修改文件"只是许愿。能用类型和配置表达的约束,别指望模型自觉。


总结:克制是设计出来的,不是忍出来的

回到开头那份"不做"清单。我现在的理解是,它读起来像一份态度宣言,实际上是三样东西的输出:一个能判定对错的度量单位(token 而不是行数)、一套让需求有地方去的扩展点、以及一个敢说不的守门人。三个都在,说"不"才是低成本的;缺任何一个,功能就会顺着缝隙漏进内核。

所以下次开会有人提"我们要保持简单"的时候,别接"对,说得好"。接三个问题:简单用什么单位量?用户想要的那个功能去哪儿实现?以及,谁有权说不、说了不之后谁来扛?

答不上来的"简单",通常撑不过下一个季度。


参考