8b8f8ff20b
* feat: add providers * feat: further refactor internal packages
8.5 KiB
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:
pnpmworkspace. - 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/kandevthin 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), acleanup func() error, and anerror. - Example signature:
func Provide(cfg *config.Config, log *logger.Logger) (*impl, func() error, error)
- Each package exposes
- Usage:
- External packages depend on the public interface type, but composition uses the concrete impl.
- Repositories expose a public
Repositoryinterface, 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.yamlfor 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]gitStatuskeyed bysessionIdshell.outputskeyed bysessionId
- Active selection state lives in store:
tasks.activeTaskIdtasks.activeSessionId
Custom Hooks Pattern
- Hooks own subscriptions and return data from store:
useSessionMessages(sessionId)→ messages + loading/metauseLazyLoadMessages(sessionId)→ paginationuseSessionGitStatus(sessionId)→ git stateuseSessionShellOutput(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 callssetActiveSession(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)
initializesession/newsession/loadsession/promptsession/cancel
Agent → Backend (Notifications)
session/update(progress/content/tool calls/complete/error)
agentctl HTTP (Internal)
/healthGET/api/v1/statusGET/api/v1/startPOST/api/v1/stopPOST/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/uicomponents 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 theAgentAdapterinterface and normalizedSessionUpdatepayloads.ACPAdapteruses the ACP JSON-RPC protocol over stdio (server/adapter/acp_adapter.go).CodexAdaptersupports Codex-style JSON-RPC over stdio (server/adapter/codex_adapter.go).process.Managerowns the subprocess, wires stdin/stdout into the selected adapter, and forwards normalized updates to API streams.api/acp.goexposes 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