Run Klu in production

The checked-in Klu application has three runtime processes: the Next.js web application, the Fastify data API, and a Graphile Worker process. PostgreSQL stores application records and retrieval Documents, Redis supplies caches and streaming state, and external services provide object storage, authentication, notifications, billing, collaboration, model routing, and embeddings.

This guide describes repository behavior and configuration. It does not confirm the topology, secrets, capacity, or release state of any live Klu deployment.


Runtime boundaries

ComponentCommandDefault local addressResponsibility
Webnpm run devhttp://localhost:3000Workspace UI, NextAuth, and Next.js API routes.
Data APInpm run dev:apihttp://localhost:3001Authenticated /v1 API, streaming routes, file proxy, and optional OpenAPI UI.
Workernpm run dev:workerNo HTTP listenerGraphile jobs, scheduled tasks, Context indexing, async Actions, evals, imports, exports, and fine-tune synchronization.

Run all three for full product behavior. The checked-in Procfile.dev also starts tier -v serve for local billing and metering workflows.

Prepare a local operator environment

The root package requires Node.js 20.10.0 or later and pins Node 20.10.0 with Volta. PostgreSQL must support pgvector.

From the application repository:

npm install
cp .env.example .env

Configure .env before starting a process. The web environment schema rejects missing, empty, or invalid required values at startup. SKIP_ENV_VALIDATION bypasses that guard; reserve it for tightly controlled diagnostics because later code can still fail when it reads an absent value.

Configure the data stores

Klu uses two Prisma schemas:

  • DATABASE_URL points to the main application database for users, workspaces, Apps, Actions, data, job metadata, and configuration.
  • DOCUMENTS_DATABASE_URL points to the documents database for Context chunks and vector embeddings.

The URLs can point to separate databases or to one PostgreSQL database when both schemas can coexist. Enable vector for the documents schema. The main Prisma schema also declares vector and uuid-ossp.

GRAPHILE_DATABASE_URL is the Graphile Worker queue connection. It can use the main database, as shown in .env.example, or another database provisioned for the queue. The web/API job producer and worker must use the same queue database.

Apply main migrations with the checked-in Prisma schema:

npx prisma migrate deploy --schema prisma/schema.prisma

