# 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/ # Next.js frontend (SSR + WS + Zustand) ├── cli/ # CLI tool (TypeScript) └── 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`) - **Frontend**: Next.js (`cd apps && pnpm --filter @kandev/web dev|lint|typecheck`) - **UI**: Shadcn components via `@kandev/ui` --- ## Backend Architecture ### Package Structure ``` apps/backend/ ├── cmd/ │ ├── kandev/ # Main backend binary entry point │ ├── agentctl/ # Agentctl binary (runs inside containers or standalone) │ └── mock-agent/ # Mock agent for testing ├── internal/ │ ├── agent/ │ │ ├── lifecycle/ # Agent instance management (see below) │ │ ├── agents/ # Agent type implementations │ │ ├── controller/ # Agent control operations │ │ ├── credentials/ # Agent credential management │ │ ├── discovery/ # Agent discovery │ │ ├── docker/ # Docker-specific agent logic │ │ ├── dto/ # Agent data transfer objects │ │ ├── handlers/ # Agent event handlers │ │ ├── registry/ # Agent type registry and defaults │ │ ├── runtime/ # Runtime name constants │ │ ├── settings/ # Agent settings │ │ └── mcpconfig/ # MCP server configuration │ ├── agentctl/ │ │ ├── client/ # HTTP client for talking to agentctl │ │ └── server/ # agentctl HTTP server │ │ ├── api/ # HTTP endpoints │ │ ├── adapter/ # Protocol adapters (ACP, Codex, Copilot, Amp) │ │ ├── instance/ # Multi-instance management │ │ ├── mcp/ # MCP server integration │ │ ├── process/ # Agent subprocess management │ │ └── shell/ # Shell session management │ ├── orchestrator/ # Task execution coordination │ │ ├── executor/ # Launches agents via lifecycle manager │ │ ├── handlers/ # Orchestrator event handlers │ │ ├── messagequeue/ # Message queue for agent prompts │ │ ├── queue/ # Task queue │ │ ├── scheduler/ # Task scheduling │ │ └── watcher/ # Event handlers │ ├── task/ │ │ ├── controller/ # Task HTTP/WS controllers │ │ ├── dto/ # Task data transfer objects │ │ ├── events/ # Task event types │ │ ├── handlers/ # Task event handlers │ │ ├── models/ # Task, Session, Executor, Message models │ │ ├── repository/ # Database access (SQLite) │ │ └── service/ # Task business logic │ ├── analytics/ # Usage analytics │ ├── clarification/ # Agent clarification handling │ ├── common/ # Shared utilities │ ├── db/ # Database initialization │ ├── debug/ # Debug tooling │ ├── editors/ # Editor integration │ ├── events/ # Event bus for internal pub/sub │ ├── gateway/ # WebSocket gateway │ ├── integration/ # External integrations │ ├── lsp/ # LSP server │ ├── mcp/ # MCP protocol support │ ├── notifications/ # Notification system │ ├── persistence/ # Persistence layer │ ├── prompts/ # Prompt management │ ├── sysprompt/ # System prompt injection │ ├── user/ # User management │ ├── workflow/ # Workflow engine │ └── worktree/ # Git worktree management for workspace isolation ``` ### Key Concepts **Orchestrator** coordinates task execution: - Receives task start/stop/resume requests via WebSocket - Delegates to lifecycle manager for agent operations - Handles event-driven state transitions - Located in `internal/orchestrator/` **Lifecycle Manager** (`internal/agent/lifecycle/`) manages agent instances: - `Manager` (`manager.go`) - central coordinator for agent lifecycle - `Runtime` interface (`runtime.go`) - abstracts execution environment (Docker, Standalone, Remote Docker) - `ExecutionStore` (`execution_store.go`) - thread-safe in-memory execution tracking - `session.go` - ACP session initialization and resume - `streams.go` - WebSocket stream connections to agentctl - `events.go` - publishes events to the event bus - `container.go` - Docker container operations - `process_runner.go` - agent process launch and management - `profile_resolver.go` - resolves agent profiles/settings **agentctl** is an HTTP server that: - Runs inside Docker containers or as standalone process - Manages agent subprocess via stdin/stdout (ACP protocol) - Exposes workspace operations (shell, git, files) - Supports multiple concurrent instances on different ports **Executor Types** (database model): - `local_pc` - Standalone process on host ✅ - `local_docker` - Docker container on host ✅ - `remote_docker`, `remote_vps`, `k8s` - Planned **Runtime Interface** implements execution: ```go type Runtime interface { Name() runtime.Name HealthCheck(ctx context.Context) error CreateInstance(ctx context.Context, req *RuntimeCreateRequest) (*RuntimeInstance, error) StopInstance(ctx context.Context, instance *RuntimeInstance, force bool) error RecoverInstances(ctx context.Context) ([]*RuntimeInstance, error) } ``` ### Execution Flow ``` Client (WS) Orchestrator Lifecycle Manager Runtime agentctl | | | | | | task.start | | | | |--------------->| LaunchAgent() | | | | |--------------------->| CreateInstance() | | | | |----------------->| (container) | | | | |--------------->| | | StartAgentProcess() | | | | |--------------------->| ConfigureAgent() | | | | |-----------------------------> | | | | Start() | | | | |-----------------------------> | | | | | agent proc | | | |<---- stream updates (WS) ---------| |<--- WS events -| | | | ``` **Session Resume:** - `TaskSession.ACPSessionID` - stored ACP session ID for resume - `ExecutorRunning` - tracks active executor state (container ID, port, worktree) - On backend restart: `RecoverInstances()` finds running containers, reconnects streams **Provider Pattern:** Packages expose `Provide(cfg, log) (*impl, cleanup, error)` for DI. Returns implementation, cleanup function, and error. Cleanup called during graceful shutdown. **Worktrees:** `internal/worktree/Manager` provides workspace isolation. Each session can have its own worktree (branch) to prevent conflicts between concurrent agents. --- ## agentctl Server ### HTTP Endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/health` | GET | Health check | | `/api/v1/status` | GET | Agent status | | `/api/v1/configure` | POST | Configure agent before start | | `/api/v1/start` | POST | Start agent subprocess | | `/api/v1/stop` | POST | Stop agent | | `/api/v1/acp/*` | Various | ACP protocol (initialize, session/*, prompt) | | `/api/v1/shell/*` | Various | Shell operations | | `/api/v1/workspace/*` | Various | Git status, file changes | | `/ws/updates` | WS | Agent event stream | | `/ws/workspace` | WS | Workspace event stream (git, files, shell) | ### Adapter Model Protocol adapters normalize different agent CLIs: - `AgentAdapter` interface defines `Start()`, `Stop()`, `Prompt()`, `Cancel()` - `ACP` - ACP JSON-RPC over stdio (Claude Code) - `Codex` - Codex-style JSON-RPC (OpenAI Codex) - `CopilotAdapter` - GitHub Copilot SDK - `AmpAdapter` - Sourcegraph Amp CLI - `process.Manager` owns subprocess, wires stdio to adapter - Factory pattern in `adapter/factory.go` selects adapter by agent type --- ## ACP Protocol JSON-RPC 2.0 over stdin/stdout between agentctl and agent process. **Backend -> Agent (Requests)** - `initialize` - handshake - `session/new` - create new session - `session/load` - resume existing session - `session/prompt` - send user message - `session/cancel` - cancel current operation **Agent -> Backend (Notifications)** - `session/update` with types: `message_chunk`, `tool_call`, `tool_update`, `complete`, `error`, `permission_request`, `context_window` --- ## Frontend Architecture ### UI Components **Shadcn Components:** Import from `@kandev/ui` package: ```typescript import { Badge } from '@kandev/ui/badge'; import { Button } from '@kandev/ui/button'; import { Dialog } from '@kandev/ui/dialog'; // etc... ``` **Do NOT** import from `@/components/ui/*` - always use `@kandev/ui` package. ### Data Flow Pattern (Critical) ``` SSR Fetch -> Hydrate Store -> Components Read Store -> Hooks Subscribe ``` **Never fetch data directly in components.** ### Store Structure (Domain Slices) ``` lib/state/ ├── store.ts # Root composition ├── slices/ # Domain slices │ ├── kanban/ # boards, tasks, columns │ ├── session/ # sessions, messages, turns, worktrees │ ├── session-runtime/ # shell, processes, git, context │ ├── workspace/ # workspaces, repos, branches │ ├── settings/ # executors, agents, editors, prompts │ ├── diff-comments/ # code review diff comments │ └── ui/ # preview, connection, active state ├── hydration/ # SSR merge strategies hooks/domains/{kanban,session,workspace,settings}/ # Domain-organized hooks lib/api/domains/ # API clients ├── kanban-api, session-api, workspace-api, settings-api, process-api ├── plan-api, queue-api, workflow-api, stats-api ├── user-shell-api, debug-api ``` **Key State Paths:** - `messages.bySession[sessionId]`, `shell.outputs[sessionId]`, `gitStatus.bySessionId[sessionId]` - `tasks.activeTaskId`, `tasks.activeSessionId`, `workspaces.activeId` - `repositories.byWorkspace`, `repositoryBranches.byRepository` **Hydration:** `lib/state/hydration/merge-strategies.ts` has `deepMerge()`, `mergeSessionMap()`, `mergeLoadingState()` to avoid overwriting live client state. Pass `activeSessionId` to protect active sessions. **Hooks Pattern:** Hooks in `hooks/domains/` encapsulate WS subscription + store selection. WS client deduplicates subscriptions automatically. ### WS **Format:** `{id, type, action, payload, timestamp}`. --- ## Best Practices ### Code Quality (enforced by linters) Static analysis runs in CI and pre-commit. New code **must** stay within these limits: **Go** (`apps/backend/.golangci.yml` - errors on new code only): - Functions: **≤80 lines**, **≤50 statements** - Cyclomatic complexity: **≤15** · Cognitive complexity: **≤30** - Nesting depth: **≤5** · Naked returns only in functions **≤30 lines** - No duplicated blocks (**≥150 tokens**) · Repeated strings → constants (**≥3 occurrences**) **TypeScript** (`apps/web/eslint.config.mjs` - warnings, will become errors): - Files: **≤600 lines** · Functions: **≤100 lines** - Cyclomatic complexity: **≤15** · Cognitive complexity: **≤20** - Nesting depth: **≤4** · Parameters: **≤5** - No duplicated strings (**≥4 occurrences**) · No identical functions · No unused imports - No nested ternaries **When you hit a limit:** extract a helper function, custom hook, or sub-component. Prefer composition over growing a single function. ### Backend - Provider pattern for DI; stderr for logs, stdout for ACP only - Pass context through chains; event bus for cross-component comm ### Frontend - **Data:** SSR fetch → hydrate → read store. Never fetch in components - **UI Components:** - Import shadcn components from `@kandev/ui`, NOT `@/components/ui/*` - **Always prefer native shadcn components** over custom implementations - Check `apps/packages/ui/src/` for available components (pagination, table, dialog, etc.) - For data tables, use `@kandev/ui/table` with TanStack Table; use shadcn Pagination components - Only create custom components when shadcn doesn't provide what's needed - **Components:** <200 lines, extract to domain components, composition over props - **Hooks:** Domain-organized in `hooks/domains/`, encapsulate subscription + selection - **WS:** Use subscription hooks only; client auto-deduplicates - **Interactivity:** All buttons and links with actions must have `cursor-pointer` class ### Plan Implementation - After implementing a plan, run `make typecheck test lint` to verify the changes --- ## 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. --- **Last Updated**: 2026-02-17