1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スマホのChatGPTとローカルAIをつなぐ。MCP接続の先に必要だった、承認・起動・検証の設計

1
Posted at

AIに仕事を渡す壁 スマホとローカルAIをつなぐ実験

先に、結論を3行で書きます。

  • MCPへ接続できることと、承認された作業を別のAIへ渡し、継続実行させることは別です。
  • クラウド側のDesklyを作業と承認の正本、ローカルのmany-ai-cliをセッション実行の所有者に分け、PRを人間にも読める引き継ぎ場所にしました。
  • 候補取得とローカル受け口の一部は実装・合成検証が進みましたが、公開PR読取の権限契約と実運用の一周は未完成です。

接続と業務移譲、二層の役割分担をまとめた図

時間に合わせて、読み方を選べます。

この記事は2026年10月時点の設計・実装途中の記録です。そのまま貼れば自律開発が完成する手順書ではありません。後半の型や擬似コードは説明用であり、稼働中のAPI仕様や実行権限を表しません。

コード修正の依頼が、受け渡しの設計になった

WebメールのスマホUIを改善する作業を、スマホのChatGPTから進めようとしました。GitHubで専用ブランチとDraft PRを作り、レビューや検証をローカルのAIへ渡す想定です。

ところが、変更を作る工程より、担当を移す工程で何度も止まりました。

クラウド側からはローカルの検証実績が見えない。PRに指示を書いても、別のAIが自動で読み始めるわけではない。MCPへ接続しただけでは、受信者の起動も、作業の一意な確保も、結果の返却も決まらない。

そこで、既存の二つの道具を連携させる構想に整理しました。

道具 もともとの役割 今回担わせたい役割
Deskly 案件・作業・承認・次の行動を管理 承認された作業と権限、実行状態の正本
many-ai-cli 複数のAI CLI、承認待ち、セッションを管理 ローカル端末の受信・実行・セッション照合
GitHub PR コード変更・レビューの管理 指示と成果を、別の担当が読み直せる場所
資料保存先 画像・補足資料の保管 原本への参照を共有。権限管理は別途必要

many-ai-cliは自作のローカルWebダッシュボードです。複数AIのセッション管理をすでに持っていたので、新しいCLIランチャーを別に作るより、既存の実行境界へ薄く接続する方針にしました。

今回の重要な変更は、AIを追加することではありません。仕事の状態を決める側と、端末でプロセスを起動する側を、別々の責任として扱うことです。

まず、この記事で混ぜないもの

最初に用語を分けます。同じ「できた」という言葉でまとめると、後の判断が崩れたためです。

接続・作業発見・承認・起動・証拠を分けた全体像

次の図は、この記事で分けて見る五つの段階です。つながることと、仕事が進むことを、別の層として数えます。

確認したこと それだけでは確認していないこと
MCPの認証が通った 全ツールを実行できる、全リポジトリを読める
PRへ指示を保存した 担当AIが読んだ、受け付けた、起動した
作業候補が一覧に出た その候補を自分が実行する権限を得た
claimが成功した ローカルプロセスが起動した
ACKが返った 実装・テスト・レビューが完了した
テストが成功した 現在のPR HEADと同じ版を検証した
ローカルの全検査が成功した GitHubの必須チェックが成功した
Draftブランチに実装がある 通常版・本番に配備されている

この記事でいう「正本」は、その事実を最終的に更新・判定する場所です。データの控えを一切作らない、という意味ではありません。

クラウドと端末に同じ仕事の記録があっても、クラウドの記録が作業権限を決め、端末の記録は起動の照合に使うなら、役割は分けられます。一方、両方が独立に「未着手だから再起動してよい」と判断すると危険です。

MCPは作業キューやジョブランナーの代わりではない

MCPでは、サーバーが公開するツールと、その入力スキーマなどをクライアント側が扱えます。Tools仕様には、ツールの一覧取得、呼び出し、結果の形式が定義され、利用者が呼び出しを拒否できることも重視されています。MCP Tools仕様

ただし、それを接続しただけで、今回ほしい次の機能が自動でそろうわけではありません。

  • 作業を永続的に保持すること
  • 担当を一意に決めること
  • 端末がオフラインでも作業を失わないこと
  • 起動結果が不明なときに、二重起動せず照合すること
  • どの版の結果かを確かめること

