From 2cbefe74e39bd5f95234caed9ef6478ba7a98222 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 2 Jun 2026 12:45:13 -0600 Subject: [PATCH] docs: documented graph-based agent llm skills usage --- Graph-Agents.md | 56 +++++++++++++++++++++++++++++++++ Skills.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/Graph-Agents.md b/Graph-Agents.md index 9ab02d4..05c528b 100644 --- a/Graph-Agents.md +++ b/Graph-Agents.md @@ -68,6 +68,10 @@ global_tools: # global tools available to nodes - web_search_coyote.sh mcp_servers: # MCP servers available to nodes - pubmed-search +skills_enabled: true # optional; master switch for skills in `llm` nodes +enabled_skills: # optional; the *universe* of skills referenceable by any llm node + - code-review + - git-master conversation_starters: # suggested prompts in the UI - "Research WebAssembly outside of the browser" variables: # optional; agent variables @@ -138,6 +142,13 @@ nodes: write in the same super-step (the validator catches missing reducers at load time). See [Parallel Execution](#parallel-execution) for the full reducer reference and merge semantics. +- **`skills_enabled` / `enabled_skills`:** Optional [skills](Skills) policy + for the graph. `skills_enabled: false` turns skills off for every `llm` + node in the graph regardless of node-level settings. `enabled_skills` is + the *universe*: the set of skill names any `llm` node may reference in + its own `enabled_skills`. Subset-violations are caught by the validator at + load time. See [Skills in Graph Agents](Skills#skills-in-graph-agents) for + the full per-node story. ### `{{initial_prompt}}`: Automatically Seeded @@ -458,6 +469,51 @@ failure message containing one of: `timed out`, `rate limit`, `429`, other error fails immediately without consuming further attempts. The default is `1` (no retries). +### Skills on `llm` nodes + +`llm` nodes are the only place [skills](Skills) attach inside a graph. Two +optional fields, mirroring the same fields on roles and agents: + +```yaml +implement: + type: llm + prompt: "{{plan_summary}}" + tools: [fs_write, fs_patch, fs_cat] + skills_enabled: true # optional; default inherits from graph level + enabled_skills: # optional per-node restriction; must be a subset + - code-review # of graph-level enabled_skills + - verification-gates +``` + +Semantics (discovery-only, no auto-load): + +- **`skills_enabled`:** Per-node override of the skills master switch. + `false` disables skills for this node only: no `skill__list` / + `skill__load` / `skill__unload` meta-tools are exposed to the model. + `true` or omitted inherits the graph-level / agent-level setting. +- **`enabled_skills`:** Per-node restriction of which skills the model can + see and load. The graph-level `enabled_skills` is the universe; the + per-node list must be a subset (the validator catches violations at + load time). When set, `skill__list` shows only these skills and + `skill__load` only accepts these names. +- **The model loads what it needs.** Nothing is auto-loaded. While the + node runs, the model uses `skill__list` to discover what's available + and `skill__load` to bring a skill's body / tools / MCP servers into + scope when it actually needs them. This matches the design intent of + skills as a dynamic capability layer. +- **Per-node policy isolation.** The node temporarily narrows the active + agent's skill policy to the node's `enabled_skills`. The original + policy is restored when the node finishes, so subsequent nodes see + their own policy. Any skills the model loaded during the node persist + into subsequent nodes by default; they're real registry insertions, + not node-scoped state. (Use the skill's own `auto_unload: true` + frontmatter for skills that should clean up automatically at turn + end.) + +Agent nodes (which spawn full sub-agents) intentionally have no +`enabled_skills` field. The spawned child agent's own +`config.yaml`/`graph.yaml` declares its skill policy. + --- ## rag diff --git a/Skills.md b/Skills.md index 42ce164..2d61602 100644 --- a/Skills.md +++ b/Skills.md @@ -198,6 +198,90 @@ Four validation points enforce these rules: --- +# Skills in Graph Agents + +[Graph agents](Graph-Agents) integrate with the skill system at two levels: a graph-wide *universe* set at the top of +`graph.yaml`, and a per-node *auto-load* set on `llm` nodes. Other node types (script, approval, input, rag, map, end) +have no skill surface. Skills only apply where a model call happens. + +## Graph level — the universe + +The top-level `skills_enabled` and `enabled_skills` fields in `graph.yaml` work just like they do on a normal agent's +`config.yaml`: they declare the policy ceiling for the whole graph. + +```yaml +name: coder +skills_enabled: true +enabled_skills: + - code-review + - git-master + - verification-gates +``` + +- **`skills_enabled: false`** at the graph level turns skills off for every `llm` node in the graph regardless of + node-level fields. Equivalent to "skills don't exist for this graph." +- **`enabled_skills`** at the graph level is the *universe*: the set of skill names any `llm` node in the graph is + allowed to reference in its own `enabled_skills`. The validator rejects per-node entries that aren't in this set at + load time. +- Omitting `enabled_skills` inherits whatever the role / global cascade resolves to (typically "all visible"). + +## Per-node: discovery-only on `llm` nodes + +`llm` nodes can independently declare `skills_enabled` and `enabled_skills`. The graph-level field gates what's +*allowed* in the graph at all; the node-level field narrows that universe to what *this* node's model can see and +load. Nothing is auto-loaded. The model uses `skill__list` and `skill__load` to bring skills in as it needs them, +matching the rest of Coyote's skill model. + +```yaml +nodes: + implement: + type: llm + prompt: "{{plan_summary}}" + tools: [fs_write, fs_patch, fs_cat] + enabled_skills: # must be a subset of graph-level enabled_skills + - code-review + - verification-gates + # skills_enabled: false # would disable skills for this node only +``` + +Semantics: + +- **Discovery, not auto-load.** When the node runs, `skill__list` is exposed to the model along with `skill__load` + and `skill__unload`. The model decides which skills (if any) to load based on the task at hand. Loaded skills + compose into the system prompt and union tools/MCP servers via the standard `effective_role` pipeline; the + loading itself goes through the same `refresh_tool_scope` path as `.skill load` in the REPL. +- **Per-node policy.** When `enabled_skills` is set on the node, the graph's effective skill policy is temporarily + narrowed to that subset for the duration of the node. `skill__list` will only show those skills and `skill__load` + will only accept those names. When the node finishes, the original policy is restored so the next node sees its + own. +- **`skills_enabled: false`** at the node level is an off-switch: no `skill__*` meta-tools are exposed and no skills + can be loaded from this node. Useful when one node in an otherwise skill-enabled graph should run with a strict, + narrow context (for example a structured-output extraction step that shouldn't load editorial conventions). +- **Skill state carry-over.** Skills the model loads during a node persist into subsequent nodes (they're real + registry insertions, not node-scoped state). If you want a skill to clean up automatically at turn end, mark it + `auto_unload: true` in the skill's own frontmatter. + +## Agent nodes have no skill surface + +`agent` nodes spawn a full child agent (graph or normal). That child agent declares its own skill policy in its own +`config.yaml` / `graph.yaml`. Adding a skill field on the agent node would be redundant at best and surprising at +worst. The child author already controls what skills the child uses. So agent nodes intentionally don't accept +`enabled_skills` / `skills_enabled`. + +## Validation + +| Where | Check | Severity | +|----------------|------------------------------------------------------------------------------------------------------------|------------| +| Graph load | Per-node `enabled_skills` entries are non-empty strings | Hard error | +| Graph load | Per-node `enabled_skills` are a subset of graph-level `enabled_skills` (when set) | Hard error | +| Node execution | Each auto-loaded skill resolves to a real installed skill (otherwise that skill is skipped with a warning) | Warn | +| Node execution | Each auto-loaded skill's MCP / function-calling requirements are satisfied | Warn | + +The validator only runs `validate_before_run` (the default); inspecting a graph via `coyote --info -a ` does +not trigger validation, so subset-violation errors surface on the first real run. + +--- + # Auto-unload Skills with `auto_unload: true` in their frontmatter are removed from the registry at the end of each turn. Specifically,