Concepts
This page explains the model behind Canopy: what a skill is structurally, why it’s shaped that way, and what happens when one runs. You don’t need to read it to use the canopy agent — /canopy create, /canopy scaffold, and /canopy improve will produce spec-compliant skills without you knowing any of this. But if you want to author by hand, debug execution, or extend the framework, the concepts below are the ones that matter.
For the formal grammar and field-level reference, see Reference. For one-page lookup, see Cheatsheet.
The tree as source of truth
Most “AI skill” formats are prose: a paragraph telling the agent what to do, in what order, with what guardrails. Prose is interpreted — by a model, every time, slightly differently. Two runs of the same skill can take different paths because the model parsed the same paragraph two different ways.
Canopy replaces the paragraph with a tree: a sequence of named operations, with explicit branching. The tree is the source of truth. The runtime walks it top-to-bottom; the model only fills in what each leaf does, never which leaf to pick next.
release
├── EXPLORE >> current_version | version_files
├── SHOW_PLAN >> new_version | files | changelog
├── ASK << Proceed? | Yes | No
├── IF << Yes
│ └── BUMP_FILES << version_files | new_version
└── ELSE
└── natural language: Cancelled by user.
Every node is either an op call (ALL_CAPS name) or natural-language prose. Op calls are deterministic — the runtime resolves the name, runs the op’s body, and binds outputs to context. Natural-language nodes are one-shot: the model executes the prose with whatever context is in scope, then moves to the next sibling.
<< is input, >> is output, | separates options or fields. Two equivalent surface syntaxes are accepted — markdown lists (nested *) or box-drawing (the fenced tree characters above). Pick whichever reads more clearly for the skill.
Skill anatomy
Every skill is a single SKILL.md file (uppercase, exact spelling — the agentskills.io spec is case-sensitive). The file has frontmatter, a safety preamble for canopy-flavored skills, then named sections in this order:
---
name: skill-name
description: One-line description shown in the skill picker.
compatibility: Requires the canopy-runtime skill (published at github.com/kostiantyn-matsebora/claude-canopy). Install with any agentskills.io-compatible tool — e.g. `gh skill install`, `git clone`, the repo's `install.sh`/`install.ps1`, or the Claude Code plugin marketplace. Supports Claude Code and GitHub Copilot.
metadata:
argument-hint: "<required-arg> [optional-arg]"
canopy-features: [interaction, control-flow, verify]
---
> **Runtime required.** This skill uses Canopy tree notation; canopy-runtime must be active.
> [...safety preamble guard block...]
Preamble: $ARGUMENTS — parse and set context variables here.
---
## Tree ← execution pipeline (required)
## Rules ← invariants and safety constraints
## Response: ← output format declaration
compatibility is required for every ## Tree skill — it tells an agent that’s never seen Canopy before how to install the runtime. It’s free-text (≤500 chars per the agentskills.io spec), names canopy-runtime + a source repo, and lists install tools as alternatives. Don’t structure it as { requires: [...] } — that’s non-spec and gh skill install rejects it.
The safety preamble halts execution on agents without canopy-runtime active. /canopy create and /canopy scaffold insert it automatically.
metadata is the spec-compliant home for non-spec frontmatter fields. Two canopy-specific entries live here:
metadata.canopy-features(v0.21.0+) — slice manifest declaring which primitive families the skill uses. The runtime lazy-loads only the named slices (proportional context cost). Valid values:interaction,control-flow,parallel,subagent,explore,verify. Thecoreslice (IF/ELSE_IF/ELSE/END/BREAK) is implicit-always-loaded. Auto-generated by/canopy create/scaffold/improve.metadata.canopy-contracts(v0.22.0+) — opt-in runtime contract enforcement. Set tostrictto validate each contract-bearing op’s input before firing and output before binding into context. Default (omitted) keeps contracts descriptive only.
Other non-spec fields (argument-hint, user-invocable, version, …) also live inside metadata, not at the frontmatter root.
## Agent (legacy, soft-compat) — pre-v0.20 skills declared an explore subagent via a top-level ## Agent section. It still works — the runtime treats it as a single-element marked op named EXPLORE. New skills should use the v0.20+ marker form instead (see Subagents below): per-op > **Subagent.** markers on op definitions and bold call-sites in the tree. The marker form composes naturally with PARALLEL for multi-subagent fan-out; ## Agent is single-subagent-only.
For full field tables, frontmatter validation rules, and the safety-preamble exact text, see Reference — Framework Spec.
## Tree
The execution pipeline. Nodes run top-to-bottom; IF/ELSE_IF/ELSE, SWITCH/CASE/DEFAULT, and FOR_EACH give branching; PARALLEL fans out children to concurrent subagent invocations. Required for canopy-flavored skills.
## Rules
Skill-wide invariants — short bullet list. These are guardrails the runtime keeps in scope for the entire execution, not per-op behavior. Don’t repeat op-level checks here.
## Response:
Declares the output shape as pipe-separated field names — e.g. ## Response: version | files updated. The model fills the named fields when the tree finishes.
Subagents
When a skill needs work that should run in its own context window — substantial file reads, deep analysis, schema-shaped output — it dispatches that work as a subagent op. The subagent runs in a fresh context, returns only its declared schema-shaped output, and the parent’s context stays small and predictable.
Since v0.20.0, subagent dispatch is per-op, not per-section. An op definition opts in by carrying a > **Subagent.** blockquote marker; call sites opt in by bolding the op name in the tree. The two markers must agree — vscode flags drift.
Op definition — marker as the first content under the heading:
## REVIEW_ASPECT << aspect | file_paths >> findings
> **Subagent.** Output contract: `assets/schemas/aspect-findings.json`.
REVIEW_ASPECT << aspect | file_paths
├── Read `assets/constants/review-aspects.md` → § matching `aspect`
├── FOR_EACH << path in file_paths
│ └── read the file at `path`
└── apply criteria; return findings shaped per the contract
Call site — bold around the op name in the tree:
* PARALLEL
* **REVIEW_ASPECT** << "security" | context.file_paths >> security_findings
* **REVIEW_ASPECT** << "performance" | context.file_paths >> perf_findings
Plain OP_NAME << ... >> ... always means inline (default). Bold **OP_NAME** << ... >> ... always means subagent dispatch. The marker form composes naturally with PARALLEL for multi-typed parallel fan-out, each child running in its own context window.
Strict-input contract for subagent ops. A subagent op’s body may use only its << named inputs and static skill assets (assets/constants/..., assets/templates/..., assets/policies/...). It MUST NOT reach into ambient context.<name> not declared in the signature, or read bindings produced by prior tree nodes that weren’t passed via <<. If the body legitimately needs ambient state, drop the marker and keep it inline.
Universal contracts (v0.22.0+) — the same Output contract: / Input contract: references that ride inside the > **Subagent.** marker can also stand alone on inline ops:
## RENDER << findings | template_path >> report_text
> **Input contract:** `assets/schemas/render-input.json`
> **Output contract:** `assets/schemas/render-output.json`
vscode walks the binding graph (producer >> ctx.foo → consumer << ctx.foo) and surfaces drift between producer output schemas and consumer input schemas as authoring-time diagnostics. Skills that opt into metadata.canopy-contracts: strict get the same checks enforced at runtime.
Legacy: the ## Agent section (soft-compat)
Pre-v0.20 skills declare a single explore subagent via a top-level ## Agent section, with EXPLORE >> context as the first tree node. This still works — the runtime treats it as syntactic sugar for a single-element marked op named EXPLORE. The legacy form has three canonical body shapes — (A) minimal, (B) sub-task bullets, (C) op reference — described in canopy-runtime/references/ops/subagent.md. New skills should use the marker form above; existing skills can migrate via /canopy improve (action migrate-agent-to-marker). The marker form is the only way to declare more than one subagent in a skill.
Ops
An op is a named, reusable step. Anywhere the tree has an ALL_CAPS identifier, that’s an op call. The runtime resolves the name and executes the op’s body.
Simple op — prose body for linear behavior:
## FETCH_DEFAULTS
Fetch the chart's upstream default values from the internet.
Branching op — tree-form body for control flow inside the op itself:
## EDIT_TAG << image_defined_in | target_tag
* EDIT_TAG << image_defined_in | target_tag
* IF << image_defined_in = chart-defaults-only
* CREATE_ENV_OVERRIDE
* ELSE — edit tag in-place at the path from image_defined_in
The op signature line declares inputs (<<) and outputs (>>). Skill-local ops live in references/ops.md (one file, all ops) or references/ops/<name>.md (one file per op — preferred for complex skills with >5 ops).
Op lookup order
When the runtime sees an ALL_CAPS identifier in a tree node, it resolves the name in this order:
- Skill-local —
<skill>/references/ops.mdor<skill>/references/ops/<name>.md - Consumer-defined cross-skill ops (optional) — declared via
compatibility, packaged as a separate skill - Framework primitives —
IF,ELSE_IF,ELSE,SWITCH,CASE,DEFAULT,FOR_EACH,PARALLEL,BREAK,END,ASK,SHOW_PLAN,EXPLORE,VERIFY_EXPECTED. Defined in canopy-runtime’s primitive slices (index:canopy-runtime/references/ops.md; per-feature slice files underreferences/ops/). Never overridden.
For the full primitives table with signatures and examples, see Reference — Primitives.
Inline op vs subagent op vs contract-bearing op
Every op call uses the same OP_NAME << inputs >> outputs syntax. Three independent concerns layer on top:
| Concern | Decided by | Surface |
|---|---|---|
| Where the body runs (parent context vs. dispatched out-of-context) | The op definition’s > **Subagent.** marker — present → subagent, absent → inline |
Bold **OP_NAME** at call sites for subagent dispatch; plain OP_NAME for inline (the two must agree, vscode flags drift) |
| Whether the op declares typed input/output schemas (v0.22.0+) | Bare > **Input contract:** \…` / > Output contract: `…\ `` blockquote markers on the op definition (or the same clauses inside a > **Subagent.** block) |
Authoring-time type-flow diagnostics; runtime enforcement under metadata.canopy-contracts: strict |
| Whether the parent context carries the op’s ambient state | The marker — subagent ops are forbidden from reading context.<name> not in their signature; inline ops may use ambient state freely |
Subagent strict-input contract |
The three concerns are independent. An op may be inline + contract-bearing (typed but runs in parent context), inline + schema-less (back-compat default), subagent + contract-bearing (typical v0.22 pattern), or subagent + output-contract-only (typical v0.20 pattern). Skills that mix forms freely are fine — the parser handles each op on its own.
See Subagents above for the marker-form dispatch model, including the ## Agent legacy soft-compat note.
Category resources
Structured content — JSON, tables, scripts, schemas, templates — does not belong inline in SKILL.md. SKILL.md is orchestration only. Structured content lives in subdirectories alongside SKILL.md and is loaded at point-of-use via Read \
| Directory | Contains |
|---|---|
scripts/ |
Executable code (.ps1, .sh) |
references/ops.md or references/ops/<name>.md |
Skill-local op definitions |
references/<other>.md |
Supporting docs loaded on demand |
assets/templates/ |
Fillable output documents with <token> placeholders |
assets/constants/ |
Read-only lookup data (mapping tables, enum-like value lists) |
assets/schemas/ |
Structure definitions (subagent contracts, data shapes) |
assets/checklists/ |
Evaluation criteria lists (- [ ] ...) |
assets/policies/ |
Behavioural constraints |
assets/verify/ |
Post-execution expected-state checklists for VERIFY_EXPECTED |
One concern per file. Don’t bundle unrelated content into a single resource file — the file is the unit of reuse, and a multi-concern file forces every consumer to load everything.
SKILL.md must NOT contain: tables, JSON or YAML blocks, scripts, inline templates, or inline examples. If you find yourself writing one of those inline, extract it to the matching category subdir.
For full per-category semantics and read-time behavior, see Reference — Category Resource Subdirectories.
Execution model — under the hood
Here’s what happens when the runtime executes a canopy-flavored skill:
┌────────────────────────────────────────────────────────────────────────────┐
│ my-skill/SKILL.md │
│ │
│ Stage 1: Initialize context │
│ ┌─ Frontmatter + Preamble ───────────────────────────────────────────┐ │
│ │ name, description, compatibility, metadata.argument-hint │ │
│ │ metadata.canopy-features (slice manifest, v0.21+) │ │
│ │ metadata.canopy-contracts (strict mode opt-in, v0.22+) │ │
│ │ + safety preamble guard block │ │
│ │ parse $ARGUMENTS, set context variables │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Stage 2: Detect platform + load runtime spec (lazy, slice-driven) │
│ ┌─ canopy-runtime ───────────────────────────────────────────────────┐ │
│ │ detect host: Claude Code | GitHub Copilot │ │
│ │ load runtime-{claude,copilot}.md per active host │ │
│ │ load skill-resources.md (skill anatomy, op lookup, categories) │ │
│ │ load ops/core.md (always — IF/ELSE_IF/ELSE/END/BREAK) │ │
│ │ load ops/<slice>.md ONLY for slices in metadata.canopy-features │ │
│ │ (interaction, control-flow, parallel, subagent, explore, verify)│ │
│ │ Manifest absent → fall back to loading every slice (back-compat) │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Stage 3: Subagent dispatch (optional — when the tree opens with one) │
│ ┌─ marker-form dispatch (v0.20+, canonical) ─────────────────────────┐ │
│ │ bold call site (e.g. **EXPLORE** >> context) under ## Tree │ │
│ │ resolves to op def carrying > **Subagent.** Output contract: ... │ │
│ │ Claude Code: native Task subagent | Copilot: fleet/inline fallback│ │
│ │ capture schema-shaped output into context.<binding> │ │
│ │ (legacy ## Agent singular still supported as a single-EXPLORE op) │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Stage 4: Plan and confirmation gate │
│ ┌─ ## Tree entry steps ──────────────────────────────────────────────┐ │
│ │ SHOW_PLAN >> fields │ │
│ │ ASK << Proceed? | Yes | No │ │
│ │ No -> stop without changes │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ Yes │
│ ▼ │
│ Stage 5: Execute workflow actions (iterative loop) │
│ ┌─ ## Tree action steps ─────────────────────────────────────────────┐ │
│ │ run op calls + natural-language nodes top-to-bottom │ │
│ │ evaluate IF / ELSE_IF / ELSE / SWITCH / CASE / FOR_EACH / PARALLEL│ │
│ │ bold call sites dispatch out-of-context per op-def marker │ │
│ │ if metadata.canopy-contracts: strict (v0.22+): │ │
│ │ validate << bound input against op's Input contract before fire │ │
│ │ validate >> output against op's Output contract before bind │ │
│ │ halt with [contract-violation] on drift │ │
│ │ repeat until no remaining actions │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Stage 6: Verify expected outcomes │
│ ┌─ VERIFY_EXPECTED ──────────────────────────────────────────────────┐ │
│ │ compare resulting state against verify checklist │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ ┌─ ## Rules (guardrails) ────────────────────────────────────────────┐ │
│ │ • Never overwrite without confirmation │ │
│ │ • Always show plan before changes │ │
│ │ Enforced for the full duration of skill execution │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Stage 7: Respond │
│ ┌─ ## Response ──────────────────────────────────────────────────────┐ │
│ │ Declares output format: Summary / Changes / Notes │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
Op lookup (ALL_CAPS node -> definition): Category resources (standard layout):
1. my-skill/references/ops.md or ops/<name>.md (skill-local) scripts/ -> run named shell section
2. consumer-defined cross-skill ops (optional) assets/schemas/ -> op input/output contracts
3. canopy-runtime/references/ops.md + ops/<slice>.md (sliced) assets/policies/ -> active rules / guardrails
IF, ELSE, SWITCH, FOR_EACH, PARALLEL, ASK, SHOW_PLAN, ... assets/templates/ -> fill <token> -> write file
assets/constants/ -> load named values
Backward-compatible: legacy <skill>/ops.md at root still works. assets/checklists/ -> evaluation criteria
assets/verify/ -> post-run checklist
references/<other>.md -> docs loaded on demand
Runtime specs (loaded at Stage 2, slice-driven since v0.21.0):
canopy-runtime/references/runtime-claude.md -> .claude/ paths, native subagents
canopy-runtime/references/runtime-copilot.md -> .github/ paths, inline subagent fallback
canopy-runtime/references/skill-resources.md -> anatomy, op lookup, categories, contracts
canopy-runtime/references/ops.md -> primitive-slice index
canopy-runtime/references/ops/<slice>.md -> per-feature slice (loaded on demand)
The seven stages are not magic — they’re just what the tree’s first nodes typically look like for a well-formed skill. A trivial skill with no subagent, no SHOW_PLAN, no VERIFY_EXPECTED skips Stages 3, 4, and 6. The runtime doesn’t enforce stages; it walks the tree.
The runtime / authoring split
Canopy ships as three separate skills, not one:
| Skill | What it does | When you need it |
|---|---|---|
canopy-runtime |
Execution engine — interprets canopy-flavored skills, owns the primitive specs, platform detection, op-lookup chain. | Always. Without it, a ## Tree skill is just text. |
canopy |
Authoring agent — /canopy create / modify / scaffold / validate / improve / advise / refactor / convert. Depends on canopy-runtime for the framework spec. |
Only when authoring. Skip it if you only run skills others wrote. |
canopy-debug |
Trace wrapper — runs any canopy-flavored skill with phase banners + per-node tracing. | Only when debugging. Optional. |
The split exists so a consumer who just wants to execute canopy skills (someone else’s, ones they wrote earlier) can install canopy-runtime alone — no authoring agent in their slash menu, no extra context loaded ambiently. Authoring is the heavier role; it gets its own skill that depends on the runtime.
For install paths and how to pick which combination to install, see Getting Started — Authoring vs. execution.
agentskills.io alignment
Canopy is a meta-framework on top of agentskills.io. It doesn’t replace the spec; it adds a tree-shaped skill format on top of the existing one.
What’s spec-compliant (any agentskills.io-aware tool can install + read):
SKILL.mdat the skill root (uppercase, exact spelling)- Frontmatter with
name,description,license,compatibility,metadata,allowed-tools - Standard layout:
scripts/,references/,assets/{templates,constants,schemas,checklists,policies,verify}/ - The
compatibilityfield as free-text (≤500 chars), naming dependencies + install tools
What’s canopy-specific (loaded ambiently via canopy-runtime):
- The
## Treesection and its tree-notation grammar - Framework primitives (
IF,ELSE_IF,ELSE,SWITCH,CASE,DEFAULT,FOR_EACH,PARALLEL,ASK,SHOW_PLAN,EXPLORE,VERIFY_EXPECTED,BREAK,END) - Per-op subagent dispatch —
> **Subagent.**op-def markers + bold**OP_NAME**call sites (v0.20+); legacy## Agentsingular soft-compat - Universal op contracts —
> **Input contract:**/> **Output contract:**blockquote markers on op definitions (v0.22+); strict-mode runtime enforcement viametadata.canopy-contracts: strict - The op-lookup chain (skill-local → project → framework’s sliced primitive spec)
- Per-skill
metadata.canopy-featuresslice manifest — runtime lazy-loads only the named primitive families (v0.21+)
This split matters because an agent that doesn’t have canopy-runtime active will still be able to parse a canopy skill (the frontmatter is spec-compliant) and the safety preamble will halt execution before the agent tries to interpret ## Tree — preventing free-form prose-style misexecution.
Debugging
Use the canopy-debug skill to trace any canopy-flavored skill’s execution in real time:
/canopy-debug <skill-name> [arguments]
No changes to the target skill are required. canopy-debug wraps the target with phase banners and per-node tracing, so each tree node’s execution is visible.
For the full debug-mode reference (output protocol, trace ops, verify steps), see Reference — Debug Mode.