KB LabsDocs

Overview

Last updated April 7, 2026


Why the gateway exists and what it does.

The gateway is the central front door of a KB Labs deployment. Every client — the CLI, Studio, external tools, plugin-hosted code running in a container, the host agent — talks to the gateway, and the gateway routes traffic to the right upstream service (REST API, workflow daemon, marketplace).

Without it, every client would need to know the URLs of every service, handle auth against each of them separately, and deal with CORS across multiple origins. The gateway flattens all of that into a single endpoint.

Source lives in infra/kb-labs-gateway/.

What it does

  • Single endpoint. Clients connect to the gateway on one URL; the gateway figures out which backend should handle each request.
  • Auth termination. Bearer tokens are verified at the gateway; upstream services don't need their own token validation.
  • WebSocket tunneling. The host agent connects to the gateway over WSS; the gateway tracks which agents are connected and dispatches capability calls to them.
  • CORS handling. Studio is a browser SPA hosted separately from the API services — the gateway sets CORS headers so browsers can talk to it.
  • Static tokens. Development and service-to-service tokens are seeded into the gateway's token store at bootstrap, so internal processes can authenticate without going through the full OAuth flow.
  • Observability. Every request flows through a single place, which makes structured logging, metrics, and tracing much easier to set up.

What it is not

  • Not a load balancer. There's no replica set, no round-robin, no weighted routing. One gateway instance in front of one of each upstream service is the shape. You can put a load balancer in front of multiple gateway instances, but the gateway itself doesn't do that work.
  • Not a service mesh. It doesn't inject sidecars, doesn't do mTLS between services, doesn't rewrite internal RPC calls.
  • Not a plugin execution backend. The gateway routes requests — it doesn't run plugin code. Plugin execution happens in the REST API, workflow daemon, or container backends.

Service manifest

TypeScript
{
  schema: 'kb.service/1',
  id: 'gateway',
  name: 'Gateway',
  version: '1.0.0',
  description: 'Central router — aggregates REST API, Workflow, Marketplace',
  runtime: {
    entry: 'dist/index.js',
    port: 4000,
    healthCheck: '/health',
  },
  dependsOn: ['rest', 'workflow'],
  env: {
    PORT: { description: 'HTTP port', default: '4000' },
    NODE_ENV: { description: 'Environment mode', default: 'development' },
  },
}

dependsOn: ['rest', 'workflow'] tells kb-dev to boot the REST API and workflow daemon before the gateway starts — the gateway will happily start without them, but requests will get 502s until they're up.

Source: infra/kb-labs-gateway/apps/gateway-app/src/manifest.ts.

Configuration

The gateway reads its config from the gateway section of .kb/kb.config.json. The schema is enforced by GatewayConfigSchema:

TypeScript
interface GatewayConfig {
  port: number;                                         // default 4000
  upstreams: Record<string, UpstreamConfig>;            // default {}
  staticTokens: Record<string, StaticTokenEntry>;       // default {}
}
 
interface UpstreamConfig {
  url: string;                      // full URL of the upstream
  prefix: string;                   // path prefix on the gateway
  rewritePrefix?: string;           // if set, rewrite prefix before forwarding
  websocket?: boolean;              // default false
  excludePaths?: string[];          // paths NOT to proxy (handled by gateway itself)
  description?: string;
}
 
interface StaticTokenEntry {
  hostId: string;
  namespaceId: string;
}

Both upstreams and staticTokens default to {} — you can run a gateway with an empty config, you just won't be able to do much with it.

Example from the reference config:

JSON
{
  "gateway": {
    "port": 4000,
    "upstreams": {
      "rest": {
        "url": "http://localhost:5050",
        "prefix": "/api/v1",
        "websocket": true,
        "description": "REST API — main platform BFF"
      },
      "workflow": {
        "url": "http://localhost:7778",
        "prefix": "/api/exec",
        "rewritePrefix": "",
        "description": "Workflow Daemon — direct access"
      },
      "marketplace": {
        "url": "http://localhost:5070",
        "prefix": "/api/v1/marketplace",
        "description": "Marketplace Service — unified entity management"
      }
    },
    "staticTokens": {
      "dev-studio-token": {
        "hostId": "studio",
        "namespaceId": "default"
      }
    }
  }
}

The upstreams it aggregates

Out of the box, the gateway is wired up with three HTTP upstreams plus one WebSocket tunnel:

UpstreamDefault prefixForwards toNotes
rest/api/v1REST API :5050The main platform API. Plugin routes mount under this. Also accepts WebSocket upgrades.
workflow/api/execWorkflow daemon :7778With rewritePrefix: "", /api/exec/jobs/* becomes /jobs/* on the daemon.
marketplace/api/v1/marketplaceMarketplace :5070Entity install/list/update.
ws (built-in)/wsHost agent WebSocket endpoint, handled by the gateway itself.

You can add more upstreams by editing gateway.upstreams in kb.config.json — the gateway re-reads config at startup.

Routing rules

When a request arrives:

  1. Match prefix. The gateway checks each configured upstream in order, picking the first whose prefix matches the request path.
  2. Check excludePaths. If the matched path is in the upstream's excludePaths, the gateway handles it internally instead of proxying.
  3. Apply rewritePrefix. If rewritePrefix is set, the gateway replaces the matched prefix with rewritePrefix before forwarding. Set it to "" to strip the prefix entirely.
  4. Forward the request to the upstream URL with any remaining path components appended.

If no upstream matches, the gateway returns 404.

Auth at the gateway

Every proxied request passes through an auth middleware that validates a Bearer token in the Authorization header. The token can be:

  • A dev / service static token — seeded from gateway.staticTokens in the config and stored in the cache.
  • A runtime-issued token — minted by the gateway's own /auth/token endpoint (exchanging client credentials from a registered host).
  • A host agent JWT — the WSS connection itself carries a JWT for agent authentication.

If no valid token is present, the gateway returns 401. Upstream services themselves generally trust that the gateway has already validated the caller, so they can focus on business logic instead of re-implementing auth.

See Authentication for the full flow.

Host registry

One of the gateway's less-obvious responsibilities is tracking which host agents are currently connected. The HostRegistry keeps a cache of active hosts, backed by a SQLite store (@kb-labs/gateway-core / SqliteHostStore) when the platform has a SQL adapter configured — otherwise it's cache-only and hosts are lost on restart.

When a host agent connects via WSS, the gateway registers it in this registry. When the workspace-agent adapter needs to dispatch a capability call, it asks the registry for a connected host in the matching namespace and tunnels the call through the WebSocket.

See Routing for the dispatch details.

Observability

The gateway uses @kb-labs/shared-http's correlated logger with serviceId: 'gateway', so every log line is tagged and correlatable with downstream services. It also emits diagnostic events for registry restore failures, auth failures, and upstream connection errors.

Metrics are exposed at /metrics in Prometheus text format.

Running the gateway

Bash
kb-dev start gateway

kb-dev respects dependsOn and boots rest and workflow first. If you want to start the gateway by itself (for debugging a config change), pass --no-deps.

Directly:

Bash
cd infra/kb-labs-gateway/apps/gateway-app
node ./dist/index.js

Required env

  • GATEWAY_JWT_SECRET — required in production. The secret used to sign and verify JWT tokens. If unset, the gateway logs a warning and falls back to an insecure default (dev only).
  • PORT — optional override of the default 4000.
  • NODE_ENV'production' disables Swagger UI and verbose logging.
Overview — KB Labs Docs