echo-agent 前身为 2025 年 11 月启动的个人助理项目 fubot最初面向长期陪伴型个人智能体围绕认知记忆、上下文延续、用户偏好沉淀、任务闭环与持续自我优化展开。随着真实场景迭代项目逐步形成多入口接入、统一事件模型、消息总线、Agent Loop、多模型抽象、工具调用、MCP 接入、任务调度、权限审批、运行轨迹、长期记忆和受控自演进等能力。目前已支持微信、QQ、CLI、Gateway、Webhook、Cron 等入口服务用户超过 20 万、累计下载超过 50 万是面向长期运行、记忆增强和可持续成长智能体的开源 Agent Runtime。项目地址https://github.com/fuyuxiang/echo-agent你让 Agent “帮我修复测试失败”。它读完日志判断需要运行测试。第一次是pytest第二次可能是安装依赖第三次可能是删除临时文件。每一步看起来都像正常工程动作但它们的副作用完全不同有的只是读取状态有的会写文件有的会联网有的可能不可恢复。这时真正的问题不是“模型能不能想到正确命令”而是系统能不能在命令执行前回答这一步是否允许执行、是否必须拒绝、是否需要用户授权。这就是 Approval Gate 要解决的问题。问题入口如果只看传统文本型 Chatbot它的基本形态仍是用户输入文本模型返回文本。回答错了可以追问理解偏了可以纠正系统本身不会因为一句话就改文件、跑命令、发消息。Agent 不一样。它把模型输出接到了工具系统上。模型不再只是生成答案而是在生成行动计划读文件、写文件、执行命令、访问网络、启动进程。行动一旦接入真实环境安全问题就从“回答质量”变成了“执行授权”。上一章讲的安全基础层已经能产生很多信号工具是否暴露、命令是否危险、路径是否敏感、网络是否允许、风险等级是什么。但信号本身还不是决策。系统还需要一个统一入口把这些信号合成最后结果。Approval Gate 不是一个“确认弹窗”而是 Agent 行动系统的最后决策门。为了不停留在抽象层面下面以 echo-agent 的实现为例。它把所有模型发起的工具调用先送入InferenceStage再进入ApprovalGate.check。工具只有通过这道门才会继续执行。审批门Approval Gate 的输出不是简单的 true / false而是一个ApprovalCheck。它有两个核心字段denial和approved_actions。如果denial是None表示工具可以继续执行如果denial是ToolResult表示工具调用被拒绝或审批超时approved_actions则记录本次已经批准的动作标识供后续工具内部策略继续使用。dataclass class ApprovalCheck: denial: ToolResult | None None approved_actions: frozenset[str] frozenset()这个设计有一个很重要的工程含义审批失败不是异常退出而是一个模型可观察到的工具结果。InferenceStage会把拒绝信息写回模型上下文模型可以解释失败原因、请求用户授权或者选择更安全的替代方案。例如用户要求“清理项目里没用的文件”。模型如果直接生成递归删除命令Approval Gate 可以拒绝或请求审批拒绝结果会作为 tool 消息返回。模型接下来可以改成先列出候选文件、生成删除计划再让用户确认范围。审批门因此不是中断对话而是把风险决策放回 Agent Loop。决策链echo-agent 当前的ApprovalGate.check有清晰的判断顺序。这个顺序比单个规则更重要。硬阻断必须最先发生。已经被 static guard 判定为危险的命令不能再被auto_approve、trusted channel 或 Smart Approval 放行。Elevated 权限也要早于普通风险放行因为 local、remote 或 full security 的执行范围可能超出沙箱。可以把这条链压缩成几层阶段作用Static guard命中破坏性命令、敏感路径、禁用工具、network deny 时直接拒绝Elevated check对 local、remote、full security 等高权限执行做通道和用户校验Risk classification把工具调用分为 read-only、write、exec、dangerousLow-risk passread-only 和 write 默认放行但仍受路径、沙箱和工具内部策略约束Auto paths处理 auto_approve、personal CLI、trusted channel、approval mode offRequired check根据 auto_deny、guard ask、推理确认、require_approval、dangerous 判断是否需要审批Allowlist / unattended复用已批准 pattern或在无人值守场景按策略拒绝Smart / manual低风险 exec 可智能判断否则进入人工审批最小伪代码大致是这样async def check(tool_name, arguments, event): guard evaluate_tool_call(config, tool_name, arguments) if guard.denied: return deny(guard.reason) if requires_elevated(tool_name) and not elevated_allowed(event): return deny(requires elevated execution rights) risk classify_risk(tool_name, arguments) if risk in {READ_ONLY, WRITE}: return allow() if auto_approved(tool_name, risk, event): return allow() if not approval_required(tool_name, guard, risk): return allow() pattern_key build_pattern_key(tool_name, arguments) if risk EXEC and allowlist.approved(event.session_key, pattern_key): return allow() if unattended(event): return handle_unattended_policy(risk, pattern_key) if risk EXEC and mode smart: return await smart_or_escalate(tool_name, arguments, guard) return await manual_approval(tool_name, arguments, event, guard)这里最容易误解的是 read-only 和 write 的放行。它不等于“写操作没有风险”而是说普通写操作的主要边界放在路径策略、safe write root、工具暴露策略和具体工具内部校验里。例如write_file可以写普通用户文件但仍应阻断~/.ssh/id_rsa、/etc/passwd、系统路径和safe_write_root外路径。Approval Gate 不应该把每次正常编辑都变成弹窗否则 Agent 会失去可用性它要把注意力集中在 exec、dangerous 和上下文不确定的动作上。好的审批系统不是把所有动作都交给人而是知道哪些动作可以自动化哪些动作必须拒绝哪些动作需要授权。授权范围人工审批最怕两个极端范围太窄用户反复确认范围太宽一次批准变成长期风险。echo-agent 用 allowlist 处理这个问题。审批级别分三种ONCE、SESSION、ALWAYS。一次性批准只对当前请求生效session 批准写入当前会话always 批准写入永久 allowlist 并保存到文件。allowlist 不是直接保存完整命令字符串而是通过build_pattern_key做归一化。比如exec执行python3 -m pytestpattern 可能是exec:python3execute_code执行 Pythonpattern 是code:pythonprocess启动 npmpattern 是process:npm。这是一种折中。完整参数过细会导致相似动作不断重复审批只按工具名过粗又会把完全不同风险混在一起。pattern_key让系统可以复用“同类动作”的授权同时保留重新判断的空间。审批通过后approved_actions会包含工具名、guard 的pattern_key和approval_action。后续ToolExecutionContext会把这些信息传给工具。ShellTool、CodeExecTool、ProcessTool等工具可以再次检查这次执行是否真的包含被批准的动作。这解决了一个常见脱节问题Gate 层判断allowlist_miss需要审批用户批准后工具内部再次扫描命令仍可能得到 ask。如果没有approved_actions工具会再次拦住同一个动作有了它工具能知道这个模式已经被本轮授权覆盖。审批范围的原则可以压成一句话批准应覆盖完成当前任务所需的最小行动集合。人工审批当自动路径不能放行时系统进入 manual approval flow。echo-agent 会通过ApprovalManager.request_approval创建审批请求。请求有签名签名由用户、动作、工具名和参数生成如果相同请求已经 pending系统会返回已有请求避免重复创建审批项。审批请求不是临时变量而是状态对象。它要包含请求 ID、工具名、参数、发起用户、当前状态、审批结果并支持等待、去重、查询和持久化。用户侧的控制命令很直接/approvals /approve request_id /approve request_id session /approve request_id always /deny request_id [原因]这些命令是控制面命令不需要进入完整模型推理。/approvals会列出当前用户可见的 pending 请求/approve和/deny会直接改变审批状态。谁可以审批也不能靠模型判断。书稿里的规则是如果配置了admin_users只有管理员能审批如果没有配置管理员请求发起者可以审批自己的请求。这让授权关系绑定到用户和通道而不是绑定到模型的一句话。如果审批超时ApprovalGate会返回 denial。无人值守场景也会单独处理cron、scheduler 或带_unattendedmetadata 的事件默认策略是 denyallow_safe也只放行 write或者已经命中 allowlist 的 exec。没有人在场时系统不应该默默执行高风险动作。Smart ApprovalSmart Approval 的价值是降低误报不是替代责任归属。在 echo-agent 中它只出现在较靠后的阶段风险为EXEC、approval mode 为smart、provider 可用时才运行。它可以返回三种结果approve、deny、escalate。approve 会把 pattern 写入本 sessiondeny 会返回拒绝escalate 则继续进入人工审批。例如python -c print(hello)可能因为 inline interpreter 被标记为需要审批但上下文显示风险很低。Smart Approval 可以减少这种低价值打断。但它不能覆盖 static guard。破坏性删除、写块设备、格式化文件系统、读取敏感凭证、network deny 下访问外网这些动作不应该因为另一个模型判断“看起来没问题”就被放行。智能审批的边界是降低打扰不是转移授权责任。这也是为什么 Approval Gate 必须有固定时序。Smart Approval 在硬阻断之后只处理规则无法充分判断、但又可能低风险的 exec 场景。真正有副作用、外部可见、涉及成本或凭证的动作仍应由明确策略或用户授权承担责任。生产可用性判断一个 Approval Gate 是否接近生产级不能只看“有没有 approve 按钮”。更硬的检查项应该是这些检查项可验证标准执行前入口所有模型工具调用是否先进入统一 gate再进入工具执行硬阻断优先static guard 是否早于 auto approve、trusted channel、smart approval权限绑定elevated 执行是否绑定 channel、sender_id、admin_users风险分流是否区分 read-only、write、exec、dangerous并有不同路径审批结果拒绝是否作为 tool result 写回上下文而不是静默失败授权范围是否支持 once、session、always并能解释 pattern 范围无人值守cron、scheduler 是否默认保守避免等待不到审批或静默执行状态持久化pending 请求、一次性授权、审批历史是否能跨重启恢复工具协同工具内部是否读取approved_actions避免 gate 与工具策略脱节回归测试是否覆盖请求创建、去重、批准、拒绝、超时、持久化和审批命令这些检查项背后的共同原则是审批不是 UI 功能而是执行控制协议。它要让系统能解释每次行动为什么被允许、为什么被拒绝、为什么需要人参与以及批准范围到哪里结束。审批和澄清也要分开。用户说“删除没用的文件”系统缺的首先是范围信息其次才是删除授权。正确做法是先澄清“哪些文件算没用”再对具体删除动作请求审批。把澄清当审批会让用户批准一个自己并未理解的行动把审批当澄清又会让系统显得啰嗦。小结Approval Gate 的核心不是让用户多点几次确认而是把 Agent 的行动授权显式化。模型负责提出候选行动系统策略负责识别风险人类在规则无法充分判断且影响较大的地方做授权。低风险、可恢复、边界清楚的动作自动放行硬阻断动作直接拒绝高影响或上下文不确定的动作进入审批。这样设计之后Agent 不只是“会调用工具”而是知道每次工具调用如何被授权、如何被记录、如何在失败后回到对话中继续推进任务。全篇完本文为 echo-agent 设计笔记系列第 16 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣欢迎加入技术交流群参与日常讨论。下一篇我们将探讨 《Agent Memory 设计笔记从短期上下文到长期经验》敬请期待。