2つのユーザー、1つのCLI:人間とエージェント
開発者向けツールはスクリプトからCLIへ進化し、当社の内部プラットフォームもプロジェクト構築やデプロイを自動化するCLIとして成長した。このCLIは、開発者(人間)とAIエージェントの両方が利用する単一のインターフェースとして設計されている。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
ほとんどの開発者ツールは同じように始まります。繰り返し行う必要がある作業があるため、スクリプトを書きます。そのスクリプトにフラグが追加され、さらにサブコマンドが増え、気づけば CLI が完成しています。
私たちも、ソリューションチームのスピードアップを支援する内部プラットフォームツールを構築する際に、まさに同じ経験をしました。このツールはプロジェクトのスキャフォールディングを行い、開発環境を起動し、設定ファイルを生成し、ステージング環境へデプロイします。
CLI の範囲が広がるにつれて、内部ユーザーの数も増えました。しかしその後、面白いことが起こりました:突然、私たちは人間の開発者だけでなく、コーディングエージェント(AI エージェント)のためにも構築するようになったのです。
優れた DX とは何か
エージェントについて語る前に、まず人間開発者にとって CLI が実際に快適であるために必要な要素について話しましょう。
Spaces は退屈な部分に対して明確な方針を持っています。無難なディレクトリ構造を採用することで、ユーザーがそれを考える必要をなくします。以前なら最後のプロジェクトからコピーしていただろう設定ファイルを自動生成します。また、サービス同士を接続し、API とフロントエンドが YAML の編集に 1 時間費やすか「雰囲気」で繋ぐのを待つことなく、初日から通信できるようにします。
典型的なセッションは以下のようになります:
3 つのコマンドだけですべて完了します。何もない状態から、ホットリロード機能付きのマルチサービスプロジェクト、データベース、そして生成された Dockerfiles を備えた実行可能な環境へと移行できます。これが基準です。
重要なコマンドは通常、次の 3 つのカテゴリに分類されます:
- スキャフォールディング -- これらは構造を作成し、質問を行い、オプションを表示し、探索を可能にします。
- 開発 -- これらは内部ループ(インナーループ)です。単に「実行」するだけです。
- オペレーション -- これらは本番環境に触れるものです。行動する前に注意深く、二度確認してください。
これはすべて最低限必要な条件に過ぎません。しかし、2 番目のユーザーが登場したことで、私たちは少し異なる、そしてはるかに興味深いものが必要であることを発見しました。
2 番目のユーザー:エージェント
init 用に TUI(ターミナルユーザーインターフェース)モジュールピッカーを構築しました。見事な変更やコマンドで、非常に魅力的に見えました。
しかし、エージェントがそれを使おうとすると、エージェントは生の ANSI エスケープコードを見ました。\x1b[36m?\x1b[0m コンポーネントを選択してください。矢印キーを送信したり、選択を切り替えたりできませんでした。コマンド自体から完全にロックアウトされてしまったのです。
振り返れば修正策は明白に見えます:--components フラグを追加するだけです。しかし、真の洞察は単なる 1 つのフラグよりもはるかに大きなものでした。
すべてのプロンプトは正体不明のフラグである
CLI が対話形式で質問をするたびに、暗黙的な契約が存在します。「この情報を入手しないと先に進めない」というものです。その契約を満たすには、対話型プロンプトを使用するか、フラグまたは設定ファイルを使用するかの 2 つの方法があります。
肝心なのは、まず「情報」を考慮し、次に「入力方法」を考えることです。
3 つの入力経路がありながら、実行パスは 1 つです。ビジネスロジックは入力がどのように到着したかを知らないので、テストは一度行えばよく、3 回行う必要はありません。
-y フラグには特別な注意が必要です。それは単なる「確認のスキップ」ではありません。「プログラム的に必要なすべてを提供するので、標準入力(stdin)でブロックしないでください」という契約を表しています。-y を指定して実行すると、すべてのプロンプトはフラグ値またはスマートなデフォルト値に解決されます。もし解決できない場合は、フリーズするのではなく、明確にエラーを報告して失敗します。
実践例:本記事における対話要素。
これらの要素は、新しいリポジトリ内で構築されました。エージェントには後からプロジェクトを [spaces cli] に接続してデプロイするよう指示が出され、まずは関連するコマンドに対して --help を実行してインターフェースを理解することから始めました。その結果、必要な設定ファイルを特定し、config.yaml を生成し、Dockerfile とレジストリの設定を接続しました。手取り足取りの指導は不要でした。
同じ一連のプロセスで、GitHub Actions の CI パイプラインもセットアップされました。単一のプロンプトからライブデプロイまでにかかった時間は 10 分未満です(このサイクルタイムをさらに短縮するために現在積極的に取り組んでいます)。その後、リポジトリは完全に設定され、埋め込みコンテンツが Koyeb を介して Space としてデプロイされました。これは Spaces 自体を通じてスキャフォールドされ、デプロイされたものです。はい、この Spaces に関するブログ記事に埋め込まれているインタラクティブなデモは、Space として動作しています。*Spaception* です。
すべてのインタラクティブ入力に対応するフラグが存在したため、エージェントはエンドツーエンドで自律的に操作できました。
インターフェースとしての構造化データ
私たちがチームで構築していた CLI は、適用 AI エンジニアがアプリをリリースするのを支援しました。しかし、すべてのアプリが同じ見た目をしているわけではありません。バックエンドとベクトルデータベースが必要なものもあれば、リレーショナルデータベース、フロントエンド、いくつかの API だけで済むものもあります。さらに、UI が全くないワーカー専用サービスとして動作するものさえあります。
モジュールタイプをハードコードしようとはしませんでした。そのため、各コンポーネントが自身のプロパティを宣言するプラグインとなるようなプラグインシステムを構築しました:
プラグインは自己記述可能です。一覧表示、シリアライズ、差分比較が可能です。人間は TUI ピッカーを閲覧し、エージェントはレジストリにクエリを実行して JSON を受け取ります。同じデータでも、レンダリング方法が異なります。
これは、私たちが気づいていなかった問題を偶然解決しました。以前は新しいモジュールタイプを追加するたびに、ピッカー、Dockerfile 生成器、環境ファイル作成ツール、Compose テンプレートをすべて更新する必要がありました。現在は、プラグインクラスを一つ書くだけで済みます。レジストリが唯一の真実源となり、すべてのコンポーネントがそこから情報を取得します。
プロジェクトのエージェントへの教育
**
古くから「コンテンツは王様である」という格言があります。エージェントにおいては、文脈こそが王様です。
エージェントの使いやすさを向上させるために私たちが最も効果的に行ったことは、初期化時に必ず 2 つのファイルを生成することでした:
context.json** -- プロジェクトの構造化されたスナップショット:存在するモジュール、使用しているポート、実行すべきコマンド、必要な環境変数など。
AGENTS.md -- LLM 向けに記述されたルールのセットで、通常のドキュメントよりも命令的です。「このプロジェクトは PostgreSQL を使用しています」という記述ではなく、「データベースの変更をテストする前に mycli dev --migrate を実行してください」といった具体的な指示です。
これらのファイルを読み込んでから行動するエージェントは、劇的に少ないミスを犯します。つまり、ポート番号を推測したり、間違ったテストコマンドを実行したり、ツールチェーンですでに管理されている依存関係をインストールしようとしたりすることはありません。
コンテキストファイルはまた、古くなったエージェントの前提条件に対するキャッシュバスターとしても機能します。モジュールを追加したりデプロイ先を変更したりした場合、次の開発または初期化時に自動的にコンテキストファイルが更新されます。エージェントは毎回最新のステータスを読み取ります。
暗黙的な状態は敵である
私たちが直面した最も微妙な問題は、暗黙的な状態でした。私たちの add コマンドは、現在の作業ディレクトリから config.yaml を読み込んでいました。人間であれば、考えずに右側のフォルダに移動しますが、ワークスペースのルートからコマンドを実行するエージェントには、サブディレクトリにいる必要があるという認識がありません。
解決策:
すべての暗黙的な仮定——CWD(現在の作業ディレクトリ)、環境変数、$HOME 内のドットファイル——は、エージェントが躓く場所です。適切なフォールバックを持つ明示的なパラメータを設けることで、エージェントの問題を解決し、人間にとってもスクリプト作成が容易になります。
チェックリスト
振り返ってみると、個々の変更は小さかったものです。一貫して適用された原則のセットに過ぎません:
- すべての対話型入力にはフラグ相当のものがあること
- 各フラグにはヘッドレスモード用のスマートなデフォルト値があること
- 状態は明示的であること。CWD、環境変数、設定パスは仮定ではなく入力の一部であること
- プラグインは単なるコードではなくデータモデルであり、デフォルトで内部参照可能であること
- コンテキストファイルがエージェント(および CI やスクリプト)に対してプロジェクトの構造化された記述を提供すること
みんなのためのより良いツールの構築
面白いことに、これらすべてによって CLI が人間にとって悪くなったわけではありません。TUI ピッカーは依然として機能し、華やかに見え、進行状況スピナーはまだ回転し、確認ダイアログも依然として確認を行います。私たちは単に第二の扉を追加しただけです。
そしてその第二の扉が、実はより重要なものとなりました。エージェントが人間よりも重要だからではありません。彼らが課す制約こそが、CLI をコンポーザブル(組み合わせ可能)で、スクリプト可能で、テスト可能にするための同じ制約だからです。エージェントのために設計することは、結果としてみんなのためのより良いツールを構築することを強要しました。
現在、開発者向けツールを構築している場合、専用のエージェント API は不要です。すべての input() 呼び出し、CWD(現在の作業ディレクトリ)に関する前提、そして美観のみを重視した出力について、その向こう側のユーザーが人間ではなくプロセスではないかと問いかける必要があります。
この問いに対する答えは、いずれにせよあなたのツールを向上させるでしょう。
Spaces CLI は、Mistral AI の Lorenzo Signoretti 氏、Riwa Hoteit 氏、Sam Fenwick 氏によって構築されました。Applied AI チームへの特別な感謝の意を表します。彼らは CLI の最も初期かつ最も厳しい利用者であり、その実世界での利用がここで説明されるすべてのパターンを形作ってきました。顧客と共に、痛烈な課題を解決するためのアプリケーションを構築する姿を見ることを楽しみにしています。
人間とエージェントの間にあるツールリングはまだ確立されつつあります。AI とインフラの交差点で開発者向けツールの構築に興味があるなら、採用情報をご覧ください。
原文を表示
Most developer tools start the same way. You need to do a thing repeatedly, so you write a script. The script grows flags. The flags grow subcommands. Before you know it, you've got a CLI.
We had this exact experience when building an internal platform tool to help our solutions team ship faster. It scaffolds projects, spins up dev environments, generates configs, and deploys to staging.
As our CLI grew in scope, so did the number of our internal users. But then, something interesting happened: suddenly, we were no longer building just for human developers, but for coding agents too.
What Good DX Looks Like
Before we talk about agents, let's talk about what makes a CLI actually pleasant for human developers.
Spaces is opinionated about the boring stuff. It picks sane directory structures so you don't have to. It generates the config files you'd otherwise copy from the last project. It wires services together so your API and your frontend can talk on day one, not after an hour of YAML editing or “vibing”.
Here's what a typical session looks like:
Three commands. You go from nothing to a running multi-service project with hot reload, a database, and generated Dockerfiles. That's the bar.
The commands that matter tend to fall into three buckets:
- Scaffolding -- These create structure, ask questions, show options, let you explore.
- Development -- These are your inner loop. They just “run”.
- Operations -- These touch production. Careful, double-check before acting
This is all table stakes. But then our second user showed up and we discovered we needed something a little different, and a lot more interesting.
The Second User: The Agent
We built a TUI module picker for init. Fancy changes, commands, it looked super.
Then an agent tried to use it.
The agent saw raw ANSI escape codes. \\\\x1b[36m?\\\\x1b[0m Select components. It couldn't send arrow keys or toggle selections. It was locked out of the command entirely.
The fix seems obvious in retrospect: add a --components flag. But the real insight was bigger than one flag.
Every prompt is a flag in disguise
Every time your CLI asks an interactive question, there's an implicit contract: "I need this piece of information to proceed." To fulfill that contract, you can use an interactive prompt. Or you can use a flag or config file.
The trick is to think about the *information* first and the *input method* second.
Three input paths, one execution path. The business logic doesn't know how the inputs arrived. This means you test it once, not three times.
The -y flag deserves special attention. It's not just "skip confirmations." It's a contract that says: *I'm providing everything you need programmatically, don't block on stdin.* When you run with -y, every prompt resolves to either a flag value or a smart default. If it can't, it fails loudly instead of hanging.
Example in practice: the interactive elements in this post.
The elements were built in a fresh repository. An agent was retroactively asked to wire the project into [spaces cli] for deployment. It started by running --help across the relevant commands to understand the interface. From that, it figured out which configuration files were needed, generated a config.yaml, and connected the Dockerfile and registry settings - no hand-holding required.
In the same pass, it set up the GitHub Actions CI pipeline. From a single prompt to a live deployment took under 10 minutes (a cycle time we're actively working to bring down). After that, the repository was fully configured and the embeds were deployed with Koyeb— as a Space, scaffolded and deployed through Spaces itself. Yes, the interactive demos embedded in this blog post about Spaces are running as a Space. *Spaception*.
Because every interactive input had a flag equivalent, the agent could operate autonomously end-to-end.
Structured Data as Interface
The CLI our team was building helped our applied AI engineers ship apps. But not every app looks the same. Some need a backend and a vector database. Others just need a relational database, a frontend, and some APIs. A few are worker-only services with no UI at all.
We were not about to hardcode the module types. So we built a plugin system where each component is a plugin that declares its own properties:
Plugins are introspectable. You can list them, serialize them, diff them. A human browses a TUI picker. An agent queries the registry and gets JSON back. Same data, different rendering.
This accidentally solved a problem we didn't know we had. Before, adding a new module type meant updating the picker, the Dockerfile generator, the env file writer, and the compose template. Now it means writing one plugin class. The registry is the single source of truth and everything reads from it.
Teaching Agents About Your Project
There's an old saying that content is king. With agents, context is.
The most impactful thing we did for agent usability was generating two files on every init:
context.json -- a structured snapshot of the project: what modules exist, ports they use, commands to run, env vars they need.
AGENTS.md -- a set of rules written for LLMs and more imperative than your regular one. Not "this project uses PostgreSQL" but "run mycli dev --migrate before testing database changes."
An agent that reads these files before acting makes dramatically fewer mistakes. Basically, it won’t guess at port numbers, run the wrong test command, try to install dependencies that are already managed by the toolchain.
The context file also acts as a cache-buster for stale agent assumptions. When you add a module or change a deploy target, the context file updates automatically on the next dev or init. The agent reads fresh state every time.
Implicit State is the Enemy
The subtlest problem we hit was implicit state. Our add command read config.yaml from the current working directory. A human cds to the right folder without thinking. An agent running commands from a workspace root has no idea it needs to be in a subdirectory.
The fix:
Every hidden assumption -- CWD, environment variables, dotfiles in $HOME -- is a place where an agent will trip. Explicit parameters with sensible fallbacks solve it for agents and make scripting easier for humans too.
The Checklist
Looking back, the changes were small individually. Just a set of principles applied consistently:
- Every interactive input has a flag equivalent
- Every flag has a smart default for headless mode
- State is explicit. CWD, env vars, and config paths are inputs, not assumptions
- Plugins are data models, not just code. Introspectable by default
- Context files give agents (and CI, and scripts) a structured description of the project
Building a Better Tool for Everyone
The funny part is that none of this made the CLI worse for humans. The TUI picker still works and looks fancy, progress spinners still spin, confirmation dialogs still confirm. We just added a second door.
And that second door turned out to be the more important one. Not because agents matter more than humans, but because the constraints they impose are the same constraints that make a CLI composable, scriptable, and testable. Designing for agents forced us to build a better tool for everyone.
If you're building developer tools right now, you don't need a separate agent API. You need to look at every input() call, every CWD assumption, every pretty-printed-only output, and ask: what if the user on the other end is a process, not a person?
The answer to that question will improve your tool either way.
Spaces CLI is built by Lorenzo Signoretti, Riwa Hoteit & Sam Fenwick at Mistral AI. Special thanks to our Applied AI team, the earliest and most demanding users of the CLI, whose real-world usage shaped every pattern described here. We're excited to see the applications it will help them build with our Customers to solve painful use cases.
The tooling layer between humans and agents is still being figured out. If building developer tools at the intersection of AI and infrastructure sounds like your thing, we’re hiring.
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み