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

218 lines
8.5 KiB
Markdown

# 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