ここはアプリケーション側の契約です。

ChatGPTとローカルのClaude Codeなどが同じMCPサーバーを利用できても、同じ会話・承認・実行状態を自動共有しているとは扱いません。各クライアントの認証、許可されたツール、利用する入口を確認します。

また、今回のChatGPT上で使えた連携を、全プラン・全端末で同じように使えるという一般的な案内にはしません。接続方式と提供条件は、実際の環境と公式のカスタムMCP接続案内で確認する対象です。

Deskly MCPとCloudflare MCPは、違う権限の入口

Deskly自体がCloudflare上にあるので、最初は二つのMCPの役割が分かりにくくなりました。

整理すると、通常の仕事の受け渡しに必要なのは、Desklyの業務APIです。WorkerやD1の設定を変更する権限とは別です。

入口 例 通常のローカル開発AIに必要か
Deskly 作業候補の参照、認可された結果の報告 担当範囲に応じて必要
GitHub PRやコミットの参照、許可されたコード変更 仕事ごとに限定
Cloudflare管理 Worker・DBの構成確認や配備 インフラ変更担当に限定

Cloudflare公式MCPは、OAuthで許可した権限の範囲でCloudflare APIを操作する入口です。今回も、接続後はまず読み取りを行い、既存の実行環境と保存先の対応を確認しました。Cloudflare公式MCP

ローカルHubが仕事を読むために、Cloudflare全体の管理権限を持つ必要はありません。Desklyが公開する狭い入口へ接続する形を目指しています。

業務の依頼を読む権限と、依頼を保管するインフラを改造する権限を、同じ接続にまとめないことが出発点です。

PRを引き継ぎ場所にした理由

最初は、チャットで作った長い指示を、ローカルAIへコピーする流れになっていました。これを「PRを読んで」に変えました。

PRには、コードと近い場所へ次の情報を残せます。

  • 今回の目的と対象範囲
  • 参照するブランチ・コミット
  • クラウド側とローカル側の担当分担
  • 調査結果と未決のAPI契約
  • テストの実行範囲と未検証事項
  • マージ・配備を行っていないこと

ただし、PRコメントを、そのまま実行キューの一行にはしません。

コメントは、人間が読める説明と記録です。承認された対象、権限の有効期限、取り消し、実行回数を強制するのは、別の認証済みサービスが担います。

この違いを保たないと、第三者のコメント、編集された説明、レビュー対象に含まれる指示で、ローカルPCが動いてしまいます。

PRの内容は、まず作業のデータとして読むものです。そこに書いてある文章を、自動的にOSコマンドや上位の承認へ昇格させません。

クラウドとローカルで、一つずつ持つ責任

設計を二層に分けました。

Desklyは作業権限、many-ai-cliは端末のセッションを所有する

利用者
  │ 目的・対象・許可する操作を決める
  ▼
Deskly
  │ 正規の依頼・承認・現在の権限・実行記録
  │ 認証された候補取得と、作業種別ごとの許可
  ▼
many-ai-cli Hub
  │ ローカル承認・回復用記録・セッション所有
  ▼
既存のAI CLI
  │ 許可した範囲の作業
  ▼
成果と証拠を、同じ依頼へ返す

この図は最終的に目指す責任分担であり、全経路が本番で動いているという図ではありません。

Desklyは、依頼の正当性を判定する側です。many-ai-cliは、端末の事情を知る側です。動いているセッション、停止中の処理、ローカルの同意、起動結果を扱います。

GitHub PRはコード変更の正本です。Driveなどの資料保存先は画像の原本です。それらをDesklyへ丸ごと複製せず、必要な参照と確認結果を結び付けます。

「唯一の正本」を、すべての情報を一つのDBへ詰めることとは捉えていません。事実ごとに、その事実を決める場所を一つにします。

新しいポーラーを、既存のSessionEngineへ直接入れない

many-ai-cliのローカル担当は、Rust側の既存構成を調べました。

セッションの受付と登録はSessionEngineが所有し、アプリケーションの起動側が関連サービスを構成して、開始・停止・終了待ちを管理しています。今回の受け口は、そのライフサイクル側で依存を渡す候補として整理されました。公開実験README

