SKILL.md Format
Define agent skills using the SKILL.md format with YAML frontmatter and tool definitions.
SKILL.md Format
Skills are defined in Markdown files inside skills/<skill-name>/SKILL.md. Each file supports optional YAML frontmatter and two body formats.
---
name: weather
icon: π€οΈ
category: utilities
tags:
- weather
- forecast
- api
description: Weather data skill
metadata:
forge:
requires:
bins:
- curl
env:
required: []
one_of: []
optional: []
---
## Tool: weather_current
Get current weather for a location.
**Input:** location (string) - City name or coordinates
**Output:** Current temperature, conditions, humidity, and wind speed
## Tool: weather_forecast
Get weather forecast for a location.
**Input:** location (string), days (integer: 1-7)
**Output:** Daily forecast with high/low temperatures and conditionsEach ## Tool: heading defines a tool the agent can call. The frontmatter declares binary dependencies and environment variable requirements. Skills compile into JSON artifacts and prompt text during forge build.
YAML Frontmatter
Top-level fields:
| Field | Required | Description |
|---|---|---|
name | yes | Skill identifier (kebab-case) |
icon | yes | Emoji displayed in the TUI skill picker |
category | yes | Grouping for forge skills list --category (e.g., sre, developer, research, utilities) |
tags | yes | Discovery keywords for forge skills list --tags (kebab-case) |
description | yes | One-line summary β and the skill's activation trigger. The agent routes a request to a skill by matching it against this description in the ## Available Skills catalog, so state when the skill fires (the phrases/intents a user would say), not just what it does. description: When the user asks the time ("what time is it", "current time"), reply in German with Brisbane time. routes far more reliably than description: German time skill. See skill routing. |
Skill activation / routing
Installed skills are advertised to the agent in an ## Available Skills catalog (built by the runtime) as - name: description. The agent is directed to check the catalog for a matching skill before answering from its own defaults and, on a match, read_skill to load and follow it (issue #271). Two things make this reliable:
- A trigger-rich
description(above) β the only signal the agent matches on before loading the skill. - The runtime routing directive in the catalog preamble, which tells the agent to prefer a matching skill over its own default behavior (and to fall back to defaults only when nothing matches, so unrelated requests aren't over-routed).
Note that description serves two audiences: the internal routing catalog above, and the public A2A Agent Card (skills[], FWS-1), where it projects verbatim. Trigger-rich phrasing reads slightly like a routing rule to an external caller, but is generally more informative than a bare capability label β this dual use is deliberate. If the two audiences ever need to diverge, a dedicated triggers: field is the escape hatch.
The metadata.forge.requires block declares runtime dependencies:
binsβ Binary dependencies that must be in$PATHat runtime. Each entry is either a scalar name (matched against the embedded registry) or a mapping with its own install method (url:,run:,apt:,apk:). See Binary Dependencies for the resolution pipeline, install methods, and the four ways to add a binary.env.requiredβ Environment variables that must be setenv.one_ofβ At least one of these environment variables must be setenv.optionalβ Optional environment variables for extended functionality
The metadata.forge.runtime field selects how the skill's tool is executed (issue #182):
| Value | Behavior |
|---|---|
script (default; empty = script) | The ## Tool: binds to a script at skills/<dir>/scripts/<tool>.{sh,py,js} (the tool name matches the file in either its underscore or hyphen form) and is invoked as <interpreter> <scriptPath> <jsonArgs>, interpreter chosen by extension. |
binary | The first metadata.forge.requires.bins entry IS the executable. The runtime resolves it via exec.LookPath and invokes <binary> <jsonArgs> directly β no bash fork, no script file required. Skill body is documentation only. |
# Binary skill β wraps the `infil` binary directly. OTel-instrumented
# binaries inherit the parent agent's `tool.<name>` span via TRACEPARENT
# env (see observability-tracing.md Β§ Subprocess propagation).
metadata:
forge:
runtime: binary
requires:
bins:
- name: infil
version: ">=0.4.0"Both runtimes receive the same env passthrough (skill-declared env.optional, provider base URLs, TRACEPARENT + curated OTEL_* for tracing) β the binary path just removes the wrapper hop.
Frontmatter is parsed by ParseWithMetadata() in forge-skills/parser/parser.go and feeds into the compilation pipeline.
Legacy List Format
# Agent Skills
- translate
- summarize
- classifySingle-word list items (no spaces, max 64 characters) create name-only skill entries. This format is simpler but provides less metadata.
Skill Registry
Forge ships with a built-in skill registry. Add skills to your project with a single command:
# Add a skill from the registry
forge skills add tavily-research
# Validate skill requirements
forge skills validate
# Audit skill security
forge skills audit --embeddedforge skills add copies the skill's SKILL.md and any associated scripts into your project's skills/ directory. It validates binary and environment requirements, checks for existing values in your environment, .env file, and encrypted secrets, and prompts only for truly missing values with a suggestion to use forge secrets set for sensitive keys. If the skill declares egress_domains, they are automatically merged into the forge.yaml egress.allowed_domains list (deduplicated and sorted).
Skills as First-Class Tools
Script-backed skills are automatically registered as first-class LLM tools at runtime. When a skill has scripts in skills/scripts/, Forge:
- Parses the skill's SKILL.md for tool definitions, descriptions, and input schemas
- Creates a named tool for each
## Tool:entry (e.g.,tavily_researchbecomes a tool the LLM can call directly) - Executes the skill's shell script with JSON input when the LLM invokes it
This means the LLM sees skill tools alongside builtins like web_search and http_request β no generic cli_execute indirection needed.
For skills without scripts (binary-backed skills like k8s-incident-triage), Forge injects the full skill instructions into the system prompt. The complete SKILL.md body β including triage steps, detection heuristics, output structure, and safety constraints β is included inline so the LLM follows the skill protocol without needing an extra tool call. Skills are invoked via cli_execute with the declared binary dependencies.
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β LLM Tool Registry β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ€
β Builtins β web_search, http_request β
β Skill Tools β tavily_research, codegen_* β β auto-registered from scripts
β read_skill β load any SKILL.md on demand β
β cli_execute β run approved binaries β
βββββββββββββββββββ΄ββββββββββββββββββββββββββββββββ€
β System Prompt: full skill instructions inline β β binary-backed skills
βββββββββββββββββββββββββββββββββββββββββββββββββββSkill Execution Security
Skill scripts run in a restricted environment via SkillCommandExecutor:
- Isolated environment: Only
PATH,HOME, and explicitly declared env vars are passed through - OAuth token resolution: When
OPENAI_API_KEYis set to__oauth__, the executor resolves OAuth credentials and injects the access token,OPENAI_BASE_URL, and the configured model asREVIEW_MODEL - Configurable timeout: Each skill declares a
timeout_hintin its YAML frontmatter (e.g., 300s for research) - No shell string execution: a
## Tool:script runs as<interpreter> <script> <json-input>β the interpreter is chosen by extension (.sh/.bashβbash,.pyβpython3,.jsβnode; #405 D2), NOT by passing the command through a shell string, so the JSON argument is a single opaqueargv[1]and can't be shell-injected. Ensure a non-shell interpreter (python3/node) is provisioned (requires.bins); a## Tool:whose interpreter is missing from PATH is skipped with a warning rather than failing at call time. - Egress proxy enforcement: When egress mode is
allowlistordeny-all, a local HTTP/HTTPS proxy is started andHTTP_PROXY/HTTPS_PROXYenv vars are injected into subprocess environments, ensuringcurl,wget, Pythonrequests, and other HTTP clients route through the same domain allowlist used by in-process tools (see Egress Security)
Symlink Escape Detection
The skill scanner validates symlinks when a filesystem root path is available. Symlinks that resolve outside the root directory are skipped with a warning log. This prevents malicious symlinks in skill directories from escaping the project boundary. The scanner exposes ScanWithRoot(fsys, rootPath) for callers that need symlink validation, while the original Scan(fsys) remains backward-compatible.
Trust Policy Defaults
The default trust policy requires checksum verification (RequireChecksum: true). Skills loaded without a signature emit a warning log at scan time. Signature verification remains opt-in (RequireSignature: false).
Skill Categories & Tags
All embedded skills must declare category, tags, and icon in their frontmatter. Categories and tags must be lowercase kebab-case.
---
name: k8s-incident-triage
icon: βΈοΈ
category: sre
tags:
- kubernetes
- incident-response
- triage
---Use categories and tags to filter skills:
# List skills by category
forge skills list --category sre
# Filter by tags (AND semantics β skill must have all listed tags)
forge skills list --tags kubernetes,incident-response