Claude Fable 5.1 & GPT-6 Astra packages are live

Rpc

Free

gRPC and typed RPC — protobuf evolution rules, deadlines and cancellation, streaming, error models, and when RPC beats REST.

224 lines9.6 KB Gemini API
targetModels
Gemini 3.8 FlashGemini 3.7 FlashGemini 3.1 ProGemini 3 FamilyFuture Gemini Models
name
rpc
category
API
description
gRPC and typed RPC — protobuf evolution rules, deadlines and cancellation, streaming, error models, and when RPC beats REST.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Gemini: scripts/model-profiles.json -->

#Purpose

Rules for RPC-style APIs — gRPC, Connect, tRPC, Twirp. RPC models procedures, not resources: the unit is an operation with a typed request and response.

Use RPC for internal service-to-service traffic where you control both ends and want a schema-enforced contract, low overhead and streaming. Use REST for public APIs consumed by clients you do not control. → API/rest


#Schema evolution is the whole contract

Protobuf's wire format is positional: field numbers are the contract, names are not.

proto
message Order {
  string id            = 1;
  int64  total_cents   = 2;
  string currency      = 3;
  reserved 4;                        // was `amount`, removed 2026-08 — never reuse
  reserved "amount";
  OrderStatus status   = 5;
}
ChangeSafe
Adding a new field with a new numberYes
Renaming a field (same number)Yes on the wire; breaks JSON mapping and generated code
Removing a fieldOnly with reserved on its number and name
Reusing a field numberNever — old clients decode garbage into the new field
Changing a field's typeNo, except within the documented compatible sets
Changing optional/repeatedNo
Adding an enum valueWire-safe; clients must have a default branch
Renaming a service or methodBreaking — it is the wire path

Reusing a retired field number is the classic protobuf data-corruption bug: a stale client sends its old meaning, and the new server accepts it as the new field. reserved makes it a compile error instead.

Gate it in CI:

bash
buf breaking --against '.git#branch=main'
buf lint

Every enum reserves 0 as *_UNSPECIFIED. Proto3 cannot distinguish an unset scalar from its zero value, so 0 must never be a meaningful state.


#Deadlines and cancellation

Every RPC call sets a deadline. This is not optional and it is the single most common gRPC production failure: a call with no deadline waits forever, holds a connection and a goroutine, and cascades into a fleet-wide hang.

go
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
resp, err := client.GetOrder(ctx, &pb.GetOrderRequest{Id: id})
  • Propagate the deadline through every downstream call. gRPC does this via context automatically — do not create a fresh context.Background() mid-chain, which severs cancellation.
  • Give each hop less budget than its caller, leaving room for the response.
  • Honour cancellation server-side: check ctx.Err() before expensive work and between loop iterations. A client that gave up should not still be costing you.
  • Set a server-side maximum as a backstop against clients that omit deadlines.

Retries must only apply to idempotent methods, with backoff and jitter, and a budget (retryThrottling) so a struggling service is not retried into collapse. → System Design/resilience


#Errors

Use the standard status codes; they carry retry semantics that generated clients and service meshes act on.

CodeMeaningClient retries
INVALID_ARGUMENTRequest is malformed regardless of stateNo
FAILED_PRECONDITIONValid, but system state forbids itNo, until state changes
NOT_FOUNDAbsent, or hidden from this callerNo
PERMISSION_DENIEDAuthenticated, not authorizedNo
UNAUTHENTICATEDMissing or invalid credentialsAfter re-auth
RESOURCE_EXHAUSTEDQuota or rate limitWith backoff
ABORTEDConcurrency conflictYes, at a higher level
UNAVAILABLETransient — the only safely auto-retried codeYes
DEADLINE_EXCEEDEDRan out of timeOnly if idempotent
INTERNALServer bugNo

Attach machine-readable detail with google.rpc.ErrorInfo and BadRequest rather than encoding structure into the message string.

Never return INTERNAL for a client mistake — it is unretryable, alerts your on-call, and tells the caller nothing actionable.


#Streaming

PatternUse for
UnaryDefault. Use it unless you have a reason not to
Server streamingLarge result sets, live feeds
Client streamingBulk upload, telemetry
BidirectionalInteractive sessions

