kortecxdocs

Running the Runtime

What kx serve opens — the gRPC endpoint, the event stream, and the optional web console — and the auth posture it refuses to start without.

kx serve starts the agentic runtime as a server on your machine. It is the thing your apps, your agents, the CLI, and the web console all talk to. One command starts all of it.

Auth is deny-all by default

The runtime answers nobody until you tell it who to answer. A bare kx serve does not start an open server — it stops before binding anything and exits with status 2, because starting an unauthenticated server would be the wrong default.

Start it with no auth flags at all and it prints the usage text and this line on standard error:

kx: kx serve refuses unauthenticated access. Re-run with --dev-allow-local (loopback-only dev access), or --auth-token <token>=<party> / --auth-token-file <path> for token auth.

You have two ways to open it. Pick exactly one — the loopback dev posture and configured tokens are mutually exclusive, and passing both is a config error.

# Local development: accept callers from this machine only.
kx serve --dev-allow-local

# Token auth: a bearer token maps to a named party.
kx serve --auth-token-file ~/.kortecx/tokens.txt
# You start the server from a terminal; code connects to it.
from kortecx import KxClient

with KxClient("http://127.0.0.1:50151") as kx:
    result = kx.invoke("kx/recipes/echo", {"topic": "hello"}, wait=True)
    print(result.state)
    # The demo recipe returns binary bytes, so result.text is None here.
    print((result.bytes or b"")[:32])
// You start the server from a terminal; code connects to it.
import { KxClient, type Result } from "@kortecx/sdk";

const kx = new KxClient("http://127.0.0.1:50151");
const result = (await kx.invoke(
  "kx/recipes/echo",
  { topic: "hello" },
  { wait: true },
)) as Result;
console.log(result.state, result.instanceId);
kx.close();

What --dev-allow-local actually means

--dev-allow-local accepts any caller that reaches the server over loopback — the local machine. If you pass it, both --listen and --ws-listen must be loopback addresses or the server refuses to start. It is for development on your own computer. For anything a second machine can reach, use a token.

What it opens

One kx serve opens up to three surfaces. Every resolved path and endpoint is written to a startup log line on standard error (tracing is on at info by default), so you can read back exactly what resolved where.

SurfaceDefault addressWhat it is
gRPC (and gRPC-web)127.0.0.1:50151the main endpoint — the CLI and the SDKs talk here
Event streamws://127.0.0.1:50152a live WebSocket feed of what runs are doing
Web consolehttp://127.0.0.1:8888the browser interface, in builds that include it

The event stream uses the same auth posture as the gRPC endpoint: an upgrade that carries no accepted token is rejected before any stream opens.

The console is a build-time option

The web console is a non-default cargo feature (console). The prebuilt release binary is built with it and carries the interface inside the binary — no separate install, no Node toolchain. A plain cargo install build has no console: it serves nothing at 127.0.0.1:8888, and passing --console-listen on such a build is a loud config error telling you to use the prebuilt binary or build with --features console.

Where the console is present it binds loopback only — a non-loopback --console-listen is refused. Open http://127.0.0.1:8888 in a browser on the same machine and point it at http://127.0.0.1:50151. If you use a bearer token, it is held only in the page's memory and is never written to disk.

Turn the console off with --no-console, or move it to another loopback port with --console-listen <addr:port>.

Starting it, end to end

Start the server

kx serve --dev-allow-local

Leave it running. The journal, content store, and catalog resolve automatically under ~/.kortecx and are reused across restarts, so your history survives a stop and start.

Check it is answering

In a second terminal:

kx health
kx info

Neither takes a required flag of its own — only the shared client flags below. kx health is a liveness check against the gRPC endpoint and is not behind the auth interceptor, so it needs no token. kx info prints the non-secret server configuration and does need an accepted caller: on a token server, pass --token-file.

Run something

kx invoke kx/recipes/echo --args '{"topic":"durable agents"}' --wait

This runs a single-step blueprint and waits for the committed result.

Watch it live

kx events --instance <instance-id> --follow

The instance id is the 32-character hex value kx invoke prints. Or open the web console and watch the same events there.

Every flag

These are the flags kx serve accepts.

Auth posture — one of these is required

FlagDefaultMeaning
--dev-allow-localoffaccept loopback callers; requires loopback --listen and --ws-listen
--auth-token <token>=<party>accept a bearer token as a named party (repeatable)
--auth-token-file <path>one token=party per line, # for comments

--dev-allow-local and the token flags cannot be combined. Prefer --auth-token-file: a token passed on the command line is visible to anyone who can list processes on the machine.

Addresses

FlagDefaultMeaning
--listen <addr:port>127.0.0.1:50151the gRPC and gRPC-web endpoint
--ws-listen <addr:port>127.0.0.1:50152the live-event WebSocket bridge
--console-listen <addr:port>127.0.0.1:8888the web console; loopback addresses only
--no-consoledo not serve the web console

Browsers and transport

