Skip to content

API Reference

Runtime exposes two surfaces:

SurfaceProtocolEntry point
Public invocationHTTP/SSEPOST /v1/sessions/{session_id}/invoke via runtime-router
Internal gRPCgRPC + Protobufruntime.v1.RuntimeService on port 50051

The HTTP surface is the only surface clients call directly. The gRPC surface is internal — called by runtime-router after routing to the correct tenant pod.

Proto source: paper-board/proto/paperboard/runtime/v1/runtime.proto


Initiates or continues an agent session. Responses are Server-Sent Events (SSE).

Path parameters

ParameterTypeDescription
session_idstring (UUID)The agent session identifier, created by the agents service.

Request headers

HeaderRequiredDescription
AuthorizationYesBearer <JWT> — identity-issued JWT; router calls identity.AuthService.VerifyJWT to extract tenant_id, org_id, user_id.
X-Request-IdRecommendedCaller-supplied request identifier; forwarded as x-request-id gRPC metadata to the tenant pod.
X-Trace-IdRecommendedDistributed trace identifier; forwarded as x-trace-id gRPC metadata.

Response

200 OKContent-Type: text/event-stream

The response body is a stream of SSE events. Three event types are emitted:

SSE eventdata shapeDescription
(default)<text chunk string>Partial assistant text. Embedded newlines are escaped as \n.
turn_done{}The LLM turn completed. No more text chunks for this invocation.
error{"message": "<description>"}A non-recoverable error occurred mid-stream.

401 Unauthorized — missing or invalid JWT.

503 Service Unavailable — tenant pod unavailable after cold-start retry. Retry-After: 5 seconds.

Cold-start behavior

If the tenant Deployment is scaled to 0 (idle), the router calls EnsureReady, waits up to 10 seconds for the pod to be reachable, then retries the Invoke RPC once. If the retry fails, the router returns 503.


rpc Invoke(InvokeRequest) returns (stream InvokeEvent);

Server-streaming RPC. Called by runtime-router on the per-tenant pod. The tenant pod forwards the request to the agents service, injecting tenant context as gRPC metadata.

FieldTypeDescription
session_idstringAgent session identifier.
request_idstringRequest identifier (echoed from X-Request-Id header).
trace_idstringDistributed trace identifier (echoed from X-Trace-Id header).

Tenant context (tenant_id, org_id, user_id) is NOT part of this request message. It is propagated via gRPC incoming metadata headers:

Metadata keySet byValue
x-tenant-idruntime-routerTenant UUID from JWT claims
x-org-idruntime-routerOrg UUID from JWT claims
x-user-idruntime-routerUser UUID from JWT claims
x-request-idruntime-routerRequest identifier
x-trace-idruntime-routerTrace identifier

The tenant pod reads TENANT_ID and ORG_ID from its pod environment (injected at creation time by the controller); the metadata values carry the per-request user identity.

FieldOneof typeDescription
text_chunkTextChunkEventPartial assistant text from the LLM.
turn_doneTurnDoneEventThe current LLM turn has completed.
errorErrorEventNon-recoverable error; stream ends after this event.

TextChunkEvent.content is a UTF-8 string. ErrorEvent.message is a human-readable description; do not parse it programmatically.

Status codes

gRPC codeMeaning
OKStream completed normally.
UNIMPLEMENTEDInvoke is not yet wired to agents (pending S12).
UNAUTHENTICATEDMetadata missing required tenant headers.
INTERNALAgents call failed.

rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

Standard gRPC health protocol (grpc.health.v1). Each tenant pod registers runtime.v1.RuntimeService with SERVING status on startup.

Terminal window
grpc_health_probe -addr=<pod-ip>:50051 -service=runtime.v1.RuntimeService

Tenant pod lifecycle — controller API (internal)

Section titled “Tenant pod lifecycle — controller API (internal)”

The controller is not a gRPC service. It is an in-process library called by cmd/controller/main.go. The methods below are the public Go API; they are summarized here for reference.

EnsureReady(ctx, tenantID, orgID) → (deployName, scaleState, error)

Section titled “EnsureReady(ctx, tenantID, orgID) → (deployName, scaleState, error)”

Idempotent. Ensures the tenant Deployment exists at replicas ≥ 1.

scaleStateMeaning
runningPod was already running; no action taken.
scaled-upDeployment existed at replicas=0; scaled to 1.
createdNew Deployment + Service created.

On conflict between concurrent callers (K8s AlreadyExists), returns running. Retries on K8s transient errors (Conflict, ServerTimeout, TooManyRequests) with exponential backoff: 100ms / 200ms / 400ms / 800ms / 1.6s. Returns DEADLINE_EXCEEDED after 5 failed attempts.

Scales Deployments to 0 when runtime.paper-board.io/last-seen-at is older than 60 minutes. Called every 5 minutes by the sweep loop.

Deletes Deployments (and their Services) that have been at replicas=0 for ≥ 7 days. Called daily.

Hard-deletes the tenant Deployment and Service. Not recoverable; the next request will re-create them via EnsureReady.

Patches runtime.paper-board.io/last-seen-at on the Deployment using a strategic merge patch. Called by the router after each successful Invoke forward.


ResourceName patternExample
Deploymentruntime-t-{tenant_short}runtime-t-a1b7558f
Serviceruntime-t-{tenant_short} (same as Deployment)runtime-t-a1b7558f
DNS{deploy-name}.paper-board.svc.cluster.local:50051runtime-t-a1b7558f.paper-board.svc.cluster.local:50051

Tenant short-id derivation

tenant_short = lower(hex(tenant_uuid)[0:8])

Example: a1b7558f-d0ed-9de1-0aaa-...a1b7558f

Collision policy

On hash prefix collision between two different tenants, the controller appends -2, -3, … until a free slot is found. The resolved short-id is stored in the Deployment’s runtime.paper-board.io/tenant-short annotation. All subsequent lookups for that tenant read the stored annotation rather than recomputing.


KeySet onSet byValue
runtime.paper-board.io/tenant-shortDeploymentcontrollerResolved short-id (e.g. a1b7558f)
runtime.paper-board.io/tenant-idDeployment, ServicecontrollerFull tenant UUID
runtime.paper-board.io/org-idDeploymentcontrollerOrg UUID
runtime.paper-board.io/last-seen-atDeploymentrouter (via UpdateLastSeen)RFC3339 timestamp of last routed request
runtime.paper-board.io/tenantDeployment (label)controllertrue — label selector for sweep operations
app.kubernetes.io/managed-byDeployment (label)controllerruntime-controller