ウォレットにアクセスできる AI エージェントは強力そうです。

しかし同時に、リスクを伴う可能性もあります。

言語モデルはリクエストを誤解したり、誤ったツールを選択したり、行動を繰り返したり、そもそも許可されるべきではなかった目標を自信満々に追求したりする可能性があります。無制限の署名権限を与えると、それらのミスがトランザクションに変わります。

Arc 14 は、それよりも有用なものを構築することを目指しました。

Day 92–98 にかけて、私たちはエージェントに Solana データへのアクセス権を与え、その後、徐々にツール、ウォレット、MCP サーバー、ポリシーエンジン、そして自律ワークフローを導入しました。

どの段階でも、同じ境界が守られました:

モデルは「何をしたいか」を判断できました。

コードは「何を許可されるか」を決定しました。

エージェントは読み取り専用のツールから始めました

最初の課題では、賭け金を低く抑えました。

Claude と Solana devnet ツールのセットを使った小さなエージェントループを構築しました。このエージェントは、次のような自然言語の質問に答えられました:

  • このウォレットにはどれだけの SOL が入っていますか?
  • このアカウントの所有者は誰ですか?
  • このアカウントは実行可能ですか?
  • どれだけのデータを保存していますか?

モデルは Solana に直接接続することはありませんでした。

任意の RPC リクエストを構築したり、無制限のネットワークアクセスを受け取ったりすることもありませんでした。アプリケーションはツールの小さなセットを定義し、それぞれが何をするかを説明し、どのように実行するかを決めました。

Claude はこれらの説明を検査して呼び出すツールを選択できましたが、アプリケーション全体は引き続き制御を保持していました。

結果は依然として会話的なものでした。

平易な英語で質問しました。

エージェントは残高ツールを選択しました。

アプリケーションは RPC エンドポイントを呼び出しました。

結果はモデルに戻されました。

モデルはそれを説明しました。

このやりとりの背後には、シンプルなループがありました:

  1. ユーザーがエージェントに目標または質問を提示します。
  2. モデルがツールが必要かどうかを判断します。
  3. アプリケーションがツールコールを検証し、実行します。
  4. 結果がモデルに戻されます。
  5. モデルが回答できるまでループが続きます。

モデルは判断を提供しました。

アプリケーションは機能を提供しました。

ツール定義がエージェントのできることを形作りました

各ツールには 4 つの重要な部分がありました:

  • モデルがいつ使用すべきかを示す説明。
  • モデルがリクエストできる内容を定義する入力スキーマ。
  • 実際に何が起こるかを決定する実装。
  • 次のステップのための根拠をモデルに提供する出力。

これらの詳細は重要でした。

曖昧な説明は、モデルが誤ったツールを選択する原因となり得ました。

緩いスキーマは、不正な形式や曖昧なリクエストを許容し得ました。

すべての引数を信頼する実装は、モデルのミスをシステムの問題に変え得ました。

読み取り専用ツールでさえ、境界が必要でした。

残高ツールは有効な Solana アドレスを受け入れるべきです。

アカウント検査ツールは有用な構造化メタデータを返すべきです。

エラーは、開発者から元の詳細を隠すことなく、モデルが推論できる形式で返されるべきです。

プロンプトは、エージェントにどのように振る舞ってほしいかを説明しました。

ツール定義は、アプリケーションに実際に何を依頼できるかを確立しました。

そしてエージェントにウォレットを与えました

Day 93 では、賭け金が上がりました。

エージェントは独自の devnet ウォレットと 2 つの新しい能力を受け取りました:

  • 残高を確認する。
  • SOL を送信する。

これにより、読み取り専用のアシスタントから、オンチェーン状態を変更できるシステムへと変わりました。

ウォレットはモデルではなくアプリケーションに属していました。

秘密鍵は、トランザクションを構築・署名するプロセスの内部に留まりました。プロンプトに挿入されたり、ツール結果で返されたり、モデルコンテキストを通じて公開されたりすることはありませんでした。

