Cron 子系統提供排程任務執行 — 從簡單的單次延遲到帶有技能注入和跨平台遞送的週期性 cron 運算式任務。
主要檔案
| 檔案 | 用途 |
|---|---|
cron/jobs.py | 任務模型、儲存、對 jobs.json 的原子讀寫 |
cron/scheduler.py | 排程器迴圈 — 到期任務偵測、執行、重複追蹤 |
tools/cronjob_tools.py | 面向模型的 cronjob 工具註冊和處理器 |
gateway/run.py | 閘道器整合 — 在長期運行迴圈中的 cron 作業 |
hermes_cli/cron.py | CLI hermes cron 子命令 |
排程模型
支援四種排程格式:
| 格式 | 範例 | 行為 |
|---|---|---|
| 相對延遲 | 30m、2h、1d | 單次執行,經過指定時間後觸發 |
| 間隔 | every 2h、every 30m | 週期性,以固定間隔觸發 |
| Cron 運算式 | 0 9 * * * | 標準 5 欄 cron 語法(分鐘、小時、日、月、星期) |
| ISO 時間戳 | 2025-01-15T09:00:00 | 單次執行,在精確時間觸發 |
面向模型的介面是一個單一的 cronjob 工具,帶有動作風格的操作:create、list、update、pause、resume、run、remove。
任務儲存
任務儲存在 ~/.hermes/cron/jobs.json 中,使用原子寫入語義(寫入臨時檔案,然後重新命名)。每個任務記錄包含:
{
"id": "a1b2c3d4e5f6",
"name": "Daily briefing",
"prompt": "Summarize today's AI news and funding rounds",
"schedule": {
"kind": "cron",
"expr": "0 9 * * *",
"display": "0 9 * * *"
},
"skills": ["ai-funding-daily-report"],
"deliver": "telegram:-1001234567890",
"repeat": {
"times": null,
"completed": 42
},
"state": "scheduled",
"enabled": true,
"next_run_at": "2025-01-16T09:00:00Z",
"last_run_at": "2025-01-15T09:00:00Z",
"last_status": "ok",
"created_at": "2025-01-01T00:00:00Z",
"model": null,
"provider": null,
"script": null
}
任務生命週期狀態
| 狀態 | 意義 |
|---|---|
scheduled | 活躍,將在下次排程時間觸發 |
paused | 暫停 — 在恢復之前不會觸發 |
completed | 重複次數用盡或已完成的單次任務 |
running | 目前執行中(過渡狀態) |
向後相容
較舊的任務可能有一個單一的 skill 欄位而非 skills 陣列。排程器在載入時會將其正規化 — 單一 skill 會被提升為 skills: [skill]。
排程器運行時
作業迴圈
排程器在週期性作業上運行(預設:每 60 秒):
tick()
1. 取得排程器鎖(防止重疊的作業)
2. 從 jobs.json 載入所有任務
3. 篩選到期任務(next_run <= now AND state == "scheduled")
4. 對於每個到期任務:
a. 設定狀態為 "running"
b. 建立全新的 AIAgent session(無對話歷史)
c. 按順序載入附加的技能(作為使用者訊息注入)
d. 透過代理程式執行任務提示詞
e. 將回應遞送到設定的目標
f. 更新 run_count,計算 next_run
g. 如果重複次數用盡 → state = "completed"
h. 否則 → state = "scheduled"
5. 將更新後的任務寫回 jobs.json
6. 釋放排程器鎖
閘道器整合
在閘道器模式下,排程器在專用的背景執行緒中運行(gateway/run.py 中的 _start_cron_ticker),每 60 秒呼叫一次 scheduler.tick(),與訊息處理並行。
在 CLI 模式下,cron 任務只在執行 hermes cron 命令或活躍的 CLI session 期間觸發。
全新 Session 隔離
每個 cron 任務在完全全新的代理程式 session 中運行:
- 沒有來自先前運行的對話歷史
- 沒有先前 cron 執行的記憶(除非持久化到記憶體/檔案)
- 提示詞必須是自包含的 — cron 任務無法提出澄清問題
cronjob工具組被停用(遞迴保護)
基於技能的任務
cron 任務可以透過 skills 欄位附加一個或多個技能。在執行時:
- 技能按指定順序載入
- 每個技能的 SKILL.md 內容作為上下文注入
- 任務的提示詞作為任務指令附加在後面
- 代理程式處理結合的技能上下文 + 提示詞
這使得可重複使用、經過測試的工作流程無需將完整指令貼到 cron 提示詞中。例如:
建立每日資金報告 → 附加 "ai-funding-daily-report" 技能
基於腳本的任務
任務也可以透過 script 欄位附加一個 Python 腳本。腳本在每次代理程式回合之前運行,其 stdout 會作為上下文注入到提示詞中。這支援資料收集和變更偵測模式:
# ~/.hermes/scripts/check_competitors.py
import requests, json
# 取得競爭對手的發佈說明,與上次運行進行差異比較
# 將摘要列印到 stdout — 代理程式進行分析並報告
腳本逾時預設為 120 秒。_get_script_timeout() 透過三層鏈解析限制:
- 模組級覆寫 —
_SCRIPT_TIMEOUT(用於測試/猴子補丁)。只在與預設值不同時使用。 - 環境變數 —
HERMES_CRON_SCRIPT_TIMEOUT - 設定 —
config.yaml中的cron.script_timeout_seconds(透過load_config()讀取) - 預設值 — 120 秒
Provider 恢復
run_job() 將使用者設定的後備 provider 和認證池傳遞給 AIAgent 實例:
- 後備 provider — 從
config.yaml讀取fallback_providers(列表)或fallback_model(舊式字典),與閘道器的_load_fallback_model()模式一致。作為fallback_model=傳遞給AIAgent.__init__,該方法將兩種格式正規化為後備鏈。 - 認證池 — 透過
load_pool(provider)從agent.credential_pool使用已解析的執行階段 provider 名稱載入。只有在池中有認證資訊時才傳遞(pool.has_credentials())。支援在 429/限流錯誤時進行同 provider 的金鑰輪替。
這與閘道器的行為一致 — 如果沒有它,cron 代理程式在遇到限流時會直接失敗而不嘗試恢復。
遞送模型
Cron 任務結果可以遞送到任何支援的平台:
| 目標 | 語法 | 範例 |
|---|---|---|
| 原始聊天 | origin | 遞送到建立任務的聊天室 |
| 本地檔案 | local | 儲存到 ~/.hermes/cron/output/ |
| Telegram | telegram 或 telegram:<chat_id> | telegram:-1001234567890 |
| Discord | discord 或 discord:#channel | discord:#engineering |
| Slack | slack | 遞送到 Slack 主頻道 |
whatsapp | 遞送到 WhatsApp 主頁 | |
| Signal | signal | 遞送到 Signal |
| Matrix | matrix | 遞送到 Matrix 主聊天室 |
| Mattermost | mattermost | 遞送到 Mattermost 主頁 |
email | 透過 email 遞送 | |
| SMS | sms | 透過 SMS 遞送 |
| Home Assistant | homeassistant | 遞送到 HA 對話 |
| DingTalk | dingtalk | 遞送到釘釘 |
| Feishu | feishu | 遞送到飛書 |
| WeCom | wecom | 遞送到企業微信 |
| Weixin | weixin | 遞送到微信(WeChat) |
| BlueBubbles | bluebubbles | 透過 BlueBubbles 遞送到 iMessage |
| QQ Bot | qqbot | 透過官方 API v2 遞送到 QQ(騰訊) |
對於 Telegram 主題,使用格式 telegram:<chat_id>:<thread_id>(例如 telegram:-1001234567890:17585)。
回應包裝
預設情況下(cron.wrap_response: true),cron 遞送會包裝:
- 一個標題,標識 cron 任務名稱和任務
- 一個頁尾,說明代理程式無法在對話中看到已遞送的訊息
cron 回應中的 [SILENT] 前綴會完全抑制遞送 — 對於只需要寫入檔案或執行副作用的任務很有用。
Session 隔離
Cron 遞送不會被鏡像到閘道器 session 的對話歷史中。它們只存在於 cron 任務自己的 session 中。這防止了目標聊天室對話中的訊息交替違反。
遞迴保護
Cron 運行的 session 停用了 cronjob 工具組。這防止了:
- 排程任務建立新的 cron 任務
- 可能導致 token 用量爆炸的遞迴排程
- 從任務內部意外修改任務排程
鎖定
排程器使用跨進程的基於檔案的鎖定(Unix 上的 fcntl.flock,Windows 上的 msvcrt.locking),防止重疊的作業重複執行同一批到期任務 — 即使在閘道器的進程內排程器和獨立的 hermes cron / 手動 tick() 呼叫之間。如果無法取得鎖,tick() 會立即回傳 0。
CLI 介面
hermes cron CLI 提供直接的任務管理:
hermes cron list # 顯示所有任務
hermes cron create # 互動式任務建立(別名:add)
hermes cron edit <job_id> # 編輯任務設定
hermes cron pause <job_id> # 暫停運行中的任務
hermes cron resume <job_id> # 恢復暫停的任務
hermes cron run <job_id> # 觸發立即執行
hermes cron remove <job_id> # 刪除任務