Backend
Tutorial Protokol A2A: Agent Card, Task, dan Binding v1.0
Oktober 202612 menit baca

Agent2Agent (A2A) adalah protokol terbuka di bawah tata kelola Linux Foundation yang memungkinkan satu AI agent menemukan agent lain dan memberinya pekerjaan. Agent tujuan memublikasikan Agent Card yang menjelaskan skill dan cara autentikasinya, lalu client mengirim message yang membuat task yang bisa dilacak. MCP menghubungkan agent ke tool dan data; A2A menghubungkan agent dengan agent lain.
Spesifikasi 1.0 mendaftarkan well-known URI /.well-known/agent-card.json, sehingga client bisa menemukan card sebuah agent hanya dari domainnya. Card juga bisa didapat dari registry atau konfigurasi langsung. Card publik sebaiknya disajikan tanpa autentikasi dengan header Cache-Control dan ETag, sedangkan extended card yang terautentikasi tersedia lewat GetExtendedAgentCard jika agent mengaktifkannya.
TaskState memiliki sembilan nilai: UNSPECIFIED, SUBMITTED, WORKING, INPUT_REQUIRED, AUTH_REQUIRED, COMPLETED, FAILED, CANCELED, dan REJECTED, masing-masing dengan prefix TASK_STATE_. COMPLETED, FAILED, CANCELED, dan REJECTED bersifat terminal dan tidak menerima message lagi. INPUT_REQUIRED dan AUTH_REQUIRED adalah state interrupted, di mana client membalas atau memberikan credential lalu task yang sama berlanjut.
Ketiga binding menyediakan sebelas operasi yang sama, jadi pilihannya bersifat operasional, bukan fungsional. JSON-RPC 2.0 paling mudah untuk memulai dan di-debug, HTTP+JSON cocok untuk caller di belakang gateway yang melakukan routing berdasarkan path, dan gRPC pas untuk mesh internal yang sudah memakai HTTP/2. Agent Card bisa mencantumkan beberapa interface sesuai urutan prioritas, dan client memilih yang pertama didukungnya.
A2A mewajibkan TLS dan autentikasi di setiap request, tetapi menyerahkan otorisasi kepada masing-masing implementasi. Analisis A2ABreak melaporkan 11 kelemahan di tingkat spesifikasi, termasuk cross-client context injection, URL webhook yang tidak diverifikasi, dan hilangnya identitas di rantai delegasi. Ikat task dan context ke caller yang terautentikasi, validasi URL webhook terhadap SSRF, dan beri timeout pada task yang interrupted.

