Claude 被禁止为我写散文式报告:用结构化块替代
Claude Isn't Allowed to Write Me Prose
一位同时运行约十几个 agent 的用户在全局 CLAUDE.md 中禁用 "honestly"、"genuine" 等措辞和虚假对比句式,但发现风格规则只改变用词、不改变报告形态,Opus 5 之后散文变得难以忍受。
我的全局 CLAUDE.md 里有一个叫 Wording 的部分。它禁止使用“honestly”、“genuine”、“landed”以及短语“earns its keep”。后面还有一个部分禁止虚假对比的配对,比如“This is not theoretical. It is standard engineering.”,以及为追求冲击力而写的一连串短句和单行结尾。每条规则的存在都是因为日复一日地读 Claude 的文字让我疲惫不堪。
我同时运行大约十几个代理,因此我能否在几秒钟内阅读和处理来自代理的报告,还是需要几分钟,会影响我的工作速度。直到最近,我都把这当作一个写作风格问题:如果报告仍然难以阅读,那么风格规则就还不完整。这个列表不断增长,在 Opus 5 发布后增长最快,那时文字变得令人难以忍受。
Opus 5.5 写得好多了,但找到报告中重要的部分仍然是一件苦差事:模型会详细告诉我它遇到了哪些问题、如何尝试解决以及走了哪些路径,而其中大部分我都不需要。在中间某个地方,往往是顺便提到,它注意到某件事可能是个问题,可能需要进一步查看,而那句话通常埋在我本可以跳过的细节中。
我现在确信我一直在错误的层面上工作。告诉模型如何写,以及不要写什么,仍然让它写出任意长度和形状的文章;规则只改变了其中的文字。而 CLAUDE.md 中的规则只是一个请求。我需要从报告中得到的是我必须决定的两三件事,而文字即使写得很好也会隐藏这些。

如果,相反,模型只能通过一小组预定义的块与我交流,每个块都有它必须填写的字段,会怎样?
- 为了告诉我它改变了什么,它使用一个 change 块:一句话总结,加上一个在我打开之前保持折叠的较长描述。
- 为了告诉我它学到了什么,它使用一个 finding 块,该块还必须说明这个发现有多重要,以及它是否阻止工作。
- 为了告诉我它如何检查工作,它使用一个 check 块:通过、失败或未运行,如果未运行,为什么没有运行。
- 为了让我决定某事,它使用一个 question 块,包含两到六个选项,每个选项说明选择它会做什么。
一个测试框架会在每份报告到达我之前检查它,并退回任何不符合要求的内容(作为工具调用错误),因此模型永远无法递给我一篇文章。它仍然决定告诉我什么,但它告诉我的一切都必须放入这些块之一。
UI 可以用块做什么
一旦输出是一个类型化块的列表,UI 就可以按照我想阅读的方式呈现它。以下是可以做什么的例子:
我选择顺序
UI 不需要按照模型发送块的顺序显示它们:
决策变成按钮
点击一个选项,或者点击面板并按 1 或 2。
推荐的选项被标记
当模型建议做什么的选项时,我经常要求告诉我它推荐什么以及为什么。不需要往返——块可以直接要求预先提供这些信息:
细节可用但折叠
长描述、日志和证据隐藏在一行摘要后面,直到我打开我想要的那些:
一切都在一张卡片中
或者以下是我会如何将其组织成一张卡片:
点击一张卡片,然后按 1 到 4 选择一个选项,或按 0 打开或关闭细节。
作为对比,这是 Claude Code 实际结束该会话时使用的文本:

在卡片上,我花10-15秒检查标题、可能的选项,也许扫一眼“发生了什么”部分。而模型生成的文字,我需要逐字阅读和处理——花费的时间和精力要多得多。
这改变了什么
这把“智能体可以向你展示什么”的问题,从模型转移到了运行它的人身上。如今,模型决定一切,以文字形式呈现,界面则原样打印收到的内容。有了固定的块集合,模型仍然选择使用哪些块以及块里放什么,而由我决定存在哪些块以及每个块必须包含什么。
这也让交互更加稳健。模型撰写报告的方式因模型而异,甚至同一模型的次要版本之间也不同。有了块,新模型改变的是填入字段的内容,而我阅读的布局和按下的按钮保持不变。
来自一个工作智能体的同一份报告
为了尝试这个想法,我把它构建进了kvit-coder,一个我在开发的小型编码智能体。下面是同一个结果,写成它的一份报告并由其渲染器打印出来:

这与风格规则的区别在于,报告是一次工具调用,也是模型在kvit-coder中停止工作的唯一方式。
一组块
存在哪些块是一种设计选择,以下是kvit-coder目前使用的一组:
| 块 | 用于 | 必须包含 |
|---|---|---|
| change | 编辑和其他输出 | 摘要 |
| finding | 学到的东西:根本原因、答案 | 为什么重要;程度(从低到关键)是可选的 |
| check | 工作是如何被检查的 | 通过、失败、部分完成或未运行;对于部分完成或未运行,说明未检查什么以及原因 |
| warning | 风险,或阻碍工作的因素 | 严重程度,以及如果被忽略会破坏什么 |
| question | 只有我能决定的事:一个选择,或我掌握的信息 | 2到6个选项,每个说明选择它会做什么;推荐选项的理由,或为什么没有推荐 |
| next | 提议的后续步骤 | 一个运行它的选项,附带将发送的指令,以及至少一个替代方案;推荐选项及其理由 |
| unclassified | 当其他都不合适时的最后手段 | 为什么没有其他类型合适 |
通过改变块来改变智能体的行为
当文字报告出错时,唯一的补救办法是在提示词中再加一行。当块报告出错时,失败通常是某个特定块缺失或某个字段被用错了地方,修复就针对那个地方。
非文本的块
这种方法不要求块必须是文本。一个块类型是模型填充的schema加上UI中绘制它的代码,因此能绘制图片的UI可以为模型提供图片块:
- 图表块。模型提供节点和边,或Mermaid源码,以及说明文字,检查可以确保源码在报告被接受前能解析。重构之后,“请求现在按此顺序经过这三个服务”就属于这里。
- 绘图块。模型提供数据系列、轴标签和单位,以及生成这些数字的命令,检查可以确保每个系列与轴匹配。基准测试结果,或更改前后的内存使用,会放在这里。
- 分步块。模型提供有序的状态列表,每个状态附有说明,UI将它们作为动画播放或按一次键逐步查看,这适合竞态条件或数据迁移。
下面是本文前面提到的游戏 bug 的分步演示。方框、箭头以及每一步的变化都来自“模型发送的内容”下的数据;页面只负责绘制。
在这些情况中,模型都不绘制任何东西:它决定应该显示什么并提供内容,而 UI 代码每次都以相同的方式绘制。如今,一个想要解释三个服务如何协同工作的 agent 会写三段文字或一个 ASCII 图,因为文本是其界面所能显示的全部。上图是一个原型:这些块在 kvit-coder 中尚不存在。
试用一下
完整版本在 kvit-coder 中,位于 internal/report 下:report.go 包含块类型和 schema,validate.go 是检查,render.go 是布局。
其中一部分通过 What’s Next 插件 在 Claude Code 中工作,该插件使用一个 hook 来要求改变了某些内容的回合以 Claude Code 内置的问题卡片结束。

来源:Hacker News · blog.kvit.app