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或npm test然后在当前机器上执行。看起来很自然直到你发现测试脚本会写缓存、安装依赖、访问网络甚至启动一个长期占用端口的开发服务。对工程师来说问题从来不是“模型会不会生成命令”而是这条命令在哪里运行、继承谁的权限、能访问哪些文件、能不能联网、失败后留下什么状态。这就是执行器要解决的问题。问题入口工具系统解决的是“模型可以调用什么能力”。执行器解决的是“这些能力在哪里、以什么边界运行”。如果把命令执行直接写在ShellTool里短期看很快工具收到command本地subprocess一跑stdout 返回给模型。但这样会把四件事揉在一起工具协议、运行环境、安全策略和部署形态。结果是系统很难回答几个关键问题问题直接写在工具里的后果本地能跑云端怎么跑工具和宿主环境强耦合需要隔离时怎么办每个工具都要重写沙箱逻辑是否允许联网策略散落在工具内部凭证怎么注入工具容易读取全局环境变量后台进程谁清理一次调用结束后状态不可见执行器不是为了让 Agent 更敢执行命令而是为了让每一次执行都有明确的副作用半径。为了不停留在抽象层面下面以 echo-agent 的实现为例。它把ShellTool、CodeExecTool、ProcessTool和真实运行环境之间拆出一层 executor工具只表达“我要执行什么”executor 决定“在哪里执行、怎么隔离、如何返回结果”。执行契约echo-agent 的执行器代码位于echo_agent.agent.executors。基础契约由两个数据结构承担ExecRequest和ExecResponse。ExecRequest表示一次执行请求包含命令、工作目录、环境变量、超时、标准输入和凭证。ExecResponse表示执行结果包含 stdout、stderr、返回码、耗时、执行器名称和审计 ID。这组契约的意义不在于字段多而在于它把“执行命令”从某个具体 subprocess 调用中抽离出来dataclass class ExecRequest: command: str cwd: str env: dict[str, str] field(default_factorydict) timeout: int 30 stdin: str credentials: dict[str, str] field(default_factorydict) ​ dataclass class ExecResponse: success: bool True stdout: str stderr: str return_code: int 0 duration_ms: int 0 executor: str audit_id: str field(default_factorylambda: uuid.uuid4().hex[:12])所有执行器继承同一个BaseExecutor对工具层暴露统一的execute()、setup()和teardown()。class BaseExecutor(ABC): name: str base ​ abstractmethod async def execute(self, request: ExecRequest) - ExecResponse: ... ​ abstractmethod async def setup(self) - None: ... ​ abstractmethod async def teardown(self) - None: ...这个抽象让工具层不必知道底层是本地进程、临时沙箱、Docker 容器还是远程 SSH。它只需要构造一次执行请求剩下的边界问题交给执行器。这里还有一个容易被忽略的设计executor 是长期存在的。create_executor()会根据配置创建local、sandbox、container或remote执行器并让它随AgentLoop复用。原因很现实。沙箱目录、容器和远程连接的初始化都有成本。如果每次工具调用都重新 setupAgent 会变慢也会留下更多临时状态。长期存在意味着 setup 成本只付一次同时也要求 executor 在停止时通过teardown()释放资源。环境选择执行器选择不是性能偏好而是信任模型。echo-agent 当前提供四类执行器执行器运行位置适合场景主要风险LocalExecutor当前宿主机个人 CLI、受信任工作区、外部已有隔离直接影响真实文件和进程SandboxExecutor临时目录副本默认隔离、运行测试、避免污染原工作区隔离强度有限仍依赖宿主环境ContainerExecutorDocker 容器不可信代码、依赖隔离、镜像化环境需要 Docker生命周期更复杂RemoteExecutorSSH 远程主机部署、远程计算、集中资源管理凭证、审计和结果可信度要求更高LocalExecutor 最直接。它会在宿主机上用asyncio.create_subprocess_shell执行命令。执行前会检查网络策略如果network_policy deny且命令命中网络行为就返回失败结果。它适合受信任的本机场景但不适合公共入口或多租户环境。因为它继承的是当前用户权限误操作会直接落在真实文件系统和真实进程空间。SandboxExecutor 会在 sandbox root 下创建临时目录并把 workspace 复制进去。复制时会忽略.git、__pycache__、.pytest_cache、.ruff_cache、.venv、node_modules、data/logs等目录减少成本也避免把不必要的大目录带入沙箱。执行时它会把HOME和TMPDIR指向沙箱工作目录并限制PATH。如果请求的cwd不在源 workspace 下会回退到沙箱 workdir。这使得命令即使传入外部路径也不容易跳出受控目录。ContainerExecutor 通过 Docker 创建长期容器挂载 workspace 到/workspace再通过docker exec执行命令。如果网络策略是 deny容器使用--network none否则使用 bridge。Docker 不存在时setup 会明确抛出错误。RemoteExecutor 通过 SSH 执行远程命令。它会构造StrictHostKeyChecking、ConnectTimeout、key path 等参数并对 cwd、环境变量和凭证做 shell quoting。书稿里特别提到测试覆盖了带空格 cwd、特殊字符 env、密钥路径和连接参数这说明远程执行关注的不只是“能 ssh”还包括命令拼接安全。同一个pytest在不同环境里的意义完全不同。在沙箱副本中运行主要影响临时目录在真实 workspace 中运行可能写缓存、更新快照或生成报告在远程机器上运行影响范围可能跨出本机和当前用户。执行环境决定副作用半径。不能只问命令能不能跑还要问它跑错时会影响哪里。工具适配执行器不是替代工具层安全检查。它和工具层是前后两道边界。ShellTool的工具名是exec参数包括command、timeout和cwd。它在调用 executor 之前会先做几类检查内置危险模式、allowed/blocked 列表、安全策略评估以及审批上下文。例如书稿中提到内置阻断模式会覆盖/etc/passwd|shadow|sudoers|gshadow、根目录破坏性删除、文件系统格式化和系统关机等行为。之后还会检查 allowlist未命中的命令会被拒绝。cwd也不能随意指向系统目录。ShellTool 会解析传入目录把相对路径映射到 workspace 下再调用 cwd 策略检查。违反边界时工具返回类似cwd is outside workspace的失败结果。最后它才把命令交给 executorresponse await executor.execute(ExecRequest( commandcommand, cwdcwd, timeouttimeout, env{WORKSPACE: workspace}, credentialsctx.credentials if ctx else {}, ))这段最小代码说明了三件事。第一工具仍负责理解自己的参数和风险。第二执行上下文里的凭证按请求传入而不是让工具读取全局密钥。第三真实执行统一落到 executor便于切换运行环境。CodeExecTool的边界类似但重点不同。它的工具名是execute_code支持 Python、JavaScript 和 Bash 三种 runnerpython3 -、node、bash。初始化时可以限制允许语言模型请求不允许的语言会直接失败。这里的语言 allowlist 是一个很朴素但有效的边界。用户让 Agent 写一段 Python 数据处理脚本并不等于授权它运行 Bash 安装脚本允许执行 JavaScript也不等于允许它调用任意系统命令。语言本身就是风险信号。代码片段通过 stdin 传入 executor而不是拼接进 shell 命令字符串await executor.execute(ExecRequest( commandpython3 -, cwdstr(workspace), timeouttimeout, stdincode, env{WORKSPACE: str(workspace)}, credentialsctx.credentials if ctx else {}, ))这个细节很重要。长代码如果塞进命令字符串会引入 quoting、转义和截断问题。stdin 更接近“把代码交给 runner”也更容易统一记录和审计。后台进程一次性命令退出后返回码和输出就能描述大部分状态。后台进程不一样。它会继续运行继续占用端口、写日志、消耗资源甚至在当前回答结束后仍然影响系统。echo-agent 用ProcessTool管理后台进程。它的工具名是process支持四个动作start、list、poll、stop。启动前ProcessTool 同样会调用 shell 安全策略。启动成功后进程记录在模块级_PROCESSES中保存 process 对象、启动命令、开始时间、stdout/stderr 缓冲等信息。系统会异步收集输出poll返回最后一段 stdout/stderrstop先 terminate超时后 kill。这说明后台进程不是“长一点的 shell 命令”而是生命周期对象。一个生产级 Agent 如果能启动开发服务器就必须能回答它启动了什么、属于哪个会话、监听哪个端口、日志在哪里、现在是否存活、什么时候应该停止、停止失败怎么办。否则Agent 的行动会在宿主机里留下不可解释的状态。你看见端口被占用却不知道是哪次任务启动的你看见日志滚动却不知道它属于哪个用户你让 Agent 停止它也不知道该停哪一个进程。会启动后台进程只说明 Agent 有运行时能力能管理后台进程才说明它有运行时边界。输出治理命令和代码执行会产生大量输出。测试日志、构建日志、安装日志、后台进程日志都可能很长也可能包含密钥、连接串、用户路径和隐私数据。所以输出截断不是简单的体验优化而是上下文治理和安全机制。echo-agent 的 ShellTool 和 CodeExecTool 都会截断 stdout/stderr。原因有两个一是保护模型上下文窗口二是避免把大量无关或敏感输出原样灌回对话通道。更合理的输出策略应该分两层给模型的是摘要、退出码、关键错误、少量上下文和日志引用给工程师追溯的是完整原始日志或审计记录。这也是ExecResponse中return_code、duration_ms、executor、audit_id有价值的地方。一次执行不是一段字符串输出而是一条可追踪的工作单元。如果后续要把执行器做得更生产化还需要继续补齐产物管理哪些文件被创建或修改哪些报告需要返回给用户哪些沙箱产物要同步回真实 workspace哪些临时文件应该清理哪些输出需要脱敏。生产可用性判断一个 Agent 执行器是否接近生产级不能只看“能不能跑命令”。更硬的检查项应该是这些检查项可验证标准统一契约命令、cwd、env、stdin、timeout、credentials 都进入 ExecRequest环境隔离至少能区分本地、沙箱、容器或远程执行生命周期executor 有 setup/teardown后台进程有 start/list/poll/stop文件边界cwd 不能越过 workspace 或策略允许范围网络策略工具层和 executor 层都尊重 network policy凭证注入凭证从执行上下文按请求传入不读取全局密钥输出治理stdout/stderr 有截断、摘要和完整日志分层审计追踪每次执行有 executor 名称、耗时、返回码和 audit_id测试约束覆盖 allowlist、网络 deny、stdin、cwd quoting、env quoting 和审批路径这些标准背后的共同原则是模型可以提出行动但不能直接拥有宿主环境。执行器把自然语言驱动的意图变成受控物理动作。它要处理文件系统边界、网络边界、进程边界、凭证边界和资源边界。哪怕模型判断错了错误也应该被限制在工作区、副本、容器或受控远端内而不是直接扩散到最敏感环境。小结执行器是 Agent 架构中最接近真实风险的位置。工具层告诉模型“你可以请求什么能力”执行器层决定“这项能力以什么身份、在哪个环境、在什么限制下发生”。这个区分越清楚系统越容易根据任务风险切换执行环境也越容易审计和复盘。echo-agent 的 executor 抽象把本地、沙箱、容器和远程执行统一到同一套ExecRequest/ExecResponse契约下再由 ShellTool、CodeExecTool 和 ProcessTool 分别承接一次性命令、代码片段和后台进程。这不是把执行命令做复杂而是承认一个现实Agent 一旦能行动风险就不再停留在文本里。真正的工程设计必须在行动落地之前先画出边界。全篇完本文为 echo-agent 设计笔记系列第 14 篇。项目源码已开源至 GitHub。如果你对工业级 Agent 的工程落地感兴趣欢迎加入技术交流群参与日常讨论。下一篇我们将探讨 《Agent 的安全边界从可见性到执行控制》敬请期待。