
AIエージェント に長時間コマンドを実行させたい。でも、既存のCLIツールは「人間が端末で見る」前提で設計されているため、エージェントが出力をパースできません。
原因は単純です。多くのCLIが「結果」と「ログ」を標準出力(stdout)に混ぜて出力するからです。人間なら目で見分けられますが、エージェントには無理です。
agent-exec は、この問題を「stdout = JSON only、stderr = ログ」という厳格な分離で解決したRust 製ジョブランナーです。本記事では、なぜこの設計が重要なのか、どう実装されているのかを解説します。
問題: stdoutに混ざるログがパースを壊す
典型的なCLIツールの出力例を見てみましょう。
| |
人間なら「最後の行がJSON結果だな」と判断できます。しかし、エージェントにとっては以下の問題があります:
- どの行が結果か判別できない: 進捗ログとJSON結果が混在
- JSONパーサーがエラーを吐く: 最初の行から読むと
Connecting to server...でパース失敗 - 最後の行を読む保証がない: 出力が途中で切れたら結果を取得できない
この問題は、CLIが「人間が端末で見る」前提で設計されているために起きます。
解決策: stdout/stderr分離の原則
agent-exec は、以下の「Output Contract」(出力契約)を定義しています:
- stdout: JSON only — すべてのコマンドが正確に1つのJSONオブジェクトを出力
- stderr: 診断ログ(
RUST_LOGまたは-v/-vvフラグで制御)
この分離により、エージェントは以下のように確実にパースできます:
| |
tailコマンドの出力例:
| |
エージェントは、stdoutから読んだ内容をそのままJSONパーサーに渡せます。ログのフィルタリングは不要です。
設計原則: Agentic CLI Designとの関係
この設計は、私が以前提唱した「Agentic CLI Design: CLIをAIエージェント向けプロトコルとして設計する7つの原則 」の原則1: Machine-readable(機械可読が主)を徹底したものです。
原則1の要件:
--json/--output json|yaml|textオプションがある- 標準出力(stdout)= 結果 / 標準エラー出力(stderr)= ログ・進捗 を厳守(混ぜない)
- エラーも構造化(可能ならJSONで)
- スキーマは安定(破壊的変更は
schemaVersion等で管理)
agent-execは、この原則を「オプション」ではなく「デフォルト」にしました。すべてのコマンド(run、status、tail、wait、kill、list)が、常にJSON出力を返します。
実装: Rustでどう実現しているか
agent-execはRustで実装されています。stdout/stderr分離を実現するための主要な実装パターンを見てみましょう。
パターン1: stdoutへのJSON出力
すべてのコマンドは、最後に1つのJSONオブジェクトをprintln!でstdoutに出力します。
| |
重要なのは、stdoutへの出力は最後の1回だけという点です。途中の進捗やデバッグ情報は、すべてstderrに送ります。
パターン2: stderrへのログ出力
ログはeprintln!またはlogクレートを使ってstderrに出力します。
| |
ログレベルはRUST_LOG環境変数または-v/-vvフラグで制御できます:
| |
パターン3: エラーもJSON化
エラーが発生した場合も、stdoutにJSON形式で出力します。
| |
これにより、エージェントは成功・失敗を問わず、同じパーサーで結果を処理できます。
実例: 長時間ジョブの監視
agent-execの真価は、長時間実行されるコマンドをエージェントが監視する場面で発揮されます。
以下は、30秒かかるコマンドをバックグラウンドで実行し、状態を監視する例です:
| |
statusコマンドの出力例:
| |
エージェントは、この出力から"state": "running"を読み取り、「まだ実行中だから待つ」と判断できます。
完了後は以下のように変わります:
| |
"state": "exited"と"exit_code": 0から、「正常終了した」と判断できます。
タイムアウトとシグナル制御
agent-execは、タイムアウト時の挙動も構造化されています。
| |
この設計により、エージェントは以下のように段階的な終了を実現できます:
--timeout 5000: 5秒後にSIGTERMを送信(プロセスに終了を要求)--kill-after 2000: SIGTERM送信後、さらに2秒待ってもプロセスが終了しない場合、SIGKILLを送信(強制終了)
この2段階の設計は、「Agentic CLI Design 」の原則3: Safe by default(安全側デフォルト)に基づいています。
他のツールとの比較
agent-execと同様の問題を解決するツールとして、以下があります:
| ツール | 特徴 | stdout/stderr分離 |
|---|---|---|
| agent-exec | JSON専用ジョブランナー | ✅ 完全分離 |
| kubectl | Kubernetes CLI | ✅ -o jsonオプション |
| AWS CLI | AWS操作CLI | ✅ --output jsonオプション |
| 一般的なCLI | 人間向け出力 | ❌ 混在 |
agent-execの特徴は、「オプション」ではなく「デフォルト」でJSON出力を提供する点です。エージェントは、フラグを指定せずに確実にパースできます。
まとめ: CLIはプロトコルである
agent-execが示したのは、「CLIは人間向けUIではなく、エージェント向けプロトコルである」という設計思想です。
重要なポイント:
- stdout = 結果、stderr = ログを厳守する
- すべての出力をJSON化し、エージェントがパースできるようにする
- エラーも構造化し、成功・失敗を同じパーサーで処理できるようにする
- タイムアウト・シグナル制御を提供し、エージェントが長時間ジョブを安全に監視できるようにする
この設計は、slack-rs (詳細は「Agentic CLI Designを実装する: slack-rsで学ぶ7原則の具体化 」参照)やxcom-rs (詳細は「X.com API従量課金時代のCLI設計: xcom-rsが実装したコストガードレールと冪等性 」参照)と同じ「Agentic CLI Design」の系譜に属します。
あなたのCLIツールは、エージェントが読めますか?