Skill Foundations · Lesson 4

Lesson 4 — Building a multi-skill plugin

30 min
What you keep A git-hygiene plugin that bundles your commit-conventions skill into a versioned, installable library.

Two ways through this lesson: read it on this page, or run it hands-on in your coding agent. To do it in your agent:

1 · Install once per course npx tessl install tessl-academy/skill-foundations Run this once, in a fresh project directory (for example a new foundations folder — Tessl won't initialize in your home directory). It installs the skills your agent uses to guide you through the lessons interactively.
2 · Start the lesson — ask your agent “guide me through building a multi-skill plugin” Open your coding agent (Claude Code, Cursor, Codex, or Tessl Agent) in that directory and ask it the prompt above. The installed skill picks it up and walks you through this lesson step by step. Prefer a command? Launch it directly with tessl launch skill --agent claude-code -i 04-building-a-multi-skill-plugin (swap claude-code for cursor, codex, or tessl-agent).

So far you've worked with one skill at a time: you installed commit-conventions, then you wrote your own. But a single skill rarely travels alone. The moment you have a second skill that serves the same people for the same kind of work, you want to ship them together: install once, version once, update once. That bundle is a plugin, and this lesson is about building one that holds a small, coherent library of skills rather than a pile of unrelated ones.

What you'll build

You'll turn your commit-conventions skill into the first member of a git-hygiene plugin, a library for clean git workflows that could also hold pr-description and release-notes. By the end you'll have:

  • An empty plugin scaffolded with the Tessl CLI, versioned and ready to fill.
  • Your commit-conventions skill added to it and the whole plugin linting clean.
  • A working rule for deciding what belongs in the plugin, and how to keep its skills from stepping on each other.

A plugin is the unit you ship

You've already been using plugins without dwelling on them: every skill you installed arrived inside one. Worth making the distinction explicit, because it shapes everything below: the unit of authorship is the skill; the unit of distribution is the plugin. A plugin is what gets versioned, published, and installed, and it can bundle:

  • One skill: the simple case, one SKILL.md, one purpose, one version.
  • Multiple related skills: a library of sibling skills that share an audience and a release cadence.
  • Skills, rules, and docs: a richer context bundle for a problem space that needs more than skills alone.

Plugins are agent-agnostic: the same my-workspace/git-hygiene@1.0.0 installs into Claude Code, Cursor, Codex, or Gemini in whichever format each expects. You author once; Tessl handles the per-agent sync on install.

What makes skills belong in the same plugin

Before bundling anything, the judgment call: a plugin is not "everything one team has ever written." It's a coherent group of skills that share:

  • An audience: the same people install all of them.
  • A problem space: they help with adjacent jobs, not unrelated ones.
  • A release rhythm: when one needs updating, the others usually do too.

The git-hygiene family is the clean example: commit-conventions, pr-description, release-notes. Each takes git context and produces text a human needs to read. Same audience, same problem space, same likely update cadence. One plugin.

Heuristic. If skill B's release cycle would always lag skill A's by weeks, they probably belong in separate plugins. Versioning skills together means versioning them together: every change to one bumps the version for all. Bundling is a commitment, not a convenience.

Before you start

This builds directly on the skill you authored in Lesson 2, so you already have what you need: the Tessl CLI, a workspace to publish into (the namespace your plugins belong to, where your personal one is fine), and your commit-conventions skill on disk. A quick reminder of the shape of a skill, since you'll be moving one around: a skill is a SKILL.md whose description frontmatter decides when the agent loads it and whose body tells it how. Hold onto that description idea; it comes back at the end when the library grows.

What a plugin looks like on disk

A plugin is mostly convention, so a quick look at one tells you the whole structure. It's a manifest plus a directory of skills:

git-hygiene/
├── .tessl-plugin/
│   └── plugin.json        # versioned package metadata
└── skills/
    └── commit-conventions/
        ├── SKILL.md       # the contract: when to use, how to do it
        └── examples/

The .tessl-plugin/plugin.json carries the package metadata and points at the skills directory:

{
  "name": "my-workspace/git-hygiene",
  "version": "0.1.0",
  "description": "Skills for clean git workflows: commits, PRs, release notes",
  "private": true,
  "skills": "skills"
}

The field that makes multi-skill plugins painless is skills. It's a path, not a list: Tessl scans that directory for any subdirectory containing a SKILL.md and discovers each skill by convention. So you never hand-maintain a roster of skills; you drop a skill directory in and it's part of the plugin. (private: true keeps it to your workspace; a rules field works the same way for always-loaded context.)

Step-by-step

1. Scaffold the plugin

For a library that bundles more than one skill, start with an empty plugin rather than scaffolding through tessl skill new (which assumes a single-skill plugin):

tessl plugin new \
  --name my-workspace/git-hygiene \
  --summary "Skills for clean git workflows: commits, PRs, release notes" \
  --path ./plugins/git-hygiene \
  --workspace my-workspace

You'll get a plugins/git-hygiene/ with a .tessl-plugin/plugin.json and an empty skills/ directory. The version starts at 0.1.0, semver in the form MAJOR.MINOR.PATCH. Keep it in the 0.x range while you're iterating; bump to 1.0.0 when the shape is stable enough for other teams to depend on it.

2. Add your skill to it

Copy the commit-conventions skill you built in Lesson 2 into the plugin's skills/ directory:

cp -r ./skills/commit-conventions ./plugins/git-hygiene/skills/commit-conventions

That's all the wiring it needs, with no manifest edit. Because "skills": "skills" tells Tessl to scan the folder, the skill is part of the plugin the moment its directory lands there. Adding pr-description and release-notes later is the same move: copy the directory in, done.

Lint the whole plugin after each addition to catch structural mistakes early:

tessl plugin lint ./plugins/git-hygiene

Plugin lint is skill lint applied across every skill in the manifest, plus checks that the plugin-level fields (name, version, description) are present and the skills path resolves to real skill directories. Fix anything it flags and re-run until it's clean.

The thing that breaks libraries: overlapping descriptions

A library introduces a problem a single skill never has. When two skills' descriptions overlap, the agent gets confused about which one to load, and that overlap is the most common reason a library underperforms in practice.

Each description should name a situation specific enough that no other skill in the same workspace could plausibly match it:

  • commit-conventions: "the user wants a commit message"
  • pr-description: "the user is opening a PR and wants a description"
  • release-notes: "the user is preparing a release and wants notes between tags"

Three skills, three distinct user situations, no ambiguity about which fires when. A quick diagnostic: if you can read two of your descriptions and imagine the same user prompt matching both, they overlap. Tighten the narrower one, usually by naming the trigger phrase the user would actually say rather than the general topic, until the overlap is gone.

The mental model. A library's quality lives in its descriptions. The skills can each be excellent on their own, but if the agent can't tell them apart, the library is worse than the sum of its parts.

What you keep

You walk away with a git-hygiene plugin: one skill in it today, room for its siblings tomorrow, and, more durably, the judgment for when bundling is right (shared audience, problem space, cadence) and the discipline that keeps a library working (descriptions sharp enough that each skill fires on exactly one situation). Next, you'll take this plugin the last step: publishing it so your whole team can install it.

Lesson 4 complete ✓