Control plane
The operator-facing configuration surface.
The control plane is the configuration surface of Koda. It owns everything an operator tweaks — providers, agents, secrets, access policy, integrations — and exposes those concerns through a single authenticated HTTP API at port 8090. The Next.js dashboard at port 3000 is a thin consumer of that API.
What the control plane owns
Think of the control plane as the only layer that writes to product configuration. The runtime reads what the control plane publishes — it never reaches back in to modify agents or providers.
- Providers — connection records, verified credentials, default models, health checks.
- Agents — definitions (name, model defaults, prompt contracts), compiled prompts, publication state, per-agent secrets.
- Secrets — encrypted values bound to agents or to the platform itself, retrieved only inside the runtime process at execution time.
- Access policy — allowed operators, Telegram users, session handling.
- Integrations — connections catalogue, default wiring, per-agent grants, integration health.
- Onboarding — first-owner bootstrap, system settings, initial provider and agent wizards.
API organisation
Every control-plane route sits under /api/control-plane/*. The authoritative contract is docs/openapi/control-plane.json; the groups below cover what operators and custom integrations interact with most.
Authentication & sessions
POST /auth/register-owner— first-run owner creation.POST /auth/login/POST /auth/logout— operator session lifecycle.GET /auth/status— current session + operator identity.GET /auth/sessions,DELETE /auth/sessions/:id— list and revoke active sessions.POST /auth/tokens,DELETE /auth/tokens/:id— manage long-lived API tokens.POST /auth/bootstrap/exchange— redeem a short-lived setup code during first boot.
Agents
GET /agents,POST /agents— list and create agents.GET /agents/:agent_id,PATCH /agents/:agent_id— read and update definition.POST /agents/:agent_id/publish— move an agent from draft to published (runnable).POST /agents/:agent_id/activate— toggle operational state without republishing.PUT /agents/:agent_id/secrets/:secret_key— write a secret scoped to this agent only.
Providers & integrations
GET /connections/catalog— every supported integration and provider.PUT /providers/:provider_id/connection/api-key— wire an API-key-based provider.POST /providers/:provider_id/connection/verify— verify the saved credential before accepting it.PUT /connections/defaults/:connection_key— system-default connection for an integration.GET /integrations/:integration_id/health— recent health snapshots used by the dashboard.
System & onboarding
GET /system-settings,PATCH /system-settings— global toggles (allowed users, policy defaults).GET /onboarding/status— where the first-run flow is.POST /onboarding/bootstrap— advance the bootstrap sequence.
Authentication model
The control plane is single-user authenticated — one owner account, created on first boot. Password policy, session handling, and rate limiting are enforced before any business logic runs.
Password policy
- Minimum 12 characters.
- Three of four character classes (upper, lower, digit, symbol).
- Cannot contain the username or email.
- Not in the top-500 most common passwords list.
- Minimum Shannon entropy of 2.0 bits per character.
Sessions
- 32-byte URL-safe random tokens, hashed SHA-256 before storage.
- Default TTL of 7 days (override with
CONTROL_PLANE_OPERATOR_SESSION_TTL_SECONDS). - Transport cookie
koda_operator_session, encrypted with AES-256-GCM, flaggedHttpOnly; Secure; SameSite=Strict. - Password change revokes every other session but keeps the initiating one; password reset via a recovery code revokes all sessions for the user.
Recovery codes
- Ten single-use codes, shown once on registration. Format:
xxxx-xxxx-xxxxwith an unambiguous alphabet (noI,O,0,1). - Hashed with Argon2 before storage.
- Once any code is used, all remaining codes are invalidated — mirroring GitHub's and Google's policies.
Rate limits
Per-IP buckets back every auth-adjacent route. Responses are deliberately slowed to a ~300 ms floor to reduce timing-based enumeration.
POST /auth/login: 5 requests / 5 minutes.POST /auth/password/recover: 5 requests / hour.POST /auth/password/change: 10 requests / hour.POST /auth/register-owner: 3 requests / hour.- General operator bucket: 120 requests / minute.
- Account lockout: after
CONTROL_PLANE_OPERATOR_LOGIN_MAX_FAILURES, the account locks forCONTROL_PLANE_OPERATOR_LOGIN_LOCKOUT_SECONDS.
ALLOW_LOOPBACK_BOOTSTRAP=true, CONTROL_PLANE_AUTH_MODE=development|open, and ALLOW_INSECURE_COOKIES all exist to make local development friction-free, and they're all refused at boot when KODA_ENV=production.
Break-glass token
CONTROL_PLANE_API_TOKEN is an optional, long-lived token that lets CLI tooling talk to the control plane without an operator session. By default it is empty: you only set it when you need machine-to-machine access and you rotate it deliberately.
curl -H "Authorization: Bearer $CONTROL_PLANE_API_TOKEN" \ http://127.0.0.1:8090/api/control-plane/system-settingsAudit trail
Every auth event writes a structured security.* record through the emit_security() helper. Logins, failed attempts, password changes, recovery-code usage, and session revocations all end up in the audit table and are inspectable from the dashboard's Operations view.
Relationship to the dashboard
apps/web is a Next.js server rendering a consumer of the control-plane API. It seals the operator session token via lib/web-operator-session.ts, enforces security headers and CSP through middleware.ts, and renders the same data the OpenAPI contract describes. There is no private dashboard API — every surface is backed by a route documented in docs/openapi/control-plane.json.
Go deeper
- Runtime — what the control plane hands off to, and how tasks actually execute.
- Security — the end-to-end hardening story including CSP, cookie flags, and audit.
- Control-plane API reference — every route, request, and response from the OpenAPI spec.