Documentation

The curated reference — enough to configure and operate riz without reading source. For exhaustive depth, the repository docs and the 900+ tests are the ground truth this page is pinned to.

Install

One static binary for Linux/macOS (x86_64 + aarch64). Runtime dependencies are per-language: Bun for TypeScript, node, python3; Rust handlers are compiled binaries; WASM needs nothing (wasmtime is embedded).

shell
$ curl -fsSL https://riz.dev/install | sh        # release binary
$ cargo install --git https://github.com/24X7/riz # from source
$ riz doctor                                      # pre-flight check

Scaffold a project

riz init always fetches templates from a git location — nothing is embedded in the binary. Pass an official name (riz init --list), any owner/repo, a subdirectory of a repo, a git URL, or a local path — so teams can publish and use their own templates. Set RIZ_TEMPLATE_REPO to resolve official names from a fork.

shelltemplates load from git, never include_str!
$ riz init typescript-todo my-app              # official name → riz repo (full-stack demo)
$ riz init owner/repo my-app                   # any GitHub repo
$ riz init owner/repo/path/to/tmpl#v2 my-app   # a subdirectory, at a ref/tag
$ riz init https://host/o/r.git my-app         # any git URL (incl. file://)
$ riz init ./local/template my-app             # a local path (offline)

Official templates: typescript-http · nodejs-http · python-http · rust-http · go-http · wasm-http · the three WebSocket variants · and typescript-todo — a full-stack sample (Bun API + React/Vite client served on one binary). Flags: --ref <branch|tag>, --force (write into a non-empty dir), --git (init a repo + first commit).

riz.toml

One file declares the fleet. One [function.<name>] block = one warm process pool = N routes, mirroring AWS Lambda + API Gateway v2. Omit routes and the function serves ANY /<name>.

riz.tomlvalidated by the real config code in CI
[server]
port = 3000
host = "127.0.0.1"

[function.api]
runtime = "bun"            # bun | node | python | rust | go | wasm
handler = "src/index.handler"  # AWS file.export resolution
timeout_ms = 30000         # handler kill + respawn deadline
concurrency = 10           # pool size = max in-flight

[[function.api.routes]]
path = "/accounts/{id}"    # AWS syntax: {param}, {proxy+}
method = "GET"

Function fields

keydefaultwhat it does
runtimebun · node · python · rust · go · wasm (rust/go = unmodified official AWS Lambda binaries via the Runtime API)
handlerAWS-style file.export; for rust/go/wasm, a path to the artifact
timeout_ms30000handler deadline — exceeded = child killed + respawned
integration_timeout_ms30000gateway wait — exceeded = 504 to the client
concurrency1worker processes in the pool (≥ 1 enforced)
envoffper-function environment variables injected at spawn ([function.X.env]) — the standard way to hand a handler its DATABASE_URL / API keys; riz's own AWS_* vars win on conflict; process runtimes only (WASM keeps its deny-by-default WASI env)
memory_mb / cpu_time_secsoffopt-in RLIMIT_AS / RLIMIT_CPU caps
allowed_pathsoffLinux Landlock filesystem allowlist (WASI preopens for wasm)
authorizeroff"fnName" (REQUEST), "none", or an inline JWT block
guard_in / guard_outoff.wasm policy modules — see Guards
capabilitiesoffbroker grants (wasm only) — see Broker
mcpofftool description / typed query params / body schema — see MCP

MCP tools

Every function is a tool at /_riz/mcp (JSON-RPC 2.0 over Streamable HTTP, spec 2025-11-25, negotiates down to 2024-11-05). Path params are typed + required from the route template automatically. WebSocket functions are tools too: a tools/call with {message, timeout_ms?} opens an ephemeral real session — $connect, the message as $default, every @connections push collected as reply frames, then $disconnect. The optional block sharpens the rest:

riz.tomlvalidated in CI
[function.search]
runtime = "bun"
handler = "./index.search"

[function.search.mcp]
description = "Search the catalog."

