Hermes Agentのcronに再開地点を残す|notepadで二重処理を避ける

Hermes Agentのcronに再開地点を残す|notepadで二重処理を避ける

夜間の cron がfailedで止まりました。すぐに再実行すると、同じ投稿を 2 回作るおそれがあります。失敗したのが投稿前とは限らないからです。外部 API では作成に成功し、その後の保存だけが失敗した可能性もあります。

私はfailedを再実行の合図にしません。まず実行履歴と外部側の結果を照合します。そのうえで、Hermes Agent の cron notepad へ処理済み ID を残します。

notepad は小さな再開地点に向いています。ただし、これだけですべての二重処理は防げません。外部 API への書き込みと notepad の更新は、1 つの取引として確定しないためです。

前回完了時から今回完了後まで

前回の「Hermes Agentのcronをモデル変更から守る」では、無人 job が意図しないモデルへ移るのを防ぎました。provider と model を固定し、drift guard の状態を確認しています。

今回は、job が起動した後の再開判断を扱います。

時点状態
前回完了時cronが使うproviderとmodelを固定できました
今回開始時失敗後に、どこまで処理できたか判断できません
今回完了後runs、外部結果、notepadを照合してから再実行できます

モデル固定が済んでいない場合は、前回の手順を先に実施します。今回の操作は、推論先の変更を修復するものではありません。

なぜ再実行より照合が先なのか

Hermes Agent は、cron の実行を attempt として台帳へ記録します。状態はclaimedrunningcompletedfailedunknownです。

failedは、外部サービスに何も作られなかったことを意味しません。たとえば、次の順番で動く job を考えます。

  1. 記事ファイルを作ります
  2. Git の branch を push します
  3. GitHub で PR を作ります
  4. 完了結果を返します

3 番まで成功し、4 番で通信が切れれば、attempt は失敗として見える可能性があります。ここで job 全体を再実行すると、別 branch や重複 PR を作りかねません。

unknownにも注意が必要です。公式仕様では、再起動後に所有 process が消えたと確認された attempt がunknownになります。Hermes Agent は、この attempt を自動再実行しません。

この動作は安全側です。処理済みか分からない仕事を、推測で繰り返さないためです。

用語表

用語この回での意味
jobschedule、prompt、delivery先などを持つcronの定義です
attemptjobが1回起動した記録です
execution ledgerattemptの状態を保存する実行台帳です
notepadjobごとにkeyとvalueを保存する小さな永続領域です
再開地点どの対象まで処理したかを示す値です
外部副作用投稿、PR作成、メール送信、課金APIなど、実行先に変更を残す操作です
冪等性同じ依頼を繰り返しても、結果が重複しない性質です

再開判断の流れ

今回の境界を図にすると、次の形です。

Hermes Agent cron

      ├── execution ledger
      │     └── attemptの状態を確認

      ├── output directory
      │     └── 前回の出力を確認

      └── job notepad
            └── 最後に確認できた対象IDを保存

外部サービス
      └── 投稿ID、PR URL、commitなどを照合

照合結果
      ├── 処理済み      再実行しない
      ├── 未処理        安全確認後に再実行
      └── 判断できない  停止して人が確認

.tick.lockは、scheduler tick 同士の重複を防ぎます。ただし、外部 API に同じ投稿を 2 回送る意味上の重複までは保証しません。scheduler の排他と、業務データの重複防止は分けて考えます。

この章を飛ばせる人

次の条件をすべて満たす job では、notepad の導入を飛ばせます。

  • 読み取り専用で、外部へ変更を残しません
  • 同じ入力で何度動いても結果が増えません
  • 出力先が内容 hash などで一意になります
  • 外部サービス側で idempotency key を使っています
  • 再実行前に処理済み状態を API から取得できます

1 つでも満たさない場合は、再開地点を持つ価値があります。ただし、決済や公開処理では notepad だけに頼りません。外部サービス側の一意制約や idempotency key を優先します。

