

Canopy
CLI Reference
Last updated
canopy [command] [options]
Running canopy with no command opens the TUI (running setup
first if needed, and checking for updates (notice only — run canopy update
to install)).
Daemon
| Command | Description |
|---|---|
canopy daemon start |
Start the daemon (MCP server, scheduler, watchers) |
canopy daemon stop |
Stop the daemon |
canopy daemon status |
Show daemon status |
canopy daemon restart |
Restart the daemon |
canopy daemon logs |
Show the daemon log |
canopy daemon install-service |
Register as a system service |
canopy daemon uninstall-service |
Remove the system service |
canopy daemon health-check |
Run the daily database integrity check and backup now, instead of waiting for the daemon to find an idle moment (see Installation) |
Updates
| Command | Description |
|---|---|
canopy update |
Check the latest stable release, ask Update to <tag>? [y/N] (default no), and install it atomically; prints canopy <current> → <latest> or canopy <current> is up to date |
canopy update --check |
Print the update status without changing anything; exits 0 when up to date, 1 when an update exists, and 2 when the check could not complete (network, DNS, TLS, HTTP error, or unparsable response — one stderr line names the cause) |
canopy update --yes |
Install without the confirmation prompt |
A cargo-installed binary is never overwritten: canopy update prints
installed with cargo — run: cargo install harness-canopy --force.
After a successful replacement, a running systemd-managed daemon is restarted
with systemctl --user restart canopy.service; an unmanaged daemon uses the
normal canopy daemon stop/start path. If a graph is running, update asks
before restarting and otherwise leaves the daemon untouched and prints the
command to run later.
Plain canopy update exits 2 on the same lookup errors.
Setup & diagnostics
| Command | Description |
|---|---|
canopy setup |
Interactive setup wizard — detects AI CLIs, generates ~/.canopy/config.toml |
canopy setup --local-registry <path> |
Use a local registry directory (for registry development) |
canopy setup --force-skills |
Overwrite local skill files that diverge from the sync source, even when they were edited locally (default: skip them with a warning) |
canopy mcp |
MCP wizard — sync/add/remove canopy’s MCP entry across platforms |
canopy mcp --local-registry <path> |
Run the MCP wizard against a local registry directory instead of GitHub (same flag as canopy setup --local-registry) |
canopy mcp → Add |
Add a server via pasted README JSON, local stdio command (with args + env), or remote URL (with headers); previews entries, marks (replaces existing) overwrites, then asks Install on N platform(s)? |
canopy uninstall --dry-run |
Preview removal: stop daemon, remove service unit + canopy.service.d/ drop-ins, revert platform configs, remove skill symlinks (changes nothing) |
canopy uninstall --keep-shared-servers |
Only remove canopy’s own canopy server entry; leave shared fetch/filesystem entries in place |
canopy doctor |
Full health diagnostics |
Platform registry
| Command | Description |
|---|---|
canopy registry refresh |
Fetch the platform registry and apply it now, ignoring the 24-hour interval the TUI’s background refresh uses; prints one line per affected platform (<platform>: changed <fields>, <platform>: kept local override <fields>, <platform>: added, <platform>: updated, <platform>: removed), or registry already up to date when nothing changed. Rewrites only config.toml and the baseline file — never starts the TUI, wizard or daemon, safe while graphs run |
canopy registry refresh --json |
Print the same report as JSON (changed, kept, added, updated, deleted, up_to_date) |
canopy registry refresh --local-registry <path> |
Apply a local registry directory instead of fetching from GitHub (same flag as canopy setup --local-registry) |
Registry-owned CLI fields — binary path, model flag, headless mode,
environment variables — are rewritten from the registry. A field you edited
yourself is kept as it is; when the registry changed that same field, the
refresh reports it as a local override, so a refresh never overwrites your
choice. The command needs an existing ~/.canopy/config.toml — on a machine
that has not run setup it fails with run `canopy setup` first. The refresh
time is recorded where the TUI’s background refresh records it, so a manual
refresh also restarts that 24-hour window.
Graph inspection
| Command | Description |
|---|---|
canopy graph list |
List all graphs with status and spec progress |
canopy graph list --workdir <path> |
Filter graphs by workdir |
canopy graph info <id-or-name> |
Detailed status for a single graph (specs, runs, hooks) |
canopy graph crew [id-or-name] |
List harness/model assignments — agent nodes outside ensembles, ensemble members and agent hooks, each with platform, model, timeout, crew and availability. Omit the graph for every graph |
canopy graph crew --platform <p> --model <m> |
Filter by exact platform and case-insensitive model substring |
canopy graph crew --all |
Include archived graphs |
canopy graph crew --group-by-pair |
Summarise per platform/model pair: graph, ensemble, node and hook counts plus the graph names |
canopy graph crew --json |
Print the MCP graph_crew JSON shape instead of tables |
Graph ids can be specified by exact id, exact name, or unambiguous id prefix. Ambiguous references list the candidates.
Graph control
Mirrors the graph_run/graph_pause/graph_continue/graph_reset/
graph_schedule_autorun MCP tools — a second way to reach the same daemon
operations when an MCP client can’t. Every command resolves <id-or-name>
locally (same rule as graph inspection above) and then delegates the actual
state change to the daemon over its existing MCP endpoint; it never writes
to the database directly. If the daemon isn’t reachable on the resolved
port, the error says so explicitly instead of looking like the graph is
missing.
| Command | Description |
|---|---|
canopy graph run <id-or-name> |
Run a graph in the background, spec by spec |
canopy graph run <id-or-name> --queue <queue-id> |
Run the queue’s pending specs through the graph instead |
canopy graph run <id-or-name> --workdir <path> |
Override the graph’s workdir for this run only |
canopy graph run <id-or-name> --from <node> |
Start the first spec this run executes at a chosen node (id, id prefix, node name, or ensemble name), reusing the recorded outputs of upstream nodes; refused, naming the node, if an upstream node’s latest run failed, errored or was interrupted |
canopy graph pause <id-or-name> |
Pause a running graph after the current node finishes |
canopy graph continue <id-or-name> --retry-current-node |
Resume a paused or failed graph by retrying the current node (also resumes a paused graph whose current spec is pending with a recorded node; clears that spec’s execution count and the reply says so) |
canopy graph continue <id-or-name> --skip-next-spec |
Resume a paused or failed graph by skipping to the next spec |
canopy graph continue <id-or-name> --from <node> [--spec <spec>] |
Start a spec at a chosen node, reusing the recorded outputs of upstream nodes (refused, naming the node, if one failed, errored or was interrupted; defaults to the graph’s current spec: a running/interrupted member, else a failed one, else the next pending one) |
canopy graph reset <id-or-name> |
Reset a completed/failed graph back to pending (prompts for confirmation) |
canopy graph reset <id-or-name> --specs <id>... |
Reset specific spec ids, even if already completed |
canopy graph reset <id-or-name> --yes |
Skip the confirmation prompt |
canopy graph reset <id-or-name> --clear-notes |
Also delete the reset specs’ spec notes (default keeps them) |
canopy graph remove-spec <id-or-name> <spec-id> |
Unbind a spec from the graph, making it a standalone backlog spec; its execution history is kept |
canopy graph autorun <id-or-name> --at <iso8601> |
Schedule a one-shot resume at a future time |
canopy graph autorun <id-or-name> --quota-reset-message <text> |
Schedule a resume from a raw CLI quota-limit message |
canopy graph autorun <id-or-name> --cancel |
Cancel a pending autorun schedule |
canopy graph log <id-or-name> |
Print the graph’s durable event log, one line per event |
canopy graph log <id-or-name> --since <id|RFC3339> |
Start from an event id or a timestamp |
canopy graph log <id-or-name> --kind <k> |
Only events of this kind (repeatable) |
canopy graph log <id-or-name> --spec <id> |
Only this spec’s events |
canopy graph log <id-or-name> --follow |
Poll every 2 s and print new events until Ctrl+C |
canopy graph log prints <created_at> <KIND> spec=<name> node=<name> <message>
and goes through the daemon like the other graph subcommands. See the
Event log section.
Agents
Read-only inspection of registered agents, served from the local database
(like canopy graph info — no daemon required).
| Command | Description |
|---|---|
canopy agent show <id> |
Show one agent’s full stored definition, prompt last and untruncated |
canopy agent show <id> --json |
Print the same definition as JSON (identical to the agent_get MCP tool’s output) |
Missions
Read-only listing of the 28 gamification missions (see TUI), served from the local database — no daemon required.
| Command | Description |
|---|---|
canopy missions |
List every mission: icon, title, category, unlocked <date> or locked: <challenge>, plus N / 28 unlocked |
canopy missions --json |
Print the same report as one JSON object (unlocked, total, missions with id, title, icon, category, challenge, unlocked_at as RFC 3339 UTC or null) |
Spec backlog
| Command | Description |
|---|---|
canopy spec create --name <name> --description <text> |
Create a standalone spec (a backlog item) |
canopy spec create ... --workdir <path> |
Tag the new spec with a workdir, for backlog filtering |
canopy spec create ... --spec-type <type> |
Set the Conventional Commits type (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert). Optional — when omitted and the name is a conventional title, the type and scope are parsed from the name |
canopy spec create ... --spec-scope <scope> |
Set the optional scope (e.g. tui). Part of the deterministic commit message |
canopy spec create ... --queue <queue-id> |
Also append the new spec to an existing queue |
canopy spec create ... --queue <queue-id> --group <group> |
Add it to the queue within a context group |
canopy spec complete <spec-id> --reason <text> |
Mark a standalone spec as completed |
canopy spec skip <spec-id> --reason <text> |
Mark a standalone spec as skipped |
canopy spec reopen <spec-id> --reason <text> |
Reopen a completed/skipped spec back to pending |
canopy spec convert |
Migrate legacy heading-format spec bodies to the tagged <spec> format (skips tagged and running specs; prints a report) |
canopy spec notes <spec-id> |
Print every spec note of a spec, newest first, with its owner, last update and body (see Spec notes) |
Availability roster
| Command | Description |
|---|---|
canopy roster list [--platform <platform>] |
List roster entries with their effective state now |
canopy roster set <platform> [<model_pattern>] --unavailable [--reason <text>] [--until <rfc3339>] |
Declare a platform/model pair unavailable; until must be a future RFC 3339 UTC instant |
canopy roster set <platform> [<model_pattern>] --available |
Declare it available |
canopy roster clear <platform> [<model_pattern>] |
Delete one entry (idempotent) |
canopy roster check <platform> [<model>] |
Show the effective availability of one pair |
Omitting <model_pattern> addresses every model of the platform; a pattern
ending in * (e.g. opencode-go/*) addresses a prefix. The engine skips
effectively-unavailable members at dispatch and blocks a single unavailable
agent node or hook — it never edits a graph.
Crews
| Command | Description |
|---|---|
canopy crew list |
List every crew with its version, members and binding counts |
canopy crew show <name> |
Show one crew and every binding of it |
canopy crew create <name> --from-ensemble <id> [--description <text>] |
Create a crew seeded from an ensemble’s current members |
canopy crew delete <name> |
Delete a crew (refused while any ensemble, node or hook binds it) |
A crew is a named, ordered member list ensembles and agent nodes/hooks bind to; member editing is MCP/TUI only (see MCP tools).
Settings
Every setting in ~/.canopy/config.toml is declared once, in a typed
registry, and read and written through one surface the CLI, MCP and the TUI
all use.
| Command | Description |
|---|---|
canopy config list [--group <group>] |
List settings with their group, kind, default, effective value, where it came from (default, file, env), when it applies, and status (ok, invalid: <reason>, pending_restart, unknown) |
canopy config get <key> |
Show one setting by its dotted key (e.g. clean.retention_days) |
canopy config set <key> <value> |
Change one setting; validated first, and nothing is written if it is refused |
canopy config reset <key> |
Write the setting’s default back |
canopy config path |
Print the path of config.toml |
Only the daemon writes config.toml, and it edits the file with toml_edit,
so hand-written comments, key order and unknown keys survive every change.
canopy config set reports what the user must do for the value to take
effect: applied, applies on next run, restart the daemon, or restart the TUI. A hand edit is picked up by the daemon’s file watcher without a
restart, and an invalid value keeps the previous effective value — it is
reported by canopy config list and as a canopy doctor warning rather than
being applied. pending_restart marks a restart-only setting whose value on
disk differs from the one the running daemon loaded, including a key the file
no longer holds, and a config.toml that will not parse at all is reported
as a single config.toml row (and doctor warning) instead of being silently
ignored. skills.sources (a list of tables) must still be edited by
hand.
Spec queues
| Command | Description |
|---|---|
canopy queue group <queue> <spec> <group> |
Set the context group of an existing queue member; a spec joining a group with pending members moves next to them |
canopy queue group <queue> <spec> --clear |
Clear the member’s group (it stays in place unless that splits its old group) |
RAG
| Command | Description |
|---|---|
canopy rag auto-index start |
Resume automatic indexing (default state) |
canopy rag auto-index stop |
Pause indexing without losing the queue |
canopy rag report |
Detailed per-file indexing report, store footprint and health |
canopy rag compact |
Merge store fragments and prune old versions (daemon does it, or this process when the daemon is down) |
canopy rag backfill |
Index every configured file that is not in the vector store yet |
canopy rag purge [--yes] |
Delete the entire vector store and reset all RAG state; asks for confirmation unless --yes (-y) is given |
canopy rag model retry |
Clear a failed local embedding-model download so the daemon retries it in the background, without re-running setup |
Clean
canopy clean removes stale data. The default run is a soft cleanup:
| Command | Description |
|---|---|
canopy clean --dry-run |
Preview what would be removed; deletes and modifies nothing |
canopy clean |
Soft cleanup: sessions and files older than clean.retention_days, and graph event-log rows older than graph_event_retention_days for graphs that are not running |
canopy clean --older-than <days> |
Override the retention window for this run only |
canopy clean --hard |
Also delete orphaned projects (workdir missing) and their dependent rows; asks for confirmation unless --yes is given. Soft cleanup runs first |
canopy clean --yes |
Skip the confirmation prompt that --hard requires. A typo here can delete a real cascade |
canopy clean --no-reclaim |
Skip reclaiming freed database space (VACUUM and WAL checkpoint) even when this run deleted enough rows to warrant it |
canopy clean --stop-daemon |
Consent to stop a running daemon for a warranted reclaim, then restart it exactly as it was. Without it, an interactive terminal is prompted; a non-interactive run skips reclamation while the daemon is up |
Reclaim runs only when a run deletes at least clean.reclaim_min_deleted_rows
rows (default 50). It takes an exclusive lock and rewrites the whole database
file, which is why it can be skipped or deferred.
Settings reference
Every key the registry declares, with its default and range. canopy config list prints the same keys with the value this machine actually uses and where
it came from. Apply says when a change takes effect: applied (at once),
applies on next run, restart the daemon or restart the TUI.
| Key | Default | Range | Apply | Meaning |
|---|---|---|---|---|
mcp_filesystem_root |
home directory | — | next run | Root directory the MCP filesystem server exposes |
temperature_unit |
celsius |
celsius, fahrenheit |
restart the TUI | Temperature unit for the TUI’s system widgets |
theme |
classic |
classic, modern |
applied | TUI colour theme: bordered or borderless |
pinned_panel_face |
auto |
auto, activity, knowledge, graph, crews |
applied | Pinned right-panel face; auto lets the panel switch on its own |
tui.datetime_format |
%Y-%m-%d %H:%M |
chrono format | applied | Dates and times in the TUI (validated by formatting a fixed instant) |
tui.time_format |
%H:%M |
chrono format | applied | Time-only labels in the TUI |
tui.panel_dwell_seconds |
10 | 0–300 | applied | Seconds a panel event holds the right panel; 0 disables the dwell |
tui.brians_brain |
true |
bool | applied | Home-banner animation |
tui.atmosphere |
true |
bool | applied | Fireflies and dandelions |
tui.scroll_step |
3 | 1–20 | applied | Lines per scroll event |
tui.page_step |
15 | 5–100 | applied | Lines per page-up/down |
notifications.enabled |
true |
bool | applied | Desktop notifications |
update.check_enabled |
true |
bool | applied | Automatic update checks |
update.check_interval_hours |
24 | 1–720 | applied | Hours between automatic update checks |
announcements_enabled |
true |
bool | next run | Upgrade and tip announcements |
embeddings_model |
empty | — | restart the daemon | Embedding model used by the knowledge layer |
embeddings_idle_unload_secs |
600 | 1–86400 s | restart the daemon | Idle time before the embedding model is unloaded |
similarity_threshold |
0.25 | 0.0–1.0 | next run | Lexical-cohesion threshold for semantic chunk merging |
rag_personal_dirs |
[] |
list of paths | next run | Personal directories indexed recursively |
rag_max_file_mb |
10 | 1–ceiling | next run | Per-file indexing size cap, in MB |
rag_vector_cache_entries |
64 | 1–4096 | restart the daemon | LanceDB index cache ceiling, in entries |
skills.sources |
the UniverLab skills repo | list of tables | restart the daemon | Git sources for the skill store, checked in order (edit by hand) |
skills.ttl_minutes |
15 | 0–10080 | next run | Minutes a fetched skill is served before its source is re-checked |
models.catalog_ttl_minutes |
1440 | 1–43200 | next run | Minutes the models.dev catalog cache is served |
models.native_ttl_minutes |
60 | 1–43200 | next run | Minutes a platform’s native model list is served |
agents.probe_timeout_seconds |
30 | 5–300 s | applied | Timeout for a probe before the harness gives up |
scheduler.retry_enabled |
true |
bool | next run | Retry a failed scheduled run after a delay |
scheduler.retry_delay_minutes |
60 | 1–1440 | next run | Wait before a scheduled run is retried |
scheduler.retry_max |
3 | 0–20 | next run | Retries of a failed scheduled run |
spec_attempt_limit |
3 | 1–20 | restart the daemon | Separate graph executions a spec may fail with a verdict before it is blocked |
ensemble_concurrency_cap |
4 | 1–64 | restart the daemon | Ensemble members that run at once across every graph |
graphs.* |
see Tuning the engine | see there | see there | Graph engine retry budgets, ensemble member caps and check output |
clean.retention_days |
7 | 1–3650 days | next run | Days kept before canopy clean removes sessions and files |
graph_event_retention_days |
90 | 1–3650 days | next run | Days a graph’s event log is kept before canopy clean prunes it |
clean.reclaim_min_deleted_rows |
50 | 0–100000 | applied | Deleted rows that trigger VACUUM during canopy clean |
activity.retention_per_workdir |
500 | 50–100000 | applied | Activity-log entries kept per workdir |
scheduler.retry_* and spec_attempt_limit are not in graphs.*, which is
why they are listed here.
Spec queues
Plumbing
| Command | Description |
|---|---|
canopy stdio |
Run the MCP server over stdio |
canopy bridge --id <session> |
Stdio sidecar proxy that injects canopy identity headers |
Global flags
| Flag | Description |
|---|---|
-p, --port <port> |
Override the daemon port (default 7755) |
--help |
Show help for any command |
--version |
Show canopy version |