跳至主要内容
五折优惠 全部方案,限时优惠。起价 $2.48/mo
15 min left
AI 与机器学习

如何在 VPS 上调度 AI 智能体通宵运行

S 作者 Sajjad 15 分钟阅读
Schedule AI Agents Overnight: a dark terminal showing a 02:00 timestamp and a green exit code 0 line, next to a clock and a completed job card

凌晨两点,一个定时任务在一台从不休眠的 VPS 上触发。一次无界面的 claude -p 运行会在克隆好的仓库里处理排队任务,不向任何人发问,然后退出。等你早上查看时,已经有一个提交在等着,或者一份报告,或者一份日志,清楚说明它停在哪里、为什么停。没有人盯着它。

这和整夜开着终端、指望 SSH 连接别断掉是两回事。夜间智能体运行最常见的故障点就是宿主机:笔记本进入睡眠、合上盖子、网络掉线,或者系统更新在任务中途重启机器。认证失败、API 报错和权限卡顿依然可能让任务失败,但一台常开的主机能消除最容易出现的那种故障。

本文讲的是真正的机制:各大编码智能体 CLI 自带的无界面参数、按计划触发运行的两种方式以及该选哪一种、底层主机需要什么,以及那些护栏,它们能防止一次无人值守的运行花掉或搞坏你事后不想解释的东西。

简短版本

  • 各大编码智能体 CLI 都提供有文档说明的非交互模式,把一个提示词跑完就退出。Claude Code 用的是 claude -p,Codex CLI 用的是 codex exec,Gemini CLI 用的是 gemini -p。这不是变通手段,而是官方特性。
  • Claude Code 自己也有调度能力:Routines、Desktop 定时任务,以及 /loop。对一部分读者来说这确实就够了,而且比 VPS 更省心。
  • 跑一个每晚任务,cron 完全够用。但在可能重启的机器上,systemd 定时器是更好的默认选择,因为 Persistent=true 能补上 cron 会悄悄跳过的那次运行。
  • CLI 本身很轻,因为推理发生在服务商的 API 上。按它要运行的命令来给 VPS 选配置(测试、构建、容器、并行任务),而不是按模型。
  • 让你放心把定时任务丢在那儿的,是那些护栏:收窄的工具权限、回合上限、按退出码分支。定时任务本身并不是安全机制。

你需要准备什么

在写下第一行 crontab 或第一个 unit 文件之前,先准备好这五样东西:

  • 一台你能通过 SSH 登录、运行基于 systemd 的 Linux 发行版的 VPS。
  • 在这台 VPS 上装好智能体 CLI:Claude Code、Codex CLI 或 Gemini CLI。
  • 为你选定的 CLI 准备一份非交互式凭据。Claude Code 的 bare 模式不会读取账号登录信息,所以需要在环境变量里提供 ANTHROPIC_API_KEY ,或者在设置里配置一个 apiKeyHelper 。普通的 print 模式运行,以及 Codex 和 Gemini,也都可以使用各自文档所述的账号登录凭据。
  • 一个供智能体操作的仓库或任务目录。
  • 拥有编辑 crontab 或编写 systemd unit 文件权限的 shell 访问。

在没有会话附着的情况下运行智能体

无界面模式对比:Claude Code 运行 claude -p,输出可选 text、json 或 stream-json;Codex CLI 运行 codex exec,提供 JSONL 流和沙箱策略;Gemini CLI 运行 gemini -p,无需 TTY

各大编码智能体 CLI 都自带一个正为此设计的非交互模式。Claude Code 接受 -p,也可写作 --print。Codex CLI 接受 codex exec。Gemini CLI 接受 -p,也可写作 --prompt。三者都接受一个提示词,跑完就退出。没有聊天循环,不用一直开着终端,也没有什么需要重新连接。

Claude Code 能在没有活动会话的情况下运行吗?可以。 传入 -p 会以非交互模式运行提示词:Claude Code 执行到底,打印结果,然后退出。没有聊天循环,也没有需要保活的东西,而且它跑在与交互式 CLI 相同的 Agent SDK 上,依据是 Anthropic 官方的无界面模式文档.

