エージェントは従来の CLI との併用でより良く機能する(12 分読了)
TLDR AI が報告した研究により、AI エージェントは従来のコマンドラインインターフェース(CLI)と組み合わせることで、その性能と信頼性が大幅に向上することが示された。
キーポイント
CLI との統合による性能向上
複雑なタスクを実行する際、AI エージェントが従来の CLI ツールを直接利用することで、推論の精度と実行成功率が劇的に改善されることが実証された。
信頼性と予測可能性の確保
独自の抽象化レイヤーに依存するのではなく、既存で検証済みの CLI コマンドを使用することで、エージェントの動作における不確実性やハルシネーションが減少する。
開発者ワークフローへの適合
このアプローチは、開発者が既に慣れ親しんでいるツールチェーンをそのまま活用できるため、学習コストを抑えつつ即座に実装可能な解決策となる。
影響分析・編集コメントを表示
影響分析
この記事は、AI エージェントの実用化において「新しい抽象化レイヤーの構築」から「既存インフラの有効活用」へのパラダイムシフトを示唆しています。開発者が信頼できる CLI ツールをエージェントに委譲することで、複雑なシステム制御におけるリスクを低減し、実環境での導入障壁を下げる重要な示唆を含んでいます。
編集コメント
既存のツールを最大限に活用するこのアプローチは、過剰な抽象化によるリスクを回避し、実務レベルでの AI エージェント導入を加速させる鍵となるでしょう。
2026 年 7 月 7 日
0 件の反応
image
プリンシパル・デベロッパー・アドボケート
今、あるアドバイスが広まっています。それは、エージェントがツールをより効果的に利用できるようにするために、CLI(コマンドラインインターフェース)の引数(args)を置き換え、単一の --json ペイロードを使用するべきだというものです。その考え方の背景には、エージェントはすでに構造化された形式で思考しており、ネストされたデータ構造が JSON にきれいにマッピングできるという点があります。一方、フラットな引数は、複数の値のグループを区別するために --service-name を繰り返し指定するなど、不自然な規約を強いることになり、本質的に曖昧さを生みます。さらに、エージェントはすべての値の型を正しく理解する必要があるという問題もあります。
これは妥当な仮説であり、私たちはこれが測定によって裏付けられるかどうかを知りたかったのです。私たちが収集したデータは、興味深い結果を示しました。
何を検証したか
私たちは、マルチサービスデプロイメントを作成する合成 CLI「podctl」を構築しました。シナリオは意図的に複雑に設定されており、独立した設定を持つ 2 つのサービス、3 レベルのネスト(services[].resources.cpu.request)、配列、混合されたデータ型、およびサービス間の相互参照が含まれています。デプロイメント仕様全体で 30 以上の異なる値が存在します。以下が、引数による呼び出しの例です:
podctl create \
--name "api-gateway-prod" \
--env production \
--region us-east-1 \
--replicas 3 \
--service-name auth \
--service-image ghcr.io/acme/auth:v2.4.1 \
--service-port 8080 \
--service-protocol grpc \
--service-cpu-request 250m \
--service-cpu-limit 500m \
--service-mem-request 128Mi \
--service-mem-limit 256Mi \
--service-health-path /healthz \
--service-health-interval 10s \
--service-health-timeout 3s \
--service-health-retries 3 \
--service-env DB_HOST=db.internal \
--service-env DB_PORT=5432 \
--service-name gateway \
--service-image ghcr.io/acme/gateway:v1.8.0 \
--service-port 443 \
--service-protocol https \
--service-cpu-request 500m \
--service-cpu-limit 1000m \
--service-mem-request 256Mi \
--service-mem-limit 512Mi \
--service-health-path /ready \
--service-health-interval 15s \
--service-health-timeout 5s \
--service-health-retries 5 \
--service-env UPSTREAM=http://auth:8080 \
--service-depends-on auth \
--scaling-min 2 \
--scaling-max 10 \
--scaling-metric cpu \
--scaling-target 70 \
--rollout-strategy canary \
--rollout-canary-percent 10 \
--rollout-canary-pause 300
--service-name フラグが 2 回出現して新しいサービスブロックを開始している点に注目してください。エージェントは、2 つ目の --service-name の後のフラグが auth ではなく gateway に属するものであることを理解する必要があります。そして、同じデプロイを JSON ペイロード形式で記述すると以下のようになります:
podctl create --json '{
"name": "api-gateway-prod",
"environment": "production",
"region": "us-east-1",
"replicas": 3,
"services": [
{
"name": "auth",
"image": "ghcr.io/acme/auth:v2.4.1",
"port": 8080,
"protocol": "grpc",
"resources": {
"cpu": {"request": "250m", "limit": "500m"},
"memory": {"request": "128Mi", "limit": "256Mi"}
},
"healthCheck": {
"path": "/healthz",
"interval": "10s",
"timeout": "3s",
"retries": 3
},
"env": {"DB_HOST": "db.internal", "DB_PORT": "5432"}
},
{
"name": "gateway",
"image": "ghcr.io/acme/gateway:v1.8.0",
"port": 443,
"protocol": "https",
"resources": {
"cpu": {"request": "500m", "limit": "1000m"},
"memory": {"request": "256Mi", "limit": "512Mi"}
},
"healthCheck": {
"path": "/ready",
"interval": "15s",
"timeout": "5s",
"retries": 5
},
"env": {"UPSTREAM": "http://auth:8080"},
"dependsOn": ["auth"]
}
],
"scaling": {
"min": 2,
"max": 10,
"metric": "cpu",
"targetPercent": 70
},
"rollout": {
"strategy": "canary",
"canaryPercent": 10,
"canaryPauseSec": 300
}
}'
その魅力は明らかです。JSON バージョンでは階層構造が明示され、各サービスが独立したオブジェクトとなり、リソースが自然にネストされ、どのフィールドがどこに属するかという曖昧さがなくなります。理論上、これはエージェントにとって正しく理解しやすいはずです。
テスト対象には 2 つの別々の CLI(コマンドラインインターフェース)を使用しました。一方は個別の引数(args)のみを受け付け、他方は --json ペイロードのみを受け付けます。両者は同じ検証バックエンドを共有し、同一の正規化された構造に統一されています。テスト時には、アクティブなバリアントが podctl という名前にリネームされ、エージェントが一貫したバイナリ名を見るようにしています。CLI はエージェントにとって未知のものなので、入力モデルはゼロから発見する必要があります。引数の場合、これは CLI のヘルプを呼び出すことを意味します。JSON の場合も同様にヘルプの呼び出しが必要ですが、さらに JSON スキーマを返す追加コマンドを呼び出す必要があります。
単一の CLI が両方のモードをサポートするとエージェントが好みのアプローチを選択できて結果にバイアスがかかる可能性があるため、2 つの別々のバイナリを使用しました。
各モデルについて、入力モードごとに 5 回ずつ、同じプロンプトと評価基準を用いて実行し、GitHub Copilot Chat をハーン(テスト枠組み)として使用しました。選択したモデルは、コーディングに一般的に使用されている Claude Haiku 4.5、Claude Sonnet 4.6、Claude Sonnet 5、GPT-5.3-Codex、MAI-Code-1-Flash です。
正しさ:引数モードが完全制覇
すべての引数プロファイルにおいて、どのモデルであっても 5 回の試行すべてで完璧な正しさを達成しました。テスト中最小規模のモデルである Haiku 4.5 も、Sonnet 5 と同様に正確なデプロイを確実に生成しました。
JSON の場合、状況は異なっていました。3 つの最強モデルは、引数との一致において 5/5 の正答率を達成しました。一方、2 つの小型モデルはそうではありませんでした。Haiku 4.5 は JSON モードで 5 回のデプロイメントのうち 2 回のみが正しく完了しました。MAI-Code-1-Flash は 3 回/5 回でした。
Model
args
JSON
Claude Sonnet 5
5/5
5/5
Claude Sonnet 4.6
5/5
5/5
GPT-5.3-Codex
5/5
5/5
MAI-Code-1-Flash
5/5
3/5
Claude Haiku 4.5
5/5
2/5
引数は入力空間を十分に制約するため、小型モデルであっても一貫して正しい出力を生成できます。JSON モードでは、モデル自身が構造を管理する必要があり、これはすべてのモデルが負担できる能力コストではありません。
コスト
開発者は試行回数ではなくトークンに対して課金されます。そのため、入力トークン、キャッシュされたトークン、出力トークンに消費量を分解し、GitHub Models の価格体系に基づいてタスクあたりのコストを計算しました。
Model
args
JSON
Ratio
GPT-5.3-Codex
$0.05
$0.54
11x
MAI-Code-1-Flash
$0.01
$0.08
10x
Claude Sonnet 4.6
$0.05
$0.47
9x
Claude Haiku 4.5
$0.03
$0.23
8x
Claude Sonnet 5
$0.08
$0.32
4x
JSON モードでは、すべてのモデルでタスクあたりのコストが 4 倍から 11 倍に増加しました。これらは各呼び出しごとの数値であり、チームがこのようなタスクを多数回実行する場合、すぐに累積して大きな負担となります。
生のトークンギャップはさらに大きかった(4 倍から 14 倍)ものの、キャッシュがその打撃を和らげます。JSON モードではエージェントが何度もリトライしますが、各リトライの入力は下部のレートで主にキャッシュされます。しかし、キャッシュで相殺できないのが出力トークンです。各リトライはエラーについて推論し、新たな回避策を生成することを意味し、その回避策は創意工夫に富みます:JSON を変数を通じてパイプライン化したり、一時ファイルに書き込んだり、異なるエスケープ戦略を試したりします。これらすべてが出力であり、出力トークンは最も高価なカテゴリです。JSON リトライは引数パスよりも 7 倍から 14 倍多くの出力トークンを生成しました。ここがコストの源泉です。
それらの追加トークンはどこへ行くのでしょうか?リトライです。モデルが JSON ペイロードを構築し、CLI がそれを拒否した場合、エージェントはエラーを読み取り、修正を試みて再試行します。JSON は失敗する道筋が多い(構文エラーと誤ったネストが明白な例ですが)ため、次のセクションで見るように、シェルエスケープがさらに失敗の層を追加します。より多くの失敗モードはより多くのリトライを意味し、より多くのリトライはより多くのお金を意味します。
シェルエスケープ税
しかし、リトライだけが物語のすべてではありません。「JSON 用に書き換えよ」という助言が考慮していない要因があります:シェルエスケープです。
エージェントがコマンドライン上で JSON ペイロードを渡す場合、シェルに対して引用符のエスケープ処理を行う必要があります。異なるシェルではネストされた引用文字列に対するエスケープルールが異なり、どれも単純ではありません。モデルは通常、有効な JSON を生成しますが、シェルのエスケープ規則によって破綻することがあります:シングルクォートで囲まれたブロック内のダブルクォート付き文字列や、意図とは異なる解釈をシェルが行うネストされたエスケープなどです。
主要な実験は Windows 環境で行われ、ここではエージェントがデフォルトで PowerShell を使用します。Haiku 4.5 の JSON 失敗事例は、このエスケープ問題を明確に示しています。5 回の試行のうち 3 回は成功する呼び出しに至りませんでした。エージェントは正しい JSON コンテンツを生成しましたが、それをシェルを通過させることができませんでした。ベース 64 エンコーディング、ファイルパイプ、ヒアストリング、cmd.exe のパススルーを試みましたが、各試行でトークンを消費し続け、それでも失敗しました。
同じ実験を Sonnet 4.6 で macOS 環境(エージェントがデフォルトで Bash を使用)でも実行し、シェルの選択が結果に影響を与えるかどうかを確認しました。PowerShell では引数と JSON のコスト差が 9 倍でしたが、Bash では 1.5 倍に縮小しました。同じモデル、同じペイロード、同じ正しさです。違いは完全に、各シェルが引用された JSON 文字列をどのように処理するかによるものです。一方、引数は両方のシェルで安定していました:PowerShell で$0.05、Bash で$0.07。これがポイントです:JSON は環境によって大きく異なるシェルのクォーティング動作に依存しますが、引数はそうではありません。
なぜ制約がエージェントを助けるのか
より深い洞察は、制約がモデルにどのような効果をもたらすかについてです。引数(args)は、JSON にはない方法でエージェントの出力を拘束します。
引数を使用する場合、--help テキストが有効な入力の正確なセットを定義します。各オプションには名前と型があり、時には有効値の列挙(enum)も含まれます。モデルはタスクの説明から特定の名前付きオプションとその値へとマッピングを行います。
一方、--json を使用する場合、モデルはスキーマを取得するか、あるいは --help からそれを推論して、それを満たす自由形式のオブジェクトを構築します。構文は有効でなければならず、ネスト構造も正しくなければなりません。フィールド名と型も一致している必要があります。さらに、全体がシェルのエスケープ処理に耐えうるものでなければなりません。これら一つひとつが失敗モードであり、引数(args)は設計上それらを排除します。
強力なモデルは最終的に JSON を正しく扱えるようになりますが、「最終的には」という言葉には多くの意味が含まれています。それでもなお、シェルエスケープの失敗に直面し、そこから回復するのです。その回復にかかるコストは、引数(args)パスの場合と比較して 4 倍から 11 倍も高く、正答率が同じであってもです。小規模なモデルも同様のエスケープ失敗に遭遇しますが、回復できず、そこで正答率が低下します。小規模なモデルにとって、引数(args)が提供する制約こそが、一貫した成功と頻発する失敗を分ける要因となります。実は、有効入力空間を狭めることが、モデルの能力におけるギャップを補償することが判明しました。モデルは JSON のネスト構造を正しくする必要はありません。なぜなら、正しくする必要がある JSON ネスト構造が存在しないからです。
これがあなたの CLI にとって何を意味するか
もしエージェントが使用する CLI を構築しているなら、データは「--json で書き直す」という方向とは異なる示唆を与えています。引数(args)を維持してください。これらはモデルの能力スペクトラム全体で機能し、環境依存の失敗モードを持ちません。また、トークン数とコストも少なくて済みます。
しかし、入力データが本当に複雑な場合はどうでしょうか?テストシナリオではネストされたサービスにわたる 30 以上の値がありましたが、引数はすべてのモデルで完璧に処理しました。プログラム利用やバッチ操作のために --json オプションを提供したいのであれば構いません。ただし、JSON に置き換えるために引数を削除したり、JSON がエージェントの成果を向上させると期待したりしてはいけません。今回の実験では、JSON は正答率を一度も向上させることはなく、常にコストを増加させました。
JSON の方が *本来* 良く機能するはずだという直感は紙の上では理にかなっています。しかし、「そうあるべきだ」という考えは、実際の OS で動作する実際のシェルで実行される実際のモデルとの接触には耐えられません。注目すべきは、引数はシェル全体で制約され予測可能である一方、JSON は表現力はあるものの脆いということです。エージェント駆動型の CLI 利用においては、制約された形式の方が勝ります。
書き直す前に測定せよ
これは、AX シリーズの残りの部分に通底する教訓です。妥当なアドバイスと計測された結果は異なるものであり、両者が食い違う場合、測定結果が勝ります。誰かが CLI の入力モデルを再構築することを推奨する場合でも、まずは実験を行ってください。シナリオを選び、評価基準を定義し、対象となるオーディエンスに適したハーンセス(テスト枠組み)、モデル、OS を選択して、両方のアプローチで数回実行してください。必要な箇所を書き換えることなく、1 日あれば答えが得られるはずです。
Author

