相互運用のマトリクス:4 つの SDK にまたがる稼働中のエージェント¶
英語版の参考訳です(25/10/2026 更新)。内容が異なる場合は英語版が優先されます。識別子、コマンド、コードブロックは翻訳せずにそのまま掲載しています。
run.mjs は SDK ごとに 1 つのエージェントのプロセス(TypeScript、Python、Go、Rust)を起動し、
互いに協働させます。ある SDK の上に作ったエージェントがアイデンティティを作り、意図を宣言し、行動し、
市民権のバンドルに署名します。別の SDK の上に作ったエージェントは、相手を信頼する前にそうするように、
それらを検証します。ここでの検証とは、バンドルが一貫しており、それ自身が列挙する鍵で署名されていることを意味します。
相手への信頼、アドミッション、行動の認可ではありません(シナリオ 2 はアドミッションを扱いますが、それも
どの行動も認可しません)。プロセスはオーケストレーターが中継するメッセージ以外に何も共有しないため、
すべての組(4 つの SDK で 16 組。各 SDK とそれ自身の組を含む)はファイルの比較ではなく、本当の相互運用です。
5 番目の、検証だけをするエージェント ref は、リファレンスの検証器 lib/verify.js(node bin/dcp.js verify-bundle
と検証サーバーが実行するもの)を実行します。これは @dcp-ai/sdk の検証器(2.2.0 が npm に出るまではワークスペースの SDK)の
前に、すべてのアーティファクトを schemas/v2 に照らして検証します。SDK の検証器はこれをしません。
オーケストレーターは間のネットワークでもあります。転送中のバンドルを改ざんし、各 検証器は改ざんされたすべてのバンドルを同じように拒否しなければなりません。予期しない判定、クラッシュ、時間切れは どれも実行を失敗させます。
シナリオ 1(run.mjs)は、DCP-01(アイデンティティ)、DCP-02(意図とポリシー)、DCP-03(監査)の一部を
SDK の間で試します。これらの仕様の完全な適合性テストではありません。
シナリオ 2(admission.mjs)は、4 つの SDK にまたがって strict-v1 のプロファイル
(spec/profiles/admission)のもとでのアドミッションを扱います。DCP-04 の A2A のハンドシェイクには、まず SDK の作業が必要です
(最後のロードマップを参照)。
実行¶
(cd sdks/typescript && npm install && npm run build) # agents/ts imports sdks/typescript/dist
npm install # agents/ref: the reference verifier, on that SDK
pip install -e sdks/python # dependencies of agents/py
node tools/interop-matrix/run.mjs # all four SDKs and the reference verifier
node tools/interop-matrix/run.mjs --agents ts,py # a subset
node tools/interop-matrix/admission.mjs # scenario 2, the four SDKs, with its negative control
Go と安定版の Rust のツールチェーンが PATH になければなりません。run.mjs は agents/go と agents/rust をビルドします。
オプション:--agents a,b(ts、py、go、rust、ref のいずれか)、--python PATH(既定は $INTEROP_PYTHON または python3)、--timeout MS
(リクエストごと、既定は 60000)、--out DIR(既定は tools/interop-matrix/out で、レポートと、
予期しない判定を出したすべてのバンドルがここに書かれます)。admission.mjs は ref を除いて同じ
オプション(既定は --out tools/interop-matrix/out/admission)に加え、
--skip-negative-control、--negative-control、--check-negative-control、--replay FILE
(後述)を受け取ります。
エージェントのプロトコル¶
エージェントは stdin から 1 行に 1 つの JSON のリクエストを読み、各リクエストにちょうど 1 行の JSON の応答を stdout に順番に書きます。それ以外はすべて stderr に出します。
{"id": 1, "cmd": "identity", "args": {"owner_name": "Interop Owner", "jurisdiction": "CL"}}
{"id": 1, "ok": true, "result": {"agent_id": "agent:…", "human_id": "rpr:…", "session_nonce": "…"}}
{"id": 2, "ok": false, "error": "message"}
エージェントは自身の状態(鍵、アイデンティティ、意図、監査証跡)を、プロセスが生きている間メモリーに保持します。
鍵がそこから出ることはありません。以下で契約が定める値は、書かれたとおりに正確に生成しなければなりません。
それ以外はすべて SDK 自身の問題で、それこそがマトリクスがテストするものです(SDK が独自に加える省略可能な
フィールド、たとえば Go の SHA3 の audit_merkle_root_secondary は、すべての検証器が受け入れる限り
問題ありません)。
identity {owner_name, jurisdiction} → {agent_id, human_id, session_nonce, principal_keys, agent_keys}¶
2 つの複合鍵ペア(Ed25519 + ML-DSA-65)を生成します。1 つは人のプリンシパル用、1 つは
エージェント用です。新しいセッションの nonce(64 文字の 16 進数)、human_id = rpr:<uuid4>、agent_id = agent:<uuid4> も作ります。
鍵のエントリー:{kid, alg, public_key_b64, created_at: <now RFC 3339>, expires_at: null, status: "active"}。
- RPR(
schemas/v2/responsible_principal_record.schema.json):dcp_version "2.0"、human_id、session_nonce、legal_name=owner_name、entity_type "natural_person"、jurisdiction、liability_mode "owner_responsible"、override_rights true、issued_atは現在時刻、expires_at null、contact null、binding_keys= 人の 2 つの鍵のエントリー。ResponsiblePrincipalのコンテキストのもとで、 人の鍵で複合署名(pq_over_classical)されたSignedPayload。 - パスポート(
agent_passport.schema.json):dcp_version "2.0"、agent_id、session_nonce、keys= エージェントの 2 つの鍵のエントリー、principal_binding_reference=human_id、capabilities ["api_call", "payments"]、risk_tier "medium"、created_atは現在時刻、status "active"。AgentPassportのもとでエージェントの鍵で複合署名される。
principal_keys と agent_keys は公開の半分で、[{kid, alg, public_key_b64}]、クラシカルのものが
先です。シナリオ 2 はそれらから、帯域外でプリンシパルを登録します。
2 回目の identity は 1 回目を、それに伴う意図と証跡とともに置き換えます。
intent {action_type, channel, data_classes, estimated_impact, risk_score, applied_policy_hash} → {intent_id, decision, security_tier}¶
- 意図(
intent.schema.json):dcp_version "2.0"、intent_id=intent:<uuid4>、session_nonce、agent_id、human_id、timestampは現在時刻、action_type、target {channel, to: null}、data_classes、estimated_impact、requires_consent= (estimated_impactが"high"か)、security_tier= この意図とrisk_scoreについての SDK のセキュリティティアの計算(DCP-AI v2.0 §14.2)。 - ポリシー判断(
policy_decision.schema.json):dcp_version "2.0"、intent_id、session_nonce、decision=risk_score< 500 ならapprove、< 800 ならescalate、それ以外はblock。risk_score、reasons ["interop-matrix: risk_score <n>"]、required_confirmationは approve ならnull、それ以外は{"type": "human_approve", "fields": ["action_type"]}、applied_policy_hashは引数から、timestampは現在時刻、resolved_tier= 意図のsecurity_tier。
どちらもエージェントの鍵で複合署名されます(Intent、PolicyDecision のコンテキスト)。
act {intent_id, outcome, tool, result_ref} → {audit_id, count}¶
その意図の証跡に監査エントリー(audit_entry.schema.json)を 1 つ追加します:dcp_version "2.0"、
audit_id = audit:<uuid4>、session_nonce、prev_hash = 証跡の最初のエントリーなら "GENESIS"、
それ以外は "sha256:" + hex(sha256(canonical(previous entry)))、hash_alg "sha256"、
timestamp は現在時刻、agent_id、human_id、intent_id、
intent_hash = "sha256:" + hex(sha256(canonical(intent payload)))、
policy_decision = 判断に応じて approved / escalated / blocked、outcome、
evidence {tool, result_ref, evidence_hash: "sha256:" + hex(sha256(utf8(result_ref)))}、
pq_checkpoint_ref null。count は証跡の長さです。このエントリーは合成の記録です。act は何も実行せず、
outcome、tool、result_ref はオーケストレーターが送る文字列です。
bundle {intent_id, session_issued_at?, session_expires_at?, intended_verifier?} → {signed_bundle}¶
その意図の市民権のバンドル(signed_bundle.schema.json)です。署名された RPR、パスポート、
意図、ポリシー判断、意図の証跡の全体、そしてマニフェスト:session_nonce、
rpr_hash / passport_hash / intent_hash / policy_hash(ペイロードのハッシュ)、
audit_merkle_root("sha256:" + SDK の監査の Merkle ルート)、audit_count、
canonicalization_profile "dcp-jcs-v1"。署名:hash_alg "sha256"、created_at は現在時刻、
signer {type: "human", id: human_id, kids: [human classical kid, human pq kid]}、
manifest_hash、Bundle のコンテキストのもとで人の鍵による composite_sig。SDK の
バンドルのビルダーと署名者を使ってください。Go と Rust のビルダーは audit_merkle_root_secondary も書きます。
省略可能な引数は、署名の前に、与えられたとおりにマニフェストに入ります。
verify {signed_bundle, mode} → {valid, errors}¶
SDK の V2 のバンドルの検証器です。mode が "" なら既定の検証器のポリシーで、それ以外の値は、相互運用のテストと同じく、
default_mode とすべてのリスクティアに適用される検証モードです。不正な形式の
バンドルは valid: false の結果で、決してエラーの応答にはなりません。
rotate_principal {} → {human_id, principal_keys}¶
プリンシパルの新しい複合鍵ペアと、同じ human_id、現在時刻の issued_at、
新しい binding_keys を持ち、新しい鍵で署名された新しい RPR です。以降のバンドルはそれらを使います。
configure {tenant, trust_set, profile} → {} または {config_error}¶
テナントの運用者の設定で、SDK 自身のローダーで読み込みます。信頼セット
(createPrincipalTrustStore とその対応物)と strict-v1 のプロファイル
(resolveStrictV1Profile)です。設定のエラーは {config_error: <CONFIG_* code>} です。
admit {tenant, signed_bundle, revocation, now_ms} → アドミッションの結果¶
テナントのプロファイルと信頼セット、失効のスナップショット、時計を使った、SDK の統合された
アドミッションの API(admitSignedBundleV2 とその対応物)です。結果は SDK 自身の JSON の形
(spec §8)から verification を除き、verification_errors を加えたものです。
shutdown {} → {}¶
エージェントは応答した後に終了します。
シナリオ 1:DCP-01 から DCP-03¶
各 SDK を生成側として:identity、3 回の act を伴う定型の意図(api_call、チャネル api、
["none"]、影響 low、リスク 100)、1 回の act を伴う支払いの意図(initiate_payment、
チャネル payments、["financial_data"]、影響 high、リスク 900 なのでブロックされる)
(拒否も証拠です)、そして両方の bundle。次に各 SDK を検証側として、両方の
バンドルを既定のポリシーと hybrid_required のもとで確認します。どちらも検証を通らなければなりません。
その後、オーケストレーターは転送中の定型のバンドルを改ざんし、各検証器は、注記がない限り両方のモードで、 それぞれのコピーを拒否しなければなりません:
| 攻撃 | 何が変わるか |
|---|---|
intent_changed |
署名の後に intent.payload.estimated_impact が high になる |
audit_reordered |
最初の 2 つの監査エントリーの位置が入れ替わる |
audit_entry_dropped |
最後の監査エントリーが削除され、マニフェストは保たれる |
session_spliced |
マニフェストの session_nonce が置き換えられる |
bundle_signature_swapped |
バンドルの署名が支払いのバンドルのものになる |
passport_swapped |
パスポートが別のエージェント(次の SDK の生成側)のものになる |
pq_stripped |
バンドルの署名の pq の枠が削除される(classical_only)。hybrid_required のみ |
intent_from_other_bundle |
意図とポリシー判断が支払いのバンドルから来る(同じセッションと署名者) |
trail_from_other_bundle |
監査証跡が支払いのバンドルから来る(同じセッションと署名者) |
クラッシュする、時間切れになる、順番を外れて答えるエージェントは停止され、以降のそれへのリクエストは ただちに失敗するため、壊れたエージェントは失敗として現れ、実行が止まったままになることはありません。
シナリオ 2:strict-v1 のもとでのアドミッション¶
各 SDK を生成側として:identity、1 回の act を伴う定型の意図(リスク 100 なので approve)、
そしてマニフェストがセッションの期間 [T0, T0 + 300 s](プロファイルの最大)に署名する 3 つのバンドルです。
1 つはこの検証器(verifier:interop-admission)向け、1 つは別のオーディエンス向け、そして
rotate_principal の後に、ローテーションしたプリンシパルの鍵で署名したものです。
オーケストレーターが運用者です。生成側が報告した公開鍵から各プリンシパルを登録し
(アドミッション中のバンドルからは決して登録しません)、信頼セットを作り、失効の
スナップショットを提供します。生成側のエージェントと鍵で、checked_at は時計の 10 秒前です。各アドミッションは
固定の時刻 T0 + 120 s(期限切れのケースは期限の 1 ミリ秒後)に実行されます。テナント A と B は、
ハーネスが行う運用者の設定の選択です。どの統合もキャッシュも関与しません。
各 SDK を利用側として、各生成側について以下の 13 のケースを、この順に、1 つの
プロセスで実行します。期待される verified、principal_trust、profile、profile_compliance、admitted、
action_authorized(常に false)、recorded_decision、trust_set_version、コードの集合は、
仕様から来て admission.mjs に書かれており、決して SDK からは来ません。
| ケース | 設定 | 期待値 |
|---|---|---|
trusted_admitted |
テナント A が登録済みのすべてのプリンシパルを信頼する | 受け入れ |
trust_withdrawn |
このプリンシパルを除いたテナント A、同じバンドル | TRUST_PRINCIPAL_UNKNOWN |
trust_restored |
以前と同じテナント A、同じバンドル | 受け入れ |
tenant_b_empty_trust |
テナント B、空の信頼セット | TRUST_PRINCIPAL_UNKNOWN |
principal_key_revoked |
新しいスナップショットがプリンシパルの鍵を失効させる | REVOCATION_REVOKED |
agent_key_revoked |
新しいスナップショットがエージェントの鍵を失効させる | REVOCATION_REVOKED |
other_audience |
別の検証器向けに署名されたバンドル | PROFILE_AUDIENCE_MISMATCH, VERIFICATION_FAILED |
session_expired |
署名された期限の 1 ミリ秒後の時計 | PROFILE_SESSION_EXPIRED, VERIFICATION_FAILED |
rotation_new_keys_not_enrolled |
ローテーションしたバンドル、更新していない信頼セット | TRUST_KEY_NOT_ANCHORED |
rotation_overlap_new_keys |
古い鍵と新しい鍵が有効 | 受け入れ |
rotation_overlap_old_keys |
同じ、元のバンドル | 受け入れ |
rotation_old_keys_retired |
古い鍵が revoked、元のバンドル |
TRUST_KEY_REVOKED |
rotation_new_keys_after_retirement |
同じ、ローテーションしたバンドル | 受け入れ |
不一致があると、出力ディレクトリに最小の再現が書かれます。そのアドミッションについて利用側に与えられたものすべてで、
テナントの実効の信頼セットとプロファイル(最後に読み込まれたもの。前のケースが読み込んだ場合も
configuration_loaded_in とともに含む)、そしてそれが返したものです。公開の材料だけで、秘密鍵は含みません。
node tools/interop-matrix/admission.mjs --replay tools/interop-matrix/out/admission/<producer>-<consumer>-<case>.json
--replay は、記録された SDK の新しい利用側を起動し、記録されたテナントを設定し、記録された
バンドルを記録された時計でアドミッションにかけ、記録された出力と完全に一致することを求めます。
両側のすべてのキー(正規化したフィールド、コード、検証器のエラー、呼び出しが失敗したときの error)で、
片側にだけあるキーは違いとなるため、異なる 2 つのエラーが再現と見なされることはありません。
再現すれば 0、異なれば 1、記録が不完全なら 2 で終了します。
ネガティブコントロール。 マトリクスの後、ランナーは ts の組について、新しい一時ディレクトリと
新しい実行 ID で、最大 120 秒、--negative-control を付けて自身を起動します。その実行は
trust_withdrawn の期待される admitted を反転します。コントロールが合格するのは、子が 1 で終了し、その
レポートがこの実行で書いたもの(同じ実行 ID)で negative_control_run: true を持ち、唯一の
組 ts>ts が 13 中 12 で、失敗がちょうど 1 つ、trust_withdrawn の admitted のフィールドである場合だけです。
--check-negative-control はコントロール自体をテストします。正しい実行を受け入れ、起動できない子
(レポートがない場合と、同じディレクトリに前の実行が残したレポートがある場合)と、止まったままの子
(時間の上限で停止)を拒否しなければなりません。--negative-control-case NAME は代わりに別の
ケースを反転し、意図的に再現のファイルを作ります。
最初の実行:16 組 × 13 ケース = 208 の結果がすべて期待どおりで、ネガティブコントロールは 求められたとおりに失敗しました。
最初の実行が見つけたこと¶
16 の SDK の組とリファレンスの検証器は、すべてのバンドルとすべての攻撃について一致します。エージェントを 書く中で、次の SDK の欠落が見つかりました。どれも SDK の検証器の間の判定の食い違いではありません:
- Go の
SignBundleV2がsignature.signer.idを空のままにしていた:署名者の ID を受け取っていませんでした。4 つの SDK の検証器はそのようなバンドルを受け入れ、リファレンスの検証器、CLI、サーバーは拒否していました (signer/id must NOT have fewer than 6 characters)。修正済み:署名者は今ではバンドルの RPR のhuman_idで、 Go のエージェントはSignBundleV2をそのまま使います。signature.signerのブロック(id、kids)、created_at、manifest_hashは、署名されたマニフェストの外にある認証されていないメタデータです。どの 検証器もそれらを RPR と照合せず、manifest_hashを再計算もしません(5 つすべてがゼロを受け入れます)。 それらの形を制約するのはスキーマだけです。 - Go の型付きのペイロードにティアのフィールドがなかった:
IntentV2にはsecurity_tierがなく、PolicyDecisionV2にはresolved_tierがありませんでした。今は両方あります。(contactとtarget.toは スキーマ上は省略可能なので、Go のエージェントのマップがnullを書くのは契約に合わせるためだけです。) - Rust のネイティブの API にはバンドルのビルダーも署名者もなかったため、Rust のエージェントは
プリミティブからマニフェストを組み立てて署名していました。修正済み:
build_bundle_v2とsign_bundle_v2(dcp_ai::v2::bundle)で、エージェントは今それを使います。wasmのフィーチャーのwasm_build_bundleとwasm_sign_bundleは今それらを呼びます。以前はwasm_sign_bundleがcreated_at ""を書き、signer.idを書かなかったため、リファレンスの検証器はそれが署名したすべてのバンドルを拒否していました。 - 空の監査証跡:
spec/VERIFICATION.mdが認めるとおり(64 個のゼロのルート)、すべての検証器は 監査エントリーのない正しく署名されたバンドルを受け入れます。PQ のチェックポイントに使われるauditEventsMerkleRootのヘルパーは、4 つの SDK すべてで同じく空のリストを拒否します。
注記(2026 年 10 月 25 日)。 上の発見は、最初の実行で記録されたとおりに残しています。最初のものは
mainでは もう当てはまりません。4 つの SDK の検証器は今では、署名されていないsignatureのブロックを署名された内容と照合します (spec/VERIFICATION.mdV2-4)。signer.idは RPR のhuman_id、signer.kidsは複合署名の kid、manifest_hashは正規のマニフェストの SHA-256、hash_algは許可された値でなければならないため、ゼロは 拒否されます。signature.created_atは、どの検証器も認証しない、署名されていないメタデータのままです。
ロードマップ¶
- シナリオ 1(DCP-01 から 03)と 2(アドミッション)を CI で。
- DCP-04:現在すべての SDK は
A2A_HELLOとA2A_WELCOMEを作りますが、どれもA2A_CONFIRM/A2A_ESTABLISHEDや DCP-04 §3.4 のセッション鍵の導出を実装しておらず、メッセージが告げる ハイブリッドの X25519 + ML-KEM-768 の KEM を持つのは TypeScript だけで、Rust にはセッションの層がありません。 それらが揃えば、プロトコルにhello、welcome、confirm、send、receiveが加わり、 オーケストレーターは 2 つのエージェントの間でメッセージを再送し、並べ替え、つなぎ替えます。 - 異なる SDK のエージェントが 1 ホップずつ作る DCP-09 のマンデートの連鎖。