Files
kandev/docs/specs/feature-toggles/spec.md
T
2026-07-31 14:23:16 +01:00

11 KiB

status, created, owner
status created owner
draft 2026-06-14 tbd

Feature Toggles

Why

Users can already opt into runtime features through environment variables and profiles.yaml, but those controls are invisible from the app and require terminal access. Users need a settings page that explains experimental features, lets them choose install-level overrides, and clearly handles the restart required for startup-gated features such as Office mode and Debug mode.

What

  • Kandev adds a Feature Toggles settings page at /settings/system/feature-toggles.
  • The page shows user-facing runtime toggles, not every profile/env knob.
  • V1 exposes three user-facing toggles:
    • Office mode — enables the autonomous-agent Office surface.
    • App status bar — shows the general-purpose desktop/tablet status bar and phone Status drawer entry. It is stable and off by default in production; enabling it changes visibility only. Disabling it does not suppress the problem-only WebSocket connectivity warning.
    • Debug mode — enables local diagnostic/debug behavior, including agent message debug logs.
  • Office mode is marked Experimental and includes a concise description of what it does and the risks of enabling it.
  • Debug mode is a single user-facing toggle. Agent message debug logs are part of Debug mode, not a duplicate top-level toggle.
  • Every v1 toggle requires restart before the changed value takes effect.
  • The page distinguishes effective value, saved override, default value, and environment-locked value.
  • Explicit environment variables remain authoritative. If an env var controls a flag, the UI shows the flag as locked and does not allow overriding it.
  • Users can reset a saved override back to the profile/env default.
  • After saving any restart-required change, the UI shows a persistent restart banner and offers a restart action when Kandev can safely restart itself.
  • If automatic restart is unsupported, the UI shows manual restart guidance.
  • /api/v1/features continues to return effective feature booleans for SSR gating and existing useFeature() callers.

Tech stack

  • Backend: Go, Gin HTTP handlers, SQLite persistence.
  • Frontend: Next.js App Router, React, Zustand, @kandev/ui shadcn components.
  • E2E: Playwright under apps/web/e2e.
  • Runtime defaults: profiles.yaml embedded by apps/backend/internal/profiles.

Commands

make fmt
make typecheck test lint
make -C apps/backend test
cd apps && pnpm --filter @kandev/web typecheck
cd apps && pnpm --filter @kandev/web lint
cd apps/web && pnpm e2e -- settings/feature-toggles.spec.ts settings/mobile-feature-toggles.spec.ts

Project structure

apps/backend/internal/runtimeflags/      Runtime flag registry, store, service, handlers
apps/backend/internal/system/restart/    Restart capability/request support
apps/backend/cmd/kandev/                 Startup override application and route wiring
apps/web/app/settings/system/feature-toggles/page.tsx
apps/web/components/settings/system/feature-toggles-settings.tsx
apps/web/lib/api/domains/runtime-flags-api.ts
docs/decisions/0018-runtime-settings-overrides.md
docs/decisions/0019-restart-supervisor.md

Planned E2E coverage:

apps/web/e2e/settings/feature-toggles.spec.ts         TODO
apps/web/e2e/settings/mobile-feature-toggles.spec.ts  TODO

Code style

Use typed contracts at the backend boundary and keep env-var knowledge in the backend registry rather than duplicating it in React components.

type RuntimeFlagDefinition struct {
	Key             string
	EnvVar          string
	Kind            RuntimeFlagKind
	Label           string
	Description     string
	Stability       RuntimeFlagStability
	RiskLevel       RuntimeFlagRiskLevel
	RiskDescription string
	RestartRequired bool
	Mutable          bool
}

Frontend components render the API shape and avoid hardcoding flag-specific logic except for layout/grouping.

Data model

Feature Toggle overrides are install-scoped and stored in SQLite.

runtime_flag_overrides

Field Type Constraint
key TEXT primary key, e.g. features.office
value INTEGER required, 0 or 1
created_at DATETIME required
updated_at DATETIME required

Absence of a row means "use the effective default". A false row is a real override and must not be treated as missing.

API surface

GET /api/v1/runtime-flags

Returns all user-facing runtime toggle states and metadata.

{
  "flags": [
    {
      "key": "features.office",
      "kind": "feature",
      "label": "Office mode",
      "description": "Enables autonomous agent office workflows and related settings.",
      "stability": "experimental",
      "risk_level": "medium",
      "risk_description": "Office mode is still evolving. Workflows, routes, and background automation may change between releases and should be reviewed before relying on them.",
      "effective_value": false,
      "default_value": false,
      "override_value": true,
      "source": "override",
      "env_var": "KANDEV_FEATURES_OFFICE",
      "env_locked": false,
      "restart_required": true,
      "requires_restart_to_apply": true
    }
  ]
}

PATCH /api/v1/runtime-flags/:key

{ "override": true }

override can be true, false, or null. null clears the saved override. Unknown keys return 404. Env-locked keys reject writes with 409.

GET /api/v1/system/restart-capability