CLI非交互模式参数行为结构化输出
Claude Code-p / --print把提示词跑完,打印结果,然后退出--output-format 设为 text、json 或 stream-json
Codex CLIcodex exec把进度流式输出到 stderr,把最终消息写到 stdout,然后退出--json 以获得 JSONL 事件流
Gemini CLI-p / --prompt以非交互方式执行提示词,然后退出--output-format json

这里最要紧的是 Claude Code 自己的参数,因为你实际要写脚本对付的就是它们。其中两个让运行能一直往下走,不会停下来索要一个半夜没人能批准的权限: --allowedTools,它预先批准指定的工具;还有 --permission-mode,它为整次运行设定基准。 --max-turns 限制一次运行最多能进行多少个智能体回合,超过就以错误退出。

--bare 会跳过 hook、skill、插件、MCP 服务器,以及诸如 CLAUDE.md这样的项目指令,从而让脚本化运行更快、更可预期。这也意味着任务依赖的每条指令都必须写进提示词或命令里。bare 模式同样不会读取你的账号登录信息,所以 Anthropic 的文档建议在环境变量中设置 API 密钥 ,然后再运行。Claude Code 会直接拒绝 --bg-p组合使用,也会同样拒绝 --cloud (当你给它一段任务描述时)。它会指出冲突所在并停下,而不是做出模棱两可的事。

一个完整的调用示例,把提示词和工具列表按你的任务改一改:

claude --bare -p "Review open PRs in this repo and summarize any blockers in NOTES.md" \
  --allowedTools "Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json

把预算和命令匹配模式按任务调整;这个示例还假设运行它的账号已经配置好 GitHub CLI 认证。

如果你正在一台全新的 VPS 上配置 Claude Code,想看在没有浏览器的机器上完成认证的步骤,这部分单独写在 如何在无界面服务器上完成 Claude Code 认证;上面的简短说明已足以让定时运行跑起来。

Codex CLI 的 exec 模式(说明见 OpenAI 的非交互模式文档)接受 --sandbox 来选择策略。 read-only 是默认值, workspace-write 允许智能体在自己的工作区内写入,而 --json 会把 stdout 变成机器可解析的事件流,而不是纯文本。对无人值守的任务,请避免使用 danger-full-access ,除非该进程已被隔离,而且你是有意承担这个风险。

Gemini CLI 的无界面模式(文档见 该项目自己的无界面模式文档)会在没有 TTY 的环境中自动启用,也可以用 -p显式指定。它会针对一般错误、输入错误和触及回合上限分别返回不同的非零退出码,而不是笼统地给一个失败码。

调度应该放在哪里

在动手做这些配置之前:智能体厂商也许已经替你把调度做好了。Claude Code 提供三个内置选项,其中某一个可能真的比你自己运维一台 VPS 更合适。

云端(Routines)Desktop 定时任务/loop
运行位置Anthropic 的云端你的机器你的机器
机器必须开着无需必填必需必需
需要保持会话打开无需必填无需必填必需
最小间隔1小时1 分钟1 分钟
访问本地文件没有,它从一份全新克隆运行完全访问完全访问

Anthropic 官方的定时任务文档 把这件事写成一个真正的三选一,而不是把 VPS 摆在顶端的等级排序。如果你的任务不需要本机状态、能接受一小时的最小间隔,而且你只用 Claude Code,那么 Routines 比下面要讲的方案更省心:你的机器关着时,Anthropic 会在云端用一份全新克隆替你跑。

/loop 值得了解,但不适合这个场景,因为它需要一个开着且空闲的会话,而这正是你想摆脱的约束。同一份文档还把 GitHub Actions 列为第四个选项,适合那些触发点本来就在 CI 里、而不是绑在某台特定机器的定时任务上的团队。

当任务需要完整的本地文件系统和工具权限、当你希望同一套机制在 Claude Code、Codex CLI 和 Gemini CLI 上表现一致、或者 Routines 允许的最小间隔太粗时,自管的 VPS 就有了它的位置。传统的无服务器函数在这里通常别扭,因为它得先恢复凭据、克隆仓库,还要在平台的运行时限内跑完。像 GitHub Actions 这样的临时 CI runner,在每次运行都重新检出可以接受时,仍是有效的第三条路。如果你手头已经有一台闲置的常开设备,homelab 机器同样能用;代价是要靠自家网络的可靠性和远程访问,而不是服务商的。