Apply every prisma/docs_migrations/*/migration.sql file to DOCUMENTS_DATABASE_URL in filename order. The checked-in npm run migrate:docs wrapper is interactive and its control flow does not provide a reliable unattended deployment path. Use an operator-reviewed migration step that stops on the first psql error.

Configure required services

The authoritative required-key list is src/env.mjs. The primary groups are:

  • Authentication and mail: NEXTAUTH_SECRET, NEXTAUTH_URL, Google and GitHub client credentials, and EMAIL_SERVER_* plus EMAIL_FROM.
  • Storage and caching: AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_S3_BUCKET_NAME, AWS_S3_REGION, REDIS_URL, and ENGINE_CACHE_PREFIX.
  • API and model execution: NEXT_PUBLIC_API_URL, GATEWAY_URL, KLU_API_KEY, KLU_USER_ID, Azure gateway values, OPENAI_API_KEY, and paired KLU_EMBEDDING_API_BASE_0_9 and KLU_EMBEDDING_API_KEY_0_9 values.
  • Jobs: GRAPHILE_DATABASE_URL and GRAPHILE_CONCURRENT_JOBS.
  • Product integrations: Nango, Novu, Liveblocks, Tier, Segment, and Sentry values declared in the schema.

.env.example is useful as a starting point, though it does not enumerate every required embedding slot from the current schema. Compare the finished file with src/env.mjs before deployment.

Start the services

Use separate terminals during local development:

npm run dev
npm run dev:api
npm run dev:worker

The production entry points are npm run start:app, npm run start:api, and npm run start:worker. Build the web application before start:app. The API and worker execute TypeScript through their workspace start scripts and load the root .env file.

The API listens on PORT or port 3001 when PORT is absent. It binds to 0.0.0.0. The web application uses Next.js defaults unless its environment overrides them.

Check health and readiness

Both HTTP services expose /api/healthcheck:

curl --fail --silent --show-error http://localhost:3000/api/healthcheck
curl --fail --silent --show-error http://localhost:3001/api/healthcheck

An HTTP 200 proves that the corresponding process can serve that route. The handlers do not query PostgreSQL, Redis, the queue, object storage, the gateway, or the embedding service. Add dependency-aware probes in your deployment platform when you need deeper readiness evidence.

In development, the data API serves generated OpenAPI documentation at http://localhost:3001/docs. Production enables it only when API_ENABLE_SWAGGER_DOCS or API_ENABLE_SWAGGER has a true-like value.

Verify the worker through logs and an end-to-end job. A practical smoke test is to index a small Context source and confirm Ready for Search, or run an asynchronous Action and confirm its data record reaches a terminal result.

Operate asynchronous jobs

The web and API enqueue work through Graphile Worker. The worker polls every second and runs one job concurrently unless GRAPHILE_CONCURRENT_JOBS is set. Increase concurrency only after checking database connections, provider rate limits, memory, and job-specific fan-out.

Repository job creation defaults to maxAttempts: 1. Several cron tasks also set one attempt explicitly. A transient provider or database error therefore commonly requires a new job or a product-level retry. Do not assume queue-level automatic retries.

Batch jobs begin as running, become completed when their performed count reaches the target count, and become failed when the worker reports a job error. Batch metadata records error text, job ID, and task name. Context source processing has an additional source status; use that source status because the processor catches indexing errors after recording FAILED.

The worker's checked-in crontab uses UTC and schedules fine-tune synchronization, dataset token-count synchronization, OpenAI file synchronization, and curator runs. Review worker/src/crontab before changing deployment replicas: Graphile's job locking coordinates workers, while upstream API quotas and job cost still constrain safe parallelism.

Configure API boundaries

The Fastify API defaults to a 40 MiB request-body limit. Override it with the first valid positive value among:

  • API_BODY_LIMIT_BYTES or API_MAX_BODY_SIZE_BYTES
  • API_BODY_LIMIT_MB or API_MAX_BODY_SIZE_MB

Byte settings take precedence over megabyte settings. Invalid or non-positive values fall back to 40 MiB.

Set API_CORS_ALLOWED_ORIGINS or CORS_ALLOWED_ORIGINS to a comma-separated list of HTTP or HTTPS origins. The API fails startup when the variable is present and contains no valid origin. When neither variable is set, the checked-in API uses Fastify CORS with origin: true, so production operators should set an explicit allowlist.

Request logs include a generated request ID, method, path without query parameters, status code, and response time. The API and worker redact common bearer tokens, API keys, signed query values, and credential-like URL parameters in their explicit error summaries. This is targeted redaction rather than a general guarantee that every application log is free of sensitive data. Avoid logging raw request bodies and audit new log fields.

Understand the checked-in deployment topology

flightcontrol.json defines production and staging environments in us-east-1. Each environment deploys the web, API, and worker separately and attaches health checks to the two HTTP services. Production uses main; staging uses staging.

Watch paths establish independent release boundaries:

  • Web watches the repository while excluding api/**/* and worker/**/*.
  • Production API watches api/**/*, src/server/**/*, and src/env.mjs.
  • Production worker watches worker/**/*, src/server/**/*, and src/env.mjs.

A shared-server change can therefore release API and worker services independently from the web service. Coordinate migrations and backward-compatible contracts across those rollout windows.

The checked-in Flightcontrol database service assigns its connection string to GRAPHILE_DATABASE_URL. DATABASE_URL and DOCUMENTS_DATABASE_URL are supplied outside the visible service definition. Confirm their actual bindings in the deployment control plane. Repository configuration expresses deployment intent and does not prove the current live environment matches it.

Incident troubleshooting

Web starts and API calls fail

Check NEXT_PUBLIC_API_URL, then call both health endpoints. Confirm the API PORT, CORS allowlist, and API service logs. A green web health check gives no evidence about API health.

API or web exits during startup

Look for Invalid environment variables and the flattened field list. Compare .env or deployment secrets with src/env.mjs, including all embedding base/key slots. Also verify URL-typed fields contain complete URLs.

Jobs remain queued

Confirm the worker is running, then compare GRAPHILE_DATABASE_URL across producers and workers. Look for Worker runner connected to database. Check queue connectivity and worker logs before enqueueing duplicates.

Jobs fail once and stop

This matches the default one-attempt policy. Correct the underlying provider, database, payload, or configuration error, then trigger the product operation again. Use the recorded job ID and task identifier to correlate logs.

Context shows completed batch work with failed sources

Inspect each source status and the process-context-source logs. The source processor records FAILED without rethrowing its error, so the queue event alone can look successful. Reindex after correcting the loader, embedding, or documents-database failure.

Retrieval fails while the main product works

Check DOCUMENTS_DATABASE_URL, the vector extension, documents migrations, and embedding endpoint/key pairs. The main database can remain healthy while Context persistence or vector search is unavailable.

Streams disappear or cannot be consumed

Check Redis connectivity and API logs. Streaming state uses cache records, and a stream URL can return that no data remains when its cache entry is missing or the stream has already been consumed.

A deployment is healthy and behavior is stale

Identify which service owns the changed path and confirm that service actually released. Then check Redis and the repository's targeted cache invalidation paths. Keep process health, dependency health, deployment completion, and product behavior as separate checks.