Skip to main content
AgentKit backends scale like long-lived gRPC services. Rapida opens one Talk stream for each live conversation and keeps that stream attached to one backend replica until the conversation ends. Use a stable load-balanced endpoint as your AgentKit URL, such as agent.your-domain.com:50051. Scale the backend replicas behind that endpoint.
Active conversations are distributed when new streams are created. An active stream is not moved between replicas during the conversation.

Capacity benchmark per instance

There is no universal conversations-per-pod number. Capacity depends on your runtime, LLM latency, tool latency, memory use, streaming behavior, and how much state you keep in the process. Benchmark each AgentKit backend shape before setting production autoscaling limits. Track these metrics during a load test: Use the highest stable conversation count that keeps latency and error rate inside your SLO, then add headroom.
Example:
Use the example only as a sizing pattern. Replace safe_conversations_per_pod with your measured benchmark.

Text-only EC2 benchmark

Use this benchmark only for a text-only AgentKit backend. In this benchmark:
  • One active conversation means one open AgentKit Talk stream.
  • Rapida handles telephony, audio, STT, and TTS.
  • Your AgentKit backend receives text and returns text.
  • The benchmark backend does not call an LLM, database, workflow engine, or external tool.
  • Each message is small, usually under 4 KB.
  • Each active conversation sends one user message every 10 to 15 seconds.
  • A passing test keeps CPU under 60%, memory under 70%, and stream errors under 0.1%.
The numbers below are safe starting targets for capacity planning. They are not hard limits. Measure your own backend before using these numbers for a production commitment.

SDK concurrency knobs

AgentKit SDKs expose concurrency differently. Use max_workers guidance only for the Python SDK. For Node.js, use process and pod counts as the concurrency unit. If you use Node.js, Go, or async Python, start with these targets: Read the table like this:
For high availability, use at least two instances even when one larger instance can handle the expected load. If you use the synchronous Python starter, size by max_workers. Each open stream can occupy a worker thread, so the safe active conversation count is close to the worker count. Use this process to find your real number:
  1. Start at the target in the table.
  2. Keep all streams open for at least 30 minutes.
  3. Send one user message per conversation every 10 to 15 seconds.
  4. Increase active conversations by 25%.
  5. Stop when CPU exceeds 60%, memory exceeds 70%, p95 response latency increases sharply, or stream errors exceed 0.1%.
  6. Use the last passing value as your measured capacity.
  7. Plan production at 70% of measured capacity so you have headroom.
Before testing above 10,000 active streams on one host:
  • raise ulimit -n for the AgentKit process
  • confirm proxy and server HTTP/2 max stream settings
  • tune keepalive and idle timeouts across the load balancer, proxy, and server
  • use connection draining before deploys
  • track per-stream memory with production logging enabled

Horizontal scaling

Run multiple AgentKit backend replicas behind one gRPC-aware load balancer. Recommended production shape:
  • Run AgentKit on Kubernetes, ECS, Nomad, or another orchestrator that supports health checks and rolling deploys.
  • Put a gRPC-capable load balancer or service mesh in front of the replicas.
  • Configure the Rapida AgentKit endpoint to the load-balanced address.
  • Keep per-conversation state either stream-local or in an external store such as Redis or Postgres.
  • Drain replicas before deployment so existing streams can finish.
For Python AgentKit servers, tune max_workers based on your benchmark and blocking behavior:
For Node.js AgentKit servers, there is no max_workers setting. Run more Node.js processes, pods, or instances when you need more concurrency. Expose health checks for Kubernetes or the load balancer:
Scale on a mix of signals:

Traffic distribution by tenant or dynamic value

You can route AgentKit traffic dynamically with metadata and a gRPC-aware L7 proxy. Rapida sends saved AgentKit metadata on the Talk stream. For outbound calls, you can override or add metadata for one conversation with agentkit.metadata.
Distribution model:
  1. Rapida dials one stable AgentKit endpoint.
  2. The gRPC proxy inspects request metadata.
  3. The proxy routes the new Talk stream to the matching backend pool.
  4. The stream stays on that backend replica until the conversation ends.
Use this for:
  • tenant-specific backend pools
  • regional routing
  • premium or regulated customer isolation
  • model-tier routing
  • canary releases by tenant or percentage
A TCP/L4 load balancer cannot inspect AgentKit metadata. Use an L7 gRPC proxy such as Envoy, Istio, Linkerd, Traefik, or NGINX with gRPC routing support when routing depends on metadata.
Keep metadata keys simple and lowercase, such as tenant, region, or tier. If metadata can be influenced by customer input, validate or sign it before using it for authorization or isolation.

NGINX metadata routing

Use NGINX as an L7 gRPC proxy when you want one public AgentKit endpoint and multiple backend pools. This example routes by the AgentKit metadata key tenant:
Configure Rapida with the NGINX address as the AgentKit endpoint:
Then set per-call metadata when the conversation should go to a specific tenant pool:
If you use metadata key x-tenant-id, NGINX exposes it as $http_x_tenant_id. Hyphens become underscores in NGINX header variables.

Kubernetes NGINX router

A Kubernetes Service can load balance between pods, but it cannot inspect AgentKit metadata. To route by metadata, run a gRPC-aware proxy such as NGINX in front of tenant-specific AgentKit services. This example creates one public router service and two internal tenant backend services:
  • agentkit-acme
  • agentkit-globex
Point the Rapida AgentKit endpoint to the router service DNS name or load balancer DNS name. Each tenant backend can be a normal Kubernetes Deployment and Service. The router selects the service based on metadata; the selected service then load-balances across that tenant’s pods.
Repeat the backend Service and Deployment for each pool you reference in the NGINX map.
Do not use tenant metadata as the only security boundary. Validate the metadata at the proxy or backend, and use token auth or signed routing claims when tenant isolation matters.
External references:

Retries, failover, and draining

Retries are safe only before a stream is established. Once a conversation is active, retrying on another replica can duplicate tool calls or lose in-memory conversation state unless your backend is designed for resumability. Recommended retry policy: Production recommendations:
  • Set connectTimeoutMs low enough to fail over quickly during startup, but high enough for your private network.
  • Keep keepaliveTimeMs and keepaliveTimeoutMs aligned with your proxy and firewall timeouts.
  • Use readiness checks that fail before shutdown so new streams stop landing on draining pods.
  • Set a termination grace period long enough for normal conversations to finish.
  • Make tool calls idempotent with a stable tool_id where possible.
  • Store critical conversation state outside the process if the business requires recovery from pod failure.

Customer checklist

  • Benchmark safe active conversations per pod before production traffic.
  • Configure Rapida with a load-balanced agentKitUrl.
  • Use TLS and metadata-based auth for production.
  • Route by metadata only through a gRPC-aware L7 proxy.
  • Scale on active streams, latency, CPU, memory, and downstream provider limits.
  • Drain pods before deploys.
  • Treat active-stream failover as an application-level feature, not a load-balancer feature.