Skills
A skill is a reusable instruction bundle - a SKILL.md file (or a flat .md file) that teaches an agent a procedure: how to review a diff, run a release, follow a house style. Skills are mounted once and shared by every agent in a run, so project knowledge does not have to be copied into each agents.<name>.prompt.
Loading is progressive: the system prompt gets only a compact index (skill name + description). The full body is returned by the read_skill tool when the agent decides it needs it. Context usage therefore tracks the skills actually used, not the size of the skills tree.
Directory layout
Both layouts work, and can be mixed in one directory:
skills/
├── code-review/
│ └── SKILL.md # directory form
├── release-checklist.md # flat form
└── notes/ # no SKILL.md -> skippedLoadSkills scans the top level of the directory:
- A subdirectory is a skill when it contains a
SKILL.md; the rest of that subdirectory is ignored by the loader (the body may still reference files next to it). - A flat
.mdfile at the top level is a skill. - Other files and subdirectories without a
SKILL.mdare skipped.
An unreadable directory, or a directory containing no skills, is an error - the CLI exits 3 before the first LLM call.
Frontmatter
Frontmatter is optional. When present it supplies the index entry:
---
name: code-review
description: Review a diff for correctness, security, and missing tests.
---
# Code Review
Read the diff, then report findings as `file:line - severity - issue`.namefails back to the directory name (for<slug>/SKILL.md) or the file stem (for<slug>.md).descriptionfalls back to the first non-empty body line (heading markers stripped).- Malformed frontmatter is ignored: the whole file is treated as the body, and the fallback name/description apply.
Names are matched case-insensitively by read_skill. When several directories are layered, the first directory wins on a duplicate name, so put the project-specific directory before a shared one.
CLI
--skills DIR is accepted by flow, goal, and agent. Repeat it to layer directories:
zenflow flow review.yaml \
--model fptcloud/DeepSeek-V4.1-Flash \
--system-prompt @./review-policy.md \
--skills ./skills \
--skills ../shared-skillsThe flag is CLI-only: there is no skills: field in workflow YAML. The same run from a library embedding uses WithSkills.
Because read_skill is an ordinary tool, the permission gate applies. On a non-interactive terminal, allow it explicitly:
zenflow agent "review the diff" --skills ./skills --strict --allow read_skill--sandbox can also allow it by adding it on top of the safe set: --sandbox --allow read_skill. Without an allow entry, strict mode (and therefore sandbox mode) denies the tool, and the agent cannot load any skill body.
Go API
skills, err := zenflow.LoadSkills("./skills")
if err != nil {
return err
}
orch := zenflow.New(
zenflow.WithModel(model),
zenflow.WithSkills(skills),
zenflow.WithSystemPromptSuffix("Always cite file:line."),
)
defer orch.Close()LoadSkills(dir)loads one directory;LoadSkillsDirs(dirs...)merges several (first wins).WithSkills(s)installs the collection on the orchestrator:Newregisters theread_skilltool on the catalog and appendss.IndexBlock()to the shared system-prompt suffix.Skills.Names(),Skills.Len(),Skills.IndexBlock(), andSkills.Tool()expose the collection for callers that want to build their own prompt or tool wiring.
Prompt ordering
For every step agent the system slot is built as:
agents.<name>.prompt- the workflow-declared role (the base).--system-prompt/WithSystemPromptSuffix- operator policy, appended after the base.- The skills index - appended after the operator policy.
The coordinator installed via WithCoordinator receives the same operator policy and skills index, so coordinator behaviour follows the same rules as the step agents. Standalone RunAgent calls receive them too; with no workflow-declared agent prompt, the operator policy becomes the system slot directly.
Interaction with tool filtering
The read_skill tool is added to the orchestrator catalog, not to every agent unconditionally. An agent that declares a tools: allowlist only sees the tools it lists - include read_skill there:
agents:
reviewer:
description: Reviews the change.
tools: ["read", "grep", "read_skill"]An agent with no tools: field gets every registered tool, including read_skill. A caller that registers its own tool named read_skill keeps it: the built-in loader does not overwrite an existing entry.
Tool steps (tool: read with toolInput) do not run an LLM, so they never use skills.
See also
- Agents - where the base system prompt comes from.
- Tools - the catalog and the permission gate.
- Go API: options -
WithSystemPromptSuffix,WithSkills, and the full option set. - CLI flags -
--system-promptand--skills.