# Kandev Engineering Guide > **Purpose**: This document captures up-to-date architecture notes, agent protocols, and frontend data-fetching patterns for Kandev. > > **Related**: [ARCHITECTURE.md](ARCHITECTURE.md), [docs/asyncapi.yaml](docs/asyncapi.yaml) ## Repo Layout - `apps/backend/`: Go backend (WebSocket gateway, orchestrator, agent lifecycle, agentctl build). - `apps/web/`: Next.js frontend (SSR + WS + Zustand store). - `apps/packages/`: Shared packages/types. - `apps/landing/`: Marketing/landing site that shares UI components with the web app. - `apps/backend/dockerfiles/`: Agent container images (e.g., augment-agent). ## Tooling and Workspace - **Package manager**: `pnpm` workspace. - **Workspace root**: repository root. - **Web app**: `apps/web` (Next.js, client + server components). - **Backend**: `apps/backend` (Go services, agent lifecycle, ws). - **UI**: Shadcn-based components are wrapped under `@kandev/ui`. Common commands: ```bash pnpm -C apps/web dev pnpm -C apps/web lint pnpm -C apps/web typecheck make -C apps/backend test make -C apps/backend lint ``` ## Backend Architecture (High Level) - **Transport**: WebSocket for realtime updates; HTTP for CRUD + SSR fetches. - **Gateway**: Single WS endpoint at `ws://localhost:8080/ws`. - **Orchestrator**: Starts/stops/resumes tasks, manages sessions. - **Agent lifecycle**: - Supports Docker and standalone. - Creates agentctl instances for workspace access (shell, git, file ops). - Starts agent subprocesses explicitly. - **agentctl**: - HTTP server for per-agent instance management. - Uses ACP (JSON-RPC 2.0 over stdin/stdout) to talk to agent process. - Streams ACP updates and workspace outputs to backend. ### Provider Pattern (Backend) - **Goal**: Keep `cmd/kandev` thin by constructing dependencies via package-local providers. - **Location**: Providers live next to the implementation (e.g. `internal/events/provider.go`, `internal/task/repository/provider.go`). - **API contract**: - Each package exposes `Provide(...)` that returns a pointer to the concrete implementation (not an interface), a `cleanup func() error`, and an `error`. - Example signature: `func Provide(cfg *config.Config, log *logger.Logger) (*impl, func() error, error)` - **Usage**: - External packages depend on the public interface type, but composition uses the concrete impl. - Repositories expose a public `Repository` interface, while the implementation struct stays unexported. - Call `cleanup()` during shutdown; providers should not own shared resources unless they created them. ### Runtime / agentctl Flow (Simplified) ``` Client (WS) Backend Runtime agentctl Agent | orchestrator.start | | | | |------------------->| LaunchAgent | Create instance | | | |--------------------->|------------------->| | | | StartAgentProcess | Configure/Start |----> agent process | | | ACP calls | |<--- ACP messages | |<-------------------| WS notifications | | | ``` ### Protocols - **ACP**: JSON-RPC 2.0 over stdin/stdout (agent <-> agentctl <-> backend). - **WS Message Format**: ```json { "id": "uuid", "type": "request|response|notification|error", "action": "action.name", "payload": { "...": "..." }, "timestamp": "2026-01-10T12:00:00Z" } ``` - See `docs/asyncapi.yaml` for WS actions and payloads. ## Frontend Architecture ### Key Principles - **Store is the source of truth** (Zustand store slices). - **SSR preloads data** into the store via `StateHydrator`. - **Components read from store only**; avoid ad-hoc fetches in UI components. - **Reusable components** preferred; split large pages into smaller components. - **Data fetching should live where it is required**; avoid leaking fetches into parent controllers. ### Data Fetching and SSR - SSR fetch in layout/page, map to store shape, hydrate via `StateHydrator`. - WebSocket events update store slices for real-time sync. - Only re-fetch when WS cannot deliver full payloads (document why). ### Subscription Strategy - Components subscribe to the channels they need (task, session, repo), using hooks. - WebSocket client deduplicates subscriptions with reference counting. - Reconnects re-subscribe all active channels automatically. - Event handlers update store slices; components only read from store. - URL params set active task/session in store for consistent view state. **Why This Works** - Components declare their dependencies explicitly. - Subscription cleanup is automatic on unmount. - WebSocket client guarantees only one real subscription per channel. - Store updates flow globally, components re-render only on relevant slices. ### Three-Layer Model ``` Layer 1: WebSocket Client (connection + dedup) Layer 2: Event Handlers (route WS payloads to store) Layer 3: Components (subscribe + render) ``` ### Store Structure - Task/session scoped data is keyed in store: - `messages.bySession[sessionId]` - `messages.metaBySession[sessionId]` - `gitStatus` keyed by `sessionId` - `shell.outputs` keyed by `sessionId` - Active selection state lives in store: - `tasks.activeTaskId` - `tasks.activeSessionId` ### Custom Hooks Pattern - Hooks own subscriptions and return data from store: - `useSessionMessages(sessionId)` → messages + loading/meta - `useLazyLoadMessages(sessionId)` → pagination - `useSessionGitStatus(sessionId)` → git state - `useSessionShellOutput(sessionId)` → shell output - Hooks subscribe on mount, cleanup on unmount; no manual dedup in components. ### Page-Level Responsibilities - SSR fetches initial task/session + board snapshot, hydrates store. - Page reads URL `sessionId`, resolves task via session, and calls `setActiveSession(taskId, sessionId)` after load. - Page coordinates task list + session list loading (e.g., `listTaskSessions`). - Components render from store and trigger hooks for live updates. ## Agents and ACP ### ACP Basics - **Transport**: JSON-RPC 2.0 over stdin/stdout. - **Format**: newline-delimited JSON. - **Direction**: request/response + notifications. **Backend → Agent (Requests)** - `initialize` - `session/new` - `session/load` - `session/prompt` - `session/cancel` **Agent → Backend (Notifications)** - `session/update` (progress/content/tool calls/complete/error) ### agentctl HTTP (Internal) - `/health` GET - `/api/v1/status` GET - `/api/v1/start` POST - `/api/v1/stop` POST - `/api/v1/acp/*` (initialize, session/new, session/load, prompt, stream) - `/api/v1/shell/*` (status, stream, buffer) - `/api/v1/workspace/*` (git status, file changes) ### Agent Types - Augment agent lives at `apps/backend/dockerfiles/augment-agent/`. - Registry defaults in `apps/backend/internal/agent/registry/defaults.go`. ## Frontend UI System (Shadcn) - Prefer `@kandev/ui` components over ad-hoc HTML. - Reuse components; avoid duplicating UI patterns. - Split large pages into smaller components; keep layout vs data vs presentation separated. ## Best Practices - Prefer store-driven state over local fetches. - Prefer reusable components over duplication; split large pages into smaller child components. - Keep data fetching scoped to the component that needs it; avoid leaking fetches into parent controllers. - Avoid WS request/response inside UI components when store actions can handle it. - Keep SSR fetches minimal and map responses into store-friendly shapes. - Component subscriptions should be local; WS client should dedupe. - Keep agents logging to stderr; stdout only for ACP. ## agentctl Server Architecture (Adapter Model) The agentctl server now uses a protocol adapter layer to support multiple agent CLIs while keeping a consistent HTTP + WebSocket surface for the backend. - `server/adapter/` defines the `AgentAdapter` interface and normalized `SessionUpdate` payloads. - `ACPAdapter` uses the ACP JSON-RPC protocol over stdio (`server/adapter/acp_adapter.go`). - `CodexAdapter` supports Codex-style JSON-RPC over stdio (`server/adapter/codex_adapter.go`). - `process.Manager` owns the subprocess, wires stdin/stdout into the selected adapter, and forwards normalized updates to API streams. - `api/acp.go` exposes protocol-agnostic endpoints (`/api/v1/acp/*`) that delegate to the adapter. - `instance/` manages per-agent instance servers on dedicated ports (control server creates instances; each instance hosts its own API server). --- **Last Updated**: 2026-01-19