选 cron 还是 systemd 定时器?

cron 与 systemd 定时器对比:左边是一行 crontab,以及被直接跳过的错过运行;右边是 .service 加 .timer 组合,用 Persistent=true 补跑、由 journald 记录日志,并靠单实例控制重叠

两个工具都能按同样的时间表触发同一条命令,但它们在机器重启后会发生什么、以及各自要花多少配置功夫上分道扬镳:

cronsystemd 定时器
配置成本一行 crontab一个 .timer 文件加一个 .service 文件
错过运行的补跑没有,跳过的那次运行就是没了Persistent=true 会在系统恢复后立刻运行它
日志记录手动,得自己重定向输出自动,由 journald 捕获
依赖顺序没有完整的 systemd 顺序控制,支持 After= 和 Requires=

在很少重启的机器上跑每晚任务,普通 cron 完全够。坑在于环境:cron 启动时只有一个很小的 PATH,不会替你进入仓库目录,而且会在第一份还在跑的时候若无其事地再启动第二份。把仓库路径、收窄的智能体命令和凭据加载都放进一个受保护的包装脚本里,然后用 flock 来防止运行重叠。

# /usr/local/bin/agent-nightly
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export ANTHROPIC_API_KEY="$(
  cat "$HOME/.config/agent-nightly/anthropic_api_key"
)"
cd /srv/myrepo
exec /usr/local/bin/claude --bare -p \
  "Run the nightly dependency audit and write the findings to NOTES.md" \
  --allowedTools "Bash(npm audit *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json
# crontab -e
0 2 * * * /usr/bin/flock -n "$HOME/.local/state/agent-runs/nightly.lock" /usr/local/bin/agent-nightly >> "$HOME/.local/state/agent-runs/nightly.log" 2>&1

把凭据目录和日志目录创建一次,然后给包装脚本加上可执行权限:

install -d -m 700 \
  "$HOME/.config/agent-nightly" \
  "$HOME/.local/state/agent-runs"
touch "$HOME/.config/agent-nightly/anthropic_api_key"
chmod 600 "$HOME/.config/agent-nightly/anthropic_api_key"
"${EDITOR:-nano}" \
  "$HOME/.config/agent-nightly/anthropic_api_key"
sudo chmod 755 /usr/local/bin/agent-nightly

只把 API 密钥粘贴进凭据文件里,不要直接写在 crontab 中。

systemd 定时器要多花些配置功夫,但能给你两样 cron 没有的东西:不用手写重定向的 journald 日志,以及 Persistent=true。下面的例子假设有一个专用账号 agent-runner 拥有 /srv/myrepo。把 API 密钥放在只有 root 能读的凭据文件里,而不要嵌进 unit 文件。

按照 systemd.timer 手册,设置 Persistent=true 的含义是:“如果在定时器处于非活动状态期间,该服务单元本应至少被触发过一次,那么它会被立即触发。”于是,本该在你的 VPS 因内核更新而重启时触发的那次运行,会在机器恢复的那一刻跑起来,而不是悄无声息地消失到下一个计划时间点。

创建服务要用的、仅 root 可读的凭据文件:

sudo install -d -m 700 /etc/agent-nightly
sudo touch /etc/agent-nightly/anthropic_api_key
sudo chmod 600 /etc/agent-nightly/anthropic_api_key
sudoedit /etc/agent-nightly/anthropic_api_key

只把 API 密钥粘贴进这个文件。