[function.search.mcp.query.limit]
type = "integer"      # string | integer | number | boolean
description = "Max results"
required = true

Guards

A guard is a wasm32-wasip1 module run as a sibling pool. guard_in sees every event before the handler; guard_out sees every response before bytes leave. One module can serve both. Failures fail closed.

verdict contractone JSON line per input
{"action":"allow"}                          # pass through
{"action":"allow", "event":{…}}            # guard_in: mutated event
{"action":"allow", "response":{…}}         # guard_out: replaced response
{"action":"deny", "statusCode":451, "body":"…"}

Capability broker

WASM-only in v1. Resources are declared once (credentials host-side via env names); grants reference them per function. The guest calls pg_query(grant, request) via host imports — no sockets, no DSNs in guest memory.

grant keydefaultenforces
type / resourcev1: "pg" → a declared [resources.pg.<name>]
moderead-writeread-only runs inside a genuine READ ONLY transaction
max_inflight4concurrency cap — excess rejected (throttled), never queued
rate_per_sectoken-bucket rate limit
call_timeout_ms1500per-call deadline — a stalled backend is cut, host unaffected
max_request_bytes / max_response_bytes64 KiB / 1 MiBpayload caps both directions
Error codes the guest can match on: denied · throttled · timeout · too_large · bad_request · backend — a closed set, always JSON, never a trap.

LLM gateway

Enable with a [gateway] block (see the gateway page for a full example). Providers: openai, anthropic, ollama, mock. Routing by model prefix, then default_provider, then the fallback chain.

endpointdoes
POST /_riz/v1/chat/completionsOpenAI shape; SSE on stream: true
POST /_riz/v1/embeddingsembeddings, same routing
GET /_riz/v1/modelsconfigured providers/models
GET /_riz/v1/usagecumulative cost + tokens per provider

