Skills Reference¶
Skills declare specialist AI roles that participate in pipeline runs. Each skill is a directory under the configured skills root with two files:
SKILL.md— frontmatter (declarative metadata) + body (per-role prompts).agentsmith.md— optional, discussion-pipeline-only fields (display name, emoji, triggers, convergence criteria). The legacy## orchestrationsection is no longer read.
SKILL.md frontmatter is the single source of truth for orchestration metadata as of phase p0111. See migration guide for before/after.
Directory layout¶
<skills-root>/
├── _index/ # auto-generated, gitignored
│ ├── coding.yaml
│ └── security.yaml
├── concept-vocabulary.yaml # operator-extensible vocabulary
├── coding/ # used by fix-bug, add-feature
│ ├── architect/
│ │ ├── SKILL.md
│ │ └── references/...
│ └── tester/
├── security/ # used by security-scan
├── api-security/ # used by api-security-scan
├── legal/ # discussion pipeline
└── mad/ # discussion pipeline
Categories coding/ and security/ are strict mode — SKILL.md files lacking roles_supported are rejected at boot with a migration hint. legal/ and mad/ are exempt; they default to roles_supported=[Analyst].
SKILL.md frontmatter¶
Full schema:
---
name: architect # unique within the skills root
display_name: Architect
emoji: 🏛️
description: System architect who plans architectural changes.
roles_supported: [lead, analyst, reviewer] # which SkillRoles triage may assign
activation: # whether the skill participates at all
positive:
- { key: <vocab-key>, desc: "Why this key activates the skill." }
negative:
- { key: <vocab-key>, desc: "Why this key suppresses the skill." }
role_assignment: # per-role activation
lead:
positive:
- { key: <vocab-key>, desc: "Why this skill leads in this case." }
negative:
- { key: <vocab-key>, desc: "Why this skill should not lead." }
analyst:
positive: [...]
reviewer:
positive: [...]
references: # citable from body via {{ref:<id>}}
- { id: solid, path: "references/solid.md" }
output_contract:
schema_ref: skill-observation.schema.json
hard_limits:
max_observations: 8
max_chars_per_field: 500
output_type: # per-role expected output shape
lead: plan
analyst: list
reviewer: list
filter: list # or "artifact"
---
activation and role_assignment keys must exist in the project's concept vocabulary. ConceptVocabularyValidator warns at boot for unknown keys (it does not fail).
Body — per-role sections¶
The body splits into ## as_<role> sections:
## as_lead
Instructions for when this skill is assigned the Lead role.
## as_analyst
Instructions for the Analyst role.
## as_reviewer
Compare the implementation against the plan: {{plan}}.
Cite references with {{ref:solid}}.
## as_filter
Instructions for the Filter role (output: list = reduce; artifact = synthesize).
Skills without per-role sections fall back to the whole body for any assigned role — used by legacy skills under legal/ and mad/.
The body is lazily resolved by SkillBodyResolver:
- The
## as_<role>section matching the assigned role is selected (or the full body if none). {{ref:<id>}}placeholders are inlined from the file at the matchingreferences[].path.{{plan}}(in## as_reviewersections) is substituted with the run'sPlanArtifact. Reviewers without a same-run lead see(no plan provided).
The resolver caches per (skill, role) for the process lifetime; references are read once.
Output contract¶
output_contract.output_type[<role>] declares what the LLM is expected to return for that role:
| Value | Returned shape |
|---|---|
list |
JSON array of SkillObservation objects. |
plan |
Single structured plan object that reviewers compare against (Lead's typical output). |
artifact |
Free-form text (Filter mode for synthesis, e.g. final security report). |
hard_limits.max_observations caps how many observations the LLM may emit. max_chars_per_field caps each string field. Both are enforced by parsers downstream.
Per-provider overrides¶
Optional SKILL.<provider>.md files in a skill directory replace the base SKILL.md when the active provider matches. The active provider is read from primary_provider: at the root of agentsmith.yml; when unset (the default) the base SKILL.md always wins.
<skills-root>/coding/architect/
├── SKILL.md # base — used unless an override matches
├── SKILL.openai.md # used when primary_provider: openai
├── SKILL.claude.md # used when primary_provider: claude
└── references/
File-naming convention¶
SKILL.<provider>.md where <provider> matches one of: claude, openai, azure-openai, gemini, ollama. Matching is exact; an openai override is ignored when primary_provider: claude.
Frontmatter merge rules¶
Top-level frontmatter keys merge shallowly between override and base:
- A key declared in the override replaces base's value entirely (no deep merge inside
activation.positive[],role_assignment[role].*, etc. — the operator manages a complete declaration when overriding a key). - A key omitted from the override inherits base's value as-is.
- The body is taken from the override's file (after splitting on
## as_<role>headers).
name and roles_supported are special: the override must declare both, and they must match base exactly. Mismatched name or roles_supported is rejected at boot with a clear error — sibling skills with different identities should be different skill directories, not overrides of the same one.
Active-provider configuration¶
# agentsmith.yml
primary_provider: openai
projects:
agent-smith:
agent:
type: openai
model: gpt-4.1
...
Multi-provider deployments where, say, Planning uses Claude and Coding uses OpenAI use the single primary_provider for skill resolution — skill content is shared across the run, not partitioned by task. Operators with strong per-task tuning needs can author finer-grained config in a follow-up.
Boot-time logging¶
When an override loads, the framework logs at INFO level: Provider override loaded for skill 'architect' from .../SKILL.openai.md. Default behavior (no override matched, or no primary_provider set) is silent.
Zero overrides ship by default¶
The mechanism is the deliverable. No SKILL.<provider>.md files ship in the agent-smith-skills catalog. Provider-tuning is opt-in per skill, opt-in per operator — author the override locally, version it alongside the base in your project's skills directory, and set primary_provider to activate it.
Index files¶
SkillIndexBuilder writes a compact projection of each category's loaded skills to <skills-root>/_index/<category>.yaml at boot. The index is what the triage step reads; the LLM never sees the full body content of unassigned skills.
Read-only filesystems trigger a warning, not a crash — the index is best-effort cache, and the framework falls back to in-memory aggregation per process when writes fail. Operators who mount skills read-only can set SkillsConfig.IndexPath to a writable location.