Episode ini membahas cara men-skalakan kolaborasi antar agent: connection pooling, caching agent card, load balancing, tradeoff streaming vs polling, meminimalkan payload, dan kapan memilih gRPC untuk throughput tinggi.

Di episode 17 kalian sudah menyusun task pattern tingkat lanjut: fan-out dan fan-in antar agent, alur human-in-the-loop lewat state input-required, hingga idempotency, retry, dan checkpoint untuk task berumur panjang. Semua pola itu berjalan baik ketika jumlah task masih puluhan. Masalah muncul ketika task menjadi ribuan per detik: pola yang benar secara logika bisa berantakan secara teknis.
Episode 18 ini adalah pijakan berikutnya: menjaga latensi dan stabilitas saat skala membesar. Kita akan membahas connection pooling supaya koneksi TCP tidak dibuka-buka setiap task, caching agent card agar traffic discovery tidak membebani, load balancing untuk banyak replika remote agent, tradeoff streaming vs polling, minimalisasi payload, dan kapan harus beralih ke binding gRPC untuk throughput tinggi.
Setiap handshake TCP dan TLS memakan waktu puluhan milidetik — murah untuk sekali, mahal untuk seribu kali. Client A2A yang ditulis naif biasanya membuka koneksi baru per task. Solusinya adalah connection pooling: satu koneksi HTTP keep-alive yang dipakai ulang untuk banyak request.
import httpx
from a2a_sdk import A2AClient
transport = httpx.AsyncHTTPTransport(
limits=httpx.Limits(max_connections=50, max_keepalive_connections=20)
)
async with httpx.AsyncClient(transport=transport, timeout=30.0) as client:
a2a = A2AClient(client)
for task in daftar_task:
result = await a2a.send_task(task)Perhatikan dua parameter httpx.Limits: max_connections membatasi total koneksi yang boleh terbuka, sedangkan max_keepalive_connections menentukan berapa koneksi idle yang disimpan hidup untuk pemakaian berikutnya. Nilai ini harus disesuaikan dengan batas koneksi di sisi remote agent — kalau client membuka 50 koneksi tapi server hanya menampung 20, sisanya mengantre di kernel. Aturan praktis: mulai dari 10-20 keepalive per host, lalu ukur.
Info
Connection pooling bekerja paling baik untuk remote agent yang dipanggil sering dalam satu proses. Untuk workload sekali-panggil (misalnya serverless yang memulai proses baru per request), pooling justru menambah kompleksitas tanpa manfaat — biarkan koneksi mati secara alami.
Sebelum task dikirim, client harus mengambil agent card untuk tahu kemampuan remote agent. Jika di-fetch setiap task, ini memboroskan bandwidth dan menambah latensi tiap request. Agent card jarang berubah — beda seminggu pun masih valid. Solusinya: cache dengan TTL.
import time
class AgentCardCache:
def __init__(self, ttl_seconds=3600):
self.ttl_seconds = ttl_seconds
self._store = {}
def get(self, url: str):
entry = self._store.get(url)
if entry and time.time() - entry["ts"] < self.ttl_seconds:
return entry["card"]
return None
def set(self, url: str, card: dict):
self._store[url] = {"card": card, "ts": time.time()}Setelah card di-cache, client bisa langsung mengirim tasks/send tanpa menunggu round-trip discovery. TTL harus lebih pendek dari jadwal rilis versi agent kalian: jika agent di-deploy beberapa kali sehari, turunkan TTL ke 300 detik; jika jarang berubah, 3600 detik aman. Untuk signed agent card (sudah dibahas di episode 9 dan akan diperdalam di episode 21), cache juga harus menyimpan hasil verifikasi tanda tangan agar verifikasi kriptografis tidak diulang.
Ketika satu remote agent sudah tidak sanggup menampung beban, jawabannya adalah replika — dan replika butuh load balancer. Tantangan khusus A2A: task itu stateful. Task yang sudah jalan di replika A harus dilanjutkan di replika A; jika diarahkan ke replika B, client tidak akan menemukan state-nya.
upstream a2a-agent {
least_conn;
server agent-a.internal:3000;
server agent-b.internal:3000;
server agent-c.internal:3000;
}
server {
listen 443 ssl;
location / {
proxy_pass http://a2a-agent;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Cookie $http_cookie;
}
}Strategi least_conn cocok karena task A2A punya durasi bervariasi — replika yang menerima task berat akan lebih sepi di koneksi berikutnya. Tapi kunci sebenarnya ada di sticky session: jika task state disimpan per-replika, pastikan client yang sama selalu mendarat di replika yang sama. Alternatif yang lebih kokoh adalah menyimpan state task di store bersama (Redis, database) sehingga replika mana pun bisa melanjutkan — lalu load balancer bisa memakai strategi murni tanpa stickiness. Jangan lupa health check memakai tasks/get dengan task dummy, bukan hanya ping TCP.
Episode 7 membahas SSE streaming dan push notification. Di level performa, keputusan ini menjadi tradeoff nyata:
async def poll_task(client, task_id: str, timeout: int = 120):
deadline = time.time() + timeout
delay = 1.0
while time.time() < deadline:
task = await client.get_task(task_id)
if task.status not in {"submitted", "working"}:
return task
await asyncio.sleep(delay)
delay = min(delay * 1.5, 10.0)
raise TimeoutError("task terlalu lama")Polling dengan exponential backoff adalah kompromi terbaik untuk mayoritas kasus: task pendek terdeteksi cepat, task panjang tidak menebak interval tetap. Gunakan streaming hanya ketika interaktivitas real-time itu bernilai — misalnya agent menampilkan hasil parsial kepada user yang menunggu.
Payload besar adalah musuh latensi diam-diam. Antrean jaringan diisi data yang tidak perlu, dan parsing JSON memakan CPU di kedua sisi. Tiga teknik yang langsung terasa:
file punya dua bentuk: dataURI (isi byte disematkan) atau URL (referensi). Untuk file besar, kirim URL; isi file di-download terpisah, paralel, dan bisa di-cache.{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{ "type": "text", "text": "Ambil detail order ini" },
{
"type": "file",
"url": "https://cdn.internal/orders/8321.json",
"mimeType": "application/json"
}
]
}
}
}Perhatikan bagian file di atas: file 2 MB tidak dikirim lewat wire A2A — hanya URL-nya. Ini yang membedakan sistem yang sanggup ratusan task per detik dari yang macet di 20 task per detik.
Kalau pooling, payload kecil, dan polling masih belum cukup — misalnya kawanan agent mengirim task sepanjang hari dengan volume sangat tinggi — saatnya binding gRPC. Keunggulan utamanya:
syntax = "proto3";
message Task {
string id = 1;
string status = 2;
repeated string messages = 3;
}
message TaskQuery {
string id = 1;
}
service A2AAgentService {
rpc SendMessage(stream TaskQuery) returns (stream Task);
rpc GetTask(TaskQuery) returns (Task);
}gRPC bukan pengganti — ia memakai ulang server handler yang sama dengan binding HTTP/JSON (kita sudah bahas gateway-nya di episode 11). Strategi yang sehat: jadikan HTTP/JSON sebagai default publik, dan gRPC sebagai jalur internal antar agent yang saling kenal. Client internal yang sibuk bisa langsung bicara gRPC, sementara ekosistem luar tetap memakai JSON-RPC standar.
Episode 18 menaikkan level dari "task berjalan benar" menjadi "task berjalan benar dalam ribuan". Connection pooling memotong biaya handshake, caching agent card meniadakan traffic discovery berulang, load balancing menyebar beban sambil menjaga state task, polling backoff menyeimbangkan latensi dan kesederhanaan, payload ringkas mengosongkan bandwidth, dan gRPC membuka jalur throughput tinggi antar agent internal.
Inti yang harus dibawa pulang:
Di episode 19 kita masuk wilayah yang lebih menarik: ekosistem x402 dan commerce — bagaimana agent bisa membayar agent lain lewat ekstensi HTTP 402, mandate, dan approval. Sampai jumpa!