> For the complete documentation index, see [llms.txt](https://docs.kyvvu.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kyvvu.com/deployment/configuration.md).

# Configuration Reference

**What you'll learn:** All environment variables for the SDK, engine, and platform API.

***

## SDK / Engine configuration

Precedence (highest to lowest): explicit kwargs > environment variables > `.env` in cwd > built-in defaults.

### Authentication and identity

| Env var          | Default                      | Description                                                                             |
| ---------------- | ---------------------------- | --------------------------------------------------------------------------------------- |
| `KV_API_URL`     | `https://platform.kyvvu.com` | Base URL of the Kyvvu platform API.                                                     |
| `KV_API_KEY`     | --                           | Bearer API key (`KvKey-...`). Required for policy fetch.                                |
| `KV_AGENT_KEY`   | --                           | Stable agent identifier used to fetch policies.                                         |
| `KV_INSTANCE_ID` | auto-generated               | Identifier for this runner instance. A random suffix is appended to prevent collisions. |

### Log output

| Env var                | Default                         | Description                                                                                                                                       |
| ---------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `KV_LOG_LOCATION`      | `stdout`                        | WHERE logs go. URL → HTTP POST, file path → JSONL, `stdout` → terminal, `none`/empty → disabled.                                                  |
| `KV_LOG_FORMAT`        | `kv`                            | HOW logs are formatted: `kv` (Kyvvu batch API), `json`, or `otlp`.                                                                                |
| `KV_INCIDENT_LOCATION` | unset (inherit trace sink)      | WHERE incidents go. Same vocabulary as `KV_LOG_LOCATION`. Unset inherits the trace sink, using the `…/api/v1/incidents` path for the `kv` format. |
| `KV_INCIDENT_FORMAT`   | unset (inherit `KV_LOG_FORMAT`) | HOW incidents are formatted: `kv`, `json`, or `otlp` (a standalone `kyvvu.incident` span).                                                        |

### Behaviour

| Env var                | Default      | Description                                                                                     |
| ---------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
| `KV_ENVIRONMENT`       | `production` | Forwarded to `EvalContext.environment`.                                                         |
| `KV_LOG_PAYLOADS`      | `full`       | `full` includes step input/output in logs. `metadata_only` redacts content but preserves shape. |
| `KV_TEMPLATE_LOCATION` | built-in     | Path to a custom YAML behaviour template.                                                       |

### Cache and limits

| Env var                     | Default | Description                                                                                                     |
| --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `KV_POLICY_TTL_SECONDS`     | `300`   | How long to cache fetched policies (seconds).                                                                   |
| `KV_HTTP_TIMEOUT_SECONDS`   | `10`    | Per-request HTTP timeout.                                                                                       |
| `KV_TASK_MAX_AGE_SECONDS`   | `3600`  | Abandoned-task eviction threshold for `sweep_stale_tasks()`.                                                    |
| `KV_SWEEP_ENABLED`          | `true`  | Whether the background sweeper thread starts automatically. Set `false` to call `sweep_stale_tasks()` manually. |
| `KV_SWEEP_INTERVAL_SECONDS` | `300`   | How often the background sweeper runs (seconds).                                                                |
| `KV_SWEEP_FLUSH_ON_EVICT`   | `true`  | Whether to attempt a batch log post for evicted tasks before discarding their buffers.                          |

### Resilience (opt-in)

| Env var                           | Default          | Description                                                                                                                                                                            |
| --------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `KV_POLICY_FAIL_MODE`             | `open`           | `open` = allow all when no policies loaded. `closed` = block all `step_execution` behaviors when no policies are available.                                                            |
| `KV_POLICY_CACHE_PATH`            | empty (disabled) | File path for on-disk policy cache. Written after each successful fetch; loaded on cold start if the API is unreachable.                                                               |
| `KV_POLICY_CACHE_MAX_AGE_SECONDS` | `86400`          | Maximum age (seconds) of the disk cache before a staleness warning is emitted. The cache is still used when stale.                                                                     |
| `KV_POLICY_HMAC_SECRET`           | empty (disabled) | Shared secret for HMAC-SHA256 verification of policy fetch responses. Must be set on both the engine and API for signing to activate.                                                  |
| `KV_REGISTRATION_TTL`             | empty (infinite) | How long a cached agent registration is valid before re-registering. Accepts `30m`, `24h`, `7d`, or raw seconds. Empty = cache never expires (re-registration only on payload change). |

### Logging

| Env var        | Default | Description                                                                                      |
| -------------- | ------- | ------------------------------------------------------------------------------------------------ |
| `KV_LOG_LEVEL` | `INFO`  | Log level for `kyvvu` / `kyvvu_engine` Python loggers. Set to `DEBUG` for per-evaluation traces. |

***

## Policy-bundle integrity (HMAC)

`KV_POLICY_HMAC_SECRET` adds integrity and authenticity protection to the policy-distribution channel. When set, the API signs every policy-fetch response with HMAC-SHA256 (header `X-Kyvvu-Policy-Signature`, computed over the JSON body) and the engine verifies that signature before trusting the policies. It is defense-in-depth on top of TLS: TLS protects the connection, while the HMAC binds each policy bundle to a shared secret that a passive proxy or compromised intermediary cannot forge.

Set `KV_POLICY_HMAC_SECRET` to the **same value** on the API and on every verifying engine. Generate a secret with:

```bash
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
```

### Rollout order

1. **Set the API first.** Engines that do not yet have the secret simply skip verification, so enabling it on the API is non-breaking.
2. **Then set the engines.** Each engine begins verifying once the secret is present on both sides.

An engine configured with a **mismatched** secret rejects every fetch and keeps serving its previously cached policies. On a cold start with no cache, an engine running `KV_POLICY_FAIL_MODE=closed` blocks all `step_execution` behaviors until the secret is aligned.

### Rotation caveat

With a single shared secret, rotation is **not** zero-downtime. During rotation the API and the engines briefly hold different secrets; fetches are rejected and policies stop updating (the last cached bundle is still enforced) until both sides are aligned again. Plan rotations for a window where a short policy-update pause is acceptable.

### Scope caveat

This is a **self-hosted / full-stack** feature: a single shared secret is only appropriate when one operator controls both the API and the engine. It is not intended for hosted / multi-tenant verification. Per-API-key / asymmetric scoping for multi-tenant deployments is tracked in issue #278.

***

## Email (magic-link auth)

Magic link is the sole login and signup path, so every environment needs a working transport -- there is no password fallback. Transport is selected by `KV_EMAIL_TRANSPORT`:

| Env var                    | Default                         | Description                                                        |
| -------------------------- | ------------------------------- | ------------------------------------------------------------------ |
| `KV_EMAIL_TRANSPORT`       | `smtp`                          | `smtp`, `ses`, or `log` (log is refused in production; see below). |
| `KV_SMTP_HOST`             | empty                           | SMTP server hostname. Required when `KV_EMAIL_TRANSPORT=smtp`.     |
| `KV_SMTP_PORT`             | `587`                           | SMTP server port (TLS).                                            |
| `KV_SMTP_USER`             | empty                           | SMTP authentication username (`smtp` transport only).              |
| `KV_SMTP_PASSWORD`         | empty                           | SMTP authentication password (`smtp` transport only).              |
| `KV_SMTP_FROM`             | `noreply@kyvvu.com`             | Sender / `From` address, used by both `smtp` and `ses`.            |
| `KV_WEB_URL`               | `http://localhost:3000`         | Dashboard URL used to build magic-link URLs.                       |
| `KV_EMAIL_ALLOWED_DOMAINS` | `kyvvu.com,mail-test.kyvvu.com` | Recipient allowlist enforced outside production.                   |

In local development, `make dev` points `KV_SMTP_HOST` at Mailpit, so mail is readable at <http://localhost:8025> without any SES setup. `KV_EMAIL_TRANSPORT=log` logs the magic-link URL instead of sending -- must be set explicitly and is refused in production, since it puts a live login credential in the log.

### Production setup (AWS SES)

Dev, stage and production all send via the **SES API** (`KV_EMAIL_TRANSPORT=ses`), authenticated as the ECS task role -- there is no SMTP credential to provision or copy. The domain identity, DKIM, and MAIL FROM subdomain are provisioned by Terraform (`infra/terraform/ses.tf`); the task role's `ses:SendEmail`/`ses:SendRawEmail` grant is provisioned per environment by `modules/platform-env/email.tf`, scoped to that one domain identity.

Note: New SES accounts start in sandbox mode. Request production access via AWS Console → SES → Account dashboard.

***

## Next steps

* [Self-Hosted Setup](https://github.com/Kyvvu/platform/tree/main/docs/deployment/self-hosted.md) — deployment guide
* [Architecture](/core-concepts/architecture.md) — how components use these settings
