A write is not just a write

A write is not just a write

“写入”不仅仅是“写入”

The point became clear to me while working on the GitHub adapter (a more or less simple GitHub gateway for AI agents creating PRs). At first, creating a pull request looks like one write. A branch is created, commits are pushed, and a PR appears. The API call is concrete, and the result is easy to name. 在开发 GitHub 适配器(一个用于 AI 代理创建 PR 的简易 GitHub 网关)时,我深刻意识到了这一点。起初,创建一个拉取请求(Pull Request)看起来只是一次“写入”操作:创建一个分支,推送提交,然后 PR 就出现了。API 调用是具体的,结果也很容易定义。

But after building around it for a while, the word “write” started to feel too imprecise. A pull request is not only a technical object. It changes the state of a repository and creates something that another person may have to review. If it duplicates an existing proposal, it can already be noise before anyone reviews it. If it touches a deployment file instead of a README, the same operation suddenly has a very different weight. The operation name did not change. The effect did. 但在围绕它开发了一段时间后,我开始觉得“写入”这个词太不精确了。拉取请求不仅仅是一个技术对象。它改变了仓库的状态,并创建了需要他人审核的内容。如果它重复了现有的提案,那么在任何人审核之前,它就已经构成了干扰。如果它修改的是部署文件而不是 README,那么同样的操作瞬间就有了完全不同的分量。操作名称没变,但影响变了。

In the previous parts, I argued that the agent should not hold the write credentials, that tool access is not the same as impact permission, and that blocked requests should return boundary feedback instead of generic tool failures. This leads to the next point (or let’s call it problem). Even if write authority is moved behind a boundary, the word “write” is still too coarse. A permission table can say that an action may write. That is useful, because it separates reading from changing. However, it does not say where the change lands, who sees it, which systems react to it, or whether another decision is still possible before the effect becomes larger. 在之前的文章中,我曾提出:代理不应持有写入凭证;工具访问权限不等同于影响权限;被拦截的请求应返回边界反馈,而不是通用的工具故障。这引出了下一个观点(或者说是问题):即使将写入权限置于边界之后,“写入”这个词依然过于粗糙。权限表可以规定某个操作允许“写入”,这很有用,因为它区分了读取和修改。然而,它并没有说明修改落在了哪里、谁会看到它、哪些系统会对其做出反应,或者在影响扩大之前是否还有决策空间。

A draft and a sent email are both writes. A pull request and a merge are both writes. Updating test data and changing production data are both writes. Technically, all of them change state. Operationally, they are not the same event. This is the weakness of flat write access. A system can enforce the permission correctly and still allow the wrong kind of effect. 草稿和已发送的邮件都是“写入”。拉取请求和合并都是“写入”。更新测试数据和更改生产数据也都是“写入”。从技术上讲,它们都改变了状态;但从操作层面看,它们并非同一类事件。这就是扁平化写入权限的弱点:系统可以正确执行权限控制,但仍然可能导致错误的影响。

Reach is the missing property

“影响范围”(Reach)是缺失的属性

The useful distinction is not only whether an action changes state. The useful distinction is reach. By reach, I mean how far the effect travels before another decision is required. A change that stays inside an isolated workspace has low reach. A change that creates work for another person, becomes visible to users, changes production, or sends something outside the system has higher reach. 有意义的区分不仅在于操作是否改变了状态,更在于“影响范围”(Reach)。我所说的“影响范围”,是指在需要下一次决策之前,该操作的影响能传播多远。局限在隔离工作区内的变更,影响范围较小;而那些会给他人增加工作量、对用户可见、改变生产环境或向系统外部发送数据的变更,则具有更大的影响范围。

This is why the same technical write may need different treatment. A draft email still stays inside a controlled workflow, while a sent email leaves it. A pull request is not a merge, but it already changes the repository workflow. A staging change can be acceptable with little friction, while the same change against production should pass a stronger gate. The boundary should therefore not only ask whether the verb is allowed. It should ask how far the requested effect is allowed to go. This is the point where “write” stops being a sufficient impact class. 这就是为什么同样的技术性“写入”可能需要不同的处理方式。草稿邮件仍处于受控工作流中,而发送邮件则脱离了该流程。拉取请求虽不是合并,但它已经改变了仓库的工作流。针对测试环境的变更可以轻松接受,但同样的变更应用到生产环境则必须通过更严格的门槛。因此,边界检查不应只询问“是否允许该动作”,而应询问“允许该操作的影响传播多远”。这就是“写入”不再足以作为影响分类标准的原因。

Reversibility is not enough

“可逆性”是不够的

