echo-agent 前身为 2025 年 11 月启动的个人助理项目 fubot,最初面向长期陪伴型个人智能体,围绕认知记忆、上下文延续、用户偏好沉淀、任务闭环与持续自我优化展开。随着真实场景迭代,项目逐步形成多入口接入、统一事件模型、消息总线、Agent Loop、多模型抽象、工具调用、MCP 接入、任务调度、权限审批、运行轨迹、长期记忆和受控自演进等能力。目前已支持微信、QQ、CLI、Gateway、Webhook、Cron 等入口,服务用户超过 20 万、累计下载超过 50 万,是面向长期运行、记忆增强和可持续成长智能体的开源 Agent Runtime。

项目地址:GitHub - fuyuxiang/echo-agent: Echo Agent 是一个可自托管、长期运行、持续学习的 AI Agent,面向个人与团队的私有自动化场景。它可以部署在自有服务器上,统一连接模型、工具、记忆、权限与消息入口。内置四层认知记忆、遗忘曲线与矛盾检测机制,能够在跨会话任务中持续沉淀上下文,并保持长期记忆的质量。针对命令执行、文件操作等高风险行为,它提供基于 LLM 的审批与解释机制,为关键操作建立可审计、可追溯的安全边界。原生支持 MCP、A2A、多模型路由、任务调度、工具调用和多通道接入,覆盖 CLI、Gateway API、微信、Telegram 等入口。它让 Agent 带着长期记忆和可进化技能,持续、安全地为你工作。 · GitHub

15-cover

你让 Agent “帮我修复测试失败”。

它读了日志,准备运行测试,又判断依赖可能缺失,于是生成了一条安装命令。命令看起来合理,但里面可能包含网络访问、脚本执行、文件覆盖,甚至会改动用户目录里的配置。

这时真正的问题不是模型有没有“安全意识”,而是系统能不能回答:这次行动会使用哪些工具、访问哪些路径、触发哪些副作用、是否需要审批、最后在哪里执行

这就是 Agent 安全边界要解决的问题。

问题入口

如果只靠提示词告诉模型“不要做危险操作”,系统仍然是不可靠的。

模型可能判断错,把破坏性命令当成清理缓存;也可能被网页、文档、工具输出里的注入文本诱导;更关键的是,模型不知道真实运行环境。它不知道当前执行器是否联网、路径是否敏感、通道是否可信,也不知道用户是否已经授权这类副作用。

提示词负责行为引导,安全策略负责硬边界。

对工程系统来说,安全不能停在“模型应该怎么想”,而要落到“代码允许什么发生”。Agent 的每次行动,都应该先经过系统层判断,再进入执行器。

为了不停留在抽象层面,下面以 echo-agent 的实现为例。它的安全策略不是一个总开关,而是一组从工具可见性到执行控制的分层边界。

工具可见性

第一层边界不是拦截危险动作,而是减少模型一开始能看到的动作空间。

工具越多,模型可选择的行动越多,误调用和注入攻击后的影响范围也越大。一个只需要读文件、改代码、跑测试的任务,不应该默认暴露技能安装、定时任务、外部消息发送、任意网络请求这类能力。

echo-agent 在工具发现后,会根据 ToolsConfig 做过滤:

tools = filter_tools_by_policy(config, tools)

它支持 minimalmessagingcodingfull 四类 profile。minimal 只保留最小交互和读取能力;messaging 增加消息、记忆和媒体能力;coding 增加文件写入、补丁、任务和工作流能力;full 尽量暴露全部工具,但仍受安全 profile 和 deny 约束。

15-工具可见性

仅按工具名过滤还不够。工具名会扩展,风险属性却相对稳定。echo-agent 因此引入 capabilities,把工具映射到 fs.readfs.writeprocess.execcode.execnetwork.outboundskill.install 等能力。

这解决了自定义工具和 MCP 动态工具的问题。一个工具即使不在内置名单里,只要声明了 network.outbound,在网络策略为 deny 时就应该被识别和限制。

def tool_capabilities(tool_or_name):
    declared = getattr(tool_or_name, "capabilities", None)
    if declared:
        return frozenset(declared)
​
    name = tool_name(tool_or_name)
    if name.startswith("mcp_"):
        return frozenset({"mcp.call"})
