Appearance
3.13 交互层:审批、权限预设、命令与 Plan 模式
本章概览:人类与运行中 agent 协作的平面由
interaction/包组与plan/的 plan-mode 构成:审批(approval)、用户问答(user-questions)、权限预设(permission-presets)、人类命令(commands)与 plan 模式。它们通过既有 agent 与 session 契约集成,不改变 agent 循环。
概述 / 定位
interaction/ 是产品包组:真实的人驱动的界面。五个包:dsh-commands(ctx.commands,命令注册与派发)、dsh-user-approval(ctx.approval,一问一答的审批)、dsh-permission-presets(ctx.permissionPresets,用户可读的权限预设)、dsh-user-questions(ctx.userQuestions,问答缝)、dsh-tool-ask-user(在 ctx.tools 上注册 ask_user_question 工具)。交互应用提供具体的命令/审批/问答适配器;自动化走 ACP(Automation Client Protocol)。dsh-plan-mode(ctx.planMode)是 plan/ 下的可选包,拥有日志化的每 agent plan 协作状态。
审批:一问一答的关闭式决策
ctx.approval.request(req) 回答一个问题:"这个具体动作能否继续?"。ApprovalOutcome 封闭且 fail-closed:allowed-once(只授予被问的那个动作)| rejected | cancelled | unavailable——缺失、非属主、抛错、不合规的回答者一律成为 unavailable 而非开门。请求必须属于打开的 turn:服务先追加 approval/asked,取得一个结果,追加 approval/decided,再返回;审计对是 log-only,模型只见调用方派生的工具结果,权限 UI 不是上下文。
回答者是 approval/request waterfall 监听器:返回一个结果即认领请求,或调 next() 委托;第一个回答占据唯一决策槽;agent 作用域监听器只收到该 agent 的请求。会话策略 ApprovalPolicy 为 'ask' | 'never':ask 委托给组合的回答者链(无回答者则链落空为 unavailable);never 确定性返回 rejected 且不派发任何回答者——它在服务内、waterfall 派发之前强制,之后 prepend 注册的回答者也无法绕过。生效值 = 会话日志最后一条 approval/policy 事件,回退到配置;setApprovalPolicy 是唯一写路径,回放可重建覆盖。两种策略都向 cache 安全的 runtime-context 快照贡献完整含义,切换时追加新快照并告知模型。
ask 退化为 deny 的链路:工具流水线的 tools/pre-execute 门返回 ask 决策时由 ctx.approval 服务处理(ctx.get('approval') 机会式注入),未挂载则退化为 deny——审批不可用即拒绝,绝不静默放行。
出处:
packages/interaction/user-approval/README.md(deepseek-harness-src/packages/interaction/user-approval/README.md)
用户问答:暂停工具调用等人类
ctx.userQuestions.ask() 让模型侧工具或权限插件暂停工作、等待人类回答后恢复——工具调用 await 一个 promise,结果回到普通 agent 循环。请求 AskUserQuestionRequest 携带 questions[]:每项有稳定 id(回显到答案,批量问题保持可路由)、question 文本、可选 detail/header/options/multiSelect/intent;答案 AskUserQuestionAnswer.answers 按 id 返回 selected(选项标签)与 custom(自由文本:单选时覆盖 selected,多选时补充)。意图 AskUserQuestionIntent.kind = 'plan-review' 显式命名批准标签(approve),只改呈现不改协议——不认识该标签的 UI 走通用选项流,答案字段相同。
一次上下文只允许一个 UserQuestionProvider(第二个注册抛 DUPLICATE_PROVIDER);无 provider 时 ask() 抛 NO_PROVIDER 而非降级。请求带 agent 时必须是被注册表识别的确切运行时根(CALLER_NOT_LIVE/DELEGATED_CALLER):运行时所有权而非持久血缘决定边界——被属主 agent 拥有的活动子 agent 不能问人类,血缘深但以新运行时根恢复的会话可以。模型侧 ask_user_question 工具把模型参数翻译成请求并返回紧凑 JSON;等待人类期间零 token 成本。
权限预设:捆绑沙箱模式与审批策略
ctx.permissionPresets 把两个机制旋钮捆绑为可命名选择:每个预设捆绑 sandbox/mode 与 approval/policy。出厂默认两个:workspace-write(workspace-write + ask)与 danger-full-access(danger-full-access + never)——"严格无头模式"(CI、无人值守)即后者。set(session, name) 先记录 log-only 的 permissionPresets/preset 事件,再在生效值实际变化时调用各旋钮 setter;选择事件先于旋钮事件,保留用户意图。current(events) 优先仍匹配的记录选择,然后首个匹配表项,否则返回 custom(可显示、不可选择)。会话创建时把 permissionPresets/preset、sandbox/mode、approval/policy 钉进该会话,之后的更改不影响既有会话。另有两个可选子包:permissions session-projection 单元与 /permissionPresets 命令(裸调用报告当前预设与表,带参数经 set 切换)。
命令系统:直接分派不经模型轮次
ctx.commands.register(definition) 注册小写命令名、人类可读描述、可选输入提示、recordInput 与可中止 handler。普通上下文注册为全局;命令插件挂在 agent.ctx 之下时声明自身 commands 注入、创建精确的 agent 作用域定义,遮蔽同名全局(同层重名注册抛错)。execute(agent, line, signal) 用 parseCommand() 解析(斜杠在字节零、小写名、空白或结尾分隔)并只运行已知命令:语法或未知名返回 undefined 且不记日志(从未进入 handler)。
命令生命周期记录 log-only 对:handler 前追加 command/run(铸造 commandId,含结构化名与 rawInput,除非 recordInput: false),settle 后追加 command/done(结果 kind、文本,成功时可用 sourceEventSeq 引用更早的非命令领域事件);两者都是无 turn 包裹的直接追加,经普通 checkpoint 落盘。结果由派发 UI 直接渲染,永不进入模型历史;注册表从不隐式把 rawInput 提交给模型——命令生产者可显式用接收 agent 调度模型可见工作(如 /plan [message] 经 agent.steer())。注册/注销发 commands/change 通知。产品命令示例:/plan(本节)与 /compact(3.9)。
出处:
packages/interaction/commands/README.md(deepseek-harness-src/packages/interaction/commands/README.md)
Plan 模式:日志化的软指导
ctx.planMode 拥有每 agent 的日志化协作状态:plan/mode ({ active }) 是 log-only、整值替换的会话事件;foldPlanMode(events) 返回最后日志值或 false——resume、fork、compaction 都从日志恢复,无活动镜像,UI 经 session/event 观察已提交的翻转。plan 模式是软指导:沙箱模式与审批策略独立执行限制,不读写 plan 状态,部署须分别配置。
激活时,配置的 section 文本以 plan:policy 提示词段(order 50)进入每个模型请求;未激活不贡献文本。set(agent, active) 在 agent 空闲时立即追加(committed),运行中挂起为待定选择、由下一个已接受的 in-turn agent/pre-step 追加(queued),返回 committed/queued/cancelled/noop;选择永不强制继续。入口:/plan [message](裸 /plan 选择激活;其他非空参数先选择再经 agent.steer() 提交为下一条普通用户消息);/plan off 选择未激活并取消待定进入。
退出工具 exit_plan_mode 在两种状态都注册(工具目录稳定,进出 plan 不改 schema),但仅激活态可执行:要求以 # 开头的完整 markdown 计划,经用户问答缝以 plan-review 意图呈现,需要精确的用户批准(Approve 标签)——批准返回 { approved: true } 并记录静默(不叙述)的待定退出,plan 指导在助手当前工具批次剩余部分仍生效;"保持计划"是携带用户反馈的失败调用,模型修订后重提;驳回(dismissal)同样失败调用并告知模型留在 plan 模式等待消息。无交互通道或审核中服务重载都使调用失败,绝不静默退出 plan 模式。
出处:
packages/plan/plan-mode/README.md(deepseek-harness-src/packages/plan/plan-mode/README.md)
关键设计决策
- fail-closed 贯穿:审批缺席回答者 =
unavailable;ask无审批服务 = deny;never在 waterfall 前强制;plan 退出必须精确批准。 - 交互包集成于既有 agent/session 契约而非改 loop:工具调用 await 人类回答、命令直接派发、事件进日志。
- 命令与审计事件 log-only:"模型可见即已记录",模型只见调用方结果,权限/命令 UI 不是上下文。
- plan 状态纯日志折叠:无活动镜像,恢复即重放;软指导与沙箱/审批的强制边界分离。
- 权限预设是"选择"不是新机制:沙箱与审批仍消费各自旋钮,预设只是捆绑与记录意图。
扩展点
- 新回答者:注册
approval/requestwaterfall 监听器,认领或next()委托;每个部署组合一个终结回答者。 - 新交互通道:
ctx.userQuestions.registerProvider(provider)提供 UI 实现;intent可加标签,未知标签回退通用选项流。 - 新命令:
ctx.commands.register(definition);/plan、/compact、/permissionPresets是产品消费者示例;命令生产者可用agent.steer()显式调度模型可见工作。 - 新预设:在
permissionPresets配置表中加捆绑项(custom保留、不可配置);defaultPreset决定未来会话默认。 - plan 指导:配置
section文本;进入/退出经/plan命令或exit_plan_mode审核;不提供任意命名模式/工具过滤/沙箱/审批参数。
参考资料
- packages/interaction/user-approval/README.md — 审批缝(
deepseek-harness-src/packages/interaction/user-approval/README.md) - packages/interaction/user-questions/README.md — 问答缝(
deepseek-harness-src/packages/interaction/user-questions/README.md) - packages/interaction/tool-ask-user/README.md — ask_user_question 工具(
deepseek-harness-src/packages/interaction/tool-ask-user/README.md) - packages/interaction/permission-presets/README.md — 权限预设(
deepseek-harness-src/packages/interaction/permission-presets/README.md) - packages/interaction/commands/README.md — 命令注册表(
deepseek-harness-src/packages/interaction/commands/README.md) - packages/plan/plan-mode/README.md — plan 模式(
deepseek-harness-src/packages/plan/plan-mode/README.md) - docs/subsystems/approval.md(
deepseek-harness-src/docs/subsystems/approval.md) - docs/subsystems/user-questions.md(
deepseek-harness-src/docs/subsystems/user-questions.md) - docs/subsystems/commands.md(
deepseek-harness-src/docs/subsystems/commands.md) - docs/subsystems/plan.md(
deepseek-harness-src/docs/subsystems/plan.md) - approval-seam Agent Note(
deepseek-harness-src/.agents/notes/implemented/feature/2026-07-06-approval-seam.md) - plugin command registration Agent Note(
deepseek-harness-src/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) - plan-specific collaboration state Agent Note(
deepseek-harness-src/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)