ピクセルを使わないデスクトップエージェント
- アクセシビリティツリー型エージェントが何を読むのか、スクリーンショット型エージェントとプロトコルレベルでどう違うのかを理解する
- 多くの computer-use ハーネスが間違えているリトライ規則と、「失敗したか?」が誤った問いである理由を学ぶ
- ref がスナップショットにスコープされる理由と、ref が古くなったときハーネスが推測せずに何をすべきかを知る
- 失敗した読み取りに基づく行動を防ぐ 3 状態のプロパティモデルを知る
- このアプローチでは解決できない 2 つの失敗モード、すなわち途中で切れたツリーとアクセシビリティ面を持たないアプリを見分ける
- セキュリティ上の面を理解する: 画面上のすべてがエージェントの入力になる
2026年10月28日、Vercel Labs は agent-desktop の v1.0.0 をタグ付けし、10月31日には GitHub のトレンド首位に載りました。これは Rust 製の CLI と C ライブラリ (Apache-2.0) で、OS のアクセシビリティツリー、つまりスクリーンリーダーが使うのと同じ構造を読むことで、macOS・Windows・Linux 上のエージェントに computer use を与えます。macOS は AX API、Windows は UI Automation、Linux は AT-SPI です。Vercel 自身の言い方は「agent-browser の、コンピューター全体版」。
これまでの紹介記事はどれも見出しで止まっています。ピクセルではなくツリーだからクリックが狙った場所に当たる、と。それは事実ですが、面白さでいえば半分のうちの小さいほうです。
面白いほうの半分は、リポジトリに CONCEPTS.md が入っていることです。これは事実上、computer-use ハーネスが不確実性をどう扱うべきかの仕様書であり、主流の computer-use API が丸ごとあなたに任せている問いに答えています。エージェントループを作る人は、ツールを入れなくてもモデルのためにこのページを読んでください。スクリーンショット型の Anthropic・OpenAI・Google の API は computer-use エージェント比較 で扱っています。こちらはもう一方のアーキテクチャです。
画面ではなくツリーを読むと何が変わるか
スクリーンショット型エージェントは画像を受け取り、座標を返します。契約は「ピクセルを渡すので、どこをクリックするか教えて」。UI について知っていることはすべて視覚から推論するしかなく、すべての操作は座標への賭けです。
ツリー型エージェントは構造を受け取ります。スナップショットは、操作可能な要素ごとに ref を持つ JSON を返し、操作は点ではなく ref を指名します。
- ウィンドウ、アプリ、またはサーフェスのアクセシビリティツリーのスナップショットを取ります。ロール、名前、状態を含む JSON が返り、操作可能なノードごとに ref が付き、その ref が属するスナップショット ID も付きます。
- 操作が発火する前に、ref はスナップショット時に保存された同一性の証拠を使って「現在の」ライブツリーと照合し直されます。このステップがすべてを決めます。詳細は後述します。
- interaction lease のもとでディスパッチします。これはデスクトップ入力に対するマシン全体の排他権で、一度に 1 プロセスだけが保持するため、2 つの呼び出し元が互いの操作に合成入力を割り込ませることはありません。
- 要素を読み直し、変更が実際に届いたかを確認します。再読込がそう言って初めて、変更は「配信済み」になります。
ここから生じる実際上の違いは、見た目より大きなものです。
- ref は、座標を壊すレイアウト変更を生き延びます。 ウィンドウ内で 200 px 下に動いたボタンも、ツリー上では同じノードです。スクリーンショット型エージェントにとっては新しい賭けになります。
- デフォルトでヘッドレスです。 入力はシステムポインターを動かさず、フォーカスも奪わずにアクセシビリティ API を通るため、あなたがマシンを使い続けている間にエージェントがアプリを操作できます。アプリがプログラム的な入力を無視する場合に物理入力を合成する
--headedモードもあります。カーソルを奪い合うスクリーンショット型エージェントとは、動作モデルがまったく異なります。 - スナップショットは画像ではなくテキストです。 請求額とコンテキストウィンドウに効きます。エージェントがトークンを燃やす理由 を参照してください。ただしタダの勝利ではありません。密なツリーは大きくなるので、リポジトリ自身の答えは、まず粗くマップし (
--skeleton --compact)、次に--rootで領域を掘り下げることです。 - それでもスクリーンショットが要る場面があります。 このツールは
snapshotと並んでscreenshotも備えています。キャンバス描画のアプリ、独自描画の UI、ゲームにはアクセシビリティ面がほとんど、あるいはまったくなく、ツリーをいくら読んでも生まれません。
最小の 観察 / 実行 / 検証 ループ
npm install -g agent-desktop agent-desktop version # macOS only: accessibility permission must be granted first agent-desktop permissions --guide # 1. observe — -i limits output to interactive elements agent-desktop snapshot --app Finder -i # 2. act — the ref MUST carry its snapshot ID; a bare @e8 is rejected agent-desktop click @s8f3k2p9:e8 # 3. verify — re-snapshot and confirm the change landed agent-desktop snapshot --app Finder -i # dense UI? map coarsely, then drill into one region agent-desktop snapshot --app Slack --skeleton --compact agent-desktop snapshot --root @s8f3k2p9:e42
出力は ok ブール値と、data またはコード・disposition・提案を持つ error を含む JSON です。コマンドは、観察 (snapshot, find, get, screenshot, list-surfaces)、ref への操作 (click, type, set-value, scroll, toggle, expand)、生のキーボード/マウス (press, hover, drag, key-down)、ウィンドウ/アプリ管理 (launch, focus-window, resize-window, list-apps)、システム (permissions, status, batch, skills) に分かれます。
盗む価値のある考え方: 成功ではなく「配信済み」
規則はこれで、リポジトリの中で最も役に立つ一点です。
重要な区別は成功か失敗かではない。配信されたか、されていないかである。
type コマンドがエラーを返したとき、エージェントが何をするかを考えてみてください。「エラーならリトライ」というハーネスの本能的な挙動は積極的に危険です。「エラー」が、まったく別の 2 つの状況をひとまとめにしているからです。
- キー入力がフィールドにまったく届いていない。リトライはタダです。
- キー入力が届いたかもしれないが、確認の読み取りが失敗した。リトライすると全部が 2 回入力されます。
この 2 つを区別できないハーネスは、フォームを黙って二重送信し、送信ボタンを二重クリックし、行を重複させます。そのため agent-desktop は、すべての変更操作をブール値ではなく 3 つの配信状態に分類します。
| 配信状態 | 意味 | リトライしてよいか |
|---|---|---|
| 未配信 | 引数エラー、アクセス拒否、または読み取り専用コマンド。操作が対象に届いていないことが実証されている | 安全。 自由にリトライしてよい |
| 配信済み・未検証 | 入力は送られたが効果は未確認。物理入力合成の典型的な結果 | 危険。 状態の読み直しだけが正しい対応 |
| 配信済み | 要素の再読込で効果が確認された | 完了。リトライするものはない |
読み取りにも並行する 4 分類があり、最初のものが微妙です。
- 確定した不在 — プロバイダーに問い合わせた結果、対象に本当にそのプロパティやパターンがない。最終的な答えであり、リトライしません。
- リトライ可能な失敗 — タイムアウトや接続切れのようなトランスポート障害。予算の範囲で繰り返す価値があります。
- 利用不能な要素 — 消えた。要素が解決済みだったなら、古い ref として確定します。ツリー走査の途中で起きたならリトライ可能です。
- 終端的な失敗 — 拒否、または未分類のコード。最終的なもので、そのまま表面化します。
ツールはこれを直接公開しています。error.disposition.retry が safe のときだけリトライします。
- 今日、自前の computer-use ループを書いているなら、ほかに何も持ち帰らなくてもこの 1 つの考え方を移植してください。すべての操作を「届いた可能性があるか」で分類し、「届いたかもしれない」はリトライではなく状態を読み直す別の分岐にします。冪等性はリトライコードの性質ではなく、個々の操作の性質であり、どれがどちらかを知っているのはツール層だけです。
ref がスナップショットにスコープされる理由
agent-desktop の ref は @e8 のように見えますが、単独では使えません。素の ref は INVALID_ARGS で失敗します。スナップショット ID で修飾する必要があります。@s8f3k2p9:e8 です。
手間に見えますが、これは意図的な型システムです。述べられている理由は、ref は 1 つのスナップショット内では決定的だが、UI の変更をまたいでは安定しないということです。スナップショット ID は名前空間であり、すべての検索は作成された名前空間の中に閉じ込められます。セッションのもとで取ったスナップショットには、後でも同じセッションスコープが必要です。
つまり面倒な構文が、本物のカテゴリエラーを「表現できないもの」にします。3 操作前の観察から持ち越した識別子が、いまそのスロットを占めている何かに黙って解決されてしまう、ということが起こりえません。スクリーンショットの世界では、古いスクリーンショットの座標は新しいものと構文上区別がつかず、毎回「何か」に解決されます。
fail-closed な解決: 推測を拒否する
操作が発火するとき、ref は段階的な手順でライブツリーに対して解決され、その順序はどの証拠が信頼できるかについての判断を表しています。
- スナップショット時に記録されたパスを、現在のツリーの中でたどります。
- 候補を記録内容と照合します。まずネイティブ ID、次にロールに応じた安定したテキストです。位置は候補までは連れて行ってくれますが、それが正しいものかを決めるのは同一性です。
- パスがもう存在しないなら、より広く検索します。ただし無制限ではなく、境界を設けます。
- 最後の手段として、一意なジオメトリで解決します。ただしそれは、ref に照合できる意味のある同一性が「まったくない」場合に限られます。Windows と Linux では、保存された bounds が正の面積を持つ矩形でなければなりません。範囲ゼロの保存ハッシュがジオメトリ一致に昇格することはありません。
設計上のコミットメントは、これらのどれも確信できる一致を生まなかったときに現れます。解決はフェイルクローズします。欠落、古い、曖昧な一致は、最善の推測に解決されるのではなく、拒否されます。STALE_REF、AMBIGUOUS_TARGET です。macOS では、ツリーの順序が足元でずれても要素が黙って取り違えられないよう、実装がネイティブのアクセシビリティオブジェクトを保持しています。
これはピクセル型エージェントの劣化の仕方と正反対です。古い座標は必ず「何か」に当たります。古い ref はエラーを返します。黙った誤操作を、大きな音を立てる失敗と引き換える。それが信頼性の主張のすべてであり、エージェントが監督なしで行うことにはそれが正しい取引です。
- 同じ考え方は、リポジトリの部分結果の二面的な扱いにも現れます。スナップショットが走査の途中で時間予算を使い切った場合、走査を捨てずに観察できた分を返しますが、完全性はノードごとに報告します。このフィールドを無視するハーネスは、途中で切れたツリーを自信満々に推論して、ボタンは存在しないと結論づけます。不在を結論する前に完全性を確認してください。
3 状態のプロパティ: Absent、Unknown、Known
小さな仕組みですが、影響は大きいものです。プロパティは決してブール値に潰されません。3 つの状態が区別されたままです。
- Known — 値が読み取れた。
- Absent — プロバイダーに問い合わせた結果、本当に値がない。
- Unknown — 読み取りそのものが失敗した。
ほとんどのハーネスは Unknown を false や null に平らにしてしまい、エージェントは失敗した読み取りを否定的な結果であるかのように扱って行動します。チェックボックスは「チェックされていない」(読めなかっただけ)、フィールドは「空である」(クエリがエラーになっただけ)。リポジトリの言い回しでは、不確実性は伝わっていかなければなりません。優先度の高い証拠源が失敗したとき、その失敗はならされて消えるのではなく、先へ運ばれます。アクセシブルネームはプラットフォームごとのアダプターではなく、ランク付けされたスロットからコアで計算されるため、共有ロジックがプラットフォーム固有のトークンを目にすることはなく、どこでも同じ不確実性ルールが適用されます。
このページから 2 つ持ち帰るなら、配信セマンティクスとこれです。どちらも同じ規律の事例です。「知らないこと」を「知っていること」のふりをさせない。
名指しすべき 2 つの限界
interaction lease が覆うのはディスパッチだけです。 入力の排他はマシン全体で、一度に 1 プロセスが保持しますが、lease は操作に先立つ待機を意図的に覆いません。要素が操作可能になるのを待つ時間は長くなりうるからです。実際上の帰結として、要素は操作可能になったあと、あなたの待機とディスパッチの間に再び変化しうるのです。だから検証ステップは任意ではありません。
プラットフォーム対応は一様ではなく、Linux が粗い縁です。 アクセシビリティツリー、クリック、入力は 3 つの OS すべてで動きますが、Linux ではクリックと入力が X11/GNOME の注意点つきで AT-SPI を通り、スクリーンショットには X11 か Wayland portal が必要で、Wayland ではクリップボードに wl-clipboard が必要で、アプリ管理は起動と一覧のみ、通知は GNOME Shell です。macOS ではアクセシビリティ権限を明示的に付与する必要があり、さらにユーザー所有でモード 0700 の TMPDIR が要ります。Windows は、同じターミナル整合性レベルなら特別な権限は不要です。約 60 あるコマンドのすべてがあらゆるプラットフォームで動くわけではありません。実際に動かす予定のマシンで agent-desktop status を確認してください。
セキュリティ上の面は画面全体
便利さは魅力的なので、はっきり述べる必要があります。
アクセシビリティツリー型エージェントはアプリケーションのテキスト内容を読みます。これはフォーカス中のウィンドウのスクリーンショットより厳密に豊かな取得で、画面上にはあるけれどあなたが見ていたわけではない内容まで含みうるものです。リポジトリは明言しています。スナップショット、スクリーンショット、クリップボードの内容には機微なデータが含まれうるので、公開ログには出さないこと。実際の緩和策も 1 つ組み込まれています。セキュアフィールドです。プラットフォームが要素を秘密としてマークすると、読み取り経路はその値と関連するテキストの証拠の両方を差し控え、操作経路はそこへ書き込めても、試みた値や観察した値を返すことはありません。
これでパスワードは守られます。開いているメール、Slack の DM、隣のウィンドウの顧客レコードは守られません。帰結は 2 つです。
- トランスクリプトが機微な成果物になります。 デスクトップセッションのエージェントログは、ターミナルの履歴というより画面録画に近いものです。そのように扱ってください。エージェントが何をアップロードするか を参照してください。
- 読み取れるすべての面がインジェクションの面です。 アプリが表示するあらゆるテキストがエージェントの入力になるため、細工されたカレンダー招待、ファイル名、チャットメッセージは、ネットワークリクエストなしのプロンプトインジェクションの経路になります。エージェントが読むウェブ と同じ理屈がデスクトップにも当てはまり、入門としては プロンプトインジェクション があります。マシン全体の入力排他を持ち、ポインターを動かさないエージェントは、構造上、その操作があなたの目に見えないエージェントでもあります。
- ヘッドレスがデフォルトであることは、使いやすさの機能であると同時に、監督上の危険でもあります。エージェントがポインターを動かさずフォーカスも奪わないので、動いていることを示す周囲の合図がありません。無人で実行するなら、補う統制はあなたの注意ではなく、ログと、範囲を絞った対象アプリでなければなりません。一般的なパターンは、自律実行のハードニングのページで扱っています。
このアーキテクチャが正しい選択になるとき
| 状況 | 選ぶもの |
|---|---|
| 本物のアクセシビリティ対応を持つネイティブデスクトップアプリ (Finder、Office、ほとんどの Cocoa/WinUI アプリ) | ツリー型エージェント。 安定した ref、安価なテキストスナップショット、座標計算なし |
| ブラウザ内のウェブアプリ | まずブラウザ専用のハーネス。DOM はブラウザの AX 投影より良いツリーです。computer-use エージェント比較 と 共有ログインのブラウザ を参照 |
| Canvas/WebGL アプリ、ゲーム、独自描画 UI、リモートデスクトップ | スクリーンショット型エージェント。 読めるアクセシビリティ面がありません |
| あなたが作業を続けている間にエージェントを動かす必要がある | ツリー型エージェント、ヘッドレスモード。フォーカスを奪わず、ポインターの奪い合いもなし |
| 監督なしで、繰り返され、変更を伴う操作 | ツリー型エージェント、そして配信セマンティクスを守る。設計全体がこのケースのために存在します |
| 目視での検証 (「見た目は正しいか?」) | ハーネスに関係なくスクリーンショット。ツリーはレイアウトが壊れていることを教えられません |
Check yourself
0/5- アクセシビリティツリーは座標への賭けを名前付きの参照に置き換えるので、スクリーンショット型エージェントを壊すレイアウト変更は問題になりません。ただしキャンバス、ゲーム、独自描画 UI には読めるツリーがありません。
- 配信モデルを盗むこと: 操作は、エラーを返したかではなく、届いた「可能性」があるかで分類する。「届いたかもしれない」には再読込がふさわしく、リトライは決してふさわしくない。
- ref をスナップショットにスコープするのは手間ではなく型システム。古い識別子のバグを、黙って間違わせるのではなく、表現できなくする。
- fail-closed な解決こそが信頼性の主張: 古い座標は必ず何かに当たるが、古い ref は STALE_REF を返す。監督のない操作には、大きな音を立てる失敗のほうを選ぶ。
- Unknown と Absent は別物として扱う。失敗したクエリを否定的な結果と読むエージェントは、何もないものに自信を持って行動する。
- ヘッドレス動作は、エージェントが作業中であるという周囲の合図を消す。無人で動かすなら、統制はあなたの注意ではなく、ログと範囲を絞った対象アプリである。
出典とさらに読む
- vercel-labs/agent-desktop — プロジェクト本体: README、インストールとプラットフォーム対応表、コマンドグループ、JSON 出力の形、
--skeleton/--compact/--rootフラグ、ライセンス (Apache-2.0)、v1.0.0〜v1.0.2 のリリース履歴 - agent-desktop — CONCEPTS.md — このページの後半のすべての出典: ref のスコープ、段階的な fail-closed 解決、4 つの読み取り結果、配信セマンティクス、3 状態の証拠、interaction lease、セキュアフィールド、部分的な観察
- agent-desktop — docs/faq.md と docs/json-output.md — エラーコードと機械可読な出力の契約
- Apple — Accessibility API (NSAccessibility) · Microsoft — UI Automation · GNOME — AT-SPI2 — 基盤となる 3 つのプラットフォームのツリー
- AILmanac 内の関連: computer-use エージェント比較 — Anthropic・OpenAI・Google のスクリーンショット型 API と、このアーキテクチャが回避する座標のバグ
- AILmanac 内の関連: AI エージェント向け共有ログインブラウザ — エージェントが設計上あなたのセッションを継承するブラウザ版
- AILmanac 内の関連: プロンプトインジェクション — いまやデスクトップのあらゆるウィンドウに当てはまる入門
- AILmanac 内の関連: エージェントが何をアップロードするか — デスクトップエージェントのトランスクリプトを画面録画のように扱うべき理由
次へ
- computer-use エージェント比較 — もう一方のアーキテクチャと、大手 3 社のアクション空間がどう分かれるか
- AI エージェント向け共有ログインブラウザ — セキュリティ姿勢が反転するブラウザのケース
- 自律実行のハードニング — 周囲の合図なしに動くエージェントの周りに置くべき統制
- エージェントランタイム: isolate とコンテナ — このループを実際にどこで実行すべきか