【系列:手搓自主 AI Agent:Hermes 架构原理剖析 · 第 10 篇】 执行环境抽象与定时任务让命令在任意地方跑、让 Agent 在未来主动干活导读命令只能在本地跑Agent 只能被动响应本篇拆解 Hermes 阶段 3 收官两章用 snapshot 文件传递状态让命令在本地/Docker/SSH 任意环境执行用任务即消息机制让 Agent 到点自动开工。附完整代码与 9 个初学者常踩的坑。两个主动需求产品经理又来了。“能不能让 agent 在 Docker 容器里跑命令”“30 分钟后帮我看看部署成功没。”“每 2 小时查一下磁盘。”“工作日早上 9 点总结 git log。”前 9 篇我们手搓的 Hermes Agentterminal 工具只有 4 行subprocess.run直接在本地跑。定时任务完全没有——agent 只能被动等用户说话。本篇解决这两个问题命令在哪跑s14 执行环境抽象到点自己动s15 定时任务。这也是阶段 3 的最后一章。前半·执行环境if-else 会爆炸两个问题状态不连续 分支爆炸先看现状。s01-s11 的 terminal 工具长这样subprocess.run(command,shellTrue)每次调用都是全新的 bash 进程。cd /tmp之后下一条命令又回到默认目录。export FOObar之后下一条命令echo $FOO是空的。状态不连续。产品说支持 Docker你写ifbackenddocker:subprocess.run([docker,exec,...])elifbackendssh:subprocess.run([ssh,host,...])elifbackendk8s:...六个后端六个分支。每个分支还要处理状态传递、错误处理、超时控制。代码爆炸。而且状态问题更严重Docker 里cd了下一条命令又回到/。为什么不维护长驻 bash你可能会想那我不每次启动新 bash维护一个常驻进程不就行了三个坑等着你。坑一输出边界。没有分隔符你不知道哪段输出对应哪条命令。得手动塞echo __MARKER__做标记。坑二卡死传染。cat命令在等输入整个 bash 卡住。你想单独杀掉这条命令做不到——进程是同一个。坑三多行命令。for循环要等它执行完才知道什么时候读输出。你得懂 bash 语法才能判断现在该不该读。三个坑每一个都够你调一晚上。Hermes 方案snapshot 文件传递状态每条命令仍然启动新 bash但通过两个文件传递状态self._session_iduuid.uuid4().hex[:12]self._snapshot_pathf/tmp/hermes-snap-{self._session_id}.sh# 环境变量self._cwd_filef/tmp/hermes-cwd-{self._session_id}.txt# 工作目录核心是五行包装脚本source snapshot.sh ← 恢复上次保存的环境变量 cd {上次的工作目录} ← 恢复上次的目录 {用户的命令} ← 真正要执行的 export -p snapshot.sh ← 保存新的环境变量 pwd -P cwd.txt ← 保存新的工作目录每次执行完把新的状态存回文件。下一条命令启动时先恢复再执行。看真实代码_wrap_commanddef_wrap_command(self,command:str)-str:parts[]ifself._snapshot_ready:parts.append(fsource{self._snapshot_path}2/dev/null)parts.append(fcd{shlex.quote(self.cwd)}2/dev/null)parts.append(command)parts.append(f_exit$?; export -p {self._snapshot_path}2/dev/null; fpwd -P {self._cwd_file}2/dev/null; exit $_exit)return; .join(parts)注意_exit$?这个细节。先存退出码再保存 snapshot。如果顺序反了export -p的退出码永远是 0会覆盖用户命令的退出码。到时候你的 agent 执行ls /nonexist都返回成功。三种后端本地 / Docker / SSH基类叫BaseExecutionEnvironment注意不是文档里的 BaseEnvironment。子类只需实现_run_bash(cmd_string)和cleanup()。LocalBackend直接subprocess.Popen([bash, -c, cmd_string])。但有个关键细节——密钥过滤_SECRET_BLOCKLIST# 过滤 OPENAI_API_KEY 等防止 agent 执行env | grep KEY拿到你的 API key。Docker 和 SSH 后端不需要这个——容器和远程机器天然没有你的密钥。DockerBackend先docker run -d长驻容器sleep infinity每条命令docker exec。为什么要长驻因为 snapshot 文件在容器文件系统里。容器销毁snapshot 就丢了。安全参数是重点--cap-drop ALL # 防 mount / 改网络 --security-opt no-new-privileges # 防提权 --pids-limit 256 # 防 fork 炸弹 --memory 512m # 内存上限SSHBackendControlMasterautoControlPathControlPersist300。连接复用30 条命令不用 30 次握手。BatchModeyes不弹交互提示。一行配置切后端terminal:backend:docker# local / docker / ssh工具代码一行不用改。远程后端读 cwd 文件多一步docker exec cat/ssh cat仅此而已。教学边界这层只管 terminal 工具。read_file不走这层——它没有跨命令状态open(path)改docker exec cat path即可。过渡能跑了但只能被动响应执行环境搞定了。命令可以在任意地方跑状态连续切换后端一行配置。但 agent 还是只能被动响应。用户不说它不动。“30 分钟后帮我看看部署成功没”——30 分钟后谁来看后半·定时任务让 Agent 到点自己动核心洞察任务即消息三个需求——延时、间隔、cron——一个共同点在未来某个时间主动做事。Hermes 的方案定时任务不是新的执行路径。它只是一个会在未来某个时间假装用户说了句话的机制。从GatewayRunner往下全部复用。工具、错误恢复、权限、委派——都自动生效。看真实代码dataclassclassCronJob:job_id:strschedule:str# 30m / every 2h / 0 9 * * 1-5prompt:str# 触发时发给 agent 的消息session_key:strcreated_at:strnext_fire:floatone_shot:bool# True delete after firing到期后构造一个MessageEventtextjob.prompt,platformcron,chat_id原用户 session_key送进GatewayRunner._handle_message。三个好处全部能力免费复用——工具、错误恢复、权限、委派执行历史自动持久化——SQLite 记录回复自动路由回原平台——用户在哪聊的结果回哪去三种调度格式格式示例含义one_shot延时30m30 分钟后一次True间隔every 2h每 2 小时循环FalseCron0 9 * * 1-5工作日 9 点False为什么不只用 cron因为用户说30 分钟后的频率远高于设个 cron。延时格式一行搞定parse_schedule(30m)。parse_schedule逻辑every前缀 → 循环间隔裸时长 → 一次性延时否则 → cron 表达式。_UNITS{s:1,m:60,h:3600,d:86400}_next_cron_fire五字段从下一分钟逐分钟找最多 366 天。注意 Pythonweekday()0Mon vs cron 0Sun 的转换——这个坑踩的人最多。JobStorejobs.json 锁持久化用jobs.json不用 SQLite。任务少、人可读、不需要全文搜索。threading.Lock()# 主线程写 后台线程读get_due返回到期任务advanceone_shot 直接 pop循环任务重新parse_schedule。JobScheduler后台线程 30 秒扫一次daemonTrue# 后台线程whileTrue:forjobinget_due():fire(job)advance(job)time.sleep(30)cron_toolaction create/list/delete闭包绑定 store session_key。CLI 模式fire_callback直接调run_conversation()没有 Gateway 的场景。避坑9 个初学者常踩的坑两章合并精选每一个都是真实事故。执行环境 4 错忘了过滤 API key——agent 执行env | grep KEY直接拿到密钥Docker 不加资源限制——fork 炸弹 / 内存打爆宿主机SSH 每条命令建新连接——30 条命令 30 次握手慢到怀疑人生命令包装用串联——用户命令非零时 export 不执行snapshot 断掉定时任务 5 错在主线程检查定时任务——input()阻塞定时任务永远不触发忘了加锁——字典遍历时大小改变RuntimeError一次性任务不删除——30 秒后又触发重复执行cron 星期几搞错——Python 0Mon vs cron 0Sun差一天没处理 jobs.json 损坏——原子写 JSONDecodeError降级阶段 3 收官多平台接收s12-s13 跨环境执行s14 定时主动做事s15。你的 agent 现在能接住任何平台的任何消息在任意环境执行命令到点自己开工。阶段 4 开始高级能力MCP、浏览器、语音视觉。下一篇第 11 篇拆解高级能力扩展继续关注。你在做定时任务时踩过哪个坑欢迎留言交流。参考文献Hermes Agent 教学仓库agents/s14_terminal_backends.py本文代码素材72864 字节真实可运行Hermes Agent 教学仓库agents/s15_scheduled_tasks.py本文代码素材90769 字节真实可运行Hermes Agent 教学仓库docs/zh/s14-terminal-backends.md与docs/zh/s15-scheduled-tasks.mdsnapshot 机制、任务即消息详解源码获取如需本系列全部源码请在以下链接克隆https://gitcode.com/ganxin7932508/learn-hermes-agent.git

