
以前の記事「CLIの出力設計を間違えると、AIエージェントは何も読めない 」で紹介した agent-exec に、ずっと欲しかった機能を入れました。Rust 製ジョブランナーに追加したジョブ完了時のイベント通知です。
これまではジョブの完了を知るには agent-exec wait か agent-exec status をポーリングするしかなかったんですが、実際にオーケストレーションパイプラインを組むとポーリングが辛い。間隔が短すぎるとリソースの無駄、長すぎると反応が遅れる。結局「完了したら教えてくれ」が一番素直だよなと思って、Job Finished Events を実装しました。
Job Finished Events とは
ジョブが終了状態(正常終了・異常終了・シグナルによる停止)になったとき、外部に完了イベントを通知する仕組みです。通知先は2つ選べます。
- File Sink: NDJSONファイルにイベントを追記
- Command Sink: 任意のコマンドを実行し、stdinにイベントJSONを渡す
両方同時に指定することもできます。
File Sink: NDJSONファイルへの追記
--notify-file を指定すると、ジョブ完了時にイベントJSONを1行としてファイルに追記します。
ファイルが存在しなければ新規作成、存在すれば末尾に追記されます。NDJSON(1行1JSON)形式なので、複数ジョブのイベントを1ファイルにまとめて tail -f で監視したり、後からバッチ処理したりできます。
| |
実行後、/tmp/events.ndjson には以下のようなJSONが1行追記されます。
| |
Command Sink: コマンド実行による通知
--notify-command を使うと、ジョブ完了時に任意のコマンドを実行できます。イベントJSONはそのコマンドのstdinに渡されます。
v0.1.3からコマンドはシェル文字列で指定します。設定されたシェルラッパー経由で実行されるため、パイプやリダイレクトもそのまま書けます。
| |
この例ではイベントJSONを /tmp/event.json に保存していますが、実際にはSlack通知スクリプトやWebhookの呼び出しなど、好きなコマンドを指定できます。
Command Sinkで設定される環境変数
コマンド実行時、以下の環境変数が自動的に設定されます。
| 環境変数 | 内容 |
|---|---|
AGENT_EXEC_EVENT_PATH | 永続化された completion_event.json のパス |
AGENT_EXEC_JOB_ID | 完了したジョブのID |
AGENT_EXEC_EVENT_TYPE | イベントタイプ(現在は job.finished 固定) |
AGENT_EXEC_EVENT_PATH が地味に便利で、通知コマンドの中でイベントJSONを再読み込みしたいときにstdinを消費せずアクセスできます。
シェルラッパーの設定
v0.1.3では、--notify-command や通常のコマンド実行に使われるシェルラッパーを設定できるようになりました。優先順位は以下の通りです。
--shell-wrapper "bash -lc"CLIフラグ(最優先)--config /path/to/config.tomlで指定した設定ファイル- デフォルトXDGパス:
~/.config/agent-exec/config.toml - ビルトインのプラットフォームデフォルト
設定ファイルはTOML形式で書けます。
| |
-lc を指定するとログインシェルとして実行されるので、~/.bash_profile で設定したPATHやエイリアスが使えます。CI環境とローカル環境でシェルの挙動を揃えたいときに便利です。
イベントJSONのスキーマ
job.finished イベントのペイロードは以下のフィールドを含みます。
| フィールド | 型 | 説明 |
|---|---|---|
schema_version | string | スキーマバージョン(現在 "0.1") |
event_type | string | "job.finished" |
job_id | string | ジョブID |
state | string | "exited"、"killed"、または "failed" |
command | string[] | 実行コマンド |
cwd | string | 作業ディレクトリ |
started_at | string | 開始時刻(ISO 8601) |
finished_at | string | 終了時刻(ISO 8601) |
duration_ms | number | 実行時間(ミリ秒) |
exit_code | number | absent | 終了コード(kill時はフィールド省略の場合あり) |
signal | string | null | 停止シグナル(正常終了時はnull) |
stdout_log_path | string | stdoutログのパス |
stderr_log_path | string | stderrログのパス |
state が "killed" の場合、exit_code フィールドは省略されることがあり、代わりに signal(例: "SIGTERM")が入ります。"failed" はジョブの起動自体に失敗した場合です。
また、ジョブディレクトリには completion_event.json が自動保存されます。通知の送信結果(各Sinkの成否)も含まれるので、デバッグ時にも役立ちます。
実践例: イベント駆動のパイプライン
「テスト実行 → 結果を通知」という簡単なパイプラインを組んでみます。--notify-command にシェルコマンドを直接書けるので、スクリプトファイルを別途用意する必要はありません。
| |
--notify-file と --notify-command を同時に指定しているので、NDJSONファイルに監査ログとして残しつつ、リアルタイムの通知も飛ばせます。
もう少し読みやすくしたい場合は、AGENT_EXEC_EVENT_PATH を使ったシンプルなパターンもあります。
| |
OpenClawとの連携
agent-execのイベント通知は、OpenClaw
のようなエージェントプラットフォームとの相性が特に良いです。ジョブが終わったら結果をセッションに返す、チャットに通知する、といったパターンが --notify-command ひとつで組めます。
チャットへの通知
openclaw message send を使えば、ジョブ完了をTelegramなどのチャットに直接通知できます。シェル文字列形式なので、パイプでつないでワンライナーで書けます。
| |
AGENT_EXEC_EVENT_PATH を使えば、stdinを消費せずにイベントJSONを参照できるので、より複雑な通知にも対応できます。
| |
起動元セッションへの結果返送
もう一つ便利なパターンが、ジョブを起動したOpenClawセッションに結果を返すことです。起動元のセッションがログを確認して、リトライするか次のタスクに進むかを判断できます。
| |
セッション側ではイベントペイロードを読んで stdout_log_path や stderr_log_path を確認し、次のアクションを決められます。重い処理をagent-execに任せて、結果だけをエージェントセッションで受け取る——このパターンは、エージェントオーケストレーションの基本形になると思っています。
ポーリングとの使い分け
イベント通知が入ったからといって wait や status が不要になるわけではありません。使い分けの目安はこんな感じです。
| ユースケース | 推奨 |
|---|---|
| バッチパイプラインで次のジョブを起動 | --notify-command |
| 複数ジョブのイベントを集約・分析 | --notify-file |
| インタラクティブに結果を待つ | agent-exec wait |
| ジョブ状態の随時確認 | agent-exec status |
パイプラインやオーケストレーションでは --notify-command が自然で、人間が手元で確認するなら wait が手軽です。
まとめ
agent-exec の Job Finished Events は「ポーリングするくらいなら教えてくれ」をそのまま実装した機能です。File SinkとCommand Sinkの2つの通知先を選べて、イベントJSONにはジョブの実行時間やログパスも含まれるので、通知先で必要な情報は大体揃います。
自分のユースケースでは、エージェントのオーケストレーションで「ジョブAが終わったらジョブBを起動」というチェーンを組むのに使っています。ポーリングの間隔調整から解放されるだけで、パイプラインの設計がだいぶ楽になりました。