Skip to content

10|人机协作不在循环里:审批、命令、提问为什么是另一条平面 ​

先别把斜杠命令发给模型 ​

人在输入框里打了一行斜杠命令,或者工具管道弹了一张"允许这次执行吗"的确认框,或者模型决定问人一道选择题。三件事看起来都像"跟人说句话",于是最省事的实现是:把它们当成一条特殊的用户消息,或者让循环进入一种"等待人类"的内部状态。

这个系统把这三件事收在另一条平面上:人用来和正在跑的 Agent 协作的一组服务——提问、审批、权限预设、命令。它们走已有的 Agent 和会话合同,不改循环。

命令的挂钩就一句话:登记到一个专门的命令注册表,派发时不经过模型的一次 turn。人机命令的定义也钉得很死——斜杠开头、由人这一侧的适配器解释执行的指令,不会变成模型消息;它既不是模型面对的工具,也不是命令执行能力(比如跑一段 shell 命令)。三种人机动作,三条缝,零次"循环特供状态"。


1. 命令平面:斜杠在字节 0,不在对话记录里 ​

先给判断:斜杠命令能被识别,是因为适配器在把文本交给"追加下一轮"这个动作之前,先问了命令注册表。问过了,循环连这行字都看不见。

命令解析只认一件事:第一个字节是斜杠,后面是合法的命令名(字母、数字、下划线、短横线),然后结束或遇到空白。名字后面的每一个字节,包括那截空白,原样当作"原始输入"传给具体的命令处理器。注册表不替你解析参数——各命令自己的语法,各命令自己认。

执行动作的边界写在它自己的定义里:解析并执行一条已知命令,不发给模型。命中一条命令,会先往这个 Agent 的会话上追加一条"仅写日志"的"命令开始运行"记录,再跑处理器,结算时追加一条"命令执行完成"记录。这两笔记录都是直接追加,没有 turn 包着它们——它们跟一次模型请求的记账单位无关。语法不对或者名字未知,直接返回"无法识别",什么都不记——这类输入从未真正进入过任何处理器。

命令的执行结果由适配器直接渲染,永不进入模型历史。注册表从不偷偷把原始输入转交给 Agent 当作一条消息。如果某个命令的处理器确实需要调度一段模型可见的工作(比如某个命令在确认了某种模式之后,需要正式追加一条下一轮消息),那份"发消息"的责任归这个命令处理器自己,不归命令注册表本身。

未知的斜杠命令,会被适配器直接拒绝,不会当成普通对话内容转发给模型。这看起来不近人情,却消灭了一种更脏的失败:模型把一个打错的命令当成一句闲聊,再一本正经地回复"好的我来处理"。命令的发现、执行、界面输出都不消耗模型的处理配额,也不会打乱模型侧的缓存状态。

登记方式跟第 08 章同一套:普通上下文登记的命令全局生效;某个 Agent 专属上下文登记的命令插件可以声明自己需要的依赖,铸一份恰好属于这个 Agent 的命令定义,遮蔽同名的全局命令。这种"只对某个子上下文注入"的设计,特意避免让核心循环去依赖一个界面层的服务。循环继续不认识任何具体的斜杠命令。界面热更新监听命令表变化的通知,观察者出错也不能否决登记这个动作本身。

如果斜杠命令也走"追加下一轮"会怎样?每条命令都会烧一次模型请求——压缩、切换权限、查看长期任务状态,全部消耗模型配额。模型甚至可能"决定不执行"你已经明确发出的指令。命令输出一旦进入模型可见的表面层,第 05 章的投影会把它当成历史的一部分,缓存状态被打乱,恢复会话之后模型还"记得"你昨天某条命令失败时看到的界面提示文本。单独一条平面,让这些交互永远只是人跟适配器之间的事。


2. 审批:管道上的一次决策,不是一轮对话 ​

判断:审批是第 07 章"询问"决策落到另一条可拦截链上的具体实现。循环仍然只是停在"一次工具调用还没返回结果"这个状态上。人面对的是一张一次性许可,模型面对的是最终那条工具结果。