ここで、定期ポーリングをセッションエンジンの内部へ混ぜたくありません。

セッションエンジンは、どの外部サービスから仕事が来たかに関係なく、セッションの受付と登録を管理する責任を持ちます。Deskly固有の通信失敗、応答サイズ、再試行間隔を入れると、責任が広がります。

外部取得は交換可能なアダプターにし、その結果を狭い型へ変換する。起動が必要になったら、既存の承認を通る起動サービスへ渡す。

止める順番も重要です。Hub終了時に、ポーラーだけが生き残って次の仕事を受け付けてはいけません。バックグラウンド処理の停止と終了待ちは、既存のライフサイクル管理へ参加させる必要があります。

「ローカルだから安全」というのではなく、ローカルにも一つの実行所有者を残すための設計です。

候補取得APIは、起動の許可を返さない

今回、Deskly側のDraft実装には、既存のPC relay向けに承認済み候補を探す読取機能が追加されました。公開側の連携READMEにも、対応する応答形式と制約が記録されています。

その候補には、依頼ID、更新版、実行世代、権限の世代、既存セッションの参照などが含まれます。しかし、PRの対象repo、PR番号、固定HEAD、読むだけの範囲は含まれません。

この差が重要でした。

初期のローカルモックには、都合よくrepoやsha、期限、承認済みフラグが入っていました。しかし、本物の応答から来ない値を、モックと同じだからという理由で補ってはいけません。

// 説明用の型。実装済みAPIへそのまま渡すための型ではありません。
type CandidateHint = {
  requestId: string;
  revision: number;
  generation: number;
  authorizationEpoch: number;
};

// 候補とは別の、認証された承認記録から解決すべき情報です。
type ApprovedTarget = {
  repository: string;
  pullRequest: number;
  headSha: string;
  scope: readonly ['read'];
  expiresAt: string;
};

CandidateHintを受け取っただけで、ApprovedTargetが存在することにはなりません。特に、自然言語のPRコメントから対象を推測し、read権限を自分で補う処理は入れません。

一覧を返す側には、読み取りの目的があります。いま実行してよいことを最終確認する側には、状態を変更する目的があります。同じ応答で曖昧に扱わない方が、境界を試験しやすくなります。

承認時に固定するのは、PR番号だけでは足りない

PR番号は変わらなくても、その中身は変わります。

「PRを読んでよい」という承認の後に、新しいコミットが追加された場合、その新しい内容まで同じ承認で読んでよいかを決める必要があります。今回の最初の実験では、曖昧に広げず、承認したHEADを固定する方向になりました。

説明用に、承認対象を組にすると次のようになります。

依頼の識別子
+ 更新版
+ 権限の世代
+ リポジトリ
+ PR番号
+ 承認したHEAD SHA
+ 許可範囲(readのみ)
+ 有効期限

SHAだけを比べるわけでもありません。

同じ内容に対して、依頼が取り消されたかもしれません。端末の権限が変わったかもしれません。対象は同じでも期限を過ぎたかもしれません。

そこで、対象の同一性と、現在の権限の有効性を別々に確かめます。

識別・版 確認したいこと
request ID どの依頼の続きか
revision 読んだ後に台帳が更新されていないか
generation 退役した試行や過去の担当の返事ではないか
authorization epoch 権限変更前の許可を使っていないか
HEAD SHA 承認したコードと同じ対象か
attempt ID どの起動試行の証拠か

フィールドを増やすこと自体が目的ではありません。異なる種類の「古さ」を、一つの番号で代用しないために分けています。

初期スコープを公開PRの読取だけにした

ローカル側との検討後、最初の実験では次の範囲に絞る方針になりました。

  • 対象は公開リポジトリのPR
  • GitHubへの読取は認証なしで行う
  • 非公開・取得不能なら、見えない状態として止まる
  • 許可する作業は読むだけ
  • 既存のSlack worker向けhandoffとは別の作業種別にする
  • この作業のclaimとhandoffはmany-ai-cli Hubが所有する

これは、将来も非公開repoを扱わないという意味ではありません。最初の契約で扱う秘密と権限を減らす選択です。

重要なのは、「この実験で確認するもの」と「最初の実案件」を同一にしないことです。出発点だったWebメール改善が非公開の環境にあるなら、そのまま最初の無認証PR読取デモに使うことはできません。

