# Kandev Engineering Guide > **Purpose**: Architecture notes, key patterns, and conventions for LLM agents working on Kandev. ## Repo Layout ``` apps/ ├── backend/ # Go backend (orchestrator, lifecycle, agentctl, WS gateway) ├── web/ # Vite/React SPA frontend (Go boot payload + WS + Zustand) ├── desktop/ # Tauri desktop shell around the native runtime ├── cli/ # npm publishing shim only (4-line exec of the Go binary) └── packages/ # Shared packages/types ``` ## Tooling - **Package manager**: `pnpm` workspace (run from `apps/`, not repo root) - **Backend**: Go with Make (`make -C apps/backend test|lint|build`) - **Launcher**: all four launch modes (`dev`, `start`, `run`, `service`) go through the native Go binary at `apps/backend/bin/kandev`; the launcher package is `apps/backend/internal/launcher`. `make dev` execs a copy of that binary (`bin/kandev-launcher`). - **CLI**: `apps/cli` is the npm-published shim only (`bin/cli.js` + `bin/native-shim.js`); its tests run on the Node built-in test runner (`node --test`). - **Frontend**: Vite/React SPA (`cd apps && pnpm --filter @kandev/web dev|build:vite|lint`; for direct web typecheck use `cd apps/web && pnpm run typecheck`) - **Web-only scripts** live in `apps/web/package.json`; run them from `apps/web` (for example `pnpm run i18n:check` or `pnpm run i18n:ratchet`) or use `pnpm --filter @kandev/web ...`, not from `apps/`. - **Desktop**: Tauri shell (`cd apps && pnpm --filter @kandev/desktop build|e2e`; Rust tests from `apps/desktop/src-tauri`) - **UI**: Shadcn components via `@kandev/ui` - **E2E**: Playwright (`cd apps/web && pnpm e2e:raw`). The `containers` project (gated on `KANDEV_E2E_CONTAINERS=1`, formerly `docker`) covers both the Docker executor and the SSH executor — anything that needs a real Docker daemon on the host lives there. See `apps/web/e2e/README.md`. - **GitHub repo**: `https://github.com/kdlbs/kandev` - **Container image**: `ghcr.io/kdlbs/kandev` (GitHub Container Registry) ### Worktrees and commit hooks A fresh git worktree shares `.git/` but **not** `apps/node_modules/`. The missing install breaks not just the commit-msg hook (`pnpm exec commitlint` → `Command "commitlint" not found` / `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL`) but any pnpm command — `vitest` fails with `Failed to resolve import "vitest"`, eslint similarly. Run `pnpm install --frozen-lockfile` from `apps/` once after creating the worktree, before running tests/lint/commits; subsequent pnpm commands work normally. --- ## Scoped guidance Architecture notes and per-area conventions live alongside the code they describe. Read the file in the directory you're working in: - `apps/backend/AGENTS.md` — Go backend: package structure (incl. `internal/office/` and `internal/agent/runtime/`), key concepts (orchestrator, workflow engine, agent runtime, lifecycle manager), execution flow, provider pattern, backups, testing conventions, Go lint limits. - `apps/backend/internal/agentctl/AGENTS.md` — agentctl HTTP server: route groups, adapter model, ACP protocol. - `apps/backend/internal/agentctl/server/api/AGENTS.md` — reverse-proxy body rewriting (`Accept-Encoding`), iframe-blocking header stripping. - `apps/backend/internal/integrations/AGENTS.md` — adding a new third-party integration (Jira/Linear pattern, both backend and frontend halves). The `/add-integration` skill mirrors this for scaffolding new integrations. - `apps/desktop/AGENTS.md` — Tauri desktop app: runtime resources, Rust process lifecycle, packaging, signing, and smoke tests. - `apps/web/AGENTS.md` — Vite/React SPA frontend: shadcn imports, Go boot-payload hydration, store slice structure (incl. `office`), WS format, component conventions, TS lint limits. --- ## Best Practices ### Engineering Principles - Choose the simplest implementation that fully meets the current requirements. Avoid speculative abstractions, configuration, and indirection. - Grow the system in layers. Start from the smallest version that works end to end, and add each new capability on top of a product that already works. Never trade a working product for unfinished complexity. - Keep components modular and concerns clearly separated. - Prefer established, well-maintained libraries when they reduce overall complexity or improve reliability. Do not reimplement common functionality without a clear reason. - Lean on the dependencies already in the project before writing your own implementation or adding packages. Do not assume a library lacks a capability without checking its documentation and types. - Make architectural decisions for the long term. Do not accept a stopgap that only works for now and is meant to be replaced later. ### Commit Conventions (enforced by CI) Commits to `main` **must** follow [Conventional Commits](https://www.conventionalcommits.org/) (`type: description`). PRs are squash-merged - the PR title becomes the commit, validated by CI. Changelog is auto-generated from these via git-cliff (`cliff.toml`). See `.agents/skills/commit/SKILL.md` for allowed types and examples. The commitlint hook caps the header at **100 characters** (`type(scope): description`). The pre-commit prettier hook reformats staged TS/TSX files, and the `gofmt (backend)` hook can reformat staged Go files; if either hook changes files, the commit fails. Re-stage the reformatted files and create a new commit (don't `--amend`). Before committing, use `git diff --cached --name-only | grep '\.go$' | xargs gofmt -l`; run `gofmt -w` and re-add any files it reports. ### Release & Versioning Stable Kandev releases use one **SemVer** `X.Y.Z` across npm, Homebrew, Scoop, GitHub Releases, Desktop, and containers. Scheduled npm-only Nightlies use `X.Y.(Z+1)-nightly.sha<12-hex>` without moving any Stable channel. Both flows run in `.github/workflows/release.yml`. A Stable release is complete only after the GitHub Release, npm, Homebrew, and Scoop publications succeed and are verified. Full details are in the `/release` skill — load it when cutting a release, changing version channels, or debugging release artifacts. ### Code Quality Static analysis runs in CI and pre-commit. Each subtree has its own thresholds: - Go limits: see `apps/backend/AGENTS.md` (and `apps/backend/.golangci.yml`). - TypeScript limits: see `apps/web/AGENTS.md` (and `apps/web/eslint.config.mjs`). When you hit a limit: extract a helper function, custom hook, or sub-component. Prefer composition over growing a single function. ### Testing Every code change must include tests for new or changed logic. Backend: `*_test.go` files alongside the source. Frontend: `*.test.ts` files for utility functions, hooks, API clients, and store slices. Exceptions: config files, generated code, React component markup. Use `/tdd` for test-driven development. ### Internationalization The web UI is localized with i18next (`namespace:key`). **All new user-facing copy must go through `t()` / ``** — never a hardcoded literal — regardless of which directory you are in. This is enforced, not just requested. Two ratchets, both of which only tighten: - **New code, everywhere** — `pnpm run i18n:ratchet` (pre-commit + CI) fails on a hardcoded string in a file you added, or on a line you changed, regardless of directory. Untouched lines are never judged, so you are never asked to migrate code you did not write. - **Listed paths, whole file** — `i18next/no-literal-string` is a lint error for the paths in `i18nGuardFiles` (`apps/web/eslint.i18n.options.mjs`). The migration of existing strings is **complete**, and the list covers every area it touched; append a path in the same PR that adds it or externalizes one still off the list. Never remove an entry to make a build pass — a check rejects that. Three rules that cause silent, hard-to-find bugs when broken: never translate a string compared with `===` (type-to-confirm tokens become impossible to type); never call `t()` at module scope (it freezes at the boot locale and the pseudo-locale cannot detect it); never pass an English plural ending as a value — use `count` with `_one`/`_other`. `pnpm run i18n:check` enforces the last two. A clean lint is not proof a file is done: the rule only sees literals in JSX. The positions it cannot inspect — SCREAMING_CASE config tables, plain `.ts` helpers, parameter defaults, toast and setter arguments — are gated by `scripts/check-nonjsx-copy.mjs`, which runs inside `pnpm run i18n:check` and the new-code ratchet. Mark a genuine non-string (a persisted value, an agent-facing prompt, a `===` token) with `// i18n-exempt: `; an unexplained marker fails. The pseudo-locale (Settings → General → Appearance, dev/e2e builds) is still the completeness check for copy no literal scan can see. Full guide: [`docs/i18n.md`](docs/i18n.md). UI punctuation is plain and intentional: do not use the Unicode em dash (U+2014) in user-facing copy or locale values. Use a period, colon, comma, semicolon, or parentheses instead. This applies to English, pseudo, and translated catalogs, rendered fallback strings. `pnpm run i18n:check` enforces it. Historical `CHANGELOG.md` entries are excluded because the changelog is generated release history and remains immutable. ### Knowledge - **Public docs:** Website-ready user documentation lives in `docs/public/**`. Use `/docs-maintainer` when a change affects CLI commands, config keys, install/deploy flows, workflows, executors, public APIs, screenshots, or user-facing terminology. - **Specs:** Feature specs live in `docs/specs//spec.md` — the durable "what & why" of a feature, written before coding. Use `/spec` to write or update a spec. See `docs/specs/INDEX.md`. - **Decisions:** Architecture decisions are recorded in `docs/decisions/`. Read `docs/decisions/INDEX.md` for an overview. When making significant architectural choices, create a new ADR via `/record decision`. - **Plans:** Implementation plans are generated from specs via `/plan` and committed under `docs/plans//plan.md`, with individual sibling task files named `docs/plans//task--.md`. Specs are the living requirements; plans and task files are implementation records for the current buildout. ### Plan Implementation - Specs, plans, and task files are the durable source of implementation scope, dependency order, and task-level validation. Keep their statuses and recorded command results accurate. The feature and fix skills define the workflow. ### Observability - In dev mode (`KANDEV_MOCK_AGENT=true` or `debug.pprofEnabled`), `/debug/vars` exposes the stdlib expvar handler. Office provider-routing metrics live under `routing_*` (route attempts, fallbacks, parked runs, provider degraded/recovered counters). The metrics are also still emitted as structured `routing.metric.*` zap logs for human debugging. - ADR 0015's step-completion-signal telemetry lives under `workflow_*`: `workflow_step_completion_signal_received_total` (`internal/workflow/signalmetrics/`), labelled by `source` and `agent_type`, counts accepted `step_complete_kandev` signals. Its separate `workflow_step_completion_signal_fallback_used_total` counter is labelled by `agent_type` and counts manual fallback uses. The fallback button does not have a production increment site yet, so the counter remains zero until that UI ships. ### GitHub Operations Skills use `gh` CLI by default. If a `gh` command fails (not installed, not authenticated, etc.), use whatever GitHub tools are available in the environment (MCP GitHub tools, API tools, etc.) to accomplish the same operation. The goal is the same — the tool may differ. For multiline Markdown issue or PR bodies, write the body to a file and pass it with the relevant `gh ... --body-file ` option. Do not send escaped newlines through `--body`; GitHub will render them literally. For PR review/fixup workflows, prefer the repo helpers before manually querying GitHub/GraphQL: `scripts/pr-state --summary ` for checks and unresolved-thread state, `scripts/pr-state --comment ` for a full review-comment body, `scripts/pr-resolve list ` for actionable unresolved review threads, and `scripts/pr-resolve reply ""` to reply, resolve, and react in one call. When a Kandev system message references an MCP tool that is not visible in the active tool list, use the runtime's tool discovery mechanism, such as `tool_search` when available, before falling back to a less specific workflow. Some task messaging and platform helpers are exposed on demand. ### Single-Session Model Workflow The user-started primary session owns durable artifacts, integration judgment, and user communication. Platform-provided investigation and explorer agents remain available. Launch planned native implementation subagents only after the user explicitly authorizes them; this repository does not prescribe their roles or model tiers. The read-only `pr-poller` is the sole repository-defined exception: use it only after the user explicitly asks to wait for or monitor PR updates. Use the user's strong model for specs, plans, task files, and high-risk design. When the user asks to proceed with feature planning, produce the spec, plan, and task files as one design package; pause after a spec only when the user explicitly requests spec review or a material open question prevents planning. At the completed-plan checkpoint, return control with a concise handoff. Do not call `ask_user_question_kandev` (or an equivalent approval prompt) to ask the user to approve the package or switch models. The user reviews the files, switches the main session if desired, and sends a later explicit implementation request. Detailed feature, fix, validation, and delegation routing lives in the relevant skills, especially `planner-orchestration`. Workflow-generated phase text such as `[IMPROVE PHASE]`, "implement the requested change", TDD checklists, verification commands, or commit steps does not opt a feature or behavior-changing fix out of spec-driven development. If the request neither references an existing reviewed package nor explicitly asks to implement a package created during a prior design turn, run `spec-driven-development` through the plan/task checkpoint and stop before production or permanent test changes. End that turn at the design-package handoff; do not ask for approval or a model switch. A generic implementation envelope is not an explicit opt-out; the user must either explicitly ask to implement the created package in a later turn or explicitly say to skip planning. ### Kandev Task Creation Use Kandev task/session MCP APIs only when the user explicitly asks to create or manage persistent Kandev platform tasks or sessions. Never use `create_task_kandev`, `spawn_session_kandev`, or `message_task_kandev` as a repository-work mechanism or fallback. When the user explicitly requests related Kandev follow-up work, use `create_task_kandev` with `parent_id: "self"`. That preserves workspace, workflow, repository, agent profile, and executor context from the current task. For genuinely unrelated top-level tasks, do not rely on workspace defaults when the user expects continuity; explicitly preserve the current task's `agent_profile_id` / `executor_profile_id`, or ask if the intended profile is ambiguous. When the user requests a persistent remediation task discovered during PR review that must start after merge, create it with `parent_id: "self"`, `workspace_mode: "new_workspace"`, and the reviewed PR's base branch; otherwise a same-repository subtask inherits the reviewed branch. Set `start_agent: false` when the follow-up is intentionally queued until merge. ### Third-party integrations Jira and Linear are the model (per-workspace credentials, 90s auth-health poller via `internal/integrations/healthpoll`, settings page with status banner). New integrations should **reuse the shared shapes** rather than copying either. Full layout, file conventions, and Jira-vs-Linear divergence notes in `apps/backend/internal/integrations/AGENTS.md` and the `/add-integration` skill — load either when scaffolding a new integration. ### Kandev plugins Production Kandev plugins live in dedicated repositories, not in this monorepo. Start at the [canonical plugin authoring guide](docs/public/plugins-authoring.md), then use `/create-kandev-plugin` for plugin creation, modification, bug fixes, packaging, release, and marketplace work. Official plugins use public `kdlbs/kandev-plugin-` repositories and start from [`kdlbs/kandev-plugin-template`](https://github.com/kdlbs/kandev-plugin-template). The recommended workflow is: choose recipe → edit manifest → implement → validate → package → smoke test. When the user explicitly requests a persistent Kandev task, attach the plugin repository through that task's workflow; otherwise do not use Kandev task/session APIs as repository-work mechanisms. Contract authority is intentionally split by implementation boundary: frontend authors use [`docs/plans/plugins/PLUGIN-API.md`](docs/plans/plugins/PLUGIN-API.md) plus [`apps/web/lib/plugins/types.ts`](apps/web/lib/plugins/types.ts), with concrete Host UI exports in `apps/web/lib/plugins/host-api.ts`; backend authors use `apps/backend/pkg/pluginsdk`, the wire contract in `apps/backend/proto/kandev/plugin/v1/plugin.proto`, and manifest/package rules in `apps/backend/internal/plugins/manifest` and `apps/backend/internal/plugins/pkgtar`. The in-tree fixture under `apps/backend/cmd/plugin-fixture` is test support, not a production plugin starter. ### Runtime profiles (prod / dev / e2e) **`profiles.yaml` at the repo root** is the single source of truth for env-driven runtime defaults — feature flags, mock providers (agent / GitHub / Jira / Linear), debug switches, and e2e tuning knobs. The backend embeds it (`//go:embed` via `apps/backend/internal/profiles/`) and at startup calls `profiles.ApplyProfile()` to write the matching profile's env vars onto its own process, *only when each var is not already set* — so launchers, shells, and per-spec overrides still win. Runtime feature toggles add a SQLite-backed override tier managed through `Settings > System > Feature Toggles`. Effective values use this precedence: explicit environment variable > SQLite override > profile default. The typed runtime flag registry lives in `apps/backend/internal/runtimeflags/registry.go`; each registration owns the public metadata, environment variable, config reader, and config applier. Do not add parallel per-flag maps or switches. Profile selection: `KANDEV_E2E_MOCK=true` → `e2e`, `KANDEV_DEBUG_DEV_MODE=true` or `KANDEV_DEBUG_PPROF_ENABLED=true` → `dev`, otherwise `prod`. The Go dev launcher (`apps/backend/internal/launcher/dev.go`) and `apps/web/e2e/fixtures/backend.ts` set only the selector — they no longer hardcode the underlying values. For any task that adds, rolls out, promotes, graduates, or removes a release toggle, use `/runtime-feature-flags`. That skill contains the file-by-file checklist, disabled-path requirements, test commands, promotion procedure, and retired-identity removal steps; do not rely on an agent discovering an ADR or public docs. In brief: merge risky features off in every shipped profile, enable a selected install with an admin override or explicit environment, restart when required by registry metadata, then test. Change `prod:` to `"true"` for the all-user release while retaining the registry entry as a kill-switch. Remove the live flag after the feature is permanent, move its key and environment variable to the append-only retired identities in `runtimeflags/registry.go`, and never reuse either identity. Completeness tests cover the registry/profile/frontend contracts. Runtime overrides and restart support are documented in `docs/decisions/0018-runtime-settings-overrides.md` and `docs/decisions/0019-restart-supervisor.md`. --- ## Maintaining This File This file is read by AI coding agents (Claude Code via `CLAUDE.md` symlink, Codex via `AGENTS.md`). If your changes make any section of this file outdated or inaccurate - e.g., you add/remove/rename packages, change architectural patterns, add new adapters, modify store slices, or change conventions - **update the relevant sections of this file as part of the same PR**. Keep descriptions concise and factual. Do not add speculative or aspirational content. When a change is scoped to a single subtree, update the scoped `AGENTS.md` instead of (or in addition to) this root file. See the "Scoped guidance" pointers at the top. --- ## Remote cloud environment For developing in ephemeral cloud VMs (Cursor Cloud, Codex, GitHub Codespaces, etc.), see [`docs/remote-cloud-environment.md`](docs/remote-cloud-environment.md) — covers runtime requirements, generated-file gotchas, dev-mode setup, key commands, and Firecracker-specific caveats.