Agentic Notes Library

Chapter 4: Protocol Architecture — JSON-RPC, gRPC/Protobuf, and MCP as a Unified Typed Stack

An agentic platform that communicates through untyped strings, ad hoc HTTP endpoints, and free-form prompt concatenation is not an architecture—it is a fragility surface. The moment multiple agents coordinate, tools multiply, memory laye...

March 20, 2026 23 min read 5,024 words
Chapter 04MathRaw HTML

Preamble#

An agentic platform that communicates through untyped strings, ad hoc HTTP endpoints, and free-form prompt concatenation is not an architecture—it is a fragility surface. The moment multiple agents coordinate, tools multiply, memory layers diversify, and external applications integrate, every boundary without a typed contract becomes a latent failure mode. This chapter establishes the formal protocol stack that transforms an agentic system from prompt glue into a deterministic, observable, versionable, and production-hardened distributed system.

The thesis is precise: three protocol layers, each selected for the mechanical properties its boundary demands, unified through a common IDL strategy and cross-protocol gateway fabric. JSON-RPC 2.0 governs the application boundary where human-facing clients and external integrators require simplicity, schema transparency, and transport neutrality. gRPC/Protobuf governs internal agent-to-agent and agent-to-service execution where latency, streaming, and type safety are non-negotiable. The Model Context Protocol (MCP) governs tool, resource, and prompt surface discovery where dynamic registration, capability negotiation, and bidirectional context exchange define the interaction pattern. Together, they form a closed, typed, observable execution envelope around every agentic operation.


4.1 The Three-Layer Protocol Thesis: Boundary (JSON-RPC), Internal (gRPC), Discovery (MCP)#

4.1.1 Architectural Motivation#

Agentic systems exhibit three fundamentally distinct communication regimes, each imposing different constraints on serialization format, transport, latency tolerance, schema evolution, and discoverability. Collapsing these into a single protocol yields either excessive complexity at the boundary (forcing external clients to manage Protobuf compilation) or insufficient performance internally (routing all inter-agent traffic through JSON parsing). The three-layer thesis resolves this by assigning each protocol to its mechanical optimum.

Layer Classification:

LayerProtocolBoundary TypePrimary ConstraintSerializationTransport
BoundaryJSON-RPC 2.0Application ↔ PlatformSimplicity, universality, human readabilityJSONHTTP/1.1, WebSocket
InternalgRPC/ProtobufAgent ↔ Agent, Agent ↔ ServiceLatency, streaming, type safety, backpressureProtobuf (binary)HTTP/2
DiscoveryMCPAgent ↔ Tool/Resource/PromptDynamic registration, capability negotiation, context exchangeJSON (MCP schema)stdio, SSE/HTTP

4.1.2 Formal Layer Separation Invariants#

Let P={Pboundary,Pinternal,Pdiscovery}\mathcal{P} = \{P_{\text{boundary}}, P_{\text{internal}}, P_{\text{discovery}}\} denote the protocol set. For any message mm traversing the system, define the protocol assignment function:

π(m):MP\pi(m) : \mathcal{M} \rightarrow \mathcal{P}

where M\mathcal{M} is the universe of all messages. The following invariants must hold:

  1. Boundary Exclusivity: Every message originating from or destined to an external client must transit PboundaryP_{\text{boundary}}:
mMext:π(m)=Pboundary\forall m \in \mathcal{M}_{\text{ext}} : \pi(m) = P_{\text{boundary}}
  1. Internal Optimization: Every message between co-located agents or services within the platform trust boundary must transit PinternalP_{\text{internal}}:
mMint:π(m)=Pinternal\forall m \in \mathcal{M}_{\text{int}} : \pi(m) = P_{\text{internal}}
  1. Discovery Isolation: Every capability query, tool registration, resource enumeration, or prompt surface negotiation must transit PdiscoveryP_{\text{discovery}}:
mMdisc:π(m)=Pdiscovery\forall m \in \mathcal{M}_{\text{disc}} : \pi(m) = P_{\text{discovery}}
  1. No Protocol Leakage: No internal Protobuf types shall appear in boundary responses; no MCP-specific discovery semantics shall leak into gRPC service definitions without explicit gateway translation.

4.1.3 Cross-Cutting Concerns#

All three layers share cross-cutting requirements that are enforced uniformly:

  • Distributed Tracing: Every request carries an OpenTelemetry trace_id and span_id, propagated across protocol boundaries via gateway headers or metadata.
  • Deadline Propagation: Absolute deadlines (not relative timeouts) are set at the boundary and decremented at each hop.
  • Error Classification: All protocols map errors into a unified taxonomy (§4.2.3) before surfacing to the caller.
  • Versioned Contracts: Every schema element carries a semantic version; breaking changes are detected mechanically (§4.5).
  • Authentication Context: Caller identity and authorization scope propagate as typed metadata, never as ambient state (§4.8).

4.1.4 Pseudo-Algorithm: Protocol Layer Router#

ALGORITHM ProtocolLayerRouter
INPUT: message m, source_context src, destination_context dst
OUTPUT: routed_message with protocol-appropriate envelope
 
1.  CLASSIFY message origin:
       IF src.trust_zone = EXTERNAL:
           assigned_protocol ← JSON_RPC_2_0
       ELSE IF src.trust_zone = INTERNAL AND dst.trust_zone = INTERNAL:
           assigned_protocol ← GRPC_PROTOBUF
       ELSE IF m.type ∈ {TOOL_DISCOVERY, RESOURCE_ENUM, PROMPT_SURFACE, CAPABILITY_QUERY}:
           assigned_protocol ← MCP
       ELSE:
           assigned_protocol ← GRPC_PROTOBUF  // default internal
 
2.  EXTRACT cross-cutting metadata from m:
       trace_context ← extract_or_generate_trace(m)
       deadline ← compute_remaining_deadline(m.original_deadline, elapsed_time)
       auth_context ← extract_caller_credentials(src)
       schema_version ← resolve_contract_version(m.method, dst.service_id)
 
3.  IF deadline ≤ 0:
       RETURN error(DEADLINE_EXCEEDED, trace_context)
 
4.  CONSTRUCT protocol envelope:
       SWITCH assigned_protocol:
           CASE JSON_RPC_2_0:
               envelope ← build_jsonrpc_request(m, trace_context, auth_context, schema_version)
           CASE GRPC_PROTOBUF:
               envelope ← serialize_protobuf(m, trace_context, deadline, auth_context)
           CASE MCP:
               envelope ← build_mcp_message(m, trace_context, auth_context)
 
5.  VALIDATE envelope against registered schema for (m.method, schema_version):
       IF validation fails:
           RETURN error(SCHEMA_VIOLATION, trace_context, validation_errors)
 
6.  DISPATCH envelope to transport layer for assigned_protocol
7.  RECORD span(trace_context, assigned_protocol, m.method, dst.service_id, deadline)
8.  RETURN dispatch_handle

4.1.5 Token-Budget Implications#

The protocol choice directly affects the context engineering budget for the agent. When an agent receives tool descriptions via MCP, those descriptions consume tokens in the active context window. When tool invocation results return via gRPC, the binary-to-JSON transcoding step determines how many tokens the result occupies. The protocol stack must therefore be conscious of the agent's token budget BtokensB_{\text{tokens}}:

Btokens=Brole+Btask+Btools+Bretrieval+Bmemory+Bstate+BreserveB_{\text{tokens}} = B_{\text{role}} + B_{\text{task}} + B_{\text{tools}} + B_{\text{retrieval}} + B_{\text{memory}} + B_{\text{state}} + B_{\text{reserve}}

where BtoolsB_{\text{tools}} is directly influenced by how many tool schemas are loaded (MCP lazy loading minimizes this), and BretrievalB_{\text{retrieval}} depends on the serialization efficiency of returned evidence (Protobuf-to-summary transcoding reduces this).


4.2 JSON-RPC 2.0 at the Application Boundary: Schema Design, Batch Requests, Error Taxonomy#

4.2.1 Why JSON-RPC 2.0#

JSON-RPC 2.0 is selected for the boundary layer based on a precise set of mechanical properties:

  1. Transport Neutrality: JSON-RPC is agnostic to transport—it operates identically over HTTP, WebSocket, stdin/stdout, or message queues.
  2. Simplicity of Integration: External clients (web applications, mobile SDKs, CLI tools, third-party platforms) require no code generation, no Protobuf compiler, no schema registry client—only a JSON parser.
  3. Batch Semantics: The specification natively supports batched requests, enabling clients to submit multiple agent operations atomically.
  4. Deterministic Structure: Every request and response follows an invariant structure (jsonrpc, method, params, id, result, error), enabling mechanical validation without protocol-specific parsers.
  5. Notification Support: Fire-and-forget messages (no id field) support event-driven patterns without requiring response tracking.

4.2.2 Schema Design Principles#

Every JSON-RPC method exposed at the boundary must satisfy:

  • Typed params: Defined by a JSON Schema document registered in the schema registry under a semantic version.
  • Typed result: The success payload schema is equally specified; no method returns untyped JSON objects.
  • Idempotency Key: State-mutating methods accept an optional idempotency_key in params to enable safe retries.
  • Pagination Tokens: List-returning methods use opaque cursor-based pagination, never offset-based.
  • Deadline Header: The HTTP header X-Deadline-Ms or a deadline_ms field in params propagates the caller's absolute deadline.

Canonical Request Structure:

{
  "jsonrpc": "2.0",
  "method": "agent.submitTask",
  "params": {
    "task_id": "uuid-v7",
    "objective": "...",
    "context_refs": ["mem://session/abc", "retrieval://query/xyz"],
    "constraints": { "max_steps": 12, "budget_tokens": 8192 },
    "idempotency_key": "client-generated-uuid",
    "deadline_ms": 1719500000000
  },
  "id": "req-001"
}

Canonical Success Response:

{
  "jsonrpc": "2.0",
  "result": {
    "task_id": "uuid-v7",
    "status": "ACCEPTED",
    "estimated_completion_ms": 4500,
    "trace_id": "otel-trace-abc123"
  },
  "id": "req-001"
}