本番案件を公開へ変更して実験を成立させるのではなく、公開して問題のない専用の対象で、まず受け渡しを確かめます。

この範囲でさえ、まだ実環境の一周は完了していません。公開PR読取の作業種別に対応するクラウド側の契約は、執筆時点では提案段階です。

既存PC relayを、そのまま汎用PR readerにしなかった

既存コードには、PCへの受け渡し、一回限りの権限、不明状態の記録がありました。

ところが、実装を追うと、その受信処理は、特定の配送形式と有限のpermission callbackを使う経路でした。今回ほしいPR読取作業を、そのまま表すものではありませんでした。

ここで二つの選択肢がありました。

既存の受信処理を起動し、さらにHub側でも別のAIセッションを作る。あるいは、責務を分け直し、PR読取の仕事はHubの正規経路だけで扱う。

後者に寄せました。

再利用するのは、認証された作業、現在の権限、一回限りのauthority、結果不明時の停止といった基盤です。Slack向けのwireをPR読取の命令と解釈したり、Node受信側とRust Hubの両方が同じ作業を起動したりはしません。

コードの再利用率より、実行の所有者が一意であることを優先した形です。

なお、初期の設計文書やPRコメントには「Node receiverの再利用を優先する」という案が残ります。これはその後の照合で修正された検討履歴です。最新の決定と混ぜないようにする必要があります。

claim、handoff、ACK、実行完了は、それぞれ別の事実

この種のシステムでは、似た言葉が並びます。今回も、初期案のclaim/lease/ackを、すでに存在するAPIであるかのように扱いかけました。

でも、名前より重要なのは、何が確定する操作なのかです。

操作・記録 確定させたいこと それだけで確定しないこと
discovery 現在見える候補 実行権限、端末の起動
claim 正規側の規則で候補を確保 OSプロセスの存在
handoff 許可された仕事を指定の受け手へ渡す 作業の成功
ACK 特定の依頼を受領した コード変更、テスト、レビュー
start証拠 対象のローカル実行を開始した 期待した成果が完成した
result証拠 実行結果と対象の対応 マージ許可、本番配備許可

名前は実装によって異なります。この表は、現在のDesklyがこのすべてを同一のAPIとして提供している、という説明ではありません。

たとえば、MCPのpreviewが返す確認用データは、何らかの変更を適用するための前提です。それを、ローカルHubで利用者が確認したことの証拠へ勝手に読み替えません。

同じ「承認」に見えても、承認した対象と権限の境界が違います。

一回限りの権限だけで、プロセス起動のexactly-onceは保証できない

サーバー上で、一回しか消費できない起動権限を作る。これは必要な部品です。

ただし、それだけで「AIプロセスが必ず一度だけ動く」とは言えません。DBのトランザクションと、別の端末にあるOSプロセスの起動は、同じ原子的な操作ではないからです。

次の順序を考えます。

サーバーで権限を消費
  ↓
ローカルへ成功応答
  ↓
ローカルで起動意図を記録
  ↓
AIプロセスを起動
  ↓
起動結果を記録

どの矢印の間でも、通信断やクラッシュが起き得ます。

権限だけ消費して起動しなかった場合と、起動したが記録が残らなかった場合では、復旧時にしてよいことが違います。

起動していないと思って再実行すると、後者では二重起動になります。起動したと思って完了にすると、前者では仕事が消えます。

ここを、クライアントのメモリ上のSetで防ぐことはできません。プロセスが終了すれば、その集合自体が失われます。

今回採っているのは、万能なexactly-onceを宣言することではなく、分からないときに再起動しないことと、後から照合できる情報を残すことです。

権限消費とプロセス起動の間には結果不明の窓がある

再起動後のjournalは、再実行の許可証ではない

ローカルの回復用journalには、途中で止まった試行を残します。

ここに記録したいのは、依頼と実行世代、試行ID、対象、現在までに確認した段階です。秘密の鍵、PR本文、配送wire全文を保存する場所にはしません。

実装されたローカル受け口の説明では、SQLiteへ状態を置き、再度開いたときに処理中だった行をheldへ移す設計になっています。これは自動再実行のためではなく、不確実な仕事を照合待ちにするためです。many-ai-cliのjournal説明