検証用jobを選ぶ

本番の投稿 job や公開 job では試しません。すでに登録済みの、読み取り専用の検証 job を使います。

まず job 一覧から ID を確認します。

hermes cron list

表示された検証用 job の ID を、環境変数へ入れます。

JOB_ID='<your-test-job-id>'
printf '%s\n' "$JOB_ID"

<your-test-job-id>は説明用の記号です。実際の出力ではありません。自分の環境にある検証用 job ID へ置き換えます。

空文字のまま進めないように確認します。

test -n "$JOB_ID"

成功時は何も表示されず、終了コードは0です。

直近のattemptを確認する

次に、対象 job の実行履歴を 5 件に絞ります。

hermes cron runs "$JOB_ID" --limit 5

表示内容は job ごとに変わります。確認する項目は、attempt の時刻と状態です。公式仕様で使われる状態は次の 5 つです。

claimed
running
completed
failed
unknown

これは固定の実行例ではなく、状態名の一覧です。実際のコマンドは、その job に保存された行を表示します。

completedでも外部結果を確認します。agent の処理完了と、通知先への delivery 成功は同じ判定ではありません。

failedまたはunknownなら、まだhermes cron runを実行しません。次の順で確認します。

  1. cron の出力ファイルを確認します
  2. Git の branch、commit、PR を確認します
  3. 外部サービスの投稿 ID や作成時刻を確認します
  4. job が保存した再開地点を確認します

cron の出力は、公式仕様では次の場所に保存されます。

~/.hermes/cron/output/{job_id}/{timestamp}.md

{job_id}{timestamp}は説明用のプレースホルダです。パスを組み立てて削除せず、読み取りだけで確認します。

notepadの現在値を読む

job の notepad を一覧表示します。

hermes cron notepad "$JOB_ID" list

初回は key が存在しない場合があります。表示形式は Hermes Agent の版で変わる可能性があります。固定した文言を期待せず、終了コードと key の有無を確認します。

終了コードは直後に取得します。

printf 'exit=%s\n' "$?"

正常終了なら、期待する形は次のとおりです。

exit=0

検証用の再開地点を保存する

検証では、demo-001を処理済み ID の代わりに使います。実在する投稿 ID や秘密は保存しません。

hermes cron notepad "$JOB_ID" set last_item demo-001

setの表示文言には依存しません。続けてgetで読み返します。

hermes cron notepad "$JOB_ID" get last_item

読み返した値として確認する内容です。

demo-001

さらに一覧からも確認します。

hermes cron notepad "$JOB_ID" list

last_itemdemo-001の組が確認できれば、再開地点を保存できています。

外部副作用の前後を分ける

実際の job では、次の順番を守ります。

1. 対象IDを決めます
2. notepadの処理済みIDを読みます
3. 外部サービス側にも同じIDがないか照合します
4. 未処理なら外部操作を1回だけ実行します
5. 外部側の作成済みIDを読み返します
6. 成功を確認してからnotepadを更新します
7. 最終結果を返します

大切なのは 5 番です。HTTP 応答を受けただけで成功扱いにせず、作成先から読み返します。

それでも隙間は残ります。外部操作が成功した直後、notepad 更新前に process が落ちる場合です。次回は notepad だけを見ると未処理に見えます。

そのため、外部サービス側の照合を省けません。API が idempotency key を提供するなら、対象 ID から安定した key を作ります。データベースなら、一意制約を使います。GitHub PR なら、head branch や open PR を検索してから作成します。

notepad は判断材料です。単独で exactly-once を保証する仕組みではありません。

failedとunknownから安全に再開する

再実行の前に、3 つの結果を並べます。

確認先見るもの判断
execution ledgerfailedまたはunknownになった時刻失敗したattemptを特定します
外部サービス対象ID、作成時刻、URLすでに副作用が発生したか確認します
notepadlast_itemなどの再開地点jobが最後に確認できた対象を確認します