モデルは鍵を必要としませんでした。

提案された受取人と金額を受け入れるツールが必要なだけでした。

アプリケーションは、その後、トランザクションを構築・署名するかどうかを判断できました。

この分離は重要です。なぜなら、モデルコンテキストは秘密を安全に保管する場所ではないからです。

プロンプト、ツールコール、ログ、レスポンスは、保存・検査されたり、コンポーネント間で渡されたりする可能性があります。そこに置かれた秘密鍵は、誤って漏洩したり、生成された出力に繰り返し現れたりする可能性があります。

署名レイヤーの内部に鍵を保持することは、モデルが実行に必要な認証情報を持たずにアクションをリクエストできることを意味しました。

エージェントは、あるアドレスに 0.05 SOL を送信するよう依頼できました。

署名済みトランザクションにそのリクエストを変えることができるのは、アプリケーションだけでした。

送金上限はコード内に存在しました

エージェントが送信できる SOL の量を制限するために、プロンプトに頼りませんでした。

送金ツールはハードキャップを強制しました。

提案された金額がその上限を超えた場合、アプリケーションはトランザクションの構築前にリクエストを拒否しました。

0.1 SOL を超えて送信しないようモデルに指示することはできました。おそらくほとんどの場合、その指示に従うでしょう。

しかし、プロンプトは誤解されたり、矛盾する指示で上書きされたり、一貫性なく適用されたりする可能性があります。

コードレベルのチェックは異なる振る舞いをします。

金額が高すぎる場合、送金は実行されません。

リクエストがどれほど説得力があるかは関係ありません。

モデルがどれほど自信を持って聞こえるかは関係ありません。

提案されたアクションが、より広い目標をサポートしているように見えるかは関係ありません。

署名レイヤーは拒否します。

お金、権限、不可逆的なアクションに関わるルールは、そこに属するべきです。

1 つの送金上限では不十分でした

送金ごとの上限は、1 つの大きなトランザクションから保護します。

しかし、エージェントが多くの小さなトランザクションを行うのを止めることはできません。

送金ごとに 0.1 SOL に制限されたエージェントでも、10 回の送金を試み、合計で 1 SOL を費やす可能性があります。

これが、送金ツール内の検証が始まりに過ぎない理由を示しました。

より広いシステムは、次のことも決定する必要がありました:

  • どの受取人が許可されるか?
  • 1 回のトランザクションでいくら送信できるか?
  • セッション全体でいくら費やすことができるか?
  • エージェントは何回のツールコールを実行できるか?
  • ループはどれだけ続くことができるか?
  • トランザクションの失敗後には何が起こるべきか?
  • どのアクションをログに記録すべきか?

これらの決定は、プロンプトやツールの実装に散在させるのではなく、専用のポリシーレイヤーに属するべきでした。

MCP がツールを再利用可能にしました

Day 94 では、Solana ツールを Model Context Protocol サーバーに移行しました。

それまでは、ツールは 1 つのアプリケーション内に存在していました。

それは機能しましたが、機能を特定のエージェント実装に結びつけることになりました。

MCP サーバーはそれらを再利用可能にしました。

次のようなタスクのためのツールを公開しました:

  • ウォレットの残高を読み取る。
  • オンチェーンのボールト状態を検査する。
  • 保護されたプログラム命令を呼び出す。

互換性のある AI クライアントは、同じプロトコルを通じてこれらのツールを発見して使用できました。

サーバーは利用可能なものを説明し、構造化されたリクエストを受け入れ、構造化された結果を返しました。

これは Arc 13 で IDL が果たした役割に似ていました。

IDL は Solana プログラムをソフトウェアに発見可能にしました。

MCP は、エージェント向け機能を AI クライアントに発見可能にしました。

有用な点は、単に別のクライアントがツールを呼び出せるようになったことだけではありませんでした。