FlagDefaultMeaning
--cors-origin <origin>deny allallow one browser origin on the gRPC-web shim (repeatable)
--tls-cert <pem> / --tls-key <pem>plaintextin-binary TLS for the gRPC listener; give both or neither

--cors-origin takes a full origin — https://dashboard.example.com, scheme and host and port. It is repeatable and it never accepts a wildcard. When the console is running, the runtime adds the console's own bound loopback origins (http://127.0.0.1:<port> and http://localhost:<port>, at whatever port it actually bound) to the allowlist automatically, so you do not need a --cors-origin for it — including when you moved it with --console-listen.

TLS covers the gRPC listener. The WebSocket bridge and the web console are plaintext — if a remote browser needs the event stream, put a TLS proxy in front.

Storage

FlagDefaultMeaning
--journal <path>under ~/.kortecxthe durable journal
--content <dir>under ~/.kortecxthe content store
--catalog-dir <dir>beside the journalcatalog and sidecars (blueprints, teams, telemetry, capture)
--content-max-bytes <N>32 MiBthe PutContent payload cap, enforced fail-closed

All or nothing

The zero-config layout fires only when you pass none of --journal, --content, or --catalog-dir. Pass any one of them and you own the whole layout. To move everything at once, set KX_DATA_DIR instead.

Work and observation

FlagDefaultMeaning
--workers <N>1embedded worker pool size; above 1, Pure, IO and tool steps run concurrently
--max-lease <N>16how many steps a worker leases at a time
--metrics-listen <addr:port>offan opt-in Prometheus /metrics endpoint (RED metrics — rate, errors, duration)
--webhook-listen <addr:port>offan opt-in inbound webhook surface for event triggers, with per-trigger HMAC or bearer auth
--audit-log <path>offa best-effort JSONL record of the run lifecycle

--workers must be a positive integer and is clamped to a built-in maximum. Start at 1 and raise it if your runs have several independent Pure, IO or tool steps ready at the same time.

The audit log is written off the truth path. It records join keys only — never payload bytes, never model output, never secrets — and turning it on never changes a run's committed facts or its product digest.

Environment variables

VariableApplies toMeaning
KX_DATA_DIRkx servebase directory for the zero-config data layout (default ~/.kortecx)
KX_WORKERS / KX_SERVE_WORKER_POOLkx serveworker pool size when --workers is absent; the flag always wins
KX_SERVE_MEMORYkx serveenable the durable memory RPCs and the kx memory surface
KX_DATA_DIR=/data/kortecx kx serve --dev-allow-local

KX_SERVE_MEMORY is the older spelling of the flag and still works; KX_FLAG_SERVE_MEMORY is the current name and wins if both are set.

Durable memory is gated. It needs KX_SERVE_MEMORY=1, a served model, and an hnsw build — see Memory. Datasets require an hnsw build too.

A server two machines can reach

If a second machine or a browser on another host needs to reach the runtime, three things change together.

Use tokens, not --dev-allow-local

Write one token=party per line. Blank lines and lines starting with # are ignored, and any other line that is not token=party is a config error:

# ~/.kortecx/tokens.txt
a-long-random-string=alice
another-long-random-string=ci-runner

Bind a reachable address and turn on TLS

kx serve \
  --auth-token-file ~/.kortecx/tokens.txt \
  --listen 0.0.0.0:50151 \
  --tls-cert /etc/kortecx/cert.pem \
  --tls-key /etc/kortecx/key.pem

Only the gRPC listener moves here. The event stream stays on its own loopback default unless you also pass --ws-listen, and the console cannot be moved off loopback at all.

Name the browser origins you allow

kx serve \
  --auth-token-file ~/.kortecx/tokens.txt \
  --listen 0.0.0.0:50151 \
  --tls-cert /etc/kortecx/cert.pem \
  --tls-key /etc/kortecx/key.pem \
  --cors-origin https://dashboard.example.com

Each origin you want is its own --cors-origin. There is no wildcard.

Read this before you expose a port

Bearer tokens the runtime holds are compared with an ordinary map lookup, not a constant-time one, so a timing side-channel is possible in principle. This is a stated limitation of the single-system local gateway. A token in a browser page is visible to that page's JavaScript. See Production for what this does and does not cover.

Clients

Every client command — kx invoke, kx events, kx projection, and the rest — takes the same connection flags:

FlagDefaultMeaning
--endpoint <url>http://127.0.0.1:50151which server to talk to
--token <t> / --token-file <p>the bearer token; prefer the file
--tls-ca <pem>the CA to trust for an https:// endpoint
--jsonoffmachine-readable output
kx invoke kx/recipes/echo --args '{"topic":"hello"}' --wait \
  --endpoint https://runtime.example.com:50151 --token-file ~/.kortecx/my-token

What this is not

The runtime is single-system by default. One kx serve is one machine: its journal, its content store, its workers. Running across several nodes is on the roadmap, not in the box today. The server runs for exactly as long as you leave it running — it does not stop or start itself, and it does not add or remove capacity on its own.