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 doctor output 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:

#LayerSourceWhen
1CLI--skill-dir <path> (repeatable)Ephemeral overrides, CI pinning
2Env$HARN_SKILLS_PATH (colon-separated on Unix, ; on Windows)Deployment config, Docker, cloud agents
3Project.harn/skills/<name>/SKILL.md walking up from the scriptDefault for repo-scoped skills
4Manifest[skills] paths + [[skill.source]] in harn.tomlMulti-root, shared across siblings
5User~/.harn/skills/<name>/SKILL.mdPersonal skills across projects
6Package.harn/packages/**/skills/<name>/SKILL.mdSkills shipped via [dependencies]
7System/etc/harn/skills/ + $XDG_CONFIG_HOME/harn/skills/Managed / enterprise
8HostRegistered via the bridge at runtimeCloud / 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):

FieldTypePurpose
namestringOptional in frontmatter when the directory name already provides it; Harn falls back to the enclosing skill directory basename.
shortstringRequired. One-sentence compact card describing what the skill does and when to use it. Always loaded into the startup registry and catalogs.
descriptionstringOptional longer summary. Useful for richer CLI output or custom matchers, but not required for lazy discovery.
when-to-usestringLonger activation trigger.
disable-model-invocationboolIf true, never auto-activate — explicit use only.
allowed-toolslist of stringRestrict 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-invocableboolExpose the skill to end users via a slash menu.
pathslist of globFiles the skill expects to touch.
contextstring"fork" runs in an isolated subcontext.
agentstringSub-agent that owns the skill.
hooksmap or listShell commands for lifecycle events. Filesystem skills only surface hooks when their detached provenance verifies as trusted.
modelstringPreferred model alias.
effortstringlow / medium / high.
require-signatureboolRequire a valid detached signature before the skill is admitted to the startup registry or promoted by load_skill(...).
trusted-signerslist of stringOptional signer fingerprint allowlist layered on top of the trusted registry.
shellstringShell to run the body under when context is shell-ish.
argument-hintstringUI 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). $0 is reserved.
  • ${HARN_SKILL_DIR} → absolute path to the skill directory
  • ${HARN_SESSION_ID} → opaque session id threaded through the run
  • ${OTHER_NAME} → looks up OTHER_NAME in 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 full SKILL.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-owned load_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:

  1. Keep a compact catalog of available skills in the system prompt.
  2. 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"
  • paths is joined against the directory holding harn.toml and supports a single trailing * component (packages/*/skills).
  • lookup_order inverts layer priority — for example, preferring user over project on a personal checkout without touching the repo.
  • disable removes entire layers from discovery; harn doctor reports the disabled set.
  • signer_registry_url points at a flat directory or URL prefix that serves <fingerprint>.pub signer files for skill signature verification.
  • Skills that declare require_signature = true are 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 type git expect their materialized checkout to live under .harn/packages/<name>/skills/ — run harn install to populate it.
  • registry entries 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 a SkillManifestRef into a Skill.
  • 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 harn binary (harn-language, harn-orchestration, harn-testing, …). Access these with harn skills list, harn skills get <name>, and harn skills dump --all. Set HARN_SKILLS_DIR to a directory of recursive SKILL.md files to make list, get, and dump read 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 with harn skills resolved, harn skills inspect <name>, and harn skills match. What you see there is byte-for-byte the registry that harn run / harn test / harn check hand 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/repo shorthand (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_query event, pairing each query with its tool_search_result so 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".