第 07 章已经把"询问"画在执行前检查点和闸门之间。真正兑现这个决策的逻辑是:去问是否挂载了审批服务,没挂就直接降级成拒绝;调用如果连所属的 Agent 都没有,也直接拒绝——没有会话可以审计,没有界面可以路由这次请求。否则就正式发起一次审批请求,带上是谁在问、要执行什么、为什么要问、以及取消信号。"仅本次允许"就放行;拒绝、取消、或者审批通道整体不可用,会带着不同的理由分别否决,好让模型分清"人明确说不"和"审批通道压根没接上"。

审批服务的应答者只承认四个结果词——放行一次、拒绝、取消、不可用;缺失应答者或应答失败时默认拒绝;一次许可只覆盖被请求的那一个具体动作。应答者本身也是一条可拦截链上的监听器:返回明确结果就拍板,委托下去就交给下一层。每个部署应该组合出恰好一个最终拍板的应答者——多个监听器的先后顺序不代表谁的优先级更高。某些自动化协议桥会为它拥有的会话提供一次性的机器决策,充当这个最终应答者。

审计记录在日志里,不在模型里。审批的"发起询问"和"给出答案"这两笔记录被明确标记为不属于模型可见的表面层。发起审批请求之前,会先检查当前是否存在一个还没关闭的 turn——没有就直接报错,因为这一对审计记录必须被某个 turn 包住,否则重新加载会话时会变成两个 turn 之间的孤儿数据。然后追加"发起询问"记录,等待决定,再追加"给出答案"记录。任何一侧的追加操作在提交前失败,整个请求直接判定失败:宁可返回一个明确的失败,也不返回一个没有真正入账的决定。

模型看见什么?那两笔审计记录是仅写日志的,模型看不到。模型只看见这次工具调用最终得到的那条工具结果——允许、拒绝、取消,或者通道不可用。人点按钮时看到的界面本身不是模型上下文的一部分。当前的审批策略(比如"总是询问"还是"从不询问")会通过运行时上下文的一份快照告诉模型,而不是把整段审批对话逐条塞进对话记录里。

"仅本次允许"是唯一的许可粒度。没有"记住这次,以后都准"这种选项。要改变默认策略,走专门的策略设置接口,或者下面要讲的权限预设——那是另一笔耐久事件,不是这张一次性许可票的副作用。

如果审批做成一轮新 turn 会怎样?人点"允许"会变成一条用户消息,模型可能重新决定要不要再调用同一个工具,说不定还得再弹一次审批框。或者循环进入一种"暂停"状态,恢复协议变成第二套独立的待办队列。这里的选择是:工具调用对应的那个执行过程还没结束,人只是这个执行过程的一个应答者。第 04 章的 turn / step 计数,不必为"等人回应"专门长出第三层结构。


3. 提问:模型敲门,人回答,回来仍是一条工具结果 ​

判断:向人提问的这个能力,是第 06 章说的那种消费者模式的一个例子。人答完,循环看见的是一次普通的工具结果。

这个能力的定义方式很干净:一个专门的服务定义,供模型面对的工具或权限插件在需要暂停工作、问人一个问题时使用。真正提供界面交互的实现,一个上下文只能挂一个;发起提问的动作会一直等待,直到这个界面实现给出答案。面向模型的那个"问人一个问题"工具,自称是一个消费者:它不渲染界面,不知道输入具体怎么被采集,只负责把模型给出的参数翻译成一次标准的提问请求,再把人给出的答案原样交回循环。

工具本身依赖工具注册表和这个提问服务;工具调用会一直停住,直到界面实现真正返回结果。这条设计把循环合同收成一句话:循环本身不用变;一次工具调用等一个还没完成的承诺,工具执行完毕之后,循环照常沿着原来的路径继续往下走。

