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.

Level 250 minTrack 6 · CustomisationLast verified 2026-10

By the end of this page you can

What makes a skill great

QualityMeaningTest
Triggers correctlyLoads when relevant, stays out otherwiseTrigger set (below)
Deterministic where possibleScripts do exact work; prose handles judgementSame input → same output
LeanSKILL.md is short. Detail lives in references loaded on demand.SKILL.md under ~150 lines
VerifiableEnds with a check the agent must runClear "done" criteria
OwnedSomeone maintains it; it has a versionMetadata + 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.

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)
Scripts beat paragraphs Every rule you can check with a script is a rule the agent can't forget. Put "how to decide" in prose and "what must be true" in scripts. The agent runs them and fixes its own output.

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.

PromptShould 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.

Plannerread-only toolsstrong reasoning model→ PLAN.md Test-writeredit src/test only+ scala-munit-suite skill→ failing tests Implementeredit src/main, run sbtMetals MCP tools→ green build Reviewerread diff onlyfresh context→ findings fix findings
---
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 ruleWhy
Least privilege tools per roleA 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 roleA strong reasoning model for planning and review. A fast model for mechanical implementation.
Few roles3–5 roles cover most work. Dozens become hard to choose between and maintain.

Sharing and maintaining

Anti-patterns

Try it (45 min)
  1. Build the scala-munit-suite skill (or a Python equivalent) with the check script.
  2. Write a 10-prompt trigger set and measure the hit rate. Improve the description until it is at least 9/10.
  3. Create planner and reviewer custom agents with restricted tools. Run spec → plan → implement → review on a small task.

Key takeaways