Skills
Harn discovers skills — bundled instructions, tool lists, and
activation rules — from the filesystem and from the host process. Every
skill is a directory containing a SKILL.md file with YAML
frontmatter plus a Markdown body; the format matches Anthropic's
Agent Skills
and Claude Code specs, so
skills you author once work across both environments.
This page describes:
- the layered discovery hierarchy (CLI > env > project > manifest > user > package > system > host),
- the SKILL.md frontmatter Harn recognizes, including the required
compact
short:card, - the body substitution (
$ARGUMENTS,$N,${HARN_SKILL_DIR},${HARN_SESSION_ID}) that runs over SKILL.md before the model sees it, - the
harn.toml[skills]/[[skill.source]]tables, and - the
harn doctoroutput for diagnosing collisions / missing entries.
The companion language form — skill NAME { ... } — is documented in
Language basics and the skill builtins
(skill_registry, skill_define, skill_find, skill_list,
skill_render, load_skill, skills_catalog_entries,
render_always_on_catalog, …) in Builtin functions.
Layered discovery
When harn run / harn test / harn check starts, every discovered
skill is merged into a single registry and exposed as the pre-populated
VM global skills. That startup registry is intentionally compact: it
keeps the frontmatter card and activation metadata, but leaves the full
Markdown body behind the lazy load_skill(...) path. The layers — in
order of highest to lowest priority — are:
| # | Layer | Source | When |
|---|---|---|---|
| 1 | CLI | --skill-dir <path> (repeatable) | Ephemeral overrides, CI pinning |
| 2 | Env | $HARN_SKILLS_PATH (colon-separated on Unix, ; on Windows) | Deployment config, Docker, cloud agents |
| 3 | Project | .harn/skills/<name>/SKILL.md walking up from the script | Default for repo-scoped skills |
| 4 | Manifest | [skills] paths + [[skill.source]] in harn.toml | Multi-root, shared across siblings |
| 5 | User | ~/.harn/skills/<name>/SKILL.md | Personal skills across projects |
| 6 | Package | .harn/packages/**/skills/<name>/SKILL.md | Skills shipped via [dependencies] |
| 7 | System | /etc/harn/skills/ + $XDG_CONFIG_HOME/harn/skills/ | Managed / enterprise |
| 8 | Host | Registered via the bridge at runtime | Cloud / embedded hosts |
Name collisions: when two layers both expose a skill named deploy,
the higher layer wins. The shadowed entry is recorded so harn doctor
can surface it. Scripts that need both at once can register a
fully-qualified <namespace>/<skill> id via [[skill.source]] in the
manifest (see below).
SKILL.md frontmatter
The frontmatter is YAML, delimited by --- on its own line above and
below. Unknown fields are not hard errors — harn doctor reports
them as warnings so newer spec fields roll out cleanly.
See Skill provenance for detached signatures,
trusted signers, and load_skill(..., require_signature: true).
---
name: deploy
short: Deploy the application when the user asks for a release
description: Deploy the application to production
when-to-use: User says deploy / ship / release
disable-model-invocation: false
user-invocable: true
allowed-tools: [bash, git]
paths:
- infra/**
- Dockerfile
context: fork
agent: ops-lead
model: claude-opus-4-7
effort: high
shell: bash
argument-hint: "<target-env>"
hooks:
on-activate: echo "starting deploy"
on-deactivate: echo "deploy ended"
---
# Deploy runbook
Ship it: `$ARGUMENTS`. Skill directory: `${HARN_SKILL_DIR}`.
Recognized fields (Harn normalizes hyphens to underscores, so
when-to-use and when_to_use are the same key):
| Field | Type | Purpose |
|---|---|---|
name | string | Optional in frontmatter when the directory name already provides it; Harn falls back to the enclosing skill directory basename. |
short | string | Required. One-sentence compact card describing what the skill does and when to use it. Always loaded into the startup registry and catalogs. |
description | string | Optional longer summary. Useful for richer CLI output or custom matchers, but not required for lazy discovery. |
when-to-use | string | Longer activation trigger. |
disable-model-invocation | bool | If true, never auto-activate — explicit use only. |
allowed-tools | list of string | Restrict tool surface while the skill is active. Entries accept three shapes: an exact tool name ("deploy_service"), a namespace tag ("namespace:read" — matches every tool declared with namespace: "read"), or "*" (escape hatch that keeps the full surface, useful for skills that only carry prompt context). |
user-invocable | bool | Expose the skill to end users via a slash menu. |
paths | list of glob | Files the skill expects to touch. |
context | string | "fork" runs in an isolated subcontext. |
agent | string | Sub-agent that owns the skill. |
hooks | map or list | Shell commands for lifecycle events. Filesystem skills only surface hooks when their detached provenance verifies as trusted. |
model | string | Preferred model alias. |
effort | string | low / medium / high. |
require-signature | bool | Require a valid detached signature before the skill is admitted to the startup registry or promoted by load_skill(...). |
trusted-signers | list of string | Optional signer fingerprint allowlist layered on top of the trusted registry. |
shell | string | Shell to run the body under when context is shell-ish. |
argument-hint | string | UI hint for $ARGUMENTS. |
Tool scoping with namespace:<tag>
Tool declarations that carry a namespace: field can be grouped into
one allowed-tools entry instead of enumerating names. Given
tool_define(reg, "read_file", "...", {namespace: "read", ...})
tool_define(reg, "list_files", "...", {namespace: "read", ...})
tool_define(reg, "write_file", "...", {namespace: "write", ...})
a skill with allowed-tools: ["namespace:read"] scopes the turn to
read_file + list_files and hides write_file. Exact tool names
and the wildcard "*" remain valid and can mix freely:
allowed-tools: ["namespace:read", "grep", "*"]
Malformed entries fail loudly at skill_define time — a bare ":"
without a tag or a colon-prefixed entry that isn't namespace: raises
so authors don't silently scope to an empty set.
Body substitution
When a skill body is rendered (via skill_render, load_skill, or by a
host before handing the body to the model), the following substitutions
run over the Markdown body:
$ARGUMENTS→ all positional args joined with spaces$N→ the N-th positional arg (1-based).$0is reserved.${HARN_SKILL_DIR}→ absolute path to the skill directory${HARN_SESSION_ID}→ opaque session id threaded through the run${OTHER_NAME}→ looks upOTHER_NAMEin the process environment$→ literal$
Missing positional args ($3 when only $1 was supplied) pass
through unchanged so authors see what wasn't supplied rather than a
silent empty substitution.
let deploy = skill_find(skills, "deploy")
guard deploy != nil else {
exit(1)
}
let rendered = skill_render(deploy, ["prod", "us-east-1"])
// rendered now has $1 and $2 replaced with "prod" and "us-east-1".
Progressive disclosure with load_skill
Lazy skill loading surfaces in two places, both resolving the skill id against the active registry and applying the same substitution rules:
load_skill("deploy")is a stdlib builtin for Harn code. It hydrates the fullSKILL.md, applies substitution, and returns the rendered body as a string.- When an agent loop receives a skill registry through
skills:, Harn also exposes a runtime-ownedload_skill({ name })tool for the model. That tool calls the same lazy loader and returns the rendered body in the next turn.
Builtin example:
let runbook = load_skill("deploy")
assert(contains(runbook, "Deploy runbook"), "full body is fetched lazily")
If the target skill has disable-model-invocation: true, the runtime
tool returns a typed error instead of leaking the body. Direct Harn-code
load_skill("name") calls are explicit and are not blocked by that
flag.
Always-on catalog helper
The recommended harness convention is:
- Keep a compact catalog of available skills in the system prompt.
- Let the model call the runtime
load_skill({ name })tool only when one of those entries looks relevant.
Harn ships two pure helpers for that pattern:
let entries = skills_catalog_entries(skills)
let catalog = render_always_on_catalog(entries, 2000)
skills_catalog_entries projects the resolved registry into compact
{name, description, when_to_use} cards, with description sourced
from the required short: frontmatter (sorted deterministically by
skill id, using <namespace>/<name> when present).
render_always_on_catalog formats those cards into a stable prompt
block and trims the list to the requested character budget.
Copy-pasteable example:
let catalog = render_always_on_catalog(skills_catalog_entries(skills), 2000)
let result = agent_loop(
"Help me ship this release",
catalog,
{
provider: "mock",
model: "gpt-5.4",
loop_until_done: true,
skills: skills,
},
)
On a later turn the model can emit:
load_skill({ name: "deploy" })
and the next turn will see the substituted SKILL.md body in the tool
result, while any allowed-tools declared by that skill narrow the
tool surface for subsequent turns.
harn.toml [skills] + [[skill.source]]
Projects that share skills across siblings or pull them from a remote tag use the manifest instead of a per-script flag:
[skills]
paths = ["packages/*/skills", "../shared-skills"]
lookup_order = ["cli", "project", "manifest", "user", "package", "system", "host"]
disable = ["system"]
signer_registry_url = "./signers"
[skills.defaults]
tool_search = "bm25"
always_loaded = ["look", "edit", "bash"]
[[skill.source]]
type = "fs"
path = "../shared"
[[skill.source]]
type = "git"
url = "https://github.com/acme/harn-skills"
tag = "v1.2.0"
[[skill.source]]
type = "registry" # reserved, inert until a marketplace exists
url = "https://skills.harnlang.com"
name = "acme/ops"
pathsis joined against the directory holding harn.toml and supports a single trailing*component (packages/*/skills).lookup_orderinverts layer priority — for example, preferringuseroverprojecton a personal checkout without touching the repo.disableremoves entire layers from discovery;harn doctorreports the disabled set.signer_registry_urlpoints at a flat directory or URL prefix that serves<fingerprint>.pubsigner files for skill signature verification.- Skills that declare
require_signature = trueare omitted from the startup registry unless their detached signature chain verifies. - User and system layer skills are also omitted when they carry a failed provenance check; unsigned entries can still load, but executable hook frontmatter is only surfaced for verified skills.
[[skill.source]]entries of typegitexpect their materialized checkout to live under.harn/packages/<name>/skills/— runharn installto populate it.registryentries are accepted but inert until a Harn Skills marketplace exists.
harn doctor
harn doctor reports the resolved skill catalog:
OK skills 3 loaded (1 cli, 1 project, 1 user)
WARN skill:deploy shadowed by cli layer; user version at /home/me/.harn/skills/deploy is hidden
WARN skill:review unknown frontmatter field(s) forwarded as metadata: future_field
SKIP skills-layer:system layer disabled by harn.toml [skills.disable]
CLI flags
harn run --skill-dir <path>(repeatable) — highest-priority layer.harn test --skill-dir <path>— same semantics for user tests and conformance fixtures.$HARN_SKILLS_PATH— colon-separated list of directories, applied to every invocation.
Bridge protocol
Hosts expose their own managed skill store through three RPCs:
skills/list(request) — response is an array of{ id, name, description, source }entries.skills/fetch(request) — payload{ id: "<skill id>" }; response is the full manifest + body shape so the CLI can hydrate aSkillManifestRefinto aSkill.skills/update(notification, host → VM) — invalidates the VM's cached catalog. The CLI re-runs discovery on the next boundary.
See Bridge protocol for wire-format details.
Managing skills
The harn skills CLI splits into two complementary surfaces:
- Canonical corpus — the Harn skills normally shipped inside the
harnbinary (harn-language,harn-orchestration,harn-testing, …). Access these withharn skills list,harn skills get <name>, andharn skills dump --all. SetHARN_SKILLS_DIRto a directory of recursiveSKILL.mdfiles to makelist,get, anddumpread from disk while iterating locally; an unset, missing, or empty directory falls back to the embedded corpus. - Layered FS discovery — user-authored skills picked up from
--skill-dir,HARN_SKILLS_PATH, project, manifest, user, packages, system, and host layers. Access these withharn skills resolved,harn skills inspect <name>, andharn skills match. What you see there is byte-for-byte the registry thatharn run/harn test/harn checkhand to the VM.
harn skills list
Lists the canonical skill corpus for this build of harn. By default
that is the embedded corpus; when HARN_SKILLS_DIR points at one or
more recursive SKILL.md files, the disk corpus is listed instead.
Pass --json for a versioned JsonEnvelope payload that agents and
CI can pipe through jq.
$ harn skills list
Embedded canonical skills (7):
harn-agent Agent runtime, supervisor wiring, and tool callers.
harn-diagnostics Diagnostic codes, severity rules, suppression hints.
harn-language Harn syntax, modules, types, diagnostics, script structure.
harn-orchestration Triggers, orchestrator handoffs, parallelism primitives.
harn-providers Provider catalog, model packs, fallback policy.
harn-testing Conformance suite, deterministic test patterns.
harn-tracing Transcripts, eval replay, observability surfaces.
Run `harn skills get <name>` for one entry's frontmatter.
Run `harn skills get <name> --full` to include the body.
The --json output uses the standard envelope (schemaVersion, ok,
data, error, warnings). The data.skills array contains the
same frontmatter fields shown above:
$ harn skills list --json | jq '.data.skills | length'
7
harn skills get <name>
Prints frontmatter for a single canonical skill. Pass --full to
include the SKILL.md body; pass --json to wrap the output in a
JsonEnvelope for machine consumption. HARN_SKILLS_DIR follows the
same disk-override/fallback behavior as harn skills list.
$ harn skills get harn-language
name: harn-language
short: Harn syntax, modules, types, diagnostics, and script structure.
description: Use for Harn language syntax, typechecking, modules, imports, and idiomatic script authoring.
when_to_use: Use when writing, reviewing, or explaining Harn source code and language-level behavior.
$ harn skills get harn-language --full --json | jq '.data | keys'
[
"body",
"description",
"name",
"short",
"when_to_use"
]
Unknown names return ok: false with a skill_not_found error code
and the list of available skills in error.details.available.
harn skills dump --all [--out <dir>]
Writes every active canonical skill to disk as a <dir>/<name>/SKILL.md
tree so agents and CI can review the corpus offline. By default that
means the embedded corpus; when HARN_SKILLS_DIR contains recursive
SKILL.md files, dump mirrors that disk corpus byte-for-byte instead.
Defaults the output directory to ./skills. Refuses to overwrite
existing files unless --force is passed.
$ harn skills dump --all --out /tmp/skills
Wrote 7 skill(s) to /tmp/skills
/tmp/skills/harn-agent/SKILL.md
/tmp/skills/harn-diagnostics/SKILL.md
…
harn skills resolved
Prints every FS-resolved skill with the layer it came from. Pass
--all to include shadowed entries; pass --json for newline-delimited
JSON records:
$ harn skills resolved
Resolved skills (3):
deploy [cli] Deploy to production when release work is requested
review [project] Review a pull request when asked for code review help
helpers/utils [package] Shared helpers when the task needs the acme/ops package
Shadowed skills (1):
deploy winner=[cli] hidden=[user] origin=/home/me/.harn/skills/deploy
harn skills inspect <name>
Dumps the resolved SKILL.md — frontmatter, bundled files under the
skill directory, and the full body — for a specific skill. Accepts
bare <name> or fully-qualified <namespace>/<name>:
$ harn skills inspect deploy
id: deploy
name: deploy
layer: cli
short: Deploy to production when release work is requested
description: Deploy to production with rollback support
skill_dir: /repo/.harn/skills/deploy
Bundled files:
files/runbook.md
files/rollback.sh
---- SKILL.md body ----
Run the deploy. Confirm replicas and then flip traffic.
harn skills match "<query>"
Runs the built-in metadata matcher (same scorer the agent loop uses)
against a prompt and prints the ranked candidates with their scores.
Supports --working-file to simulate path-glob matches:
$ harn skills match "deploy the staging service" --top-n 3
Match results for: deploy the staging service
1. deploy score=2.400 [cli] prompt mentions 'deploy'; 1 keyword hit(s)
2. review score=0.400 [project] 1 keyword hit(s)
Confirms that a SKILL.md's short: and when_to_use: frontmatter
attract the intended prompts.
harn skills install <spec>
Materializes a git ref or local path into .harn/skills-cache/ so
the filesystem package walker picks it up on the next run. The
.harn/skills-cache/ layout mirrors .harn/packages/:
$ harn skills install acme/harn-skills --tag v1.2.0
installing acme/harn-skills to .harn/skills-cache/harn-skills
installed — layer=package, path=.harn/skills-cache/harn-skills
<spec> accepts:
- A full git URL:
https://github.com/acme/harn-skills.git owner/reposhorthand (expands to GitHub):acme/harn-skills- A local filesystem path:
../shared/skills/deploy
Pass --namespace <ns> to shelf the install under a subdirectory so
it shows up in the resolver as <ns>/<skill>. Pass --tag <ref> to
pin a git branch or tag. Every install rewrites
.harn/skills-cache/skills.lock with the resolved source + commit.
harn skills new <name>
Scaffolds a new SKILL.md and files/ directory under .harn/skills/:
$ harn skills new deploy --description "Deploy to production"
Scaffolded skill 'deploy' at .harn/skills/deploy
SKILL.md
files/README.md
Edit the SKILL.md frontmatter and body, then run `harn skills resolved`
to verify the compact card is picked up.
Pass --dir <path> to target a different destination (for example
~/.harn/skills/deploy to scaffold under the user layer instead of
the project layer), or --force to overwrite an existing directory.
Portal observability
The Harn portal (harn portal) surfaces two skill-focused panels on
every run detail page:
- Skill timeline — horizontal bars showing which skills activated on which agent-loop iteration and when they deactivated. Hover a bar for the matcher score and the reason the skill was promoted.
- Tool-load waterfall — one row per
tool_search_queryevent, pairing each query with itstool_search_resultso you can see which deferred tools entered the LLM's context in each turn. - Matcher decisions — per-iteration expansions showing every candidate the matcher considered, its score, and the working-file snapshot it scored against.
The runs index page takes a skill=<name> filter so you can narrow
evals to runs where a specific skill was active. The same
skill=<name> query parameter works from a URL, making it easy to
link to "every run that used deploy".