1. はじめに
2026 年 7 月、Oracle Autonomous AI Database Serverless(以下 ADB)に、Select AI Agent のチームを A2A(Agent2Agent)プロトコルで外部に公開する A2A Server が追加されました1。A2A は、別々に作られた AI エージェントの間で、HTTP でタスクを依頼して結果を受け取るための公開仕様です2。
外から呼ぶ手順そのものは、curl で呼ぶ記事3や、A2A の Python SDK と OpenAI Agents SDK から呼ぶ記事4がすでに日本語で出ています。本記事で確かめたいのは、呼ばれた側の DB で何が起きるかです。公式ドキュメントには、呼び出し側の認証について次の一文があります。
データベース資格証明はアクセス・トークンの取得に使用され、後続のすべてのリクエストは直接データベース接続のかわりにトークンを使用します。
Agent2Agentプロトコルについて5
外部のエージェントは DB に接続せず、トークンだけで DB の中のエージェントを動かす、という説明です。では、DB に接続していない呼び出しは、DB の中では誰のセッションとして動き、何が記録されるのか。前回の記事6で作った Supervisor 付きのチーム構成を Always Free の ADB に作り直し、A2A で公開して確かめます。呼び出し自体は、タグを 1 つ付け、コメントと空行を除いて 24 行の Python を書くだけでできました。
1.1. 結論(先出し)
- A2A 経由の実行は、トークンを取った DB ユーザーではなく、ADB の内部ユーザー
C##CLOUD$SERVICEのセッション(moduleRUN_A2A_AGENT、action は A2A のタスク ID)として動いた - そのため、DB ユーザー名でセッションを探しても A2A の実行は見つからず、デフォルトの統一監査にも記録されなかった。追える手がかりは module・action と、チームの持ち主の実行履歴(
CONVERSATION_ID= A2A のcontextId)である - Supervisor がユーザーに聞き返して DB 側の履歴が
WAITING_FOR_HUMANのままでも、A2A の応答はcompletedだった - Supervisor 付きチームを別スキーマへ移すと、
EXPORT_TEAM/IMPORT_TEAMでは Supervisor が含まれなかった。GET_DEFINITIONの出力なら、実行順を並べ替えれば Supervisor ごと作り直せた
1.2. 検証ゴール
| # | 確かめること | 確認できれば OK の条件 |
|---|---|---|
| 1 | A2A 経由の実行が DB からどう見えるか | 実行中のセッションのユーザー・接続元・スキーマと、エージェントの履歴・統一監査に記録されるものを、PL/SQL で直接実行した場合と並べて比べられる |
| 2 | A2A の状態と DB の状態が一致するか | 完了・聞き返しのそれぞれで、A2A の応答と DB の履歴の状態を並べられる |
| 3 | Supervisor 付きチームを別のスキーマへ移せるか | 定義を書き出して別スキーマで作り直したチームが、Supervisor を含めて A2A から呼べる |
2. 検証環境
| 項目 | 内容 |
|---|---|
| ADB | Oracle Autonomous AI Database Serverless 26ai、Always Free(1 OCPU)、ap-tokyo-1 |
| AI プロファイル |
OCI_OSAKA(OCI Generative AI 大阪リージョンの meta.llama-3.3-70b-instruct を、リソースプリンシパルで呼ぶ) |
| チーム |
OPS_SUPERVISOR_TEAM(Supervisor 1 つ+ワーカー 2 つ。Top SQL 解析と統計確認)。前回の記事6と同じ構成を、この ADB に作り直したもの |
| チームの持ち主 | DB ユーザー DBA_COPILOT
|
| クライアント | Windows 11 の Python 3.12.0、requests 2.33.0、a2a-sdk 0.3.26 と 1.1.4 |
A2A のクライアントは、最初は requests で JSON をそのまま組み立てて送りました。公式ドキュメントが挙げる外部クライアントの例は Google Gemini Enterprise だけで、Python などのコード例が無く7、何を送れば動くかを 1 つずつ確かめたかったためです。そのあとで、A2A の公式 Python SDK でも同じことができるかを試しています。
3. 構成
本記事の A2A のクライアントは PC の Python です。Python は ADB のリスナーに SQL*Net で接続するのではなく、ADB の HTTPS の API(https://dataaccess.adb.ap-tokyo-1.oraclecloudapps.com/adb/...)を呼びます。図中の Agent Card は、チームの名前・説明・使えるツール・呼び出し先の URL を記述した JSON で、A2A Server がチームの定義から自動で作ります8(中身は 4.3 章)。流れは次のとおりです。
図 1: A2A で ADB のチームを呼ぶ流れ
呼び出しは非同期です。message/send はすぐにタスク ID を返し、クライアントは tasks/get で完了を待ちます。
「A2A Server」は、1 台のサーバーというより、この HTTPS の API と、自分の ADB の中で動く処理の 2 つで構成されています。
API のホスト名 dataaccess.adb.ap-tokyo-1.oraclecloudapps.com は、DNS で名前解決すると、Database Actions(ブラウザの SQL 画面)や OML と同じ 3 つの IP アドレスになりました。ウォレットで接続するリスナーとは別のアドレスです。トークン発行・一覧・Agent Card、それに Managed MCP Server も、このホストの配下にあります。
処理は、自分の ADB の中の C##CLOUD$SERVICE のセッションで動きます。このセッションは ADB 自身のホストから共有サーバー経由で接続され、クライアントのドライバ名とバージョンの列は空でした。接続直後の接続サービスは SYS$USERS です。実行が始まると _low に切り替わります。そのあとスキーマをチームの持ち主(今回は DBA_COPILOT)に切り替えてから、DBMS_CLOUD_AI_AGENT.RUN_TEAM を呼んでいました。中身は、持ち主に代わって PL/SQL でチームを実行しています。公式ドキュメントにも、A2A を有効にしても追加のコンピュートは割り当てられず、ADB の割り当てを使うと書かれています9。
4. 外から呼んでみる
4.1. A2A Server を有効にする
有効化は PL/SQL ではなく、ADB に付ける freeform タグで行います10。
# 既存の freeform タグを全部置き換えるので、他のタグがあれば同じ JSON に含める
oci db autonomous-database update \
--autonomous-database-id <ADB の OCID> \
--freeform-tags '{"adb$feature": "{\"name\":\"a2a_server\",\"enable\":true}"}' \
--force --wait-for-state AVAILABLE
図 2: ADB のタグ。freeform タグの adb$feature に A2A の値が入っている
--wait-for-state AVAILABLE で待つ更新は、あとで付け直した 3 回で 9.5〜17.6 秒でした。認証なしで一覧の URL にリクエストを送ると、更新前は HTTP 403(The tool you are trying to access is disabled by your administrator.)でした。更新のあと 1 分おきに確認すると、2 回目で HTTP 401(ADB-00015 Invalid credential.)に変わりました。401 は「A2A Server は動いているが、トークンが無い」という応答です。
なお、2026 年 9 月 18 日時点のコンソールでは、ADB の詳細(「ツール構成」タブと一般情報)に A2A の URL を表示する欄は見当たりませんでした。URL は公式ドキュメントの形式から組み立てるか、4.3 章の一覧 API で取得します。
この ADB には、前に Managed MCP Server を有効にするため、同じキー adb$feature で MCP の値を付けていました11。キーは 1 つなので、A2A の値で上書きしたことになります。公式ドキュメントには「MCP Server と A2A Server は独立して有効化する」と書かれており10、上書き後も MCP のエンドポイントは 401 と WWW-Authenticate を返していました。無効なら 403 になるはずなので、MCP は有効なままだと判断しました。
4.2. トークンを取得する
公式の手順は、ADMIN で OAuth クライアントを登録し12、DB ユーザーのパスワードでベアラートークンを取得する13、の 2 段階です。
# DB ユーザー(ここではチームの持ち主 DBA_COPILOT)のパスワードでトークンを取得する
BASE = "https://dataaccess.adb.ap-tokyo-1.oraclecloudapps.com/adb"
r = requests.post(
f"{BASE}/auth/v1/databases/{ocid}/token",
json={"grant_type": "password", "username": "DBA_COPILOT", "password": password},
timeout=30,
)
token = r.json()["access_token"] # expires_in は 3600 秒
今回は OAuth クライアントを登録してからトークンを取得しましたが、トークン取得のリクエストに、登録で得た client_id と client_secret は不要でした(公式の例も同じです)。
4.3. 一覧と Agent Card
トークンで agents/ を呼ぶと、そのユーザーが持つチームの一覧が返ります。ADMIN のトークンでは空でした。見えるのは自分が持つチームだけです。同じ ADB の Managed MCP Server でも、ログインしたユーザーが持つツールだけが見えたので、同じ仕様と考えられます。
一覧には、Supervisor の無い NL2SQL_DATA_RETRIEVAL_TEAM だけが出て、OPS_SUPERVISOR_TEAM は出ませんでした。18 分後に取り直しても同じです。一方、URL を組み立てて Agent Card を取得すると HTTP 200 で返り、呼び出しもできました。
取得した Agent Card の抜粋を載せます。
{
"name": "OPS_SUPERVISOR_TEAM",
"description": "Supervisor-led ops support team: top SQL analysis + statistics freshness",
"url": "https://dataaccess.adb.ap-tokyo-1.oraclecloudapps.com/adb/a2a/v1/databases/<ADB の OCID>/agents/OPS_SUPERVISOR_TEAM",
"capabilities": {"streaming": false, "pushNotifications": false, "stateTransitionHistory": false},
"skills": [
{"id": "28", "name": "SQL_TOOL", "description": "This tool can access the data in database. ..."},
{"id": "31", "name": "CHECK_TABLE_STATS_TOOL", "description": "Returns optimizer statistics freshness for one table as JSON: ..."}
],
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3.0"
}
skills には、チームのワーカーが使うツールが 5 つ並びました(上は 2 つに省略)。Supervisor 自身もワーカーのエージェントも skills には出ません。
4.4. 質問を送って答えを受け取る
A2A v0.3.0 の JSON-RPC で message/send を送り、tasks/get で完了を待ちます2。
# 質問を送る(すぐに submitted とタスク ID が返る)
body = {
"jsonrpc": "2.0", "id": "1", "method": "message/send",
"params": {"message": {
"kind": "message", "messageId": str(uuid.uuid4()), "role": "user",
"parts": [{"kind": "text", "text": "いま一番重いSQLはどれ?"}],
}},
}
headers = {"Authorization": f"Bearer {token}"}
task = requests.post(card["url"], json=body, headers=headers, timeout=60).json()["result"]
# 3 秒ごとに状態を確認する
while task["status"]["state"] in ("submitted", "working"):
time.sleep(3)
poll = {"jsonrpc": "2.0", "id": "2", "method": "tasks/get", "params": {"id": task["id"]}}
task = requests.post(card["url"], json=poll, headers=headers, timeout=30).json()["result"]
print(task["artifacts"][0]["parts"][0]["text"]) # 答えの本文
A2A の公式 Python SDK(a2a-sdk)でも試しました。2026 年 9 月 18 日時点で PyPI の最新は 1.1.4 で、A2A 仕様 1.0 の実装に 0.3 との互換モードが付いたものです。ADB は 0.3.0 準拠なので、0.3 系の最終版 0.3.26 も並べて試したところ、どちらも 1 回目で呼べました。1.1.4 は Agent Card の protocolVersion を見て 0.3 用の通信方式を自動で選ぶため、互換モードを指定するコードは不要でした。
4.5. 答えが返った
記事用に 1 本にまとめたスクリプト(コメントと空行を除いて 24 行)を実行すると、次のように出ます。
> python a2a_ask.py "いま一番重いSQLはどれ?"
チーム: OPS_SUPERVISOR_TEAM skills: ['SQL_TOOL', 'DISTINCT_VALUES_CHECK', 'RANGE_VALUES_CHECK', 'GENERATE_CHART', 'CHECK_TABLE_STATS_TOOL']
0.0 秒 submitted
3.6 秒 working
7.1 秒 working
...(中略)...
70.2 秒 working
73.7 秒 completed
現在最も重いSQLクエリは、SQL_IDが「7ukk8uw1539mm」で、ELAPSED_PER_EXEC_SECが41.173秒です。
最初に requests で JSON を組み立てて送ったときは、message/send は 0.97 秒で submitted を返し、tasks/get で working が続いたあと、送信から 19.1 秒の確認で completed になりました。
{"id": "de62c18f-...", "contextId": "5AB5B432-C6AF-...", "status": {"state": "submitted"}}
{"id": "de62c18f-...", "status": {"state": "working"}}
{"id": "de62c18f-...", "status": {"state": "completed"},
"artifacts": [{"name": "select_ai_agent_result",
"parts": [{"kind": "text", "text": "現在最も重いSQLクエリは、SQL_IDが「gyuu22b55z3g0」です。..."}]}]}
DB 側の履歴では、Supervisor のタスクのあとに Top SQL ワーカーのタスクが続いており、前回の記事6と同じ委譲の流れでした。LLM は Llama 3.3 70B のままです。
答えの「一番重い SQL」は、1 回目がこの ADB に常時接続している監視ツールの SQL、上の実行例では直前の A2A 実行で動いた RUN_TEAM の呼び出しでした。どちらも A2A とは別の、エージェントがどの SQL を答えに選ぶかの話なので、本記事では扱いません。
5. DB から見た A2A
5.1. 誰のセッションで動くか
A2A で呼んでいる最中に、ADMIN で V$SESSION を module で絞ると、次の 1 行が見えます。
図 3: A2A 実行中の V$SESSION(Database Actions)。ユーザーは C##CLOUD$SERVICE、module は RUN_A2A_AGENT、action は A2A のタスク ID
A2A で呼んだ実行と、同じ質問を DBA_COPILOT で接続して PL/SQL(DBMS_CLOUD_AI_AGENT.RUN_TEAM)で直接実行した場合を並べます。
| 項目 | PL/SQL で直接実行 | A2A 経由 |
|---|---|---|
| セッションのユーザー | DBA_COPILOT |
C##CLOUD$SERVICE |
| 実行中のスキーマ | DBA_COPILOT |
C##CLOUD$SERVICE から DBA_COPILOT に切り替わる |
| 接続元(machine) | 手元の PC | ADB 自身のホスト |
| module | 接続したクライアント(Python) | RUN_A2A_AGENT |
| action | 空 | A2A のタスク ID(de62c18f-...) |
| 接続サービス |
_high(接続時に選んだもの) |
SYS$USERS から _low に切り替わる |
client_identifier |
空 | 空 |
| エージェントの履歴 |
DBA_COPILOT の USER_AI_AGENT_TEAM_HISTORY ほか |
DBA_COPILOT の USER_AI_AGENT_TEAM_HISTORY ほか |
| 履歴の時刻 | 日本時間(セッションのタイムゾーン) | UTC |
| 統一監査(デフォルトのまま) | 確認していない | 0 件 |
A2A 経由の実行は、トークンを取った DBA_COPILOT ではなく、ADB の内部ユーザー C##CLOUD$SERVICE のセッションとして動きました。V$SESSION を DB ユーザー名で絞っても、A2A の実行は出てきません。手がかりは module の RUN_A2A_AGENT と、action に入る A2A のタスク ID です。
それでも、エージェントの履歴は DBA_COPILOT の USER_AI_AGENT_TEAM_HISTORY に記録されました。CONVERSATION_ID は A2A 応答の contextId と同じ値です。「誰のチームが、どの会話で動いたか」は、履歴から追えます。「どのセッションで動いたか」は、action のタスク ID から A2A の応答を経由してたどることになります。
履歴の時刻は、直接実行の行は日本時間、A2A 経由の行は UTC で入っていました。列はタイムゾーンを持たない型なので、START_DATE の降順で並べると A2A の行が 9 時間古く見えます。行の特定は時刻ではなく、TEAM_EXEC_ID と CONVERSATION_ID で行うのが確実です。
統一監査は、ADMIN から見て、A2A を呼んだ時間帯に DBA_COPILOT と C##CLOUD$SERVICE のどちらの行もありませんでした。監査ポリシーはデフォルトのままで、追加していません。公式ドキュメントのセキュリティの章には、SYS_CONTEXT('A2A_AGENT_ACCESS_CONTEXT$', 'USER_IDENTITY') などで呼び出し元を取得できると書かれています9。
5.2. 聞き返しても A2A は completed を返す
Supervisor 付きチームは、情報が足りないとユーザーに聞き返します。前回の記事6では、その間チームの状態が WAITING_FOR_HUMAN(人の入力待ち)になり、同じ会話で答えると再開しました。
A2A で「統計が古い表はある?」と送ると、答えは「Which table would you like to check for stale statistics?」という聞き返しでした。範囲外の「明日の東京の天気は?」も、3.8 秒で completed になりました。こちらの答えは、質問を英訳した 1 文だけでした。どちらも DB 側の履歴は WAITING_FOR_HUMAN のままで、終了時刻は空です。
| 質問 | A2A の応答(tasks/get) |
DB のチームの履歴 |
|---|---|---|
| 統計が古い表はある?(答えは聞き返し) | completed |
WAITING_FOR_HUMAN(終了時刻なし) |
| 明日の東京の天気は?(答えは質問の英訳) | completed |
WAITING_FOR_HUMAN(終了時刻なし) |
A2A の仕様には、入力待ちを表す input-required という状態があります2。今回の ADB は、この状態を返しませんでした。呼び出す側のエージェントは、聞き返しを「完了した答え」として受け取ることになります。
5.3. 別のスキーマへ移す
開発用のスキーマで作ったチームを別のスキーマへ移す手段として、8 月に EXPORT_TEAM / IMPORT_TEAM と GET_DEFINITION が追加されました14。前回の記事6では EXPORT_TEAM の出力に Supervisor が含まれないことを見ていたので、今回は取り込む側まで試しました。
| 方法 | Supervisor | 結果 |
|---|---|---|
EXPORT_TEAM → 新しいユーザーのスキーマで IMPORT_TEAM
|
含まれない | 取り込みは成功し、Supervisor の無い順番実行("process":"sequential")のチームになった。エラーも警告も出ない |
EXPORT_TEAM → 同じスキーマで IMPORT_TEAM(チーム名だけ変更) |
取り込めず |
ORA-20050: Tool SQL_TOOL already exists. で失敗。変えられるのはチーム名だけで、中のツール名は元のまま |
GET_DEFINITION('TEAM', ..., '{"dependent_objects":true}') の PL/SQL を、同じスキーマで名前だけ変えて実行 |
含まれる | 出力の先頭が CREATE_TEAM のため、そのまま先頭から実行すると参照先のエージェントが無く失敗。ツール → エージェント → タスク → チームの順に並べ替えると成功し、A2A からも Supervisor 経由で呼べた(ツールが使う関数は同じスキーマにあるもの) |
新しいユーザーのスキーマに取り込んだチームは、A2A から呼べませんでした。Agent Card が HTTP 400(-32603 Failed to get agent card)、message/send が -32602 Agent not found です。原因は、取り込んだツールが元のスキーマ(DBA_COPILOT)の関数を指したままだったことでした。公式ドキュメントにも、ツールが使う自作の PL/SQL は移送されないと書かれています14。
これは、別の新しいユーザーに「自分の関数を呼ぶツール」と「見えない関数を指すツール」のチームを並べて作ると再現しました。前者は Agent Card が HTTP 200 で返り、5.8 秒で答えが返りました。後者は同じ 2 つのエラーになりました。どちらのチームも agents/ の一覧には出ています。今回、CREATE_TOOL は参照先の関数が見えない状態でもエラーを返さず、参照先の関数を呼べないことは A2A で Agent Card を取得するまで分かりませんでした。
6. 考察
6.1. A2A で呼ばれた実行を DB から追う
A2A 経由の実行は C##CLOUD$SERVICE のセッションで動き、デフォルトの統一監査には記録されませんでした(5.1 章)。DB ユーザー名でセッションや監査を絞る普段のやり方では、A2A からの利用が見えません。
実行中なら V$SESSION の module RUN_A2A_AGENT と action(A2A のタスク ID)から、終わったあとならチームの持ち主の USER_AI_AGENT_TEAM_HISTORY から追えます。後者の CONVERSATION_ID は A2A の contextId と一致するので、呼び出し側のログと突き合わせられます。
「どの外部エージェント・どの利用者から呼ばれたか」を DB 側に記録したい場合は、公式の A2A_AGENT_ACCESS_CONTEXT$9 をツールの中で読んで書き込むか、統一監査のポリシーを追加する必要があると考えられます(どちらも未検証)。
6.2. 完了の判定は DB 側の状態も見る
聞き返しでも A2A の応答は completed でした(5.2 章)。呼び出す側が A2A の状態だけで分岐すると、聞き返しを答えとして扱ってしまいます。
必要な入力をタスクの指示文(instruction)に書いておくと、聞き返しを減らせると考えられます。DB 側では WAITING_FOR_HUMAN の実行が増え続ける可能性があるため、件数を定期的に数えると、聞き返しの頻度を把握できると考えられます。
6.3. 移したあとは Agent Card で確かめる
IMPORT_TEAM はエラーも警告も出ないまま Supervisor の無いチームとして取り込み、ツールの参照先の関数を呼べなくてもエラーを出しませんでした(5.3 章)。GET_DEFINITION は Supervisor を含めて書き出しますが、実行順の並べ替えと、ツールが使う PL/SQL の移送は自分で行う必要があります。
移したあとは、A2A で Agent Card を取得して HTTP 200 が返ることを確かめるのが、今回試した中で最も手早い確認でした。一覧に出ることは確認になりません。参照先の関数を呼べないツールを持つチームも一覧に出て、逆に Supervisor 付きのチームは正常でも一覧に出ませんでした。
7. まとめ
| 確かめたこと | 結果 |
|---|---|
| 外から呼べるか | Always Free でもタグ 1 つで有効になり、Python(requests・a2a-sdk 0.3.26 / 1.1.4)から呼べた。所要時間は 19.1〜73.7 秒 |
| DB からどう見えるか |
C##CLOUD$SERVICE のセッション(module RUN_A2A_AGENT、action はタスク ID)。履歴はチームの持ち主に記録され、CONVERSATION_ID = contextId。デフォルトの統一監査には記録されない |
| 聞き返したとき | A2A は completed、DB は WAITING_FOR_HUMAN
|
| 別スキーマへ移せるか |
EXPORT_TEAM/IMPORT_TEAM では Supervisor が含まれない。GET_DEFINITION は並べ替えれば Supervisor ごと作り直せる。ツールが使う PL/SQL は別に移す |
外部のエージェントから ADB の中のエージェントを呼ぶことは、Always Free でもタグの付与とトークンの取得だけでできました。一方で、DB から見ると「誰が呼んだか」はデフォルトでは追いにくく、「完了したか」は A2A と DB で一致しません。A2A で公開するなら、この 2 点を DB 側でどう記録するかを先に決めておく必要があると考えられます。
参考
-
Oracle Autonomous AI Database Serverlessの新機能(2026 年 7 月の、Select AI Agent のチームを A2A プロトコルで公開する項目) ↩
-
A2A Protocol Specification v0.3.0(
message/send・tasks/get・タスクの状態・AgentCard) ↩ ↩2 ↩3 -
Getting Started with Oracle AutonomousDB Managed A2A Server: From Obtaining an Agent Card to Running curl Locally(Zenn・日本語。curl で
message/send・tasks/getまで) ↩ -
Google A2AをサポートしたOCI Autonomous Databaseでマルチエージェントを実装してみる(Qiita。A2A の Python SDK と OpenAI Agents SDK から呼ぶ) ↩
-
Oracle Autonomous AI Database の Select AI Supervisor Agent を試してみた(前回の記事。同じチーム構成) ↩ ↩2 ↩3 ↩4 ↩5
-
エージェントのコール(外部クライアントの例は Google Gemini Enterprise のみ) ↩
-
エージェント・カードの取得(name はチーム名、skills は
CREATE_TOOLのツール) ↩ -
A2A エージェントのセキュリティ(ページ名は「セキュリティ」。
A2A_AGENT_ACCESS_CONTEXT$のUSER_IDENTITY・TEAM_NAME、追加のコンピュートを割り当てないこと) ↩ ↩2 ↩3 -
A2Aサーバーの有効化(freeform タグ
adb$feature。MCP Server と A2A Server は独立) ↩ ↩2 -
Oracle Autonomous AI Database の MCPサーバを Claude Desktop につないで「いま重いSQL」を聞けるようにしてみた(Managed MCP Server もタグ
adb$featureで有効にする) ↩ -
OAuthクライアントを登録します(OAuth)(登録できるのは ADMIN のみ) ↩
-
Bearerトークンの生成(
grant_type: password) ↩ -
DBMS_CLOUD_AI_AGENTパッケージ(
EXPORT_TEAM・IMPORT_TEAM・GET_DEFINITION。移送されない依存物の一覧) ↩ ↩2

