Add an Agent CLI
Register a local TUI agent or ship a tested built-in passthrough, ACP, or native Codex app-server integration.
Choose the smallest integration that matches the CLI:
| Need | Path |
|---|---|
| Use a local CLI without changing Kandev source | Add a custom 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 |
| Integrate Codex's native app-server | Extend the dedicated Codex app-server transport |
ACP, the native Codex app-server protocol, REST, and MCP are different boundaries. The native app-server adapter is specific to Codex and experimental; it is not a general custom-agent protocol. REST/WebSocket control agentctl and Kandev. MCP supplies tools to an agent; it is not a runtime adapter.
Quick path
- Use Add custom agent for a local CLI, and pick its protocol there.
- Add a built-in
TUIAgentonly when every Kandev install needs it. - Use a full ACP integration for structured chat, tools, models, modes, or resume. Codex app-server support uses its dedicated native transport.
- Validate the path you chose:
- local TUI: installation discovery and exact command-token construction;
- built-in, ACP, or native Codex: declared permissions, credentials, resume, and MCP delivery.
Register a local agent
Open Settings → Agents, choose Add custom agent, and provide:
- a display name;
- a protocol: Terminal or ACP;
- a command available on the executor's
PATH; - for Terminal only, an optional model/profile label and an MCP strategy.
Kandev derives a stable slug from the display name, persists the definition, registers it at startup, and creates a default profile in the selected protocol's mode. 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.
Terminal runs the command as a raw CLI under a PTY. Use {{model}} in the command to insert the optional model value. There are no structured chat messages, no tool-call visibility, and no capability probing; Kandev's MCP server reaches the CLI only through the selected MCP strategy, which writes the config file shape that CLI reads.
ACP drives the command over the Agent Client Protocol on standard input and output, so the agent gets structured chat, tool calls, and the models and modes learned by the host-utility capability probe. Give the command that starts the ACP server, typically behind a flag (my-agent --acp). Resolved MCP servers travel in session/new, so no MCP strategy applies and none is offered. The protocol is not probed from the command: a CLI's ACP server command is not the command that renders its terminal, so choosing wrong would launch a JSON-RPC server into a terminal tab.
Use a source integration when the agent must ship for every installation, needs logos/auth/permissions/install scripts, needs safe structured argument building, or needs a bridge because the CLI has no ACP server of its own.
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:
package agents
func NewMyAgent() *TUIAgent {
return NewTUIAgent(TUIAgentConfig{
AgentID: "my-agent",
AgentName: "My Agent",
Command: "my-agent",
Desc: "Run My Agent in terminal passthrough mode.",
ModelFlag: NewParam("--model", "{model}"),
WaitForTerm: true,
})
}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.
The exact TUIAgentConfig contract is:
| 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 |
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.
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.
Ship a structured ACP integration
Use gemini.go as a current ACP example and agent.go as the contract. A full integration implements agents.Agent:
- identity:
ID,Name,DisplayName,Description,Enabled,DisplayOrder; - assets and discovery:
Logo,IsInstalled; - execution:
BuildCommand,Runtime; - policy:
PermissionSettings,BillingType,RemoteAuth,InstallScript.
Optional interfaces add specific capabilities:
| 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 |
HarnessUpdateAgent | Optional trusted host self-update command and metadata package, separate from managed npm pinning |
Build the runtime command
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.
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 rejects other structured protocols.
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.
Extend the native Codex app-server integration
The native Codex transport is a separate, experimental integration behind features.codexAppServer / KANDEV_FEATURES_CODEX_APP_SERVER. It keeps the codex-app-server identity separate from codex-acp. Do not generalize its provider-specific wire types for another agent. Update the pinned schema fixtures under pkg/codexappserver/schema/, the semantic adapter under internal/agentctl/server/adapter/transport/codexappserver/, and the normalization contract together. The feature toggle is off by default and restart-required.
Detect installation and authentication
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.
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.
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.
Declare a harness-owned updater
Only a built-in agent with a trusted CLI self-updater implements
HarnessUpdateAgent. Its HarnessUpdateSpec declares a fixed metadata package
for the stable-release status reference and tokenized update argv. OMP
declares @oh-my-pi/pi-coding-agent and omp update; Kandev runs the latter
directly on the host, then probes omp acp. Request input cannot replace the
package, command, channel, or update version. Do not implement
ManagedNPMRuntimeAgent for such an agent: that interface activates exact
Kandev-managed npm versions, whereas a self-updater follows its own installed
package manager and configured channel. This Settings capability does not
change BuildCommand, Runtime, or the remote InstallScript.
Discover models and modes
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.
Configure MCP
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.
For optional passthrough mode, embed or implement PassthroughAgent and set PassthroughConfig.MCPStrategy. Existing strategies in internal/agent/mcpconfig/passthrough.go cover Claude, Codex, Cursor, Pi, and OpenCode configuration shapes.
An MCP strategy must:
- 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.
When investigating a structured agent's MCP behavior, run acpdbg mcp-probe <agent>. It injects a temporary HTTP sentinel in session/new and reports delivery, initialize, and tools/list separately. A sentinel that is not contacted during the bounded probe window is unobserved, not proof that the agent cannot use MCP. New passthrough strategies must report an unavailable or explicit materialization failure rather than silently omitting MCP configuration.
Add assets and registration
Optional logos live at:
apps/backend/internal/agent/agents/logos/my-agent_light.svg
apps/backend/internal/agent/agents/logos/my-agent_dark.svgEmbed 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:
all := []agents.Agent{
// Existing agents...
agents.NewMyAgent(),
}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.
Test the integration
Start with package tests:
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-backendCover:
- 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.
Then run make test-backend and start make dev. Verify discovery, profile selection, start/prompt/resume, model/mode refresh, permissions, and passthrough if supported.
The real adapter suite is separate:
make -C apps/backend test-e2eIt 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.
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 |