外部側に対象が存在すれば、job 全体を再実行しません。必要なら、未完了の後半だけを別の安全な手順で進めます。

外部側と notepad の両方に対象がなく、未処理だと確認できた場合だけ再実行を検討します。

hermes cron run "$JOB_ID"

このコマンドは、その場で job の完了を返すものではありません。公式仕様では、次の scheduler tick での実行を trigger します。gateway scheduler は通常 60 秒ごとに tick します。

実行を予約した後は、履歴を再確認します。

hermes cron runs "$JOB_ID" --limit 5

新しい attempt がcompletedになるまで、成功とは報告しません。外部副作用を伴う job では、外部側の結果も再度読み返します。

doctorとincidentsを補助に使う

job の定義や scheduler 全体に問題がないか、doctor で確認します。

hermes cron doctor

個別 attempt の代わりではありません。設定不備の候補を探す補助診断です。

incident の一覧も確認できます。

hermes cron incidents list

状態で絞る場合は、detectedalertedclosedを使います。

hermes cron incidents list --state detected

incident を ack する前に、原因と外部結果を確認します。ack は処理の巻き戻しではありません。既知の incident として扱う操作です。

検証用keyを削除する

検証が終わったら、作成した key だけを削除します。job や他の key は消しません。

hermes cron notepad "$JOB_ID" delete last_item

削除後に読み返します。

hermes cron notepad "$JOB_ID" list

last_itemがないことを確認します。戻り文言ではなく、削除対象の key が一覧から消えた事実を見ます。

よくあるエラー

症状原因対応
JOB_IDのjobが見つかりませんIDの転記ミスか、別profileを見ていますhermes cron listを同じprofileで実行します
notepadのkeyが空です初回実行か、key名が一致していませんlistで全keyを確認し、綴りを揃えます
failedなので再実行したら重複しました外部側の成功を照合していません投稿ID、branch、PR、commitを先に検索します
unknownが残ります再起動時に元processの完了を確定できませんでした自動再実行せず、出力と外部結果を照合します
run後すぐ履歴が増えません実行は次のscheduler tickで始まりますgatewayとcronの状態を確認し、次のtickを待ちます
同じkeyを複数用途で上書きしましたnotepadのkey設計が粗すぎますlast_prlast_postのように用途を分けます
notepad更新前にprocessが落ちました外部操作とnotepad更新が同一transactionではありません外部サービスを検索し、idempotency keyや一意制約を併用します
doctorが正常でもjobが失敗しますdoctorは外部APIの業務結果まで保証しませんruns、出力、外部側の3点を確認します

運用へ入れる前の確認

再開地点を置くだけでは不十分です。job の完了条件にも、読み返しを含めます。

  • 対象 ID は入力から安定して作ります
  • 外部操作の前に、同じ ID がないか検索します
  • 外部操作の後に、作成結果を読み返します
  • 読み返し成功後に notepad を更新します
  • failedunknownを自動再実行しません
  • 再実行前に branch、PR、投稿 ID、出力を確認します
  • 判断できない場合は止めます

cron を止めないことより、重複を増やさないことを優先します。再開地点は速度のためではなく、停止後の判断を具体化するために置きます。

出典

資料確認した内容
Hermes Agent Scheduled Taskscronの実行台帳、attempt状態、unknownの扱い、tick lock、出力先、runの動作を確認しました
Hermes Agent CLI Commands Referencehermes cronのCLI体系を確認しました
NousResearch/hermes-agentHermes Agentの公式ソースと更新履歴の参照先です

CLI の usage は、2026 年 9 月 7 日にhermes cron --helphermes cron notepad --helphermes cron runs --helphermes cron incidents --helphermes cron doctor --helpで確認しました。版が変わった場合は、手元の help を正として引数を見直します。

この記事をシェア