A tempting classification is reversible vs. irreversible. This helps, but it is not stable enough as the main boundary. Real systems are rarely cleanly reversible. A draft may allocate an ID. A temporary object may leave logs, cache entries, metrics, notifications, or review noise. A staging change may start automatic work. A pull request may consume reviewer attention even if it is later closed. The primary object may be deleted, but the system has already changed. 一种诱人的分类方式是“可逆”与“不可逆”。这有一定帮助,但作为核心边界标准还不够稳定。现实中的系统很少能做到完全彻底的“可逆”。草稿可能会占用一个 ID;临时对象可能会留下日志、缓存条目、指标、通知或审核干扰;测试环境的变更可能会触发自动化任务;拉取请求即使后来被关闭,也已经消耗了审核者的注意力。主要对象或许可以被删除,但系统状态已经发生了改变。

Therefore, reversibility should only be a secondary signal. A reversible action can still cross an important boundary. An action that cannot be fully undone can still be isolated and low impact. What matters for admission is not only whether the object can be removed, but what became visible, costly, dependent, or relevant while it existed. Reach captures that better than reversibility. 因此,可逆性只能作为次要信号。一个可逆的操作仍可能跨越重要的边界;而一个无法完全撤销的操作也可能被隔离在低影响范围内。准入控制的关键不仅在于对象是否可以被删除,还在于它存在期间产生了哪些可见的、昂贵的、依赖性的或相关的影响。相比可逆性,“影响范围”能更好地捕捉到这一点。

The boundary needs structure, not guesswork

边界需要结构,而非猜测

A boundary cannot reliably control reach if the agent only submits plain language. A request such as “update the customer record” is not enough. It may mean a harmless internal note, a billing change, a correction in production data, or a change that notifies someone. These are different effects, even if the sentence looks similar. A second model may help as an additional signal, but it should not be the main boundary. If the control layer has to read a paragraph and guess the effect, the hard part has only moved into another interpretation step. 如果代理只提交自然语言,边界就无法可靠地控制影响范围。像“更新客户记录”这样的请求是不够的。它可能意味着一条无害的内部备注、一次账单变更、生产数据的修正,或者是一次会通知他人的变更。即使句子看起来相似,它们的影响也截然不同。第二个模型或许可以作为辅助信号,但不应成为主要边界。如果控制层必须阅读一段文字并猜测其影响,那么难题只是转移到了另一个解释步骤中。

The cleaner design is that the agent submits a structured intent before an external effect is possible. The request should make the relevant parts explicit: the kind of action, the target, the expected state, the environment, and the intended result. This does not mean that the agent gets to declare its own safety. The agent can propose what it wants to do. But the system around the boundary has to make the relevant effect visible. 更简洁的设计是:在产生外部影响之前,代理必须提交结构化的意图。请求应明确相关要素:操作类型、目标、预期状态、环境以及预期结果。这并不意味着代理可以自行声明安全性。代理可以提出它想做什么,但边界周围的系统必须使相关影响变得可见。

A path, repository, branch, account, environment, URL, or table is not only a string in the request. It has to be resolved by the system that owns the boundary. This is important because a tool name does not tell us enough. A generic tool may be able to do harmless and risky work. Treating the whole tool as high impact creates review noise. Treating it as low impact misses dangerous use. 路径、仓库、分支、账户、环境、URL 或表不仅仅是请求中的字符串,它们必须由拥有边界的系统进行解析。这一点至关重要,因为工具名称本身提供的信息不足。一个通用工具既能执行无害操作,也能执行高风险操作。将整个工具视为高影响会产生过多的审核干扰,而将其视为低影响则会漏掉危险的使用行为。

The useful classification is therefore not “this tool is safe” or “this tool is dangerous”. The useful classification depends on the resolved target and on the effect that the interface exposes. A write to an agent-private scratch folder is different from a write to a deployment file. A request to a status endpoint is different from a request that changes billing. A test data update is different from a production data update. This only works when the interface exposes enough detail. 因此,有意义的分类不是“这个工具安全”或“这个工具危险”,而是取决于解析后的目标以及接口所暴露的影响。向代理私有的临时文件夹写入与向部署文件写入是不同的;向状态端点发送请求与更改账单的请求是不同的;更新测试数据与更新生产数据也是不同的。只有当接口暴露了足够的细节时,这种机制才能奏效。

If the target cannot be resolved, the request should be treated as broad. If a tool hides what it may change, the boundary cannot magically recover that information from the tool name. In that case, the safer answer is to wrap the tool with a narrower interface, require review, or block the request. Narrow admission only works when narrow effects are visible. The tool is not the boundary. 如果目标无法解析,该请求应被视为“影响范围广”。如果一个工具隐藏了它可能改变的内容,边界无法仅凭工具名称神奇地恢复这些信息。在这种情况下,更安全的做法是为工具封装一个更窄的接口、要求人工审核或直接拦截请求。只有当影响范围清晰可见时,精细化的准入控制才能生效。工具本身并不是边界。