Agent skills as SKILL.md playbooks, not plugins
Blog · ai-agents

Agent skills as SKILL.md playbooks, not plugins

One offline skills library for a local agent and a coding assistant, and the trust rules around it

One of my scheduled AI checks got slower every week until it timed out. It was the only one of my cron tasks that asked a language model to do the work: 70 to 300 seconds per run, growing. The other tasks called a small script directly and finished in under 1 second each run.

That was April 2026. By October I had a library of agent skills that both my local AI agent and an AI coding assistant read, in the open SKILL.md format. This post is about what a skill should be (a procedure, not a program), how I load the same files into two different agents, and the security rules that come with “the agent reads instructions from a folder”.

Contents · 10 sections

TL;DR #

  • A skill is a written procedure plus the tools it may use. It should be data, not code.
  • Use the open Agent Skills (SKILL.md) format: YAML frontmatter with a name and a one-line description, then Markdown instructions. Any skills-aware agent can read it.
  • Keep three things apart: code skills (scripts), pipelines (fixed tool sequences) and playbooks (instructions for the model).
  • If a check can be a command, make it a command. Do not use an LLM for deterministic cron work.
  • The skills folder is part of your attack surface: root-owned, not group-writable, read-only to the agent, and changes go through review.

From scripts to playbooks #

My first agent had a plugin folder: drop a Python file into skills/, export a list of {name, description, parameters, function}, and the agent discovers it at startup. That is a convenient design and a dangerous one.

On 2 October 2026 a report job was ported into lsn-agent-v3, my local AI agent, as a new Python skill. Files in that folder were auto-loaded at the next restart as read-only, ungated tools: no confirmation, because “skills are read-only by default”. One of the new skills, a backup verifier, put a model-supplied backup name into a shell=True command. A second AI session reviewing the commit caught it. The fix accepted only a valid Kubernetes resource name, but the real lesson was structural: a skill that is code runs with the agent’s full rights, and a folder that loads code automatically is a remote code execution path for anything that can write to it.

On 8 October, an audit of the remaining Python skills found something less dramatic and just as bad: false all-clears. One skill reported 0 CrowdSec bans when there were 49,591; others reported Docker, SSH or kubectl as healthy when they were unavailable. A script that swallows its errors produces confident green text, and the model repeats it.

Three kinds of skill #

The agent now has three kinds, all exposed to the model as skill_<name> tools:

Kind What it is Who runs the steps Typical use
Code skill a Python file the skill’s code complex logic, parsing
Pipeline a fixed list of templated tool calls the agent, each step through the permission gate repeatable reports
Playbook instructions plus a short tool list the model, guided by the text diagnosis, procedures

Pipelines and playbooks are declarative and live in the agent’s own store. Every pipeline step goes through the same permission gate as any other tool call, shell arguments are quoted automatically, and pipelines cannot call privileged tools, other skills or skill management. Code skills can be drafted by the model, but drafts are only linted, never run. Installing one is a critical action: approval plus a second-factor code, bound to the SHA-256 of the reviewed draft, written root-owned.

The third kind is where SKILL.md comes in.

The SKILL.md format #

On 11 October I adopted the open Agent Skills standard and created one git repository as the single offline source of truth. Each skill is a folder:

skills/<name>/
  SKILL.md        # required: frontmatter + instructions
  references/     # optional docs, loaded only when the body links them
  scripts/        # optional helpers
  assets/         # optional templates

A real (lightly trimmed) example:

---
name: kubernetes-triage
description: Diagnose what is wrong in one Kubernetes namespace (unhealthy pods, Warning
  events, crash loops, pending or evicted pods) and summarise the likely cause without
  changing anything. Use for "what is broken in <namespace>" or "why is <app> down".
x-agent-tools: [k8s_pod_status, k8s_summary, host_service_status]
x-agent-parameters:
  type: object
  properties:
    namespace: {type: string, description: the namespace to triage}
  required: [namespace]
---

# Kubernetes namespace triage

A **read-only** procedure. Never patch, delete, scale or restart here.

1. Pod health: flag every pod not Running/Completed, with phase, restarts and reason.
2. Recent Warning events, newest first, grouped by scheduling, image, probes, volumes.
3. For the worst pod, read the last ~50 log lines (and the previous container's logs
   if it is crash-looping).
4. Classify the cause: image, config/secret, resources, dependency, probe, storage.
5. Report the unhealthy pods, the single most likely cause with the evidence line,
   and the smallest safe next action.

The format works by progressive disclosure. At startup an agent reads only each skill’s name and description. When a task matches, it loads the body. It reads references/ only if the body asks. So the description is the trigger: it must say what the skill does and when to use it, in one line.

