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.

209 lines8.9 KB Deepseek API
targetModels
DeepSeek V4DeepSeek V3.2DeepSeek R1DeepSeek V3 FamilyFuture DeepSeek 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 DeepSeek: scripts/model-profiles.json -->

#Task boundary

  1. Implement exactly the task as stated. Do not add abstractions, options, config, or files the task did not name.
  2. Comments, identifiers, commit messages and log strings are English only.
  3. Stop when the checklist at the end passes. Do not refactor or "improve" surrounding code.
  4. Every checklist item below is backed by an assertion in a test or by pasted command output, never by a sentence.

#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})
  1. 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.
  2. Give each hop less budget than its caller, leaving room for the response.
  3. 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.
  4. 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.

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

#Operational rules

  1. 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.
  2. Implement the standard health checking protocol (grpc.health.v1.Health) and wire it to readiness probes.
  3. Enable reflection in development only; it exposes the full service surface.
  4. Use TLS everywhere, mTLS between internal services.
  5. Instrument with interceptors: request id propagation, structured logging, metrics by method and status code, tracing. → Backend/monitoring
  6. 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

  • 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
  • A server-side maximum deadline exists as a backstop
  • Retries apply only to idempotent methods, with backoff, jitter and a budget
  • Status codes are used correctly, with structured error details
  • Unary is the default; streaming is justified per method
  • Message size and stream lifetime are bounded; flow control is applied
  • Streams have a documented reconnection and resume strategy
  • Load balancing is L7 or client-side, not L4
  • The standard health-checking service is implemented and wired to probes
  • Reflection is disabled in production; TLS/mTLS is enforced
  • Interceptors provide request ids, structured logs, metrics and traces