Ringkasan Utama
A2A 1.0 adalah protokol Linux Foundation untuk satu agent memanggil agent lain. Agent memublikasikan Agent Card di /.well-known/agent-card.json, menerima message yang membuat task dengan state submitted, working, interrupted, dan terminal, mengirim update lewat Server-Sent Events atau webhook, serta menyediakan sebelas operasi yang sama lewat JSON-RPC, gRPC, atau HTTP+JSON.
Pertanyaan awal saya sempit: bisakah purchasing agent milik distributor menanyakan ke ERP kami apakah sebuah SKU tersedia di gudang tertentu, tanpa harus membuat API partner khusus lagi beserta PDF penjelasannya? Integrasi partner di ERP biasanya memang berupa pasangan itu, endpoint REST plus dokumen, dan keduanya cepat tidak sinkron begitu salah satunya berubah. Agent2Agent (A2A) menawarkan kontrak yang berbeda: agent mendeskripsikan dirinya dalam card yang bisa dibaca mesin, dan client mana pun yang patuh spesifikasi bisa menemukannya, melakukan autentikasi, lalu memberinya pekerjaan.
Tutorial protokol A2A ini membahas spesifikasi 1.0 dari sudut pandang developer yang harus mengimplementasikannya: Agent Card dan signature-nya, task lifecycle beserta state yang sering membuat bingung, streaming versus push notification, dan tiga binding standar. Di bagian akhir ada server minimal berbasis SDK JavaScript resmi yang membuka agent cek stok ERP, ditambah celah keamanan pada protokol itu sendiri yang ditemukan sebuah analisis pada September 2026. Semua nama field dan method di bawah berasal dari spesifikasi yang dipublikasikan atau dari source code SDK.
A2A diumumkan Google pada April 2025 dan diserahkan ke Linux Foundation pada Juni 2025; repository spesifikasinya memberi tag v1.0.0 pada Maret 2026 dan rilis perbaikan bug v1.0.1 pada Mei 2026. Spesifikasinya berlapis: data model kanonis yang didefinisikan dalam satu file Protocol Buffers sebagai satu-satunya sumber normatif, sekumpulan operasi abstrak, dan protocol binding yang memetakan operasi tersebut ke wire. Akibat praktisnya, sebelas operasi yang sama tersedia apa pun binding yang Anda pilih, dengan nama method identik di JSON-RPC dan gRPC.
| Operasi | Method JSON-RPC dan gRPC | Endpoint HTTP+JSON |
|---|---|---|
| Mengirim message | SendMessage | POST /message:send |
| Mengirim message dan streaming update | SendStreamingMessage | POST /message:stream |
| Membaca satu task | GetTask | GET /tasks/{id} |
| Daftar task, dengan cursor pagination | ListTasks | GET /tasks |
| Membatalkan task | CancelTask | POST /tasks/{id}:cancel |
| Subscribe ulang ke task yang berjalan | SubscribeToTask | POST /tasks/{id}:subscribe |
| Mendaftarkan webhook untuk task | CreateTaskPushNotificationConfig | POST /tasks/{id}/pushNotificationConfigs |
| Membaca satu konfigurasi webhook | GetTaskPushNotificationConfig | GET /tasks/{id}/pushNotificationConfigs/{configId} |
| Daftar konfigurasi webhook sebuah task | ListTaskPushNotificationConfigs | GET /tasks/{id}/pushNotificationConfigs |
| Menghapus konfigurasi webhook secara idempoten | DeleteTaskPushNotificationConfig | DELETE /tasks/{id}/pushNotificationConfigs/{configId} |
| Mengambil extended card yang terautentikasi | GetExtendedAgentCard | GET /extendedAgentCard |
Dua hal dalam tabel itu lebih penting daripada kelihatannya. ListTasks baru ada dibanding 0.3, dan spesifikasi mewajibkannya hanya mengembalikan task yang boleh dilihat caller terautentikasi, diurutkan berdasarkan update terakhir. Selain itu, setiap request sebaiknya membawa header A2A-Version: spesifikasi menyatakan nilai kosong dianggap 0.3, jadi client yang lupa mengirimnya diam-diam bernegosiasi memakai protokol lama dengan server yang masih mendukung keduanya.
Agent Card adalah kontrak publik sebuah agent, dan spesifikasi mendaftarkan well-known URI untuknya sehingga discovery cukup bermodal domain. Berikut card untuk agent cek stok, disajikan tanpa autentikasi karena client harus membacanya dulu sebelum tahu cara melakukan autentikasi.
GET /.well-known/agent-card.json HTTP/1.1
Host: stock-agent.example-erp.co.id
{
"name": "ERP Stock Check Agent",
"description": "Answers available-to-promise stock per SKU and warehouse for approved distributors.",
"version": "1.3.0",
"supportedInterfaces": [
{ "url": "https://stock-agent.example-erp.co.id/a2a/jsonrpc",
"protocolBinding": "JSONRPC", "protocolVersion": "1.0" },
{ "url": "https://stock-agent.example-erp.co.id/a2a/rest",
"protocolBinding": "HTTP+JSON", "protocolVersion": "1.0" }
],
"provider": { "organization": "Example Distribution", "url": "https://example-erp.co.id" },
"capabilities": {
"streaming": true,
"pushNotifications": true,
"extendedAgentCard": false
},
"securitySchemes": {
"partnerBearer": {
"httpAuthSecurityScheme": { "scheme": "bearer", "bearerFormat": "JWT" }
}
},
"securityRequirements": [ { "schemes": { "partnerBearer": { "list": [] } } } ],
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["application/json"],
"skills": [
{
"id": "stock-availability",
"name": "Stock availability",
"description": "On-hand minus reserved quantity for one SKU in one warehouse.",
"tags": ["erp", "inventory", "stock"],
"examples": ["Is SKU BRG-10422 available in warehouse SBY-01?"]
}
],
"signatures": [ { "protected": "eyJhbGciOiJFUzI1NiIs...", "signature": "QFdkNLNs..." } ]
}Sajikan card dengan Cache-Control max-age dan ETag yang diturunkan dari versi card atau hash isinya, sesuai rekomendasi spesifikasi, supaya client melakukan revalidasi dengan If-None-Match alih-alih mengunduhnya di setiap panggilan. Jangan cantumkan hostname internal atau apa pun yang sensitif: card publik, sesuai definisinya, bisa dibaca siapa saja, dan spesifikasi mengingatkan bahwa extended card yang terautentikasi pun sebaiknya tidak memuat URL layanan internal.
Agent Card boleh ditandatangani dengan JSON Web Signature, RFC 7515, agar client bisa memastikan card tidak diubah selama transit atau oleh cache. Bagian sulitnya bukan kriptografi, melainkan menghasilkan byte yang sama di kedua sisi; karena itu spesifikasi mewajibkan JSON Canonicalization Scheme, RFC 8785, setelah tahap pembuangan nilai default. Prosedur penandatanganannya:
// Before canonicalisation (as served):
{
"name": "Example Agent",
"description": "",
"capabilities": { "streaming": false, "pushNotifications": false, "extensions": [] },
"skills": []
}
// After default-stripping + RFC 8785 JCS -- this exact byte string is what gets signed.
// "extensions": [] is gone (repeated, not REQUIRED); "description": "" stays (REQUIRED);
// streaming:false stays because it was explicitly set on an optional field.
{"capabilities":{"pushNotifications":false,"streaming":false},"description":"","name":"Example Agent","skills":[]}
// Protected header, base64url-decoded:
{"alg":"ES256","typ":"JOSE","kid":"key-1","jku":"https://example.com/agent/jwks.json"}Verifikasi adalah kebalikannya: buang nilai default, keluarkan signatures, kanonisasi, ambil public key berdasarkan kid dan jku lewat HTTPS atau dari trusted key store, lalu periksa. SDK JavaScript resmi menyediakan helper canonicalizeAgentCard dan verifyAgentCardSignature serta hook penandatanganan di request handler-nya, jadi Anda tidak perlu menulis JCS sendiri. Yang sering menjebak adalah melakukan serialisasi card lewat library yang menambahkan array kosong atau boolean default yang dibuang oleh penandatangan, sehingga card terlihat valid tetapi signature-nya tidak pernah lolos verifikasi.
Signature yang valid membuktikan siapa yang memublikasikan card, bukan bahwa skill yang tercantum nyata atau aman. Analisis A2ABreak mencatat klaim skill tanpa atestasi dan celah antara kepercayaan key dan klaim kemampuan sebagai dua temuan terpisah. Perlakukan card bertanda tangan sebagai iklan yang terautentikasi, dan kunci domain jku ke provider yang benar-benar terikat kontrak dengan Anda.
Message adalah satu giliran; Task adalah unit kerja ber-state yang bisa dibuat oleh sebuah message. Server boleh menjawab SendMessage dengan Message langsung untuk request sederhana, atau dengan Task yang dilacaknya. TaskState mendefinisikan sembilan nilai, yang pertama TASK_STATE_UNSPECIFIED, dan delapan sisanya terbagi menjadi tiga kelompok perilaku yang harus diperlakukan berbeda oleh kode client Anda.
| State | Jenis | Yang harus dilakukan client |
|---|---|---|
TASK_STATE_SUBMITTED | Aktif | Sudah diterima, belum dimulai. Biarkan stream terbuka atau lakukan polling. |
TASK_STATE_WORKING | Aktif | Sedang diproses. Tunggu event status dan artifact. |
TASK_STATE_INPUT_REQUIRED | Interrupted | Baca status message, lalu balas pada taskId yang sama. |
TASK_STATE_AUTH_REQUIRED | Interrupted | Dapatkan credential di luar jalur, lalu subscribe, polling, atau balas. |
TASK_STATE_COMPLETED | Terminal | Baca artifact-nya. Task tidak menerima message lagi. |
TASK_STATE_FAILED | Terminal | Catat status message-nya. Retry berarti task baru. |
TASK_STATE_CANCELED | Terminal | Pastikan pembatalan berhasil. CancelTask adalah permintaan, bukan jaminan. |
TASK_STATE_REJECTED | Terminal | Agent menolak. Jangan mengulang input yang sama begitu saja. |
Mode blocking adalah default. Kecuali client menyetel returnImmediately ke true di konfigurasi pengiriman, SendMessage wajib menunggu sampai task mencapai state terminal atau interrupted sebelum mengembalikan respons. Itu tidak masalah untuk cek stok, tetapi fatal untuk rekonsiliasi tiga jam di belakang load balancer dengan timeout 60 detik; jadi agent yang berjalan lama sebaiknya dipanggil secara non-blocking, dengan stream, polling, atau webhook yang membawa hasilnya. Mengirim message ke task yang sudah terminal menghasilkan UnsupportedOperationError; pertanyaan lanjutan setelah selesai harus menjadi task baru di contextId yang sama.
Versi 1.0 menghapus discriminator kind. Di 0.3, text part adalah objek dengan kind bernilai text; di 1.0 nama member-lah yang menjadi discriminator, sehingga text part cukup berupa objek dengan member text, dan event stream datang terbungkus sebagai statusUpdate atau artifactUpdate. Bentuk nilai enum juga berubah, menjadi TASK_STATE_COMPLETED dan ROLE_USER. Parser 0.3 yang ditulis manual akan gagal pada event 1.0 pertama, jadi periksa setiap client yang tidak Anda kendalikan sebelum mematikan mode kompatibilitas.
SDK resmi @a2a-js/sdk mencapai lini stabil v1.0 pada Juli 2026 dan mengimplementasikan ketiga binding di balik satu DefaultRequestHandler. Logika Anda berada di AgentExecutor, yang menerima RequestContext dan memublikasikan event Task, status, dan artifact ke event bus. Executor di bawah adalah seluruh agent-nya: menolak partner yang tidak dikenal, meminta input yang kurang alih-alih gagal, memeriksa akses gudang, dan mengembalikan jawaban sebagai data part terstruktur.
// stock-executor.ts -- npm install @a2a-js/sdk express
import { Task, TaskState, Role, Part, Message } from "@a2a-js/sdk";
import {
AgentExecutor, AgentEvent, RequestContext, ExecutionEventBus,
} from "@a2a-js/sdk/server";
import { erp } from "./erp-client"; // your existing inventory service
import { PartnerUser } from "./partner-auth";
const SKU = /\b[A-Z]{3}-\d{4,6}\b/;
const WAREHOUSE = /\b[A-Z]{3}-\d{2}\b/;
const text = (value: string): Part => ({
content: { $case: "text", value }, metadata: undefined, filename: "", mediaType: "text/plain",
});
function agentMessage(ctx: RequestContext, value: string): Message {
return {
role: Role.ROLE_AGENT, messageId: crypto.randomUUID(), parts: [text(value)],
taskId: ctx.taskId, contextId: ctx.contextId, extensions: [], metadata: {}, referenceTaskIds: [],
};
}
export class StockExecutor implements AgentExecutor {
async execute(ctx: RequestContext, bus: ExecutionEventBus): Promise<void> {
const { taskId, contextId } = ctx;
const status = (state: TaskState, note?: string) =>
bus.publish(AgentEvent.statusUpdate({
taskId, contextId, metadata: undefined,
status: { state, timestamp: new Date().toISOString(),
message: note ? agentMessage(ctx, note) : undefined },
}));
// A streaming turn MUST open with a Task (or a single Message).
const snapshot: Task = ctx.task ?? {
id: taskId, contextId, artifacts: [], history: [ctx.userMessage], metadata: {},
status: { state: TaskState.TASK_STATE_SUBMITTED, timestamp: new Date().toISOString(),
message: undefined },
};
bus.publish(AgentEvent.task(snapshot));
const partner = ctx.context?.user;
if (!(partner instanceof PartnerUser)) {
return status(TaskState.TASK_STATE_REJECTED, "Unknown partner.");
}
// Read every text part across the task history, so a follow-up turn that
// only says "SBY-01" still finds the SKU sent in the first turn.
const said = [...(ctx.task?.history ?? []), ctx.userMessage]
.flatMap((m) => m.parts)
.map((p) => (p.content?.$case === "text" ? p.content.value : ""))
.join(" ");
const sku = said.match(SKU)?.[0];
const warehouse = said.match(WAREHOUSE)?.[0];
// Interrupted, not failed: the same taskId continues when the partner answers.
if (!sku || !warehouse) {
return status(TaskState.TASK_STATE_INPUT_REQUIRED,
"Send the SKU (e.g. BRG-10422) and warehouse code (e.g. SBY-01).");
}
// Authorise against the partner's contract, not against what the card advertises.
if (!partner.warehouses.includes(warehouse)) {
return status(TaskState.TASK_STATE_REJECTED, `No access to warehouse ${warehouse}.`);
}
status(TaskState.TASK_STATE_WORKING);
const { onHand, reserved } = await erp.stockLevel(sku, warehouse);
bus.publish(AgentEvent.artifactUpdate({
taskId, contextId, append: false, lastChunk: true, metadata: undefined,
artifact: {
artifactId: crypto.randomUUID(), name: "stock-level", description: "",
metadata: undefined, extensions: [],
parts: [{
content: { $case: "data",
value: { sku, warehouse, onHand, reserved, available: onHand - reserved } },
metadata: undefined, filename: "", mediaType: "application/json",
}],
},
}));
status(TaskState.TASK_STATE_COMPLETED);
}
// Stock lookups finish in one round trip; there is nothing to abort mid-flight.
cancelTask = async (): Promise<void> => {};
}Ada tiga keputusan yang disengaja di sini. Input yang kurang menjadi TASK_STATE_INPUT_REQUIRED, bukan FAILED, karena task yang interrupted tetap memegang taskId-nya dan agent milik partner cukup membalas. Otorisasi diperiksa terhadap kontrak partner di dalam executor, karena card mengiklankan apa yang bisa dilakukan agent untuk siapa saja, bukan apa yang boleh dilakukan caller ini. Dan hasilnya berupa data part dengan mediaType application/json, sehingga agent pemanggil mem-parsing angka, bukan kalimat. Menghubungkannya ke HTTP hanya butuh beberapa baris:
// server.ts
import express from "express";
import { AGENT_CARD_PATH } from "@a2a-js/sdk";
import { DefaultRequestHandler, InMemoryTaskStore } from "@a2a-js/sdk/server";
import { agentCardHandler, jsonRpcHandler, restHandler } from "@a2a-js/sdk/server/express";
import { stockAgentCard } from "./agent-card"; // the card shown above, as an AgentCard
import { StockExecutor } from "./stock-executor";
import { requirePartnerToken, partnerUserBuilder } from "./partner-auth";
// InMemoryTaskStore loses every task on restart -- an INPUT_REQUIRED task the
// partner answers after a deploy becomes TaskNotFoundError (-32001).
// Swap in DatabaseTaskStore from @a2a-js/sdk/server/database for production.
const handler = new DefaultRequestHandler(stockAgentCard, new InMemoryTaskStore(), new StockExecutor());
const app = express();
// The public card stays unauthenticated: discovery happens before credentials exist.
app.use(`/${AGENT_CARD_PATH}`, agentCardHandler({ agentCardProvider: handler }));
// Everything else requires the partner's bearer token (spec 7.4: authenticate EVERY request).
app.use("/a2a", requirePartnerToken);
app.use("/a2a/jsonrpc", jsonRpcHandler({ requestHandler: handler, userBuilder: partnerUserBuilder }));
app.use("/a2a/rest", restHandler({ requestHandler: handler, userBuilder: partnerUserBuilder }));
app.listen(8080);Modul partner-auth, yang tidak ditampilkan, berisi middleware Express biasa yang memvalidasi bearer token dan sebuah UserBuilder yang mengubah request tervalidasi menjadi objek PartnerUser berisi id partner dan daftar gudang yang diizinkan; sample autentikasi milik SDK memakai pola yang sama dengan Passport. Memasang jsonRpcHandler dan restHandler pada request handler yang sama berarti satu card bisa mencantumkan kedua interface tanpa logika ganda. Binding gRPC dipasang dengan cara serupa lewat grpcService dari subpath gRPC, tetapi membutuhkan peer dependency grpc-js dan protobuf serta hanya berjalan di Node.
Streaming di binding JSON-RPC dan HTTP+JSON memakai Server-Sent Events. Setiap event adalah StreamResponse yang berisi tepat satu dari task, message, statusUpdate, atau artifactUpdate, dan stream sebuah task wajib diawali objek Task lalu ditutup ketika task mencapai state terminal. Jika koneksi terputus, SubscribeToTask menyambung kembali dan, menurut spesifikasi, mengirim Task terkini lebih dulu sehingga tidak ada yang hilang di antara GetTask dan stream baru.
curl -N https://stock-agent.example-erp.co.id/a2a/jsonrpc \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PARTNER_TOKEN" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "SendStreamingMessage",
"params": { "message": {
"messageId": "9b7c1f0e-0d5a-4f43-9d1e-2a1f6b1f3c11", "role": "ROLE_USER",
"parts": [ { "text": "Is BRG-10422 available in SBY-01?" } ] } }
}'
# Content-Type: text/event-stream -- one StreamResponse per event, exactly one member set:
data: {"jsonrpc":"2.0","id":1,"result":{"task":{"id":"t-81","contextId":"c-12","status":{"state":"TASK_STATE_SUBMITTED"}}}}
data: {"jsonrpc":"2.0","id":1,"result":{"statusUpdate":{"taskId":"t-81","contextId":"c-12","status":{"state":"TASK_STATE_WORKING"}}}}
data: {"jsonrpc":"2.0","id":1,"result":{"artifactUpdate":{"taskId":"t-81","contextId":"c-12","lastChunk":true,"artifact":{"artifactId":"a-1","name":"stock-level","parts":[{"data":{"sku":"BRG-10422","warehouse":"SBY-01","onHand":340,"reserved":120,"available":220},"mediaType":"application/json"}]}}}}
data: {"jsonrpc":"2.0","id":1,"result":{"statusUpdate":{"taskId":"t-81","contextId":"c-12","status":{"state":"TASK_STATE_COMPLETED"}}}}
# stream closes on the terminal stateKetika caller tidak bisa mempertahankan koneksi, push notification mengirim bentuk StreamResponse yang sama ke webhook. Konfigurasinya bisa ikut dalam request pengiriman, seperti contoh di bawah lewat binding REST, atau didaftarkan belakangan dengan CreateTaskPushNotificationConfig. Pengirimannya at-least-once, jadi penerima wajib menjawab dengan status 2xx, menganggap duplikat sebagai hal normal, dan memeriksa bahwa taskId memang milik task yang dibuatnya.
# Same operation over the HTTP+JSON binding, fire-and-forget with a webhook.
POST /a2a/rest/message:send HTTP/1.1
Content-Type: application/a2a+json
Authorization: Bearer <partner token>
A2A-Version: 1.0
{
"message": { "messageId": "4e0d...", "role": "ROLE_USER",
"parts": [ { "text": "Reorder check: BRG-10422 in SBY-01" } ] },
"configuration": {
"returnImmediately": true, // default false = block until terminal/interrupted
"taskPushNotificationConfig": {
"url": "https://partner.example.com/a2a/webhook",
"token": "per-task-random-value", // echo-check this on every delivery
"authentication": { "scheme": "Bearer", "credentials": "<webhook secret>" }
}
}
}
# The agent later POSTs the same StreamResponse shapes to the webhook:
POST /a2a/webhook Content-Type: application/a2a+json Authorization: Bearer <webhook secret>
{ "statusUpdate": { "taskId": "t-82", "contextId": "c-12", "status": { "state": "TASK_STATE_COMPLETED" } } }Tidak ada yang melarang Anda membuka ketiganya; spesifikasi hanya meminta setiap interface yang tercantum setara secara fungsional. Untuk agent yang menghadap partner, taruh JSON-RPC di urutan pertama karena paling mudah di-debug dengan curl, dan tambahkan REST di urutan kedua hanya jika gateway partner membutuhkan routing berbasis path. Setiap binding tambahan adalah permukaan baru yang harus diautentikasi dan diuji.
A2ABreak, paper yang diunggah ke arXiv pada September 2026 oleh tim peneliti termasuk Elisa Bertino, memodelkan spesifikasi A2A sebagai state machine dengan 37 state dan 76 transisi, lalu melaporkan 11 kerentanan yang bisa dieksploitasi oleh penyerang yang mengikuti spesifikasi secara persis. Ini bukan bug implementasi yang hilang dengan meng-upgrade library; ini titik-titik di mana server yang patuh spesifikasi tetap tidak aman kecuali Anda menambahkan kontrol. Yang paling relevan untuk agent ERP yang menghadap partner:
Sisa daftarnya mencakup klaim skill tanpa atestasi, signature yang mengautentikasi identitas tetapi bukan kemampuan, stream SSE yang terus bocor setelah pencabutan akses, chunk artifact yang dirakit ulang tanpa pemeriksaan integritas, race condition modifikasi bersamaan, dan delegasi melingkar. Benang merahnya: A2A mendefinisikan cara agent berbicara, dan sengaja menyerahkan urusan siapa boleh melakukan apa kepada setiap implementasi. Bagian 13.1 spesifikasi menyatakannya dengan jelas: pemeriksaan otorisasi wajib dilakukan di setiap operasi dan sebelum query apa pun yang bisa mengungkap ada tidaknya sebuah resource.
Batasi GetTask dan ListTasks ke caller sebelum menyentuh database. Mengembalikan TaskNotFoundError untuk task milik partner lain sudah benar; mengembalikan error izin justru memberi tahu caller bahwa taskId itu ada. Dengan task ID yang berurutan atau mudah ditebak, perbedaan itu mengubah agent stok Anda menjadi oracle untuk volume order pesaing.
A2A 1.0 memberi tim ERP sesuatu yang tidak pernah diberikan endpoint REST plus PDF: kontrak yang mendeskripsikan dirinya sendiri, bisa ditandatangani, dengan task lifecycle yang nyata dan pilihan transport. Yang tidak diberikannya adalah otorisasi, masa kedaluwarsa, atau identitas lintas hop, jadi aturan yang saya pegang sederhana: biarkan protokol membawa percakapannya, dan simpan setiap keputusan tentang siapa boleh melihat stok yang mana di kode Anda sendiri, diperiksa di setiap panggilan.
Sumber