ウォレット鍵と安全ルールはサーバー上に留まりました。

新しいクライアントは、どちらにも直接アクセスできませんでした。

すべてのクライアントと同じ、制御されたインターフェースを受け取りました。

これにより、Solana 統合を再構築したり、権限を新しいクライアントに移したりすることなく、モデル、デスクトップクライアント、またはエージェントフレームワークを変更できました。

サーバーは信頼境界のままでした

MCP はツールを発見・再利用しやすくしました。

しかし、すべてのクライアントを信頼できるようにしたわけではありません。

互換性のあるクライアントはツールコールをリクエストできましたが、サーバーは依然としてそれを検証する必要がありました。

クライアントの設定が誤っている可能性があります。

モデルが誤ったツールを選択する可能性があります。

ユーザーが意図された範囲外のアクションをリクエストする可能性があります。

リクエストに無効なアドレスや過大な金額が含まれている可能性があります。

サーバーは引き続き以下の責任を負いました:

  • ツール入力の検証。
  • ポリシーチェックの適用。
  • 秘密鍵の保護。
  • トランザクションの構築と署名。
  • 有用なエラーの返却。
  • 発生したことの記録。

ツール結果も信頼できませんでした。

オンチェーンのテキストやメタデータは、他の人によって書き込まれる可能性があります。モデルに返されるメモ、トークン名、またはデコードされたアカウントフィールドには、その振る舞いに影響を与えることを意図した指示が含まれている可能性があります。

アプリケーションは、それがツールから返ってきたという理由だけで、その内容を信頼できるガイダンスとして扱うことはできませんでした。

敵対的なデータがモデルの推論を変えたとしても、同じポリシールールが、何に署名できるかを制御し続けました。毒されたレスポンスは、モデルが提案する内容に影響を与える可能性はありますが、許可リストを拡大したり、支出上限を引き上げたり、セッションバジェットをリセットしたりすることはできませんでした。

これは Arc 12 からのセキュリティの教訓を引き継ぎました。

信頼できない入力は、トランザクションアカウントに限定されません。エージェントが読み取り、コンテキストに取り込むデータも含まれます。

デフォルト拒否のポリシーエンジンを構築しました

Day 95 では、シンプルな送金上限を適切なポリシーレイヤーに変えました。

提案された送金は、すべてのルールを通過しない限り拒否されました。

ポリシーは以下をチェックしました:

  • 受取人が有効な Solana アドレスかどうか。
  • 受取人が許可リストに載っているかどうか。
  • 金額が送金ごとの上限を下回っているかどうか。
  • セッション全体の支出が予算内に収まっているかどうか。

アプリケーションは、モデルコンテキスト外で累積支出を追跡しました。

エージェントは、自身の残りの予算を記憶したり、リセットしたり、再解釈したりすることはできませんでした。ポリシーエンジンは、アプリケーションが所有するセッション状態からそれを計算しました。

これはデフォルト拒否の設計でした。

システムは、送金を拒否する理由を探しませんでした。

承認のために、送金がすべての条件を満たすことを要求しました。

いずれかのチェックが失敗した場合、トランザクションは署名されませんでした。

許可リストのエントリが欠落している場合、拒否されました。

不正な形式のアドレスである場合、拒否されました。

金額が上限を超えている場合、拒否されました。

セッションバジェットが枯渇している場合、拒否されました。

予期しない入力は、実行にまで到達しませんでした。

停止しました。

意図と許可は別々の問題でした

モデルの仕事は、目標について推論することでした。

ポリシーエンジンの仕事は、提案されたアクションがルールに適合するかどうかを決定することでした。

エージェントは、ウォレットが 0.4 SOL 不足していることを正しく計算するかもしれません。

それが、0.4 SOL を送金する許可を持っていることを意味するわけではありません。

金額が送金ごとの上限を超えている可能性があります。

宛先が許可リストにない可能性があります。

残りのセッションバジェットが 0.2 SOL しかない可能性があります。