4.2.3 Error Taxonomy#

A rigorous error taxonomy is essential for deterministic client-side handling. The JSON-RPC 2.0 specification defines error codes in the range [32768,32000][-32768, -32000] for protocol-level errors. The agentic platform extends this with application-level codes.

Unified Error Code Space:

Code RangeCategoryExamples
32700-32700Parse ErrorMalformed JSON
32600-32600Invalid RequestMissing jsonrpc field, wrong version
32601-32601Method Not FoundUnknown method name
32602-32602Invalid ParamsSchema validation failure on params
32603-32603Internal ErrorUnclassified server fault
32000-32000 to 32099-32099Server Errors (reserved)Server overloaded, shutting down
1000100019991999Agent Execution ErrorsStep limit exceeded, recursion bound hit, tool failure
2000200029992999Retrieval ErrorsNo evidence found, source timeout, provenance unverifiable
3000300039993999Memory ErrorsWrite policy violation, dedup conflict, expiry rejection
4000400049994999Authorization ErrorsInsufficient scope, credential expired, approval required
5000500059995999Rate/Quota ErrorsRate limit exceeded, token budget exhausted, cost cap hit

Canonical Error Response:

{
  "jsonrpc": "2.0",
  "error": {
    "code": 1003,
    "message": "Agent recursion depth exceeded",
    "data": {
      "max_depth": 8,
      "current_depth": 9,
      "agent_id": "planner-v2",
      "trace_id": "otel-trace-abc123",
      "recovery_hint": "REDUCE_DECOMPOSITION_GRANULARITY"
    }
  },
  "id": "req-001"
}

4.2.4 Batch Request Semantics#

JSON-RPC 2.0 natively supports batch requests as JSON arrays. The agentic platform enforces additional constraints:

  1. Batch Size Limit: BBmax|B| \leq B_{\max} where BmaxB_{\max} is configurable per client tier (e.g., 50 for standard, 200 for enterprise).
  2. Atomic Deadline: The batch inherits a single deadline; individual requests within the batch cannot extend it.
  3. Independent Execution: Each request in the batch is processed independently—no implicit ordering or transactionality. Clients requiring ordering must use explicit dependency chains via depends_on fields in params.
  4. Partial Success: The response array may contain a mix of success results and error objects. Clients must handle partial success.

Formal Batch Throughput Model:

Let nn be batch size, tˉi\bar{t}_i the mean processing time per request, and cc the concurrency limit for batch execution. The expected batch latency:

Tbatch=nctˉi+toverheadT_{\text{batch}} = \left\lceil \frac{n}{c} \right\rceil \cdot \bar{t}_i + t_{\text{overhead}}

where toverheadt_{\text{overhead}} accounts for batch parsing, response assembly, and serialization. This model informs the gateway's concurrency pool sizing.

4.2.5 Pseudo-Algorithm: JSON-RPC Boundary Gateway#

ALGORITHM JsonRpcBoundaryGateway
INPUT: raw_http_request
OUTPUT: http_response containing JSON-RPC result(s)
 
1.  PARSE raw_http_request.body as JSON:
       IF parse fails:
           RETURN jsonrpc_error(-32700, "Parse error")
 
2.  DETERMINE if payload is batch (JSON array) or single (JSON object):
       IF batch:
           requests ← payload
           IF |requests| > B_max:
               RETURN jsonrpc_error(-32600, "Batch size exceeds limit")
       ELSE:
           requests ← [payload]
 
3.  EXTRACT deadline:
       deadline ← MIN(header["X-Deadline-Ms"], NOW() + default_timeout_ms)
 
4.  FOR EACH request r IN requests DO IN PARALLEL (bounded by concurrency c):
       a. VALIDATE r against JSON-RPC 2.0 structure:
             REQUIRE fields: jsonrpc="2.0", method (string)
             IF r has "id": it is a call; ELSE: it is a notification
       b. RESOLVE method → handler mapping from method_registry:
             IF not found: EMIT error(-32601) for this request
       c. VALIDATE r.params against registered JSON Schema for (r.method, version):
             IF invalid: EMIT error(-32602, validation_details)
       d. AUTHENTICATE caller from request headers:
             auth_ctx ← verify_token(header["Authorization"])
             IF auth fails: EMIT error(4001, "Authentication failed")
       e. AUTHORIZE caller against method's required scopes:
             IF insufficient: EMIT error(4002, "Insufficient scope")
       f. CHECK rate limit for (auth_ctx.client_id, r.method):
             IF exceeded: EMIT error(5001, "Rate limit exceeded", retry_after)
       g. CHECK idempotency:
             IF r.params.idempotency_key EXISTS in idempotency_store:
                 RETURN cached_response for that key
       h. PROPAGATE trace context:
             span ← start_span("jsonrpc." + r.method, parent=extract_trace(headers))
       i. TRANSLATE r → internal gRPC request via gateway codec:
             internal_req ← transcode_jsonrpc_to_grpc(r, auth_ctx, deadline, span)
       j. DISPATCH internal_req to appropriate gRPC service with remaining deadline
       k. AWAIT response or deadline expiry:
             IF deadline exceeded: EMIT error(-32000, "Deadline exceeded")
       l. TRANSCODE gRPC response → JSON-RPC result
       m. IF r.params.idempotency_key EXISTS AND response is success:
             STORE (idempotency_key → response) with TTL
       n. RECORD span metrics and close span
 
5.  ASSEMBLE responses array (preserving order, matching by id)
6.  IF original was single request: RETURN responses[0]
     ELSE: RETURN responses as JSON array
7.  EMIT gateway metrics: batch_size, latency_p50/p99, error_rate, auth_failures

4.3 gRPC/Protobuf for Internal Agent-to-Agent and Agent-to-Service Communication#

4.3.1 Proto3 Schema Design for Agent Messages, Tool Invocations, and Memory Operations#

Design Rationale#

Internal communication between agents, between agents and the retrieval engine, between agents and memory services, and between agents and tool execution backends requires:

  • Binary efficiency: Protobuf serialization is 310×3\text{–}10\times more compact than JSON, reducing network bandwidth and deserialization cost.
  • Strong typing: Proto3 schemas enforce field types, enumerations, and nested message structures at compile time.
  • Code generation: Language-specific stubs are auto-generated, eliminating hand-written serialization.
  • Schema evolution: Proto3's field numbering and backward-compatibility rules enable non-breaking schema evolution.
  • Streaming: gRPC natively supports unary, server-streaming, client-streaming, and bidirectional streaming RPCs.

Core Proto3 Message Taxonomy#

The agentic platform's Protobuf schema is organized into four domains:

  1. Agent Execution Domain (agent.proto)
  2. Tool Invocation Domain (tool.proto)
  3. Memory Operations Domain (memory.proto)
  4. Retrieval Domain (retrieval.proto)

Agent Execution Messages — Pseudo-Schema:

message AgentTask {
    string task_id = 1;                       // UUID v7
    string parent_task_id = 2;                // for decomposed subtasks
    string agent_type = 3;                    // e.g., "planner", "coder", "verifier"
    TaskObjective objective = 4;
    ExecutionConstraints constraints = 5;
    repeated ContextReference context_refs = 6;
    TraceContext trace = 7;
    AuthContext auth = 8;
    google.protobuf.Timestamp deadline = 9;
    uint32 schema_version = 10;
}
 
message TaskObjective {
    string natural_language = 1;
    repeated StructuredGoal goals = 2;
    repeated string success_criteria = 3;
}
 
message ExecutionConstraints {
    uint32 max_steps = 1;
    uint32 max_recursion_depth = 2;
    uint32 token_budget = 3;
    uint32 cost_budget_microdollars = 4;
    repeated string permitted_tools = 5;
    repeated string prohibited_actions = 6;
}
 
message AgentStepResult {
    string task_id = 1;
    uint32 step_number = 2;
    StepType type = 3;                        // PLAN, RETRIEVE, ACT, VERIFY, CRITIQUE, REPAIR, COMMIT
    StepOutcome outcome = 4;                  // SUCCESS, PARTIAL, FAILURE, NEEDS_REPAIR
    bytes payload = 5;                        // type-specific serialized content
    repeated ToolInvocationRecord tool_calls = 6;
    ResourceUsage usage = 7;
    TraceContext trace = 8;
}

Tool Invocation Messages — Pseudo-Schema:

message ToolInvocationRequest {
    string invocation_id = 1;                 // UUID v7, idempotency key
    string tool_name = 2;                     // fully qualified: "mcp://server/tool_name"
    string tool_version = 3;
    google.protobuf.Struct input_params = 4;  // validated against tool's input schema
    AuthContext caller_auth = 5;
    google.protobuf.Timestamp deadline = 6;
    TraceContext trace = 7;
    MutationClass mutation_class = 8;         // READ_ONLY, IDEMPOTENT_WRITE, NON_IDEMPOTENT_WRITE
    bool requires_human_approval = 9;
}
 
message ToolInvocationResponse {
    string invocation_id = 1;
    ToolResultStatus status = 2;              // SUCCESS, ERROR, TIMEOUT, APPROVAL_PENDING
    google.protobuf.Struct output = 3;
    string error_message = 4;
    uint32 error_code = 5;
    ResourceUsage usage = 6;
    TraceContext trace = 7;
    ProvenanceRecord provenance = 8;
}
 
enum MutationClass {
    READ_ONLY = 0;
    IDEMPOTENT_WRITE = 1;
    NON_IDEMPOTENT_WRITE = 2;
    DESTRUCTIVE = 3;
}

Memory Operations Messages — Pseudo-Schema:

message MemoryWriteRequest {
    string memory_id = 1;
    MemoryLayer target_layer = 2;             // WORKING, SESSION, EPISODIC, SEMANTIC, PROCEDURAL
    string content = 3;
    map<string, string> metadata = 4;
    ProvenanceRecord provenance = 5;
    WritePolicy policy = 6;
    google.protobuf.Timestamp expiry = 7;
    string dedup_key = 8;
    TraceContext trace = 9;
    AuthContext auth = 10;
}
 