budget_usd breach → HTTP 412. Unknown models bill at the most expensive tier (the cap fails closed); ollama/* is known-free. All /_riz/v1/* routes are bearer-gated when a token is configured.

Endpoint surface

endpointauthdoes
GET /health · GET /readyopenliveness / readiness probes
GET /_riz/healthopenper-function health, percentiles, guard timing
GET /_riz/metricsbearerPrometheus text format
GET /_riz/registrybearerfunction + route inventory
POST /_riz/mcpbearerMCP JSON-RPC (SSE when negotiated)
GET /_riz/connections · /_riz/connections/{id}bearerWebSocket @connections management
/_riz/v1/*bearerthe LLM gateway
POST /deploydeploy_key + CIDR allowlistS3 hot-swap with health-check auto-rollback
POST /cache/invalidatebearerevict cached responses by key or prefix

"bearer" = gated when RIZ_AUTH_BEARER_TOKEN / [auth] bearer_token is set (constant-time compare); open for local dev when unset.

Scaling note (WebSockets): the connection registry behind @connections is per-instance (in-memory). Running multiple replicas behind a load balancer requires sticky sessions (session affinity — Cloud Run --session-affinity, ALB target-group stickiness, nginx ip_hash); a push to a socket held by another replica returns 410 Gone, exactly as API Gateway does for a stale connection. Cross-replica broadcast needs a shared backplane — a roadmap item, not in the binary today. The full story is on the compare page.

Observability

riz exports traces as OTLP/HTTP-JSON to <endpoint>/v1/traces — spec-correct hex ids, a service.name = riz resource attribute — so any OTLP backend accepts them. There is exactly one export path: every backend below is the same POST with a different endpoint + headers. Independently, GET /_riz/metrics serves Prometheus text.

[telemetry]
enabled  = true
endpoint = "http://localhost:4318"   # any OTLP/HTTP collector or agent base URL

[telemetry.headers]           # optional — added verbatim to every export
# x-honeycomb-team = "your-key"
backendendpoint[telemetry.headers]
OTel Collector · Grafana Tempo · Jaegerhttp://localhost:4318
Datadog — Agent / Collector OTLP receiverhttp://localhost:4318— (enable the agent's OTLP ingest on :4318)
Honeycombhttps://api.honeycomb.iox-honeycomb-team
AWS X-Ray — via ADOT / OTel Collectorhttp://localhost:4318

The GA route to any vendor is an OTLP collector/agent — riz never speaks a vendor wire protocol (no StatsD, no X-Ray UDP). Datadog's agentless OTLP trace intake (a dd-api-key header straight to Datadog) is GA for metrics/logs but Preview for traces; use the Agent/Collector path until you have access. LLM-gateway spans carry OpenTelemetry GenAI attributes (gen_ai.usage.input_tokens / output_tokens, gen_ai.request.model, gen_ai.provider.name), so Datadog LLM Observability and similar classify them automatically. Copy-paste blocks for every backend: docs/integrations/observability.md.

CLI

commanddoes
riz run · riz --devboot headless · boot with the live Ratatui dashboard (--dev goes before any subcommand; run is the default)
riz init <spec> [dir] [--ref r] [--git]scaffold from git — an official name (--list), owner/repo[/subdir], a git URL, or a local path (never embedded)
riz scaffold static [dir] [--wire] [--force]generate llms.txt + .well-known/riz.json from your functions; --wire adds a [static] block
riz validate · riz routesconfig check · route table
riz deploy <fn> <bucket> <key>S3 hot-swap from the CLI
riz mcp inspect [--url] [--bearer]initialize + tools/list + typed params + SSE probe, one screen
riz doctorpre-flight: config, runtimes on PATH, handler files, port, MCP ping

A2A agent

An [agent] block (requires [gateway]) turns the instance into an agent2agent-protocol server: an Agent Card at /.well-known/agent-card.json and JSON-RPC at POST /_riz/a2a (SendMessage · SendStreamingMessage, SSE — live status + artifact events · GetTask · CancelTask; bearer-gated like the rest of /_riz/*). A delegated task runs a server-side agent loop — the configured model reasons through the gateway with this instance's own functions as tools (the same typed MCP tools, WebSocket sessions included), every call metered and budget-capped.

keydefaultwhat it does
name / descriptionAgent Card identity
modelgateway model the agent reasons with (anthropic/claude-…, mock, …)
system_promptoffprepended to every task
toolsall functionsallowlist of functions the agent may wield (also gates the card's skills)
max_hops5agent-loop cap: one model → tools round trip = one hop
task_timeout_ms60000how long SendMessage blocks before returning a working snapshot (poll GetTask)
peersoff[agent.peers] name→URL map — each peer's Agent Card is fetched and the peer becomes a delegate_to_<name> tool (riz-to-riz mesh; riz-a2a-hop header + max_hops stop delegation loops). Test any A2A server with riz a2a send <url> "message"

Lifecycle

Static files

Point [static] at a directory and a riz instance serves it on the same binary and origin as your API — no second host, no CORS, no extra infra. The compounding use is self-description: drop an llms.txt and .well-known/riz.json in that dir and a live instance answers GET /llms.txt and /.well-known/riz.json, so an agent pointed at your host discovers the when-to-use card and the /_riz/mcp endpoint with no separate marketing site. Colocating an SPA or landing page is the second use.

[static]
dir = "./public"            # required — served as the site root
mount = "/"                 # URL prefix (default "/")
spa_fallback = false        # unknown HTML route → index.html (history-API SPAs)
precompressed = false       # serve file.br / file.gz when the client allows

Don't hand-write those discovery files — generate them. riz scaffold static reads your riz.toml and emits public/llms.txt + public/.well-known/riz.json with every function rendered as a tool entry (name, runtime, routes, description) matching exactly what /_riz/mcp advertises; --wire adds the [static] block for you. Re-run it when your functions change and the instance's self-description stays in sync.

proof: function_route_wins_over_static_file_at_same_path
proof: live_instance_serves_its_llms_txt_and_well_known
proof: scaffolded_files_are_served_by_a_static_instance
proof: path_traversal_dotdot_is_rejected