AIハーネスとゲームを連携させる際の信頼性、安定性およびリアルタイム性の確保における課題と解決法
概要
近年、AIハーネスの性能向上が目覚ましく、特に2025年から2026年にかけてAIエージェントとしての機能、性能が大幅に向上している。
AIエージェントは単なる便利な道具を超え、人と共生する仲間やキャラクターとしての存在を担う可能性があると筆者は考える。
またこの共生の可能性を現実にしていくための技術は、AIエージェントを様々な産業、事業への応用を可能とし、さらに将来ヒューマノイドロボットを実現するための礎ともなると考える。
しかしまだ課題は多い。今回はAIハーネスとゲームとのリアルタイム性の課題とその解決法を提案する。ゲームはリアルタイムの生成、インタラクションを必要とする題材であり、課題解決の価値が高い。
今回の目的
AIハーネスとオンライントレーディングカードゲーム(CNPトレカアプリ)を連携し、AIハーネスがゲームを安定的かつ継続的にプレイすることを可能にする。プレイ中はAIハーネスに一切指示せずとも自律的にゲーム終了まで進めることを目標とする。
対戦ゲームであれば人とAIハーネスや、AIハーネス同士の対戦可能としたい。
認識された課題
ゲームとAIハーネスとの安定した双方向通信
リアルタイム性の確保
AIハーネスがゲームの手を判断するために必要十分な情報提供
判断のゆれやハルシネーション対策
以下一つ一つ解説する。
ゲームとAIハーネスとの安定した双方向通信の実現
ゲームとAIハーネスがインタラクションするために、双方向通信が必要となる。
既にCNPトレカアプリはSocket.ioを利用してサーバーとクライアントブラウザの双方向通信を実現している。そのインフラを活用するとして、いくつか選択肢がある。
playwrightなどを利用してchromiumなどブラウザモジュール経由で通信する
専用のSocket.ioクライアントを作成して通信する
前者は既に環境が整っており、簡単なテストなどに最適である。後者は仕組みづくりにややコストがかかるが、ゲーム専用クライアントとしてカスタマイズしやすい。前者の課題として
playwrightとのMCP接続は不安定になる場合がある。
MCPはコンテキストを消費しやすく、速度面やゲーム考察用に残しておきたいコンテキストの面で不利。実際playwrightを利用するとplaywrightの操作にAIハーネスの意識がそがれ、AIが集中して思考できずゲームにならない。
後述するAIハーネス用の情報提供層が必要なため、単に接続するだけでは情報量や構造化の面で不十分
がある。理屈上単に接続できればよい、ということではないのは理解頂けると思う。
以上の理由により、後者を採用した。
後者の専用クライアントのため、CNPトレカアプリ(本体サーバー)側に公開用のAPIを作成し、SDK経由でクライアントからアクセス可能とした。SDKとの通信はAPIキーで認証する。
その上でSDKを利用してAIハーネス用のSocket.ioクライアントを作成し、バックグラウンドで常時起動とした。以下これをローカルサーバー(LS)と記す。
LSとUIはRPCで通信できる。UIは自由に設計できるが、AIハーネスとの接続を考慮するとMCPもしくはCLIとなる。
今回はCLIを採用した。
具体的には、AIハーネスが直接CNPトレカアプリ本体へ接続するのではなく、cnptcg CLIを入口にした。
CLIは人間にもAIハーネスにも扱いやすい一回実行の操作面を提供し、常時接続やSocket.ioの購読はLSが担当する。LSはSDKを通して本体サーバーの公開APIとSocket.ioに接続する。
この構成にした理由は、責務を分け、AIハーネスの安定性および推論性能向上のためである。
CLIを実行するたびにSocket.ioへ接続し直すと、接続状態が不安定になりやすい。一方で、AIハーネスに常駐プロセスを直接扱わせると、操作面が複雑になり、速度低下やハルシネーションの可能性を増してしまう。
そこで、LSが接続を維持し、CLIはLSへRPCで指示を送る薄い入口とした。LSとSDKは必要に応じてHTTP/Socket.ioのいずれかの接続となるが、AIハーネスはそれを意識する必要がない。
また、コマンドの責務も明確に分けた。対戦ルームへの参加はmatch、対戦中の行動はduel、観戦はspectate、LSの起動停止はclientが担当する。AIハーネスが古い状態や違う接続先を見に行かないよう、コマンド境界を明確にすることが重要である。
このアーキテクチャにより、AIハーネスからはシンプルなCLI操作のみでゲームの情報取得、参加が安定的に可能となる。
リアルタイム性を実現する
双方向通信を実現しても、AIハーネスがリアルタイムに反応できなければいけない。対戦ゲームでは相手のアクションがいつ起きるか分からず、事前に行動タイミングを断定できない。したがって、ライブのゲーム情報をトリガーにする必要がある。
実現方法として
AIハーネスがポーリングして状態変化を検知する
webhook等でサーバーからAIハーネスに通知する
CLIでwaitしてAIハーネスが完了を待つ
などが考えられるが、今回はCLIでwaitする方式を採用した。シンプルにCLIに機能を集約することで開発サイクルも高速化できる。
具体的には以下のようなCLIコマンドで実現する。
cnptcg duel wait-changeを用意し、指定したゲーム状態のリビジョン番号(duelRevision)以降に状態が変化するまで待機できるようにした。
cnptcg duel wait-change --since-revision <duelRevision>状態が変化したら、CLIは変更後の状態をJSONで返す。AIハーネスはコマンドが返るまで待ち、返ってきた情報をもとに次の判断へ進む。これにより、AIハーネス自身が短い間隔でポーリングする必要がなくなる。
さらに、状態変化後すぐに判断できるよう、--with-llm-contextを追加した。
cnptcg duel wait-change --since-revision <duelRevision> --with-llm-contextこのオプションを付けると、状態変化の結果に最新のllmContextが同梱される。通常ならwait-changeのあとにduel llm-contextをもう一度実行する必要があるが、その1ステップを省略できる。対戦では1手ごとにこの処理が繰り返されるため、CLI実行回数を減らす効果は大きい。
AI同士の対戦では、2つのprofileを同時に扱うbatch wait-changeを用意した。
cnptcg batch wait-change -p ro -p op --since-revision <duelRevision> --with-llm-contextこのコマンドは、ro(ルームオーナー)/op(相手)のどちらに状態変化があり、どちらが行動可能かを判定する。片方だけが行動可能であればactionableProfileを返し、そのprofileのllmContextを使って次の選択に進める。
コツとしてはAIハーネスへ「waitはブロック実行すること」と指示するとCLI完了まで余計な行動をせずに待ってくれるようになる。少なくともcodexハーネスではこの指示が有効である。
この点は実運用で重要だった。
wait中に別のllm-contextやstate showを実行すると、AIハーネスが余計な途中状態を読んでしまい、不必要なトークン消費や判断がぶれることがあった。wait-changeを実行したら、そのコマンドが返るまで他の確認を挟まない、という運用ルールを明確にすることでこの不具合を避けた。
TTLの実装
LSとSDKは定期的にヘルスチェックを行い、正しい状態を維持するように設計されている。そのため、何も考えなければLSとSDKのSocket.ioは接続し続ける。そのためクラウド側の負荷が必要以上に高まる課題がある。
TTL(切断までの時間)を実装し、その間CLI操作がなければ自動切断する仕様とした。細かいことだが、実務上は重要である。
AIハーネスへの情報提供
CLIはJSON形式で情報を提供するが、CNPトレカアプリの内部オブジェクトをそのままJSONにすると以下の問題が生じる。
内部情報が多く、AIハーネスの判断に無関係なノイズが増える
必要な情報でもAIハーネス用の構造化ができず、AIハーネス側の無視やハルシネーションの可能性が増える
構造化の最適化
duel llm-contextでは、AIハーネスが判断に使う情報をviewとchoicesに分けて返す。
viewは現在の対戦状態をAI向けに整理したものである。ゲーム内部の状態オブジェクトをそのまま返すのではなく、以下のように判断に必要な項目へ絞っている。
現在フェーズ
手番プレイヤー
ro/opそれぞれの公開エリア枚数
自分から見える手札、デッキ枚数などの非公開情報
コンバットエリアごとの合計BP
直近アクション
重要なのは、AIハーネスに計算させるべきでない情報をあらかじめ構造化して渡すことである。
例えば、CNPトレカのルール上、コンバットエリアのBPはカード本来のBPだけでは判断できない。バフ、レイズ、エリア上の補正などを含めて比較する必要がある。
そこでcombatPowerByAreaとして、baseBp、buffBp、raiseBp、raiseCardCount、totalBpをまとめて返すようにした。
{
"combatPowerByArea": {
"COMBAT_B": {
"cardId": "BT1-105",
"baseBp": 10000,
"buffBp": 1000,
"raiseBp": 0,
"raiseCardCount": 0,
"totalBp": 11000
}
}
}また、実運用で問題になったのがゲージ枚数の読み間違いだった。CNPトレカでは、自分から見て左からA/B/Cであっても、相手から見ると左右が反転する。つまり、自分のCOMBAT_Aは相手のCOMBAT_Cに対応する。
自分 COMBAT_A -> 相手 COMBAT_C -> 相手 GAUGE_C
自分 COMBAT_B -> 相手 COMBAT_B -> 相手 GAUGE_B
自分 COMBAT_C -> 相手 COMBAT_A -> 相手 GAUGE_Aこの対応をAIハーネスが誤読すると、「Aに進出しても相手Aゲージが0だから勝利に近づかない」といった誤った評価になる。しかし正しくは、自分のA進出で見るべきなのは相手Cゲージである。
そこでgaugeCardCountsのような単純な一覧を出すのではなく、combatAreaGaugeGuideに情報を集約した。
{
"combatAreaGaugeGuide": {
"COMBAT_A": {
"combatArea": "COMBAT_A",
"ownGaugeArea": "GAUGE_A",
"ownGaugeCount": 0,
"opponentCombatArea": "COMBAT_C",
"opponentGaugeArea": "GAUGE_C",
"opponentGaugeCount": 1
}
}
}この構造なら、AIハーネスは「COMBAT_Aへ進出すると、相手のどのゲージを詰めるのか」を同じオブジェクト内で読める。似た情報を複数箇所に出すと誤読の原因になるため、ゲージ枚数の読み取りはこのガイドに唯一化した。
AIハーネスレスポンス高速化の工夫
wait後、llm-contextを取得せずともCLIからコンテキストが付加されるので、ステップを1つ省略できる。
対戦ループは、待機、判断、送信の繰り返しである。素朴には以下の3ステップになる。
cnptcg duel wait-change --since-revision <duelRevision>
cnptcg duel llm-context
cnptcg duel action choose-send --choice-id <choiceId> --expect-revision <duelRevision>しかしwait-change --with-llm-contextを使うと、待機後のllm-context取得を省略できる。
cnptcg duel wait-change --since-revision <duelRevision> --with-llm-context
cnptcg duel action choose-send --choice-id <choiceId> --expect-revision <duelRevision>AIハーネスはコマンド実行のたびに出力を読み、判断し、次のコマンドを組み立てる。そのため、1ステップ減らすことは単なる通信回数削減ではなく、思考開始までの遅延削減にもなる。
ハルシネーション対策
構造化データ内の合法手にchoice-idを付与して、選択肢であることを明確にしてハルシネーションを抑制している。
AIハーネスにDuelActionのJSONを直接生成させると、存在しない行動や古い盤面に対する行動を作る可能性がある。TCGでは、カードID、移動元、移動先、対象、フェーズなどが少しでもズレると不正な行動になる。ゲームの進行において不正手の発生は致命的である。
またCNPトレカアプリにはCPU対戦機能があり、CPUが自動プレイするための合法手生成ロジックが実装されている。
そこでAIハーネスには合法手そのものを作らせず、アプリ側で作成した合法手をAIハーネスに渡している。AIハーネスは合法手集合のchoicesからchoiceIdを選ぶ。
{
"choices": [
{
"choiceId": "choice-1-move",
"actionType": "MOVE_CARD",
"label": "MOVE_CARD",
"description": "MOVE_CARD"
},
{
"choiceId": "choice-2-turn-end",
"actionType": "TURN_END",
"label": "TURN_END",
"description": "TURN_END"
}
]
}送信時は、選んだchoiceIdと、判断時に見ていたduelRevisionだけを渡す。
cnptcg duel action choose-send --choice-id choice-2-turn-end --expect-revision <duelRevision>実際のDuelActionはLS側で復元する。これにより、AIハーネスは内部アクション形式を自作する必要がなくなり、合法手の比較検討に集中できる。
duelRevisionも重要である。AIハーネスが盤面を見てから送信するまでの間に状態が進んだ場合、古い選択肢を送るべきではない。--expect-revisionを付けることで、見ていた盤面と現在の盤面が一致しているかを送信前に確認できる。
これにより、ゲーム状態とAIハーネスとのずれを認識し、修正する機会が得られる。安定したゲーム進行にはリビジョン管理も重要な要素の一つである。
結果
以上のアーキテクチャを実装し、AIハーネスと人とで実際のCNPトレカアプリの対戦を行った。
対戦の様子は、以下のNoteで確認できる。
実際にAIハーネスが合法手を選択、送信している様子が分かる。
またAIハーネスは新規対戦ルーム作成、ルームへの参加、マリガン、アクション選択と送信の全てを行うことができ、完全自律、自動的にゲームを進行できる。
実際、ルーム参加からゲーム終了まで1度も止まることなく完全自動でAIハーネス(codex利用)対戦することができた。対戦時間は1時間弱ほどである。
さらなる課題
ターン速度
現状ではAIハーネスの生成速度の課題がある。
生成速度を早くすると、読みが浅くなり対戦の質が落ちる
深読みしようとすると、生成速度が落ちて対戦のテンポ感が下がる
計測範囲は、wait-change完了から次のwait-change開始直前までである。以下は10手分の実測値を秒に変換した棒グラフである。利用したハーネスはcodex、LLMはgpt-5.5である。
平均思考時間は、mediumが38.29秒、mediumの外れ値はカード情報の閲覧を含むので比較のため平均から除くと31.64秒、xhighが38.34秒だった。mediumのほうがxhighより平均約6.7秒短かかった。
一方で、人間が1手考えるのに必要な時間は個人差や場面差がが大きいが、難しい場面でなければ10秒前後が妥当と考えられる。
したがって、AIハーネスは人間の約3倍以上の時間がかかることが分かった。
速度改善の主な方向性は3つある。
1つ目は、CLI実行回数を減らすこと。wait-change --with-llm-contextはこのための改善である。
2つ目は、AIハーネスへ渡す情報を増やしすぎないこと。情報が多いほど読み込みに時間がかかり、重要情報を見落とす可能性も上がる。必要な情報を必要な構造で渡すことが重要である。
3つ目は、判断の型を安定させること。例えば「勝利に近づく手」「相手のゲージを詰める手」「BP比較で勝てる手」「次ターンの防御を残す手」といった評価軸を毎回同じ順番で検討できるようにすると、思考の揺れを減らせる可能性がある。
今後は、1手あたりの待機時間、llm-context読解時間、生成時間、送信時間を分けて測定し、どこがボトルネックになっているかを可視化したい。
トークン消費
トークン消費も多い。サブスク利用のため正確な計測が難しいが、以下の条件で模擬戦を行ったときのトークン消費量を示す。
AIハーネス同士で対戦
それぞれのプレイヤーがマリガン後、10手まで進める
この模擬戦では、アクション送信前に以下のチェックをハーネスに義務付けている。
送信前チェック
- revision:
- phase / turnPlayer:
- 盤面:
- 手札:
- レイキ:
- ゲージ:
- 残りデッキ:
- 直近の負け筋:
- 本命:
- 確度:
- 根拠:
- 阻害要因:
- 直近の勝ち筋:
- 本命:
- 確度:
- 根拠:
- 阻害要因:
- 合法手比較:
- 送信するchoiceId:
- 理由:100ドル契約のアカウントで、5時間枠の消費量は7%であった。
開始時のトークン残量
各10手進めた後のトークン残量
各50手進めた後のトークン残量
合計20手で7%の消費、100手で38%の消費である。CNPトレカはゲーム終了まで100手ほどかかることがあるので、40%弱ほどの消費と考えてよいかもしれない。
まとめ
AIハーネスにゲームをプレイさせるには、単にゲームへ接続できるだけでは不十分である。常時接続を安定して維持する層、AIハーネスが迷わず扱える操作面、判断に必要な情報を過不足なく渡す構造化、そして古い盤面に対する行動を防ぐ安全な送信手段が必要になる。
今回のCNPトレカアプリでは、SDK、LS、cnptcg CLIを分けることで、この課題を段階的に解決した。LSがSocket.io接続を維持し、CLIがAIハーネスにとって扱いやすい一回実行の入口となる。さらにwait-changeで状態変化を待機し、さらに判断材料を同梱することでリアルタイム性と実装の単純さを両立した。
また、AIハーネスには自由なアクションJSONを生成させず、アプリ側で生成した合法手を選ばせる形にした。これにより、ハルシネーションによる不正手を抑え、リビジョン管理によって古い盤面への送信も検知できる。AIに任せる範囲と、アプリ側で保証する範囲を明確に分けたことが、安定した自律プレイの鍵だった。
一方で、実際に最後まで自動対戦できたことで、次の課題も見えた。現状では1手あたりの思考時間が30秒以上と長く、トークン消費も大きい。今後は、AIハーネスに渡す情報をさらに精査し、判断の型を整え、待機、読解、生成、送信の各処理時間を分けて計測する必要がある。
AIハーネスとゲームの連携は、テスト自動化やデモ用途にとどまらない。リアルタイムな環境で状況を読み、合法手を比較し、継続的に行動する仕組みは、AIエージェントを実世界の複雑なシステムへ接続するための基礎になる。今回の取り組みは、そのための実践的な一歩である。








