Skip to content

Configuration

All configuration is done via environment variables. You can use a .env file — the application loads it automatically on startup.

Core

VariableRequiredDefaultDescription
DATABASE_URLYesPostgreSQL connection string
KINDE_DOMAINYesKinde tenant URL (e.g. https://your-tenant.kinde.com). Also served to the SPA via GET /api/public-config.
KINDE_CLIENT_IDYesKinde M2M client ID. Also served to the SPA via GET /api/public-config.
KINDE_CLIENT_SECRETYesKinde M2M client secret
KINDE_REDIRECT_URIYesOAuth redirect URI the SPA returns to after login
KINDE_LOGOUT_URIYesURI the SPA returns to after logout
PORTNo8080HTTP server port
LOG_LEVELNoinfoLog level: trace, debug, info, warn, error, fatal, panic
LOG_REQUESTSNofalseEnable HTTP request logging
PROMETHEUS_ENABLEDNotrueEnable Prometheus metrics at /metrics

Admin API

VariableRequiredDefaultDescription
ADMIN_API_TOKENNoEnables the admin API at /api/admin. Must be at least 50 characters

When set, the following endpoints become available:

  • POST /api/admin/users — Create a user
  • GET /api/admin/users — List users
  • DELETE /api/admin/users/:id — Suspend a user

All admin endpoints require the Authorization: Bearer <token> header.

Mailer

VariableRequiredDefaultDescription
MAILER_TRANSPORTYessmtp or log
MAILER_FROM_NAMEYesSender display name
MAILER_FROM_EMAILYesSender email address

SMTP transport

When MAILER_TRANSPORT=smtp:

VariableRequiredDefaultDescription
SMTP_HOSTYesSMTP server hostname
SMTP_PORTNo587SMTP server port
SMTP_USERNAMEYesSMTP authentication username
SMTP_PASSWORDYesSMTP authentication password

Log transport

When MAILER_TRANSPORT=log, no additional variables are needed. Emails are printed to stdout. Useful for development and testing.

LLM provider

The grading pipeline calls a large language model to evaluate submissions. The provider is selected by LLM_PROVIDER and the rest of the configuration depends on which provider is chosen. The grader code is provider-agnostic — adding a new backend means implementing a single LLMClient interface and wiring a new branch into the factory; the grading pipeline itself does not change.

VariableRequiredDefaultDescription
LLM_PROVIDERYesProvider id: anthropic or bedrock.

Anthropic

When LLM_PROVIDER=anthropic:

VariableRequiredDefaultDescription
ANTHROPIC_API_KEYNoAPI key. If omitted, the SDK falls back to its default credential discovery (e.g. the ANTHROPIC_API_KEY env var that the SDK itself reads).

The model is pinned in code (claude-sonnet-4-20250514) and intentionally not configurable — the prompts are tuned for it.

Bedrock

When LLM_PROVIDER=bedrock:

VariableRequiredDefaultDescription
AWS_REGIONNous-east-1AWS region to send Bedrock requests to. Must be a region where the us.anthropic.claude-sonnet-4-6 cross-region inference profile is supported.

Authentication uses the standard AWS credential chain — on EC2 the instance IAM role is picked up automatically; no explicit key configuration is needed. The instance role must have bedrock:InvokeModel permission for the Anthropic foundation models (already granted by the CDK stack).

Grading worker

The grading worker pulls jobs from the jobs table and runs them against the configured LLM provider. Failed jobs are rescheduled with exponential backoff (base · 2^(attempt-1), capped at one hour) until they hit the configured maximum number of attempts, at which point they are marked failed and stop being retried. Successful jobs are marked completed with completed_at = now() so total processing time can be measured.

VariableRequiredDefaultDescription
WORKER_TYPENodb to enable the polling worker, empty to disable. Shared by the grading and calibration workers.
GRADING_WORKER_CONCURRENCYNo4Number of in-process worker goroutines.
GRADING_WORKER_POLL_INTERVALNo5sHow often each worker polls for new jobs (Go duration format).
GRADING_WORKER_BATCH_LIMITNo5Max jobs a single worker processes back-to-back before sleeping for GRADING_WORKER_POLL_INTERVAL. Prevents one worker from monopolizing a large backlog when others could share the load.
GRADING_RETRY_MAXNo10Max attempts per job before it is marked failed.
GRADING_RETRY_BASE_DELAYNo5mBase delay for exponential backoff between retries (Go duration format). Capped at 1h.

Calibration worker

The calibration worker runs alongside the grading worker (same WORKER_TYPE=db switch) and grades the teacher-supplied calibration items used to anchor the rubric. It is automatically disabled when no LLM provider is configured.

VariableRequiredDefaultDescription
CALIBRATION_WORKER_POLL_INTERVALNo5sHow often the worker polls for new calibration jobs (Go duration format).
CALIBRATION_RETRY_MAXNo5Max attempts per calibration item before it is marked failed.
CALIBRATION_RETRY_BASE_DELAYNo5mBase delay for exponential backoff between retries (Go duration format).
CALIBRATION_BATCH_SIZENo50Max calibration items the worker fetches per poll.

Database connection pool

VariableRequiredDefaultDescription
DB_MAX_OPEN_CONNSNo25Maximum number of open connections
DB_MAX_IDLE_CONNSNo10Maximum number of idle connections
DB_CONN_MAX_LIFETIMENo30mMaximum connection lifetime (Go duration format)
DB_CONN_MAX_IDLE_TIMENo5mMaximum idle connection lifetime (Go duration format)

Security headers

All optional. Sensible defaults are applied.

VariableRequiredDefaultDescription
HELMET_NO_SNIFFNotrueSet X-Content-Type-Options: nosniff
HELMET_FRAME_GUARDNoDENYX-Frame-Options value
HELMET_HSTSNotrueEnable Strict-Transport-Security
HELMET_HSTS_MAX_AGENo15552000HSTS max-age in seconds
HELMET_HSTS_INCLUDE_SUBDOMAINSNotrueInclude subdomains in HSTS
HELMET_XSS_FILTERNotrueSet X-XSS-Protection: 1; mode=block
HELMET_DNS_PREFETCH_CONTROLNotrueSet X-DNS-Prefetch-Control: off
HELMET_NO_ROBOT_INDEXNofalseSet X-Robots-Tag: noindex, nofollow
HELMET_REFERRER_POLICYNostrict-origin-when-cross-originReferrer-Policy value
HELMET_CROSS_ORIGIN_OPENER_POLICYNosame-originCross-Origin-Opener-Policy value
HELMET_CROSS_ORIGIN_EMBEDDER_POLICYNoCross-Origin-Embedder-Policy value
HELMET_CROSS_ORIGIN_RESOURCE_POLICYNosame-originCross-Origin-Resource-Policy value