メインコンテンツまでスキップ

ピクセルを使わないデスクトップエージェント

上級
What you'll learn
  • アクセシビリティツリー型エージェントが何を読むのか、スクリーンショット型エージェントとプロトコルレベルでどう違うのかを理解する
  • 多くの 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 を指名します。

Guided walkthrough1 of 4
  1. ウィンドウ、アプリ、またはサーフェスのアクセシビリティツリーのスナップショットを取ります。ロール、名前、状態を含む JSON が返り、操作可能なノードごとに ref が付き、その ref が属するスナップショット ID も付きます。

ここから生じる実際上の違いは、見た目より大きなものです。

  • 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 のときだけリトライします。

Key takeaways
  • 今日、自前の computer-use ループを書いているなら、ほかに何も持ち帰らなくてもこの 1 つの考え方を移植してください。すべての操作を「届いた可能性があるか」で分類し、「届いたかもしれない」はリトライではなく状態を読み直す別の分岐にします。冪等性はリトライコードの性質ではなく、個々の操作の性質であり、どれがどちらかを知っているのはツール層だけです。

ref がスナップショットにスコープされる理由​

agent-desktop の ref は @e8 のように見えますが、単独では使えません。素の ref は INVALID_ARGS で失敗します。スナップショット ID で修飾する必要があります。@s8f3k2p9:e8 です。

手間に見えますが、これは意図的な型システムです。述べられている理由は、ref は 1 つのスナップショット内では決定的だが、UI の変更をまたいでは安定しないということです。スナップショット ID は名前空間であり、すべての検索は作成された名前空間の中に閉じ込められます。セッションのもとで取ったスナップショットには、後でも同じセッションスコープが必要です。

つまり面倒な構文が、本物のカテゴリエラーを「表現できないもの」にします。3 操作前の観察から持ち越した識別子が、いまそのスロットを占めている何かに黙って解決されてしまう、ということが起こりえません。スクリーンショットの世界では、古いスクリーンショットの座標は新しいものと構文上区別がつかず、毎回「何か」に解決されます。

fail-closed な解決: 推測を拒否する​

操作が発火するとき、ref は段階的な手順でライブツリーに対して解決され、その順序はどの証拠が信頼できるかについての判断を表しています。

Guided walkthrough1 of 4
  1. スナップショット時に記録されたパスを、現在のツリーの中でたどります。

設計上のコミットメントは、これらのどれも確信できる一致を生まなかったときに現れます。解決はフェイルクローズします。欠落、古い、曖昧な一致は、最善の推測に解決されるのではなく、拒否されます。STALE_REF、AMBIGUOUS_TARGET です。macOS では、ツリーの順序が足元でずれても要素が黙って取り違えられないよう、実装がネイティブのアクセシビリティオブジェクトを保持しています。

これはピクセル型エージェントの劣化の仕方と正反対です。古い座標は必ず「何か」に当たります。古い ref はエラーを返します。黙った誤操作を、大きな音を立てる失敗と引き換える。それが信頼性の主張のすべてであり、エージェントが監督なしで行うことにはそれが正しい取引です。

What you'll learn
  • 同じ考え方は、リポジトリの部分結果の二面的な扱いにも現れます。スナップショットが走査の途中で時間予算を使い切った場合、走査を捨てずに観察できた分を返しますが、完全性はノードごとに報告します。このフィールドを無視するハーネスは、途中で切れたツリーを自信満々に推論して、ボタンは存在しないと結論づけます。不在を結論する前に完全性を確認してください。

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 つです。

  1. トランスクリプトが機微な成果物になります。 デスクトップセッションのエージェントログは、ターミナルの履歴というより画面録画に近いものです。そのように扱ってください。エージェントが何をアップロードするか を参照してください。
  2. 読み取れるすべての面がインジェクションの面です。 アプリが表示するあらゆるテキストがエージェントの入力になるため、細工されたカレンダー招待、ファイル名、チャットメッセージは、ネットワークリクエストなしのプロンプトインジェクションの経路になります。エージェントが読むウェブ と同じ理屈がデスクトップにも当てはまり、入門としては プロンプトインジェクション があります。マシン全体の入力排他を持ち、ポインターを動かさないエージェントは、構造上、その操作があなたの目に見えないエージェントでもあります。
