All documentation
Explanation Verified 2026-09-06

Skills

A skill is a markdown file that gives an agent one scoped capability. What it is, how it differs from a tool and an agent, and where it lives.

Reading this as an agent? The same page in raw markdown: /docs/skills.md

A skill is a markdown file with YAML frontmatter. It holds instructions for one job, it is stored per workspace, and it is run by name. This page explains what a skill is and where it sits between a tool and an agent. Everything below was read out of the shipped code on 2026-09-06; the limits section names what is declared but not wired.

Skill, tool, agent

These are the three rungs of the autonomy ladder, and each compiles to a different receiver.

RungReceiverWhat it is
tooltools:composio, or a named receiveran integration; no judgment
skillskill:runa capability, scoped by its own file
agentagent:runan actor with open judgment

The clearest mechanical difference is in the workflow run guard. In one.ie/web/src/lib/step-ceiling.ts, stepAllowed() checks a tool step’s receiver name against the run’s enabledToolIds list and an agent step’s actorId against enabledAgentIds. skill:run is exempt from both, because a skill name is a different namespace from a receiver name. It is exempt from the rung ceiling too: stepWithinCeiling() reads the receiver’s explicit roleAction, and skill:run declares none, so the comparison passes. Inside a workflow run, the only thing standing between a step and a skill is authority over the workspace holding it.

A workflow step picks its rung from the shape of its config, in workflow-executor.ts’s stepBinding(): { receiver } dispatches verbatim, { composio } routes to tools:composio, { actorId } becomes agent:run, and { skill } becomes skill:run. Two more shapes sit between them — { entity } compiles to the record receiver for its dimension, { lifecycle } to entity:tag. A step naming none of the six is a no-op that traverses without side effects.

The file

A skill opens with --- and a YAML block. The parser (one.ie/web/src/lib/skill/parser.ts, mirrored in packages/cli/src/skill-parser.ts) is deliberately lenient: it reads scalars and - block lists, retries once with unquoted colons stripped, and emits diagnostics rather than throwing.

Fields the loader lifts into a Skill object: name, title, summary, description, price, accepts, trigger, applyTo, tags, when_to_use, inputSchema, outputSchema, version, license, compatibility, scripts, references, assets, evals.

Only a few fields change behaviour today. inputs and ssr are read by separate parsers off the raw frontmatter, not through the loader:

FieldRead by
nameaddressing — the string skill:run resolves
descriptionskills:list when queried by name, to label the skill
inputsskills:list when queried by name; name|Label|required lines
evalsskill eval and skill_eval, against POST /api/eval
ssrskill:run, which projects the answer over reference_sets
pricestamped into frontmatter on import

The rest is carried metadata. Missing pieces get filled in rather than refused: no name and the path stem becomes the name, no description and the first body paragraph becomes one, a triggers field folds into the description. Each substitution raises a diagnostic — no-frontmatter, missing-name, name-mismatch, name-too-long, description-from-body, triggers-merged, lenient-yaml — which oneie skill validate prints.

The repo’s shared pool shows the shape at scale: 152 skills at the top level of one.ie/ai/skills/, each a directory holding a SKILL.md, and 324 SKILL.md files counting the nested sub-pools (platforms/, strategies/, content/, seo/ and four others). 51 of those directories carry an evals/evals.json bank beside the SKILL.md.

Where a skill lives

A workspace’s catalog is an R2 prefix, {slug}/skills/. Three key shapes live there, and which one a skill has decides which readers see it.

{slug}/skills/{name}.md        flat — skill:save writes it
{slug}/skills/{name}/SKILL.md  dir  — skill import writes it
{slug}/skills/{name}/ref.json  ref  — sealed import, no body

getSkillOrRef() reads all three, in that order of preference: ref, then directory, then flat. getSkill() reads only the flat key. skill:run uses the first; agent:run uses the second. That asymmetry is named again below.

Running one

signal("skill:run", { skill: "copywriting", topic: "launch" }). Everything past skill is the skill’s input. The receiver resolves the workspace, loads the body from R2, and posts one bounded turn to the channels runtime with that body as the instructions. It returns { ok, text, skill }. Web never calls a model itself for this — a missing or unreachable channels runtime comes back as channels_not_configured, channels_<status> or channels_unreachable, a clean failure rather than a thrown run.

Authority is checked against the workspace whose catalog is read, not the caller’s own. runSkill takes the caller’s workspace as-is; a slug naming a different one goes through decide() for manage_workflows and comes back forbidden or unavailable. See Authority and access.

One structured field can come back beside the text. If the answer carries the eight-field spec with a real JSON array under Deliverables:, skill:run adds deliverables so a workflow step can fan out over it. Prose bullets add nothing, so a bad answer fails loudly instead of writing zero rows.

Sealed skills

A sealed skill is a ref.json in the importer’s catalog naming the publisher and the skill. The body never leaves the publisher’s prefix. On a run, skill:run follows the ref, loads the publisher’s body, optionally checks a D1 entitlement, and returns { ok, text, sealed: true }. The importer can invoke it and cannot read it.

Sealing is decided server-side, not by the caller: POST /api/skill/import looks up an active market_listings row for (publisher, name) and seals only when its visibility is sealed. The request must carry publisher for that lookup to happen at all.

Surfaces

WhereNames
Receiversskill:run, skill:save, skills:list
CLIoneie skill — 11 subcommands
MCPskill_run, skills_list, list_skills, discover_skill, skill_eval, unimport_skill
HTTP/api/skill/import, /api/skills/workspace, /api/skills/:name/fork, /api/skills/:name/enable, /api/eval

skill:run and skills:list carry surfaces: { mcp: true } in the receiver registry, so their MCP tools are derived from the contract rather than hand-written. The other four MCP tools are curated modules. See MCP server and CLI.

What does not work yet

  • An agent’s skills: frontmatter is not read at runtime. resolvePersona parses it and carries it onto the persona, and channels/src/personas.ts says so in the field’s own comment: nothing in channels reads persona.skills. The same holds for the skillResolver map form. The wired path is agent:run with an explicit skill argument, which folds the body into the instructions for that one turn.
  • agent:run loads only flat-key skills. It calls getSkill(), which reads {slug}/skills/{name}.md and nothing else. A skill that arrived through skill import lives at {name}/SKILL.md, so the load returns null and the agent runs with no skill body and no error.
  • skills:list queried by name is flat-key too. The unfiltered listing sees every key shape, but the name branch reads through getSkill(), so asking for an imported skill by name answers { skills: [] } — no description, no inputs.
  • oneie skill fork does not copy anything. It fetches the workspace list, refuses if the skill is sealed, and prints a success line. The copy is POST /api/skills/:name/fork, which the CLI never calls.
  • That fork route is flat-key too. It reads through getSkill(), so forking an imported skill returns 404.
  • oneie skill refresh is a stub. It always answers refreshed: 0 and ignores the refs passed to it.
  • oneie skill publish always fails, with agentskills.io API key required — set AGENTSKILLS_API_KEY.
  • price: does not bill a run. Import stamps it into the frontmatter and the import response echoes it. skill:run charges nothing, and skills:list does not return it; the only paywall on the run path is the D1 entitlement check on a sealed ref.
  • agent_skills is display state. POST /api/skills/:name/enable writes rows into that D1 table, and the only readers are the /u/[slug]/skills page, which uses them to show which agents a skill is wired to, and a fleet probe that counts them. No runtime consults the table when running a skill.

Next

  • Agents — the rung above a skill
  • Workflows — where a skill becomes one step
  • Receivers — the contracts behind skill:run
  • CLI — the oneie skill verbs