# /etc/systemd/system/agent-nightly.service
[Unit]
Description=Nightly scoped agent run
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=agent-runner
Group=agent-runner
WorkingDirectory=/srv/myrepo
Environment=HOME=/home/agent-runner
Environment=PATH=/usr/local/bin:/usr/bin:/bin
LoadCredential=anthropic_api_key:/etc/agent-nightly/anthropic_api_key
ExecStart=/bin/sh -c 'export ANTHROPIC_API_KEY="$(cat "$CREDENTIALS_DIRECTORY/anthropic_api_key")"; exec /usr/local/bin/claude --bare -p "Run the nightly dependency audit and write the findings to NOTES.md" --allowedTools "Bash(npm audit *),Read,Edit" --permission-mode dontAsk --max-turns 8 --max-budget-usd 5.00 --output-format json'
StandardOutput=journal
StandardError=journal
UMask=0077
# /etc/systemd/system/agent-nightly.timer
[Unit]
Description=Run agent-nightly.service at 2am daily, catching up missed runs

[Timer]
# Uses the VPS's configured local timezone
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=agent-nightly.service

[Install]
WantedBy=timers.target

重新加载 systemd、启用定时器,并立刻手动跑一次服务,好让凭据、权限和路径问题现在就暴露出来,而不是凌晨两点:

sudo systemctl daemon-reload
sudo systemctl enable --now agent-nightly.timer
sudo systemctl start agent-nightly.service
systemctl list-timers agent-nightly.timer
sudo journalctl \
  -u agent-nightly.service \
  -n 100 \
  --no-pager

Persistent=true 是决定性的差别:定时器会记住错过的日历触发,而不是悄悄丢掉。

VPS 到底需要什么

第一次给这类任务选配置的人常在这里意外:CLI 本身很轻,因为推理发生在服务商的 API 上。但智能体依然会在本地启动构建、测试、包管理器、语言服务器和容器,所以真正的下限由仓库的工作负载决定。

跑一个轻量的定时任务,可以把 1-2 个 vCPU、2-4 GB 内存加 NVMe 存储当作起点。大仓库、编译器、Docker 构建、测试套件或并发运行可能需要多得多。促使你加配置的是智能体要跑的最重的那条本地命令,而不是 API 背后的模型。如果你已经在这台 VPS 上跑 Docker 负载,想看更完整的预算参考, 构建机的选型与安全加固 一文对另一种无人值守负载讲了同样的权衡。

还有一件值得提前规划的事:无人值守的运行每晚都会产生日志,不管出没出问题。如果 cron 把输出写进文件,就加上 logrotate ,并且去查一下 journald 的保留上限,而不是想当然地认为默认值适合这台 VPS 的磁盘。

整套做法都依赖一台凌晨两点还醒着、并且不管你的笔记本在干什么都保持如此的主机。这正是带 root 权限的 Linux VPS 所擅长的具体差事。没有什么会让它休眠,你也不必和别人的 cron 任务共用它。

查看 Linux 套餐

在拥有 root 权限、NVMe 与 AMD EPYC 强大性能的 Linux VPS 上构建应用。

查看 Linux 套餐

别让无人值守的运行出岔子

定时运行成与不成,最大的分野在于任务是否收得够窄,窄到中途不需要人来回答问题就能跑完。野心过大的提示词会卡在那里,等一个当时没人能做的决定;范围窄、自成一体的任务则会跑完并干净退出。

这两个权限参数的存在,就是为了让运行不会在凌晨两点卡在权限询问上。但赤裸裸的 Bash 权限并不是一道窄护栏:它几乎能做服务账号能做的任何事。更好的做法是使用针对具体命令的规则,例如 Bash(git status *),把它们和 --permission-mode dontAsk搭配使用,并让服务在一个专用的非 root 账号下运行。回合数和花费各有各的上限: --max-turns 限制智能体能兜多久,而 --max-budget-usd 则给单次运行在 API 调用上的花费设上限。

小贴士:用 --output-format json 运行,并记录每次调用的 total_cost_usd 字段。这是追踪一次定时运行每晚实际花费、以及在某次运行明显比其他贵时发出告警的最干净的切入点。花五分钟接上它是值得的,因为它盯的是你的账单,不是什么抽象概念。

无人值守带来的费用失控并不是假设。 在 Hacker News 的一篇帖子里,一位用户报告了一张 37,901.73 美元的 AWS Bedrock 总账单,来自一个每日运行的编码智能体工作流,其中提示缓存只起了部分作用,约 64.7 亿输入 token 未被缓存。这发生在另一套技术栈上,不是 Claude Code 的无界面模式,但它说明了为什么成本日志和每次运行的硬性预算应当写进调度里。