Watch out
  • ヘッドレスがデフォルトであることは、使いやすさの機能であると同時に、監督上の危険でもあります。エージェントがポインターを動かさずフォーカスも奪わないので、動いていることを示す周囲の合図がありません。無人で実行するなら、補う統制はあなたの注意ではなく、ログと、範囲を絞った対象アプリでなければなりません。一般的なパターンは、自律実行のハードニングのページで扱っています。

このアーキテクチャが正しい選択になるとき​

状況選ぶもの
本物のアクセシビリティ対応を持つネイティブデスクトップアプリ (Finder、Office、ほとんどの Cocoa/WinUI アプリ)ツリー型エージェント。 安定した ref、安価なテキストスナップショット、座標計算なし
ブラウザ内のウェブアプリまずブラウザ専用のハーネス。DOM はブラウザの AX 投影より良いツリーです。computer-use エージェント比較 と 共有ログインのブラウザ を参照
Canvas/WebGL アプリ、ゲーム、独自描画 UI、リモートデスクトップスクリーンショット型エージェント。 読めるアクセシビリティ面がありません
あなたが作業を続けている間にエージェントを動かす必要があるツリー型エージェント、ヘッドレスモード。フォーカスを奪わず、ポインターの奪い合いもなし
監督なしで、繰り返され、変更を伴う操作ツリー型エージェント、そして配信セマンティクスを守る。設計全体がこのケースのために存在します
目視での検証 (「見た目は正しいか?」)ハーネスに関係なくスクリーンショット。ツリーはレイアウトが壊れていることを教えられません
Enter キーまたはスペースキーでカードを裏返します。左右の矢印キーでカードを移動できます。用語を表示しました。
1 / 10

Check yourself

0/5
  1. あなたのハーネスがフォームのフィールドに `type` コマンドを送り、配信状態が 'delivered unverified' のエラーが返ってきました。次の正しい一手はどれですか?
  2. agent-desktop が `@e8` のような素の ref を拒否し、`@s8f3k2p9:e8` を要求するのはなぜですか?
  3. スナップショットが、エージェントの期待したボタンなしで返ってきました。ボタンが存在しないと結論する前に何を確認すべきですか?
  4. アクセシビリティツリー型エージェントに最も向かないタスクはどれですか?
  5. 1 つのブラウザタブに閉じたスクリーンショット型エージェントと比べて、デスクトップのツリー型エージェントの主な新しいセキュリティ上の露出は何ですか?
Key takeaways
  • アクセシビリティツリーは座標への賭けを名前付きの参照に置き換えるので、スクリーンショット型エージェントを壊すレイアウト変更は問題になりません。ただしキャンバス、ゲーム、独自描画 UI には読めるツリーがありません。
  • 配信モデルを盗むこと: 操作は、エラーを返したかではなく、届いた「可能性」があるかで分類する。「届いたかもしれない」には再読込がふさわしく、リトライは決してふさわしくない。
  • ref をスナップショットにスコープするのは手間ではなく型システム。古い識別子のバグを、黙って間違わせるのではなく、表現できなくする。
  • fail-closed な解決こそが信頼性の主張: 古い座標は必ず何かに当たるが、古い ref は STALE_REF を返す。監督のない操作には、大きな音を立てる失敗のほうを選ぶ。
  • Unknown と Absent は別物として扱う。失敗したクエリを否定的な結果と読むエージェントは、何もないものに自信を持って行動する。
  • ヘッドレス動作は、エージェントが作業中であるという周囲の合図を消す。無人で動かすなら、統制はあなたの注意ではなく、ログと範囲を絞った対象アプリである。

出典とさらに読む​

次へ​