docs: add curated public documentation (#1701)

* docs: add curated public documentation

* docs: correct public API and deployment guidance

* docs: fix workflow trigger anchor

* docs: harden deployment and websocket examples
This commit is contained in:
José Almeida
2026-07-15 12:06:06 +01:00
committed by GitHub
parent dd1b811930
commit 55960e7555
19 changed files with 2952 additions and 2 deletions
+70
View File
@@ -0,0 +1,70 @@
---
name: docs-maintainer
description: Keep public Kandev docs current when code or behavior changes affect CLI commands, config keys, install/deploy flows, workflows, executors, APIs, screenshots, or user-facing terminology. Use this before finishing any change with public documentation impact, and when reviewing whether a change needs docs.
---
# Docs Maintainer
Use this skill to decide whether public docs need updates and to make those updates in the right place.
## Docs Boundaries
- Public website docs source lives under `docs/public/**`.
- Internal product/spec planning stays under `docs/specs/**`.
- Implementation plans stay under `docs/plans/**`.
- Architecture decisions stay under `docs/decisions/**`.
- Raw supporting notes can remain under `docs/**` outside `docs/public/**`, but do not publish them unless rewritten for users.
- The landing/docs website consumes public docs through its manifest and generated content. Do not hand-edit generated website docs.
## When Docs Need Updates
Check public docs when a change affects:
- CLI commands, flags, install commands, or runtime launch behavior.
- Configuration keys, environment variables, defaults, profiles, or feature flags.
- Workspaces, workflows, tasks, agents, executors, worktrees, Git behavior, or review flows.
- Docker, Kubernetes, service, desktop, remote environment, or Windows instructions.
- Public APIs, WebSocket messages, workflow import/export schemas, or integration contracts.
- Screenshots, visible UI labels, navigation, onboarding, or user-facing terminology.
Skip public docs when the change is:
- Purely internal refactoring with no behavior change.
- Test-only, fixture-only, or build-only without user-visible behavior.
- A speculative plan or design note that belongs in `docs/specs/**`, `docs/plans/**`, or `docs/decisions/**`.
## Workflow
1. Identify docs impact from the diff and changed behavior.
2. Search `docs/public/**` first for affected terms and commands.
3. If public docs exist, update them with the same PR as the behavior change.
4. If no public docs exist but the behavior is user-facing, add or propose the smallest useful public page/section.
5. If the change only updates implementation intent or architectural history, update specs/plans/ADRs instead.
6. Keep public docs task-oriented: prerequisites, commands, expected result, troubleshooting, and links to reference.
7. Preserve internal links inside `docs/public/**` where possible. Link to source-only raw docs only when the raw note is intentionally not published.
8. Note docs impact in the PR body.
## Validation
Run the checks relevant to your change:
```bash
# Replace SEARCH_TERM with the command, config key, or terminology that changed.
rg -n "SEARCH_TERM" docs/public docs/specs docs/decisions
```
For website docs publishing changes, also run from the landing repo:
```bash
pnpm --filter @kandev/docs fetch-docs
pnpm exec vitest run apps/docs/lib/docs-processing.test.ts
pnpm --filter @kandev/docs build
```
## Final Report
State one of:
- `Public docs updated:` with changed `docs/public/**` files.
- `Internal docs updated:` with changed specs/plans/decisions.
- `No docs change needed:` with one concrete reason.
@@ -29,6 +29,7 @@ Task arrives
|-- Review code? ---------------------------> /code-review
|-- Improve skills/agents/commands? --------> /harness-improvement
|-- Record decisions/spec changes? ---------> /record
|-- Public docs impact? --------------------> /docs-maintainer
|-- Commit/push/PR? ------------------------> /commit -> /push or /pr
`-- Release/versioning? --------------------> /release
```
+1
View File
@@ -56,3 +56,4 @@ RULES
- [ ] I have manually tested my changes and they work as expected.
- [ ] My changes have tests that cover the new functionality and edge cases.
- [ ] If my change touches UI files (`apps/web/`), I have added or updated Playwright e2e tests in `apps/web/e2e/` and verified them with `make test-e2e`.
- [ ] I checked whether this affects public docs in `docs/public/**` and updated them or noted why no docs change is needed.
+1
View File
@@ -70,6 +70,7 @@ When you hit a limit: extract a helper function, custom hook, or sub-component.
Every code change must include tests for new or changed logic. Backend: `*_test.go` files alongside the source. Frontend: `*.test.ts` files for utility functions, hooks, API clients, and store slices. Exceptions: config files, generated code, React component markup. Use `/tdd` for test-driven development.
### Knowledge
- **Public docs:** Website-ready user documentation lives in `docs/public/**`. Use `/docs-maintainer` when a change affects CLI commands, config keys, install/deploy flows, workflows, executors, public APIs, screenshots, or user-facing terminology.
- **Specs:** Feature specs live in `docs/specs/<slug>/spec.md` — the durable "what & why" of a feature, written before coding. Use `/spec` to write or update a spec. See `docs/specs/INDEX.md`.
- **Decisions:** Architecture decisions are recorded in `docs/decisions/`. Read `docs/decisions/INDEX.md` for an overview. When making significant architectural choices, create a new ADR via `/record decision`.
- **Plans:** Implementation plans are generated from specs via `/plan` and committed under `docs/plans/<slug>/plan.md`, with individual sibling task files named `docs/plans/<slug>/task-<NN>-<short-slug>.md`. Specs are the living requirements; plans and task files are implementation records for the current buildout.
+4 -2
View File
@@ -14,7 +14,8 @@ You must understand the code you submit. You're welcome to use AI tools to help
1. **Fork and branch.** Create a feature branch from `main`.
2. **Keep PRs focused.** One logical change per PR. Small PRs get reviewed faster.
3. **Test your changes.** Run `make typecheck test lint` before submitting. Manually verify that your feature works end-to-end, and add screenshots or recordings to the PR if it has a UI component. **If your change touches any UI files (anything under `apps/web/`), you must add or update Playwright e2e tests in `apps/web/e2e/` to prevent regressions.** Run them with `make test-e2e`. See [docs/test_e2e_web.md](docs/test_e2e_web.md) for patterns and fixtures.
3. **Update public docs when behavior changes.** User-facing docs live in `docs/public/**`. If your change affects CLI commands, config keys, install/deploy flows, workflows, executors, APIs, screenshots, or user-facing terminology, update the relevant public docs in the same PR.
4. **Test your changes.** Run `make fmt` first, then `make typecheck test lint` before submitting. Manually verify that your feature works end-to-end, and add screenshots or recordings to the PR if it has a UI component. **If your change touches any UI files (anything under `apps/web/`), you must add or update Playwright e2e tests in `apps/web/e2e/` to prevent regressions.** Run them with `make test-e2e`. See [docs/test_e2e_web.md](docs/test_e2e_web.md) for patterns and fixtures.
## Bug Reports
@@ -33,9 +34,10 @@ Open an issue describing the feature and the problem it solves. Keep it concise.
New code must pass the existing linters and tests:
```bash
make fmt # Format Go and web code
make typecheck # TypeScript type checking
make test # Backend + web tests
make lint # Backend + web linters
make typecheck # TypeScript type checking
```
## License
+234
View File
@@ -0,0 +1,234 @@
# Adding a New Agent CLI Integration
This guide explains how to add support for a new coding agent CLI to Kandev.
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**.
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).
---
## CLI-Only Agent (Passthrough Only) - Recommended for TUI tools
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.
This is the simplest path. A complete agent definition is ~20 lines.
### Step 1: Create the agent file
Create `apps/backend/internal/agent/agents/{agent_id}.go`:
```go
package agents
func NewClaude() *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.
ModelFlag: NewParam("--model", "{model}"),
})
}
```
Logos are optional - the UI shows a blank placeholder when none is provided. To add them, embed SVGs and pass `LogoLight` / `LogoDark`:
```go
package agents
import _ "embed"
//go:embed logos/claude_light.svg
var claudeLogoLight []byte
//go:embed logos/claude_dark.svg
var claudeLogoDark []byte
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,
})
}
```
`TUIAgentConfig` fields (all optional fields have sensible defaults):
| 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`. |
### Step 2: 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
}
// ...
}
```
### Step 3: Build and test
```bash
make -C apps/backend build && make -C apps/backend test && make -C apps/backend lint
```
Note: passthrough-only agents provide terminal interaction only. There are no structured chat messages, tool call visibility, or progress tracking in the chat UI.
---
## Adding a New Agent (Full Integration)
For agents with a structured protocol (JSON-RPC, streaming JSON, etc.), implement the full [`Agent`](../../apps/backend/internal/agent/agents/agent.go) interface.
### Step 1: Create the agent definition file
Create `apps/backend/internal/agent/agents/{agent_id}.go`.
This file implements the [`Agent`](../../apps/backend/internal/agent/agents/agent.go) interface. See the interface definition in `agent.go` for the full contract.
Embed `StandardPassthrough` to get CLI passthrough support for free:
```go
type MyAgent struct {
StandardPassthrough
}
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,
},
},
}
}
```
Key implementation notes:
- **`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()`.
### Step 2: Add logos (optional)
Logos are optional - the UI shows a blank placeholder when none is provided. To add them, place SVGs in:
```text
apps/backend/internal/agent/agents/logos/{agent_id}_light.svg
apps/backend/internal/agent/agents/logos/{agent_id}_dark.svg
```
Embed them in your agent file:
```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
}
// ...
}
```
That's it for registration. The frontend discovers agents through the API automatically.
### Step 4: Understand ACP, REST, and MCP
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
```bash
make -C apps/backend build && make -C apps/backend test && make -C apps/backend lint
```
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
---
## Updating an Existing Agent
| 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 |
---
## File Reference
| 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) |
+221
View File
@@ -0,0 +1,221 @@
# Kandev CLI
## Architecture
```mermaid
flowchart TB
subgraph CLI["kandev"]
run["run"]
dev["dev"]
start["start"]
end
run --> resolve["Resolve installed runtime"]
dev --> makedev["make dev"]
start --> binary["Local binary<br/>(embedded Vite assets)"]
resolve --> envvar["KANDEV_BUNDLE_DIR<br/>(Homebrew, tests)"]
resolve --> npmpkg["@kdlbs/runtime-{platform}<br/>(npm/npx)"]
envvar --> supervisor
npmpkg --> supervisor
makedev --> supervisor
binary --> supervisor
subgraph supervisor["Process Supervisor"]
direction LR
s1["Manages backend process"]
s2["Handles graceful shutdown"]
s3["Forwards signals"]
end
```
## Overview
The Kandev CLI (`kandev`) is the primary way to run the Kandev application. It launches the backend from an installed runtime bundle; the backend serves the web assets, API, and WebSocket gateway.
## Installation
### Homebrew (macOS, Linux)
```bash
brew install kdlbs/kandev/kandev
```
### NPX (requires npm 7+)
```bash
npx kandev@latest
```
`npx` installs the `kandev` CLI plus the matching `@kdlbs/runtime-{platform}` package via npm optional dependencies. Older npm versions silently skip optional deps and won't work — npm 7 is the floor.
### NPM (global)
```bash
npm install -g kandev@latest
```
## Quick Start
```bash
# Run the installed runtime
kandev
# Opens the app at http://localhost:38429 (or next available port)
```
## Updates
The package manager controls the runtime version:
```bash
brew upgrade kandev # Homebrew users
npm install -g kandev@latest # global npm users
npx kandev@latest # npx users (always latest)
```
## Commands
### `kandev` / `kandev run`
Runs the installed runtime bundle. This is the default command.
```bash
kandev
kandev run
```
**What happens:**
1. Resolves the runtime bundle (KANDEV_BUNDLE_DIR → npm package).
2. Starts the backend server.
3. Waits for backend health check.
4. Serves the embedded web UI from the backend.
5. Opens browser when ready.
### `kandev start`
Runs the application using the local production backend binary. Requires running
`make build` first so the Vite SPA is embedded into that binary.
```bash
make build
kandev start
```
## Options
| Option | Description | Example |
|--------|-------------|---------|
| `--version`, `-V` | Print CLI version and exit | `kandev --version` |
| `--port <port>` | Backend port (alias: `--backend-port`) | `--port 3000` |
| `--verbose`, `-v` | Show info logs from backend | `--verbose` |
| `--debug` | Show debug logs + agent message dumps | `--debug` |
| `--help`, `-h` | Show help | `--help` |
### Examples
```bash
# Print CLI version
kandev --version
# Custom port
kandev --port 18080
```
## Port Selection
By default, the CLI automatically finds available ports:
| Service | Default Port | Fallback |
|---------|--------------|----------|
| Backend | 38429 | Auto-selects from 10000–60000 |
| Web | 37429 | Auto-selects from 10000–60000 |
| AgentCtl | 39429 | Auto-selects from 10000–60000 |
If the default port is in use, the CLI finds the next available port automatically.
## Environment Variables
| Variable | Description |
|----------|-------------|
| `KANDEV_BUNDLE_DIR` | Force the runtime bundle location (set by Homebrew wrapper) |
| `KANDEV_PORT` / `KANDEV_BACKEND_PORT` | Backend port (CLI flag wins) |
| `KANDEV_WEB_PORT` | Internal web app port |
| `KANDEV_HEALTH_TIMEOUT_MS` | Override health check timeout (ms) |
## Makefile Integration
The repo includes a Makefile that wraps the CLI for common operations:
```bash
make start # Production mode (after make build)
make dev # Development mode
make build # Build everything
```
See `make help` for all available commands.
## Comparison: run vs dev vs start
| Feature | `run` | `dev` | `start` |
|---------|-------|-------|---------|
| Source | Installed runtime | Local repo | Local build |
| Hot-reload | No | Yes | No |
| Requires repo | No | Yes | Yes |
| Requires build | No | No | Yes |
| Use case | End users | Development | Testing production |
## Troubleshooting
### "No Kandev runtime found for {platform}"
The CLI couldn't find an installed runtime. Install one:
```bash
# via npm (requires npm 7+ for optional dep resolution)
npx kandev@latest
# via Homebrew
brew install kdlbs/kandev/kandev
```
If you're on npm 6 or older, optional dependencies aren't installed by `npx`. Upgrade npm: `npm install -g npm@latest`.
### Port Already in Use
The CLI automatically finds available ports. If you need a specific port:
```bash
kandev --port 18080
```
### Backend Takes Too Long to Start
Increase the health check timeout:
```bash
KANDEV_HEALTH_TIMEOUT_MS=60000 kandev
```
### Start Mode: "Backend binary not found"
Build the project first:
```bash
make build
kandev start
```
## Data Storage
| Path | Contents |
|------|----------|
| `~/.kandev/data/` | SQLite database and app data |
| Homebrew Cellar | Installed runtime (when installed via brew) |
| Under the global package root printed by `npm root -g` | Installed runtime dependency tree for `npm install -g kandev` |
| `<npm cache>/_npx/<run-id>/node_modules/` | Temporary package tree, including the platform runtime, for `npx kandev` |
Run `npm config get cache` to print `<npm cache>`. npm chooses the `_npx`
run-directory name and may remove cached runs, so do not treat that path as
persistent application data.
+149
View File
@@ -0,0 +1,149 @@
# Configuration
Kandev's backend reads configuration from three sources, in this order of precedence (later sources override earlier ones):
1. Built-in defaults (`apps/backend/internal/common/config/config.go`).
2. A YAML config file (`config.yaml`).
3. Environment variables (`KANDEV_*`).
Both the file and env vars are optional; the backend boots with sensible defaults out of the box. See [`docker.md`](./docker.md) and [`k8s.md`](./k8s.md) for deployment-specific tables; this page is the full reference.
## Config file
The backend looks for `config.yaml` in the following order:
- An explicit path passed at startup (used in tests).
- The current working directory.
- `/etc/kandev/`.
A missing file is not an error - defaults plus env vars take over.
## Environment variables
Backend configuration normally uses the `KANDEV_` prefix. Nested keys map by replacing `.` with `_` and uppercasing; **camelCase becomes one uppercase run** (no underscore inserted), because Viper does not synthesize a snake_case form. A small set of explicitly bound compatibility aliases is also supported, including the unprefixed `AGENTCTL_PORT`.
| YAML key | Env var |
|---|---|
| `server.port` | `KANDEV_SERVER_PORT` |
| `server.webInternalUrl` | `KANDEV_SERVER_WEBINTERNALURL` (or alias `KANDEV_WEB_INTERNAL_URL`) |
| `database.dbName` | `KANDEV_DATABASE_DBNAME` |
| `logging.maxSizeMb` | `KANDEV_LOGGING_MAXSIZEMB` |
| `homeDir` | `KANDEV_HOME_DIR` (alias) |
| `logging.level` | `KANDEV_LOG_LEVEL` (alias) |
| `agent.standalonePort` | `AGENTCTL_PORT` or `KANDEV_AGENT_STANDALONE_PORT` (aliases) |
The aliases on the right are explicit bindings - see `LoadWithPath` in `config.go` for the full list. New keys should follow the deterministic rule (`KANDEV_<SECTION>_<KEYUPPERCASE>`) unless compatibility requires an explicit alias.
## Full `config.yaml` example
Every key shown here has a default - copying the whole file changes nothing. Use it as a starting point and delete what you don't need to override.
```yaml
# Kandev root directory. Empty = ~/.kandev (or KANDEV_HOME_DIR if set).
# All workspace artifacts (data, tasks, worktrees, repos, sessions) live here.
homeDir: ""
server:
host: "0.0.0.0"
port: 38429 # API + WebSocket + Web UI
readTimeout: 30 # seconds
writeTimeout: 30 # seconds
webInternalUrl: "" # internal URL the backend uses to call the web app
database:
driver: "sqlite" # "sqlite" or "postgres"
path: "" # sqlite: empty = $homeDir/data/kandev.db
# postgres-only fields below (ignored when driver=sqlite)
host: "localhost"
port: 5432
user: "kandev" # required when driver=postgres
password: "" # required when driver=postgres in most setups
dbName: "kandev" # required when driver=postgres
sslMode: "disable" # disable | require | verify-ca | verify-full
maxConns: 25
minConns: 5
nats:
url: "" # empty = use in-memory event bus
clusterId: "kandev-cluster"
clientId: "kandev-client"
maxReconnects: 10
events:
namespace: "" # empty = derive from runtime data identity
docker:
enabled: true # disables Docker-based executors when false
host: "" # empty = platform default (unix:///var/run/docker.sock, etc.)
apiVersion: "" # empty = auto-negotiate
tlsVerify: false
defaultNetwork: "kandev-network"
volumeBasePath: "" # empty = /var/lib/kandev/volumes (Linux/macOS)
agent:
standaloneHost: "localhost"
standalonePort: 39429 # agentctl control port
auth:
jwtSecret: "" # empty = auto-generate a random dev secret on boot
tokenDuration: 3600 # seconds
logging:
level: "info" # debug | info | warn | error
format: "text" # accepted values: text | json; omit for environment-based default
outputPath: "stdout" # stdout | stderr | /path/to/file.log
# Rotation - only applied when outputPath is a file path.
# Active log files are created with mode 0600 (owner-only).
maxSizeMb: 100 # rotate at this size; 0 = lumberjack default (100MB)
maxBackups: 5 # 0 = unlimited
maxAgeDays: 30 # 0 = unlimited
compress: true # gzip rotated files
repositoryDiscovery:
roots: [] # absolute paths to scan for local git repos
maxDepth: 5
worktree:
enabled: true
defaultBranch: "main"
cleanupOnRemove: true
fetchTimeoutSeconds: 60
pullTimeoutSeconds: 60
repoClone:
basePath: "" # empty = $homeDir/repos
debug:
pprofEnabled: false # enables /debug/pprof and /api/v1/debug/memory
```
## Required vs optional
Almost every field has a default and is optional. The exceptions:
| Field | When required | What happens otherwise |
|---|---|---|
| `database.user` | `database.driver=postgres` | Startup fails with `database.user is required for postgres driver` |
| `database.dbName` | `database.driver=postgres` | Startup fails with `database.dbName is required for postgres driver` |
| `database.password` | `database.driver=postgres` in most setups (some Postgres configs allow passwordless local auth) | Connection fails at runtime |
| `auth.jwtSecret` | Never strictly required, but in production set an explicit value | A random secret is generated on boot - tokens become invalid on restart |
Validated value sets (any other value is a startup error):
| Field | Allowed values |
|---|---|
| `server.port` | `1`-`65535` |
| `database.driver` | `sqlite`, `postgres` |
| `database.port` | `1`-`65535` (only validated when `driver=postgres`) |
| `database.sslMode` | `disable`, `require`, `verify-ca`, `verify-full` (only validated when `driver=postgres`) |
| `logging.level` | `debug`, `info`, `warn`, `error` |
| `logging.format` | `json`, `text` |
## Tips
- **Env vars override the file.** Useful for secrets (`KANDEV_DATABASE_PASSWORD`) and per-environment knobs (`KANDEV_LOG_LEVEL`).
- **K8s / Docker:** prefer env vars for everything; skip the YAML file entirely.
- **Local dev:** drop a `config.yaml` next to where you run the backend; viper picks it up from the current working directory.
- **Format auto-detection:** when `logging.format` is omitted, Kandev defaults to `json` if `KANDEV_ENV` is `production`/`prod` or `KUBERNETES_SERVICE_HOST` is set; otherwise it defaults to `text`. The literal value `auto` is not accepted.
+67
View File
@@ -0,0 +1,67 @@
# Kandev Desktop App
Kandev desktop is a Tauri app that starts the native Kandev runtime locally and shows the existing Kandev UI in a system WebView. It does not require Node.js at runtime. Node.js, pnpm, Rust, and the Tauri CLI are build-time tools only.
## Install Artifacts
GitHub releases publish desktop artifacts alongside the existing runtime tarballs. Desktop artifact names start with `kandev-desktop-<platform>-` and each artifact has a `.sha256` checksum.
Supported desktop platforms:
- `macos-arm64`
- `macos-x64`
- `linux-x64`
- `linux-arm64`
- `windows-x64`
When signing inputs are configured, macOS artifacts are Developer ID signed and notarized and Windows artifacts are code signed. When those inputs are missing, the release still publishes unsigned desktop development builds with a release-notes warning. Linux artifacts are checksum-gated; package-manager signatures can be added later.
Unsigned macOS or Windows desktop artifacts may require manual OS security bypasses and should not be presented as trusted downloads.
## Runtime Requirements
The desktop app packages the native Kandev runtime binaries and starts:
```text
kandev --headless --port <local-port>
```
The backend binds to `127.0.0.1` and serves the same embedded Vite UI used by the CLI/Homebrew/npm runtime.
Platform WebView requirements:
- macOS: system WebKit.
- Windows: Microsoft WebView2 runtime.
- Linux: WebKitGTK and package dependencies installed by the `.deb`/`.rpm` package.
Kandev data still lives in the existing Kandev home directory, `~/.kandev` by default, unless overridden by existing environment/config settings.
## Updates
The first desktop release does not include an in-app Tauri updater. Update by downloading and installing a newer desktop artifact from GitHub Releases. Existing worktrees, settings, tasks, and the SQLite database remain in the Kandev data directory.
The npm and Homebrew channels continue to update through:
```bash
brew upgrade kandev
npm install -g kandev@latest
npx kandev@latest
```
Those channels are separate from desktop installers, even though all release artifacts share the same SemVer.
## Agent CLI Discovery
Desktop apps launched from an OS app launcher may not inherit the same shell initialization as a terminal. Kandev preserves available environment variables and adds common user binary directories such as `/usr/local/bin`, `/opt/homebrew/bin`, `/usr/bin`, `/bin`, `~/.local/bin`, and common agent install directories.
If an agent CLI is not discoverable from the desktop app:
- Set the agent profile command to the full executable path.
- Confirm the executable works from a normal terminal.
- Put custom install directories in a location visible to GUI apps, or configure them through OS-level environment settings rather than only shell startup files.
Existing explicit command paths in Kandev settings remain authoritative over `PATH` lookup.
## Release Trust
See [desktop-tauri-signing.md](../desktop-tauri-signing.md) for the CI secrets and automatic signing behavior used for desktop releases.
+316
View File
@@ -0,0 +1,316 @@
# Docker Guide
Run Kandev in a Docker container. For Kubernetes deployment, see [k8s.md](k8s.md).
## Quick Start
```bash
docker run -p 38429:38429 -v kandev-data:/data ghcr.io/kdlbs/kandev:latest
```
Open `http://localhost:38429` in your browser.
## Using the Pre-built Image
Kandev publishes images to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
```bash
# Latest release
docker pull ghcr.io/kdlbs/kandev:latest
# Specific version
docker pull ghcr.io/kdlbs/kandev:0.9.0
```
### Choosing your image: vanilla vs. universal
Two flavors are published. The default vanilla image is smallest and bundles npm-installable agent CLIs only. The `:universal` image (~1.4 GB) adds language toolchains (Go, Rust, build-essential), linters, and Playwright Chromium system libs - pick this if your agents work on Go/Rust/Python projects or drive headless browsers.
```bash
docker pull ghcr.io/kdlbs/kandev:universal
```
See the [repository image guide](https://github.com/kdlbs/kandev/blob/main/docs/images.md) for the full comparison, inclusion policy, and recipes for deriving your own image.
## Building from Source
The root `Dockerfile` is a release-image Dockerfile: it copies prebuilt binaries from a `bundle/` directory in the Docker build context. It does not compile the repository inside Docker. On a Linux host matching the target architecture, build the service bundle and prepare the same context layout used by the release workflow:
```bash
make service-bundle
rm -rf ctx
mkdir -p ctx/bundle
cp -R dist/kandev/. ctx/bundle/
cp docker-entrypoint.sh ctx/
docker build -f Dockerfile -t kandev:latest ctx
```
For a cross-architecture image, first produce or download the matching `kandev-linux-x64.tar.gz` or `kandev-linux-arm64.tar.gz` release bundle, extract its top-level `kandev/` directory into `ctx/bundle/`, copy `docker-entrypoint.sh` into `ctx/`, and build that context with the matching `--platform`. Setting `--platform` without a matching bundle only labels an image containing the wrong native binaries.
## Data Persistence
Kandev stores its SQLite database and git worktrees in `/data`. Mount a volume to persist data across container restarts:
```bash
# Named volume (recommended)
docker run -v kandev-data:/data ghcr.io/kdlbs/kandev:latest
# Bind mount to a host directory
docker run -v /path/on/host:/data ghcr.io/kdlbs/kandev:latest
```
Without a volume, data is lost when the container is removed.
### What lives on the volume
| Path | Contents |
|---|---|
| `/data/data/` | SQLite database (`kandev.db`, `-wal`, `-shm`) |
| `/data/worktrees/`, `/data/tasks/`, `/data/repos/`, `/data/sessions/`, `/data/lsp-servers/` | Per-session state |
| `/data/.npm-global/` | Agent CLIs installed via `npm install -g` (`NPM_CONFIG_PREFIX`) |
| `/data/home/` | `$HOME` for the in-container `kandev` user — `gh` CLI and agent CLI auth state |
### Persistent agent and `gh` CLI auth
The image sets `HOME=/data/home`, so every CLI that writes its auth under `$HOME` lands on the volume and survives container restarts and image upgrades:
- `gh` CLI — `~/.config/gh/hosts.yml`
- Claude Code — `~/.claude/.credentials.json`, `~/.claude.json`
- Codex — `~/.codex/auth.json`, `~/.codex/config.toml`
- Auggie — `~/.augment/session.json`
- GitHub Copilot — `~/.copilot/...`
- OpenCode, Amp — `~/.config/<tool>/...`
A one-time `docker exec -it kandev gh auth login` (or `claude login`, `codex login`, etc.) is enough; you do not need to redo it after `docker pull` and recreating the container.
> The GitHub PAT configured in **Settings → Integrations → GitHub** is stored as a secret in the database and has always persisted. The `HOME=/data/home` setup covers the separate `gh auth login` flow that the backend falls back to when no `GITHUB_TOKEN` secret is set.
## Configuration
Configuration is done via `KANDEV_`-prefixed environment variables:
```bash
docker run -p 38429:38429 \
-v kandev-data:/data \
-e KANDEV_LOG_LEVEL=debug \
ghcr.io/kdlbs/kandev:latest
```
### Environment Variables
See [`configuration.md`](./configuration.md) for the full reference (including the YAML form and every knob the backend reads). The table below covers the env vars most often set in a Docker deployment.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `KANDEV_HOME_DIR` | No | `/data` | Kandev home directory - contains `data/` (DB), `tasks/`, `worktrees/`, `repos/`, `sessions/`, and `lsp-servers/` |
| `KANDEV_DATABASE_DRIVER` | No | `sqlite` | Database driver (`sqlite` or `postgres`) |
| `KANDEV_DATABASE_PATH` | No | `$KANDEV_HOME_DIR/data/kandev.db` | SQLite database file path (override) |
| `KANDEV_LOG_LEVEL` | No | `info` | Log level: `debug`, `info`, `warn`, `error` |
| `KANDEV_LOGGING_FORMAT` | No | environment-selected (`text` in ordinary Docker runs) | Explicit format: `text` or `json`. The default becomes `json` when `KANDEV_ENV` is `production` or `prod`. |
| `KANDEV_LOGGING_OUTPUTPATH` | No | `stdout` | Log destination: `stdout`, `stderr`, or a file path (rotated when a file) |
| `KANDEV_LOGGING_MAXSIZEMB` | No | `100` | Rotate the log file when it exceeds this size (MB). File output only. |
| `KANDEV_LOGGING_MAXBACKUPS` | No | `5` | Max rotated files to retain (`0` = unlimited). File output only. |
| `KANDEV_LOGGING_MAXAGEDAYS` | No | `30` | Max age of rotated files in days (`0` = unlimited). File output only. |
| `KANDEV_LOGGING_COMPRESS` | No | `true` | Gzip rotated files. File output only. |
| `KANDEV_DOCKER_ENABLED` | No | `false` | Enable Docker runtime for agents (see below) |
> **File-mode note:** when `KANDEV_LOGGING_OUTPUTPATH` is a file path, the active log file is created with mode `0600` (owner read/write only). Run any log shipper or sidecar as the same user, or use `stdout`/`stderr` and let the container runtime collect logs.
>
> **Upgrading from a pre-`KANDEV_HOME_DIR` image?** The SQLite DB path moved from `/data/kandev.db` to `/data/data/kandev.db`. The backend auto-migrates the legacy `kandev.db` (plus any `-wal`/`-shm` files) on first boot — look for `Migrated SQLite database from pre-KANDEV_HOME_DIR location` in the logs. If you prefer to pin the old location instead, set `-e KANDEV_DATABASE_PATH=/data/kandev.db`. If you previously set `KANDEV_DATA_DIR`, replace it with `KANDEV_HOME_DIR`.
### PostgreSQL
To use PostgreSQL instead of SQLite:
```bash
docker run -p 38429:38429 \
-e KANDEV_DATABASE_DRIVER=postgres \
-e KANDEV_DATABASE_HOST=host.docker.internal \
-e KANDEV_DATABASE_PORT=5432 \
-e KANDEV_DATABASE_USER=kandev \
-e KANDEV_DATABASE_PASSWORD=secret \
-e KANDEV_DATABASE_DBNAME=kandev \
ghcr.io/kdlbs/kandev:latest
```
## Port
Kandev exposes a single port. The Go backend serves the API, WebSocket, static SPA assets, and page boot data — all on one port:
| Port | Service |
|------|---------|
| `38429` | API + WebSocket + Web UI |
Override the port:
```bash
docker run -p 9080:9080 \
-v kandev-data:/data \
ghcr.io/kdlbs/kandev:latest \
kandev start --backend-port 9080
```
## Docker Compose
Create a `docker-compose.yml`:
```yaml
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
ports:
- "38429:38429"
volumes:
- kandev-data:/data
restart: unless-stopped
volumes:
kandev-data:
```
```bash
docker compose up -d
```
### With PostgreSQL
```yaml
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
ports:
- "38429:38429"
volumes:
- kandev-data:/data
environment:
KANDEV_DATABASE_DRIVER: postgres
KANDEV_DATABASE_HOST: postgres
KANDEV_DATABASE_PORT: "5432"
KANDEV_DATABASE_USER: kandev
KANDEV_DATABASE_PASSWORD: secret
KANDEV_DATABASE_DBNAME: kandev
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:17
environment:
POSTGRES_USER: kandev
POSTGRES_PASSWORD: secret
POSTGRES_DB: kandev
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kandev"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
kandev-data:
postgres-data:
```
## Reverse Proxy
Since Kandev serves everything on a single port, a reverse proxy only needs to forward all traffic to port 38429. No extra environment variables are needed — the frontend automatically uses `window.location.origin` to reach the API.
### Docker Compose with Caddy
```yaml
services:
kandev:
image: ghcr.io/kdlbs/kandev:latest
volumes:
- kandev-data:/data
restart: unless-stopped
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- caddy-data:/data
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
volumes:
kandev-data:
caddy-data:
```
Example `Caddyfile`:
```text
kandev.example.com {
reverse_proxy kandev:38429
}
```
## Docker-in-Docker (Agent Containers)
By default, `KANDEV_DOCKER_ENABLED=false` inside the container. To enable Docker-based agent execution, mount the Docker socket:
```bash
docker run -p 38429:38429 \
-v kandev-data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
-e KANDEV_DOCKER_ENABLED=true \
ghcr.io/kdlbs/kandev:latest
```
> **Note:** Mounting the Docker socket gives the container full access to the host's Docker daemon. Only do this in trusted environments.
## Upgrading
```bash
docker pull ghcr.io/kdlbs/kandev:latest
docker compose up -d # or: docker stop kandev && docker rm kandev && docker run ...
```
The volume at `/data` carries over the database, worktrees, npm globals, and `$HOME` for agent CLIs, so there is no manual migration step.
> **Upgrading across the `HOME=/data/home` change:** if you previously ran `docker exec` to `gh auth login` or log in to agent CLIs on a pre-`HOME=/data/home` image, that state lived in the ephemeral `/home/kandev` inside the container and is not carried over. Log in once on the new container and it will persist for all subsequent upgrades. If you want to preserve the old state, copy it onto the volume before recreating the container:
>
> ```bash
> docker exec kandev sh -c 'cp -a /home/kandev/. /data/home/ 2>/dev/null || true'
> ```
## Health Check
The backend exposes a `/health` endpoint:
```bash
curl http://localhost:38429/health
```
For Docker health checks in compose:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:38429/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
```
## Troubleshooting
```bash
# View logs
docker logs kandev
# Follow logs
docker logs -f kandev
# Shell into the container
docker exec -it kandev /bin/bash
# Check data volume
docker volume inspect kandev-data
```
+154
View File
@@ -0,0 +1,154 @@
# Git Operations for Worktrees
This document describes the architecture for Git operations (Pull, Push, Rebase, Merge, and Abort) on worktrees in Kandev.
## Overview
Git operations are executed **in agentctl**, not in the backend. This ensures consistent behavior whether running locally or in Docker/remote environments. The backend acts as a proxy, forwarding requests from the frontend WebSocket to agentctl's HTTP API.
## Supported Operations
| Operation | Direction | Description |
|-----------|-----------|-------------|
| **Pull** | Remote → Worktree | Fetch and merge changes from remote for the worktree's branch |
| **Push** | Worktree → Remote | Push local commits to remote |
| **Rebase** | Base → Worktree | Rebase worktree branch onto base branch (rewrites history) |
| **Merge** | Base → Worktree | Merge base branch into worktree branch (creates merge commit) |
| **Abort** | - | Abort an in-progress merge or rebase |
## Architecture
### Request Flow
```mermaid
flowchart TD
A["Frontend UI (Git Actions Dropdown)"] --> B["WebSocket Client"]
B -->|"worktree.pull/push/rebase/merge/abort"| C["Backend WebSocket Gateway"]
C --> D["Git Handlers"]
D -->|"GetExecutionBySessionID"| E["Lifecycle Manager"]
E --> F["AgentExecution"]
F --> G["agentctl.Client"]
G -->|"HTTP POST /api/v1/git/{operation}"| H["agentctl HTTP API"]
H --> I["Git Command Execution"]
I -->|"exec.Command git"| J["Worktree Directory"]
J --> K["HTTP Response"]
K --> L["Backend Handler"]
L --> M["WebSocket Response"]
M --> N["Frontend"]
```
### Notification Flow (Git Status Updates)
After any git operation, the workspace state changes. Updates flow through the event bus:
```mermaid
flowchart TD
A["agentctl WorkspaceTracker"] -->|"git_status message"| B["Backend StreamManager"]
B -->|"OnGitStatus callback"| C["EventPublisher.PublishGitStatus"]
C --> D["Event Bus"]
D --> E["TaskNotificationBridge"]
E --> F["Hub.BroadcastToSession"]
F --> G["Frontend WebSocket"]
G --> H["Zustand Store update"]
```
## agentctl API
### Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/git/pull` | Pull from remote |
| POST | `/api/v1/git/push` | Push to remote |
| POST | `/api/v1/git/rebase` | Rebase onto base branch |
| POST | `/api/v1/git/merge` | Merge base branch in |
| POST | `/api/v1/git/abort` | Abort merge/rebase |
### Request/Response Format
**Pull Request:**
```json
{ "rebase": false }
```
**Push Request:**
```json
{ "force": false, "set_upstream": true }
```
**Rebase/Merge Request:**
```json
{ "base_branch": "main" }
```
**Abort Request:**
```json
{ "operation": "merge" }
```
Every request also accepts an optional `repo` subpath for multi-repository task workspaces. Omit it for a single-repository workspace.
**Response (all operations):**
```json
{
"success": true,
"operation": "pull",
"output": "Already up to date.",
"error": null,
"conflict_files": []
}
```
## Locking
A per-instance mutex in agentctl prevents concurrent git operations on the same worktree. If an operation is already running, new requests return HTTP 409 Conflict.
## Conflict Handling
- **Rebase conflicts**: Automatically aborted (`git rebase --abort`), conflict files returned
- **Merge conflicts**: Left in place for user/agent resolution, conflict files returned
- **Abort endpoint**: Allows manual abort of conflicted merge/rebase
## Frontend Integration
### WebSocket Actions
- `worktree.pull` - Pull from remote
- `worktree.push` - Push to remote
- `worktree.rebase` - Rebase onto base
- `worktree.merge` - Merge base branch
- `worktree.abort` - Abort operation
### Hook Usage
```typescript
const { pull, push, rebase, merge, abort, isLoading, error } = useGitOperations(sessionId);
// In component
<Button onClick={() => pull()} disabled={isLoading}>Pull</Button>
<Button onClick={() => rebase("main")} disabled={isLoading}>Rebase</Button>
<Button onClick={() => merge("main")} disabled={isLoading}>Merge</Button>
<Button onClick={() => abort("rebase")} disabled={isLoading}>Abort rebase</Button>
```
`rebase` and `merge` require the base branch as their first argument. In a
multi-repository workspace, pass the repository subpath as the optional second
argument, for example `rebase("main", "kandev")`.
## Files
### agentctl
- `server/api/git.go` - HTTP handlers
- `server/process/git.go` - Git command execution
- `client/git.go` - Client methods
### Backend
- `internal/agent/handlers/git_handlers.go` - WebSocket handlers
- `pkg/websocket/actions.go` - Action constants
### Frontend
- `lib/types/backend.ts` - TypeScript types
- `hooks/use-git-operations.ts` - WebSocket action functions and React hook
+79
View File
@@ -0,0 +1,79 @@
# Kandev Feature Guide
This page expands the short feature list in the README without turning the README into a catalog.
## Agent And Task Workflows
- **Parallel task execution:** run and review multiple agent tasks at once.
- **Kanban and pipeline workflows:** define workflow steps, prompts, automations, and agent handoffs per step.
- **Workflow import/export:** export one workflow or every workflow in a workspace as portable YAML, then import it into another workspace or Kandev install. See [workflow-import-export.md](workflow-import-export.md).
- **Subtasks:** split work into child tasks that inherit the parent task's workspace, workflow, agent profile, executor, and repositories by default.
- **Multi-repository tasks:** attach several repositories to one task. Each repository gets its own worktree and is grouped separately in changes, review, and PR surfaces.
- **Multi-branch tasks:** attach multiple branches from the same repository to one task when the work needs to produce several PRs.
- **Task documents:** tasks can hold multiple markdown documents with revision history, including plans, specs, notes, reviews, and custom documents.
- **Task labels:** workspace labels are reusable, filterable, and visible on task cards and task detail.
- **Public share links:** publish a redacted task conversation snapshot as a secret GitHub Gist after previewing exactly what will be shared. Existing shares can be revoked.
## Agent Interfaces
- **ACP agents:** Kandev supports ACP-native and ACP-adapter agents such as Claude Code, Codex, GitHub Copilot, Gemini CLI, Amp, Auggie, OpenCode, Cursor, Qwen, Factory Droid, iFlow, Kilocode, Pi, Kimi, Kiro, Qoder, Trae, Oh My Pi, and Grok.
- **Bring-your-own TUI agents:** any agent CLI can run in a PTY terminal through CLI passthrough, even without ACP support.
- **Voice mode:** dictate chat prompts from the composer. Supported engines include browser Web Speech, local in-browser Whisper Web, and server-side Whisper. Settings include language, click-to-toggle or hold-to-talk activation, auto-send, Whisper model size, and keyboard shortcut.
- **Utility agents:** one-shot helpers can generate or improve prompt text, branch names, commit messages, commit descriptions, PR titles, PR descriptions, and session summaries. Users can choose a default model, override models per action, and add custom utility agents.
- **Custom prompts:** reusable prompts can be created in settings and invoked from chat.
- **Secrets:** named secrets can be stored once and reused by profiles or integrations without pasting values into every task.
## Executors And Runtime
- **Desktop app:** install Kandev as a Tauri desktop app that starts the local backend and shows the existing Kandev UI without requiring Node.js at runtime. See [desktop-app.md](desktop-app.md).
- **Executor types:** run agents as local host processes, git worktrees, Docker containers, remote SSH sessions, or Sprites cloud environments.
- **Executor profiles:** save per-runtime configuration, including prepare scripts, environment variables, credentials, and profile-specific settings.
- **Worktree isolation:** concurrent agents work in isolated git worktrees so their changes do not collide.
- **Per-task repository setup:** repositories can copy selected ignored files, such as `.env` files, into newly created worktrees.
- **Resource monitor:** optionally show CPU, memory, disk, CPU temperature, and load metrics in desktop task and kanban topbars. Kandev can also collect execution-environment metrics for Docker, SSH, Sprites, and other remote runtimes.
## Integrations And MCP
- **Integrations:** GitHub, GitLab, Jira, Linear, Sentry, and Slack connect external work back into Kandev tasks.
- **External MCP:** Kandev exposes streamable HTTP and SSE MCP endpoints so external coding agents can manage Kandev from outside the app. Settings include copyable snippets for Claude Code, Cursor, Codex, Auggie CLI, OpenCode, and GitHub Copilot CLI.
- **Automatic session MCP:** agents launched normally inside Kandev receive the Kandev task MCP automatically.
- **Passthrough MCP:** passthrough agents can also use the Kandev MCP endpoint while keeping their native CLI interface.
## What Task Agents Can Do Through Kandev MCP
Agents running on regular kanban tasks receive a task-scoped MCP surface. The available tools let agents coordinate work without leaving Kandev:
- **Discover workspace context:** list workspaces, workflows, workflow steps, repositories, agents, executor profiles, and tasks.
- **Work with multi-repo tasks:** task responses include attached repositories, base branches, checkout branches, and positions so agents can reason about each worktree separately.
- **Create tasks and subtasks:** create top-level tasks or child tasks, optionally auto-starting an agent. Subtasks inherit the parent workspace, workflow, agent profile, executor, and repositories unless the agent supplies an override.
- **Target sibling repositories:** create a subtask in a different repository while keeping it under the same parent task and workspace.
- **Coordinate dependencies:** create tasks with blocker relationships and inspect related parent, child, sibling, blocker, and blocked-by tasks.
- **Attach more branches:** add another `(repository, branch)` worktree to an existing worktree task. Use this for multiple PRs from one task, either in the same repository or across repositories.
- **Adjust diff bases:** update a task repository's base branch so changes, ahead/behind counts, and review context compare against the right target branch.
- **Move, archive, or delete tasks:** move tasks across workflow steps, including optional handoff prompts for the next agent, or clean up tasks when appropriate.
- **Message other tasks:** send a prompt to another task's primary session. Running tasks queue the message for the next turn; idle tasks receive it immediately; created sessions start with it.
- **Read conversations:** fetch a task's conversation history, with pagination and message-type filters.
- **Record structured plans:** create, read, update, and delete task plans.
- **Signal completion:** explicitly tell Kandev that a workflow step is complete, with summary, handoff notes, and blockers.
- **See associated PRs:** task-listing and related-task responses include associated GitHub pull requests when available, including PR number, URL, title, state, and merge time.
## In Progress: Office Mode
Office mode is currently feature-flagged and not documented as a live supported feature yet. The planned direction is a higher-level autonomy workspace with:
- Persistent agent instances with roles, permissions, skills, instructions, memory, status, and executor preferences.
- Agent dashboards, run history, inbox items, approvals, and human review gates.
- Task delegation across agent teams, including parent/child task coordination.
- Routines and scheduler-driven recurring work.
- Cost tracking, model usage visibility, and budget guardrails.
- Workspace configuration import/export and sync for agent/team setup.
We'll move Office into the live feature inventory after it ships.
## System Management
- **Feature toggles:** runtime feature flags can be viewed and overridden from Settings > System, with restart prompts when required.
- **Updates:** Kandev can check for newer releases and, in supported service installs, apply an update through the UI.
- **Backups and restore:** Kandev creates snapshots before version upgrades and supports manual snapshots, download, restore, and deletion from the system settings page.
- **Disk usage:** inspect storage used by worktrees, repositories, sessions, tasks, quick chat, and backups.
- **Logs, database status, about, and licenses:** system settings include operational views useful for self-hosted installs.
+290
View File
@@ -0,0 +1,290 @@
# Kubernetes Deployment Guide
This guide covers building the Kandev Docker image and deploying it to a Kubernetes cluster.
## Prerequisites
- Docker (for building the image)
- A container registry (Docker Hub, GHCR, ECR, etc.)
- A Kubernetes cluster with `kubectl` configured
- A StorageClass that supports `ReadWriteOnce` PVCs (for SQLite persistence)
## Building the Image
The root `Dockerfile` consumes a prebuilt Linux release bundle; it does not compile the repository inside Docker. On a Linux host matching the cluster architecture, prepare the same build context used by the release workflow:
```bash
make service-bundle
rm -rf ctx
mkdir -p ctx/bundle
cp -R dist/kandev/. ctx/bundle/
cp docker-entrypoint.sh ctx/
docker build -f Dockerfile -t kandev:latest ctx
```
For cross-architecture or multi-architecture images, produce the matching `kandev-linux-x64.tar.gz` and/or `kandev-linux-arm64.tar.gz` bundles first. For each platform, extract the bundle's top-level `kandev/` directory into `ctx/bundle/`, copy `docker-entrypoint.sh` into `ctx/`, and build that context for the matching platform. A `--platform` flag alone does not cross-compile the native binaries. See the [Docker guide](./docker.md#building-from-source) for the context layout.
### Using the Pre-built Image
Kandev publishes images to GitHub Container Registry. Pull directly:
```bash
docker pull ghcr.io/kdlbs/kandev:latest
```
Or reference it in your K8s deployment:
```yaml
image: ghcr.io/kdlbs/kandev:latest
```
### Choosing your image: vanilla vs. universal
Kandev publishes two flavors: the default vanilla image (smallest, npm-installable agent CLIs only) and a `:universal` image (~1.4 GB) that adds language toolchains (Go, Rust, build-essential), linters, and Playwright Chromium system libs - useful when your agents work on Go/Rust/Python projects or drive headless browsers.
```yaml
image: ghcr.io/kdlbs/kandev:universal
```
See the [repository image guide](https://github.com/kdlbs/kandev/blob/main/docs/images.md) for the full comparison, inclusion policy, and recipes for deriving your own image when you need something neither flavor includes.
## Deploying to Kubernetes
### Quick Start
```bash
# Apply all manifests
kubectl apply -f k8s/
# Check status
kubectl get pods -l app=kandev
kubectl logs -l app=kandev -f
```
### What Gets Created
| Resource | File | Purpose |
|----------|------|---------|
| Deployment | `deployment.yaml` | Single-replica pod running backend + web |
| Service | `service.yaml` | ClusterIP exposing port 38429 |
| ConfigMap | `configmap.yaml` | Non-sensitive environment configuration |
| PVC | `pvc.yaml` | 10Gi persistent volume for SQLite + worktrees |
| Ingress | `ingress.yaml` | Example ingress with WebSocket support |
### Accessing the UI
**Port-forward** (quickest for testing):
```bash
kubectl port-forward svc/kandev 38429:38429
# Open http://localhost:38429
```
**Ingress**: Edit `k8s/ingress.yaml` to set your domain, then apply. The ingress routes all traffic to the backend on port 38429; the Go backend serves API, WebSocket, and SPA traffic on that port.
### Custom Domain / Reverse Proxy
No extra configuration is needed. The frontend automatically uses `window.location.origin` to reach the API, which works with any domain, reverse proxy, or ingress setup.
## Installing Agent CLIs
The kandev image ships with `git`, `gh` (GitHub CLI), `node`, and `npm`, but **does not bundle the coding-agent CLIs** (`claude-code`, `codex`, `auggie`, etc.) — agent choice is per-user, and bundling all of them would bloat the image significantly.
> Looking to add tools *beyond* agent CLIs - language toolchains, build tools, internal CLIs? See the [repository image guide](https://github.com/kdlbs/kandev/blob/main/docs/images.md) for the universal-image option and recipes for deriving your own image.
To install an agent inside the running pod, open **Settings → Agents** in the UI and click **Install** on the agent card under "Available to Install". The backend runs the agent's hard-coded install script (`npm install -g <pkg>`) and rescans on success.
The image sets `NPM_CONFIG_PREFIX=/data/.npm-global` so user-installed npm globals land on the PV and **survive pod restarts and image upgrades**. The same persistence applies if you `kubectl exec` and install manually:
```bash
kubectl exec -it deployment/kandev -- npm install -g @anthropic-ai/claude-code
```
After installing, log in with the agent's own auth (e.g. `claude login`), then click **Rescan** on the agents page.
### Persistent agent and `gh` CLI auth
The image sets `HOME=/data/home` for the `kandev` user, so every CLI that writes its auth state under `$HOME` lands on the PV and survives pod restarts and image upgrades. This includes:
- `gh` CLI — `~/.config/gh/hosts.yml`
- Claude Code — `~/.claude/.credentials.json`, `~/.claude.json`
- Codex — `~/.codex/auth.json`, `~/.codex/config.toml`
- Auggie — `~/.augment/session.json`
- GitHub Copilot — `~/.copilot/...`
- OpenCode, Amp — `~/.config/<tool>/...`
So a one-time `kubectl exec -it deployment/kandev -- gh auth login` (or `claude login`, `codex login`, etc.) is enough; you do not need to redo it after `kubectl set image` or a `helm upgrade`.
> The GitHub PAT configured in **Settings → Integrations → GitHub** is stored as a secret in the SQLite DB (or your external Postgres) and has always persisted. The `HOME=/data/home` setup covers the separate `gh auth login` flow that the backend falls back to when no `GITHUB_TOKEN` secret is set.
## Configuration
Kandev reads configuration via `KANDEV_`-prefixed environment variables (Viper). Put only non-sensitive values in `k8s/configmap.yaml`; inject passwords and signing keys into the Deployment from a Kubernetes Secret.
### Core Settings
See [`configuration.md`](./configuration.md) for the full reference (every backend knob and its YAML form). The tables below cover what's most commonly set in K8s manifests.
| Env Var | Required | Default | Description |
|---------|----------|---------|-------------|
| `KANDEV_SERVER_PORT` | No | `38429` | Server port (API + WebSocket + Web UI) |
| `KANDEV_HOME_DIR` | No | `/data` | Kandev home directory - contains `data/` (DB), `tasks/`, `worktrees/`, `repos/`, `sessions/`, and `lsp-servers/` |
| `KANDEV_DATABASE_DRIVER` | No | `sqlite` | Database driver (`sqlite` or `postgres`) |
| `KANDEV_DATABASE_PATH` | No | `$KANDEV_HOME_DIR/data/kandev.db` | SQLite database file path (override) |
| `KANDEV_NATS_URL` | No | empty | Shared NATS event bus URL. Required when multiple backend replicas must share events; empty selects a process-local in-memory bus. |
| `KANDEV_DOCKER_ENABLED` | No | `false` | Enable Docker runtime for agents (requires DinD) |
| `KANDEV_LOG_LEVEL` | No | `info` | Log level: `debug`, `info`, `warn`, `error` |
| `KANDEV_LOGGING_FORMAT` | No | environment-selected (`json` in K8s) | Explicit format: `json` or `text`. The literal value `auto` is not accepted. |
| `KANDEV_LOGGING_OUTPUTPATH` | No | `stdout` | Log destination: `stdout`, `stderr`, or a file path (rotated when a file) |
| `KANDEV_LOGGING_MAXSIZEMB` | No | `100` | Rotate the log file when it exceeds this size (MB). File output only. |
| `KANDEV_LOGGING_MAXBACKUPS` | No | `5` | Max rotated files to retain (`0` = unlimited). File output only. |
| `KANDEV_LOGGING_MAXAGEDAYS` | No | `30` | Max age of rotated files in days (`0` = unlimited). File output only. |
| `KANDEV_LOGGING_COMPRESS` | No | `true` | Gzip rotated files. File output only. |
> **Logging in K8s:** prefer the default `stdout` so kubelet collects logs. If you set `KANDEV_LOGGING_OUTPUTPATH` to a file, the active log is created with mode `0600` (owner read/write only); any sidecar reading it must run as the same user.
>
> **Upgrading from a pre-`KANDEV_HOME_DIR` deployment?** The SQLite DB path moved from `/data/kandev.db` to `/data/data/kandev.db`, and `KANDEV_DATA_DIR` is gone — point `KANDEV_HOME_DIR` at the same volume mount (`/data`) instead. (`KANDEV_WORKTREE_BASEPATH` still works as an explicit override if you want to keep worktrees outside the home dir.) The backend auto-migrates the legacy `kandev.db` (plus any `-wal`/`-shm` files) on first boot — look for `Migrated SQLite database from pre-KANDEV_HOME_DIR location` in the pod logs. If you'd rather pin the old path, set `KANDEV_DATABASE_PATH=/data/kandev.db` in the ConfigMap.
Keep `KANDEV_DATABASE_PASSWORD`, `KANDEV_AUTH_JWTSECRET`, and `KANDEV_OFFICE_JWTSIGNINGKEY` out of ConfigMaps. Reference Secret keys from the Deployment instead:
```yaml
env:
- name: KANDEV_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: kandev-secrets
key: database-password
- name: KANDEV_AUTH_JWTSECRET
valueFrom:
secretKeyRef:
name: kandev-secrets
key: auth-jwt-secret
- name: KANDEV_OFFICE_JWTSIGNINGKEY
valueFrom:
secretKeyRef:
name: kandev-secrets
key: office-jwt-signing-key
```
### PostgreSQL Settings (when `KANDEV_DATABASE_DRIVER=postgres`)
| Env Var | Required | Default | Description |
|---------|----------|---------|-------------|
| `KANDEV_DATABASE_HOST` | No | `localhost` | PostgreSQL host |
| `KANDEV_DATABASE_PORT` | No | `5432` | PostgreSQL port |
| `KANDEV_DATABASE_USER` | Yes | `kandev` | Database user |
| `KANDEV_DATABASE_PASSWORD` | Usually | (empty) | Database password - required unless your Postgres allows passwordless auth |
| `KANDEV_DATABASE_DBNAME` | Yes | `kandev` | Database name |
| `KANDEV_DATABASE_SSLMODE` | No | `disable` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) |
## Database: SQLite vs PostgreSQL
### SQLite (default)
- Zero-config, works out of the box
- Database stored at `/data/data/kandev.db` on the PV (derived from `KANDEV_HOME_DIR=/data`)
- **Single replica only** (SQLite is single-writer)
- Deployment strategy is `Recreate` to prevent concurrent writes
- Good for small teams / personal use
### PostgreSQL (recommended for production)
- Supports multiple replicas for horizontal scaling when paired with shared NATS
- Change deployment strategy to `RollingUpdate`
- Set via environment variables:
```yaml
# Non-sensitive values in configmap.yaml
KANDEV_DATABASE_DRIVER: postgres
KANDEV_DATABASE_HOST: postgres.default.svc.cluster.local
KANDEV_DATABASE_PORT: "5432"
KANDEV_DATABASE_USER: kandev
KANDEV_DATABASE_DBNAME: kandev
KANDEV_NATS_URL: nats://nats.default.svc.cluster.local:4222
```
Supply `KANDEV_DATABASE_PASSWORD` from a Secret as shown above. When using Postgres, the PVC is still needed for worktree storage but the database itself is external. An empty `KANDEV_NATS_URL` selects an isolated in-memory event bus in each process, so notifications and orchestration events do not coordinate across replicas; do not scale beyond one backend replica without shared NATS.
## Persistent Storage
The PVC at `/data` stores:
- **SQLite database** (`/data/data/kandev.db`, `/data/data/kandev.db-wal`, `/data/data/kandev.db-shm`)
- **Git worktrees** (`/data/worktrees/`), **tasks** (`/data/tasks/`), **repos** (`/data/repos/`), **sessions** (`/data/sessions/`), and **LSP servers** (`/data/lsp-servers/`)
- **User home** (`/data/home/`) — `$HOME` for the in-pod `kandev` user; holds `gh` CLI auth and agent CLI auth state (see [Persistent agent and `gh` CLI auth](#persistent-agent-and-gh-cli-auth) above)
- **npm globals** (`/data/.npm-global/`) — agent CLIs installed via `npm install -g`
The PVC uses `ReadWriteOnce` access mode. If your cluster requires a specific StorageClass, add it to `k8s/pvc.yaml`:
```yaml
spec:
storageClassName: your-storage-class
```
## Health Checks
The deployment includes both probes on the `/health` endpoint:
- **Liveness probe**: Restarts the pod if the backend becomes unresponsive (30s interval, 3 failures)
- **Readiness probe**: Removes the pod from service during startup or issues (10s interval, 3 failures)
The CLI launcher also performs an internal health check — it waits for the backend to be healthy before starting the web server.
## Scaling
**Single replica (SQLite)**: The default configuration uses `replicas: 1` with `Recreate` strategy. This ensures only one instance writes to SQLite at a time.
**Multiple replicas (PostgreSQL + NATS)**: Switch to Postgres, configure the same `KANDEV_NATS_URL` on every replica, change the deployment strategy to `RollingUpdate`, and increase replicas:
```yaml
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
```
## Upgrading
Rebuild the release bundle and refresh `ctx/` using [Building the Image](#building-the-image) before running the image build below.
```bash
# Build and push new image
docker build -f Dockerfile -t your-registry.com/kandev:v1.1.0 ctx
docker push your-registry.com/kandev:v1.1.0
# Update deployment
kubectl set image deployment/kandev kandev=your-registry.com/kandev:v1.1.0
# Or edit the deployment directly
kubectl edit deployment kandev
```
SQLite migrations run automatically on startup — no manual migration step needed.
> **Upgrading across the `HOME=/data/home` change:** if you used `kubectl exec` to `gh auth login` or log in to agent CLIs on a pre-`HOME=/data/home` image, that state lived in the ephemeral `/home/kandev` and is not carried over. Log in once on the new pod and it will persist for all subsequent upgrades. If you want to keep the old state, copy it onto the PV before upgrading:
>
> ```bash
> kubectl exec deployment/kandev -- sh -c 'cp -a /home/kandev/. /data/home/ 2>/dev/null || true'
> ```
## Troubleshooting
```bash
# Check pod status
kubectl get pods -l app=kandev
# View logs
kubectl logs -l app=kandev -f
# Shell into the pod (if needed)
kubectl exec -it deployment/kandev -- /bin/bash
# Check PVC status
kubectl get pvc kandev-data
# Describe pod for events
kubectl describe pod -l app=kandev
```
+60
View File
@@ -0,0 +1,60 @@
# Remote Cloud Environment Instructions
Setup notes and caveats for developing Kandev in ephemeral cloud VMs (Cursor Cloud, Codex, GitHub Codespaces, or similar sandboxed environments).
## Runtime requirements
- **mise** — `mise.toml` at the repo root pins the developer toolchain (Go 1.26, Node.js 24, pnpm 9.15.9, golangci-lint 2.9.0, go-licenses 1.6.0, pre-commit, etc.).
- **gcc / sqlite headers** — required for CGO (SQLite FTS5). `scripts/bootstrap-dev-env` installs these on common Linux/macOS package managers when it has permission.
- **Azure Repos PR creation (optional)** — only needed when testing `worktree.create_pr` against Azure remotes outside the published Docker image:
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (`az`) on `PATH`
- DevOps extension: `az extension add --name azure-devops`
- Auth: `az login` or `export AZURE_DEVOPS_EXT_PAT=<pat>`
- The `ghcr.io/kdlbs/kandev` image ships `gh`, `az`, and the `azure-devops` extension preinstalled (see root `Dockerfile`).
- Host routing table: `apps/backend/internal/agentctl/AGENTS.md`.
Provider prepare scripts should do auth + clone first, then run the shared bootstrap from the repo root:
```bash
scripts/bootstrap-dev-env
```
For E2E-capable environments:
```bash
scripts/bootstrap-dev-env --with-e2e
```
## Generated files before typecheck
Before running `make typecheck`, you must generate two files that are gitignored:
```bash
node apps/web/scripts/generate-release-notes.mjs
node apps/web/scripts/generate-changelog.mjs
```
Without these, `tsc` fails with missing module errors for `@/generated/release-notes.json` and `@/generated/changelog.json`.
## Running dev mode
`make dev` from the repo root builds the backend and starts both the Go backend (port 38429) and Vite frontend (port 37429). The CLI launcher sets `KANDEV_DEBUG_DEV_MODE=true` which activates the `dev` profile from `profiles.yaml`, enabling mock agent and other dev conveniences. No external services (database, message queue, Docker) are needed — SQLite is embedded and the event bus runs in-memory.
## Key commands
All documented in the root `Makefile`:
- `make dev` — build + start backend + web (dev mode)
- `make test` — backend (Go) + web (vitest) + CLI tests
- `make lint` — golangci-lint + ESLint
- `make typecheck` — TypeScript type-checking across all apps
- `make fmt` — format all code (run before lint to avoid false positives)
- `make test-e2e` — Playwright E2E tests (requires `make build-backend && make build-web` first). Uses `e2e/playwright.config.ts`. 1000+ specs.
## Firecracker / cloud-VM caveats
These apply to Cursor Cloud, Codex, and similar Firecracker-backed sandboxes:
- **tool PATH**: `scripts/bootstrap-dev-env` activates mise for its own process. New shells should run `eval "$(mise activate bash)"` or use a shell profile that activates mise so `node`, `pnpm`, `go`, `golangci-lint`, and `pre-commit` resolve from the pinned toolchain.
- **First page load**: `make dev` compiles pages on first visit via Turbopack — expect ~25 s for the initial load.
- **CLI test flake**: `src/ports.test.ts > isPortInUse > returns false within the timeout when the host black-holes packets` fails because `192.0.2.1` behaves differently inside Firecracker. This is a known environment-specific flake, not a code issue.
- **Playwright browser installation**: `playwright install chromium` hangs during zip extraction (Node.js io_uring incompatibility with the Firecracker kernel). Workaround: `scripts/install-playwright-browsers.sh` runs the install with a timeout, then falls back to extracting the already-downloaded zips with `unzip`. `scripts/bootstrap-dev-env --with-e2e` calls this automatically. System deps can be installed via `playwright install-deps chromium`.
+219
View File
@@ -0,0 +1,219 @@
# Run Kandev as a Service
Install Kandev as an OS-managed service (systemd on Linux, launchd on macOS) so it auto-starts and stays running. User-mode services installed by `kandev service install` can self-update from the System → Updates page. Non-service installs and `--system` services still update manually: `npm i -g kandev@latest` or `brew upgrade kandev`, then re-run `kandev service install`.
This guide assumes you've already installed kandev via [Homebrew or npm](cli.md#installation) and that `kandev` works when run interactively.
> **Windows:** not yet supported. See [open issues mentioning Windows](https://github.com/kdlbs/kandev/issues?q=is%3Aissue+windows) for SCM support progress, or open a new one if there isn't one yet.
## Quick Start
```bash
# Laptop / single-user — runs as you, starts when you log in:
kandev service install
# Linux VPS / shared box — runs at boot, no login required:
sudo kandev service install --system
```
After install, kandev is reachable at `http://localhost:38429` (or `--port <N>` if you passed it).
## Run the Current Checkout as a Service
When developing from a cloned repo, use the root Make targets instead of the globally installed `kandev` binary. They install dependencies, build the currently checked-out branch, assemble a local release-style bundle under `dist/kandev`, and install the service with `KANDEV_BUNDLE_DIR` pointing at that bundle.
```bash
git checkout my-branch
make service-install
```
Useful targets:
```bash
make service-install # user service from the current checkout
make service-install PORT=3000
make service-install HOME_DIR=/path/to/kandev-home
make service-install NO_BOOT_START=1
make service-install-system # system service install; other targets below use the user service
make service-status
make service-logs
make service-logs-follow
make service-start
make service-stop
make service-restart
make service-uninstall
make service-config
```
The service runs the built snapshot in `dist/kandev`, not live source files. After switching branches or changing code, rerun `make service-install` to rebuild and refresh the service unit. Checkout-based services are marked as a local install kind, so the System → Updates page will not offer one-click npm/Homebrew self-update; update by rebuilding from the desired branch.
## User Mode vs `--system` Mode
| | **User mode** (default) | **`--system` mode** |
| -------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Install requires sudo | No | Yes |
| Unit location (Linux) | `~/.config/systemd/user/kandev.service` | `/etc/systemd/system/kandev.service` |
| Unit location (macOS) | `~/Library/LaunchAgents/com.kdlbs.kandev.plist` | `/Library/LaunchDaemons/com.kdlbs.kandev.plist` |
| Daemon runs as | You | `$SUDO_USER` if invoked via `sudo`, else the current user |
| Auto-starts on reboot | **Linux:** only after `sudo loginctl enable-linger $USER` (run once). **macOS:** only at next login. | Always, regardless of login state |
| Survives logout / SSH disconnect | **Linux:** yes, after linger. **macOS:** no (use `--system` instead). | Yes |
| Default `KANDEV_HOME_DIR` | `~/.kandev` | `/var/lib/kandev` |
| Logs | `journalctl --user-unit kandev` (Linux) · `~/.kandev/logs/service.err` (macOS) | `sudo journalctl -u kandev` (Linux) · `/var/lib/kandev/logs/service.err` (macOS) |
| Best for | Laptop, workstation | Headless Linux VPS, Mac mini server, shared box |
**30-second rule of thumb: VPS → `--system`. Laptop → default.**
Linux user-mode with `loginctl enable-linger` is functionally equivalent to system mode for a single-user VPS, but the linger one-liner itself requires sudo — so you're not avoiding sudo, just deferring it. For a VPS, `--system` is one less thing to remember after a reboot.
## Commands
```bash
kandev service install [--system] [--port <port>] [--home-dir <path>] [--no-boot-start]
kandev service uninstall [--system]
kandev service start|stop|restart|status [--system]
kandev service logs [-f] [--system]
kandev service config [--system]
```
| Command | What it does |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `install` | Writes the unit file, reloads the service manager, enables auto-start, and starts the service. Then polls `/health` for up to 30s and dumps logs if it doesn't come up. |
| `uninstall` | Stops the service, disables auto-start, removes the unit file. |
| `start` / `stop` / `restart` | Control the running service without touching the unit file. |
| `status` | Print the OS service-manager view (systemd / launchctl). |
| `logs [-f]` | Dump the last 200 lines, or stream if `-f`. journalctl on Linux; `tail` on macOS log files. |
| `config` | Print the resolved paths, env vars, and whether the service is currently installed / active. Useful for diagnosis — read-only, no privileges needed. |
### Flags
- `--system` — system-level install. Requires sudo. See the comparison above.
- `--port <N>` — bake `KANDEV_SERVER_PORT=<N>` into the unit. Defaults to 38429.
- `--home-dir <PATH>` — bake `KANDEV_HOME_DIR=<PATH>` into the unit. Defaults to `~/.kandev` (user mode) or `/var/lib/kandev` (`--system`).
- `--no-boot-start` — Linux user-mode only. Skip the `loginctl enable-linger` hint at the end of install.
- `-f`, `--follow` — only for `logs`. Stream rather than dump.
## After an Upgrade
If Kandev is running as a user-mode service installed by `kandev service install`, open **Settings → System → Updates** and use **Apply update** when it appears. The button is shown only when the backend can prove it is running from a kandev-managed service unit/plist with valid service metadata.
Both npm runtime packages and Homebrew install kandev under versioned package-manager paths. Manual upgrades replace those paths, so the unit file must be refreshed afterward.
**Manual fix:** re-run install. It's idempotent.
```bash
npm i -g kandev@latest # or: brew upgrade kandev
kandev service install # rewrites the unit with the new paths
```
If you launch `kandev` interactively after an upgrade, it will detect a stale unit and print a one-line reminder. You can also check with `kandev service config` to see the paths that would be baked in by the next install.
System services (`kandev service install --system`) do not expose UI self-update. Update them from a privileged shell, then re-run `sudo kandev service install --system`.
## Linux Boot-Start (`loginctl enable-linger`)
Linux user services normally run only while you're logged in. To keep kandev running across reboots without an active SSH session, run **once**:
```bash
sudo loginctl enable-linger $USER
```
After this, your user's systemd instance is started at boot by `systemd-logind`, and your enabled user units (including kandev) start with it. To disable later:
```bash
sudo loginctl disable-linger $USER
```
If you'd rather not deal with linger and you're already comfortable with sudo, install with `--system` instead — it sidesteps the issue entirely.
## What's Inside the Unit File
The unit hard-codes absolute paths so it works in the empty `PATH` that systemd/launchd give a fresh service. When Node tooling is installed through a per-user manager such as nvm, fnm, asdf, Volta, or mise, `kandev service install` also bakes the detected `node`/`npm`/`npx` bin directory into `PATH`; re-run the install command after changing the active Node version. For fnm, the installer resolves multishell symlinks when possible so the unit records the stable Node installation path instead of the temporary multishell directory. If the service file still points at a stale fnm multishell path, re-run `kandev service install` from a shell where `node`, `npm`, and `npx` resolve through valid symlinks. For `--system` installs, some `sudo` configurations reset `PATH` with `secure_path`; run the install command from an environment where `node`, `npm`, and `npx` still resolve, or put stable symlinks in the run-as user's `~/.local/bin`.
A typical Linux user unit looks like:
```ini
# managed by kandev — regenerated by `kandev service install`
[Unit]
Description=Kandev autonomous agent platform
Documentation=https://github.com/kdlbs/kandev
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/lib/node_modules/@kdlbs/runtime-linux-x64/bin/kandev --headless
Environment=KANDEV_HOME_DIR=/home/alice/.kandev
Environment=KANDEV_LOG_LEVEL=info
Environment=PATH=%h/.local/bin:%h/.bun/bin:%h/.opencode/bin:/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin
Environment=KANDEV_RUNNING_AS_SERVICE=true
Environment=KANDEV_SERVICE_MODE=user
Environment=KANDEV_SERVICE_MANAGER=systemd
Environment=KANDEV_INSTALL_KIND=npm
Environment=KANDEV_SERVICE_METADATA=/home/alice/.kandev/service/install.json
Restart=on-failure
RestartSec=5s
KillMode=mixed
TimeoutStopSec=30s
[Install]
WantedBy=default.target
```
The `--headless` flag tells the CLI not to open a browser (you'll connect to it remotely or via `localhost`). The `KANDEV_SERVICE_*` variables and `<home>/service/install.json` metadata let the backend verify that UI self-update is safe before it shows the Apply button.
## Troubleshooting
### `systemctl: command not found` / `launchctl: command not found`
Kandev's service support requires either systemd (most Linux distros) or launchd (macOS). It does not currently support OpenRC, SysV init, or Windows SCM. You can still run kandev as a daemonized process with `nohup` / `screen` / `tmux`, or wrap it in your init system of choice using the launcher info from `kandev service config`.
### "service did not become healthy within 30s"
The install succeeded (unit file written, service told to start) but kandev's HTTP `/health` endpoint never responded. `install` dumps the last 50 lines of logs when this happens — common causes:
- Port already in use → pass `--port <other>`.
- Cold-disk + slow first launch → re-run `kandev service install`; the second start is usually fast enough.
- Missing dependency on the unit's `PATH` (e.g. `git`, `docker`) → install the missing tool and `kandev service restart`.
### The unit warns about a "file that doesn't look like a kandev-managed file"
`kandev service install` refuses to silently clobber files it didn't write. If you (or another tool) had previously put something at `~/.config/systemd/user/kandev.service` or the equivalent on macOS, install will:
1. Copy the existing file to `<path>.bak`
2. Write the kandev unit in its place
3. Print a `WARNING` line so you notice
Inspect the `.bak` if you're not sure what was there.
### Service runs as `root` when you wanted it to run as your user
This happens with `--system` if `SUDO_USER` isn't set (e.g. you logged in as root directly rather than `sudo`'ing). Either run install via `sudo` from your normal user, or hand-edit the `User=` (Linux) / `UserName=` (macOS) directive in the unit file.
### After upgrading, the service silently keeps running the old version
The OS service manager keeps running whatever `ExecStart` it has — it doesn't know about npm/brew upgrades. For managed user services, use **Apply update** on the Updates page. Otherwise, **always re-run `kandev service install` after an upgrade** so the unit picks up the new paths, then `kandev service restart` to pick up new code.
## Updating: TL;DR
```bash
# Option A: managed user service
# Use Settings -> System -> Updates -> Apply update
# Option B: manual / system service
# 1. Update the binary
npm i -g kandev@latest # or: brew upgrade kandev
# 2. Refresh the unit file (rewrites paths to point at the new version)
kandev service install # add --system if that's what you used originally
# 3. (install does this automatically) Verify it came back up
kandev service status
```
## Uninstalling
```bash
kandev service uninstall # or: sudo kandev service uninstall --system
```
This stops the service, removes its unit/plist, and reloads the service manager. Data in `~/.kandev` (or `/var/lib/kandev`) is left intact — delete it manually if you want a clean slate.
+467
View File
@@ -0,0 +1,467 @@
# Kandev WebSocket API
This page documents the WebSocket envelope, current domain model, and the request actions used by the main task and agent workflow. It is not a substitute for the Go request types: payloads evolve with the application.
## Scope and source of truth
Kandev uses one persistent WebSocket connection for browser request/response traffic and real-time notifications. This applies to WebSocket-backed operations only; Kandev also has dedicated HTTP APIs. For example, workflow import and export use HTTP routes because they transfer portable documents.
Use these sources when checking an action:
- [`apps/backend/pkg/websocket/actions.go`](../../apps/backend/pkg/websocket/actions.go) defines action names and error codes.
- Registered handlers such as [`task_handlers.go`](../../apps/backend/internal/task/handlers/task_handlers.go) and [`handlers.go`](../../apps/backend/internal/orchestrator/handlers/handlers.go) define which action constants accept client requests and their payload contracts.
- [`apps/backend/pkg/websocket/message.go`](../../apps/backend/pkg/websocket/message.go) defines the message envelope.
An action constant by itself does not guarantee a request handler. Some constants are server-to-client notifications only.
**Local endpoint:** `ws://localhost:38429/ws`
Use `wss://` when Kandev is served over HTTPS.
## Message envelope
### Request
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "request",
"action": "workflow.list",
"payload": {
"workspace_id": "workspace-uuid"
},
"timestamp": "2026-07-15T09:00:00Z"
}
```
### Response
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "response",
"action": "workflow.list",
"payload": {
"workflows": [],
"total": 0
},
"timestamp": "2026-07-15T09:00:00Z"
}
```
### Notification
```json
{
"type": "notification",
"action": "task.updated",
"payload": {
"id": "task-uuid",
"title": "Update authentication"
},
"timestamp": "2026-07-15T09:00:01Z"
}
```
### Error
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "error",
"action": "task.get",
"payload": {
"code": "NOT_FOUND",
"message": "Task not found"
},
"timestamp": "2026-07-15T09:00:01Z"
}
```
| Field | Type | Contract |
|-------|------|----------|
| `id` | string | Correlates a request with its response or error. Notifications omit it. |
| `type` | string | `request`, `response`, `notification`, or `error`. |
| `action` | string | Registered action name. |
| `payload` | object | Action-specific request or response data. |
| `timestamp` | string | ISO 8601 timestamp. Server responses, notifications, and errors always include it. Client requests may omit it; the backend does not use it for request correlation. |
| `metadata` | object | Optional string-to-string metadata used by selected internal flows. |
Error codes currently include `BAD_REQUEST`, `NOT_FOUND`, `INTERNAL_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `VALIDATION_ERROR`, `CONFLICT`, and `UNKNOWN_ACTION`.
## Current domain model
```mermaid
flowchart LR
Workspace --> Workflow
Workflow --> WorkflowStep["Workflow step"]
WorkflowStep --> Task
Task --> Session
Session --> Message
Session --> Agent
```
The common request flow is:
1. List or create a workspace.
2. List or create a workflow in that workspace.
3. List the workflow steps.
4. Create a task with `workspace_id`, `workflow_id`, and `workflow_step_id`.
5. Subscribe to the task and session for live updates.
6. Start work with `session.launch`.
7. Send user turns with `message.add`, or use `message.queue.*` while a turn is active.
8. Stop a session with `session.stop`, or update the task with `task.state`.
## Registered request actions
The tables below cover the core task workflow. Check `actions.go` and handler registration for integrations and less common operational actions.
### Workspaces and workflows
| Action | Purpose |
|--------|---------|
| `workspace.list` | List workspaces. |
| `workspace.create` | Create a workspace. |
| `workspace.get` | Get a workspace by ID. |
| `workspace.update` | Update a workspace. |
| `workspace.delete` | Delete a workspace. |
| `workflow.list` | List workflows, optionally scoped by `workspace_id`. |
| `workflow.create` | Create a workflow in a workspace. |
| `workflow.get` | Get a workflow by ID. |
| `workflow.update` | Update a workflow. |
| `workflow.delete` | Delete a workflow. |
| `workflow.reorder` | Reorder workflows in a workspace. |
| `workflow.template.list` | List workflow templates. |
| `workflow.template.get` | Get a workflow template. |
| `workflow.step.list` | List steps for a workflow. |
| `workflow.step.get` | Get a workflow step. |
| `workflow.step.create` | Create a workflow's steps from a template. |
| `workflow.history.list` | List workflow history for a session. |
Direct create, update, delete, and reorder operations for individual workflow steps are exposed through the HTTP workflow API and configuration-mode MCP tools. They are not registered as ordinary WebSocket requests.
### Tasks
| Action | Purpose |
|--------|---------|
| `task.list` | List tasks for a workflow. |
| `task.create` | Create a task. |
| `task.get` | Get a task. |
| `task.update` | Update task fields. |
| `task.repository.update` | Update a task repository's base branch. |
| `task.delete` | Delete a task. |
| `task.move` | Move a task to a workflow step. |
| `task.state` | Set the task state. |
| `task.archive` | Archive or unarchive a task. |
| `task.session.list` | List sessions for a task. |
| `task.session.status` | Get task session status. |
| `task.plan.create` | Create a task plan. |
| `task.plan.get` | Get a task plan. |
| `task.plan.update` | Update a task plan. |
| `task.plan.delete` | Delete a task plan. |
| `task.plan.revisions.list` | List plan revisions. |
| `task.plan.revision.get` | Get a plan revision. |
| `task.plan.revert` | Revert a plan revision. |
### Sessions, messages, and agents
| Action | Purpose |
|--------|---------|
| `session.launch` | Unified prepare, start, resume, workflow-step, or workspace-restore operation. |
| `session.ensure` | Return an existing task session or create one. |
| `session.recover` | Resume, fresh-start, or cancel a transient retry. |
| `session.reset_context` | Reset an agent session's context. |
| `session.stop` | Stop a session. |
| `session.delete` | Delete a session. |
| `session.set_primary` | Set the primary session for a task. |
| `session.set_plan_mode` | Enable or disable plan mode. |
| `message.add` | Persist a user message and forward it to the session when appropriate. |
| `message.list` | List session messages. |
| `message.search` | Search messages. |
| `message.queue.add` | Queue a message for a busy session. |
| `message.queue.get` | Read queued messages and status. |
| `message.queue.update` | Replace queued message content. |
| `message.queue.append` | Append content to a queued message. |
| `message.queue.drain` | Dispatch one queued entry when the session can accept it. |
| `message.queue.remove` | Remove one queued entry. |
| `message.queue.cancel` | Clear the session queue. |
| `agent.list` | List agent executions. |
| `agent.launch` | Low-level agent launch. Prefer `session.launch` for task sessions. |
| `agent.status` | Get agent status. |
| `agent.logs` | Get agent logs. |
| `agent.stop` | Stop an agent execution. |
| `agent.cancel` | Cancel the active agent turn. |
| `agent.types` | List available agent types. |
Agent and task-session lifecycle states are uppercase on the wire. For example,
agent states include `PENDING`, `STARTING`, `RUNNING`, `READY`, `COMPLETED`,
`FAILED`, and `STOPPED`.
### Orchestrator and subscriptions
Only these orchestrator request actions are registered:
| Action | Purpose |
|--------|---------|
| `orchestrator.status` | Get orchestrator status. |
| `orchestrator.queue` | Get queued tasks. |
| `orchestrator.stop` | Stop execution for a task. |
Lifecycle starts and user turns use `session.launch` and `message.add`; task completion uses `task.state` or workflow automation.
Subscription requests are handled by the WebSocket gateway:
| Action | Payload key |
|--------|-------------|
| `task.subscribe` / `task.unsubscribe` | `task_id` |
| `session.subscribe` / `session.unsubscribe` | `session_id` |
| `session.focus` / `session.unfocus` | `session_id` |
| `user.subscribe` / `user.unsubscribe` | user-scoped connection state |
| `run.subscribe` / `run.unsubscribe` | `run_id` |
| `system.metrics.subscribe` / `system.metrics.unsubscribe` | no resource ID |
## Core payload examples
### List workflow steps
```json
{
"id": "request-steps",
"type": "request",
"action": "workflow.step.list",
"payload": {
"workflow_id": "workflow-uuid"
},
"timestamp": "2026-07-15T09:01:00Z"
}
```
### Create a task
`workspace_id`, `workflow_id`, and `title` are required. `workflow_step_id` selects the initial step and should be supplied by normal clients.
```json
{
"id": "request-create-task",
"type": "request",
"action": "task.create",
"payload": {
"workspace_id": "workspace-uuid",
"workflow_id": "workflow-uuid",
"workflow_step_id": "step-uuid",
"title": "Implement feature X",
"description": "Detailed requirements",
"priority": "high",
"repositories": [
{
"repository_id": "repository-uuid",
"base_branch": "main"
}
]
},
"timestamp": "2026-07-15T09:02:00Z"
}
```
To create and immediately start a task, also send `start_agent: true` and a valid `agent_profile_id`. The response then includes `session_id` and `agent_execution_id` when launch succeeds.
### Move a task
```json
{
"id": "request-move-task",
"type": "request",
"action": "task.move",
"payload": {
"id": "task-uuid",
"workflow_id": "workflow-uuid",
"workflow_step_id": "target-step-uuid",
"position": 0
},
"timestamp": "2026-07-15T09:03:00Z"
}
```
### Launch a session
`session.launch` accepts these intents: `prepare`, `start`, `start_created`, `resume`, `workflow_step`, and `restore_workspace`. If `intent` is omitted, the backend infers it from the other fields.
```json
{
"id": "request-launch-session",
"type": "request",
"action": "session.launch",
"payload": {
"task_id": "task-uuid",
"intent": "start",
"agent_profile_id": "profile-uuid",
"prompt": "Implement the task and run focused tests"
},
"timestamp": "2026-07-15T09:04:00Z"
}
```
```json
{
"id": "request-launch-session",
"type": "response",
"action": "session.launch",
"payload": {
"success": true,
"task_id": "task-uuid",
"session_id": "session-uuid",
"agent_execution_id": "execution-uuid",
"state": "RUNNING"
},
"timestamp": "2026-07-15T09:04:01Z"
}
```
### Send a user turn
```json
{
"id": "request-add-message",
"type": "request",
"action": "message.add",
"payload": {
"task_id": "task-uuid",
"session_id": "session-uuid",
"content": "Also add regression coverage"
},
"timestamp": "2026-07-15T09:05:00Z"
}
```
### Mark a task complete
```json
{
"id": "request-complete-task",
"type": "request",
"action": "task.state",
"payload": {
"id": "task-uuid",
"state": "COMPLETED"
},
"timestamp": "2026-07-15T09:06:00Z"
}
```
## Notifications
Common server notifications include:
- Task lifecycle: `task.created`, `task.updated`, `task.deleted`, `task.state_changed`.
- Workflow lifecycle: `workflow.created`, `workflow.updated`, `workflow.deleted`, `workflow.step.created`, `workflow.step.updated`, `workflow.step.deleted`.
- Session messages and state: `session.message.added`, `session.message.updated`, `session.message.deleted`, `session.state_changed`, `session.waiting_for_input`, `session.turn.started`, `session.turn.completed`.
- Agent stream compatibility events: `acp.progress`, `acp.log`, `acp.result`, `acp.error`, `acp.status`, `acp.heartbeat`.
- Permission flow: `permission.requested`; clients answer with `permission.respond`. Its payload requires `session_id` and `pending_id`, plus `option_id` unless `cancelled` is `true`. The optional `rejected` flag distinguishes an explicit denial from dismissing the request.
- Repository and executor lifecycle events matching the `*.created`, `*.updated`, and `*.deleted` action constants.
Subscribe to the relevant task and session before depending on resource-scoped notifications.
## Browser request helper
```typescript
type PendingRequest = {
resolve: (message: unknown) => void;
reject: (error: Error) => void;
};
const pending = new Map<string, PendingRequest>();
const socket = new WebSocket('ws://localhost:38429/ws');
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
const request = message.id ? pending.get(message.id) : undefined;
if (request) {
pending.delete(message.id);
request.resolve(message);
}
});
function rejectPending(reason: string) {
const error = new Error(reason);
for (const request of pending.values()) request.reject(error);
pending.clear();
}
socket.addEventListener('close', () => rejectPending('WebSocket closed'));
socket.addEventListener('error', () => rejectPending('WebSocket failed'));
async function waitForSocketOpen() {
if (socket.readyState === WebSocket.OPEN) return;
if (socket.readyState !== WebSocket.CONNECTING) {
throw new Error('WebSocket is not open');
}
await new Promise<void>((resolve, reject) => {
const cleanup = () => {
socket.removeEventListener('open', handleOpen);
socket.removeEventListener('error', handleError);
socket.removeEventListener('close', handleClose);
};
const handleOpen = () => {
cleanup();
resolve();
};
const handleError = () => {
cleanup();
reject(new Error('WebSocket failed to open'));
};
const handleClose = () => {
cleanup();
reject(new Error('WebSocket closed before opening'));
};
socket.addEventListener('open', handleOpen);
socket.addEventListener('error', handleError);
socket.addEventListener('close', handleClose);
});
}
async function request(action: string, payload: Record<string, unknown>) {
await waitForSocketOpen();
const id = crypto.randomUUID();
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
try {
socket.send(JSON.stringify({
id,
type: 'request',
action,
payload,
timestamp: new Date().toISOString(),
}));
} catch (error) {
pending.delete(id);
reject(error instanceof Error ? error : new Error('WebSocket send failed'));
}
});
}
await request('task.subscribe', { task_id: 'task-uuid' });
const launch = await request('session.launch', {
task_id: 'task-uuid',
intent: 'start',
agent_profile_id: 'profile-uuid',
prompt: 'Implement the task',
});
```
Production clients should also enforce request timeouts and route `type: "error"` responses into typed errors.
## HTTP operations that remain separate
Workflow document transfer is intentionally HTTP-based:
| Method | Route | Purpose |
|--------|-------|---------|
| `GET` | `/api/v1/workflows/:id/export` | Export one workflow. |
| `GET` | `/api/v1/workspaces/:id/workflows/export` | Export workflows from a workspace. |
| `POST` | `/api/v1/workspaces/:id/workflows/import` | Import a portable workflow document. |
Other focused HTTP routes also exist for snapshots, file transfer, health checks, and agentctl-local control APIs. Do not infer that a WebSocket action replaces every HTTP endpoint.
+123
View File
@@ -0,0 +1,123 @@
# Windows Support (WSL)
Kandev runs on Windows through WSL (Windows Subsystem for Linux).
## Prerequisites
- Windows 10 (version 2004+) or Windows 11
- WSL 2 with a Linux distribution installed (Ubuntu recommended)
## Setup
### 1. Install WSL
From PowerShell (as Administrator):
```powershell
wsl --install
```
Restart your machine if prompted, then open your Linux distribution from the Start menu.
### 2. Install system dependencies
```bash
sudo apt update
sudo apt install build-essential libatomic1 git
```
- `build-essential` provides `gcc`, required by `go-sqlite3` (CGO)
- `libatomic1` is required by Node.js
### 3. Install mise (runtime manager)
```bash
curl https://mise.run | sh
```
Add mise to your shell profile:
```bash
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
source ~/.bashrc
```
### 4. Install runtimes
```bash
mise use -g node
mise use -g pnpm
mise use -g go
```
### 5. Clone and install
```bash
git clone https://github.com/kdlbs/kandev.git kandev
cd kandev
make install
```
### 6. Run
```bash
make dev # development mode
make start # production mode
```
## Troubleshooting
### `libatomic.so.1: cannot open shared object file`
Node.js requires `libatomic1`. Install it:
```bash
sudo apt install libatomic1
```
### `pnpm: command not found`
Ensure pnpm is installed and activated via mise:
```bash
mise use -g pnpm
```
### `Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo`
The SQLite driver needs a C compiler. Install `build-essential`:
```bash
sudo apt install build-essential
```
### Browser doesn't auto-open
WSL interop must be enabled so the CLI can launch your Windows browser. Ensure `/etc/wsl.conf` contains:
```ini
[interop]
enabled=true
appendWindowsPath=true
```
Then restart WSL from PowerShell:
```powershell
wsl --shutdown
```
### `cd: can't cd to apps` during `make install`
The Makefile cannot find the repository's `apps/` directory. Run the command
from the root of a complete Kandev checkout:
```bash
cd /path/to/kandev
test -d apps
make install
```
If `test -d apps` fails, check that you cloned the repository above and did not
run `make -f /path/to/kandev/Makefile install` from another directory. A missing
pnpm installation produces `pnpm: command not found` instead.
+364
View File
@@ -0,0 +1,364 @@
# Workflow Import / Export — Portable YAML Format
Kandev workflows can be exported to a portable YAML file and imported into
another workspace (or another Kandev install). This page is the reference for
that file format: the envelope, every field, the trigger config shapes, and the
matching rules applied on import.
Everything below is derived from the source of truth:
- `apps/backend/internal/workflow/models/export.go` — the portable structs (`WorkflowExport`, `WorkflowPortable`, `StepPortable`, `AgentProfilePortable`) and `Validate()`.
- `apps/backend/internal/workflow/models/models.go` — the `StepEvents` triggers and their action types.
- `apps/backend/internal/workflow/service/service.go` — `ImportWorkflows` / `importSingleWorkflow` (matching + position→ID mapping).
> **Looking for the built-in workflows instead?** See [Workflows](workflow-tips.md)
> for the default templates and when to use each.
---
## How import/export is exposed
The format is YAML over three HTTP endpoints (all under `/api/v1`):
| Method | Path | Purpose | Response / Body |
|--------|------|---------|-----------------|
| `GET` | `/workflows/:id/export` | Export a single workflow | `application/x-yaml` |
| `GET` | `/workspaces/:id/workflows/export` | Export **all** workflows in a workspace | `application/x-yaml` |
| `POST` | `/workspaces/:id/workflows/import` | Import workflows into a workspace | YAML request body, **max 1 MB** |
Export marshals the structs to YAML; import unmarshals the request body, runs
`Validate()`, then creates each workflow. The same struct shapes also marshal to
JSON (every field carries both `yaml` and `json` tags), but the endpoints speak
YAML.
---
## Envelope
The top-level document is a `WorkflowExport`:
```yaml
version: 1
type: kandev_workflow
workflows:
- # WorkflowPortable …
- # WorkflowPortable …
```
| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `version` | int | yes | Must be exactly `1` (`ExportVersion`). Any other value is rejected. |
| `type` | string | yes | Must be exactly `kandev_workflow` (`ExportType`). |
| `workflows` | list | yes | Must contain at least one workflow; an empty list is rejected. |
`Validate()` rejects the document with a descriptive error if `version`,
`type`, or the workflow list fails these checks.
---
## `WorkflowPortable`
Each entry under `workflows:`:
```yaml
- name: My Workflow
description: Optional human description.
agent_profile: # optional, workflow-level default agent
agent_name: Claude Code
model: claude-opus-4-7
mode: default
steps:
- # StepPortable …
```
| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `name` | string | yes | Workflow name. Required by `Validate()`. Used for **dedup on import** (see [Import rules](#import-matching-rules)). |
| `description` | string | no | Omitted from export when empty. |
| `agent_profile` | object | no | Workflow-level default agent profile. See [Agent profiles](#agent-profiles). Omitted when the workflow has no profile. |
| `steps` | list | — | The workflow's steps. See [`StepPortable`](#stepportable). |
Instance-specific fields (IDs, timestamps, workspace association) are **not**
part of the portable format — they are regenerated on import.
---
## `StepPortable`
Each entry under `steps:`:
```yaml
- name: In Progress
position: 1
color: bg-blue-500
prompt: |
Optional per-step prompt sent to the agent.
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
auto_archive_after_hours: 24
agent_profile: # optional, step-level agent override
agent_name: Claude Code
model: claude-opus-4-7
mode: default
events:
# triggers — see "Triggers" below
```
| Field | Type | Required | Default in export | Notes |
|-------|------|:--------:|-------------------|-------|
| `name` | string | yes | — | Required by `Validate()`. |
| `position` | int | yes | always emitted | **0-based** index. **Must be unique** within the workflow — duplicates are rejected. Also the anchor for `move_to_step` references (see below). |
| `color` | string | — | always emitted | Tailwind background class, e.g. `bg-blue-500`, `bg-green-500`, `bg-neutral-400`. |
| `prompt` | string | no | omitted when empty | Per-step prompt template. Supports placeholders such as `{{task_prompt}}`. |
| `events` | object | — | always emitted | Triggers and their actions. See [Triggers](#triggers). |
| `is_start_step` | bool | — | always emitted | Marks the step new tasks start in. |
| `show_in_command_panel` | bool | — | always emitted | Whether the step appears in the command panel. |
| `allow_manual_move` | bool | — | always emitted | Whether users can drag the task into this step manually. |
| `auto_archive_after_hours` | int | no | omitted when `0` | Auto-archive a task this many hours after it lands in the step. `0` / omitted = never. |
| `agent_profile` | object | no | omitted when none | Step-level agent profile, overriding the workflow default. See [Agent profiles](#agent-profiles). |
> **Note:** `position`, `color`, `events`, and the three booleans carry no
> `omitempty`, so they always appear in exported files (even when `false` or
> empty). The `prompt`, `auto_archive_after_hours`, and `agent_profile` fields
> are omitted when unset.
>
> **Not in the portable format:** office/Phase-2 step metadata — `stage_type`,
> step participants (reviewers/approvers), and recorded decisions — is **not**
> exported or imported. Only the fields listed above round-trip.
---
## Agent profiles
`agent_profile` appears at both the workflow level and the step level
(`AgentProfilePortable`):
```yaml
agent_profile:
agent_name: Claude Code # required
model: claude-opus-4-7 # optional, omitted when empty
mode: default # optional, omitted when empty
```
| Field | Type | Notes |
|-------|------|-------|
| `agent_name` | string | The agent's **display name** (`AgentDisplayName`), not its internal ID. |
| `model` | string | Model identifier. Omitted when empty. |
| `mode` | string | Agent mode. Omitted when empty. |
Profiles are stored by **value** (name/model/mode) rather than by ID precisely
so they can be re-matched in a different workspace. See the matching rule below.
---
## Triggers
`events` holds the step's triggers. Each trigger is a list of actions; an
action is `{ type, config }` where `config` is an optional map.
There are two families of triggers.
### Kanban-era triggers (round-trip today)
These four triggers use typed action slices and are fully supported by
import/export:
| Trigger | Allowed action `type`s | `config` |
|---------|------------------------|----------|
| `on_enter` | `enable_plan_mode`, `auto_start_agent`, `reset_agent_context`, `set_session_mode`, `clear_decisions`, `queue_run`, `queue_run_for_each_participant` | the first three take no config; `set_session_mode` takes `mode` (the agent permission mode to apply, e.g. `acceptEdits`); `queue_run` / `queue_run_for_each_participant` use the same config keys as the office triggers (see [Office triggers](#office--phase-2-triggers-are-not-portable)) |
| `on_turn_start` | `move_to_next`, `move_to_previous`, `move_to_step` | `move_to_step` needs `step_position` |
| `on_turn_complete` | `move_to_next`, `move_to_previous`, `move_to_step`, `disable_plan_mode` | `move_to_step` needs `step_position` |
| `on_exit` | `disable_plan_mode` | — |
Example:
```yaml
events:
on_enter:
- type: auto_start_agent
on_turn_start:
- type: move_to_next
on_turn_complete:
- type: move_to_step
config:
step_position: 2
```
#### `move_to_step` uses `step_position`, not `step_id`
This is the one transformation the portable format performs. Internally a step
transition references a target step by its database `step_id`. Because IDs are
not portable, export rewrites every `move_to_step` action's
`config.step_id` → `config.step_position`, and import rewrites it back to a
freshly generated `step_id`.
So in a portable file you **always** write:
```yaml
- type: move_to_step
config:
step_position: 2 # the target step's `position`, NOT a step id
```
`Validate()` enforces that every `move_to_step` `step_position` matches an
existing step's `position` in the same workflow; a dangling reference is
rejected. Any additional keys in the action's `config` are preserved verbatim
through the conversion.
> **Built-in template YAMLs differ.** The embedded templates under
> `apps/backend/config/workflows/*.yml` use string `step_id`s (e.g.
> `step_id: review`) because they are *template definitions*, a different schema
> from this portable export format. Do not copy their `step_id:` form into a
> portable import file — use `step_position:`.
### Office / Phase-2 triggers are not portable
The portable import/export format is for kanban workflows. The runtime model
also defines `on_comment`, `on_blocker_resolved`, `on_children_completed`,
`on_approval_resolved`, `on_heartbeat`, `on_budget_alert`, and
`on_agent_error`, but these office-style triggers are outside this format.
> [!WARNING]
> Do not hand-author office-style triggers in a portable workflow file. The
> conversion in `export.go` preserves only `on_enter`, `on_turn_start`,
> `on_turn_complete`, and `on_exit`; unsupported triggers are discarded. The
> Settings UI excludes office workflows from export. [Issue
> #1109](https://github.com/kdlbs/kandev/issues/1109) records the original bug
> report and the UI-side mitigation.
---
## Import matching rules
`ImportWorkflows` → `importSingleWorkflow` applies these rules:
1. **Validation first.** The whole document is run through `Validate()`
(envelope + per-workflow name/position/`move_to_step` checks) before
anything is created. A failure aborts the entire import.
2. **Workflow dedup by name.** Existing workflows in the target workspace are
listed; any imported workflow whose `name` already exists is **skipped**
(reported under `skipped`). The rest are **created** (reported under
`created`). The result is `{ created: [...], skipped: [...] }`.
3. **Fresh step IDs + position→ID mapping.** Every step gets a newly generated
UUID. Import builds a `position → new step ID` map, then rewrites each
`move_to_step` action's `step_position` back into the new `step_id`. This is
why step positions must be unique and why `move_to_step` references positions.
4. **Agent profile matched by name/model/mode.** For each `agent_profile`
(workflow-level and step-level), import searches the target workspace's
agents and profiles for one whose **`agent_name` (display name), `model`, and
`mode` all match exactly**. On a match, that profile's ID is assigned. On **no
match**, the field is left empty — the workflow/step is created **without** an
agent profile (silently; no error). Match the names/models/modes in the
target workspace if you need the profile wired up.
---
## Complete worked example
A self-contained, valid import file with two workflows: a four-step kanban loop
(using `move_to_step` with `step_position`) and a two-step planning flow with a
per-step agent profile and prompt.
```yaml
version: 1
type: kandev_workflow
workflows:
- name: Simple Kanban
description: Assign → run → review loop.
steps:
- name: Backlog
position: 0
color: bg-neutral-400
is_start_step: false
show_in_command_panel: false
allow_manual_move: true
events:
on_turn_start:
- type: move_to_next
- name: In Progress
position: 1
color: bg-blue-500
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
events:
on_enter:
- type: auto_start_agent
on_turn_complete:
- type: move_to_step
config:
step_position: 2
- name: Review
position: 2
color: bg-yellow-500
is_start_step: false
show_in_command_panel: true
allow_manual_move: true
events:
on_turn_start:
- type: move_to_previous
- name: Done
position: 3
color: bg-green-500
is_start_step: false
show_in_command_panel: false
allow_manual_move: true
auto_archive_after_hours: 168
events:
on_turn_start:
- type: move_to_step
config:
step_position: 1
- name: Plan & Build
description: Plan first, then implement.
agent_profile:
agent_name: Claude Code
model: claude-opus-4-7
mode: default
steps:
- name: Plan
position: 0
color: bg-purple-500
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
prompt: |
Analyze the task and produce an implementation plan. Do not write code.
Save the plan with create_task_plan_kandev, then stop for review.
agent_profile:
agent_name: Claude Code
model: claude-opus-4-7
mode: plan
events:
on_enter:
- type: enable_plan_mode
- type: auto_start_agent
on_exit:
- type: disable_plan_mode
- name: Implementation
position: 1
color: bg-blue-500
is_start_step: false
show_in_command_panel: true
allow_manual_move: true
prompt: |
Retrieve the plan with get_task_plan_kandev, then implement it.
events:
on_enter:
- type: auto_start_agent
```
Importing this into a fresh workspace creates both workflows
(`created: [Simple Kanban, Plan & Build]`). Re-importing the same file leaves
them untouched (`skipped: [Simple Kanban, Plan & Build]`). The `Claude Code`
agent profiles wire up only if a profile with that exact display name, model,
and mode exists in the target workspace.
+132
View File
@@ -0,0 +1,132 @@
# Workflows
Kandev ships with five workflow templates. Each defines a sequence of steps with automated transitions - agents start, stop, and move between steps based on events like entering a step, sending a message, or an agent completing its turn.
You can use these as-is or **create custom workflows** tailored to you or your team.
> **Sharing workflows?** Workflows can be exported to and imported from a
> portable YAML file. See [Workflow Import / Export](workflow-import-export.md)
> for the full format reference and a worked example.
## Default Workflows
### Kanban
Classic kanban board with automated agent execution. Good for straightforward tasks where you want to assign work, let the agent run, and review the result.
**Steps:** Backlog → In Progress → Review → Done
| Step | What happens |
|:------:|-------------|
| **Backlog** | Backlog of tasks not yet started. Sending a message moves the task to In Progress. |
| **In Progress** | Agent starts the work automatically. When it completes, the task moves to Review. |
| **Review** | You review the agent's work. Sending a message moves it back to In Progress for another iteration. |
| **Done** | Final state. Sending a message reopens the task in In Progress. |
**When to use it:**
- Bug fixes with clear reproduction steps
- Small, well-scoped features
- Chores and refactoring tasks
- Any task where you want a simple assign → run → review loop
---
### Plan & Build
Two-phase workflow where the agent first creates a plan for your review, then implements it. The plan is saved as a structured document you can edit before the agent proceeds to implementation.
**Steps:** Todo → Plan → Implementation → Done
| Step | What happens |
|:------:|-------------|
| **Todo** | Tasks ready to be planned. |
| **Plan** | Agent analyzes the task and creates a detailed implementation plan - requirements, files to modify, step-by-step approach, risks. Supports mermaid diagrams. The plan is saved via MCP tool and the agent stops for your review. You can edit the plan in the UI before moving forward. |
| **Implementation** | Agent retrieves the plan (including your edits), acknowledges modifications, and implements step-by-step. Moves to Done on completion. |
| **Done** | Final state. |
**When to use it:**
- Features that benefit from upfront design
- Tasks where you want to steer the approach before code is written
- Larger changes spanning multiple files
- When working with less familiar codebases where you want to validate the agent's understanding first
---
### Architecture
Focused on design and architecture. The agent creates technical designs for you to review - no implementation happens in this workflow. Useful for capturing architectural decisions before any code is written.
**Steps:** Ideas → Planning → Review → Approved
| Step | What happens |
|:------:|-------------|
| **Ideas** | Backlog of architectural ideas and proposals. |
| **Planning** | Agent analyzes the task, asks clarifying questions, and produces an architectural design with mermaid diagrams. Saves the design and stops for your review. |
| **Review** | You review the design. Sending a message moves it back to Planning for revisions. |
| **Approved** | Design is accepted. Ready for implementation (in a separate task/workflow). |
**When to use it:**
- System design and technical RFCs
- Evaluating approaches before committing to implementation
- Cross-team architectural proposals
- Breaking down large projects into implementable pieces
---
### Feature Dev
Full development lifecycle with quality gates between phases - spec, implementation with TDD, automated review, QA, draft PR, and CI fixup. Each phase runs a fresh agent turn so context stays focused on the task at hand.
**Steps:** Todo → Spec → Work → Review → QA → PR → CI Fixup → Done
| Step | What happens |
|:------:|-------------|
| **Todo** | Tasks ready to be picked up. |
| **Spec** | Agent analyzes the task, explores the codebase, proposes approaches, and saves a detailed plan via MCP tool. Runs in plan mode and stops for your review - you can edit the plan in the UI before moving on. |
| **Work** | Agent retrieves the plan (including edits), acknowledges modifications, and implements using a TDD loop - failing test → minimum code → refactor → commit, one behavior at a time. |
| **Review** | Agent context is reset, then a fresh review pass checks the diff for security, correctness, performance, and code quality. Fixes trivial issues directly; reports the rest with file:line. |
| **QA** | Agent verifies the feature end-to-end - traces wiring, runs the happy path, tries to break it with boundary values and error paths, and checks test coverage. |
| **PR** | Agent runs formatters/linters, commits and pushes remaining changes, picks up the repo's PR template if present, and creates a draft PR. |
| **CI Fixup** | Agent polls CI, fetches failed logs, fixes lint/test/type errors, pushes, and re-polls until checks go green. |
| **Done** | Final state. |
**When to use it:**
- Features that need quality gates between phases
- Changes that warrant a dedicated review + QA pass before the PR goes up
- When you want a single task to carry a feature from idea to mergeable PR
---
### PR Review
Track pull requests through automated code review. The agent reviews changed files and produces structured findings.
**Steps:** Waiting → Review → Done
| Step | What happens |
|:------:|-------------|
| **Waiting** | PR queue. Sending a message starts the review process. |
| **Review** | Agent reviews the changed files in the git worktree. If there are uncommitted changes, it reviews those; otherwise, it reviews commits that diverged from the main branch. Findings are organized into four categories: **BUG**, **IMPROVEMENT**, **NITPICK**, **PERFORMANCE** - each with file:line references. |
| **Done** | Review complete. Sending a message moves it back to Review for another pass. |
**When to use it:**
- Automated first-pass code review before human review
- Catching bugs, performance issues, and style problems early
- Reviewing agent-generated PRs from other workflows
- Supplementing human review on large changesets
---
## Tips
### Repository-Level Agent Configuration
Each repository should maintain its own agent configuration - `CLAUDE.md`, `AGENTS.md`, custom skills, MCP servers, and any agent-specific instructions. This keeps agent behavior consistent with the codebase it's working on, regardless of who triggers the task or which workflow runs it.
When an agent is assigned a task in a repository, it picks up that repository's configuration automatically. Coding standards, project context, and tooling constraints travel with the code, not with the platform.
### Cross-Repository Workflows
Workflow pipelines often span multiple repositories - a backend change triggers a client update, an infrastructure change follows a service deployment. Each step in the pipeline runs an agent inside a specific repository, and that agent should use the repository's own AI harness (prompts, rules, skills) rather than a shared global config.
This keeps each agent grounded in the right context. A backend agent follows backend conventions, a frontend agent follows frontend conventions, even when they're part of the same pipeline.