Files
kandev/AGENTS.md
T
2026-02-01 19:07:52 +00:00

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: pnpm workspace (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 lifecycle
  • Runtime interface - abstracts execution environment (Docker, Standalone)
  • ExecutionStore - thread-safe in-memory execution tracking
  • SessionManager - ACP session initialization and resume
  • StreamManager - WebSocket stream connections to agentctl
  • EventPublisher - publishes events to the event bus
  • ContainerManager - 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 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()
  • ACPAdapter - ACP JSON-RPC over stdio
  • CodexAdapter - Codex-style JSON-RPC
  • process.Manager owns subprocess, wires stdio to adapter

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:

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.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}. 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/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

Last Updated: 2026-02-01