Skip to content

Package the add-a-feed procedure as a Skill

You’ve now walked OpenCode through onboarding a new feed source three times this week — sniff the format, write the adapter, register it, add the fixture test — and all three times you typed the same four-step narration with a different feed plugged in. That re-narrating is the whole waste. The steps are stable; only the feed changes. So you write the procedure down once, in a form OpenCode recognizes and runs itself, and you stop being the thing that remembers the routine.

That form is a Skill: a directory with a SKILL.md in it. The frontmatter tells OpenCode when the skill applies; the body tells it what to do. OpenCode keeps every skill’s name and description in view and exposes them through a built-in skill tool — when your request matches one, the agent calls that tool to pull the body in and follows it. You never paste the procedure again.

It’s worth pausing on why this beats the other homes the procedure could have. The four steps could sit in AGENTS.md — but that file loads in full into every session, and you onboard a feed maybe twice a week. A skill inverts the cost: the name and description stay in view for almost nothing, and the body loads only when a feed actually shows up. Four pieces of knowledge, three homes each — the ledger makes the case:

Four things you keep teaching the agent, all currently crammed into the rules file. Each needs a home: the rules file (loaded every session), a skill (a 25-token menu line every session, full body only when invoked), or the prompt (said when it comes up). Re-home them and watch the ledger.

  • cents, not floatsfact · matters most sessions · ~22 tok

    Money is integer cents — amountCents: number, never floats.

    right home

    Twenty-two tokens a session and the float bug is extinct. A fact the agent must hold while writing any code has to be in view before it starts — that’s the rules file.

  • release checklistprocedure · runs ~2×/week · ~1.2k tok

    The release ritual: bump the version, regenerate the changelog, tag, build, smoke-test — fifteen steps in a fixed order.

    idle rent

    Fifteen steps loaded into every session to be used twice a week. The other eight sessions pay 1.2k tokens for a procedure that never runs — bulk that competes with your real rules for attention.

  • migration recipeprocedure · runs ~2×/month · ~900 tok

    Schema migrations follow expand → backfill → contract, with a fixture check and a written rollback plan.

    idle rent

    Twenty sessions pay 900 tokens each for every one that migrates. The rarer the procedure, the worse a rules file suits it.

  • today’s flakeone-off · this week only · ~25 tok

    Today’s job: chase the flaky auth test in login.spec.ts.

    rent for a one-off

    True this week, noise forever after. Next month the agent still “knows” about a flake that’s long fixed.

window cost21.5k tok/weekyou re-teach0×/weekmisplaced3 of 4

The procedures are paying rent in every session for the few that use them. Move them behind a skill stub: the knowledge stays one invocation away, and the window cost collapses.

Token numbers are illustrative; the ratios are the point. Assumes 10 sessions a week. A rule pays its full size in every session; a skill pays a ~25-token stub every session and its body only when it fires; a prompt pays only when said — but you’re the one saying it.

A project skill lives at .opencode/skills/<name>/SKILL.md, walking up from your cwd to the git worktree root. Put it in the repo and it travels with feedmill for anyone who clones it. If you wanted the skill on every repo you touch — a personal habit, not a project one — the global path is ~/.config/opencode/skills/<name>/SKILL.md instead. We want this one in the repo, so:

> create the directory .opencode/skills/onboard-feed/
| Bash mkdir -p .opencode/skills/onboard-feed/ → approved

The directory name matters more than it looks. The name field in the frontmatter must match the directory that contains SKILL.mdonboard-feed here. OpenCode also constrains the name itself: 1–64 characters, lowercase alphanumeric with single hyphens between words, no leading or trailing hyphen and no --. So onboard-feed is legal; Onboard_Feed or onboard--feed would be rejected. Pick the directory name and the name field together and keep them identical.

Now the file. Two required frontmatter fields, then the body. Describe what you want to OpenCode in prose — it knows the procedure from the three times you’ve narrated it — and review what it writes:

