* docs: add plugin system guide, authoring tutorial, and manifest reference
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: update plugin docs for host data API, config, and nav additions
Cherry-picked plugin docs (d853b66b8) were written before four follow-up
PRs merged: typed capability-gated Host data API (#1753, ADR 0043),
richer frontend SDK (#1759), per-plugin settings pages (#1761), and
sidebar Integrations nav items (#1767). Re-verify against the merged
code and bring the docs current:
- plugins.md: per-plugin settings/config page, expanded Host RPC list,
updated capability vocabulary (api_read/api_write), sidebar nav note.
- plugins-authoring.md: GetConfig/GetSecret/SetSecret/DeleteSecret and
the six Host data readers, registerRoute options/PluginPageChrome,
NavItem icon/integrations section, host.api.baseUrl/navigate, and the
full host.ui component list.
- plugins-manifest.md: corrected api_read/api_write semantics (now
enforced, not reserved), config_schema validation/secret-masking
behavior, and new Host data API / config-schema sections.
- configuration.md, feature-status.md: document the KANDEV_FEATURES_PLUGINS
flag alongside the existing Office flag.
- coverage.json: new plugin-system coverage area so the public-docs
validator accounts for the three new pages.
* docs: fix plugin config route, signing claim, and data-dir layout
Address automated review findings on PR #1774:
- plugins-manifest.md: PATCH route for config is /api/plugins/{id}, not
/api/plugins/{id}/config (only GET uses the /config suffix).
- plugins-authoring.md: host.store is not read-only — setState is
genuinely exposed and writable; correct the misleading inline comment.
- plugins.md: signature verification is not currently wired up in
production (pkgtar.VerifySignature is nil, never assigned outside
tests), so every package installs and is reported as unsigned today —
correct the 'kandev verifies it' claim in both the install pipeline
step and the signed/unsigned section.
- plugins.md: KANDEV_PLUGIN_DATA_DIR is <id>/data, a sibling of the
<version>/ directory (shared across versions), not nested inside it —
fix the on-disk layout diagram.
* docs: mark plugin docs as experimental
The plugin system is feature-flagged and off by default in production
(KANDEV_FEATURES_PLUGINS). Mark all three plugin pages with
status: experimental frontmatter, matching the k8s.md convention for a
page that is wholly about an experimental feature.
* docs: add plugin UI screenshots and a capture spec
Embed four operator-facing screenshots in the plugin docs (the Plugins
list, the install dialog, a per-plugin settings page with its manifest
card, and a native plugin route). Captured from an isolated e2e backend
with the kandev-plugin-hello reference example installed.
Add e2e/tests/plugins/plugins-docs-screenshots.spec.ts to regenerate them:
it reuses the plugins e2e harness (isolated backend, plugins flag on) and
is skipped unless CAPTURE_DOCS_MEDIA=1, so it adds no CI cost. Defaults to
the repo's own e2e fixture package; override DOCS_PLUGIN_* to point at the
polished hello example (as the committed captures were taken).
* docs: fix plugin screenshot paths for the landing publisher
The Cloudflare preview build failed because markdown images under
docs/public/media/ compile to build-time module imports that the docs
site can't resolve — media/ is reserved for <DocsVideo> assets. The
landing publisher only rewrites and copies images referenced under
screenshots/ (README convention). Move the four plugin captures to
docs/screenshots/plugin-*.png and reference them as ../screenshots/*.png,
and update the capture spec to write there.
* docs: convert plugin how-it-works diagram to mermaid
The hand-drawn ASCII pipeline rendered with broken alignment and stray
syntax highlighting on the docs site. Replace it with a mermaid flowchart
(same convention as architecture.md), which renders as a clean SVG.
* docs: give plugins its own nav section
* docs: clarify plugin tools are declared/listed, not agent-invocable yet
Plugin tools[] are validated and exposed via GET /api/plugins/tools, and
the gRPC InvokeTool transport is implemented and tested, but nothing in
production calls it — no agent, MCP bridge, or HTTP route. The docs
overstated this as agent-invocable. Correct the Plugin interface comment,
the manifest tools[] rows, the how-it-works diagram, and the example
plugin description, and add a note in both the authoring and manifest
pages that InvokeTool stays unused until the agent-tool wiring ships.
* docs: clarify webhook relay in how-it-works diagram
The diagram collapsed two hops into one node, reading as if the plugin
receives the external HTTP POST directly. Split it: an external caller
POSTs to kandev's webhook route, which relays the request to the plugin
over the gRPC HandleWebhook method (the plugin serves no HTTP itself).
Also label the DeliverEvent/InvokeTool/HandleWebhook edges by their real
proto method names.
* docs: note the plugin backend is any go-plugin gRPC executable, Go SDK only
* docs: remove plugin tools from the docs
Plugin tools[] overlap with MCP (kandev's established agent-tool surface)
and the invocation path was never wired to agents. Rather than document a
half-feature that implies plugins are an alternate MCP, drop it entirely:
the Plugin interface (InvokeTool), the manifest tools[] block and field
rows, the how-it-works diagram branch, and the example-plugin mention.
Removal from the backend code is tracked separately.
* docs: replace how-it-works mermaid with a designed diagram image
The mermaid flowchart rendered poorly. Replace it with a hand-authored
HTML/CSS architecture figure (install pipeline + the runtime gRPC
interactions), rendered to docs/screenshots/plugin-architecture.png. The
diagram source lives in the docs-screenshot capture spec (skipped unless
CAPTURE_DOCS_MEDIA=1) so it regenerates deterministically at 2x.
* docs: make the how-it-works figure a real box-and-arrow diagram
The previous figure read as a list of rows. Replace it with a hand-drawn
SVG architecture diagram: the install pipeline feeds a spawned plugin
subprocess, then kandev and the plugin communicate over one gRPC
connection (DeliverEvent, HandleWebhook, and the plugin's Host API
callbacks), with the external webhook caller and the browser SPA as
edge nodes. Source lives in the capture spec's ARCH_HTML.
* docs: make the how-it-works diagram a clean monochrome flowchart
Drop the multi-color component diagram for a calm top-down flowchart
(mermaid's shape, drawn properly): install → verify → extract → spawn,
branching into event delivery and webhook relay, converging on the Host
API callbacks, with an optional native-UI step. Grayscale, no accent
colors. Source stays in the capture spec's ARCH_HTML.
* docs: dark plugin diagram + space out the two page images
- Make the how-it-works flowchart a dark rounded card (matches the docs
page in dark mode; reads as an embedded card in light). Rendered with
omitBackground so the corners are transparent.
- Move the Settings > Plugins list screenshot out of the intro (where it
sat right before the diagram) down into the "Installing a plugin"
section, so text separates the two images.
* docs: update install-dialog screenshot for aligned footer actions
Recaptured after fix(plugins): align install-dialog footer actions
(#1780) — Cancel and Install now sit together in one right-aligned
footer row instead of the Install button being stranded under the
dropzone.
* docs: account for /settings/system/storage in coverage.json
The storage settings route (added by #1699) was never recorded in the
public-docs coverage inventory — the docs validator is path-filtered to
docs/public/** so it didn't run for that PR. This docs PR is the first to
trip it. Add the route to system-health-and-maintenance.
* docs: review fixes — remove stale tools refs, tighten accuracy
Review pass against the now-merged main (tools removed in #1776):
- extending-kandev.md: drop "serves agent-invocable tools" from the plugin
overview (the feature no longer exists).
- plugins-manifest.md: the legacy endpoints block is health/events/webhooks
only (no tools path); and the `signed` field is always false today since
signature verification isn't wired.
- plugins.md: install validation covers the categories/UI-surface enums (not
a "capability vocabulary"); AutoMTLS secures the channel on all platforms,
not just Windows; and status vs signing are separate badges.
* docs: explain what Host state is (GetState/SetState)
The authoring guide listed the state RPCs but never said what 'state'
means. Add a plain-language explanation: a per-plugin, kandev-persisted
key/value store keyed by (scope, scopeID, key) holding JSON objects,
namespaced per plugin, scoped to instance/workspace/task/agent, and how it
differs from the writable data dir. Make the example a real increment.
* docs: note NavItem section:'settings' is not rendered
A NavItem with section:'settings' is accepted by the type but no sidebar
section renders it (plugin-nav-items renders main/default, the Integrations
sections render 'integrations'); annotate that settings pages use
registerSettingsRoute. Addresses repeated review feedback.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
23 KiB
title, description
| title | description |
|---|---|
| Configuration | Configure Kandev workspaces, runtimes, settings, and environment behavior. |
Configuration
Kandev has three distinct configuration surfaces:
- backend startup configuration: defaults,
config.yaml, then environment variables; - persistent product settings edited in the web UI and stored in the database; and
- executor, agent, repository, and workflow profiles stored through their own Settings pages.
This page is the startup-configuration reference. Executor-specific fields are covered in Executors, and deployment examples are in Docker, Kubernetes, and Run as a service.
Load order and lifecycle
At backend startup, later sources override earlier ones:
- embedded production-profile values and built-in defaults;
- the first readable
config.yaml; and - environment variables.
The public launcher has no --config option. It searches for a file named exactly config.yaml in the backend working directory and then /etc/kandev/. A missing file is allowed. Malformed YAML, unmarshal errors, and validated invalid values stop startup.
Configuration is read at process start; there is no file watcher. Restart Kandev after changing YAML or environment variables. The CLI, desktop shell, service manager, Docker, or Kubernetes may set environment variables on the backend, so those values can override a file unexpectedly. Use the process/service/container environment as the final source of truth.
Environment-variable naming
Viper maps a nested YAML key by replacing . with _, adding KANDEV_, and uppercasing. It does not split camelCase words. For example:
database.dbName -> KANDEV_DATABASE_DBNAME
logging.maxSizeMb -> KANDEV_LOGGING_MAXSIZEMB
repoClone.basePath -> KANDEV_REPOCLONE_BASEPATH
Some common camelCase keys have explicit compatibility aliases. Use the documented exact names below; snake_case spellings not listed here are not equivalent.
Complete backend reference
Root and server
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
homeDir |
KANDEV_HOME_DIR |
~/.kandev |
Root for data, tasks, worktrees, cloned repositories, sessions, and logs. A leading ~/ expands. |
server.host |
KANDEV_SERVER_HOST |
0.0.0.0 |
HTTP listen address. Use 127.0.0.1 for local-only access. |
server.port |
KANDEV_SERVER_PORT |
38429 |
UI, HTTP API, WebSocket, and MCP port; must be 1-65535. The launcher normally supplies its selected port. |
server.readTimeout |
KANDEV_SERVER_READTIMEOUT |
30 |
HTTP read timeout in seconds. |
server.writeTimeout |
KANDEV_SERVER_WRITETIMEOUT |
30 |
HTTP write timeout in seconds. |
server.webInternalUrl |
KANDEV_WEB_INTERNAL_URL |
empty | Development reverse-proxy target for a separately running web app. Installed releases normally serve embedded assets. |
The default host exposes the server on every interface even though the CLI prints a localhost URL. The current local product path must not be treated as an authenticated multi-user perimeter. For remote access, bind to loopback and use a trusted authenticated tunnel/proxy, or isolate the network at the deployment layer.
Database
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
database.driver |
KANDEV_DATABASE_DRIVER |
sqlite |
sqlite or postgres (case-normalized). |
database.path |
KANDEV_DATABASE_PATH |
<home>/data/kandev.db |
SQLite database path. Empty resolves to the default. |
database.host |
KANDEV_DATABASE_HOST |
localhost |
PostgreSQL only. |
database.port |
KANDEV_DATABASE_PORT |
5432 |
PostgreSQL only; must be 1-65535. |
database.user |
KANDEV_DATABASE_USER |
kandev |
Required and non-empty for PostgreSQL. |
database.password |
KANDEV_DATABASE_PASSWORD |
empty | PostgreSQL password; requirement depends on server authentication policy. |
database.dbName |
KANDEV_DATABASE_DBNAME |
kandev |
Required and non-empty for PostgreSQL. |
database.sslMode |
KANDEV_DATABASE_SSLMODE |
disable |
disable, require, verify-ca, or verify-full. |
database.maxConns |
KANDEV_DATABASE_MAXCONNS |
25 |
PostgreSQL maximum pool size. |
database.minConns |
KANDEV_DATABASE_MINCONNS |
5 |
PostgreSQL minimum pool size. |
SQLite is the supported default and enables WAL mode. PostgreSQL deployments must provision the database, network policy, TLS trust, backups, and credentials before starting Kandev. Passing the password in an environment variable avoids putting it in YAML but still exposes it to processes/administrators allowed to inspect the environment; use your platform's secret injection controls.
database.path is an advanced override. Persistence honors it, but the current Settings → System → Database and Backups services derive their displayed path, WAL files, backup source, and restore destination from <home>/data/kandev.db. Those UI operations are therefore not reliable for a custom SQLite location. Use operator-managed backup/restore for a custom path and verify the actual database path printed at startup.
Database-only snapshots also omit <home>/data/master.key, the AES-256 key used to decrypt stored secrets. Preserve that owner-only key with an independently secured home/data backup; restoring the database without its matching key leaves encrypted credentials unreadable. See Operations.
Event bus and NATS
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
nats.url |
KANDEV_NATS_URL |
empty | Empty uses the in-process event bus; otherwise connect to NATS. |
nats.clusterId |
KANDEV_NATS_CLUSTERID |
kandev-cluster |
Accepted compatibility field; the current NATS client does not consume it. |
nats.clientId |
KANDEV_NATS_CLIENTID |
kandev-client |
NATS connection name. |
nats.maxReconnects |
KANDEV_NATS_MAXRECONNECTS |
10 |
Reconnect limit; the client uses a two-second reconnect wait and a 5 MiB reconnect buffer. |
events.namespace |
KANDEV_EVENTS_NAMESPACE |
derived | Queue-group namespace. Empty derives a stable, sanitized hash from database identity. |
An external NATS URL moves event traffic across the configured network and can embed credentials/TLS parameters. Protect it as a secret where applicable, require TLS for untrusted networks, and keep namespaces distinct when deployments share one NATS server. clusterId does not provide isolation in the current implementation.
Docker runtime
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
docker.enabled |
KANDEV_DOCKER_ENABLED |
true |
Registers the local Docker executor. The client connects lazily, so startup can succeed without a daemon. |
docker.host |
KANDEV_DOCKER_HOST |
DOCKER_HOST, otherwise platform socket |
Docker endpoint used by the client. Defaults to unix:///var/run/docker.sock on Unix and npipe:////./pipe/docker_engine on Windows. |
docker.apiVersion |
KANDEV_DOCKER_APIVERSION |
empty | Empty uses Docker API negotiation. |
docker.tlsVerify |
KANDEV_DOCKER_TLSVERIFY |
false |
Accepted compatibility field; not wired into the current client. |
docker.defaultNetwork |
KANDEV_DOCKER_DEFAULTNETWORK |
kandev-network |
Accepted compatibility field; not wired into current executor networking. |
docker.volumeBasePath |
KANDEV_DOCKER_VOLUMEBASEPATH |
/var/lib/kandev/volumes on Unix; %LOCALAPPDATA%\kandev\volumes on Windows |
Accepted compatibility field; not wired into current executor volume placement. |
The Docker socket is effectively root-equivalent on many hosts. Do not publish it or assume docker.tlsVerify secures a TCP daemon—it currently does not. Configure TLS through a supported Docker endpoint/environment and validate it independently, or keep the daemon local. See Docker and Executors.
Core agent service
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
agent.standaloneHost |
KANDEV_AGENT_STANDALONE_HOST |
localhost |
Host of the core agentctl control server. |
agent.standalonePort |
AGENTCTL_PORT or KANDEV_AGENT_STANDALONE_PORT |
39429 |
Preferred control port. The launcher may supply a free fallback. |
The launcher starts agentctl, performs a one-time nonce handshake, and supplies the resulting per-launch token internally. Do not persist or proxy its bootstrap/auth state. Agent command, model, environment, permission, and MCP configuration belongs in agent profiles rather than this section.
Authentication, Office, Plugins, voice, and feature flags
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
auth.jwtSecret |
KANDEV_AUTH_JWTSECRET |
generated value | Accepted and validated compatibility configuration; the current main HTTP product path does not use it as an authentication boundary. |
auth.tokenDuration |
KANDEV_AUTH_TOKENDURATION |
3600 |
Must be positive, but is not consumed by the current main HTTP product path. |
office.jwtSigningKey |
KANDEV_OFFICE_JWTSIGNINGKEY |
random per start | HMAC key for Office agent-runtime JWTs. Set a stable secret when Office tasks must survive restarts. |
voice.openAIApiKey |
KANDEV_VOICE_OPENAI_API_KEY |
empty | Server-side transcription fallback when browser speech recognition is unavailable. Empty disables the fallback and its endpoint returns unavailable. |
features.office |
KANDEV_FEATURES_OFFICE |
false in production |
Experimental Office UI, routes, services, and automation. |
features.plugins |
KANDEV_FEATURES_PLUGINS |
false in production |
Extensible plugin system: install/manage plugins, spawn plugin backends, and load native UI bundles. Loaded plugin code runs with backend privileges. |
Do not infer security from auth.jwtSecret: setting it currently does not turn the local server into an authenticated public service. Office's JWT key has a narrower, active purpose. Store both active secrets and third-party API keys in your deployment secret manager; never commit them in config.yaml.
The voice fallback sends audio to the configured OpenAI transcription service and incurs that provider's network, data-handling, and billing behavior. Browser-native speech recognition has its own browser/vendor behavior and does not use this server key.
Logging
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
logging.level |
KANDEV_LOG_LEVEL |
info |
debug, info, warn, or error. The CLI normally supplies warn, info, or debug. |
logging.format |
KANDEV_LOGGING_FORMAT |
text, or json in production/Kubernetes |
text or json; auto is not accepted. |
logging.outputPath |
KANDEV_LOGGING_OUTPUTPATH |
stdout |
stdout, stderr, or a file path. |
logging.maxSizeMb |
KANDEV_LOGGING_MAXSIZEMB |
100 |
File-output rotation threshold. Zero uses lumberjack's 100 MiB default. |
logging.maxBackups |
KANDEV_LOGGING_MAXBACKUPS |
5 |
Rotated-file count; zero means unlimited. |
logging.maxAgeDays |
KANDEV_LOGGING_MAXAGEDAYS |
30 |
Rotated-file age; zero means unlimited. |
logging.compress |
KANDEV_LOGGING_COMPRESS |
true |
Gzip rotated files. |
The format default becomes JSON when KUBERNETES_SERVICE_HOST is non-empty or KANDEV_ENV is exactly production/prod; otherwise it is text. Rotation settings apply only to file output. Active log files are created owner-only (0600) on Unix, so a log shipper running as another user cannot read them without an explicit permission design.
Debug output may contain repository paths, subprocess output, prompts, file content, and tool-call data. Treat it as sensitive.
Repository, worktree, and clone paths
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
repositoryDiscovery.roots |
KANDEV_REPOSITORYDISCOVERY_ROOTS |
[] |
Local roots exposed to repository discovery. Prefer absolute paths. Array encoding through environment variables is Viper-dependent; YAML is clearer. |
repositoryDiscovery.maxDepth |
KANDEV_REPOSITORYDISCOVERY_MAXDEPTH |
5 |
Positive directory traversal depth. |
worktree.enabled |
KANDEV_WORKTREE_ENABLED |
true |
Enables the worktree provider. |
worktree.defaultBranch |
KANDEV_WORKTREE_DEFAULTBRANCH |
main |
Accepted compatibility field; current task behavior uses each repository's stored/detected default branch instead. |
worktree.cleanupOnRemove |
KANDEV_WORKTREE_CLEANUPONREMOVE |
true |
Accepted compatibility field; current lifecycle cleanup is controlled by repository/task operations, not this value. |
worktree.fetchTimeoutSeconds |
KANDEV_WORKTREE_FETCHTIMEOUTSECONDS |
60 |
Git fetch timeout during worktree preparation. |
worktree.pullTimeoutSeconds |
KANDEV_WORKTREE_PULLTIMEOUTSECONDS |
60 |
Git pull timeout during worktree preparation. |
repoClone.basePath |
KANDEV_REPOCLONE_BASEPATH |
<home>/repos |
Base directory for provider-backed clones. A leading ~/ expands. |
Discovery roots grant Kandev visibility into local filesystem trees and repositories. Scope them narrowly. Worktrees and clones can contain credentials or generated files ignored by Git; review repository copy-file and setup/cleanup settings before remote execution. See Git operations.
Debug configuration
| YAML key | Environment variable | Default | Current behavior |
|---|---|---|---|
debug.devMode |
KANDEV_DEBUG_DEV_MODE |
false |
Enables diagnostic endpoints and agent-message debug logging. |
debug.pprofEnabled |
KANDEV_DEBUG_PPROF_ENABLED |
false |
Legacy alias; also enables debug mode. |
Debug mode is high risk. It enables local diagnostic surfaces and implies KANDEV_DEBUG_AGENT_MESSAGES=true and KANDEV_DEBUG_PPROF_ENABLED=true when not explicitly locked by the environment. ACP JSONL frames include complete prompts, file content, and tool calls. Do not enable it on a shared or network-exposed backend.
Minimal examples
For a local-only CLI server with an isolated home:
KANDEV_SERVER_HOST=127.0.0.1 \
KANDEV_HOME_DIR="$PWD/.kandev-local" \
kandev --headless
For a file-based deployment, override only what is needed:
homeDir: "/srv/kandev"
server:
host: "127.0.0.1"
port: 38429
logging:
format: "json"
outputPath: "/var/log/kandev/backend.log"
repositoryDiscovery:
roots:
- "/srv/repositories"
A complete shape, including compatibility fields, is:
homeDir: ""
server:
host: "0.0.0.0"
port: 38429
readTimeout: 30
writeTimeout: 30
webInternalUrl: ""
database:
driver: "sqlite"
path: ""
host: "localhost"
port: 5432
user: "kandev"
password: ""
dbName: "kandev"
sslMode: "disable"
maxConns: 25
minConns: 5
nats:
url: ""
clusterId: "kandev-cluster" # compatibility-only today
clientId: "kandev-client"
maxReconnects: 10
events:
namespace: ""
docker:
enabled: true
host: "unix:///var/run/docker.sock" # use the Windows named pipe on Windows
apiVersion: ""
tlsVerify: false # compatibility-only today
defaultNetwork: "kandev-network" # compatibility-only today
volumeBasePath: "/var/lib/kandev/volumes" # compatibility-only today
agent:
standaloneHost: "localhost"
standalonePort: 39429
auth:
jwtSecret: "" # compatibility-only for the main HTTP product path
tokenDuration: 3600 # compatibility-only for the main HTTP product path
logging:
level: "info"
format: "text"
outputPath: "stdout"
maxSizeMb: 100
maxBackups: 5
maxAgeDays: 30
compress: true
repositoryDiscovery:
roots: []
maxDepth: 5
worktree:
enabled: true
defaultBranch: "main" # compatibility-only today
cleanupOnRemove: true # compatibility-only today
fetchTimeoutSeconds: 60
pullTimeoutSeconds: 60
repoClone:
basePath: ""
debug:
devMode: false
pprofEnabled: false
office:
jwtSigningKey: ""
voice:
openAIApiKey: ""
features:
office: false
plugins: false
Copying this entire file is unnecessary and can freeze old defaults in a deployment. Keep only deliberate overrides. On Windows, do not copy the Unix Docker host/path literals from this example.
Runtime feature toggles
Settings → System → Feature Toggles manages three startup-time flags:
| Key | Environment lock | Production default | Effect |
|---|---|---|---|
features.office |
KANDEV_FEATURES_OFFICE |
off | Experimental autonomous-agent Office surfaces and automation. |
features.plugins |
KANDEV_FEATURES_PLUGINS |
off | Extensible plugin system: install/manage plugins and load their backends and native UI bundles. |
debug.devMode |
KANDEV_DEBUG_DEV_MODE (also locked by explicit legacy/debug-message vars) |
off | High-risk diagnostic endpoints and ACP frame logging. |
UI changes are persisted in the database and require a restart. An explicitly set environment value wins and locks the UI control. Otherwise a database override wins over the embedded profile/default. Resetting a toggle removes its database override.
The source checkout's make dev activates the embedded development profile, which enables Office, debug surfaces, ACP logging, and a mock agent. Installed run/desktop builds select the safe production profile unless the environment explicitly opts in. E2E mock variables and routes are test-only and must never be enabled on a public deployment.
Credentials and product settings
Most integrations, executor profiles, agent profiles, MCP servers, repository settings, and UI preferences are persistent database records edited under Settings. They are not fields in config.yaml. Secret values use an encrypted secret store backed by <home>/data/master.key; filesystem permissions, database backups, and key backup are part of the security boundary.
For headless injection, Kandev can also read agent credentials from the process environment by their required name (for example ANTHROPIC_API_KEY) or the KANDEV_-prefixed form. KANDEV_CREDENTIALS_FILE adds a fallback JSON provider:
{
"ANTHROPIC_API_KEY": "replace-at-deployment-time",
"OPENAI_API_KEY": "replace-at-deployment-time"
}
The file is loaded lazily, expects a flat string-to-string object, and is cached; restart after changing it. A missing file behaves as no file credentials, while unreadable or invalid JSON produces credential-resolution errors. The database secret store and environment providers are consulted before this file. Restrict file permissions to the Kandev service account and never commit it.
Profile environment variables are eventually injected into agent subprocesses or remote executor environments. Anyone who can edit a profile, inspect a remote host/container, enable debug frame logs, or run commands as the Kandev account may be able to access them. Use least-privilege, task-scoped credentials and rotate them after exposure.
Advanced operator tuning
These startup-only variables are supported by specific runtime components but are not YAML fields. Leave them at defaults unless diagnosing a measured problem.
| Variable | Default | Parsing and effect |
|---|---|---|
KANDEV_GH_MAX_CONCURRENT |
8 |
Positive integer process-wide cap for gh subprocesses; invalid/non-positive uses default. |
KANDEV_GIT_MAX_CONCURRENT |
12 |
Positive integer process-wide cap for git subprocesses; invalid/non-positive uses default. |
KANDEV_QUEUE_MAX_PER_SESSION |
10 |
Queued user messages per session. Invalid uses default; zero/negative disables the cap. |
KANDEV_ACP_IDLE_TIMEOUT |
1h |
Go duration after which idle agentctl instances are reaped; 0 disables. Invalid uses default. |
KANDEV_ACP_IDLE_REAPER_INTERVAL |
1m |
Go duration between idle scans. Intended mainly for testing; use the default in production. |
KANDEV_ACP_NOTIF_QUEUE |
131072 |
Per-connection ACP inbound notification capacity; positive values clamp to 1024-131072, invalid uses default. |
KANDEV_PLAN_COALESCE_WINDOW_MS |
300000 |
Non-negative milliseconds for same-author plan revision coalescing; invalid/negative uses five minutes. |
KANDEV_OFFICE_SCHEDULER_TICK_MS |
5000 |
Positive integer safety-net interval for queued/retry run claiming. New-run signals are event-driven; invalid/non-positive uses five seconds. |
KANDEV_MCP_LOG_FILE |
unset | File path for per-agentctl MCP debug logs. Logs tool names, arguments, session IDs, results for tool errors, and timings; invalid paths warn and disable this sink. |
OTEL_EXPORTER_OTLP_ENDPOINT |
unset | Enables OTLP/HTTP tracing for backend and agentctl spans; unset uses a no-op tracer. See the transport warning below. |
Changing concurrency/queue values trades memory and process pressure against throughput. Values are captured during process/component initialization and need a restart.
The current OTLP exporter strips an http:// or https:// prefix from the configured endpoint and always uses WithInsecure(). Treat this as implementation-bound cleartext transport: send it only to a trusted private collector over a protected network, not directly across an untrusted network. The service name is kandev-agentctl, and spans can include task/session/execution IDs plus raw agent-event JSON truncated to 8192 characters. That payload can contain prompts, files, and tool data. Use collector-side access controls and retention accordingly.
ACP debug-log controls
These apply only when KANDEV_DEBUG_AGENT_MESSAGES=true:
| Variable | Default |
|---|---|
KANDEV_DEBUG_LOG_DIR |
<home>/logs/acp |
KANDEV_DEBUG_ACP_MAX_FILES |
200 |
KANDEV_DEBUG_ACP_RETENTION_HOURS |
48 |
KANDEV_DEBUG_ACP_MAX_FILE_BYTES |
8388608 (8 MiB) |
Retention values must be positive integers; invalid/non-positive values use defaults. Directories and files use owner-only 0700/0600 modes on Unix. Rotation, age pruning, and file-count pruning bound normal growth, but these files remain highly sensitive and can exist inside a Docker executor rather than on the host.
Validation and troubleshooting
Startup validation currently enforces:
server.port:1-65535;database.driver:sqliteorpostgres;- PostgreSQL port, non-empty user/database name, and supported SSL mode;
- positive
auth.tokenDuration; - logging level/format; and
- positive
repositoryDiscovery.maxDepth.
Other fields can pass configuration validation and still fail later—for example an unreachable NATS/PostgreSQL/Docker endpoint, unwritable log path, nonsensical timeout, or incompatible pool sizes. A field appearing in the schema does not prove its subsystem is available.
If a value appears ignored:
- confirm the exact environment spelling, especially camelCase keys;
- inspect the launcher/service/container environment for an overriding value;
- confirm
config.yamlis in the backend working directory or/etc/kandev/; - restart the backend; and
- check whether the field is marked compatibility-only above.
Use kandev --verbose to surface startup errors. Do not use --debug merely to diagnose a YAML typo on an exposed machine; verbose logs are usually sufficient.
Variables used only to assemble/test the runtime—such as KANDEV_WEB_DIST_DIR, KANDEV_DESKTOP_RUNTIME_DIR, mock/E2E switches, supervisor socket/manifest values, and bootstrap nonces—are internal implementation contracts, not supported deployment configuration. KANDEV_BUNDLE_DIR is the narrow exception documented for installer/package integration in CLI; end users should still let the installer set it.