したがって、モデルの推論が正しくても、拒否という正しい結果になる可能性があります。

それはシステムの重要な特性でした。

その安全性は、モデルが毎回正しいセキュリティ判断を下すことに依存していませんでした。

モデルは、誤ったり、過信したり、ユーザーに操作されたり、敵対的なツール出力に影響を受けたりする可能性があります。

ポリシーは、同じ提案されたアクションとセッション状態に対して、同じ結果を生成するべきです。

モデルの振る舞いは可変的でした。

ポリシーの振る舞いは、予測可能であることを意図していました。

拒否は通常のシステムの振る舞いでした

拒否されたアクションは、必ずしもエラーではありませんでした。

時には、システムが設計通りに機能していることを意味しました。

エージェントが許可リスト外のアドレスに資金を送金することを提案した場合、ポリシーエンジンはリクエストを拒否し、理由を返しました。

その後、エージェントは次に何をするかを判断できました。

ユーザーに、アクションが許可されていないことを伝えられるかもしれません。

別の受取人を尋ねられるかもしれません。

より少ない送金を提案できるかもしれません。

現在の制限の下では目標を達成できないと結論づけるかもしれません。

これは、すべての拒否をワークフローをクラッシュさせる例外として扱うよりも優れていました。

ポリシー結果は、エージェントが推論できる情報となりました。

承認されたアクションは署名に進むことができました。

拒否されたアクションは、構造化された説明とともに返されました。

エージェントは、その決定に適応できましたが、それをオーバーライドすることはできませんでした。

自律ワークフローがすべてをまとめました

Day 96 では、エージェントループ、ウォレットツール、ポリシーエンジンを、目標駆動型のワークフローで組み合わせました。

例の目標は、貯蓄用ウォレットが指定された金額以上の SOL を保持することを確認することでした。

エージェントは以下を行う必要がありました:

  1. 貯蓄用ウォレットの残高を確認する。
  2. それを目標と比較する。
  3. 不足分を計算する。
  4. 資金調達用ウォレットを確認する。
  5. 送金を提案する。
  6. 提案をポリシーに通す。
  7. 承認されたトランザクションを送信する。
  8. 新しい残高を確認する。
  9. 何が起こったかを記録する。

これは、1 回のツールコールとその後の回答以上のものでした。

エージェントは、現在の状態を検査し、アクションが必要かどうかを判断し、制限内で行動し、その後結果を確認する必要がありました。

最後の検証は重要でした。

トランザクション署名は、トランザクションが送信されたことを示します。

しかし、それだけでは意図された目標が達成されたことを証明しません。

エージェントはオンチェーン状態を再度読み取り、それを目標と比較しました。

これでループが閉じました:

観察する。

推論する。

行動する。

検証する。

通常、制約あり、実行不可能なシナリオは異なる振る舞いをしました

ワークフローをいくつかの条件下でテストしました。

通常の場合、目標のウォレットが最低残高を下回っており、資金調達用ウォレットに十分な SOL があり、必要な送金がポリシーを通過しました。

エージェントは不足分を計算し、送金をリクエストし、新しい残高を検証しました。

制約のある場合、目標は送金上限と残りのセッションバジェット内でしか達成できませんでした。

エージェントは、最も直接的なアクションを選択するのではなく、これらの制限内で作業する必要がありました。

実行不可能な場合、ポリシーまたは利用可能な資金により、目標に到達できませんでした。

受取人が許可リストにない可能性があります。

不足分がセッションバジェットを超えている可能性があります。

資金調達用ウォレットに十分な SOL がない可能性があります。

エージェントは正しく推論できても、目標を達成できない可能性がありました。

それはシステムの失敗ではありませんでした。

正しい振る舞いは、停止し、制約を説明し、ポリシーの境界を維持することでした。

「続行できません」は、時には正しい結果です。

ターン制限が無限ループを防ぎました

エージェントループには停止条件が必要です。

