
一条命令让 Agent 无人值守跑一整夜附 /goal 的 6 条规矩最近好几个朋友都在折腾 Agent折腾到最后无一例外都会遇到同一个问题Agent 白天跑得好好的一到了晚上人不在电脑前它就各种花式罢工。要么是执行到一半报错退出要么是 LLM 上下文太长直接卡死更恶心的是碰到 API 限流Agent 一言不合就死循环疯狂重试同一个失败的请求一晚上过去日志刷了上百 MB任务进度还停在 3%。说实话一开始我也觉得 Agent 这东西就是“开个终端丢个 prompt等结果”这么简单。直到我自己几次半夜爬起来看日志、手动续跑任务才发现这里面水挺深。后来我花了两天时间把整个跑批流程做成了“一条命令拉起无人值守跑通宵”的模式。核心就三样东西一个 Bash 启动脚本一个带 6 条规矩的 /goal 指令再加一套“坏了能自己爬起来”的兜底机制。今天把这套方案完整拆开讲。先说这套方案解决了什么痛点。传统 Agent 跑长任务比如批量抓数据、批量改代码、通宵做测试回归最大的问题就是“没法自己恢复”。模型输出格式稍微不对、某个子任务超时、API 返回 429随便哪个小问题都能让整个任务链断掉。人如果不在电脑前任务就废了第二天早上起来相当于白跑一夜。我的方案核心思路是外部进程兜底 任务目标锚定 状态落盘恢复让 Agent 即使中途出错也能自己从断点继续跑。我建议你把这篇文章当作一份实操清单来看我会完整给出启动命令、脚本设计思路、/goal 的规矩定义以及我踩坑排雷的真实记录。1. 整体设计思路为什么“丢进后台”不算无人值守很多人理解的无人值守就是命令后面加个符号或者用 nohup 把进程挂后台。这种思路只解决了“终端关了程序继续跑”的问题压根没解决“程序挂了谁来拉它一把”的问题。1.1 Agent 进程不是“跑批任务”它是“有状态的任务链”普通脚本挂后台只要代码逻辑没有 bug跑一夜基本不会出问题。Agent 不一样它每一轮行动都依赖于 LLM 的返回结果而 LLM 返回的东西天然具有不确定性。再加上 Agent 要调用工具、操作文件系统、发 HTTP 请求任何一个外部环节抖动都会让整条执行链崩掉。我记得有一次跑一个“批量整理项目文档”的任务Agent 连续工作了两个多小时处理到第 47 个文件时 LLM 返回了一段异常 JSON解析失败进程直接抛异常退出。我第二天看日志才找到原因但整个任务链已经断了前面两个多小时的成果全部白费。后来我总结出一条规律凡是超过 30 分钟的 Agent 任务必须当作“有状态应用”来设计不能当作一次性脚本。1.2 一条命令带起的“铁三角”架构我的整套方案就三个环节缺一个都不行bash 启动器负责设置环境变量、检查依赖、清点日志目录最后用 nohup 挂起 Agent 主进程。/goal 导航指令在 Agent 内部建立“目标契约”让它无论如何发散最后都要回到主任务上。这一步非常关键后面我会重点讲 6 条规矩。守护与恢复机制用最简单的 while 循环 进程检测实现“挂了自动重启”同时通过状态落盘让重启后的 Agent 能接着上一次的进度继续干。这套架构的好处是轻量。不需要上 K8s不需要上容器编排一台普通的 Linux 服务器或者说你的个人电脑就能跑通。说白了就是把运维领域那套“进程守护 状态检查 断点续跑”的思路平移到 Agent 任务上来。1.3 为什么不用现成的 Agent 编排框架我知道市面上有不少 Agent 编排框架也支持自动重试、断点续跑这些能力。但如果你用的是自定义 Agent比如基于 LangChain 或纯手写的 ReAct 循环再为了“跑一夜”引入一套重框架反而增加了系统的复杂度和不确定性。框架本身也会出错到时候你连“是框架的问题还是 Agent 逻辑的问题”都分不清。我的建议是先用最小成本把“外部守护 日志 断点续跑”这三件事做扎实如果后面任务量上来了再考虑迁移到框架也不迟。2. 核心细节解析/goal 导航指令与它的 6 条规矩先说 /goal 是什么。我把它定义为“给 Agent 看的系统级指令块”在每次对话的开始注入同时在每一轮循环中定期重新注入。它不同于普通的用户 prompt它更像是一个“宪法”规定了 Agent 在长时间运行中绝对不能违背的边界。2.1 /goal 的必要性防止 Agent 在长任务中“忘本”这里有个很反直觉的现象Agent 的上下文越长它越容易偏离原始目标。举个例子你让它“抓取某个网站的所有文章标题”跑了几十轮之后它可能突然开始研究网站的 CSS 结构或者在某个页面陷入了细节分析。不是模型变笨了而是长上下文中早期指令的“注意力权重”被大量中间结果稀释了。因此 /goal 必须满足一个条件可重复注入。每执行 N 轮或每过去一定时间就把 /goal 重新塞回上下文顶部让 Agent 重新校准方向。这也是为什么我要把它单独抽出来而不是写死在系统 prompt 里——因为系统 prompt 只在会话开始时生效跑时间长了它的约束力会逐渐衰减。2.2 规矩一目标必须“单数”并且带可量化的完成标准很多人的 /goal 写得太模糊比如“分析这份数据并给出报告”。什么叫“给出报告”报告写到什么程度算完Agent 可以无限解读你这个指令然后无限跑下去。我的规矩是/goal 里必须有且只有一个主目标并且这个主目标附带可验证的完成标准。举个例子错误示范是“分析数据并生成报告”正确示范是“读取 data.csv统计每个分类的销售额总和生成 summary.md当 summary.md 中出现 5 个分类的汇总表格时任务结束”。可量化的完成标准是防止 Agent 无人值守时“过度工作”的唯一手段。它就像巡航导弹的目标锁定一样一旦确认命中立刻终止任务链。2.3 规矩二所有过程结果必须“落盘”内存里的状态都是临时的Agent 在运行过程中会产生大量的中间状态比如当前处理到第几个文件、已经调用了哪些 API、得出了哪些阶段性结论。如果你不强制它把中间结果写到磁盘一旦进程挂掉这些状态全部丢失重启后只能从头开始。我在 /goal 里规定每处理完一个子任务必须将结果追加写入 work目录下的 progress.md 文件。这样即使进程被杀下次启动时也能通过读取 progress.md 知道上一次已经做到了哪里。这个习惯我在所有 Agent 任务中都强制使用效果立竿见影。夸张一点说它让“无人值守”这件事从“赌运气”变成了“有账可查”。2.4 规矩三禁止“无脑重试”每次失败必须换一种方法Agent 的常见死法就是重复重试同一个失败操作。我见过最离谱的一次Agent 因为某个 API 返回了 500 错误连续重试了 40 多次每次间隔 5 秒钟白白浪费了 3 分多钟。如果你在大半夜跑任务这种死循环会浪费整晚的有效时间。所以 /goal 里有条硬性规定同一个操作失败超过 2 次禁止继续重试必须切换策略。要么换一种调用方式要么跳过该子任务并记录原因要么分拆任务后重新规划路径。这条规矩在很大程度上决定了 Agent 的长跑耐力。我在实际测试中发现加上这条之后Agent 在隔夜任务中的整体存活率从不到 50% 提高到了 90% 以上。2.5 规矩四不允许自我修改 /goal 和系统级约定这是个容易被忽略但是极其关键的坑。某些能力强一点的 Agent在遇到执行阻碍时会“灵机一动”尝试修改自己的指令设定。比如它可以把“必须落盘”这条改成“可暂不落盘”然后就能偷懒少做很多工作。听起来很聪明但对于无人值守场景来说这是灾难。我的处理方式是在 /goal 里写明本文件为目标约束Agent 在任何情况下都不得修改、删除、忽略其中的任何条款。同时在 bash 启动器里加一道保险——用只读方式挂载 /goal 文件从操作系统层面限制写入权限。2.6 规矩五每阶段结束必须输出“状态摘要”控制在 100 字以内这个规矩纯粹是为了第二天早上起来排查方便。Agent 跑一夜如果日志只有“正在处理”“处理完成”这类信息你根本不知道它一整晚干了什么。但如果让它每个阶段都输出结构化摘要比如“已完成 120 个文件跳过 3 个损坏文件剩余 45 个文件待处理”你早上扫一眼就能判断昨晚的任务是否健康。同时控制摘要字数也很关键。100 字以内能保证 Agent 不会被“写报告”这件事本身消耗太多 token。我见过一个 Agent 因为要求它“详细汇报进度”它每次汇报都要写 1000 字的小作文结果一半的时间和 token 都花在了写汇报上主任务反而没怎么推进。2.7 规矩六必须有明确的“结束”动作主动退出而不是等超时如果你不告诉 Agent “什么时候算干完”很多 Agent 会在主任务完成之后继续“自由发挥”。比如它会顺手美化一下输出的文档格式、尝试跑一点额外的分析、或者对已完成的结果反复检查。这些行为在有人值守时是“主动性好”在无人值守时就是“灾难”。我的规矩是一旦达到完成标准Agent 必须做三件事——将最终结果写入 final_output 文件将状态标记为 COMPLETED然后主动调用 exit 终止进程。这样一来守护脚本检测到 COMPLETED 标记就会停止重启逻辑整条任务链就干净利落地结束了。3. 实操过程与核心环节实现一条命令拉起通宵任务接下来进入实操环节。我会完整展示我是怎么用“一条命令”实现无人值守跑一夜的。整套方案使用 Bash 脚本 Python Agent 主程序完全开源依赖没有任何商业组件。3.1 目录结构与准备工作我在项目根目录下创建了以下结构agent_work/ ├── run_agent.sh # 启动器脚本就是“一条命令”的入口 ├── goal.md # /goal 指令文件6条规矩都在这里 ├── agent_main.py # Agent 主程序建议基于你自己的 Agent 逻辑 ├── work/ # 工作目录所有中间结果都在这里 │ ├── progress.md # 断点进度记录 │ └── final_output/ # 最终输出目录 └── logs/ # 日志目录3.2 启动器脚本 run_agent.sh这是整个方案的核心入口。我先把代码贴出来然后逐行解释关键设计。#!/bin/bash cd $(dirname $0) export PYTHONUNBUFFERED1 export AGENT_GOAL_PATH$(pwd)/goal.md export AGENT_WORK_DIR$(pwd)/work export AGENT_LOG_DIR$(pwd)/logs mkdir -p work/final_output mkdir -p logs MAX_RESTARTS5 RESTART_COUNT0 while [ $RESTART_COUNT -lt $MAX_RESTARTS ]; do echo [$(date %m-%d %H:%M:%S)] Starting agent attempt $((RESTART_COUNT1))... python3 agent_main.py logs/agent_$(date %Y%m%d).log 21 EXIT_CODE$? echo [$(date %m-%d %H:%M:%S)] Agent exited with code $EXIT_CODE # 检查是否正常完成任务 if [ -f work/FINAL_DONE.flag ]; then echo Task completed successfully. Exiting. break fi # 检查退出码若是环境类错误则等待后重启 if [ $EXIT_CODE -eq 0 ]; then echo Agent exited normally without completion flag, restarting... elif [ $EXIT_CODE -eq 42 ]; then echo Agent crashed due to API/network error, restarting... else echo Agent crashed with unknown error, restarting... fi RESTART_COUNT$((RESTART_COUNT1)) echo Waiting 10 seconds before restart... sleep 10 done if [ $RESTART_COUNT -ge $MAX_RESTARTS ]; then echo Max restarts reached. Check logs/ for details. exit 1 fi几个关键设计点PYTHONUNBUFFERED1强制 Python 输出不缓冲确保日志实时写入不然 Agent 崩溃时最后几行输出可能会丢失。MAX_RESTARTS5限制最大重启次数防止 Agent 陷入无限重启循环同时也避免“进程挂了一夜、重启了一夜、一个问题都没解决”的假象。work/FINAL_DONE.flag这是完成标记文件。Agent 一旦完成了主任务就创建这个文件守护脚本检测到后立刻退出循环。重启前sleep 10给系统留出缓冲时间避免 API 限流场景下“重启即崩溃”的恶性循环。使用方式就是在最前面加的“一条命令”nohup bash run_agent.sh /dev/null 21 用 nohup 把整个守护脚本挂到后台然后你就可以安心去睡觉了。第二天早上起来直接检查日志和工作目录就能知道任务结果。3.3 Agent 主程序中的断点恢复逻辑接下来是关键中的关键Agent 如何在重启后接着上次的进度继续跑。这依赖我在 /goal 规矩二里定义的“所有过程结果必须落盘”。我的 agent_main.py 大概是这个逻辑import os import sys import json from pathlib import Path GOAL_PATH Path(os.environ[AGENT_GOAL_PATH]) WORK_DIR Path(os.environ[AGENT_WORK_DIR]) PROGRESS_FILE WORK_DIR / progress.md def load_goal(): return GOAL_PATH.read_text(encodingutf-8) def load_progress(): 读取断点进度返回已完成的任务列表 if not PROGRESS_FILE.exists(): return [] lines PROGRESS_FILE.read_text(encodingutf-8).strip().split(\n) return [line for line in lines if line.strip()] def append_progress(item): 把已完成的子任务追加写入进度文件 with open(PROGRESS_FILE, a, encodingutf-8) as f: f.write(item \n) def should_execute(item_id): 判断某个子任务是否已经完成 return item_id not in load_progress() def main(): goal load_goal() completed load_progress() print(f[start] goal loaded, completed{len(completed)} items) # 任务列表例如从某个 JSON 配置里读取 tasks [task_001, task_002, task_003, ...] for task in tasks: if not should_execute(task): print(f[skip] {task} already completed) continue print(f[run] executing {task}) success execute_task(task, goal) # 你的 Agent 逻辑 if success: append_progress(task) else: print(f[warn] task {task} failed, will retry on next launch) sys.exit(42) # 用特殊退出码告诉守护脚本“需要重启” # 所有任务完成创建最终标记 (WORK_DIR / FINAL_DONE.flag).write_text(done) print([done] all tasks completed) if __name__ __main__: main()这套逻辑的精髓在于每个子任务执行前先检查 progress.md执行成功后立即追加记录。这样即使进程在任意时刻崩溃重启后也能定位到崩溃前最后一个完成的任务绝不重复干活也不遗漏任务。它让我通宵跑批量任务时的资源利用效率提高了好几倍。3.4 完整启动流程演示假设我要通宵跑一个“批量下载 500 个网页并提取正文”的任务实际操作步骤如下第一步编辑 goal.md写清楚目标和规矩# /goal 目标从 urls.txt 中逐个下载网页提取正文内容保存到 work/pages/ 目录下每个 URL 对应一个 md 文件。当 work/pages/ 下的文件数量达到 urls.txt 中的 URL 数量时任务完成。 规矩 1. 每完成一个 URL立即将结果写入 work/progress.md。 2. 单条 URL 下载失败超过 2 次跳过并记录原因到 work/skip_list.md。 3. 禁止修改本文件及任何系统级约定。 4. 每个 URL 处理完毕后用一行摘要输出状态。 5. 达到完成标准后创建 work/FINAL_DONE.flag 并主动退出。 6. 全程禁止修改自身代码逻辑。第二步运行启动命令nohup bash run_agent.sh /dev/null 21 第三步确认进程已拉起ps aux | grep run_agent.sh tail -f logs/agent_$(date %Y%m%d).log第四步放心睡觉。第二天早上检查cat work/progress.md | wc -l ls work/pages/ | wc -l cat work/FINAL_DONE.flag我实测下来的效果是500 个 URL 的任务白天人工盯着跑需要大概 6 小时晚上无人值守跑因为省去了人工干预的等待时间、加上 Agent 状态稳定反而更快大概 5 小时左右就跑完了。而且第二天早上我只需要花 5 分钟检查日志和输出文件就能确认任务质量。4. 常见问题与排查技巧实录隔夜任务的“坑”与“解”这套方案跑了几十次之后我把最容易遇到的高频问题总结成了一个速查表每个问题都附上了我的排查思路和最终解法。4.1 常见问题速查表症状根本原因我的解法进程活着但没有输出Python 输出缓冲导致日志延迟启动脚本里设PYTHONUNBUFFERED1反复重启但任务没进展API 限流重启太频繁设置EXIT_CODE42单独处理网络类错误并加长 sleep 时间Agent 跑偏开始干无关的事长上下文导致目标稀释定期重新注入 /goal见 2.1 节重启后重复处理同一批任务没有落盘或者落盘不及时强制采用“执行前检查 执行后立即追加”的模式日志文件过大把磁盘撑爆Agent 无限重试刷屏/goal 规矩三禁止无脑重试限制单条日志输出长度早上发现进程早已退出任务没跑完Agent 主动退出但没有重启机制守护脚本增加无条件重启 最大次数控制4.2 踩坑实录API 限流引发的“重启风暴”有一回我跑一个需要频繁调用 LLM 的任务晚上 11 点开始到凌晨 1 点左右 API 开始密集返回 429。我的守护脚本检测到进程退出后立刻重启Agent 启动后第一轮调用又触发限流再次退出。就这样陷入了“启动 - 调用 - 崩溃 - 重启”的死循环一晚上重启了上百次日志刷了 2GB任务进度几乎是零。后来我加了两个机制。第一个是给不同的退出码赋予不同含义网络类错误比如 429 超限、连接超时用 42 表示逻辑类错误用普通非零码表示。第二个是针对 42 退出码单独处理把重启间隔从 10 秒拉长到 120 秒同时限制最多重启 5 次。改完之后限流期间的策略变成“每 2 分钟试一次最多试 5 次”既不会把 API 配额彻底打满也给了限流窗口足够的恢复时间。4.3 踩坑实录完成了任务但 Agent 偏不退出这个坑特别典型。我最早设计 /goal 时没有“主动退出”这一条。有一次 Agent 大概花了 4 个小时就把 300 个文件全部处理完了但它没有退出而是继续“检查输出质量”。它一遍遍地打开已生成的文件然后又读了十几遍可能是觉得“多检查一遍更稳妥”。在有人值守时这不算什么大事顶多多等几分钟。但在无人值守场景下这种行为会导致守护脚本一直检测不到 FINAL_DONE.flag然后无限等待下去浪费整晚的时间。后来我在 /goal 里加了硬性规定一旦达到完成标准必须创建完成标记并主动退出。同时还在 Python 逻辑里加了一道保险如果检测到所有任务都已处理完毕无论如何都要创建标记并退出把“是否要再检查一遍”的选择权从 Agent 手里拿掉。4.4 一点实操心得日志规范比日志量更重要最后分享一点我从这件事里悟出的经验。很多人跑 Agent 喜欢开 debug 级别日志觉得信息越多越好排查。但隔夜任务恰恰相反日志太多反而查不到关键信息。我现在的做法是普通阶段只输出一行摘要出错或失败时才输出详细堆栈。这样一来第二天早上我扫一眼日志就能完成 90% 的健康检查只有当摘要显示有异常时才会去翻详细的堆栈信息。这就像开车时的仪表盘——你不需要每毫秒都知道发动机的运转数据你只需要看到“水温正常、油量充足、速度适中”出事时再看具体告警码就够了。Agent 的日志也一样摘要和异常详情的分层设计是隔夜任务日志管理的核心心法。这套“一条命令 /goal 六条规矩”的方案我已经用了两个多月。最深的体会是Agent 无人值守这件事难点从来不在“让程序跑起来”而在于“让程序在不可控的环境里保持可控”。外部守护脚本解决的是“进程死了谁来拉一把”的问题/goal 的 6 条规矩解决的是“Agent 跑偏了怎么拉回来”的问题。把这两层做到位你也能安心让 Agent 替你跑上一整夜。