My extensions are namespaced with x- so other agents ignore them: a tool list (at most 15 tools, read-only preferred), a JSON-Schema for parameters (at most 12 properties) and a category. A playbook can never exceed what its tools allow, because every call still goes through the gate.

The library holds 12 skills today: Kubernetes triage, pod log scans, backup health, cluster self-heal, credential lookup, access and permissions, the shared memory, fixing an MCP tool, blog writing, and three for the Blender studio.

Validation is the CI gate #

A small validator, standard library only, checks every SKILL.md:

NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
MAX_NAME, MAX_DESC, MAX_TOOLS, MAX_PARAMS = 64, 1024, 15, 12
# - name: 1-64 chars, lowercase and single hyphens, EQUAL to the folder name
# - description: 1-1024 chars, single line, non-empty
# - body: non-empty
# - links to references/ and scripts/ must exist

It uses a deliberately small YAML subset (scalars, lists, one level of nesting) so it runs on the host, in a container and in CI without PyYAML. Exit code 1 on any error.

One mismatch to know about: the standard uses hyphens (kubernetes-triage), but my agent’s tool names allow only [a-z0-9_]. The loader maps hyphens to underscores for the tool (skill_kubernetes_triage) and leaves the folder and file in the standard form.

Trust: who may write a skill #

A playbook is instructions the model will follow with real tools. That makes the skills folder as sensitive as the code.

  • The deployed library is root-owned and writable only by root, never group- or world-writable. The agent’s loader refuses any skill file that fails that check, the same rule as for code skills. My own group-writable git checkout was refused, correctly.
  • The agent reads the library read-only, so it cannot edit its own instructions.
  • Agent-authored skills have two tiers. Quick ones go to the agent’s local store. Portable ones are written as a draft that is not loaded, reviewed in the web editor, then promoted with a critical action. A root helper writes the file into the repository and commits and pushes as my user, so the root helper never holds git credentials, then syncs the read-only deploy folder and reloads.

The same question came up with a shared memory server that has a “shared skills” area. Its version at the time had no server-side write control there: any user key could write a skill that every agent would load, a clean prompt-injection path. The controls are on the client side: no write tool reaches that area, and a nightly check flags any skill not on an approved list.

Writing good playbooks for small models #

Lessons from my cron and studio runs:

  • Name the exact tools and forbid delegation for simple status checks. A cron prompt that mentioned file paths made the small model send helpers to browse the repository, and the run timed out.
  • Define the verdict format. My alerting looked for words like “warn” or “error”, so a healthy report that mentioned them raised an alert. Prompts now require faults as lines starting WARN:, CRIT: or FAIL, and a last line ALL_CLEAR when healthy.
  • Attach the playbook automatically on the first use of its tools. Small models often call a tool without loading the procedure that explains it.
  • Keep output bounded. A security check that read about 76 MB of vulnerability reports through a skill capped at 4 MiB returned nothing useful until it switched to summaries.

Checklist #

  • Write procedures as SKILL.md playbooks; reserve code for what needs code.
  • Make the description a trigger: what and when, one line.
  • Make deterministic cron checks scripts or pipelines, not prompts.
  • Validate every skill in CI: name equals folder, description length, tool and parameter limits.
  • Deploy the library root-owned, not group-writable, read-only to the agent.
  • Route agent-authored skills through draft, review, gated promote, and commit them to git.
  • Make skills report errors as errors, never as healthy.

Frequently asked questions #

What is a SKILL.md file? #

It is the file format of the open Agent Skills standard: a Markdown file with YAML frontmatter that names a skill and describes when to use it, followed by the instructions an agent follows once the skill is activated. Optional folders hold references, scripts and assets.

Should agent skills be code or instructions? #

Mostly instructions. A playbook can only use the tools it lists, and each call goes through the agent’s normal permission checks. A code skill runs with the agent’s full rights, so it needs the same review as any other code you deploy.

Can the same skill work in different AI agents? #

Yes, if you stick to the standard fields. My local agent and an AI coding assistant read the same files; my agent-specific fields use an x- prefix that other agents ignore.

Why not let the model run cron checks? #

Because model latency is unpredictable and grows with context and load. My one prompt-based cron task took 70 to 300 seconds and finally timed out, while direct commands took under a second.

The lesson #

The useful unit of agent knowledge is not a plugin, it is a procedure: what to check, in which order, with which tools, and what a good answer looks like. Write it as a plain file in an open format, keep it in git, validate it like code and protect it like code. Then give the deterministic parts to scripts, and let the model do what only the model can do.