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:
+122
-169
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user