Skip to content

Cron 定時

源码版本v2026.7.20

職務

agent に「時刻が来たら自動で走る」能力を持たせる:定時レポート、バックアップ、ブリーフィングなど、人が介在せずに動くジョブ。cron/ サブシステムはスケジューラ、ジョブストレージ、実行記録、ライフサイクルガード、ブループリント/提案カタログ、独立した toolsets 隔離を提供する(定時ジョブは一部ツール (tool) を無効化でき、無人時の危険操作を防ぐ)。ゲートウェイ (gateway) が 60s 心拍でスケジューラを駆動する。

設計動機

無人運転がこのサブシステムの核心問題だ。agent が午前三時に自分で起動したら、誰が監視するのか? Hermes の答えは「スケジュールチェーン上で段階的に権限を下げる」ことだ。toolsets はデフォルトで対話系と cron を再起できる cronjob を切り落とす。lifecycle_guard はスクリプト内の systemctl restart hermes-gateway のようなコマンドをスケジュール前に拦む。executions テーブルは毎回の実行に照会可能な痕跡を残し、クラッシュ時は unknown とマークし、成功を装わない。「agent が自分で走る」ことを監査可能にし、ロールバック可能にする。

主要ファイル

データフロー

  1. ゲートウェイが _start_cron_ticker:22335 を起動し、60s ごとにスケジューラをトリガーする。
  2. スケジューラはジョブストレージ(cron/jobs.py:106) を読み、cron 式で到期ジョブを選ぶ。
  3. 各到期ジョブ:
  4. 実行中のクラッシュ → 次回 recover_interrupted_executions(cron/executions.py:156) が回復マークを付ける。
  5. 結果はゲートウェイ経由で配信(または指定プラットフォームにミラー)。

スケジューラの toolset カリングは三項をハードコードし、ユーザーの config.yamlagent.disabled_toolsets を上乗せする。

python
def _resolve_cron_disabled_toolsets(cfg: dict) -> list[str]:
    """Toolsets a cron-spawned agent must never receive."""
    disabled = ["cronjob", "messaging", "clarify"]
    agent_cfg = (cfg or {}).get("agent") or {}
    user_disabled = agent_cfg.get("disabled_toolsets") or []
    for name in user_disabled:
        name = str(name).strip()
        if name and name not in disabled:
            disabled.append(name)
    return disabled

cronjob を無効にするのは、cron 自身に cronを再作らせないためだ(誤発でジョブが連鎖的に積もるのを防ぐ)。messaging/clarify は対話型で、無人運転では人を待って止まるだけだ。スクリプト型ジョブは _run_job_script に流れ、パスは HERMES_HOME/scripts/ に閉じ込めて traversal を防ぐ。

実行状態機械は executions.py にあり、状態は claimed → running → completed/failed/unknown だけだ。終状態は一回しか書けない(finish_execution の UPDATE に WHERE status IN ('claimed','running') がつき、重複書き込みは rowcount=0 で None を返す)。クラッシュ回復は「プロセスが既に存在しない」実行だけを unknown にマークし、成功を装わず、自動再実行もしない——子プロセスは副作用を書いたかもしれないし、書いていないかもしれない。failed と見なして「再実行すれば終わり」と思わせるより、オペレータに調べさせる。lifecycle_guardsystemctl restart hermes-gatewaypkill hermes gateway を正規表現に組み込み、作成時と実行前の二回スキャンする。prompt と script を結合してスキャンし、「prompt で一言、スクリプトで一刺し」の分割回避を塞ぐ。

境界と失敗

  • 秒単位のドリフトと誤傷:60s 心拍なので、トリガ時点は最大一分ずれる。mark_running_jobs_interrupted はどの PID が殺されたかを区別せず、殺された瞬間に走っていたジョブをすべて中断する。終わりかけのジョブを誤傷するかもしれない。
  • unknown 状態の副作用:クラッシュ回復が unknown にマークした後は再実行しないが、スクリプトは既にメッセージを送ったりファイルを書いたりしているかもしれない。下流は「未完了」を仮定してはいけない。
  • リース心拍のスタル:_run_job_script_with_claim_heartbeat は独立スレッドでリースを更新する。心拍スレッド自身が(GIL の長期保持などで)詰まると、別のスケジューラプロセスが同一の one-shot ジョブをもう一度派遣し、重複副作用を生む。
  • prompt injection の盲点:実行時に assembled prompt(skill 内容を含む)をスキャンする。悪意ある skill はスケジュールされるその瞬間まで検出されず、ジョブ作成時には見えない。

まとめ

Cron サブシステムの重点は「無人運転の安全性」:toolsets 隔離、lifecycle_guard による誤操作防止、execution 状態機械によるクラッシュ回復。スケジュール自体はゲートウェイ心拍で駆動し、外部 cron デーモンを必要としない。

非公式コミュニティ学習サイト。MIT ライセンスの NousResearch/hermes-agent ソースに基づく。