L2 · 08 · Writing Great Skills & Custom Agents
Level 1 showed what skills and custom agents are. This page is about making them good: skills that trigger at the right moment and produce consistent results, and agent roles that divide work cleanly. It ends with how to test and share them across a team.
By the end of this page you can
- Write skill descriptions that trigger reliably, and avoid false triggers.
- Structure a skill with progressive disclosure: SKILL.md, references and scripts.
- Test skills with a trigger set and an outcome check.
- Design a small team of custom agents with clear roles, tools and hand-offs.
- Version, share and maintain customisations like code.
What makes a skill great
| Quality | Meaning | Test |
|---|---|---|
| Triggers correctly | Loads when relevant, stays out otherwise | Trigger set (below) |
| Deterministic where possible | Scripts do exact work; prose handles judgement | Same input → same output |
| Lean | SKILL.md is short. Detail lives in references loaded on demand. | SKILL.md under ~150 lines |
| Verifiable | Ends with a check the agent must run | Clear "done" criteria |
| Owned | Someone maintains it; it has a version | Metadata + CODEOWNERS |
The description is the trigger
At start-up the agent sees only each skill's name and description. It decides whether to load a skill by
matching the user's request against the description. Write it like a search snippet aimed at the model:
Weak
description: Helps with tests.
Too vague. It triggers on any test question, including Python, and also misses "add a suite for OrderService".
Strong
description: Create or extend MUnit test suites for Scala 3 code in this repo (FunSuite / CatsEffectSuite), following our naming, table-driven and fixture conventions. Use when asked to add, write or fix Scala tests, a test suite, or test coverage for a class. Not for Python or integration tests.
- What it does plus when to use it, with the verbs users actually say (add, write, fix, migrate, review).
- Scope words: language, framework, module, file types.
- Negative scope ("Not for…") to prevent false triggers between similar skills.
- Keep it under the format's limit (the open spec allows up to 1,024 characters). Precise beats long.
Anatomy of a production skill
.agents/skills/scala-munit-suite/
├── SKILL.md # short: when, steps, done-criteria
├── references/
│ ├── conventions.md # naming, fixtures, table-driven style (loaded on demand)
│ └── cats-effect-testing.md # TestControl, IO assertions (loaded only for IO code)
├── assets/
│ └── SuiteTemplate.scala # starting template
└── scripts/
└── check_suite.py # deterministic checks the agent runs at the end
--- name: scala-munit-suite description: Create or extend MUnit test suites for Scala 3 code in this repo (FunSuite / CatsEffectSuite), following our naming, table-driven and fixture conventions. Use when asked to add, write or fix Scala tests, a test suite, or test coverage for a class. Not for Python or integration tests. metadata: owner: store-platform-team version: "1.3" --- # Scala MUnit suite ## Steps 1. Find the class under test and its existing suite (`<ClassName>Suite.scala` in the mirrored `src/test/scala` path). Extend it if it exists. 2. Start from `assets/SuiteTemplate.scala`. Read `references/conventions.md` before writing. 3. If the code returns `IO`, use `munit.CatsEffectSuite` and read `references/cats-effect-testing.md`. 4. Cover: happy path, each error branch, boundaries, empty inputs. Prefer one table-driven test over many copy-pasted ones. 5. Never use `Thread.sleep` or real clocks. Never call external services. ## Done when - `python .agents/skills/scala-munit-suite/scripts/check_suite.py <suite file>` prints OK - `sbt "<module>/testOnly *<ClassName>Suite"` passes - You report which behaviours are covered and which are deliberately not.
"""Fail fast on convention violations the model tends to forget."""
import re
import sys
from pathlib import Path
RULES = [
(r"Thread\.sleep", "use TestControl / IO.sleep instead of Thread.sleep"),
(r"\bprintln\(", "no println in tests; use assertions or clues"),
(r"\.ignore\b|@Ignore", "do not ignore tests"),
(r"System\.currentTimeMillis|Instant\.now\(\)", "inject a Clock; no real time in tests"),
]
def check(path: Path) -> list[str]:
src = path.read_text(encoding="utf-8")
problems = [f"{path.name}: {msg}" for pattern, msg in RULES if re.search(pattern, src)]
if not path.stem.endswith("Suite"):
problems.append(f"{path.name}: file name must end with 'Suite'")
if not re.search(r"extends\s+munit\.(FunSuite|CatsEffectSuite|ScalaCheckSuite)", src):
problems.append(f"{path.name}: suite must extend an munit suite")
return problems
if __name__ == "__main__":
issues = [p for arg in sys.argv[1:] for p in check(Path(arg))]
print("\n".join(issues) if issues else "OK")
sys.exit(1 if issues else 0)
Testing skills
1. Trigger set
Write 10–20 prompts: some that should load the skill and some that should not (near misses). Run each in a fresh session and check the references/trace for whether the skill loaded.
| Prompt | Should trigger? |
|---|---|
"Add tests for PickupReminderJob" | Yes |
| "Our coverage for the returns module is low, fix that" | Yes |
"Why is OrderServiceSuite flaky?" | Yes |
| "Write pytest tests for the pricing script" | No (Python) |
| "Add an integration test against Postgres" | No (integration) |
| "Explain what MUnit is" | Probably no (question, not a task) |
2. Outcome check
For 3–5 representative tasks, check the result: does the script pass, do the tests compile and pass, are conventions followed? Re-run the trigger set and outcome checks whenever you edit the skill, or when the team switches models or tools.
Designing custom agents
Custom agents are roles: a focused instruction set plus a limited toolset (and sometimes a preferred model). Splitting roles improves quality because each agent has a narrow job and a clean context.
---
name: planner
description: Produces an implementation plan from a spec. Read-only; never edits code.
tools: ['search', 'read', 'fetch']
handoffs:
- label: Write the tests
agent: test-writer
prompt: Write the failing tests for the approved plan in PLAN.md.
---
You are a senior Scala engineer planning work for this repository.
1. Read the spec the user points to, AGENTS.md, and the relevant code.
2. Write PLAN.md: numbered steps (files, change, why), tests to write first, risks, open questions.
3. Keep steps small enough to be one commit each.
Do not write production code. Ask questions if the spec is ambiguous.
| Design rule | Why |
|---|---|
| Least privilege tools per role | A reviewer that can't edit can't "fix" by deleting tests. A planner can't wander into code. |
| Artefact hand-offs (PLAN.md, tests, diff) | Each agent starts fresh with only what it needs, which means less context rot. |
| Right model per role | A strong reasoning model for planning and review. A fast model for mechanical implementation. |
| Few roles | 3–5 roles cover most work. Dozens become hard to choose between and maintain. |
Sharing and maintaining
- Commit to the repo (
.agents/skills,.github/agents,AGENTS.md) so everyone gets the same setup with the code. - Personal vs team: personal preferences live in your user-level folder. Team conventions live in the repo.
- Review like code: PRs for changes, a CODEOWNERS entry, and a version bump in
metadata. - Plugins bundle skills + agents + hooks + MCP config for distribution across many repos.
- Security review: skills can contain scripts, and agents may run them. Treat third-party skills like third-party code.
- Prune quarterly: delete skills nobody triggers, and merge overlapping ones.
Anti-patterns
- The mega-skill: one skill for "everything about our backend". Split by task.
- Duplicating AGENTS.md inside skills. Reference it instead.
- Overlapping descriptions ("tests" in five skills). The agent picks randomly.
- Untested edits: a one-word description change can stop a skill triggering.
- Secrets in skills or agent files. Never. Use environment variables and your tool's secret handling.
- Build the
scala-munit-suiteskill (or a Python equivalent) with the check script. - Write a 10-prompt trigger set and measure the hit rate. Improve the description until it is at least 9/10.
- Create
plannerandreviewercustom agents with restricted tools. Run spec → plan → implement → review on a small task.
Key takeaways
- The description is the trigger. Include what, when, scope and negative scope.
- Keep SKILL.md lean. Load detail from references, and enforce rules with scripts.
- Test skills with trigger sets and outcome checks, and re-test on every change.
- Custom agents = narrow roles + least-privilege tools + artefact hand-offs.
- Version, review, share and prune customisations like code.