10 KiB
Kandev Engineering Guide
Purpose: Architecture notes, key patterns, and conventions for LLM agents working on Kandev.
Repo Layout
kandev-3/
├── apps/
│ ├── backend/ # Go backend (orchestrator, lifecycle, agentctl, WS gateway)
│ ├── web/ # Next.js frontend (SSR + WS + Zustand)
│ ├── packages/ # Shared packages/types
│ └── landing/ # Marketing site (shares UI with web)
Tooling
- Package manager:
pnpmworkspace (root) - Backend: Go with Make (
make -C apps/backend test|lint|build) - Frontend: Next.js (
pnpm -C apps/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)
├── internal/
│ ├── agent/
│ │ ├── lifecycle/ # Agent instance management (see below)
│ │ ├── registry/ # Agent type registry and defaults
│ │ ├── runtime/ # Runtime name constants
│ │ └── mcpconfig/ # MCP server configuration
│ ├── agentctl/
│ │ ├── client/ # HTTP client for talking to agentctl
│ │ └── server/ # agentctl HTTP server
│ │ ├── api/ # HTTP endpoints
│ │ ├── instance/ # Multi-instance management
│ │ ├── process/ # Agent subprocess management
│ │ └── adapter/ # Protocol adapters (ACP, Codex, REST, MCP)
│ ├── orchestrator/ # Task execution coordination
│ │ ├── executor/ # Launches agents via lifecycle manager
│ │ ├── scheduler/ # Task scheduling
│ │ ├── queue/ # Task queue
│ │ └── watcher/ # Event handlers
│ ├── task/
│ │ ├── models/ # Task, Session, Executor, Message models
│ │ ├── service/ # Task business logic
│ │ └── repository/ # Database access (SQLite)
│ ├── worktree/ # Git worktree management for workspace isolation
│ └── events/ # Event bus for internal pub/sub
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- central coordinator for agent lifecycleRuntimeinterface - abstracts execution environment (Docker, Standalone)ExecutionStore- thread-safe in-memory execution trackingSessionManager- ACP session initialization and resumeStreamManager- WebSocket stream connections to agentctlEventPublisher- publishes events to the event busContainerManager- Docker container operations
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:
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 resumeExecutorRunning- 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:
AgentAdapterinterface definesStart(),Stop(),Prompt(),Cancel()ACPAdapter- ACP JSON-RPC over stdioCodexAdapter- Codex-style JSON-RPCprocess.Managerowns subprocess, wires stdio to adapter
ACP Protocol
JSON-RPC 2.0 over stdin/stdout between agentctl and agent process.
Backend -> Agent (Requests)
initialize- handshakesession/new- create new sessionsession/load- resume existing sessionsession/prompt- send user messagesession/cancel- cancel current operation
Agent -> Backend (Notifications)
session/updatewith types:message_chunk,tool_call,tool_update,complete,error,permission_request,context_window
Frontend Architecture
UI Components
Shadcn Components: Import from @kandev/ui package:
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 (~370 lines) # 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
│ └── ui/ # preview, connection, active state
├── hydration/ # SSR merge strategies
└── selectors/ # Memoized selectors (future)
hooks/domains/{kanban,session,workspace,settings}/ # Domain-organized hooks
lib/api/domains/{kanban,session,workspace,settings,process}-api.ts # API clients
Key State Paths:
messages.bySession[sessionId],shell.outputs[sessionId],gitStatus.bySessionId[sessionId]tasks.activeTaskId,tasks.activeSessionId,workspaces.activeIdrepositories.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}. See docs/asyncapi.yaml for all actions.
Best Practices
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/tablewith TanStack Table; use shadcn Pagination components - Only create custom components when shadcn doesn't provide what's needed
- Import shadcn components from
- 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-pointerclass
Plan Implementation
- After implementing a plan, run
make typecheck test lintto verify the changes
Last Updated: 2026-02-01