Streams are stateful and complicate load balancing, retries and deployments — a long-lived stream pins a client to one pod across a rollout.

  • Bound stream lifetime and message size (grpc.max_receive_message_length).
  • Apply flow control; an unbounded producer will exhaust the consumer's memory.
  • Design reconnection with resume tokens, because streams will break.
  • Server streaming is not a substitute for pagination when the client wants a page. → API/pagination

#Operational rules

  • Connection-level load balancing fails with HTTP/2. gRPC multiplexes over one long-lived connection, so an L4 balancer pins all traffic to one backend. Use an L7 proxy (Envoy, Linkerd) or client-side load balancing with resolver updates.
  • Implement the standard health checking protocol (grpc.health.v1.Health) and wire it to readiness probes.
  • Enable reflection in development only; it exposes the full service surface.
  • Use TLS everywhere, mTLS between internal services.
  • Instrument with interceptors: request id propagation, structured logging, metrics by method and status code, tracing. → Backend/monitoring
  • For browser clients, gRPC needs a proxy (grpc-web) — Connect speaks both protocols and is usually the better choice there.

#Anti-patterns

Anti-patternWhy it failsFix
Reusing a retired field numberOld clients corrupt new datareserved number and name
Removing a field without reservedNumber becomes reusable by accidentAlways reserve
Meaningful enum 0Indistinguishable from unset in proto3*_UNSPECIFIED = 0
No breaking-change gateIncompatible schemas shipbuf breaking in CI
Calls without deadlinesHangs cascade fleet-wideDeadline on every call
Fresh context.Background() mid-chainCancellation and deadline lostPropagate the context
Ignoring ctx.Err() server-sideWork continues for a gone clientCheck between steps
Retrying non-idempotent methodsDuplicate side effectsRetry only idempotent calls
Retries without a budgetRetry storm collapses the serviceBackoff, jitter, throttling
INTERNAL for bad inputUnretryable, pages on-call, unactionableINVALID_ARGUMENT
Error detail encoded in the messageClients parse proseErrorInfo details
L4 load balancer in front of gRPCAll traffic pins to one backendL7 proxy or client-side LB
Streaming where unary would doStateful, breaks rollouts and balancingUnary by default
Unbounded stream or message sizeMemory exhaustionExplicit limits and flow control
Reflection enabled in productionFull surface disclosedDevelopment only

#Checklist

  • Verify: Field numbers are never reused; removals use reserved for number and name
  • Verify: Every enum reserves 0 as UNSPECIFIED
  • Verify: buf lint and buf breaking run in CI against the merge base
  • Verify: Every client call sets an explicit deadline
  • Verify: Deadlines propagate through the call chain with a shrinking budget
  • Verify: Servers check for cancellation before and during expensive work
  • Verify: A server-side maximum deadline exists as a backstop
  • Verify: Retries apply only to idempotent methods, with backoff, jitter and a budget
  • Verify: Status codes are used correctly, with structured error details
  • Verify: Unary is the default; streaming is justified per method
  • Verify: Message size and stream lifetime are bounded; flow control is applied
  • Verify: Streams have a documented reconnection and resume strategy
  • Verify: Load balancing is L7 or client-side, not L4
  • Verify: The standard health-checking service is implemented and wired to probes
  • Verify: Reflection is disabled in production; TLS/mTLS is enforced
  • Verify: Interceptors provide request ids, structured logs, metrics and traces

#Anchors (restated last, read last)

The rules that must hold when you stop, repeated here because the end of the context is what you act on:

  • Never return INTERNAL for a client mistake — it is unretryable, alerts your on-call, and tells the caller nothing actionable.

  • Field numbers are never reused; removals use reserved for number and name

  • Every enum reserves 0 as UNSPECIFIED

  • buf lint and buf breaking run in CI against the merge base

  • Every client call sets an explicit deadline

  • Deadlines propagate through the call chain with a shrinking budget

  • Servers check for cancellation before and during expensive work

Before reporting done, prove the module still imports — run the line for this stack and paste its output:

bash
python -c "import <package>"          # Python: the package you changed
node -e "require('./<entry>')"       # Node CJS, or: node --input-type=module -e "import './<entry>.js'"
go build ./...                        # Go