​
    return TOOL_CAPABILITIES.get(name, frozenset())

这层边界的原则很简单:不是把所有能力交给模型,再期待它谨慎使用,而是让模型只看到当前任务所需的能力

风险信号

工具可见性只能减少入口,不能替代执行前判断。因为同一个工具在不同参数下风险不同,同一个命令在不同环境里后果也不同。

echo-agent 用 risk_classifier.py 把工具调用分为四类:

风险级别典型工具策略含义
read_only读文件、列目录、搜索、网页读取通常可自动执行,但仍受路径和网络策略约束
write写文件、补丁、记忆、消息、图片生成有副作用,需要更强上下文判断
execshell 命令、代码执行、后台进程进入执行控制和审批路径
dangerous定时任务、技能安装、技能管理默认更保守,常需显式授权

分类入口的优先级也很克制:先查静态风险表,再读取工具声明的 risk_level,未知工具默认视为 write。未知不等于安全,未知意味着系统没有足够信息自动放行。

网络策略是另一条关键风险信号。书稿里的默认值是:

network_policy: Literal["allow", "deny", "restricted"] = "deny"

网络动作可能从很多路径发生:web_fetch、shell 里的 curl / wget / ssh,Python 里的 requests,Node 里的 http,甚至通过 MCP 工具转发出去。因此只禁用 web 工具是不够的,工具暴露、runtime guard 和 executor 层都要重复检查。

会调用工具只说明有行动接口;能否托付给 Agent,要看工具调用是否进入闭环、是否受权限约束、是否能被追踪和复盘。

执行前 Guard

真正接近执行边界的是 runtime guard。

echo-agent 用 GuardDecision 表达执行前判断结果:allowaskdeny。这不是一个布尔值,因为安全决策不是简单的“能不能”。有些动作可以自动执行,有些动作需要审批,有些动作必须硬阻断。

15-风险决策

GuardDecision 还带有 reasonpattern_keyapproval_action。这让系统不仅知道结果,还知道为什么命中规则。比如 network_deniedallowlist_missrecursive_delete 都可以成为后续审批、审计和测试的稳定标识。

@dataclass(frozen=True)
class GuardDecision:
    action: GuardAction = "allow"
    reason: str = ""
    pattern_key: str = ""
    approval_action: str = ""
​
    @property
    def needs_approval(self) -> bool:
        return self.action == "ask"

Shell guard 分两类规则。硬阻断包括根目录破坏性删除、块设备写入、文件系统格式化、系统关机、敏感账号文件和根凭证路径。这些操作即使模型或用户在普通任务里提出,也不应通过常规工具执行。

需要审批的模式包括递归删除、chmod 777、root ownership 修改、服务控制、大范围 kill、curl | sh、inline interpreter、疑似凭证文件写入、破坏性 SQL。这些动作不一定永远禁止,但不能静默执行。

代码执行 guard 关注敏感系统文件访问、网络访问、动态执行和进程启动。代码比普通 shell 更危险的一点在于,它可以动态拼接命令、读取文件、创建子进程,再把行为隐藏在语言运行时内部。

路径策略则处理文件系统边界。echo-agent 当前不是简单的 restrict-to-workspace,而是 default-allow 加显式 denylist:允许 Agent 操作用户目录,比如 Desktop、Documents,同时保护凭证、系统文件、设备文件和内部缓存。

这个取舍很工程化。只允许 workspace 内写入更容易推理,但会让个人 CLI 场景很受限;默认允许用户目录更实用,但必须坚决阻断 ~/.ssh~/.aws~/.kube/etc/passwd/etc/shadow/dev/zero/proc/self/fd/0 这类路径。必要时,还可以用 safe_write_root 把写入范围收紧到指定目录。

统一入口

这些规则最终要在工具调用前汇合。书稿中的统一静态 guard 入口是 evaluate_tool_call

它会读取执行策略和网络策略,再根据工具类型进入不同检查:exec 走 shell command guard;execute_code 走 code execution guard;process start 按 shell 命令评估;网络策略为 deny 时,web_fetchweb_search 会被拒绝;其他工具先默认 allow,后续再进入风险分类和审批策略。

可以把它理解成下面这段最小伪代码:

