Consider a multi-agent fraud detection pipeline in action. Five autonomous agents, each running their own specialized LLM, need to communicate in real time to make decisions on suspicious wire transactions. The agents are smart enough, the models are fast enough, but the communication infrastructure, i.e., the wire protocol, takes 400ms per hop. With five agents to communicate through, this means it takes two seconds for the agents to communicate, make a decision, and act on it. But, by the time the decision is made, the money has already been transferred.The problem isn't the model. The problem isn't the agents. The problem isn't the infrastructure running the agents or the model. The problem is the wire protocol. Moving from the current wire protocol, i.e., REST/JSON, to gRPC Bidirectional Streaming, we can reduce the overall orchestration latency from 2.1 seconds to 420ms. In this article, we will see how this works, and we will also see the "JSON Tax" as the silent killer of the agentic era.The JSON Tax: Death by a Thousand ParsesThe cost of every interaction between two agents using the REST w/ JSON protocol is:Serialization Tax - The Python objects have to be serialized to JSON and then parsed again on the receiving end. This is not free, especially for complex objects such as the fraud analysis report, which has 40 fields.Payload Bloat - JSON is human-readable, which is nice for debugging. However, it also means it is not machine-friendly. The machine has to send the field names such as transaction_amount in every single message. In contrast, Protobuf would represent the same information in 60-80% fewer bytes.Synchronous Lock Step - HTTP 1.1 is a request-response protocol. Agent A makes a request, then waits for the response, then Agent B makes a request, and so on. There is no way for both Agent B and Agent A to think out loud.Lack of Schema Support - JSON has no schema. This means that Agent A can send {"amout": 500}, and the typo is not caught by the recipient. This is a disaster in a highly regulated industry.While the cost of each of these operations is low, when multiplied by the many calls made between agents for a single transaction, and then multiplied by the many transactions per minute, the tax becomes a real bottleneck.What the JSON Tax Looks Like in PracticeEach arrow is a full round-trip over HTTP, which entails a TCP handshake, serialization of the request data into JSON, network transfer, deserialization of the response data, and so on. For agents that need to transfer dozens of intermediate reasoning tokens, this synchronous model is like requiring a team of analysts to communicate by mail instead of just talking to each other in the same office.Enter gRPC: The Industrial Nervous SystemgRPC is a high-performance RPC framework built on top of HTTP/2 and Protocol Buffers (protobuf). While REST and JSON represent the postal service, gRPC represents the direct neural link. Here’s why it matters with respect to agent-to-agent communication:| Feature | REST/JSON | gRPC/Protobuf ||----|----|----|| Encoding | Text (JSON) | Binary (Protobuf) || Schema | None (hope for the best) | Strict .proto contracts || Transport | HTTP/1.1 (one request at a time) | HTTP/2 (multiplexed streams) || Streaming | Not native (polling/SSE hacks) | First-class bidirectional || Payload size | 100% baseline | 20-40% of JSON equivalent || Code generation | Manual serialization | Auto-generated typed clients |The Architecture ShiftBut rather than a sequence of sequential HTTP calls, each agent has a persistent bidirectional stream to the central Agent Hub. The Orchestrator pushes the task onto the stream, and agents can read and write the stream concurrently. The Fraud Agent can send partial results to the Risk Agent while it is still processing. No waiting. No polling. No JSON parsing.Protobuf: The Typed Contract That Eliminates Wire HallucinationsThere is a well-known effect that occurs when building systems with REST and JSON. It is called "hallucinations on the wire." It happens when agents hallucinate data that is not actually there. It might be a missing field here, a float where an integer was expected there, or a nested object where a string was expected. In a financial system, this is not a nuisance. It is a compliance failure.Protobuf completely avoids all of these problems by providing a typed schema contract.Defining the Agent Communication Contractsyntax = "proto3";package agent_swarm;message AgentMessage { string agent_id = 1; string task_id = 2; MessageType type = 3; oneof payload { TransactionAnalysis transaction = 4; FraudScore fraud_score = 5; RiskAssessment risk_assessment = 6; ComplianceVerdict compliance_verdict = 7; }}enum MessageType { TASK_ASSIGNMENT = 0; PARTIAL_RESULT = 1; FINAL_RESULT = 2; ERROR = 3;}message FraudScore { double score = 1; // 0.0 to 1.0 string model_version = 2; repeated string indicators = 3; double confidence = 4;}// The key: bidirectional streaming serviceservice AgentHub { rpc AgentStream (stream AgentMessage) returns (stream AgentMessage);}The .proto file is the single source of truth. If the Fraud Agent attempts to send a score of type string rather than double, the code will not compile. No run-time surprises. No data corruption. No compliance issues.Bidirectional Streaming: Agents That Think Out LoudThis is where the magic happens! In a traditional REST-based system, communication is a simple "turn-based" process. Request, wait, response, repeat. With gRPC bidirectional streaming, however, Agents have a persistent full-duplex channel and may send/receive messages concurrently!How Streaming Changes the Game\Observe the following: The Fraud Agent sent partial results even before finishing processing. The Risk Agent received the heuristic score and began processing even before the Fraud Agent had finished waiting for the ML model.The 80% Latency Reduction: Where the Time GoesLet us now see exactly where time is being reduced. The following figures are based on benchmark tests averaged over 10,000 simulated transactions:REST/JSON Pipeline (Before)Orchestrator → Fraud Agent: ~400ms - TCP handshake: 15ms - JSON serialize request: 8ms - Network transit: 12ms - JSON parse request: 6ms - Agent processing: 300ms - JSON serialize response: 12ms - Network transit: 12ms - JSON parse response: 8ms - Connection teardown: 5ms - HTTP overhead: 22ms× 5 agents in chain = ~2,100ms totalgRPC Streaming Pipeline (After)Orchestrator → Hub → All Agents: ~420ms - Stream already open: 0ms (persistent connection) - Protobuf serialize: 1ms (binary, no field names) - Network transit: 5ms (HTTP/2 multiplexed) - Protobuf deserialize: 1ms - Parallel agent processing: 350ms (overlapping execution) - Partial result streaming: 0ms (piggybacked on open stream) - Final aggregation: 50ms - No connection teardown: 0msTotal: ~420ms (80% reduction)The Savings VisualizedThe key insight here is that gRPC streams not only make each call faster but also change the execution model from sequential to overlapping. That means agents can get to work with partial data as soon as possible, rather than waiting for the complete data from the previous agent.Wire Hallucinations: The Bug That Protobuf KilledLet me tell you a story that illustrates why Protobuf is important.Suppose we're designing a system where the Risk Agent expects a fraud score between 0.0 and 1.0. Suppose a new version of the Fraud Agent has been deployed that starts returning a fraud score on a 0-100 scale. Since JSON has no schema validation at the wire level, the Risk Agent would happily receive {"fraud_score": 85.0} and assume an 8,500% fraud probability. Every single transaction would be flagged as critical for hours until the issue was discovered.This is what I call a wire hallucination: legal JSON but wrong data, with no protection from the REST/JSON stack.This class of bug cannot occur when using Protobuf. The FraudScore.score field is defined as a double in the .proto, and although Protobuf itself does not have inherent support for value range validation, it is trivial when using validation interceptors:class ValidationInterceptor(grpc.aio.ServerInterceptor): """gRPC interceptor that validates Protobuf message semantics.""" async def intercept_service(self, continuation, handler_call_details): handler = await continuation(handler_call_details) return handler @staticmethod def validate_fraud_score(message): """Validate that fraud scores are within expected bounds.""" if message.HasField("fraud_score"): score = message.fraud_score.score if not (0.0