heldを作るだけでは運用は完成しません。解除の手順、誰が証拠を見て判断するか、誤って消さないかも必要です。執筆時点では、その照合と片付けの運用は残っています。

事故を避けるために止める処理と、止まったまま放置しないための処理は、両方必要です。

中断点 その場で言えること 自動でしてはいけないこと
claim送信前 送信していないと確認できる場合がある 記録なしだけで未送信と決める
claim送信後、応答前 サーバー側の結果が不明 同じ作業を無条件に再確保する
handoff応答の復号・形式検査失敗 応答を使えない サーバー未処理と判断する
launch直後、記録前 プロセスがいる可能性 もう一つ起動する
ACK消失 受領の可視性が失われた 作業が始まっていないと決める
journal欠損 手元の証拠が足りない きれいな初回として始める

この表は、実装すべき試験条件の整理でもあります。すべてを実機クラッシュ注入で検証した、という意味ではありません。

300秒のポーリングは、通信よりライフサイクルの問題

初期案は300秒ごとの確認です。この値を選んだから安全になるわけではありません。

重要なのは、一回の確認が終わらないうちに次が重なるか、端末停止時にどうなるか、起動直後の集中をどう抑えるか、停止要求が来たら終了できるかです。

定期実行へ進む前に、次を決める必要があります。

  • 接続できないときは間隔を広げるか
  • サーバーの過負荷応答をどう扱うか
  • 同じ端末内の同時pollを禁止するか
  • ページング途中の結果で、全件を見たと思い込まないか
  • プロセス終了時に、取得・承認・起動の受付をどう閉じるか
  • 取り消し後の古い候補を、どこで再検証するか

最初からWebSocketやWebhookが必要かも、この段階で考えます。外向きのポーリングならPCへの着信口を新設しない形を取りやすいですが、それで認証や起動制御まで解決するわけではありません。

また、今回のRustモックで300秒の既定値を確認しても、実時間で5分おきに動くスケジューラを検証したことにはなりません。値のテストと、稼働し続ける処理のテストを分けます。

リトライできる通信と、照合が必要な操作

「ネットワークエラーならリトライ」は、操作の種類を分けないと危険です。

対象一覧を読む処理と、一回限りの権限を消費する処理は同じではありません。

読み取りでも、ページングや取得時刻の差を考える必要はあります。ただ、実行権限を消費したか分からない状態での再送は、別の種類の不確実性を生みます。

今回のローカルMCPクライアントでは、接続に失敗して未配送と判断する場合と、届いた可能性がある不明な結果を区別する方向が記録されています。さらに、成功応答に見えても、形式を受け付けられなければ、変更操作では結果不明として扱います。

「パースできなかった」から「サーバーは何もしていない」へ飛ばないことです。

擬似コードで表すと、次の違いになります。

// 説明用。実際のMCPクライアント実装ではありません。
const outcome = await consumeOnce(request);

switch (outcome.kind) {
  case 'issued':
    // 有効な応答でも、ローカル起動前の再確認とjournalが必要。
    return prepareLocalAdmission(outcome);
  case 'spent':
    return holdForReconciliation('already-consumed');
  case 'denied':
    return recordRefusal(outcome.reason);
  case 'unknown':
    // ここでconsumeOnceを呼び直さない。
    return holdForReconciliation('server-result-unknown');
}

返答の再取得、読取の再試行、新しい試行の認可は、別の操作として決めます。「retry」という一語でまとめない方が、レビューしやすくなります。

Workers AIとWorkflowsは、後から必要な場所へ置く

クラウド側には、Workers AIで文章を分類し、Workflowsで待機と再試行を管理する案があります。

ただ、今回の最初の接続を成立させる前提にはしていません。

承認の有無や、対象SHAの一致や、権限世代の新しさは、LLMの判断で揺らしたくありません。通常のコードで判定する部分です。

一方、依頼文から「UIレビュー」「調査」「実装」などの候補を作る処理には、モデルを利用する余地があります。その出力は提案であり、認可そのものではありません。

Workflowsについても同様です。Cloudflareの公式資料では、状態を保持する複数ステップの処理や再試行を扱える一方、ステップ内の呼び出しの冪等性を考える必要があると説明されています。Workflows概要 / Rules of Workflows