def evaluate_tool_call(config, tool_name, arguments):
    if tool_name == "exec":
        if not config.tools.exec.enabled:
            return GuardDecision("deny", "exec tool is disabled")
        return evaluate_shell_command(config, arguments["command"])
​
    if tool_name == "execute_code":
        if not config.tools.code_exec.enabled:
            return GuardDecision("deny", "code execution is disabled")
        return evaluate_code_execution(config, arguments["code"])
​
    if config.execution.network_policy == "deny":
        if tool_name in {"web_fetch", "web_search"}:
            return GuardDecision("deny", "network access is denied")
​
    return GuardDecision("allow")

这段逻辑的重点是时序:工具调用先进入策略判断,再进入真实执行。不要让模型直接把字符串交给 shell,也不要让每个工具各自随手写一套安全判断。

纵深防御

Agent 安全不能依赖单点防线。提示词可能被绕过,schema 可能覆盖不了全部语义,静态扫描可能漏掉组合风险,审批也可能被误点。因此生产级 Agent 必须采用纵深防御。

15-纵深防御

层次作用
工具可见性先减少模型能看到的能力
Schema 约束让参数结构化,避免任意文本表达副作用
静态 Guard在执行前扫描命令、代码、路径和网络
风险分类把 read-only、write、exec、dangerous 分流
ApprovalGate综合上下文、权限、allowlist 和审批状态
执行器隔离限制 cwd、网络、凭证和运行环境
审计可观测记录决策、原因、审批和执行结果

每一层都假设上一层可能失败。模型遵守提示词很好,但不能只靠提示词;path policy 有用,但不能替代审批;审批能降低风险,但不能覆盖硬阻断。

Prompt Injection 也要放在这个框架里理解。它不是“模型被骗了”这么简单,而是不可信内容试图改变指令层级。网页、知识库、工具输出和记忆片段只能作为数据,不能绕过工具审批、路径策略和执行边界。

生产可用性

判断一个 Agent 的安全边界是否接近生产级,不能只看“有没有安全提示词”。更硬的检查项应该是这些:

检查项可验证标准
工具可见性是否按 profile、channel、security profile 过滤工具
能力声明自定义工具是否声明 fs.writeprocess.execnetwork.outbound
风险分类是否区分 read-only、write、exec、dangerous
网络控制web 工具、shell、代码执行、executor 是否都尊重 network policy
路径策略是否阻断凭证目录、系统文件、设备文件和内部缓存
审批信号是否能输出 allow、ask、deny,而不是只返回 true/false
审计字段是否记录 reasonpattern_key、审批来源和执行结果
回归测试是否覆盖 network deny、allowlist miss、代码联网、敏感路径和 safe_write_root

这些检查项背后的共同原则是:安全不是阻止 Agent 行动,而是定义哪些行动在什么条件下可接受。

读取当前项目文件通常可以接受;写入用户明确要求修改的工作区文件,在可回滚或可审查条件下可以接受;执行测试命令,在 cwd、网络和输出都受控时可以接受。删除文件、安装依赖、访问外网、创建定时任务、发送外部消息,则需要更强确认。访问凭证、系统目录、设备文件和内部缓存,应默认拒绝。

安全策略如果只会拒绝,会让 Agent 变得僵硬;如果过度宽松,又无法托付。成熟的做法是给出可解释降级:网络被禁用时使用本地资料,敏感路径写入被拒绝时改写到安全目录,危险删除被阻断时先生成删除计划,外部消息不能自动发送时先生成草稿。

小结

Agent 的安全边界不是一句“请谨慎执行”,而是一条从可见性到执行控制的链路。

工具可见性先缩小行动空间,capabilities 让系统按能力而不只按名称识别风险,network policy 在多层重复生效,risk classifier 把工具调用分流,runtime guard 在执行前判断 allow、ask、deny,path policy 保护敏感资产,executor isolation 把最终行动限制在受控环境中。

这一章只讲 ApprovalGate 之前的基础层。下一篇会继续往前走:当这些风险信号都已经产生,系统如何把它们汇总成一次真正可执行、可审批、可审计的决策。

(全篇完)


本文为 echo-agent 设计笔记系列第 15 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣,欢迎加入技术交流群参与日常讨论。下一篇我们将探讨 《给 Agent 加一个 Approval Gate》,敬请期待。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