Files
kandev/AGENTS.md
T
Carlos Florêncio 8b8f8ff20b feat: refactor main, add providers (#46)
* feat: add providers

* feat: further refactor internal packages
2026-01-19 21:26:29 +00:00

8.5 KiB

Kandev Engineering Guide

Purpose: This document captures up-to-date architecture notes, agent protocols, and frontend data-fetching patterns for Kandev.

Related: ARCHITECTURE.md, 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:

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:
{
  "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