ワークフローエンジンの永続化が、別マシンのプロセス起動を原子的にするわけではありません。

既存のDeskly台帳とWorkflows内部の状態のどちらが正本かも、導入時に決める必要があります。両方が独立に担当を決める構成にはしません。

クライアントの受信処理にも、失敗時の仕様が要る

ポーリングアダプターは、JSONを読んで関数を呼ぶだけにはしませんでした。

ローカル側のDraft実装には、応答サイズの制限、リダイレクトを追わないこと、期待する形以外のフィールドを拒否すること、鍵をデバッグ表示で伏せることなどが記録されています。

これらは、入力を狭めるための部品です。ただし、「型チェックがあるから安全」「serdeで読んだから承認済み」とは言えません。

型が示すのは形式です。そのデータが現在の権限で取得されたものか、承認記録に結び付くか、失効していないかは別に確認します。

MCPのセキュリティ文書も、トークンの扱い、スコープの最小化、セッションと認証の混同などを区別しています。プロトコルへ載せたことで、アプリケーション側の認可が不要になるわけではありません。MCP Security Best Practices

公開PRを読む場合でも、読む内容は信頼できる命令とは扱いません。外部資料の指示で権限が増えないことは、AIへ渡すプロンプトだけでなく、実際に利用可能なツールや実行経路でも保つ必要があります。

AI CLIの「読むだけ」モードは、周囲の設計を不要にしない

ローカル側には、読むだけの作業を既存CLIへ渡すため、プロバイダーごとに制限した起動設定を選ぶ実装案があります。

ここでも、モード名を万能な安全保証とは扱いません。

CLIごとに権限モデルは異なり、更新もあります。実際に有効な設定、利用可能なツール、外部接続、ローカルの作業ディレクトリ、利用者の確認経路までが境界です。

また、通常のmany-ai-cliの子セッションやrelayで使う権限設定と、今回の公開PR読取用の狭い設定を混ぜてはいけません。既存のREADMEに自走向けの設定があっても、それを今回の新しい入口へ自動的に流用しません。

初期実験は、明示された対象を読んで要約することに限定します。コードを書いていないから、マージや配備まで許可されたことにはなりません。

ローカルCIとGitHub Actionsの結果を、別々に扱う

この実験では、GitHub Actionsのスキップや起動前の失敗と、ローカルで済んでいたフルCI/CDの実績が混同されかけました。

ジョブが走らない原因は、条件式、イベント種別、権限、利用枠などを確認する必要があります。Draft PRだから通常ジョブを走らせない設定もありました。GitHub公式にも、jobs.<job_id>.ifで実行を制御する方法が説明されています。ジョブ実行条件

また、利用枠や請求の境界はリポジトリ所有者側にあります。一つのアカウントの予算設定を変えたことから、別の所有者のジョブも使えると推定しません。GitHub Actionsの請求

しかし、この記事で重要なのは請求設定の話そのものではありません。

ローカルで既に実行した検証を、クラウド側が知らなかったため、不要な再実行の話へ進んでしまいました。

解決したいのは「すべての結果をGitHub Actionsで再現すること」ではなく、どこで実行された結果でも、対象と検証範囲を正しく結び付けることです。

証拠の種類 保存する情報 言い過ぎないこと
利用者の報告 誰から、何が済んだと聞いたか 現HEADの全検査を独立確認済みとしない
ローカルログ 対象SHA、コマンド、環境、終了結果 異なる版の結果を無条件に転用しない
Actions実行 run、対象SHA、job、stepの結果 skippedを実行成功と呼ばない
ブラウザ確認 画面幅、ブラウザ、操作、観測 CSS文字列検査と同一視しない
レビュー 対象版、指摘、残件 テスト成功で代替しない

GitHubの必須チェックを、ローカルの成功申告だけで成功へ偽装することはしません。ブランチ保護の扱いも別の判断です。

済んだ検証を正しい版と範囲へ結び付ける

検証証拠を渡すための、最小のまとまり

外部の検証結果を引き継ぐとき、必要以上に巨大な証明基盤を作りたいわけではありません。最初は、既存ログとPRのコメントを、対象コミットに結び付けるだけでも役に立ちます。