相关新闻

最新新闻

PyCharm安装与配置全攻略:从零搭建Python开发环境

PyCharm安装与配置全攻略:从零搭建Python开发环境

1. 项目概述:为什么PyCharm是Python开发者的首选 如果你刚接触Python,或者从其他编辑器(比如VS Code、Sublime Text)转过来,面对PyCharm这个庞然大物,心里可能会犯嘀咕:安装它会不会很复杂&…

2026/8/15 3:32:11
NoMachine远程桌面:NX协议解析与高性能图形访问实战指南

NoMachine远程桌面:NX协议解析与高性能图形访问实战指南

1. 项目概述:为什么NoMachine值得你花时间了解?如果你经常需要在不同电脑之间切换工作,或者需要为家人朋友提供远程技术支持,那你一定对“远程桌面”这个概念不陌生。市面上有TeamViewer、AnyDesk、Windows自带的远程桌面&#xf…

2026/8/15 3:32:11
IDEA中Maven依赖报红问题深度解析与系统解决方案

IDEA中Maven依赖报红问题深度解析与系统解决方案

1. 项目概述:一个让开发者血压飙升的经典问题 “Maven依赖报红,本地明明有jar包,IDEA死活不加载”——这行字,但凡用过Maven和IntelliJ IDEA的Java开发者,看到都会心头一紧,血压微微升高。这不是一个简单的…

2026/8/15 3:32:11
学工一体化平台里的处分管理,流程到底怎么走才规范?

学工一体化平台里的处分管理,流程到底怎么走才规范?

✅作者简介:合肥自友科技 📌核心产品:智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

2026/8/15 3:32:11
Google Analytics性能优化:从阻塞渲染到异步加载的完整解决方案

Google Analytics性能优化:从阻塞渲染到异步加载的完整解决方案

1. 项目概述:当数据收集成为网站的性能瓶颈 如果你负责过网站的性能优化,大概率遇到过这个场景:页面加载速度测试工具(比如 Lighthouse 或 PageSpeed Insights)给你一个刺眼的红色警告——“减少第三方代码的影响”&am…

2026/8/15 3:32:11
15个AI智能体实战项目:从入门到精通,打造硬核简历

15个AI智能体实战项目:从入门到精通,打造硬核简历

你是不是也遇到过这样的困境:看到大模型和AI智能体(Agent)很火,想学点东西写到简历里,但网上的教程要么是“Hello World”级别的玩具项目,要么是理论满天飞却无从下手?或者,你跟着教…

2026/8/15 3:27:11