{
  "supported": true,
  "mode": "cli",
  "reason": ""
}

mode is cli, systemd, launchd, or unsupported.

POST /api/v1/system/restart

Requests a restart. The response is flushed before shutdown starts so the UI can enter a waiting state and poll /health.

GET /api/v1/features

Existing contract remains unchanged: effective feature booleans only.

State machine

Runtime flag state:

profile default -> override saved -> restart pending -> restarted/effective
               \-> override cleared -> restart pending -> restarted/default

Restart request state:

idle -> requested -> backend unavailable -> backend healthy -> complete
                 \-> timeout -> manual recovery shown

Permissions

Kandev is single-user in v1. Every user with settings access can view and change Feature Toggles. Future multi-user support must restrict Feature Toggles, especially Debug mode, to admins.

Failure modes

  • Environment-locked flag — UI disables the switch and shows the controlling env var. API rejects override writes with 409.
  • Override store unavailable — runtime flags API fails with a recoverable error; existing /api/v1/features continues to use startup-effective config.
  • Restart unsupported — restart banner shows manual restart instructions and no dead action button.
  • Restart requested while one is pending — API rejects or idempotently reports the pending restart.
  • Backend does not return after restart — frontend times out and shows manual recovery guidance.
  • Debug mode enabled — UI warns that local diagnostic endpoints and agent message logs may include sensitive prompt, file, and tool content.

Persistence guarantees

  • Saved overrides survive Kandev restarts.
  • Effective runtime values are applied at backend startup.
  • V1 toggles are startup-gated; changing a value does not affect already registered routes, constructed services, or running agentctl processes until restart.
  • Environment variables always win over saved overrides.

Testing strategy

  • Backend unit tests cover registry metadata, override persistence, precedence, env locking, and pending-restart computation.
  • Backend handler tests cover runtime flag APIs and restart capability/request behavior.
  • Frontend component tests cover Office experimental copy, Debug mode copy, env-locked rows, save/reset actions, and restart banner states.
  • Desktop and mobile Playwright tests cover the user workflow from settings navigation through saving a restart-required toggle.

Boundaries

  • Always: keep profiles.yaml as immutable shipped defaults.
  • Always: keep env vars authoritative over DB overrides.
  • Always: show restart-required state for v1 toggles.
  • Always: include mobile coverage for the settings page.
  • Ask first: adding a new external flag service or changing the auth model.
  • Ask first: exposing mocks/e2e tuning as user-facing toggles.
  • Never: make the Go backend fork an unmanaged replacement process for restart.
  • Never: expose agent message debug logs as a separate top-level toggle in v1.

Scenarios

  • GIVEN Office mode is using the production default, WHEN the user opens /settings/system/feature-toggles, THEN Office mode appears off with a Default source, an Experimental badge, and risk text.
  • GIVEN Office mode is off, WHEN the user enables it, THEN the override is saved and the page shows that restart is required before Office mode takes effect.
  • GIVEN KANDEV_FEATURES_OFFICE=true is set in the environment, WHEN the user opens Feature Toggles, THEN Office mode appears on, env-locked, and cannot be changed from the UI.
  • GIVEN Debug mode is off, WHEN the user enables it, THEN the page explains that local diagnostic endpoints and agent message debug logs will be enabled after restart.
  • GIVEN App status bar is using its default, WHEN the user opens Feature Toggles, THEN it appears off and explains that enabling it adds both the desktop/tablet bar and ordinary phone Status entry after restart, while urgent WebSocket connectivity warnings remain available when it is off.
  • GIVEN a toggle override exists, WHEN the user clicks Reset to default, THEN the override is removed and the page shows restart-required state if the effective runtime value differs from the restored default.
  • GIVEN restart capability is supported, WHEN the user clicks Restart Kandev, THEN the UI enters Restarting..., polls /health, and refreshes the page after the backend returns healthy.
  • GIVEN restart capability is unsupported, WHEN a restart-required change is saved, THEN the UI shows manual restart instructions instead of a clickable restart button.
  • GIVEN a mobile viewport, WHEN the user opens Feature Toggles, THEN all descriptions, badges, switches, reset buttons, and restart actions are reachable without horizontal scrolling.

Success criteria

  • A user can enable or disable Office mode from Feature Toggles and understand that restart is required.
  • A user can enable or hide the App status bar from Feature Toggles and understand that it applies after restart on every breakpoint.
  • Office mode clearly communicates experimental status and risk.
  • A user can enable or disable Debug mode without seeing duplicate agent-message debug toggles.
  • Env-controlled flags are accurately shown as locked.
  • Restart-required changes have a clear supported or manual restart path.
  • Existing SSR feature gating keeps working through /api/v1/features.
  • Desktop and mobile tests cover the core workflow.

Out of scope

  • Live-applying Office mode or Debug mode without restart.
  • Per-user, workspace-scoped, or percentage rollout flags.
  • Exposing mock providers, e2e tuning, or arbitrary env vars as user toggles.
  • A third-party feature flag service.
  • Multi-user admin enforcement in v1.