説明用の形式は、次のようなものです。

# 説明用。実装済みの登録APIや自動マージ入力ではありません。
repository: <owner/repository>
pull_request: <number>
tested_sha: <full-commit-sha>
executor: <authenticated-executor-reference>
environment: <os-and-tool-versions>
checks:
  - command: <actually-executed-command>
    outcome: pass
    exit_code: 0
    log_ref: <private-or-redacted-artifact-reference>
not_checked:
  - <explicitly-unverified-scope>

鍵や実メールを含む生ログを、そのまま公開コメントへ貼る必要はありません。必要な範囲の抜粋や、安全な保管先への参照を使います。

ここでハッシュがあれば、ある内容と同じかを照合できます。ただし、ハッシュ単体では、誰が実行したかや、ログが真実かまでは証明できません。受け取る経路と実行者の認証は別です。

署名付きの証拠を採用するなら、その署名鍵をどう管理し、誰のどの実行を信頼するかも必要です。今回は、そのような信頼済み証拠取込の全体を実装済みとはしていません。

モックの入力を、本物のAPIの仕様にしない

初期モックは、設計を考えるために便利です。

ただ、モックが便利すぎると、まだ作っていない契約が、すでにあるものとして進みます。

今回の具体例では、候補取得APIにrepoやHEADの情報がないのに、ローカルモックのWorkにはそれらがありました。モックだけを見れば、許可済みのPRを安全に読むように見えます。

実APIの出力と並べることで、この差が分かりました。

テストが通ることは、そのテストの前提で動いたということです。前提に入れた値が現実にどこから来るかは、別に追う必要があります。

このため、契約試験では、正常例を増やす前に「来ないフィールドを補っていないか」「来た情報だけで権限を作っていないか」を見ています。

本物の候補応答
  ↓ 閉じた形式として読み取る
候補の参照
  ↓ 別の認証済み承認記録と照合
固定された対象と許可範囲
  ↓ 現在の権限・対象・ローカル同意を再確認
正規の起動受付へ

この中間の一段が欠けているなら、モックの成功を理由に実接続へ進めません。

今回確認できた状態を、テスト単位で分ける

執筆時点の記録を、次のように扱っています。

範囲 根拠の種類 現在の読み方
初期の受け渡しモデル 独立した合成テスト、軽量Actions 実配送や永続性の保証ではない
Desklyの既存PC relay向け候補取得 対象ビルド・9件の合成試験の記録 公開PR読取の起動許可ではない
Rustの初期mockと型付きdiscovery実験 3件と7件の合成試験、Actions記録 Hubへ組み込まれた定期実行ではない
Rust本体のintake・鍵の読取処理 ローカル担当から24件成功の報告 実鍵・実クラウド・実AIを使う一周ではない
公開PR読取専用の作業種別 契約案 未実装のツール名・JSONを実APIと呼ばない
Hubへのserve組み込み・承認UI・結果返却 残作業として記録 自動起動の完成を宣言しない

公開側の実験READMEと、ローカル担当の24件成功報告を照合しています。

これらの件数は、試験対象・版・実行環境が異なります。合計して「全体で何件通ったので完成率が高い」とは扱いません。また、この記事の制作中にフルCIを再実行したわけではありません。

残っているのは「つなぐだけ」ではない

部品が増えると、最後は接続だけに見えます。しかし、その接続部分で権限の意味が変わるなら、そこが主要な実装です。

現在残っているのは、たとえば次の事項です。

  1. 公開PRの対象・版・read範囲・期限を、承認記録として保存し取得できる契約
  2. その作業種別専用のclaim・handoffと、一回限りの権限の整合
  3. Node側の従来配送と混同しないルーティング
  4. Hubの正規ライフサイクルへの組み込みと、利用者の確認UI
  5. 結果・拒否・保留の返却経路
  6. 再起動や通信断を含む実環境での受入れ

「まだAPIがない」という初期の説明からは前進しました。既存の配送に対する候補取得はあります。しかし、候補の一覧だけで今回の公開PR読取を許可できないため、作業の意味を表す契約がさらに必要になりました。

この変化を「足りないものが増えた」と見ることもできます。ただ、ローカル側が勝手に補っていた前提を見つけ、実装すべき場所を明確にした、とも言えます。

