Workspace runtime API
@claxedo/workspace-runtime is the per-workspace host service — one process per
workspace. It owns the routes below plus the PTY and process managers, the
LSP/VCS surface, and the relay-host tunnel to workspace-relay.
Routes
Section titled “Routes”| Route | Source | Auth |
|---|---|---|
GET /api/wr/health | src/server.ts | none; minimal liveness and exposure boundary metadata only |
GET /api/wr/capabilities | src/server.ts | exposure-dependent runtime auth |
POST /api/wr/config | src/routes/config.ts | workspace-runtime management auth |
GET /api/wr/harness-config-options | src/workspace/runtime.ts | exposure-dependent runtime auth |
GET /api/wr/events, GET /api/wr/runtime-events | src/routes/runtime-events.ts, src/routes/events.ts | exposure-dependent runtime auth |
* /api/wr/file/*, GET /api/wr/find/file | src/routes/file.ts | exposure-dependent runtime auth |
* /api/wr/diff/*, * /api/wr/git/* | src/routes/diff.ts, src/routes/git-source.ts | exposure-dependent runtime auth |
* /api/wr/pty/* | src/routes/pty.ts | exposure-dependent runtime auth |
* /api/wr/process/* | src/routes/process.ts | exposure-dependent runtime auth |
* /api/wr/hook/* | src/routes/agent-hook.ts | exposure-dependent runtime auth |
* /api/wr/session-env/* | session-env routes (mounted by the host) | exposure-dependent runtime auth |
* /session/* | SessionRoutes (mounted by the host in src/workspace/runtime.ts — not part of mountWorkspaceCore, which mounts only PTY, hooks, events, process, file, and diff/git routes) | implicit (host-level) |
* /mcp/* | MCP routes | implicit |
* /lsp, * /vcs, * /global/event | compatibility routes mounted by host | implicit |
Event contract
Section titled “Event contract”workspace-runtime has two event systems with different jobs.
| Surface | Transport | Event family | Contract |
|---|---|---|---|
GET /global/event | SSE | OpenCode-compatible CompatEnvelope values | Primary client stream for session/message/permission/question lifecycle. In OpenCode compatibility mode this may proxy the upstream OpenCode /global/event; otherwise it fans out the host RuntimeEventHub global stream and emits server.connected plus heartbeat frames. |
GET /api/wr/runtime-events | SSE | RuntimeEventEnvelope values wrapping raw AgentRuntimeEvent payloads | Primary runtime-event stream for host-mounted core clients that need adapter-native runtime events. Mounted by mountWorkspaceCore(). |
GET /api/wr/events | SSE | WorkspaceRuntimeEvent values from workspaceRuntimeBus | Neutral runtime path for the compatibility/internal process-global stream. |
GET /event | SSE | WorkspaceRuntimeEvent values from workspaceRuntimeBus | Compatibility/internal process-global stream for PTY lifecycle, PTY stream summaries, process status/config events, agent lifecycle, session lifecycle, and heartbeats. Not the primary session/message event stream. |
GET /api/wr/pty/:ptyID/connect | WebSocket | PTY bytes plus cursor metadata | Supported PTY data stream. PTY lifecycle summaries also appear on /event, but terminal bytes are delivered over this WebSocket. |
GET /api/wr/process/logs | HTTP snapshot | Text log tail | Process output is poll/snapshot based through PTY log snapshots. There is no separate supported process-output event stream. Process status summaries appear on /event. |
RuntimeEventHub is the primary hub for session/runtime events. Session routes
publish OpenCode-compatible events to its global channel and bridge only
terminal lifecycle states into workspaceRuntimeBus as agent.lifecycle
compatibility events (mapping busy session.status to Busy,
permission/question asks to UserActionRequired, session.idle to Idle, and
session.error to Error).
Deployment shapes
Section titled “Deployment shapes”workspace-runtime supports five deployment shapes. They share the same
per-workspace route surface but not the same trust boundary.
| Shape | How it is created | Listen/auth expectation |
|---|---|---|
| Local loopback / trusted local | startServer(port, { exposure: loopbackWorkspaceRuntimeExposure() }) with the default host or WORKSPACE_RUNTIME_HOST=127.0.0.1 / localhost | May run without host-level auth because the socket is loopback-only. For local development and app-owned desktop flows. |
| Private VM runtime | startServer(port, { exposure: privateNetworkWorkspaceRuntimeExposure(...), target }) inside an operator-controlled VM, usually with WORKSPACE_RUNTIME_HOST=0.0.0.0 or a private interface | Must configure relay-host auth or a private-network exposure with both a host guard and runtime auth. The dev-unsafe opt-out is only for self-managed deployments where surrounding controls are intentionally the auth boundary. |
| Relay-attached runtime | startServer() with workspaceRelayRuntimeOptionsFromEnv() / relayHostAuthFromEnv() and hostTunnelFromEnv() | Verifies Relay Host Tokens (RHTs) from workspace-relay, requires the relay transit marker on relay-issued tokens, and may maintain an outbound host tunnel to the relay. |
| Embedded Hono app | createWorkspaceRuntimeApp() mounted inside another trusted process | The embedding process owns the outer network/auth boundary. Pass relayHostAuth, configToken, and a WorkspaceTarget explicitly when exposing runtime routes outside loopback. |
| Low-level host object | createWorkspaceHost() / mountWorkspaceCore() used without startServer() | No socket is created. The caller owns routing, auth, lifecycle, and disposal; use only behind an existing trusted API surface. |
Runtime CORS follows the exposure declaration. The kit default allows only
loopback origins (http://localhost:*, http://127.0.0.1:*) on loopback
exposure — it contains no product domains. Hosts inject product origins (a
hosted app origin, for example) via the corsOrigin option on
WorkspaceRuntimeServerOptions. Relay, private-network, and embedded exposure
do not emit runtime CORS headers by default — the relay, trusted ingress, or
embedding server owns origin policy.
Relay-attached configuration
Section titled “Relay-attached configuration”Local development needs none of these — a loopback runtime boots with zero
environment variables. These knobs exist for the relay-attached production
shape. A relay-attached runtime has two independent pieces: inbound RHT
verification (relayHostAuthFromEnv()) and outbound host-tunnel
attachment (hostTunnelFromEnv()).
Inbound RHT verification
Section titled “Inbound RHT verification”| Env var | Purpose |
|---|---|
WORKSPACE_RUNTIME_RELAY_JWKS_URL | Preferred RHT verification source. Verifies relay-minted RHTs and supports signing-key rotation. |
WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM | Static PEM fallback for RHT verification when JWKS discovery is unavailable. |
WORKSPACE_RUNTIME_WORKSPACE_ID | Workspace id the runtime hosts; RHT claims and x-workspace-id must match it. |
WORKSPACE_RUNTIME_HOST_ID | Host id expected in the RHT. Defaults to workspaceId() when omitted. |
WORKSPACE_RUNTIME_TRUSTED_DIRECT_TOKEN | Direct host token accepted by the relay-host middleware without an RHT. Whole-host authority — keep it off untrusted clients. |
Relay-issued RHT requests must include x-forwarded-by: workspace-relay.
workspace-relay sets that marker after stripping client-supplied
x-forwarded-* headers. Direct trusted-token calls bypass the marker.
Outbound host tunnel
Section titled “Outbound host tunnel”| Env var | Purpose |
|---|---|
WORKSPACE_RUNTIME_RELAY_URL | Relay base URL. If unset, the runtime does not open a host tunnel. |
WORKSPACE_RUNTIME_RELAY_TUNNEL_TOKEN | Bearer token for the runtime-to-relay tunnel connection. |
WORKSPACE_RUNTIME_RELAY_WORKSPACE_IDS | Comma-separated workspace ids advertised by this host tunnel. Defaults to the current workspace id. |
WORKSPACE_RUNTIME_LOCAL_BASE_URL | Local runtime URL forwarded by the tunnel. Defaults to http://127.0.0.1:<port>. |
Additional tuning knobs (tunnel ping cadence, a full Authorization-header
override for the tunnel token, drain timeout, body-size limits, PTY
backpressure watermarks) exist with sensible defaults — see the package source
if you need them.
Standalone listen policy
Section titled “Standalone listen policy”startServer() requires an explicit exposure declaration and defaults to
127.0.0.1. A runtime may listen on a non-loopback host (0.0.0.0, ::, or a
LAN address) only when relay-host auth is configured, a private-network exposure
supplies both host guard and runtime auth, or the operator explicitly sets
WORKSPACE_RUNTIME_ALLOW_UNAUTHENTICATED_NON_LOOPBACK=1.
Target and path containment
Section titled “Target and path containment”Session, session-env, file, PTY, process, OpenCode-compat, and diff/VCS routes
are pinned to WORKSPACE_RUNTIME_DIRECTORY or the WorkspaceTarget passed to
createWorkspaceRuntimeApp(). Callers may omit directory (use the pinned
workspace), pass that exact directory, or pass the synthetic
workspace:<workspaceId> target. Any other directory is rejected. Absolute
paths, .. escapes, null bytes, and symlinks resolving outside the workspace
are rejected before any read, write, stream, or subprocess start.
For install instructions, embedding patterns, and architecture, see the workspace-runtime package page.