Co-authored-by: Kandev Agent <agent@kandev.dev>
16 KiB
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/ # CLI tool (TypeScript)
└── packages/ # Shared packages/types
Tooling
- Package manager:
pnpmworkspace (run fromapps/, not repo root) - Backend: Go with Make (
make -C apps/backend test|lint|build) - Frontend: Vite/React SPA (
cd apps && pnpm --filter @kandev/web dev|build:vite|lint; for direct web typecheck usecd apps/web && pnpm run typecheck) - Desktop: Tauri shell (
cd apps && pnpm --filter @kandev/desktop build|e2e; Rust tests fromapps/desktop/src-tauri) - UI: Shadcn components via
@kandev/ui - E2E: Playwright (
cd apps/web && pnpm e2e). Thecontainersproject (gated onKANDEV_E2E_CONTAINERS=1, formerlydocker) covers both the Docker executor and the SSH executor — anything that needs a real Docker daemon on the host lives there. Seeapps/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/andinternal/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-integrationskill 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
Commit Conventions (enforced by CI)
Commits to main must follow Conventional Commits (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 also reformats staged TS/TSX files - if it does, the commit fails. Re-stage the reformatted files and create a new commit (don't --amend).
Release & Versioning
Kandev uses a single SemVer X.Y.Z across npm, Homebrew, and GitHub release; release flow runs entirely in CI via .github/workflows/release.yml. Full details in the /release skill — load it when cutting a release 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(andapps/backend/.golangci.yml). - TypeScript limits: see
apps/web/AGENTS.md(andapps/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() / <Trans> — 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. - Migrated paths, whole file —
i18next/no-literal-stringis a lint error for the paths ini18nGuardFiles(apps/web/eslint.i18n.options.mjs). The migration of existing strings proceeds one directory per PR: when you migrate one, append it to that list in the same PR. 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, and
it skips anything assigned to a SCREAMING_CASE identifier — so a
const ROWS = [{ label: "Disk usage" }] config table passes silently. Review
those by eye. The pseudo-locale (Settings → General → Appearance, dev/e2e builds)
is the completeness check. Full guide: docs/i18n.md.
Knowledge
- Public docs: Website-ready user documentation lives in
docs/public/**. Use/docs-maintainerwhen 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/<slug>/spec.md— the durable "what & why" of a feature, written before coding. Use/specto write or update a spec. Seedocs/specs/INDEX.md. - Decisions: Architecture decisions are recorded in
docs/decisions/. Readdocs/decisions/INDEX.mdfor an overview. When making significant architectural choices, create a new ADR via/record decision. - Plans: Implementation plans are generated from specs via
/planand committed underdocs/plans/<slug>/plan.md, with individual sibling task files nameddocs/plans/<slug>/task-<NN>-<short-slug>.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=trueordebug.pprofEnabled),/debug/varsexposes the stdlib expvar handler. Office provider-routing metrics live underrouting_*(route attempts, fallbacks, parked runs, provider degraded/recovered counters). The metrics are also still emitted as structuredrouting.metric.*zap logs for human debugging.
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 <path> 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 <PR> for checks and unresolved-thread state, scripts/pr-state --comment <comment_id> for a full review-comment body, scripts/pr-resolve list <PR> for actionable unresolved review threads, and scripts/pr-resolve reply <PR> <comment_id> <thread_id> "<body>" 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, ask the user to manually switch the main
session to a lower-cost implementation model. 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 does not reference an approved spec, plan, and task file, run
spec-driven-development through the plan/task checkpoint and stop before
production or permanent test changes. Continue directly to implementation only
when approved task artifacts already exist, or when the user explicitly says
to skip planning; a generic implementation envelope is not that explicit
opt-out.
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. Official plugins use public kdlbs/kandev-plugin-<slug> repositories and start from kdlbs/kandev-plugin-template. Use /create-kandev-plugin for plugin creation, modification, bug fixes, packaging, release, and marketplace work. When a Kandev Worktree task needs the plugin repository, attach it with add_branch_to_task_kandev instead of cloning inside this worktree. Keep host API, SDK, loader, registry, and the in-tree test fixture in this repository.
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; add or update registry metadata when exposing a flag in the UI.
Profile selection: KANDEV_E2E_MOCK=true → e2e, KANDEV_DEBUG_DEV_MODE=true → dev, otherwise prod. apps/cli/src/dev.ts and apps/web/e2e/fixtures/backend.ts set only the selector — they no longer hardcode the underlying values.
To flip a feature on for every user: change its prod: to "true" in profiles.yaml. To add a new feature flag: 1 line in profiles.yaml + 1 FeaturesConfig field + the runtime flag registry entry when it should be user-toggleable + the gate at the call site + the frontend additions (FeatureFlags type, useFeature checks, notFound() from a server-side layout). Full pattern in docs/decisions/0007-runtime-feature-flags.md; 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 — covers runtime requirements, generated-file gotchas, dev-mode setup, key commands, and Firecracker-specific caveats.
Last Updated: 2026-07-27