message WritePolicy {
    bool require_validation = 1;
    bool require_dedup_check = 2;
    bool require_provenance = 3;
    PromotionCriteria promotion = 4;
    ConflictResolution on_conflict = 5;       // REJECT, MERGE, OVERWRITE, VERSION
}
 
enum MemoryLayer {
    WORKING = 0;
    SESSION = 1;
    EPISODIC = 2;
    SEMANTIC = 3;
    PROCEDURAL = 4;
}

Schema Design Invariants#

  1. Every field has an explicit number: Field numbers are immutable once published; removed fields are marked reserved.
  2. No required fields: Proto3 does not support required; application-level validation enforces mandatory fields.
  3. Enums start at 0 (UNKNOWN/UNSPECIFIED): The zero value always represents "unset" to distinguish missing from default.
  4. google.protobuf.Struct for dynamic payloads: Tool inputs/outputs use Struct when schemas vary per tool; validation occurs against the tool's registered JSON Schema, not at the Protobuf level.
  5. Timestamps use google.protobuf.Timestamp: Never raw integers for temporal values.

4.3.2 Bidirectional Streaming for Real-Time Agent Coordination#

Streaming RPC Taxonomy#

RPC TypeUse CaseCardinality
UnarySingle tool invocation, memory read/write1:11 : 1
Server-streamingAgent step-by-step progress reporting, log streaming1:N1 : N
Client-streamingBatch memory ingestion, multi-file uploadN:1N : 1
Bidirectional streamingReal-time multi-agent coordination, interactive debuggingN:MN : M

Bidirectional Streaming for Agent Coordination#

The canonical use case is the orchestrator ↔ specialist agent coordination loop. The orchestrator streams subtask assignments; the specialist agent streams back step results, intermediate findings, and resource requests in real time. This avoids the latency overhead of repeated unary RPCs.

Service Definition — Pseudo-Schema:

service AgentCoordination {
    // Orchestrator ↔ Agent bidirectional stream
    rpc CoordinateExecution(stream OrchestratorMessage) 
        returns (stream AgentMessage);
    
    // Agent → Orchestrator: step-by-step progress
    rpc ReportProgress(stream AgentStepResult) 
        returns (ProgressAck);
    
    // Orchestrator → multiple agents: broadcast context update
    rpc BroadcastContextUpdate(ContextUpdate) 
        returns (stream AgentAck);
}
 
message OrchestratorMessage {
    oneof payload {
        AgentTask new_task = 1;
        ContextUpdate context_update = 2;
        CancellationSignal cancel = 3;
        ResourceGrant resource_grant = 4;
        DeadlineExtension deadline_ext = 5;
    }
}
 
message AgentMessage {
    oneof payload {
        AgentStepResult step_result = 1;
        ResourceRequest resource_request = 2;
        EscalationRequest escalation = 3;
        CompletionReport completion = 4;
        HealthHeartbeat heartbeat = 5;
    }
}

Stream Lifecycle Management#

Formal Stream State Machine:

Define the stream state S{INIT,ACTIVE,HALF_CLOSED_CLIENT,HALF_CLOSED_SERVER,CLOSED,ERROR}S \in \{INIT, ACTIVE, HALF\_CLOSED\_CLIENT, HALF\_CLOSED\_SERVER, CLOSED, ERROR\}.

SinitopenSactiveserver closesclient closesShalf_closedboth closedSclosedS_{\text{init}} \xrightarrow{\text{open}} S_{\text{active}} \xrightarrow[\text{server closes}]{\text{client closes}} S_{\text{half\_closed}} \xrightarrow{\text{both closed}} S_{\text{closed}}

At any state, a deadline expiry or error transitions to SerrorS_{\text{error}}, triggering cleanup.

Heartbeat Requirement: For long-lived streams, agents must emit heartbeat messages at interval Δh\Delta_h. If no message (heartbeat or data) arrives within kΔhk \cdot \Delta_h (where kk is typically 3), the stream is considered dead and cleaned up.

tlast_recv+kΔh<tnow    stream_deadt_{\text{last\_recv}} + k \cdot \Delta_h < t_{\text{now}} \implies \text{stream\_dead}

4.3.3 Deadline Propagation, Cancellation Semantics, and Backpressure#

Deadline Propagation Model#

Every request entering the system at the boundary carries an absolute deadline D0D_0. As the request traverses internal services, the remaining budget decreases:

Dremaining(i)=D0j=0i1tjprocessingj=0i1tjnetworkD_{\text{remaining}}^{(i)} = D_0 - \sum_{j=0}^{i-1} t_j^{\text{processing}} - \sum_{j=0}^{i-1} t_j^{\text{network}}

where tjprocessingt_j^{\text{processing}} is the processing time at hop jj and tjnetworkt_j^{\text{network}} is the network transit time. At each hop, the service checks:

Dremaining(i)ϵmin    ABORT with DEADLINE_EXCEEDEDD_{\text{remaining}}^{(i)} \leq \epsilon_{\text{min}} \implies \text{ABORT with DEADLINE\_EXCEEDED}

where ϵmin\epsilon_{\text{min}} is the minimum useful remaining time (e.g., 50ms, below which no meaningful work can complete).

Pseudo-Algorithm: Deadline-Aware Dispatch

ALGORITHM DeadlineAwareDispatch
INPUT: request req, absolute_deadline D_0
OUTPUT: response or DEADLINE_EXCEEDED error
 
1.  t_now ← CURRENT_TIME()
2.  D_remaining ← D_0 - t_now
3.  IF D_remaining ≤ ε_min:
       RETURN error(DEADLINE_EXCEEDED, D_remaining)
 
4.  // Reserve time for response serialization and network return
    D_downstream ← D_remaining - t_reserve_response
5.  IF D_downstream ≤ ε_min:
       RETURN error(DEADLINE_EXCEEDED, D_downstream)
 
6.  SET gRPC deadline on outbound call to D_downstream
7.  DISPATCH req with deadline
8.  AWAIT response WITH timeout = D_downstream:
       IF timeout: RETURN error(DEADLINE_EXCEEDED)
       IF response.error AND error is retryable AND D_remaining > 2 * ε_min:
           // One retry with remaining budget
           D_retry ← D_0 - CURRENT_TIME() - t_reserve_response
           IF D_retry > ε_min:
               DISPATCH req with deadline D_retry
               AWAIT response WITH timeout = D_retry
9.  RETURN response

Cancellation Semantics#

gRPC provides native cancellation propagation. When a client cancels a call, all downstream RPCs spawned within that call's context are automatically cancelled. The agentic platform extends this with semantic cancellation:

  • Hard Cancel: Immediate abort; partial results discarded. Used when the user abandons a task.
  • Soft Cancel: Complete current step, then stop. Used when replanning makes the current subtask obsolete.
  • Preempt: Pause current execution, yield resources, resume later. Used under resource contention.

Cancellation Propagation Rule: When an orchestrator cancels a parent task, all child tasks must receive cancellation within tpropagation100mst_{\text{propagation}} \leq 100\text{ms}, enforced by the coordination stream.

Backpressure Mechanisms#

Backpressure prevents fast producers from overwhelming slow consumers. The platform implements backpressure at three levels:

  1. gRPC Flow Control: HTTP/2 window-based flow control limits how much data can be in flight per stream.
  2. Application-Level Backpressure: The agent coordination service monitors per-agent queue depth QiQ_i. When Qi>QmaxQ_i > Q_{\max}:
action={THROTTLE (reduce dispatch rate)if Qi(Qmax,2Qmax]REJECT (return RESOURCE_EXHAUSTED)if Qi>2Qmax\text{action} = \begin{cases} \text{THROTTLE (reduce dispatch rate)} & \text{if } Q_i \in (Q_{\max}, 2Q_{\max}] \\ \text{REJECT (return RESOURCE\_EXHAUSTED)} & \text{if } Q_i > 2Q_{\max} \end{cases}
  1. Circuit Breaker: If a downstream service's error rate ere_r exceeds threshold θe\theta_e over a sliding window of ww seconds:
er=nerrorsntotal>θe    OPEN circuit for trecoverye_r = \frac{n_{\text{errors}}}{n_{\text{total}}} > \theta_e \implies \text{OPEN circuit for } t_{\text{recovery}}

During open state, requests are fast-failed. After trecoveryt_{\text{recovery}}, a half-open probe tests recovery before closing the circuit.

Pseudo-Algorithm: Backpressure Controller

ALGORITHM BackpressureController
INPUT: incoming_request req, target_service svc
OUTPUT: dispatch decision {ALLOW, THROTTLE, REJECT, CIRCUIT_OPEN}
 
1.  // Circuit breaker check
    cb_state ← circuit_breaker[svc.id].state
    IF cb_state = OPEN:
       IF CURRENT_TIME() - cb_state.opened_at > t_recovery:
           cb_state ← HALF_OPEN
       ELSE:
           RETURN CIRCUIT_OPEN
 
2.  // Queue depth check
    Q ← svc.pending_queue.size()
    IF Q > 2 * Q_max:
       RETURN REJECT
    IF Q > Q_max:
       APPLY throttle: SLEEP(backoff_ms * (Q - Q_max) / Q_max)
       // Re-check after throttle
       IF svc.pending_queue.size() > 2 * Q_max:
           RETURN REJECT
 
3.  // Concurrency check
    active ← svc.active_requests.count()
    IF active ≥ svc.max_concurrency:
       RETURN THROTTLE (enqueue with bounded wait)
 
4.  // Dispatch
    result ← DISPATCH req to svc
    
5.  // Update circuit breaker
    IF result.is_error:
       circuit_breaker[svc.id].record_failure()
       IF circuit_breaker[svc.id].error_rate() > θ_e:
           circuit_breaker[svc.id].state ← OPEN
           circuit_breaker[svc.id].opened_at ← CURRENT_TIME()
    ELSE:
       circuit_breaker[svc.id].record_success()
       IF cb_state = HALF_OPEN:
           circuit_breaker[svc.id].state ← CLOSED
 
6.  RETURN result

4.4 Model Context Protocol (MCP) — Deep Technical Specification#

4.4.1 MCP Server Architecture: Tool Servers, Resource Servers, Prompt Surface Servers#

Architectural Role of MCP#

MCP occupies the discovery and interoperability layer of the protocol stack. Where gRPC enforces static, compile-time-known service contracts, MCP enables runtime-dynamic capability surfaces. An agent does not need to know at compile time which tools, resources, or prompt templates are available; it discovers them through MCP's capability negotiation protocol.

MCP defines three primary server archetypes:

1. Tool Servers

Tool servers expose executable capabilities—functions the agent can invoke to affect the environment or retrieve computed results. Each tool is described by a typed schema (name, description, input JSON Schema, output JSON Schema, mutation class, required permissions).

  • Examples: code execution sandboxes, database query engines, web search APIs, file system operators, CI/CD pipeline triggers.
  • Key Property: Tools are invoked by the agent through tools/call; the result is returned synchronously or via a completion callback.
  • Lazy Loading: Tool descriptions are loaded into the agent's context window only when the agent's planner selects them as relevant, minimizing BtoolsB_{\text{tools}}.

2. Resource Servers

Resource servers expose data that the agent can read—files, database records, API responses, configuration, documentation. Resources are identified by URI and may have MIME types.

  • Examples: repository file trees, documentation indices, configuration registries, log archives.
  • Key Property: Resources are read by the agent through resources/read; they are not executable.
  • Distinction from Tools: A tool does something; a resource provides information. This distinction governs authorization (resources require read scope; tools may require write scope).

3. Prompt Surface Servers

Prompt surface servers expose reusable prompt templates with typed argument slots. These are not free-text prompts but structured templates that the agent (or its orchestrator) can instantiate with specific parameters.

  • Examples: code review templates, incident analysis frameworks, summarization patterns, compliance check protocols.
  • Key Property: Prompt surfaces are retrieved and instantiated via prompts/get, returning an assembled prompt with arguments filled.

MCP Server Composition Model#

A single MCP server process may expose any combination of tools, resources, and prompt surfaces. The capability advertisement (§4.4.2) declares which categories the server supports.

Formal Server Capability Declaration:

Let S\mathcal{S} be an MCP server. Its capability set is:

Cap(S)={Ctools,Cresources,Cprompts}\text{Cap}(\mathcal{S}) = \{C_{\text{tools}}, C_{\text{resources}}, C_{\text{prompts}}\}

where each CxC_x is either present (with optional sub-capabilities like listChanged for change notifications) or absent. The client (agent runtime) adjusts its interaction pattern based on the declared capabilities.

4.4.2 Capability Discovery, Schema Negotiation, and Dynamic Tool Registration#

Initialization Handshake#

MCP uses a strict initialization protocol before any substantive communication:

Pseudo-Algorithm: MCP Initialization Handshake

ALGORITHM McpInitializationHandshake
PARTICIPANTS: client (agent runtime), server (MCP server)
 
1.  CLIENT sends `initialize` request:
       {
         method: "initialize",
         params: {
           protocolVersion: "2025-03-26",
           capabilities: {
             roots: { listChanged: true },
             sampling: {}
           },
           clientInfo: { name: "agent-runtime", version: "3.2.1" }
         }
       }
 
2.  SERVER responds with its capabilities:
       {
         result: {
           protocolVersion: "2025-03-26",
           capabilities: {
             tools: { listChanged: true },
             resources: { subscribe: true, listChanged: true },
             prompts: { listChanged: true }
           },
           serverInfo: { name: "code-tools", version: "1.4.0" }
         }
       }
 
3.  VERSION NEGOTIATION:
       negotiated_version ← MIN(client.protocolVersion, server.protocolVersion)
       IF no compatible version exists:
           ABORT connection with VERSION_MISMATCH error
 
4.  CLIENT sends `initialized` notification:
       { method: "notifications/initialized" }
 
5.  CONNECTION is now ACTIVE.
       Client may now call tools/list, resources/list, prompts/list.
 
6.  RECORD capability matrix:
       FOR EACH capability c IN server.capabilities:
           capability_registry.register(server.id, c, c.sub_capabilities)

Dynamic Tool Registration#

Tools can be registered and deregistered at runtime. When a server's tool set changes, it emits a notifications/tools/list_changed notification. The client must then re-fetch the tool list via tools/list.

Tool Registration Lifecycle:

REGISTEREDlist_changedSTALEtools/listREFRESHEDschema validationACTIVE\text{REGISTERED} \xrightarrow{\text{list\_changed}} \text{STALE} \xrightarrow{\text{tools/list}} \text{REFRESHED} \xrightarrow{\text{schema validation}} \text{ACTIVE}

Schema Negotiation Rules:

  1. Every tool's inputSchema must be a valid JSON Schema Draft 2020-12 document.
  2. The client validates the schema is well-formed upon discovery.
  3. Before invocation, the client validates the actual input parameters against the tool's inputSchema.
  4. If a tool's schema changes (detected via list_changed), all cached schema references are invalidated.

Tool Relevance Scoring for Context Loading#

Not all discovered tools should be loaded into the agent's context window simultaneously. The platform computes a tool relevance score to determine which tool descriptions to include in the prefill:

relevance(t,τ)=αsem_sim(t.desc,τ.objective)+βhist_freq(t,τ.agent_type)+γrecency(t)+δsuccess_rate(t)\text{relevance}(t, \tau) = \alpha \cdot \text{sem\_sim}(t.\text{desc}, \tau.\text{objective}) + \beta \cdot \text{hist\_freq}(t, \tau.\text{agent\_type}) + \gamma \cdot \text{recency}(t) + \delta \cdot \text{success\_rate}(t)

where:

  • sem_sim\text{sem\_sim} is the semantic similarity between the tool description and the current task objective,
  • hist_freq\text{hist\_freq} is the historical invocation frequency of tool tt by the agent type handling task τ\tau,
  • recency\text{recency} is a decay function favoring recently successful tools,
  • success_rate\text{success\_rate} is the tool's historical success rate,
  • α+β+γ+δ=1\alpha + \beta + \gamma + \delta = 1 are tunable weights.

Only tools with relevance(t,τ)>θtool\text{relevance}(t, \tau) > \theta_{\text{tool}} are included in the context prefill, enforcing the token budget BtoolsB_{\text{tools}}.

4.4.3 Local (stdio) vs. Remote (SSE/HTTP) Transport Modes#

MCP supports two transport modes, selected based on deployment topology:

Local Transport (stdio)#

  • Mechanism: The MCP server runs as a child process of the client. Communication occurs over stdin/stdout using newline-delimited JSON-RPC messages.
  • Use Case: Local development, sandboxed tool execution, co-located integrations where network overhead is unnecessary.
  • Security Model: Process isolation; the server inherits the client's environment unless explicitly sandboxed.
  • Lifecycle: The client manages the server process lifecycle (spawn, monitor, restart, kill).

Process Management Invariants:

  1. If the server process exits unexpectedly, the client must detect exit within 1 heartbeat interval and trigger restart or failover.
  2. Stderr from the server is captured for diagnostics but never parsed as protocol messages.
  3. The client must not write to stdout if the server expects exclusive stdin; multiplexing requires explicit framing.

Remote Transport (SSE/HTTP)#

  • Mechanism: The MCP server runs as a network service. The client connects via HTTP using Server-Sent Events (SSE) for server-to-client streaming and HTTP POST for client-to-server requests.
  • Use Case: Shared tool servers serving multiple agents, cloud-hosted integrations, cross-network tool access.
  • Security Model: TLS required; authentication via bearer tokens or mutual TLS.
  • Lifecycle: The server runs independently; clients connect and disconnect without affecting server state.

Transport Selection Decision:

transport(S)={stdioif S.locality=COLOCATEDS.clients=1SSE/HTTPif S.locality=REMOTES.clients>1\text{transport}(\mathcal{S}) = \begin{cases} \text{stdio} & \text{if } \mathcal{S}.\text{locality} = \text{COLOCATED} \wedge \mathcal{S}.\text{clients} = 1 \\ \text{SSE/HTTP} & \text{if } \mathcal{S}.\text{locality} = \text{REMOTE} \vee \mathcal{S}.\text{clients} > 1 \end{cases}

Formal Latency Comparison:

For a single tool invocation:

tstdio=tserialize+tpipe+texecute+tpipe+tdeserializet_{\text{stdio}} = t_{\text{serialize}} + t_{\text{pipe}} + t_{\text{execute}} + t_{\text{pipe}} + t_{\text{deserialize}} tSSE=tserialize+tTLS+tnetwork+texecute+tnetwork+tdeserializet_{\text{SSE}} = t_{\text{serialize}} + t_{\text{TLS}} + t_{\text{network}} + t_{\text{execute}} + t_{\text{network}} + t_{\text{deserialize}}

Typically tstdiotSSEt_{\text{stdio}} \ll t_{\text{SSE}} for co-located scenarios (sub-millisecond pipe transit vs. milliseconds of TLS + network), justifying stdio for latency-critical local tools.

4.4.4 Pagination, Change Notifications, and Subscription Semantics#

Pagination#

MCP methods that return lists (tools/list, resources/list, prompts/list, resources/templates/list) support cursor-based pagination:

  • The response includes a nextCursor field if more results exist.
  • The client passes cursor in subsequent requests to fetch the next page.
  • Cursors are opaque strings; the client must not parse, modify, or persist them across sessions.
  • Page size is server-determined; the client may request a preferred size but the server is not obligated to honor it.

Pagination Invariant: For a complete enumeration of NN items with page size pp:

requestspagination=Np\text{requests}_{\text{pagination}} = \left\lceil \frac{N}{p} \right\rceil

The total latency for full enumeration:

Tenum=NptpageT_{\text{enum}} = \left\lceil \frac{N}{p} \right\rceil \cdot t_{\text{page}}

This motivates caching the full list locally and refreshing only upon list_changed notifications.

Change Notifications#

When a server's capability set changes (tools added/removed, resources updated, prompt surfaces modified), it emits a notification:

  • notifications/tools/list_changed
  • notifications/resources/list_changed
  • notifications/prompts/list_changed

These are fire-and-forget notifications (no id field, no response expected). The client is responsible for re-fetching the affected list.

Change Notification Handling:

ALGORITHM HandleMcpChangeNotification
INPUT: notification n from MCP server S
 
1.  PARSE n.method:
       SWITCH n.method:
           CASE "notifications/tools/list_changed":
               affected_cache ← tool_cache[S.id]
           CASE "notifications/resources/list_changed":
               affected_cache ← resource_cache[S.id]
           CASE "notifications/prompts/list_changed":
               affected_cache ← prompt_cache[S.id]
 
2.  INVALIDATE affected_cache
 
3.  SCHEDULE async refresh:
       new_list ← CALL S.list_method() with pagination
       VALIDATE each item's schema
       UPDATE affected_cache with new_list
       COMPUTE delta (added, removed, modified items)
 
4.  IF delta affects currently active agent contexts:
       FOR EACH affected_agent a:
           IF a.state = EXECUTING:
               a.context.invalidate_tool_cache()
               a.planner.notify_capability_change(delta)
           // Do NOT abort execution; let planner adapt at next step boundary
 
5.  EMIT metric: mcp_capability_change{server=S.id, type=n.method, delta_size}

Resource Subscriptions#

MCP supports resource subscriptions via resources/subscribe and resources/unsubscribe. When a subscribed resource changes, the server emits notifications/resources/updated with the resource URI. The client can then re-read the resource.

Subscription Lifecycle:

UNSUBSCRIBEDresources/subscribeSUBSCRIBEDnotifications/resources/updatedSTALEresources/readFRESH\text{UNSUBSCRIBED} \xrightarrow{\text{resources/subscribe}} \text{SUBSCRIBED} \xrightarrow{\text{notifications/resources/updated}} \text{STALE} \xrightarrow{\text{resources/read}} \text{FRESH}

4.4.5 MCP Roots, Sampling, and Bidirectional Context Exchange#

Roots#

Roots provide the server with visibility into the client's workspace context. The client declares a set of root URIs (e.g., file system paths, repository URLs) that define the boundaries of what the server should consider when operating.

  • The server uses roots to scope operations (e.g., a code analysis tool only examines files within declared roots).
  • Roots are declared during initialization and can be updated dynamically via notifications/roots/list_changed.
  • Roots do not grant the server direct access; they inform the server's operational scope.

Security Implication: Roots define the principle of least privilege for server operations. A server that receives a root of /project/src should not attempt to read /project/secrets.

Sampling#

Sampling is MCP's mechanism for server-initiated LLM inference. A server can request the client (which has access to an LLM) to perform a completion on the server's behalf. This enables the "human-in-the-loop" pattern: the server prepares context, the client's LLM generates, and the server uses the result.

Sampling Flow:

ALGORITHM McpSamplingFlow
PARTICIPANTS: server S, client C (has LLM access)
 
1.  SERVER constructs a sampling request:
       {
         method: "sampling/createMessage",
         params: {
           messages: [
             { role: "user", content: { type: "text", text: "Analyze this code..." } }
           ],
           modelPreferences: {
             hints: [{ name: "claude-sonnet-4-20250514" }],
             costPriority: 0.3,
             speedPriority: 0.5,
             intelligencePriority: 0.8
           },
           systemPrompt: "You are a code analysis expert...",
           maxTokens: 2048,
           temperature: 0.2
         }
       }
 
2.  CLIENT receives request and applies POLICY CHECKS:
       a. Is sampling permitted by client configuration?
       b. Does the request comply with token budget constraints?
       c. Does the model preference match available models?
       d. HUMAN-IN-THE-LOOP: optionally present to user for approval
 
3.  CLIENT dispatches to LLM with appropriate model, returns result:
       {
         result: {
           model: "claude-sonnet-4-20250514",
           role: "assistant",
           content: { type: "text", text: "The analysis reveals..." },
           stopReason: "endTurn"
         }
       }
 
4.  SERVER uses result to continue its operation

Bidirectional Context Exchange Model:

MCP's architecture enables a bidirectional flow of context that is unique among tool protocols:

  • Client → Server (via Roots): "Here is my workspace scope."
  • Server → Client (via Resources): "Here is data you may need."
  • Server → Client (via Sampling): "Please reason about this for me."
  • Client → Server (via Tool Calls): "Please execute this action."

This forms a closed loop:

Clientresources, samplingroots, tool callsServer\text{Client} \xrightleftharpoons[\text{resources, sampling}]{\text{roots, tool calls}} \text{Server}

4.5 Versioned Contracts: Semantic Versioning for Agent Interfaces, Breaking Change Detection#

4.5.1 Versioning Model#

Every interface in the protocol stack carries a semantic version v=(M,m,p)v = (M, m, p) where MM is the major version, mm is the minor version, and pp is the patch version, following standard Semantic Versioning (SemVer) with agent-specific interpretations:

Version ComponentIncrement WhenAgent-Specific Meaning
Major (MM)Breaking changeTool input/output schema incompatible; agent behavior changes semantically
Minor (mm)Backward-compatible additionNew optional tool parameter, new resource type, new prompt surface
Patch (pp)Bug fix, documentationSchema unchanged; implementation-only fix

4.5.2 Compatibility Matrix#

Define the compatibility predicate compat(vclient,vserver)\text{compat}(v_{\text{client}}, v_{\text{server}}):

compat((Mc,mc,pc),(Ms,ms,ps))={trueif Mc=Msmcmsfalseif McMsdegradedif Mc=Msmc>ms\text{compat}((M_c, m_c, p_c), (M_s, m_s, p_s)) = \begin{cases} \text{true} & \text{if } M_c = M_s \wedge m_c \leq m_s \\ \text{false} & \text{if } M_c \neq M_s \\ \text{degraded} & \text{if } M_c = M_s \wedge m_c > m_s \end{cases}
  • true: Full compatibility; client can use all features up to mcm_c.
  • false: Breaking incompatibility; connection must be refused or routed to a compatible version.
  • degraded: Client expects features the server doesn't have; the client must gracefully handle missing capabilities.

4.5.3 Breaking Change Detection#

Breaking changes are detected mechanically in CI/CD by comparing the current schema against the previous release schema.

Pseudo-Algorithm: Breaking Change Detector

ALGORITHM BreakingChangeDetector
INPUT: schema_old S_old (version v_old), schema_new S_new (version v_new)
OUTPUT: list of breaking changes, or PASS
 
1.  COMPUTE diff D = structural_diff(S_old, S_new)
 
2.  FOR EACH change c IN D:
       CLASSIFY c:
         - FIELD_REMOVED: breaking (clients may depend on it)
         - FIELD_TYPE_CHANGED: breaking (serialization incompatible)
         - REQUIRED_FIELD_ADDED: breaking (old clients won't send it)
         - ENUM_VALUE_REMOVED: breaking (old clients may send it)
         - FIELD_NUMBER_CHANGED (Protobuf): breaking (binary incompatible)
         - FIELD_ADDED_OPTIONAL: non-breaking
         - ENUM_VALUE_ADDED: non-breaking (with UNSPECIFIED default)
         - DESCRIPTION_CHANGED: non-breaking
 
3.  breaking_changes ← FILTER D WHERE classification = breaking
 
4.  IF |breaking_changes| > 0:
       IF v_new.major = v_old.major:
           FAIL CI: "Breaking changes detected without major version bump"
           REPORT breaking_changes with field paths and classifications
       ELSE:
           WARN: "Breaking changes in major version bump — ensure migration guide exists"
           REQUIRE: migration_guide document exists for v_old → v_new
           REQUIRE: deprecation notice was published ≥ 2 release cycles ago
 
5.  RETURN breaking_changes or PASS

4.5.4 Multi-Version Serving#

The platform supports serving multiple major versions simultaneously via version-prefixed routing:

  • JSON-RPC: Method names include version prefix (e.g., v2.agent.submitTask)
  • gRPC: Service definitions are versioned in package names (e.g., package agent.v2;)
  • MCP: Protocol version is negotiated during initialization handshake

Sunset Policy: A major version is supported for at least NsunsetN_{\text{sunset}} release cycles (configurable, typically 3–6 months) after the successor is released. After sunset, requests to the old version receive error code DEPRECATED_VERSION with a migration URI.


4.6 Interface Definition Language (IDL) Strategy: Protobuf, JSON Schema, OpenAPI, MCP Schema Unification#

4.6.1 The IDL Fragmentation Problem#

The three-layer protocol stack introduces three distinct schema languages:

  1. Protobuf (Proto3): For gRPC service definitions and internal message types.
  2. JSON Schema (Draft 2020-12): For MCP tool input/output schemas and JSON-RPC parameter validation.
  3. OpenAPI 3.1: For HTTP API documentation consumed by external integrators.

Without a unification strategy, schema definitions drift, validators diverge, and breaking changes in one layer go undetected in others.

4.6.2 Canonical Source Strategy#

The platform adopts a single canonical source for each domain type, with automated generation of derivative schemas:

Canonical Sourcecodegen{Protobuf,JSON Schema,OpenAPI}\text{Canonical Source} \xrightarrow{\text{codegen}} \{\text{Protobuf}, \text{JSON Schema}, \text{OpenAPI}\}

Decision Matrix for Canonical Source:

DomainCanonical IDLRationaleGenerated Artifacts
Agent messages, memory opsProtobufInternal perf-critical, strong typing→ JSON Schema (for boundary validation), → OpenAPI (for docs)
Tool schemasJSON SchemaMCP native format, dynamic registration→ Protobuf Struct wrappers (for internal transport)
Boundary API surfaceOpenAPI 3.1Developer portal, SDK generation→ JSON Schema (params validation), → Protobuf (gateway transcoding)

4.6.3 Schema Unification Pipeline#

Pseudo-Algorithm: Schema Unification Pipeline

ALGORITHM SchemaUnificationPipeline
INPUT: canonical schema files, target formats
OUTPUT: unified, validated schema artifacts
 
1.  DISCOVER all canonical schema files by convention:
       proto_files ← glob("schemas/proto/**/*.proto")
       json_schemas ← glob("schemas/json/**/*.schema.json")
       openapi_specs ← glob("schemas/openapi/**/*.yaml")
 
2.  FOR EACH proto file p IN proto_files:
       a. COMPILE p with protoc → validate syntax
       b. GENERATE json_schema from p using proto-to-jsonschema converter
       c. GENERATE openapi fragment from p using protoc-gen-openapi
       d. STORE generated artifacts with provenance: {source: p, generator_version, timestamp}
 
3.  FOR EACH json_schema j IN json_schemas:
       a. VALIDATE j against JSON Schema meta-schema
       b. GENERATE protobuf Struct wrapper message for j
       c. REGISTER j in MCP tool schema registry
 
4.  FOR EACH openapi spec o IN openapi_specs:
       a. VALIDATE o with openapi-validator
       b. EXTRACT params schemas → validate against corresponding json_schemas
       c. GENERATE SDK client stubs
 
5.  CROSS-VALIDATE:
       FOR EACH type T that exists in multiple IDLs:
           proto_schema ← lookup(T, proto_files)
           json_schema ← lookup(T, json_schemas)
           openapi_schema ← lookup(T, openapi_specs)
           
           field_diff ← structural_compare(proto_schema, json_schema, openapi_schema)
           IF field_diff contains SEMANTIC_MISMATCH:
               FAIL: "Schema drift detected for type {T}: {field_diff}"
 
6.  PUBLISH unified schema registry with version metadata
7.  EMIT metrics: schema_count, drift_violations, generation_duration

4.6.4 Schema Registry Architecture#

The schema registry is a versioned, queryable store of all schema artifacts:

  • Storage: Git-backed (for auditability) with a query API layered on top.
  • Indexing: By type name, version, domain, protocol, and provenance.
  • Queries: "Give me the JSON Schema for ToolInvocationRequest at version 2.3.x" → returns the exact schema with provenance.
  • Validation Service: Any service can submit a payload + type name + version and receive a validation result (pass/fail with error paths).

4.7 Cross-Protocol Gateway Design: JSON-RPC ↔ gRPC ↔ MCP Translation Layers#

4.7.1 Gateway Architecture#

The cross-protocol gateway is the critical integration point where the three protocol layers meet. It is not a monolithic translator but a composable codec pipeline with distinct stages.

Gateway Topology:

External Client


┌─────────────────────────┐
│  JSON-RPC Ingress       │  ← TLS termination, auth, rate limiting
│  (HTTP/WebSocket)       │
└─────────┬───────────────┘


┌─────────────────────────┐
│  Protocol Codec Layer   │  ← JSON-RPC ↔ gRPC transcoding
│  (Bidirectional)        │  ← MCP ↔ gRPC bridging
└─────────┬───────────────┘


┌─────────────────────────┐
│  gRPC Internal Mesh     │  ← Agent services, memory, retrieval
└─────────┬───────────────┘


┌─────────────────────────┐
│  MCP Connector Manager  │  ← Tool servers, resource servers
│  (stdio / SSE adapters) │
└─────────────────────────┘

4.7.2 Transcoding Rules#

JSON-RPC → gRPC Transcoding:

Define the transcoding function Γjg\Gamma_{\text{jg}}:

Γjg:JsonRpcRequestProtobufMessage×gRpcMetadata\Gamma_{\text{jg}} : \text{JsonRpcRequest} \rightarrow \text{ProtobufMessage} \times \text{gRpcMetadata}

The transcoding applies the following transformations:

  1. Method Mapping: JSON-RPC method "agent.submitTask" → gRPC service AgentService, method SubmitTask.
  2. Params → Protobuf: JSON params object is deserialized into the corresponding Protobuf message using the schema registry. Type coercion follows strict rules (no implicit string-to-int conversion).
  3. Metadata Injection: Trace context, auth context, and deadline are extracted from HTTP headers and injected as gRPC metadata.
  4. Response Mapping: gRPC response Protobuf → JSON result object; gRPC status codes → JSON-RPC error codes.

gRPC → MCP Bridging:

When an agent (operating within the gRPC mesh) needs to invoke an MCP tool:

Γgm:ToolInvocationRequest (Protobuf)MCP tools/call (JSON-RPC)\Gamma_{\text{gm}} : \text{ToolInvocationRequest (Protobuf)} \rightarrow \text{MCP tools/call (JSON-RPC)}
  1. Tool Resolution: The tool_name field is resolved against the MCP tool registry to identify the target server and transport mode.
  2. Params Extraction: The input_params (google.protobuf.Struct) is serialized to JSON for the MCP arguments field.
  3. Auth Propagation: Caller-scoped credentials are mapped to MCP-compatible auth headers or tokens.
  4. Result Transcoding: MCP content results (text, image, resource references) are packed back into ToolInvocationResponse.output.

4.7.3 Pseudo-Algorithm: Cross-Protocol Gateway#

ALGORITHM CrossProtocolGateway
INPUT: request from any protocol layer
OUTPUT: response in the originating protocol format
 
1.  IDENTIFY source protocol:
       source ← detect_protocol(request)  // JSON_RPC, GRPC, MCP
 
2.  IDENTIFY target service and its native protocol:
       target_service, target_protocol ← route(request.method)
 
3.  IF source = target_protocol:
       // No transcoding needed; pass through
       DISPATCH request directly
       RETURN response
 
4.  // Transcoding required
    SWITCH (source → target_protocol):
    
       CASE JSON_RPC → GRPC:
           a. LOOKUP proto message type for request.method from schema_registry
           b. DESERIALIZE request.params → proto message (validate against schema)
           c. EXTRACT auth, trace, deadline from HTTP headers → gRPC metadata
           d. DISPATCH gRPC call with deadline
           e. AWAIT gRPC response
           f. SERIALIZE proto response → JSON result
           g. MAP gRPC status → JSON-RPC error code (if error)
           h. RETURN JSON-RPC response
 
       CASE GRPC → MCP:
           a. RESOLVE tool_name → MCP server connection (stdio or SSE)
           b. SERIALIZE proto Struct → JSON arguments
           c. CONSTRUCT MCP tools/call request
           d. SET timeout from gRPC deadline remaining
           e. DISPATCH to MCP server
           f. AWAIT MCP response
           g. PARSE MCP content → proto Struct
           h. CONSTRUCT ToolInvocationResponse proto
           i. RETURN gRPC response
 
       CASE MCP → GRPC:
           a. MAP MCP sampling/createMessage → internal LLM service gRPC call
           b. TRANSLATE message format (MCP messages → proto LlmRequest)
           c. DISPATCH, AWAIT, TRANSLATE response back to MCP format
           d. RETURN MCP response
 
       CASE JSON_RPC → MCP:
           // Two-hop: JSON_RPC → GRPC → MCP (agent mediates)
           // Direct JSON_RPC → MCP is prohibited (violates layer isolation)
           RETURN error(PROTOCOL_VIOLATION, "Direct boundary→MCP not permitted")
 
5.  LOG transcoding event with latency, source, target, trace_id
6.  INCREMENT counter: gateway_transcoding_total{source, target, method, status}

4.7.4 Gateway Performance Model#

The gateway introduces transcoding latency ttranscodet_{\text{transcode}}. For a request traversing kk protocol boundaries:

Ttotal=Torigin+i=1kttranscode(i)+i=1ktnetwork(i)+TexecutionT_{\text{total}} = T_{\text{origin}} + \sum_{i=1}^{k} t_{\text{transcode}}^{(i)} + \sum_{i=1}^{k} t_{\text{network}}^{(i)} + T_{\text{execution}}

Minimizing kk is a key design goal; ideally k2k \leq 2 for any end-to-end path (boundary → internal → tool). The gateway caches compiled transcoding plans (method → proto type mapping, field mappings) to minimize per-request schema lookups, achieving ttranscode<1mst_{\text{transcode}} < 1\text{ms} for typical messages.


4.8 Authentication, Authorization, and Caller-Scoped Credential Propagation Across Protocol Boundaries#

4.8.1 Trust Model#

The agentic platform operates under a zero-trust, caller-scoped security model. Key principles:

  1. No Ambient Authority: Agents do not possess their own credentials. They operate under the authority of the originating caller (user, service account, or upstream agent).
  2. Caller-Scoped Credentials: The original caller's identity and permission scope propagate through every protocol boundary, from JSON-RPC ingress through gRPC mesh to MCP tool invocations.
  3. Least Privilege for Tools: Each tool invocation is authorized against the caller's scope, not the agent's. An agent cannot escalate privileges beyond what the caller granted.
  4. Human-in-the-Loop for Mutations: State-changing operations classified as NON_IDEMPOTENT_WRITE or DESTRUCTIVE may require explicit human approval before execution.

4.8.2 Credential Propagation Chain#

Define the credential propagation chain for a request originating from user uu with scope set Σu\Sigma_u:

uΣuBoundary (JSON-RPC)ΣuInternal (gRPC)ΣuTool (MCP)u \xrightarrow{\Sigma_u} \text{Boundary (JSON-RPC)} \xrightarrow{\Sigma_u'} \text{Internal (gRPC)} \xrightarrow{\Sigma_u''} \text{Tool (MCP)}

At each boundary, the scope may be narrowed but never widened:

ΣuΣuΣu\Sigma_u'' \subseteq \Sigma_u' \subseteq \Sigma_u

This is enforced by the gateway's scope-filtering middleware.

4.8.3 Authentication Mechanisms by Protocol Layer#

LayerMechanismToken FormatValidation
JSON-RPC (Boundary)Bearer token in Authorization headerJWT (RS256) or opaque reference tokenToken introspection or local JWT validation with JWKS
gRPC (Internal)Metadata key authorizationPlatform-issued short-lived JWTLocal validation against platform JWKS; mTLS for service identity
MCP (Tool)Bearer token in HTTP header (SSE) or env variable (stdio)Scoped access tokenTool server validates against platform token endpoint

4.8.4 Pseudo-Algorithm: Authorization Gate#

ALGORITHM AuthorizationGate
INPUT: request req, caller_identity id, required_scopes R, mutation_class mc
OUTPUT: ALLOW or DENY with reason
 
1.  EXTRACT caller scopes S from id.token:
       S ← decode_and_validate_token(id.token)
       IF token invalid or expired:
           RETURN DENY("Invalid or expired credentials")
 
2.  CHECK scope coverage:
       missing ← R \ S  // set difference
       IF |missing| > 0:
           RETURN DENY("Missing required scopes: " + missing)
 
3.  CHECK mutation class:
       IF mc = NON_IDEMPOTENT_WRITE OR mc = DESTRUCTIVE:
           IF policy_requires_approval(req.method, id.role):
               approval ← request_human_approval(req, id, timeout=approval_timeout)
               IF approval = DENIED OR approval = TIMEOUT:
                   RETURN DENY("Human approval not granted")
               RECORD approval_audit_entry(req, id, approval.approver, approval.timestamp)
 
4.  CHECK resource-level authorization:
       IF req targets specific resource R:
           IF NOT has_access(id, R, req.action):
               RETURN DENY("No access to resource " + R)
 
5.  ISSUE scoped downstream token:
       downstream_scopes ← S ∩ R  // narrow to only needed scopes
       downstream_token ← mint_token(id, downstream_scopes, ttl=req.remaining_deadline)
 
6.  ATTACH downstream_token to req.auth_context
7.  RETURN ALLOW

4.8.5 Credential Rotation and Revocation#

  • Short-Lived Tokens: Internal tokens have TTL ≤ 15 minutes; they are never persisted.
  • Revocation: A revocation event (user deactivation, scope change, security incident) propagates to all active sessions within trevocation30st_{\text{revocation}} \leq 30\text{s} via a revocation list broadcast.
  • Rotation: JWKS keys rotate on a configurable schedule (default: 24 hours) with overlap periods ensuring no validation gaps.

4.9 Observability Integration: Distributed Tracing (OpenTelemetry) Across All Protocol Layers#

4.9.1 Observability as a First-Class Protocol Concern#

An agentic system that cannot be observed cannot be debugged, optimized, or trusted. Observability is not an afterthought bolted onto the protocol stack—it is a first-class cross-cutting concern woven into every protocol boundary, every gateway transcoding, and every agent execution step.

The platform adopts OpenTelemetry (OTel) as the observability framework, providing unified tracing, metrics, and logging across all three protocol layers.

4.9.2 Trace Context Propagation#

Every request carries a W3C Trace Context consisting of:

  • trace-id: 128-bit globally unique trace identifier.
  • span-id: 64-bit identifier for the current span.
  • trace-flags: Sampling flags.
  • tracestate: Vendor-specific key-value pairs.

Propagation by Protocol Layer:

LayerPropagation Mechanism
JSON-RPC (HTTP)traceparent and tracestate HTTP headers
gRPCgrpc-trace-bin binary metadata key (or traceparent text metadata)
MCP (SSE/HTTP)traceparent HTTP header on POST requests; embedded in SSE event metadata
MCP (stdio)_trace field in JSON-RPC params or a dedicated trace envelope

4.9.3 Span Hierarchy for Agentic Execution#

The trace hierarchy mirrors the agentic execution structure:

[Root Span: user_request]
  ├── [Span: jsonrpc_gateway]
  │     ├── [Span: auth_validation]
  │     ├── [Span: schema_validation]
  │     └── [Span: grpc_transcode]
  ├── [Span: agent_orchestrator]
  │     ├── [Span: plan]
  │     ├── [Span: decompose]
  │     │     ├── [Span: subtask_1]
  │     │     └── [Span: subtask_2]
  │     ├── [Span: retrieve]
  │     │     ├── [Span: semantic_search]
  │     │     ├── [Span: exact_match]
  │     │     └── [Span: rank_fuse]
  │     ├── [Span: act]
  │     │     └── [Span: tool_call_via_mcp]
  │     │           ├── [Span: mcp_gateway_transcode]
  │     │           └── [Span: tool_execution]
  │     ├── [Span: verify]
  │     ├── [Span: critique]
  │     └── [Span: commit]
  └── [Span: response_synthesis]

4.9.4 Metrics at Protocol Boundaries#

Each protocol boundary emits standardized metrics:

Counter Metrics:

  • protocol_requests_total{protocol, method, status, version} — Total requests by method and outcome.
  • protocol_errors_total{protocol, error_code, method} — Error counts by code.
  • gateway_transcoding_total{source_protocol, target_protocol, method} — Transcoding volume.

Histogram Metrics:

  • protocol_request_duration_seconds{protocol, method} — Latency distribution.
  • gateway_transcoding_duration_seconds{source, target} — Transcoding latency.
  • mcp_tool_call_duration_seconds{tool_name, server_id} — Per-tool invocation latency.

Gauge Metrics:

  • grpc_active_streams{service, method} — Currently active streaming RPCs.
  • mcp_connected_servers{transport} — Active MCP server connections.
  • circuit_breaker_state{service} — 0=closed, 1=half-open, 2=open.

4.9.5 Pseudo-Algorithm: Observability Middleware#

ALGORITHM ObservabilityMiddleware
INPUT: request req, protocol_layer, handler_fn
OUTPUT: response with trace and metrics recorded
 
1.  EXTRACT OR GENERATE trace context:
       parent_ctx ← extract_trace_context(req, protocol_layer)
       IF parent_ctx = NULL:
           trace_id ← generate_trace_id()
           parent_ctx ← new_root_context(trace_id)
 
2.  START span:
       span ← tracer.start_span(
           name = protocol_layer + "." + req.method,
           parent = parent_ctx,
           attributes = {
               "protocol": protocol_layer,
               "method": req.method,
               "version": req.schema_version,
               "caller_id": req.auth.client_id
           }
       )
 
3.  START timer:
       t_start ← CURRENT_TIME_NS()
 
4.  EXECUTE handler:
       TRY:
           response ← handler_fn(req, span.context)
           span.set_status(OK)
           status_label ← "success"
       CATCH error e:
           span.set_status(ERROR)
           span.record_exception(e)
           status_label ← "error"
           response ← construct_error_response(e)
 
5.  RECORD metrics:
       t_duration ← CURRENT_TIME_NS() - t_start
       metrics.counter("protocol_requests_total",
           labels={protocol_layer, req.method, status_label, req.schema_version}).increment()
       metrics.histogram("protocol_request_duration_seconds",
           labels={protocol_layer, req.method}).observe(t_duration / 1e9)
       IF status_label = "error":
           metrics.counter("protocol_errors_total",
               labels={protocol_layer, response.error.code, req.method}).increment()
 
6.  INJECT trace context into response:
       inject_trace_context(response, span.context, protocol_layer)
 
7.  END span:
       span.end()
 
8.  RETURN response

4.9.6 Log Correlation#

All structured log entries include:

  • trace_id: For correlation with distributed traces.
  • span_id: For correlation with specific spans within a trace.
  • agent_id: The agent instance emitting the log.
  • task_id: The task being executed.
  • level: Severity (DEBUG, INFO, WARN, ERROR, FATAL).

Logs are emitted via OpenTelemetry's log bridge, ensuring they appear in the same trace viewer as spans and metrics.


4.10 Protocol Compliance Testing, Fuzzing, and Contract Verification in CI/CD#

4.10.1 Testing Taxonomy#

Protocol compliance testing for the agentic platform covers four layers:

Test CategoryWhat It ValidatesWhen It Runs
Schema ConformanceMessages match their IDL definitionsEvery commit (CI)
Contract CompatibilityNew schemas are backward-compatible with previous versionsEvery commit (CI)
Protocol ComplianceImplementations conform to JSON-RPC 2.0, gRPC semantics, MCP specNightly + pre-release
FuzzingRobustness against malformed, unexpected, or adversarial inputsContinuous (dedicated fuzzing infra)
Integration ComplianceEnd-to-end cross-protocol flows produce correct resultsPre-merge + staging

4.10.2 Schema Conformance Testing#

Pseudo-Algorithm: Schema Conformance Test Suite

ALGORITHM SchemaConformanceTests
INPUT: all proto files, json schemas, openapi specs, test corpus
OUTPUT: PASS/FAIL with detailed report
 
1.  FOR EACH proto file p:
       a. COMPILE p with protoc --strict mode
       b. GENERATE test message instances using proto-gen-fuzz
       c. ROUND-TRIP test: serialize → deserialize → assert equality
       d. VALIDATE default values match specification
 
2.  FOR EACH json schema j:
       a. VALIDATE j against meta-schema (Draft 2020-12)
       b. FOR EACH positive example e+ IN test_corpus[j]:
           ASSERT validate(e+, j) = PASS
       c. FOR EACH negative example e- IN test_corpus[j]:
           ASSERT validate(e-, j) = FAIL
           ASSERT error message identifies correct field
 
3.  FOR EACH openapi spec o:
       a. VALIDATE o with openapi-spec-validator
       b. GENERATE request/response examples from spec
       c. VALIDATE examples against corresponding json schemas
 
4.  CROSS-VALIDATE:
       FOR EACH type T with representations in multiple IDLs:
           proto_instance ← generate_random_instance(T, proto)
           json_transcoded ← proto_to_json(proto_instance)
           VALIDATE json_transcoded against json_schema[T]
           proto_round_tripped ← json_to_proto(json_transcoded)
           ASSERT proto_instance = proto_round_tripped (field-by-field)
 
5.  REPORT: total tests, pass count, fail count, coverage percentage

4.10.3 Contract Compatibility Testing#

This validates that schema changes do not break existing clients (§4.5.3). The breaking change detector runs on every pull request that modifies any schema file.

CI Pipeline Integration:

ALGORITHM ContractCompatibilityCI
INPUT: pull_request PR modifying schema files
OUTPUT: CI verdict (PASS, WARN, FAIL)
 
1.  IDENTIFY modified schema files in PR diff
2.  FOR EACH modified schema file f:
       a. FETCH previous released version f_old from schema registry
       b. RUN BreakingChangeDetector(f_old, f_new)
       c. IF breaking changes detected AND major version not bumped:
           FAIL CI with detailed breaking change report
       d. IF breaking changes detected AND major version bumped:
           REQUIRE migration guide document in PR
           REQUIRE deprecation notice for old version
           WARN with change summary
 
3.  RUN SchemaUnificationPipeline to ensure derived schemas are consistent
4.  RUN SchemaConformanceTests on new schemas
5.  IF all pass: APPROVE schema changes

4.10.4 Protocol Fuzzing#

Fuzzing validates robustness against adversarial or malformed inputs. The platform fuzzes at three levels:

Level 1: Serialization Fuzzing

  • Feed randomly mutated byte sequences to Protobuf deserializers.
  • Feed randomly generated JSON to JSON-RPC parsers.
  • Verify: no crashes, no unbounded memory allocation, proper error responses.

Level 2: Semantic Fuzzing

  • Generate structurally valid but semantically nonsensical messages (e.g., negative token budgets, impossible deadlines, tool names with injection characters).
  • Verify: proper validation errors, no state corruption, no information leakage in error messages.

Level 3: Protocol State Fuzzing

  • Send messages out of order (e.g., tools/call before initialize; gRPC data frames before HEADERS).
  • Send concurrent conflicting requests (e.g., simultaneous writes to the same memory key).
  • Verify: proper state machine enforcement, no deadlocks, no data races.

Pseudo-Algorithm: Protocol Fuzzer

ALGORITHM ProtocolFuzzer
INPUT: target_endpoint, protocol_type, corpus_seed, max_iterations N
OUTPUT: list of discovered violations
 
1.  INITIALIZE fuzzer:
       corpus ← load_seed_corpus(corpus_seed)
       violations ← []
       coverage_tracker ← new CoverageTracker()
 
2.  FOR i = 1 TO N:
       a. SELECT base input from corpus (weighted by coverage novelty)
       b. MUTATE base input using strategy based on protocol_type:
           SWITCH protocol_type:
               CASE JSON_RPC:
                   mutation ← random_choice([
                       flip_json_type,           // string ↔ number ↔ bool ↔ null
                       remove_required_field,
                       inject_extra_field,
                       overflow_string_length,
                       inject_unicode_edge_cases,
                       duplicate_id_field,
                       malform_json_syntax
                   ])
               CASE GRPC:
                   mutation ← random_choice([
                       corrupt_protobuf_varint,
                       truncate_message,
                       set_unknown_field_number,
                       overflow_repeated_field,
                       send_wrong_message_type
                   ])
               CASE MCP:
                   mutation ← random_choice([
                       send_before_initialize,
                       call_nonexistent_tool,
                       exceed_sampling_token_limit,
                       inject_malformed_cursor,
                       send_notification_with_id
                   ])
       
       c. SEND mutated input to target_endpoint
       d. CAPTURE response and system state:
           response_time, response_code, response_body, memory_usage, error_logs
       
       e. CHECK invariants:
           - No crash (process still alive)
           - Response time < timeout_limit
           - Memory usage < memory_limit
           - Response is valid protocol message (even if error)
           - No sensitive data in error response
           - No state corruption (verified by health check)
       
       f. IF any invariant violated:
           violations.append({
               input: mutated_input,
               violation_type: failed_invariant,
               response: captured_response,
               iteration: i
           })
           ADD mutated_input to corpus (for further exploration)
       
       g. UPDATE coverage_tracker with execution path information
 
3.  REPORT violations with reproducible test cases
4.  EMIT metrics: total_iterations, violations_found, coverage_percentage

4.10.5 Integration Compliance Testing#

End-to-end tests validate that the complete protocol stack (JSON-RPC → gateway → gRPC → MCP → tool → response) produces correct, observable, and secure results.

Test Scenario Categories:

  1. Happy Path: Submit task → agent plans → retrieves → calls tool → returns result. Validate trace completeness, response schema, and latency bounds.
  2. Deadline Propagation: Set a tight deadline at the boundary; verify it propagates to MCP tool calls and triggers DEADLINE_EXCEEDED at the correct layer.
  3. Auth Propagation: Submit request with limited scopes; verify tool calls are rejected when tool requires scope not in caller's set.
  4. Batch Semantics: Submit batch JSON-RPC request; verify independent execution, partial success handling, and response ordering.
  5. Stream Lifecycle: Open bidirectional gRPC stream; verify heartbeat enforcement, graceful half-close, and error propagation.
  6. Version Mismatch: Connect with old client version to new server; verify graceful degradation or informative rejection.
  7. Circuit Breaker: Simulate tool server failure; verify circuit opens after threshold, fast-fails during open state, and recovers during half-open.

Pseudo-Algorithm: End-to-End Compliance Test Runner

ALGORITHM EndToEndComplianceTestRunner
INPUT: test_suite TS, environment env (staging/CI)
OUTPUT: test report with pass/fail/skip per scenario
 
1.  PROVISION test environment:
       - Deploy gateway, agent services, mock MCP servers
       - Configure known tool schemas, auth tokens, rate limits
       - Initialize OpenTelemetry collector for trace capture
 
2.  FOR EACH test scenario s IN TS:
       a. SETUP:
           - Reset state (clear caches, memories, idempotency stores)
           - Configure mocks for s.expected_behavior
       
       b. EXECUTE:
           - Send request(s) per s.protocol and s.input
           - Capture response(s), latency, traces, metrics, logs
       
       c. ASSERT response correctness:
           - Response schema matches expected schema
           - Response values match expected values (or acceptable ranges)
           - Error codes match expected error codes (for failure scenarios)
       
       d. ASSERT observability:
           - Trace exists with expected span hierarchy
           - All spans have required attributes (protocol, method, caller_id)
           - Metrics counters incremented correctly
           - Logs contain trace_id correlation
       
       e. ASSERT security:
           - Auth context propagated correctly through trace
           - Unauthorized requests rejected at correct layer
           - No credential leakage in error responses or logs
       
       f. ASSERT performance:
           - Latency within SLO bounds for the scenario
           - No memory leaks (compare pre/post memory)
       
       g. RECORD result: {scenario: s.name, status: PASS/FAIL, details, duration}
 
3.  GENERATE test report:
       - Total: pass/fail/skip counts
       - Coverage: protocol paths exercised
       - Regressions: newly failing tests (compared to last run)
       - Performance: latency percentiles per scenario
 
4.  IF any FAIL AND env = CI:
       BLOCK merge with failure report

4.10.6 Continuous Compliance Enforcement#

Compliance testing is not a one-time activity; it is a continuous enforcement mechanism integrated into the development lifecycle:

TriggerTests RunGate
Every commitSchema conformance, contract compatibilityMerge blocked on failure
Every PR with schema changesBreaking change detection, cross-IDL validationMerge blocked on failure
NightlyFull protocol compliance suite, integration testsAlert on regression
Continuous (background)Fuzzing (all levels)Ticket created on violation
Pre-releaseFull suite + performance benchmarks + canary deploymentRelease blocked on failure
Post-deploymentSynthetic probes (golden signal tests)Rollback on SLO violation

Quality Gate Formula:

A release candidate RCRC passes the protocol compliance gate if:

Gate(RC)={PASSif npassntotalθpassncritical_fail=0tp99tSLOFAILotherwise\text{Gate}(RC) = \begin{cases} \text{PASS} & \text{if } \frac{n_{\text{pass}}}{n_{\text{total}}} \geq \theta_{\text{pass}} \wedge n_{\text{critical\_fail}} = 0 \wedge t_{p99} \leq t_{\text{SLO}} \\ \text{FAIL} & \text{otherwise} \end{cases}

where θpass\theta_{\text{pass}} is the minimum pass rate (typically 0.998\geq 0.998), ncritical_failn_{\text{critical\_fail}} counts failures in security, auth propagation, or data integrity tests, and tp99t_{p99} is the 99th percentile latency.


Chapter Summary#

This chapter established the typed protocol stack that transforms an agentic platform from ad hoc prompt orchestration into a deterministic, observable, versionable distributed system. The key architectural decisions and their justifications:

DecisionJustification
JSON-RPC 2.0 at the boundaryUniversal accessibility, transport neutrality, batch semantics, minimal integration burden
gRPC/Protobuf internallyBinary efficiency (310×3\text{–}10\times compression), streaming, strong typing, deadline propagation, native cancellation
MCP for discoveryRuntime-dynamic tool/resource/prompt registration, capability negotiation, bidirectional context exchange, lazy loading for token budget optimization
Cross-protocol gateway with codec pipelineClean layer isolation while enabling end-to-end flows; cached transcoding plans for sub-millisecond translation
Caller-scoped credential propagationZero-trust security model; scope narrowing across boundaries (ΣΣΣ\Sigma'' \subseteq \Sigma' \subseteq \Sigma); human-in-the-loop for destructive mutations
OpenTelemetry across all layersUnified trace context propagation; span hierarchy mirrors agent execution structure; mechanical correlation of logs, metrics, and traces
Continuous contract verificationBreaking changes detected in CI; fuzzing discovers robustness gaps; integration tests validate end-to-end correctness; quality gates enforce release criteria

The protocol stack is not merely a communication layer—it is the immune system of the agentic platform. Every typed contract prevents a class of runtime errors. Every versioned schema prevents a class of deployment failures. Every propagated deadline prevents a class of resource leaks. Every scoped credential prevents a class of security violations. Every trace span prevents a class of debugging nightmares. Together, they establish the mechanical foundation upon which reliable, scalable, and auditable agentic systems are built.


End of Chapter 4.