Skip to content
Blog

Skills, rules and hooks for coding agents

Some instructions a coding agent can skip now and then, some it has to see every time, and some a machine should enforce for it.

 · 4 min read

On this page

Our agents kept skipping our type-safety standards. We'd written them up as a skill, an instruction the agent pulls in when it decides a task needs it, and most of the time that worked. Sometimes the task was worded differently, the context was already crowded or the request came in sideways, and the skill never loaded. The agent would then write loose types that switch off the type checker, in a codebase that forbids them, and nothing flagged it.

A skill that loads most of the time is fine for a workflow playbook. For a type-safety standard, every time it doesn't load is a time bad code can ship without anyone noticing. After that we sorted every instruction by asking what happens when the agent skips it.

Three places an instruction can live

The first is a skill. In Claude Code, the agent always sees a skill's short description, but the full content only loads when the skill is invoked. That keeps the agent's context (everything it holds in mind for a task) small. If the agent doesn't think a skill is relevant, the content never shows up. Our skills cover workflows like filing a ticket, setting up branches and opening a merge request. Our broader design principles, such as clear naming and keeping things simple, live there too.

The second is an always-on rule, loaded into every session whatever the task. Our type-safety rule lives here now, along with the order that work moves through from ticket to merge. The agent never misses these, but they're still words in a prompt, so following them is still the model's call.

The third is a hook, a small script Claude Code runs at fixed points whether the agent wants it to or not. The docs describe this as deterministic control, where an action always happens instead of relying on the model to choose it. Ours stop an agent from calling work done until the automated checks pass, and refuse a push until a human developer has explicitly approved that exact code.

Software has always had a split between what the compiler checks every time and what a team follows by convention. A skill is like a convention, which usually gets followed. A hook is like the compiler. An always-on rule sits in between, always in front of the agent with nothing forcing the agent to obey it. So when I call something probabilistic or deterministic, I mean "usually happens" or "always happens", and only hooks and automated checks get the second label.

How we decide where something goes

If skipping an instruction produces a bug, it can't be a skill. Standards, security constraints and forbidden patterns go in an always-on rule at the very least, and if a machine can check it, a machine should. Our type-safety rule is written so it can become a lint error once a codebase is ready for that. The rule reminds the agent, and the check makes sure.

If skipping an instruction only costs some rework, it's a skill. A workflow skill that doesn't load means the agent does a step the long way or someone corrects it. A missed note about naming or readability is in the same bucket, because a later review picks it up and it doesn't break anything.

The cost of always-on

Rules cost context on every request, and skills only cost it when they load. So there's constant pressure to move things out of the rules, and I think that's the wrong move for anything that affects correctness. We keep the always-on set deliberately small, small enough that adding one that really belongs there is never a hard call. When the rules start to bloat, we tighten the wording. We don't demote a standard to a skill to make room.