Principal Developer Advocate(シニア開発者広報)
Waldek は Microsoft の Principal Developer Advocate であり、AI コーディングエージェントに注力しています。彼は AI コーディングエージェントの研究を行い、Microsoft の製品やサービスにおけるエージェント体験の評価と改善に取り組んでいます。
原文を表示
July 7th, 2026
0 reactions

Principal Developer Advocate
There’s advice making the rounds: replace your CLI args with a single --json payload so agents can use your tool more effectively. The thinking being, that agents already think in structured formats, and nested data maps cleanly to JSON. Flat args on the other hand, force awkward conventions like repeating --service-name to delimit multi-value groups, which is inherently ambiguous. Not to mention, that the agent needs to get the types of all values right.
It’s a reasonable hypothesis, and we wanted to know if it holds up under measurement. The data we collected, showed something interesting.
What we tested
We built a synthetic CLI called podctl that creates multi-service deployments. The scenario was deliberately complex: two services with independent configuration, three levels of nesting (services[].resources.cpu.request), arrays, mixed types, and cross-references between services. Over 30 distinct values across the deployment spec. Here’s what the args invocation looks like:
podctl create \
--name "api-gateway-prod" \
--env production \
--region us-east-1 \
--replicas 3 \
--service-name auth \
--service-image ghcr.io/acme/auth:v2.4.1 \
--service-port 8080 \
--service-protocol grpc \
--service-cpu-request 250m \
--service-cpu-limit 500m \
--service-mem-request 128Mi \
--service-mem-limit 256Mi \
--service-health-path /healthz \
--service-health-interval 10s \
--service-health-timeout 3s \
--service-health-retries 3 \
--service-env DB_HOST=db.internal \
--service-env DB_PORT=5432 \
--service-name gateway \
--service-image ghcr.io/acme/gateway:v1.8.0 \
--service-port 443 \
--service-protocol https \
--service-cpu-request 500m \
--service-cpu-limit 1000m \
--service-mem-request 256Mi \
--service-mem-limit 512Mi \
--service-health-path /ready \
--service-health-interval 15s \
--service-health-timeout 5s \
--service-health-retries 5 \
--service-env UPSTREAM=http://auth:8080 \
--service-depends-on auth \
--scaling-min 2 \
--scaling-max 10 \
--scaling-metric cpu \
--scaling-target 70 \
--rollout-strategy canary \
--rollout-canary-percent 10 \
--rollout-canary-pause 300
Notice how --service-name appears twice to start a new service block. The agent has to figure out that flags after the second --service-name belong to the gateway, not to auth. And here’s the same deployment as a JSON payload:
podctl create --json '{
"name": "api-gateway-prod",
"environment": "production",
"region": "us-east-1",
"replicas": 3,
"services": [
{
"name": "auth",
"image": "ghcr.io/acme/auth:v2.4.1",
"port": 8080,
"protocol": "grpc",
"resources": {
"cpu": {"request": "250m", "limit": "500m"},
"memory": {"request": "128Mi", "limit": "256Mi"}
},
"healthCheck": {
"path": "/healthz",
"interval": "10s",
"timeout": "3s",
"retries": 3
},
"env": {"DB_HOST": "db.internal", "DB_PORT": "5432"}
},
{
"name": "gateway",
"image": "ghcr.io/acme/gateway:v1.8.0",
"port": 443,
"protocol": "https",
"resources": {
"cpu": {"request": "500m", "limit": "1000m"},
"memory": {"request": "256Mi", "limit": "512Mi"}
},
"healthCheck": {
"path": "/ready",
"interval": "15s",
"timeout": "5s",
"retries": 5
},
"env": {"UPSTREAM": "http://auth:8080"},
"dependsOn": ["auth"]
}
],
"scaling": {
"min": 2,
"max": 10,
"metric": "cpu",
"targetPercent": 70
},
"rollout": {
"strategy": "canary",
"canaryPercent": 10,
"canaryPauseSec": 300
}
}'
You can see the appeal. The JSON version makes the hierarchy explicit. Each service is a distinct object, resources nest naturally, and there’s no ambiguity about which field belongs where. On paper, this should be easier for an agent to get right.
We used two separate CLIs as the test subjects. One accepts only individual args, the other accepts only a --json payload. Both share the same validation backend and normalize to the same canonical structure. At test time, whichever variant is active gets renamed to podctl so the agent sees a consistent binary name. Since the CLI is unknown to the agent, it would have to discover the input model from scratch. For args, it means invoking the CLI’s help. For JSON, it also means invoking help but also calling an extra command that returns the JSON schema.
We used two separate binaries because a single CLI supporting both modes would let the agent pick its preferred approach, likely skewing the results.
We ran each model five times per input mode with identical prompts and evaluation criteria, using GitHub Copilot Chat as the harness. For the models we picked Claude Haiku 4.5, Claude Sonnet 4.6, Claude Sonnet 5, GPT-5.3-Codex, and MAI-Code-1-Flash, which are all commonly used for coding.
Correctness: args swept
Every args profile achieved perfect correctness across all five runs, regardless of model. Haiku 4.5, the smallest model in the test, produced correct deployments as reliably as Sonnet 5.
For JSON, the picture looked differently. The three strongest models matched args with 5/5 correctness. The two smaller models didn’t. Haiku 4.5 managed 2 out of 5 correct deployments in JSON mode. MAI-Code-1-Flash managed 3 out of 5.
Model
args
JSON
Claude Sonnet 5
5/5
5/5
Claude Sonnet 4.6
5/5
5/5
GPT-5.3-Codex
5/5
5/5
MAI-Code-1-Flash
5/5
3/5
Claude Haiku 4.5
5/5
2/5
Args constrain the input space enough that even smaller models produce correct output consistently. JSON requires the model to manage its own structure, and that’s a capability tax not every model can pay.
The cost
Developers pay for tokens, not attempts. So we broke down the token consumption into input, cached, and output tokens and calculated cost per task using GitHub Models pricing.
Model
args
JSON
Ratio
GPT-5.3-Codex
$0.05
$0.54
11x
MAI-Code-1-Flash
$0.01
$0.08
10x
Claude Sonnet 4.6
$0.05
$0.47
9x
Claude Haiku 4.5
$0.03
$0.23
8x
Claude Sonnet 5
$0.08
$0.32
4x
JSON mode cost 4x to 11x more per task across every model. These are per-invocation numbers, and for a team running such tasks many times it would quickly compound.
The raw token gap was even larger (4x to 14x) but caching softens the blow. In JSON mode, the agent retries many times, and each retry’s input is mostly cached at the lower rate. What caching can’t offset though is the output tokens. Each retry means the agent reasons about the error and produces a new workaround, and those workarounds get creative: piping JSON through variables, writing to temp files, trying different escaping strategies. All of that is output, and output tokens are the most expensive category. JSON retries generated 7x to 14x more output tokens than the args path. That’s where the cost comes from.
Where do those extra tokens go? Retries. When a model constructs a JSON payload and the CLI rejects it, the agent reads the error, attempts a fix, and tries again. JSON has more ways to fail (syntax errors and wrong nesting are the obvious ones) and as we’ll see in the next section, shell escaping adds another layer of failures on top. More failure modes mean more retries, and more retries mean more money.
The shell escaping tax
Retries aren’t the whole story though. There’s a factor the “rewrite for JSON” advice doesn’t account for: shell escaping.
When the agent passes a JSON payload on the command line, it has to escape quotes for the shell. Different shells have different escaping rules for nested quoted strings, and none of them are simple. Models routinely produced valid JSON that broke on shell escaping: double-quoted strings inside single-quoted blocks, nested escaping that the shell interpreted differently than intended.
The primary experiment ran on Windows, where agents default to using PowerShell. The Haiku 4.5 JSON failures illustrate the escaping problem clearly. Three of the five runs never reached a successful invocation. The agent produced correct JSON content but couldn’t get it past the shell. It tried base64 encoding, file piping, here-strings, and cmd.exe passthrough, burning tokens on each attempt, and still failed.
We ran the same experiment with Sonnet 4.6 on macOS, where agents default to Bash, to see whether shell choice affected the results. On PowerShell, the cost gap between args and JSON was 9x. On Bash, it collapsed to 1.5x. Same model, same payload, same correctness. The difference was entirely in how each shell handles quoted JSON strings. Args on the other hand were stable across both shells: $0.05 on PowerShell vs. $0.07 on Bash. That’s the point: JSON introduces a dependency on shell quoting behavior that varies wildly across environments. Args don’t.
Why constraints help agents
The deeper finding is about what constraints do for models. Args constrain the agent’s output in ways that JSON doesn’t.
With args, the --help text defines the exact set of valid inputs. Each option has a name and a type, sometimes with an enum of valid values. The model maps from the task description to specific option names and values.
With --json, the model gets a schema (or infers one from --help) and constructs a free-form object that must satisfy it. The syntax must be valid, and the nesting must be correct. The field names and types must match. And the whole thing must survive shell escaping. Each of those is a failure mode that args eliminate by design.
Strong models eventually get JSON right, but “eventually” is doing a lot of work. They still hit shell escaping failures, they just recover from them. That recovery costs 4x to 11x more than the args path, even when correctness is identical. Smaller models hit the same escaping failures but can’t recover, which is where correctness drops. For smaller models, the constraints args provide are the difference between consistent success and frequent failure. It turns out, that narrowing the valid input space compensates for gaps in model capability. The model doesn’t need to get JSON nesting right because there’s no JSON nesting to get right.
What this means for your CLI
If you’re building a CLI that agents will use, the data points in a different direction than “rewrite it with --json.” Keep your args. They work across the model capability spectrum and don’t have environment-dependent failure modes. They also cost fewer tokens and money.
But what if your input data is genuinely complex? Our test scenario had 30+ values across nested services, and args handled it perfectly across every model. If you want to offer a --json option for programmatic use or batch operations, that’s fine. But don’t remove args in favor of JSON, and don’t expect JSON to improve agent outcomes. In this experiment, JSON never improved correctness and always increased cost.
The intuition that JSON *should* work better makes sense on paper. But “should” doesn’t survive contact with actual models running in actual shells on actual operating systems. Notice, that args are constrained and predictable across shells. JSON is expressive but fragile. For agent-driven CLI usage, constrained wins.
Measure before you rewrite
This is the same lesson that runs through the rest of the AX series: plausible advice and measured outcomes are different things, and when they disagree, the measurements win. If someone recommends restructuring your CLI’s input model, run the experiment first. Pick a scenario, define evaluation criteria, select relevant harnesses, models and OS for your audience and do a couple of runs on both approaches. You’ll have your answer in a day without rewriting anything you didn’t need to rewrite.
Author

Principal Developer Advocate
Waldek is a Principal Developer Advocate at Microsoft focusing on AI Coding Agents. He researches AI Coding Agents, and evaluates and improves Agent Experience for Microsoft's products and services.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み