The other day I saw someone in a group asking “how do I create a skill for Claude Code?”, and the answers were things like “use this template”, “install this package”, “follow this 20-step guide”. People talk about skills as if they were something magical, an advanced feature that demands special knowledge.

It doesn’t. And to convince you of that, I went into the source code of OpenCode to see what actually happens under the hood.

But first, I need to explain how LLMs really work. Without that, skills make no sense.

LLMs do nothing

An LLM is a function. Text goes in, text comes out. It doesn’t access the internet, doesn’t read files, doesn’t execute code. It predicts the next token based on what it received.

That is literally all.

When you send a message to Claude or GPT and it “reads a file” or “searches the web”, it’s not the model doing that. It’s the system around it. The model only generates text. The one that acts is the program orchestrating the conversation.

Tools: giving the model hands

For an LLM to interact with the real world, we use tools (or function calling). The flow is:

  1. You send a message along with a list of available tools - each with a name, a description, and parameters
  2. The model analyzes the message and decides whether it needs to use a tool
  3. If so, it responds asking for the execution: { "tool": "read_file", "args": { "path": "src/main.ts" } }
  4. The host system (not the model) executes the tool and returns the result
  5. The model receives the result and continues generating the response

The model never executes anything. It only asks for execution. The agent is the one that runs it.

Agents: the loop that connects everything

An agent is that loop. Simplified to the extreme:

while not done:
    response = llm.generate(messages, tools)
    if response has tool_call:
        result = execute(tool_call)
        messages.append(result)
    else:
        return response

The agent keeps the history, injects the tool definitions, executes the calls, and feeds the model with the results. OpenCode does exactly that in packages/opencode/src/session/prompt.ts.

If you want to truly understand agents, study this loop. Everything else is implementation detail.

How OpenCode registers tools

In OpenCode, every tool implements a Tool.Info interface defined in packages/opencode/src/tool/tool.ts:

export interface Info<Parameters, M> {
  id: string
  init: (ctx?) => Promise<{
    description: string
    parameters: Parameters
    execute(args, ctx): Promise<{ title, metadata, output }>
  }>
}

Every tool has an id, a description, the parameters it accepts, and an execute function. The ToolRegistry in packages/opencode/src/tool/registry.ts gathers all of them - built-in, custom, and from plugins - and hands them to the model on every interaction.

The built-in tools are registered like this:

return [
  ReadTool, GlobTool, GrepTool, EditTool, WriteTool,
  BashTool, TaskTool, WebFetchTool, SkillTool,
  // ...
]

Notice that SkillTool in the middle. Remember that name.

Now we can talk: what is a skill?

A skill in OpenCode is a markdown file named SKILL.md with a YAML frontmatter:

---
name: agents-sdk
description: Build AI agents on Cloudflare Workers using the Agents SDK
---

# Cloudflare Agents SDK

Here go detailed instructions, code examples,
references, best practices...

That’s it. A .md file with a name and a description.

The code that discovers these files lives in packages/opencode/src/skill/skill.ts. It scans for SKILL.md in global directories (~/.claude/skills/, ~/.agents/skills/), project directories (.opencode/skills/), custom paths, and even remote URLs.

The trick: SkillTool is just a tool

The SkillTool in packages/opencode/src/tool/skill.ts is a tool like any other. In its init() function, it:

  1. Scans all the available SKILL.md files
  2. Builds its own description listing what it found:
<available_skills>
  <skill>
    <name>agents-sdk</name>
    <description>Build AI agents on Cloudflare Workers...</description>
  </skill>
</available_skills>

That description goes to the model along with the other tools. When the model decides it needs a skill, it makes a regular tool call:

{ "tool": "skill", "args": { "name": "agents-sdk" } }

The SkillTool receives that call, reads the corresponding SKILL.md, and returns its content. The model uses those instructions to continue the work.

Read that again. It’s the same flow as any tool. The model asks, the system reads a file, the content goes back into the context.

Think of it this way: if the model can call read_file to read a code file, why can’t it call a tool to read an instructions file? That’s exactly what SkillTool does. The only difference is the convention - a standardized place to put reusable instructions that the model pulls on demand.

How to create a skill, for real

  1. Create a folder inside .opencode/skills/ (or .claude/skills/, depending on the agent)
  2. Put a SKILL.md inside it with name and description in the frontmatter
  3. Write the instructions in markdown

Done. There is no step 4.

The model will see your skill’s short description in the list of available tools. If it finds it relevant to what it’s doing, it will call the tool and read the full content. If it doesn’t, it ignores it. You don’t force anything.

The full content only enters the context when the model asks for it. The descriptions are lightweight; the heavy markdown stays out until it’s needed.

One tip on what to put in a skill: Sean Goedecke wrote about generating skills after solving the problem, not before. The idea is that the LLM writes better skills after it has already iterated on the solution, because then it distills what it learned. It makes sense - you don’t write good documentation before understanding the problem.

Conclusion

A skill is a markdown file that a tool reads when the model asks for it. The same tool calling that lets the model read files or run commands is what lets it load a skill. There is no framework, no runtime, no magic.

If you know how to write markdown, you know how to create skills.

Thanks for reading!