小贴士:Claude Code 成功时以退出码 0 结束,失败时返回非零码。一个检查退出状态的包装脚本可以在失败时给你发通知,这样糟糕的一夜第二天早上就会浮出水面,而不是三天后你碰巧去看时才发现。

至少要让每个任务跑在专用分支或一次性 worktree 上,并要求人工审核后再合并。收窄权限的凭据、文件系统隔离和服务器层面的影响范围控制,是更大的一个话题,值得单独成篇,而不是在一篇调度指南末尾加一段。

让你能放心不管这套定时任务的,是那些护栏:定时任务本身并不是安全机制。

cron 什么时候就不够用了

定时跑一条提示词,不需要比前面讲的更多的东西。但三个串起来的步骤,带条件判断、重试和 Slack 通知,就是另一回事了。

有三个选项值得了解,各自出于不同理由更进一步:

  • Dagu 是最轻的一步:用 YAML 定义、自成一体的作业,带 DAG 依赖、重试,还有一个能看到跑了什么的 Web 界面。
  • n8n 最合适的场景是:智能体运行只是若干集成与通知之间的一个节点,而不是整个工作流。
  • Kestra 是三者中最重的,为编排数据与基础设施流水线而生。当给智能体排期只是更大流水线的一环、而非目的本身时,它才是正解。

对每晚只跑一条提示词的读者来说,这三个都属于杀鸡用牛刀,把话说明白比劝你上一套超出需要的方案更好。如果将来某条步骤链确实撑得起其中之一, Dagu, n8n,以及 Kestra 都支持一键部署,而这恰好在你权衡配置成本值不值得的那一刻,是实打实的方便。

像 LangChain 或 CrewAI 这样的多智能体编排框架完全是另一回事:那是在构建智能体系统,而不是给一个已经存在的 CLI 排期。

常见问题

Claude Code 能在没有活动会话时运行吗?

可以。传入 -p 会以非交互模式运行提示词:Claude Code 执行到底、打印结果,然后退出,没有聊天循环,也没有需要一直开着的会话。

既然 Claude Code 已经有 Routines,我还需要 VPS 吗?

不一定。Routines 在你的机器关着时也能在 Anthropic 云端运行,并从全新克隆开始,但它访问不到只存在于你本机的文件,而且最小间隔是一小时。当任务需要本地文件、需要任意间隔,或者需要一套在多家厂商 CLI 上表现一致的机制时,自管的 VPS 才有它的价值。

定时运行智能体,该用 cron 还是 systemd 定时器?

如果这台 VPS 会因为维护而重启,就选 systemd 定时器。 Persistent=true 会在系统恢复后立刻补跑那次本该在停机期间触发的任务,而 cron 没有对应机制。对一直开着的机器上的每晚任务,cron 就够了。

在 VPS 上定时跑 AI 智能体需要多少内存?

轻量的定时任务,可以从 1-2 个 vCPU、2-4 GB 内存起步,然后按智能体要跑的最重的本地命令来定配置。构建、测试、Docker、大仓库和并发运行的影响,远大于远端的模型推理。

按计划运行智能体会改变计费方式吗?

调度不会带来单独的计费模式。Claude Code 的 -p 既可以用订阅凭据,也可以用 API 密钥,但 --bare 会忽略订阅登录,因此需要在环境变量里提供 ANTHROPIC_API_KEY ,或者在设置里配置一个 apiKeyHelper ,或在设置里配置对应项。Codex 和 Gemini 则沿用你为各自 CLI 配置的认证方式。由于价格和使用条款变动很快,配置时请查看服务商的当前价格和你自己的用量数据。对于走 API 的 Claude Code 运行,你还可以记录 total_cost_usd JSON 输出中的该字段。

分享

讨论

评论

登录后参与讨论。

博客更多内容

继续阅读。

准备好部署了吗? 起价 $2.48/月。

独立云厂商,自 2008 年起。AMD EPYC、NVMe、40 Gbps。14 天退款保证。