最初の受入れは、公開PRを一つ読むところまで

最初の実環境のデモでは、対象を小さくします。

公開して問題のないPRを用意し、利用者がそのHEADと読むだけの範囲を承認する。ローカル側は候補を取得し、承認された対象を確認し、既存Hubの正規経路で一つの作業を開始する。結果には、読んだ版と、何を確認したかを残す。

このとき、成功例だけでは足りません。

試験 期待する結果
承認したHEADからPRが更新された 保留し、勝手に新しい版へ追従しない
権限を取り消してから起動する 起動しない
同じ候補を二度取得した 新しい実行を増やさない
確保結果の応答が失われた 未実行と決めず、照合待ち
起動直後にHubを終了する 再起動後に無条件で再実行しない
非公開PRを指定する 認証を拡張せず、見えないとして止まる
PR本文が権限拡大を要求する 作業資料として扱い、許可範囲を変えない
結果報告が二重に届く 同じ試行へ結び付け、新しい完了を作らない

ここまで受け入れてから、コード変更やテスト実行の作業種別を考えます。読み取り用の許可を、そのまま編集・マージ・配備へ使い回しません。

「スマホ完結」は、スマホで全部実行する意味ではない

今回、スマホは操作の入口です。

スマホ上でRustをコンパイルしたいわけでも、全部のAIをスマホの中で動かしたいわけでもありません。クラウドで保持する仕事はクラウドへ、ローカル環境で実行する仕事はローカルへ置きます。

人間が端末間で長い指示をコピーしなくて済むこと。どの仕事が自分の確認待ちなのか分かること。途中で会話を切り替えても、PRと台帳から続きを確認できること。

この三つができれば、実行環境が分かれていても、操作としてはかなり一つに近づきます。

そのため、クラウド側の会話を「常駐の指揮者」と呼ぶ場合も注意しています。この実験のチャットが、閉じた後まで継続して監督していたわけではありません。持続する仕事の管理は、これから明示的なランナーやサービスへ渡す部分です。

実装量より、取り違えない境界を先に確認する

今回の途中経過から、次の順番が扱いやすいと感じています。

まず、既存コードを読み、セッションと承認の所有者を特定する。次に、外部から来るデータと、権限を与えるデータを分ける。そのうえで、候補取得から結果までの間に、何が確定し、何がまだ不明なのかを列挙する。

それから、正常例と同じくらい、古い版・取り消し・応答消失・クラッシュを試す。

LLMを選ぶことや、短いポーリング間隔にすることは、その後でもできます。

反対に、最初から自然言語の指示をキューへ積み、エージェントを自由に起動できる形にすると、あとから「どの許可で動いたのか」を戻すのが難しくなります。

自律実行を増やすほど、人間の確認回数を減らしたくなります。それでも、承認の対象を狭くし、実行の証拠を結び付ける作業までなくすわけにはいきません。

あわせて読みたい

公開資料と、この先

many-ai-cliのREADMEには、現在のローカルHubの利用方法があります。今回の連携はDraft PRで進めているもので、通常版の機能とは区別してください。

この記事で確認した公開側の参照は、主に次のものです。

開発を止めずに、読取範囲と実行範囲を分ける。そのために、最初の自動作業を「公開PRを一つ読む」に絞りました。

まだ完成していませんが、失敗したら何を直すべきかが、接続設定の話だけではなくなりました。発見なのか、承認なのか、起動なのか、結果返却なのか。どの境界で止まったかを説明できる形に近づいています。

MCPで道具をつなぐところから、許可された仕事を最後まで取り違えないところへ。 今回いちばん大きかったのは、この設計上の移動でした。


※ ヘッダー画像とインフォグラフィックは AI(画像生成)で作成しています。

書いた人: ishizakahiroshi
群馬の北部で、保護猫2匹と暮らす、在宅エンジニア(何でも屋)
https://ishizakahiroshi.com/
https://github.com/ishizakahiroshi
X(業務委託・各種相談はこちら):
https://x.com/ishizakahiroshi

バックエンド・インフラ・AI連携まわりで、業務委託のご相談を受け付けています。フルリモートです。スポットや週2〜3時間からでも歓迎で、いろんな案件に携われたらうれしいです。こんな相談、歓迎です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?