universelabSCI · CLI · BIO
ESEspañol

Canopy

Node Documents

Last updated

Nodes

A node document is one reusable node — a Rust Designer, a Python Validator, the classic implementer — that is shared and installed like a whole graph, then added to any graph by name:

{
  "format_version": 1,
  "name": "auditor",
  "kind": "agent",
  "description": "Audits a change against the spec.",
  "config": { "prompt_template": "audit this", "timeout_minutes": 10 },
  "preset": { "name": "auditor-preset", "content": "audit well" }
}

kind is agent, check, gate, router, or ensemble. An ensemble document carries the ensemble shape in config: prompt_template, members (each without platform/model), min_pass, and the same fields graph_ensemble add accepts (kind, timeouts, infra budgets, commit_rights, mutex). A document never carries a harness — who runs a node is the installer’s decision, not the author’s. canopy graph validate <path> checks a node document exactly as it checks a graph document (ok with node_kind, or every problem with its JSON path).

Install one from the catalogue:

canopy node install <slug>
canopy node install <owner>/<repo>/<slug>@<ref>
canopy node install draft:<id>
canopy node install <ref> --as <name> --replace --overwrite-presets --yes --show-prompts --json

The ref grammar is the graph ref grammar: a bare <slug> is the official UniverLab entry, <owner>/<repo>/<slug> a community one (each with an optional @<ref>), plus draft:<id> for a node draft shared from the editor — the same one-use, 30-minute, unreviewed draft as a graph, with the same draft not found message (see Install a graph); a draft holding a graph document is refused, saying which kind it found. The document is fetched through graphs.catalogue_url and validated exactly as canopy graph validate validates it. Before anything is written, the same review canopy graph install prints comes first: every shell command (config.command) labelled by node, every agent instruction (prompt_template, prompt_override, and the preset content behind prompt_preset — bundled or builtin — with its first 3 lines and its total line count, full text with --show-prompts), and the attention marks for command shapes worth a second look (pipe to shell, sudo, rm -rf, chmod 777, git push/--force, writes under ~/.ssh, ~/.config or /etc, eval, base64 -d, pipe to python -c/node -e). Then it asks Install <name> from <source>? [y/N] — official documents are asked about exactly like community ones. Without a TTY and without --yes it prints the summary and refuses, exiting 1 with refusing to install without confirmation: re-run with --yes after reviewing the commands above; a draft’s refusal adds that the draft is gone and asks for a new link from the editor. --yes installs after the summary without the question, and --json prints the review as JSON on stdout with the question on stderr.

Only after the confirmation is the document written to ~/.canopy/nodes/<name>.json, stamped with the catalogue entry it came from (slug, owner/repo/slug, or draft:<id>). Its bundled preset is installed as ~/.canopy/prompts/<preset.name>.md with the same no-silent-overwrite rule as a graph’s presets: a file owned by another source aborts the install unless --overwrite-presets (or --replace) is given. A graph document fetched from a node endpoint is refused, and a 410 says <name> was removed from the catalogue after a community report: <reason> (<issue>).

A name already installed from a different source is refused, naming both sources — the installed copy (and every graph using it) is never silently replaced. --as <local-name> installs under another local name instead (the bundled preset is renamed with it: a <doc>-… preset keeps its suffix under the new name, any other preset becomes <as>-<preset>, with the config’s prompt_preset rewritten to match), or --replace overwrites. --as takes a plain slug (lowercase letters, digits, -); anything else is refused before anything is fetched. Reinstalling from the same source updates in place, and an installed copy still overrides a builtin of the same name.

Over MCP, node install { ref, as?, replace?, overwrite_presets?, accept? } follows the same gate: without accept: true it installs nothing and returns { needs_review: true, review: { source, commands, prompts, attention } }; with accept: true it installs and echoes the same review in the result. An agent must show the review to its user before passing accept: true.

List what is available:

canopy node list
canopy node list --json

The list shows installed copies and the builtins embedded in the binary, each marked installed or builtin, with the catalogue entry that installed it next to the name (catalogue_source in --json; a hand-written copy shows local). The builtins are implementer, cargo-gates, reviewer-committer, commit-check, resilience, and ensemble-proposers; they resolve offline, with no install step. An installed copy of a builtin’s name wins over the builtin.

Add one to a graph:

graph_node { action: "add", spec_id, name: "audit", node: "auditor",
             config_overrides: { "platform": "claude", "model": "opus" } }
graph_ensemble { action: "add", spec_id, name: "Proposers", node: "ensemble-proposers",
                 members: [{ "platform": "openrouter" }, …],
                 from_node: "<kickoff>", condition: "always",
                 on_pass_to: "<arbiter>" }

node is the alternative to kind + config, and config_overrides shallow-merges on top of the document’s config — the override wins, the rest of the document is preserved. An agent node document has no harness, so the call is refused with a message naming the document unless the overrides supply platform (or cli, or crew). An ensemble document supplies the shared prompt, the member count and the ensemble shape, but its members still need a platform each from the caller’s members list.

Remove an installed copy:

canopy node remove <name>

A builtin with no installed copy cannot be removed; an installed copy that overrides a builtin is removed like any other, and the builtin resolves again afterwards.

Node documents replaced the retired blueprint tools and the blueprint parameter outright: graph_node and graph_ensemble add take node, kind + config, or — for ensembles — a crew, and nothing else.