谁有资格发起提问,被守得很严:一次带着具体 Agent 的提问请求,这个 Agent 必须是注册表里那个恰好还活着的、运行时意义上的根 Agent。已经落盘的血缘信息不构成权威——一个历史上确实经历过委托的会话,被当作一个全新的运行时根重新恢复之后,是可以发起提问的;反过来,一个当前正被另一个活着的 Agent 持有的孩子,即便它落盘的委托深度是零,也会被拒绝提问。子代理如果遇到需要请示的问题,应该把这个未决问题写进自己给父亲的最终汇报里,不能直接弹到父亲的界面上。这正是第 08 章"血缘数据不影响可见性、也不会自动捐赠权威"这条纪律在人机交互面上的镜像。

提问附带的"意图标签"只影响呈现方式:某个标签可以让识别它的界面把问题内容画成一份待审核的计划,不认识这个标签的界面就画成普通的选项列表;调用方最终读到的答案字段格式是一样的。不要把这个意图标签理解成又一种循环内部状态。

如果提问做成循环内部状态会怎样?"等待回答问题"和"等待审批"和"正常运行"会缠在一起,每条路径的取消逻辑各写一份。做成一个普通工具,取消就是这次调用的取消信号,超时就是第 07 章那条"真正执行"这一层的责任,最终结果就是第 05 章能够被投影的一条工具结果。特殊情况就这样消失了。


4. 权限预设是旋钮的捆绑,不是第四种循环状态 ​

判断:人面对的"工作区内可写""完全放开权限"这类档位,是两个已有旋钮的固定搭配。它不引入新的执行世界,也不引入新的 turn 种类。

每个预设名字都捆绑了"沙箱模式"和"审批策略"这两个旋钮。默认提供两档:一档是"沙箱内可写工作区 + 审批策略设为总是询问";另一档是"完全放开 + 审批策略设为从不询问"。界面可以把这些预设画成一个简单的选择器;真正的沙箱执行和审批逻辑继续读它们各自的旋钮状态,预设只是提前替你把两个旋钮拧到某个固定组合。

选择一个预设,会先记一笔仅写日志的"选择了某个预设"记录,然后只在旋钮的有效值真正发生变化时才去拧那两颗底层旋钮。选择记录排在旋钮变化记录之前,这样即便两个不同的预设恰好共享同一组底层旋钮值,用户"选择了某个预设"这个意图也不会因为旋钮值没变而丢失。有一条专门的命令属于这条平面——查看当前档位和可选项,或者带参数直接切换档位;它是一条命令,不是模型工具。

会话创建时,会把这三笔相关事实钉进日志;之后修改全局设置不会影响已经在跑的会话。恢复一个会话时,会保留它种子里记录的权限状态,只补上缺失的耐久事实。人真正改动的是这一次会话的政策快照,不是一个全局变量。

把三条缝放回一张表,切割线就清楚了:

人做了什么走哪条平面循环看见什么模型看见什么
打一条斜杠命令命令平面什么都没看见,除非命令显式发起了一次追加什么都没看见(命令输出不是消息)
点"允许这次执行"审批平面,挂在执行前检查点上这次工具调用的执行过程正常结束允许或拒绝的工具结果
回答模型的选择题提问平面,经由那个提问工具一次普通的工具结果答案数据
把权限档位调到完全放开权限预设平面,可能经由一条命令没有直接事件政策快照,或者后续工具行为的变化

自动化协议桥和不带界面的演示场景,可以完全不挂命令适配器。没有挂载审批服务时,工具管道默认拒绝。没有挂载提问实现时,那个提问工具会失败成一条普通的工具错误。人机平面整体缺席时,循环照样是循环——只是有些门敲不开。第 07 章提到的"审批服务是机会性依赖、不挂载也能跑"和第 09 章"没挂的层就当它不存在",是同一句话在不同层面的应用。


5. 结语:带走一句话 ​

人跟 Agent 协作有自己的平面:斜杠命令是适配器提前截下的指令,审批是工具管道上的一次默认拒绝的决策,提问是等一个承诺结果的普通工具调用——循环内部不必出现"正在等人"这种状态。