docs: overhaul public product guides (#1749)

* docs: overhaul public product guides

* fix(docs): address review findings

* docs: clarify validation contracts

* docs: complete validator API contracts

* docs: clarify validator existence check
This commit is contained in:
José Almeida
2026-07-17 11:30:37 +01:00
committed by GitHub
parent 934b713ee9
commit c633b8faa1
66 changed files with 7322 additions and 3096 deletions
+122 -169
View File
@@ -1,239 +1,192 @@
---
title: "Add an Agent CLI"
description: "Add a new ACP adapter or agent CLI integration to Kandev."
description: "Register a local TUI agent or ship a tested built-in passthrough or ACP agent integration."
---
# Adding a New Agent CLI Integration
# Add an Agent CLI
This guide explains how to add support for a new coding agent CLI to Kandev.
Choose the smallest integration that matches the CLI:
Agents are defined entirely in Go. The backend discovers them through a registry, and the frontend picks them up automatically via the API - **no frontend changes are needed**.
| Need | Path |
|---|---|
| Use a local interactive CLI without changing Kandev source | Add a custom TUI agent in Settings |
| Ship a passthrough-only CLI as a built-in | Add a declarative `TUIAgent` and registry entry |
| Show structured chat, tool calls, modes, and resume | Implement a built-in ACP agent |
For a clean reference implementation, see [`gemini.go`](../../apps/backend/internal/agent/agents/gemini.go) (ACP protocol). For a CLI-only TUI agent, see the `TUIAgent` declarative type in [`tui_agent.go`](../../apps/backend/internal/agent/agents/tui_agent.go).
ACP, REST, and MCP are different boundaries. ACP is the only structured agent protocol accepted by the current agentctl adapter factory. REST/WebSocket control agentctl and Kandev. MCP supplies tools to an agent; it is not a runtime adapter.
---
## Register a local TUI agent
## CLI-Only Agent (Passthrough Only) - Recommended for TUI tools
Open **Settings → Agents**, choose **Add TUI Agent**, and provide:
If an agent has a CLI but no structured protocol (no JSON-RPC, no streaming API), use the **`TUIAgent` declarative type**. The agent runs in a terminal session and input/output passes through a WebSocket - users interact with the full agent TUI directly.
- a display name;
- an optional model/profile label;
- a command available on the executor's `PATH`.
This is the simplest path. A complete agent definition is ~20 lines.
Use `{{model}}` in the command to insert the optional model value. Kandev derives a stable slug from the display name, persists the definition, registers it at startup, and creates a default passthrough profile.
### Step 1: Create the agent file
This path is terminal passthrough only. It does not provide structured chat messages, tool-call visibility, ACP capability probing, or Kandev MCP injection. The command is split into an executable and arguments on whitespace; it is not evaluated by a shell, and shell quoting/expansion is not supported.
Create `apps/backend/internal/agent/agents/{agent_id}.go`:
Use a source integration when the agent must ship for every installation, needs logos/discovery/auth/permissions, needs safe structured argument building, or supports ACP.
## Ship a built-in passthrough agent
Definitions live in `apps/backend/internal/agent/agents/`. The declarative `TUIAgent` implements `agents.Agent` plus `agents.PassthroughAgent`, but Kandev classifies it as passthrough-only.
Create `apps/backend/internal/agent/agents/my_agent.go`:
```go
package agents
func NewClaude() *TUIAgent {
func NewMyAgent() *TUIAgent {
return NewTUIAgent(TUIAgentConfig{
AgentID: "claude",
AgentName: "Claude",
Command: "claude",
Desc: "Claude Code - an agentic coding tool by Anthropic.",
// Optional: let users select a model in profile settings.
// The {model} placeholder is replaced with the user's chosen model at launch.
// Omit this if the CLI doesn't accept a model flag.
AgentID: "my-agent",
AgentName: "My Agent",
Command: "my-agent",
Desc: "Run My Agent in terminal passthrough mode.",
ModelFlag: NewParam("--model", "{model}"),
WaitForTerm: true,
})
}
```
Logos are optional - the UI shows a blank placeholder when none is provided. To add them, embed SVGs and pass `LogoLight` / `LogoDark`:
Only set `ModelFlag` when the CLI accepts that flag. `WaitForTerm` is useful for a full-screen TUI that needs an initial terminal resize before process start.
```go
package agents
The exact `TUIAgentConfig` contract is:
import _ "embed"
| Fields | Required | Meaning |
|---|---|---|
| `AgentID`, `AgentName`, `Command`, `Desc` | Yes | Stable lowercase ID, names, executable, and description |
| `Display`, `Order` | No | Alternate display name and list order; defaults are name and 99 |
| `LogoLight`, `LogoDark` | No | Embedded SVG bytes; the UI otherwise shows a placeholder |
| `IdleTimeout`, `BufferMax`, `WaitForTerm` | No | Passthrough buffering and terminal-start behavior |
| `ModelFlag`, `CommandArgs` | No | Typed model parameter and fixed argument tokens |
| `DetectOpts` | No | Installation detection; defaults to `WithCommand(Command)` |
| `Protocol` | No | Runtime metadata, default ACP; it does not turn `TUIAgent` into structured chat |
//go:embed logos/claude_light.svg
var claudeLogoLight []byte
Do not shell-concatenate user values. `CommandArgs` is a token slice and `ModelFlag` uses the typed parameter builder. Detection should inspect known commands/files without executing untrusted content.
//go:embed logos/claude_dark.svg
var claudeLogoDark []byte
`TUIAgentConfig` does not expose a passthrough MCP strategy. If this CLI needs Kandev/profile MCP servers, implement a full agent with an explicit `PassthroughConfig.MCPStrategy`.
func NewClaude() *TUIAgent {
return NewTUIAgent(TUIAgentConfig{
AgentID: "claude",
AgentName: "Claude",
Command: "claude",
Desc: "Claude Code - an agentic coding tool by Anthropic.",
ModelFlag: NewParam("--model", "{model}"),
LogoLight: claudeLogoLight,
LogoDark: claudeLogoDark,
})
}
```
## Ship a structured ACP integration
`TUIAgentConfig` fields (all optional fields have sensible defaults):
Use [`gemini.go`](../../apps/backend/internal/agent/agents/gemini.go) as a current ACP example and [`agent.go`](../../apps/backend/internal/agent/agents/agent.go) as the contract. A full integration implements `agents.Agent`:
| Field | Required | Description |
|-------|----------|-------------|
| `AgentID` | Yes | Unique lowercase identifier (e.g. `"claude"`) |
| `AgentName` | Yes | Display name (e.g. `"Claude"`) |
| `Command` | Yes | Binary name to run (e.g. `"claude"`) |
| `Desc` | Yes | Description shown in the UI |
| `ModelFlag` | No | Model CLI flag, e.g. `NewParam("--model", "{model}")`. Lets users pick a model in profile settings. Omit if the CLI doesn't support model selection. |
| `CommandArgs` | No | Extra args appended after `Command` (e.g. `[]string{"--debug"}`) |
| `WaitForTerm` | No | When `true`, delays process start until the terminal WebSocket connects and sends its first resize event. Use this for TUI apps that need accurate terminal dimensions on startup (e.g. ncurses-based UIs). Most agents don't need this. Defaults to `false`. |
| `LogoLight` / `LogoDark` | No | Embedded SVG bytes for light/dark themes. The UI shows a blank placeholder if omitted. |
| `DetectOpts` | No | Custom detection options. Defaults to `WithCommand(Command)` which checks if the binary is in `$PATH`. |
- identity: `ID`, `Name`, `DisplayName`, `Description`, `Enabled`, `DisplayOrder`;
- assets and discovery: `Logo`, `IsInstalled`;
- execution: `BuildCommand`, `Runtime`;
- policy: `PermissionSettings`, `BillingType`, `RemoteAuth`, `InstallScript`.
### Step 2: Register the agent
Optional interfaces add specific capabilities:
Add your agent to `LoadDefaults()` in [`apps/backend/internal/agent/registry/registry.go`](../../apps/backend/internal/agent/registry/registry.go):
| Interface | Capability |
|---|---|
| `InferenceAgent` | One-shot inference through the host utility |
| `PassthroughAgent` | Optional direct terminal mode |
| `NativeBinaryAgent` | Prefer an installed native binary over a package launch |
| `LoginAgent` | Interactive PTY-backed authentication |
```go
func (r *Registry) LoadDefaults() {
all := []agents.Agent{
// ... existing agents ...
agents.NewMyAgent(), // add here
}
// ...
}
```
### Build the runtime command
### Step 3: Build and test
`BuildCommand(CommandOptions)` must return a tokenized command that launches an ACP-speaking process or bridge. Use `Cmd`, `Command`, and `Param` builders for model, session, permission, prompt, and configured CLI flags. Never interpolate them into a shell string.
```bash
make -C apps/backend build && make -C apps/backend test && make -C apps/backend lint
```
`Runtime()` describes working directory, environment, resource limits, required/stripped variables, mounts, session recovery, and the command used outside the host. Set `RuntimeConfig.Protocol` to `agent.ProtocolACP`. The factory in [`internal/agentctl/server/adapter/factory.go`](../../apps/backend/internal/agentctl/server/adapter/factory.go) rejects other structured protocols.
Note: passthrough-only agents provide terminal interaction only. There are no structured chat messages, tool call visibility, or progress tracking in the chat UI.
If the upstream CLI has no ACP server, add or reuse a bridge that speaks ACP, or keep the integration passthrough-only. Adding a protocol constant alone does not create an adapter.
---
### Detect installation and authentication
## Adding a New Agent (Full Integration)
`IsInstalled` returns a `DiscoveryResult` with availability, matched path, MCP support/paths, installation paths, and resume/shell/workspace capabilities. Use `Detect` with bounded `DetectOption` checks for known commands or files.
For agents with a structured protocol (JSON-RPC, streaming JSON, etc.), implement the full [`Agent`](../../apps/backend/internal/agent/agents/agent.go) interface.
Declare remote credential methods through `RemoteAuth`, required environment through `RuntimeConfig.RequiredEnv`, and variables that must never reach the child through `StripEnv`. `InstallScript` runs in remote environments; keep it deterministic, pinned where possible, and free of embedded secrets.
### Step 1: Create the agent definition file
Permission settings must map to actual CLI or agentctl behavior. Test supervised, autonomous, and plan-shaped policies when supported. Do not advertise a permission toggle that only changes the UI.
Create `apps/backend/internal/agent/agents/{agent_id}.go`.
### Discover models and modes
This file implements the [`Agent`](../../apps/backend/internal/agent/agents/agent.go) interface. See the interface definition in `agent.go` for the full contract.
Do not maintain a static model list in the agent definition. `internal/agent/hostutility/` probes the ACP server and caches models, modes, and capabilities learned during `session/new`. Test empty, changed, and failed capability responses.
Embed `StandardPassthrough` to get CLI passthrough support for free:
### Configure MCP
```go
type MyAgent struct {
StandardPassthrough
}
In structured mode, resolved MCP servers normally travel in ACP `session/new`. Set `RuntimeConfig.ProjectMCPStrategy` only when an adapter cannot forward those servers and the CLI requires a project file.
func NewMyAgent() *MyAgent {
return &MyAgent{
StandardPassthrough: StandardPassthrough{
PermSettings: myAgentPermSettings,
Cfg: PassthroughConfig{
Supported: true,
Label: "CLI Passthrough",
Description: "Show terminal directly instead of chat interface",
PassthroughCmd: NewCommand("myagent"),
ModelFlag: NewParam("--model", "{model}"),
PromptFlag: NewParam("--prompt", "{prompt}"),
IdleTimeout: 3 * time.Second,
BufferMaxBytes: DefaultBufferMaxBytes,
},
},
}
}
```
For optional passthrough mode, embed or implement `PassthroughAgent` and set `PassthroughConfig.MCPStrategy`. Existing strategies in [`internal/agent/mcpconfig/passthrough.go`](../../apps/backend/internal/agent/mcpconfig/passthrough.go) cover Claude, Codex, Cursor, Pi, and OpenCode configuration shapes.
Key implementation notes:
An MCP strategy must:
- **`BuildCommand()`** returns the CLI or bridge command for structured protocol mode. It must start an ACP-speaking process; include the CLI's ACP flag when one is required.
- **`Runtime()`** returns configuration used when running the agent in Docker or as a standalone process. Full integrations set `Protocol: agent.ProtocolACP`; the adapter factory only accepts ACP.
- **`IsInstalled()`** uses the `Detect()` helper with `WithFileExists()` to check for known installation paths. Set `SupportsMCP` and `MCPConfigPaths` on the result.
- **MCP servers in passthrough** — set `MCPStrategy` on `PassthroughConfig` so the agent receives kandev's own tools server (plus any profile-configured MCP servers) when run in passthrough mode. Omitting it means **no MCP servers are injected in passthrough**. Pick the strategy that matches how your CLI loads MCP servers: `mcpconfig.ClaudeStrategy{}` (temp JSON file + `--mcp-config` flag), `mcpconfig.CodexStrategy{}` (repeated `-c mcp_servers.…` overrides), `mcpconfig.CursorStrategy{}` (project-local `.cursor/mcp.json`), `mcpconfig.PiStrategy{}` (project-local `.pi/mcp.json`), or `mcpconfig.OpenCodeStrategy{}` (temp JSON + `OPENCODE_CONFIG` env var). To add a strategy for a CLI with a different mechanism, implement `mcpconfig.PassthroughMCPStrategy`. See ADR 0014, ADR 0020, and `internal/agent/mcpconfig/passthrough.go`.
- **MCP project files in protocol mode** — set `RuntimeConfig.ProjectMCPStrategy` only when the structured protocol adapter does not pass ACP `session/new` MCP servers through to the underlying CLI. Pi uses this to write `.pi/mcp.json` before the subprocess starts.
- **Models and modes** are discovered from the ACP server during host-utility probing. Agent definitions do not maintain a separate static model list.
- Use `Cmd()` builder for constructing commands fluently: `Cmd("myagent", "--flag").Model(...).Settings(...).Build()`.
- materialize only resolved, scoped servers;
- avoid modifying a user's global CLI configuration;
- pass arguments/environment as tokens;
- clean temporary or project files on normal and failed exits;
- avoid logging tokens, headers, or generated configuration.
### Step 2: Add logos (optional)
### Add assets and registration
Logos are optional - the UI shows a blank placeholder when none is provided. To add them, place SVGs in:
Optional logos live at:
```text
apps/backend/internal/agent/agents/logos/{agent_id}_light.svg
apps/backend/internal/agent/agents/logos/{agent_id}_dark.svg
apps/backend/internal/agent/agents/logos/my-agent_light.svg
apps/backend/internal/agent/agents/logos/my-agent_dark.svg
```
Embed them in your agent file:
Embed them with `//go:embed`. Keep the `AgentID` stable and independent from the display name; persisted profiles and API data refer to it.
Register every shipped agent in `LoadDefaults()` in [`internal/agent/registry/registry.go`](../../apps/backend/internal/agent/registry/registry.go):
```go
//go:embed logos/myagent_light.svg
var myagentLogoLight []byte
//go:embed logos/myagent_dark.svg
var myagentLogoDark []byte
```
### Step 3: Register the agent
Add your agent to `LoadDefaults()` in [`apps/backend/internal/agent/registry/registry.go`](../../apps/backend/internal/agent/registry/registry.go):
```go
func (r *Registry) LoadDefaults() {
all := []agents.Agent{
// ... existing agents ...
agents.NewMyAgent(), // add here
}
// ...
all := []agents.Agent{
// Existing agents...
agents.NewMyAgent(),
}
```
That's it for registration. The frontend discovers agents through the API automatically.
Ordinary identity, availability, profiles, models, and modes reach the UI through the registry API. A new capability, setting, or wire field can still require web types/components and mandatory Playwright coverage.
### Step 4: Understand ACP, REST, and MCP
## Test the integration
These names describe separate boundaries; they are not interchangeable agent runtime options:
| Boundary | Role in Kandev |
|----------|----------------|
| ACP | The only structured agent runtime protocol accepted by the agentctl adapter factory. Set `Runtime().Protocol` to `agent.ProtocolACP`. ACP adapters can wrap CLIs whose internals use other transports. |
| REST | Local HTTP control APIs exposed by agentctl for operations such as Git, processes, and workspace state. REST is not selected as an agent `Protocol`. |
| MCP | The tool protocol used to give agents Kandev and profile-configured tools. MCP servers are passed through ACP `session/new` when supported or materialized by `MCPStrategy` / `ProjectMCPStrategy`. MCP is not an agent runtime adapter. |
To integrate a structured agent, provide or reuse an ACP server/bridge and configure `agent.ProtocolACP`. The adapter factory in [`factory.go`](../../apps/backend/internal/agentctl/server/adapter/factory.go) rejects non-ACP runtime protocols. If a CLI has no ACP server or bridge, integrate it as a passthrough-only `TUIAgent` instead of adding a protocol constant.
### Step 5: Build and test
Start with package tests:
```bash
make -C apps/backend build && make -C apps/backend test && make -C apps/backend lint
cd apps/backend
CGO_ENABLED=1 go test -tags fts5 ./internal/agent/agents ./internal/agent/registry
CGO_ENABLED=1 go test -tags fts5 ./internal/agent/runtime/lifecycle \
./internal/agentctl/server/adapter/...
cd ../..
make lint-backend
```
Start the dev server and verify:
- The agent appears in the agent selector in the UI
- Agent execution works (start a task, send a prompt)
- Passthrough mode works (toggle to CLI passthrough in the session)
- Model listing works
Cover:
---
- unique ID and deterministic registry order;
- installed, missing, and ambiguous discovery;
- every model/session/permission argument as exact tokens;
- required and stripped environment variables;
- ACP initialize, `session/new`, prompt, cancellation, resume, and shutdown;
- capability probe changes and failures;
- passthrough terminal sizing, buffering, cleanup, and MCP materialization when offered;
- local/worktree plus each declared container or remote runtime;
- missing login, missing credential, and interrupted installation paths.
## Updating an Existing Agent
Then run `make test-backend` and start `make dev`. Verify discovery, profile selection, start/prompt/resume, model/mode refresh, permissions, and passthrough if supported.
| Change | Where |
|--------|-------|
| Update CLI version | `BuildCommand()` and `Runtime().Cmd` in the agent file |
| Change model or mode discovery | Update the ACP server/bridge capability response; Kandev learns these during host-utility probing |
| Update permissions | `PermissionSettings()` variable and `PassthroughConfig` |
| Update Docker image | `Runtime()` - change `Image`/`Tag` fields |
| Change structured runtime | Keep `Runtime().Protocol` on ACP and update the ACP server/bridge command |
The real adapter suite is separate:
---
```bash
make -C apps/backend test-e2e
```
## File Reference
It may launch installed third-party agents, require login, and incur paid usage. Run only after reading the target tests. Root `make test-e2e` is the browser Playwright suite, not this real-agent suite.
| File | When to modify |
|------|---------------|
| `apps/backend/internal/agent/agents/{agent_id}.go` | Always - agent definition |
| `apps/backend/internal/agent/agents/logos/{agent_id}_{light,dark}.svg` | Optional - agent logos (UI shows blank placeholder if omitted) |
| `apps/backend/internal/agent/registry/registry.go` | Always - register in `LoadDefaults()` |
| `apps/backend/internal/agent/agents/tui_agent.go` | Never - provides `TUIAgent` declarative type (read-only reference) |
| `apps/backend/pkg/agent/protocol.go` | Read-only reference for the ACP runtime protocol value |
| `apps/backend/internal/agentctl/server/adapter/factory.go` | Read-only reference for ACP adapter construction |
| `apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go` | ACP transport implementation and bridge behavior |
| `apps/backend/internal/agent/agents/passthrough.go` | Never - provides `StandardPassthrough` (read-only reference) |
| `apps/backend/internal/agent/agents/agent.go` | Never - defines the `Agent` interface (read-only reference) |
## Update an existing agent
Trace the behavior instead of assuming one file owns it:
| Change | Inspect |
|---|---|
| Identity, command, image, auth, permissions, resume | Agent definition under `internal/agent/agents/` |
| Models, modes, structured capabilities | ACP server/bridge and host-utility probe |
| Passthrough MCP format | Agent `PassthroughConfig` and `internal/agent/mcpconfig/` |
| Structured MCP gap | `RuntimeConfig.ProjectMCPStrategy` and adapter behavior |
| Registry/default availability | `internal/agent/registry/` and settings reconciler |
| Packaged native binary | Runtime bundle matrices, npm runtime packages, desktop resources, release tests |
| New user-visible field | Backend DTO/API, web types/UI, Playwright, and public docs |