> write .opencode/skills/onboard-feed/SKILL.md. It's the procedure we keep
doing by hand: onboarding a new feed source. Frontmatter with name and a
description that says when to use it. Body is the four steps — sniff the
format, write the parser adapter, register it, add a fixture test.
| Edit .opencode/skills/onboard-feed/SKILL.md
---
name: onboard-feed
description: >-
Onboard a new RSS/Atom/JSON feed source into feedmill. Use when the
user wants to add a feed, or when a new source needs a parser adapter
and the steps are: detect the format, map it to the item struct,
register the adapter, and add a fixture test under testdata/.
---
# Onboard a new feed source
Run these four steps in order for the feed URL the user gives you.
1. **Sniff the format.** Fetch a sample of the feed. Decide whether it's
RSS, Atom, or one of our JSON-feed shapes, and find where it puts the
id, title, published timestamp, and link.
2. **Write the adapter** in `parser/` that maps this feed's quirks onto
feedmill's internal item struct. Reuse an existing adapter as a
template; don't invent a new shape.
3. **Register the adapter** in the source-to-adapter map so the sync
server reaches for it when it sees this source.
4. **Add a fixture test.** Drop the sample in `testdata/`, assert the
adapter produces the expected items, and run `go test ./parser/`.
Apply this change?
Allow once Always allow Reject

Read the frontmatter before you approve. The name matches the directory. The description is doing the real work here — it’s the only thing OpenCode sees when it’s deciding whether to reach for this skill, because the body stays unloaded until the skill tool fires. A vague description like “feed stuff” means the agent never connects “add this feed” to this procedure. A good one names the trigger (“add a feed”, “new source needs a parser adapter”) and sketches the payoff, in the 1–1024 characters the field allows. Write it for the agent’s selection problem, not as a title.

Those two fields are all you must supply. OpenCode recognizes a small set of optional ones too — license, compatibility, and a metadata string map — and ignores anything else. There’s deliberately no disable-model-invocation or context: fork key the way you might reach for in another tool; OpenCode’s frontmatter is just these fields, and a skill folder authored for Claude Code loads here unchanged — OpenCode reads .claude/skills/ (and .agents/skills/) directly, and any extra keys those tools wrote get dropped rather than erroring.

Here’s the payoff. A new feed shows up — a podcast that publishes a JSON feed — and you say the thing you’d say to a teammate, with no mention of the procedure. OpenCode matches your request against the skill descriptions it’s holding, sees onboard-feed, and calls the skill tool to load the body:

> add this source to feedmill: https://example.com/pod/feed.json
| Skill onboard-feed
loaded: Onboard a new feed source (4 steps)
| Bash curl -s https://example.com/pod/feed.json | head -40 → approved
This is JSON Feed 1.1 — items[].date_published is RFC 3339, items[].url
is the link, items[].id is the guid. I'll add a jsonfeed adapter, map
those onto the item struct, register it, and add a fixture test.
| Edit parser/jsonfeed.go
| Edit parser/registry.go
| Edit testdata/example_pod.json
| Edit parser/jsonfeed_test.go
| Bash go test ./parser/
ok feedmill/parser 0.197s
Done. Added a JSON Feed adapter for example.com/pod, registered it, and
pinned it with a fixture test.

Read that first beat. You never said “use the onboard-feed skill” — OpenCode chose it from the description and pulled the body in with the skill tool, the same way it’d reach for read or bash. From there it ran your four steps, in your order, with the fixture test you’d otherwise have to remember to ask for. The procedure that lived in your head, and that you’d narrated three times, now lives in the repo and runs on recognition.

The same approval model still applies inside the skill: the body is instructions, not a bypass. Each edit and each bash command the skill drives still goes through the permission you’ve set for the current agent — so if you’re on an agent with edit: "ask", you’ll approve the adapter edit exactly as you would any other. A skill makes the agent remember the routine; it doesn’t make it trusted to run it unwatched. That gating is its own lever, and the last lesson in this chapter is about turning it up and down.

One procedure, written once, now owned by the tool. The next time you add a feed you describe the source and read the diff — the four-step dance is OpenCode’s job. But you wrote this skill for OpenCode, in OpenCode’s path. The next lesson takes the more interesting claim at face value: that the same folder works when you’re driving Claude Code on this repo, with no rewrite — and looks at how far that portability stretches to other tools that read the same skill paths. Next: reuse skills across tools.