The PR that passes every test. And still needs changes.
The most frustrating AI-generated code isn't always broken code. Sometimes it's perfectly reasonable code that doesn't belong in your codebase.
I’m a staff engineer working mostly with Java, Spring Boot, and Azure, and more of the code I review now involves a coding agent. Take a new endpoint in a Spring Boot API: it builds, the tests are green, and a quick manual check looks fine. Then you open the pull request.
The 404 returns Spring's default error instead of your team's ApiError. The X-Trace-Id header is missing. A test uses getOrderById_returns404, even though every other test in that class follows should<Outcome>When<Condition>.
None of those choices is universally wrong. They're just wrong for this team. An agent can know Java and Spring Boot inside out and still have no reason to know the conventions people here learned through years of reviews, production incidents, and "we tried that once" conversations.
The problem isn't that the agent can't write code. It's that it doesn't know how we write code.
Every new session, the same conversation.
That knowledge has a name: tribal knowledge. It includes the unwritten rules that matter in your systems but not necessarily anywhere else. Which template a pull request needs. Why a service must deploy after another. The tiny header rule everyone eventually learns the hard way.
When a teammate learns one of those rules, they usually remember it. A coding agent can correct the code in the current session, but that correction won't reliably show up in a new one unless the guidance is saved somewhere the agent can find it.
The agent fixes it. The review is done.
You just taught the same lesson twice.
- Errors MUST use ApiError (code, message, traceId) from the shared @RestControllerAdvice.
Saved as guidance the agent can reuse on matching tasks.
That little shift—from a review comment to a file in the repo—is the idea behind a skills library. It doesn't magically teach the model your entire organization. It gives the agent a reusable, task-specific playbook where one would otherwise be missing.
So what's actually in a skill?
A skill is surprisingly boring, in the best way. It's a folder with a SKILL.md file. That file has two required YAML frontmatter fields—name and description—followed by regular Markdown instructions. You can add scripts, references, and templates when you need them. That's the core of the open Agent Skills specification.
Here's the start of the skill from the synthetic Spring Boot Orders API I built for the talk. The details are intentional: what the skill covers, which conventions matter, and which requests shouldn't use it.
---
name: spring-boot-api
description: >-
Add or change REST endpoints in the Orders API
(Spring Boot, package dev.skillslibrary.orders)
the way this team reviews them - ApiError error
bodies with codes, X-Trace-Id handling, thin
controllers over services, record DTOs,
should<Outcome>When<Condition> tests with AssertJ.
Use when adding an endpoint, an error case, or
controller tests in this repo. Not for build,
dependency, or database-schema work.
---
## Hard rules (MUST: a reviewer blocks the PR)
- Errors MUST be `ApiError` (`code`, `message`, `traceId`)
from the one `@RestControllerAdvice`.
- Controllers MUST NOT read or set `X-Trace-Id`.
`TraceIdFilter` owns it.
- Controllers MUST NOT import from `..domain..`.
Entities never leave the service.
Excerpt from the full skill in the demo repo ↗. The description is wrapped here for reading; the rules are quoted as written.
The description deserves more attention than its size suggests. It's a discovery hint. Supported clients can expose skill names and descriptions first, then load the body when a task calls for it. A vague description like “helps with APIs” makes it harder to pick the right skill—and easier to pick it when it doesn't belong.
First, the name and description.
The agent sees enough metadata to recognize that this skill may be relevant. It hasn't needed the full playbook yet.
Then, the SKILL.md instructions.
Once the skill is invoked, the agent gets the procedure: conventions, steps, examples, checks, and explicit “do not use” boundaries.
Finally, only the extra files it needs.
A referenced example, a template, or a script is brought in when the work calls for it. A small skill needn't drag an entire wiki into context.
This is the progressive-disclosure idea in the spec. Exact discovery and invocation behavior depend on the client. For example, Claude Code project skills can live under .claude/skills/<name>/SKILL.md. In the demo repo's skill, the frontmatter is about 111 tokens, the body about 458, and the two reference files about 530 and 452 (characters ÷ 4).
Don't just read about it. Give the agent a task.
The demo repository has a small Spring Boot Orders API, team conventions, the skill above, ArchUnit checks for layering, and a script that reports which conventions a change follows. The rules are deliberately more specific than the defaults you'd get from a generic code-generation prompt.
- Errors are
ApiErrorobjects withcode,message, andtraceId—not the default Spring error body. X-Trace-Idis echoed on responses and included in errors.- Controllers stay thin, entities don't escape the service layer, and DTOs are records.
- Controller tests use
@WebMvcTest, AssertJ, and the team's naming convention.
Add GET /api/orders/{id} with a 404 response
and controller tests.Try it once with the skill available and once without. Start from the same commit, use a fresh session each time, and compare the diffs and validation results. If you want to be thorough, repeat each condition: an agent doesn't always make the same choices twice.
A useful caveat: the existing Orders API already follows these conventions. An agent can read the surrounding code and copy the patterns even without a skill. That's not a flaw in the exercise; it's reality. If both versions succeed, the skill may not be adding much for that task.
Skills aren't a replacement for everything else.
One of the questions I hear most is “Isn't this just a giant CLAUDE.md?” Not quite. I think about the pieces by the job each one does, rather than treating them as competing products.
Project instructions
CLAUDE.md or AGENTS.md: shared facts and always-relevant rules. Usually loaded as project context.
Skills
Task-specific playbooks. Available for discovery; detailed instructions loaded when relevant.
MCP tools
Connections and actions: read the issue tracker, query a system, call an API. Tool behavior varies by client.
Hooks & checks
Enforcement at defined points, such as linting or tests. Don't rely on an agent remembering to verify everything.
Specialized agents
Delegate a piece of work to a focused worker or workflow when the platform supports it.
Reusable prompts
Questions or commands you invoke yourself. Useful when there's no need for automatic discovery.
My simplest mental model from the talk: MCP gives an agent access. A skill gives it a procedure. You often want both. But if a rule can be checked deterministically by a test, put it in a test too.
A good skill can still go very wrong.
I wish the story ended with “write a SKILL.md and enjoy perfect pull requests.” It doesn't. A skill can help an agent make better choices, but it can also make the agent more confident about a bad choice.
A 2026 research paper I referenced in the talk, Demystifying Agent Skills: Why They Work—Until They Don't, looked at agent runs with and without skills. The part I found most interesting wasn't simply whether a run passed. It was what the skill changed.
Share of classified skill-use cases where the skill stabilized execution steps.
Share of cases classified primarily as injecting missing knowledge.
The share of actually used skills that were the right skill when the pool grew from 5 to 100.
These percentages describe specific analyses and experimental conditions in the paper—not an expected improvement percentage for your own repo. Read the study ↗
The same study counted runs where the agent misapplied or ignored the guidance it was given: 0.8% without skills, 10.0% with them. In other words, skills often work because they give an agent a sequence it can follow. But that also raises the stakes when the sequence is meant for the wrong situation.
The right procedure, wrong target
In a synthetic Kubernetes exercise for the talk, a runbook intended for a stateless service was applied to a stateful database. In my local tests, the older models I tried removed a volume claim in 9 of 9 runs. Later models stopped in 16 of 16; adding explicit disqualifying conditions to the skill helped older ones stop too (6 of 6). Those are small, setup-specific runs—not a ranking of models—but the lesson stuck: say when the procedure must not be used.
The skill nobody opens
If discovery misses the right skill, the instructions might as well not exist. Test descriptions with real task phrasing, including lookalike requests where the skill should not activate.
The stale rule that still sounds authoritative
Two slightly different copies of a skill create two competing sources of truth. Treat skills like code: ownership, review, version control, and a way to update consumers.
Instructions that sound good but say nothing
“Handle failures appropriately” leaves the risky decisions to the agent. A useful procedure says what to inspect, what success looks like, and when to stop. For hard guarantees, add programmatic checks.
I don't want an agent that blindly follows a runbook. I want one that knows when the runbook stops applying.
Three things I'd try on Monday.
You don't need a platform team, a hundred-page strategy, or a library of fifty skills to find out whether this is useful. Start with one annoyance everyone recognizes.
Find the comment you keep writing.
Look through recent pull requests. Pick a correction that's specific to your codebase, not a generic style preference. Write the rule, why it exists, and a concrete example in a skill.
Test whether the skill gets picked.
Write three requests that should invoke it and two that shouldn't. Use the words teammates actually type. Tighten the description until discovery makes sense.
Compare against doing nothing.
Same task, same starting commit, fresh sessions—with and without the skill. Check the convention that motivated you to write it. If the skill doesn't improve the outcome, revise it or move on.
That's a much better starting point than trying to capture everything your team knows. Build the library from real friction, one procedure at a time.
The best skill might be the review comment you never have to write again.
I built the starter kit so there's somewhere concrete to begin: a template, a simple linter, and before-and-after runbook examples. The Spring Boot demo gives you a task to test. And the slides are there if you'd rather see the talk version.
One question to leave with: what does your team keep explaining that your repository still doesn't say?