Appearance
Agent runtime
Status: Implemented in v0.1 — v0.1 concepts. APIs shown are the as-built v0.1 surface.
The runtime is the governed execution engine. Every capability invocation — from the AI SDK adapter, an MCP client, a workflow step, a test, or your own server code — funnels through it. There is exactly one execution path, so there is exactly one place where governance is enforced.
ts
const runtime = createAgentRuntime({
registry: capabilities,
policies: [orgIsolation, refundLimit],
approvals: { coordinator: approvalStore },
audit: auditSink,
tracing: createOpenTelemetryTracing(),
});What the runtime does
For each invocation (normative detail: execution pipeline):
- Resolves and gates — id lookup, surface exposure (deny by default).
- Validates — input against the procedure's schema; later, output against its schema.
- Evaluates policies — deterministic decisions: allow, deny, hide, require-approval.
- Coordinates approvals — suspends, binds input by hash, resumes with integrity checks.
- Bounds execution — timeout + caller cancellation as one composed
AbortSignal. - Invokes the procedure — through oRPC's call path, so your middleware runs unchanged.
- Retries — only within explicit, side-effect-aware eligibility rules.
- Normalizes errors — everything becomes a
CapabilityErrorwith a model-safe face. - Emits evidence — audit events and trace spans for every stage that matters.
What the runtime refuses to do
- Authenticate. Adapters and your app own identity; the runtime verifies an
Actoris present and well-formed, nothing more. - Authorize application-level questions. "May Dana refund order 42?" is your middleware's question; the runtime guarantees middleware runs, and adds agent-specific gates around it (ADR-008).
- Persist. Approval storage and audit storage are interfaces; core ships only an in-memory coordinator for dev/test (ADR-010).
- Call models. The runtime has no idea LLMs exist (ADR-003).
The result envelope
invoke never throws for governed outcomes; it returns one of four statuses:
ts
const result = await runtime.invoke("orders.refund", input, { actor, context });
switch (result.status) {
case "completed": result.output; // redacted output
case "approval-required": result.approval; // suspended; surface to an approver
case "failed": result.error; // CapabilityError (denials, validation, handler errors)
case "cancelled": result.error; // TIMEOUT or CANCELLED
}Why an envelope instead of exceptions: approval-required is a normal outcome, not an error; adapters need a deterministic, exhaustive set of cases to translate; and control flow by exception across an adapter boundary loses type information. Direct callers who prefer throwing use unwrap(result).
Discovery
ts
const descriptors = await runtime.describe("aiSdk", { actor, context });Discovery returns minimal, policy-filtered descriptors (id, description, input JSON Schema, classifications, requiresApproval hint). It is a courtesy to clients, never a security boundary — the pipeline re-checks everything on invoke (SI-2).
One runtime or several?
A runtime binds a registry to one governance configuration. Common shapes:
- One runtime for the app — typical; per-request data flows through
actor/context, not runtime construction. - Surface-specific runtimes — an MCP deployment with a narrowed registry and stricter policies alongside a fuller internal runtime.
- Test runtimes —
@orpc-agent/testingbuilds deterministic ones with fakes (adapters/testing.md).
Runtimes are stateless per invocation and safe for concurrent use; state lives in the coordinator and your sinks.
Related
- Normative: execution-pipeline.md · API: reference/runtime.md
- Policies · Approvals · Errors · Lifecycle walkthrough