それがない場合、モデルはツールの呼び出し、失敗したアクションのリトライ、または同じ推論の無限の再検討を続ける可能性があります。

Arc 14 には明示的なターン制限が含まれており、アプリケーションは固定数のステップ後にワークフローを停止できました。

これは偶発的なループから保護し、1 つのリクエストが消費できる時間、トークン、ネットワークアクティビティの量を制限しました。

ターン制限はシンプルな制御ですが、エージェントシステムの実際の特性に対処します。

モデルは次に何をするかを判断しました。

アプリケーションは、判断を続けることを許可される時間を決定しました。

他のシステムも、時間制限、ツールコール制限、または特定のアクションの承認ゲートを使用するかもしれません。

正確な制御は異なる場合があります。

重要な点は、エージェントが自分自身に無制限の時間や無制限の試行を許可できないことです。

構造化ログがワークフローを検査可能にしました

最終的な回答しか見えない場合、自律的な振る舞いを信頼するのは困難です。

したがって、ワークフローは各重要なステップの構造化ログを書き込みました。

これらのログは以下を記録できました:

  • エージェントが受け取った目標。
  • 選択したツール。
  • 提案した引数。
  • 関連するポリシー決定。
  • アクションが承認または拒否された理由。
  • トランザクション署名。
  • 検証ステップの結果。
  • 最終的な結果。

これにより、システムが元の目標から結果へどのように移行したかのトレースが作成されました。

ログはデバッグに役立ちましたが、その価値はそれだけではありませんでした。

エージェントが目標を正しく解釈したかどうかを示しました。

ポリシーエンジンが期待されるルールを適用したかどうかを明らかにしました。

繰り返されるアクションを可視化しました。

成功したワークフローがその作業を検証した証拠を提供しました。

また、拒否を説明しやすくしました。

ログがなければ、送金が行われなかったことしかわからないかもしれません。

ログがあれば、受取人が許可リストになかったこと、または残りの予算が低すぎたことを確認できました。

自律システムにはこの種の証拠が必要です。

最終的なレスポンスだけでは不十分です。

ドキュメントは境界を説明することを強制しました

Day 97 では、システムの構築から説明へと移行しました。

以下をカバーする公開技術ウォークスルーを書きました:

  • エージェントループ。
  • 利用可能なツール。
  • MCP サーバー。
  • ウォレットの境界。
  • ポリシーエンジン。
  • ツールコントラクト。
  • 全体のアーキテクチャ。
  • 実際の実行からのログ。

最も重要な区別は、可変的な振る舞いと不変的な振る舞いの間でした。

モデルの推論は可変的でした。

異なるツールを選択したり、説明の仕方を変えたり、同じ目標に向かって別の道をたどったりするかもしれません。

ポリシーレイヤーは不変的でした。

同じ提案されたアクションとセッション状態は、モデルがその意図をどのように説明したかに関わらず、同じ承認決定を生み出すべきです。

これにより、アーキテクチャを理解しやすくなりました。

エージェントが安全なのは、モデルが振る舞うように指示されたからではありません。

モデルが署名機能に到達できるのは、固定のルールを強制するコードを通じた場合だけだからです。

ドキュメントは、その境界がどこに存在するかを正確に示す必要がありました。

ツールコントラクトはプロンプトと同じくらい重要でした

エージェントシステムの公開ウォークスルーは、簡単にプロンプトの長い説明になりがちです。

プロンプトは重要でしたが、この Arc の一部に過ぎませんでした。

より強力な材料は、ツールコントラクトとポリシールールにありました。

送金リクエストにはどのフィールドが必要でしたか?

ポリシーの前にどの検証が行われましたか?

拒否後にどのポリシー結果が返されましたか?

秘密鍵はどこにありましたか?

累積セッション支出はどこに保存されましたか?

どのコンポーネントがトランザクションを構築しましたか?

ワークフローはどのように成功を確認しましたか?

ツールコールとポリシー決定はどのようにログに記録されましたか?

信頼できないツール出力が署名ルールをバイパスするのをどのように防ぎましたか?

これらの詳細は、システムが実際に何を保証できるかを説明しました。

プロンプトは意図された振る舞いを説明しました。

ツールコントラクトは、モデルがリクエストできるアクションを定義しました。

ポリシーエンジンは、どのリクエストが進行できるかを定義しました。

署名者は、ネットワークに到達するものを制御しました。

ログは、何が発生したかを示しました。

それが完全な安全の物語でした。

最終デモは、アクションと拒否の両方を示しました

Day 98 では、ワークフローを短い録画デモにしました。

デモは自然言語の目標から始まりました。

エージェントは残高を検査し、ツールを選択し、アクションを提案し、ポリシーに通しました。

承認されたトランザクションが devnet に表示され、オンチェーンで検証できました。

しかし、デモは拒否も示す必要がありました。

それは成功した送金と同じくらい重要でした。

資金を移動できるシステムは、デモンストレーションが容易です。

安全でない、または許可されていないリクエストを拒否できるシステムは、より説得力があります。

拒否は、モデルがアクションを提案したとしても、ポリシーエンジンが制御を維持していることを示しました。

したがって、最も有用なデモには以下が含まれました:

  • 元の目標。
  • エージェントのツールコール。
  • ポリシー決定。
  • 承認されたアクションのトランザクション署名。
  • 結果として生じるオンチェーン状態。
  • ポリシーによって拒否されたリクエスト。
  • 拒否の理由。

これにより、境界がコードのどこかに存在することをオーディエンスに信頼させるのではなく、可視化しました。

Arc 14 が私たちに教えてくれたこと

Arc 14 は、AI モデルにウォレットを渡して最善を期待することではありませんでした。

それは、制御を維持するシステムの中にエージェントを構築することでした。

Arc の終わりまでに、私たちは以下の方法を学びました:

  • 明確に定義された Solana ツールを中心としたエージェントループを構築する。
  • ネットワークアクセスと秘密鍵をモデルコンテキストの外に保持する。
  • ツール結果とオンチェーンデータを信頼できない入力として扱う。
  • 送金上限、許可リスト、予算をアプリケーショコードで強制する。
  • 再利用可能な Solana 機能を MCP サーバーにパッケージ化する。
  • 異なる AI クライアント間で同じ安全ルールを適用する。
  • 観察、行動、検証を行うワークフローを実行する。
  • モデル外でセッション状態を追跡する。
  • エージェントのターンを制限し、構造化された実行ログを記録する。
  • 承認されたアクションと拒否されたアクションの両方をデモンストレーションする。

中心的な教訓は、自律性と権限は同じものではないということでした。

モデルは、どのツールが役立つかを判断できます。

目標を解釈できます。

不足分を計算できます。

送金を提案できます。

拒否を説明できます。

しかし、秘密鍵は持っていません。

どの受取人が許可されているかを決定しません。

自身の支出上限を設定しません。

自身のセッションバジェットを記憶したり、リセットしたりしません。

ポリシーエンジンをオーバーライドしません。

モデルは推論します。

コードは権限を保持します。

Arc 14 の課題を再訪する

Day 92: 定義されたツールを通じて質問に答える、読み取り専用の Solana エージェントを構築する

Day 93: 鍵と支出上限をコード内に保持したまま、エージェントに devnet ウォレットを与える

Day 94: Solana ツールを再利用可能な MCP サーバーとしてパッケージ化する

Day 95: 許可リストと予算を備えた、デフォルト拒否のポリシーエンジンを構築する

Day 96: 自律的なウォレット管理ワークフローを実行・検証する

Day 97: エージェントアーキテクチャ、ツールコントラクト、ポリシールール、実行ログをドキュメント化する

Day 98: 成功したアクションとポリシー拒否の両方を示す公開デモを録画する