Half a charter says what the agent may not do. The other half says what it can do at all, and that half is four primitives deep: a command, a skill, a sub-agent, an MCP server. They form one ladder, from a macro the model reads every word of to a process it can only call. This page is the mechanics of each rung, what makes a good one, and how to get a capability found once you have written it.
What a capability is, the four attributes every one of them carries, and the two words this page prices everything in. One section, because the ladder starts in Part II.
A charter is easy to read as a list of prohibitions. Most of a working charter is not. It is the set of things the agent can do at all, and teams install those without designing them, because installing feels free. It is not free. A capability has the same four attributes a constraint has, and one more that constraints never need.
Constraint Engineering ends its section on component families with a ten-second test. Remove the component and ask what changed. Run that test and keep only the answers that point one way.
| Remove it, and | You removed a | Where it is designed |
|---|---|---|
| the agent can do less | capability | this page |
| the agent can do more | constraint | Constraint Engineering |
| a number moved | setting | the charter's config block |
| you know less about the run | evidence | The Audit Trail |
| the agent forgot something | state | memory, and it needs an expiry |
The five answers are exclusive in practice and not in theory. A skill that says always run the formatter reads like a capability and behaves like a rule. Removing it lets the agent do more, so it is a constraint, and it belongs in the charter at the rung the charter chose. That single misfiling is the most common one I see, and section 12 lists it as an anti-pattern rather than a style preference.
A constraint carries a layer, a rung, a policy and a verdict. A capability carries four of its own. Three of them have obvious counterparts. The fourth does not exist on the constraint side at all, because a constraint does not need to be found.
| Attribute | The question it answers | Constraint counterpart | Designed in |
|---|---|---|---|
| trigger | Who invokes this, and how do they find it? | none. A constraint fires on its own | section 9 |
| rung | What carries it, and how much of it can the model reinterpret? | rung: reach and authority | sections 1–5 |
| grant | What may it touch while it runs? | layer: whose rule it is | section 10 |
| return | What crosses back to the caller, and what stays behind? | policy: what happens when it fires | the ledger, in the sequel |
The missing counterpart is the whole reason these two pages exist. A hook runs because an event happened. A gate runs because the pipeline reached it. Neither one waits to be noticed. Every capability on the ladder below waits to be noticed, and three of the four wait to be noticed by a model reading a one-line description.
Every rung below is priced twice on this page, so the two words are worth pinning here. The standing cost is what the capability puts in the context window on every turn, whether it fires or not: a name, a description, a schema. The invoked cost is what it puts there when it does fire: a body, a file, a result. One is paid thousands of times a week and the other is paid when the work needs it.
That is the whole reason the ladder has four rungs rather than one file format, and the accounting behind it belongs to the sequel rather than to this page. Keeping Capabilities Honest measures both prices, and the rest of this page just states them per rung and moves on.
A capability is a component whose removal means the agent can do less. It is described by a trigger, a rung, a grant and a return. It charges context on every turn and pays out only when its trigger matches. Design all four attributes or you have installed a file and called it a design.
One ladder, from open to hidden. A command hides nothing and the model reads every word. A server hides everything but a schema. Each section states what the rung is, what it costs, what it buys, and the signal that you are standing on the wrong one. Section 5 handles the case where the answer is not a rung at all.
A command is a file of prompt text with a name. Somebody types the name and the text becomes the turn. Nothing is hidden, nothing is decided, and nothing fires unless a person makes it fire. That last property is the rung's whole advantage and its whole ceiling.
A markdown file, in the project at .claude/commands/ or in the account at
~/.claude/commands/, with a subdirectory for a namespace. The frontmatter is
small and every field is a lever on the ledger.
---
description: Check the current diff against the charter's rung-1 rules
argument-hint: [base-branch]
allowed-tools: Bash(git diff:*)
disable-model-invocation: true
---
The diff under review, against $1:
!`git diff $1...HEAD --unified=0`
Read @docs/charter.md. Check the diff against the rung-1 section only.
Report one line per violation, and nothing else. Do not fix anything.
Three of those lines are the interesting ones. $1 and $ARGUMENTS
make the file a function rather than a note. The ! prefix runs a shell command
when the command expands and drops the output in, which means the prompt arrives already
holding its evidence. disable-model-invocation takes the description out of the
standing bill, which is the right default for anything a human always initiates.
! injection collects the diff, the log, the failing test before the model starts. It cannot forget to look.A command cannot fire when the model needs it. It fires when a person remembers it. So the moment the value of the capability depends on it running at the right time rather than at a chosen time, this rung is wrong, and it is wrong in two different directions.
| What you notice | What it means | Where it goes |
|---|---|---|
| it must run every time, and someone forgot | this is not a capability. It is a rule with a typing requirement | a hook, or a gate. Constraint Engineering |
| the model should have known to do this | the trigger belongs to the model, not to you | a skill. Section 2 |
| you type it constantly with the same arguments | it is a default, not a capability | the charter, or a setting |
A command that guards something that matters is a guardrail made of human memory. If the consequence of skipping it is a bad merge rather than a worse paragraph, it was never a capability. Promote it to a mechanism that fires on its own and keep the command as the convenient path, not as the enforcement.
A skill is knowledge and procedure the model loads when it decides the job needs them. It is the first rung where the trigger belongs to the model, which makes one line of frontmatter the highest-leverage text in the whole capability. Everything else about the design follows from keeping the standing cost at that one line.
A skill is a directory with a SKILL.md and whatever it needs beside it. The
three levels are not a packaging detail. They are the two prices of
section 0, expressed as a file layout.
| Level | What it holds | When it is paid for | Budget |
|---|---|---|---|
| description | when to use this, in the words a user would use | every turn | one or two sentences. Treat it as expensive |
| SKILL.md body | the procedure. The steps, the order, the refusals | on trigger | short enough to read in one pass |
| bundled files | reference tables, schemas, examples, scripts | only if something reads or runs them | as large as the job needs |
migration-writer/
├── SKILL.md the procedure. Around a page
├── references/
│ ├── column-types.md the table nobody memorizes
│ └── rollback.md read only when a rollback is asked for
└── scripts/
└── check_reversible.py deterministic. Run, do not read
--- SKILL.md ---
---
name: migration-writer
description: Write, review or roll back a database migration in this
repository. Use when the user mentions a migration, a schema change, adding
or dropping a column, an index, or a backfill. Covers the reversibility
check this repository requires before a migration can merge.
allowed-tools: Read, Write, Edit, Bash(python scripts/check_reversible.py:*)
---
1. Read the existing migrations in db/migrate to match the local style.
2. Write the migration. Every one is reversible or it does not merge.
3. Run scripts/check_reversible.py against the new file. Do not
reimplement the check by reading the file yourself.
4. For column types, read references/column-types.md. Do not guess.
The script in that layout is the part people skip, and it is the cheapest determinism on the page. A bundled script has no standing cost, no schema, no process to run, and no configuration. It answers the same way every time. Reach for an MCP server after you have established that a script cannot do it, not before.
Two limits, and they are different in kind. The first is that the boundary is politeness. The model can read the skill file, disagree with it and proceed. A skill cannot refuse anything, so a rule you need obeyed does not become obeyed by being written in a skill. It becomes obeyed by a hook or a gate, and the skill is where the accompanying knowledge goes.
The second is that everything it loads lands in this window. A skill whose body dumps a reference manual on trigger has moved cost from the standing column to the invoked column and then blown the invoked column. That is what the bundled files are for, and it is the most common repair I make to a skill somebody else wrote.
If its body is mostly reading and the model then works from what it read, it wanted to be a sub-agent, and section 3 explains why the window pays for that mistake. If the model keeps declining to follow it, it wanted to be a constraint, and no amount of stronger wording changes the rung it is on.
A sub-agent runs the work in a window the parent never sees and hands back a report. It is the only rung that gives context back rather than taking it, and the price of that is a lossy, one-shot return. Design the return first. Everything else about a sub-agent is easier than that part and matters less.
A markdown file in .claude/agents/. The frontmatter names it, describes when to
use it, and declares the tools it may hold. The body is its system prompt, so the file is a
job description rather than a procedure.
---
name: charter-auditor
description: Audit a branch against the charter and report violations with
file and line. Use when asked whether a change conforms, before opening a
pull request, or when a reviewer disputes a rule.
tools: Read, Grep, Glob, Bash(git log:*), Bash(git diff:*)
model: sonnet
---
You audit changes against docs/charter.md. You do not fix anything and you
do not write files.
Return exactly this shape, and nothing else:
one line per violation: path:line · rule id · what it violates
then a final line: N violations across M files
If there are none, return exactly: no violations.
Two lines in that file do most of the work. The tools line is a grant, and a
grant left off is a grant inherited by accident, which
section 10 treats as a real defect rather than an untidiness.
The return block is the interface. A sub-agent whose return shape is unstated returns prose,
and prose is exactly what you were trying to keep out of the parent window.
The parent cannot see what the sub-agent saw. That is the point and it is also the whole cost. Three consequences follow, and the first two are avoidable.
flowchart LR P["the parent turn"] -->|"task prompt, plus a grant"| A["the sub-agent"] A --> R1["forty file reads"] A --> R2["three wrong guesses"] A --> R3["the finding"] R3 -->|"the report, in the stated shape"| P R1 -.->|"never crosses"| P R2 -.->|"never crosses"| P class P dim class A pass class R1 block class R2 block class R3 warn
A sub-agent for a job that needs three lines of knowledge is a context wall around nothing. You paid a task prompt, a fresh system prompt and a round trip to avoid loading a paragraph. The rule I use: if the work does not involve reading or searching more than the parent wants to hold, it is a skill.
The other direction is subtler. If the parent keeps asking follow-up questions about what the child found, the wall is in the wrong place. Either the return shape is too narrow, or that work belonged in the parent's own window all along.
At this rung the capability stops being text the model reads and becomes a process the model calls. Another language, another machine, credentials it never sees. You buy determinism and a real trust boundary. You pay the largest standing bill on the ladder, and you pay it for every tool the server declares rather than for the ones you use.
A server speaking the Model Context Protocol, connected over stdio to a local process, or over HTTP or server-sent events to a remote one. Where you declare it decides who gets it, and that is a layer decision in the sense Anatomy of a Charter uses: whoever owns the file owns the capability.
| Scope | Who gets it | Use it when |
|---|---|---|
project · .mcp.json | everyone who checks out the repository | the server is part of how this codebase is worked on |
| user | you, in every project | a personal tool, and you accept the standing cost everywhere |
| local | you, in this project | trying it out, or holding credentials that must not be shared |
{
"mcpServers": {
"incidents": {
"type": "http",
"url": "https://incidents.internal/mcp",
// auth happens out of band. The model never holds the token
},
"repo-graph": {
"command": "uvx",
"args": ["repo-graph-mcp", "--root", "."]
}
}
}
Most teams use MCP as a tool bus and never touch the other two. That is a budget mistake as much as a design one, because tools are the expensive primitive and resources are the cheap one.
| Primitive | Who pulls it | Standing cost | Good for |
|---|---|---|---|
| tools | the model, when a schema matches | the full schema of every declared tool, every turn | actions, and lookups the model must decide to make |
| resources | a human or the model, by reference | none per item. They are fetched, not advertised | documents, records, dashboards. Anything addressable |
| prompts | a human, by name | a name and a line | server-side commands. Rung 0, shipped by whoever runs the server |
A server's instructions are the fourth thing it can put in your window, and they arrive without being asked for. Read them before you enable the server in project scope. They are prose at rung 0 of the constraint chain, written by somebody who has never seen your charter, and the model cannot tell them apart from yours.
A server with twenty tools stands in your window twenty times, in full, on every turn, with every parameter description. The tools you never call are indistinguishable from the ones you do. Four cuts, in the order I would make them.
The process wall runs both ways. You cannot see in, and the model cannot see in either. So a tool whose description is vague is a capability you cannot repair from the outside. On the skill rung a confused model can read the file and work it out. Here it can only call the thing and hope. The schema description is the capability as far as selection is concerned, which puts the craft of section 9 in someone else's repository when the server is not yours.
The second limit is a security property rather than a cost. What comes back through the wall is text, and it lands in the window next to your instructions. A server that returns a third party's content is returning an untrusted string into a context that treats strings as instructions.
flowchart LR M["the model"] -->|"a call that matches the schema"| S["the server, another process"] S -->|"credentials the model never sees"| U["the upstream system"] U -->|"content written by other people"| S S -->|"a result, as text, into your window"| M X["your charter, in the same window"] --- M class M dim class S pass class U block class X warn
Prompt Injection covers that path properly. The capability-side rule is short: a tool that reaches content other people write needs its return treated as data, and the constraint that enforces it does not live in the server. It lives in your harness, where you can see it.
One tool, your language, no credentials, no network, no other client. That is a script in a skill, and a skill costs one line where the server costs a schema on every turn. I have written this server twice and deleted it twice. The four questions in section 7 exist to stop me writing it a third time.
Three things can reach an external system: a command-line tool the agent already has, a script that calls the API, or an MCP server. All three put a process between the model and the system. They differ in where the interface description lives, and that is what you are choosing between. Most teams reach for the third and needed the first.
Think about what a schema actually does. It tells the model what calls exist, what arguments they take, and what comes back. A CLI has all of that too. Its schema is the tool's own help text and the model's prior knowledge of it, and neither of those stands in your context window on every turn. That is the trade in one sentence, and the rest of this section is the cases where it goes the other way.
| A CLI, run with Bash | A script calling the API | An MCP server | |
|---|---|---|---|
| interface lives | in the tool's help, and in what the model already knows | in your file, which you wrote | in your window, on every turn |
| standing cost | none | none | a full schema per declared tool |
| discovery | the model guesses flags. Sometimes wrongly | none to guess. The script is the interface | typed and advertised. The strongest of the three |
| auth | the tool's own credential store. The model never sees a token | an environment variable the script reads | the server's, including interactive sign-in flows |
| grant precision | excellent. Bash(gh pr view:*) is one subcommand | excellent. One script, granted by name | per tool, and only for the tools it declares |
| output shape | whatever it prints. Yours to pipe and trim | exactly what you return | whatever the server author chose |
| other clients | anyone with a shell | nobody. It is yours | every client that speaks the protocol |
| fails by | a wrong flag, retried. Turns spent guessing | going stale when the API moves | standing cost, and a schema you cannot repair |
# a CLI, pinned in a skill so nothing is guessed
allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*)
2. Read the pull request with:
gh pr view $NUMBER --json title,body,files
Do not use other gh subcommands for this step.
# a script, when the CLI's output is the wrong shape
scripts/pr_summary.py $NUMBER # prints five lines, not five pages
# a server, when four other agents need the same access
mcp__reviews__get_pull_request # a schema, standing, every turn
The CLI route has a cost that does not appear in the ledger. The model guesses a flag, the command fails, it reads the help text, it tries again. Three turns for a lookup. A typed schema makes that failure rare, and a broad surface makes it common, so the wider the tool you are reaching for, the better the server looks. A skill that pins the exact invocations closes most of the gap for a few commands and none of it for forty.
The second honest case is authentication. A CLI works because someone signed in on that machine. A server can own the sign-in flow, refresh the token, and keep the credential in a process the model cannot read. When the alternative is a token pasted into an environment variable in a script, the server is buying a real security property and not just tidiness.
Wrapping a CLI you already have in an MCP server is the most expensive way to gain nothing.
You pay a schema on every turn for a capability Bash already provided, you lose
the ability to pipe and trim the output, and you add a process to keep alive. If the reason
is that the model keeps getting the flags wrong, the fix is a skill with the invocations
written down.
Who owns a capability, and which rung it belongs on. Two sections. The first is the question that decides who may change the words, and the second is the question everyone asks first and should ask second.
A plugin ships commands, skills, sub-agents, hooks and server declarations under one name, from one repository, with one owner. It changes nothing about how any of them work and everything about who decides what they say. That makes it the packaging question and the layer question at the same time, and it is a dependency in the ordinary sense: it changes under you.
Before plugins there were two places a capability could live: your account, or this repository. Both answers are about you. A plugin adds a third, and the third one is about somebody else, which is why it is worth a section of its own rather than a line in the catalog.
| Lives in | Who owns the words | Who gets it | Updates | Right when |
|---|---|---|---|---|
| user scope | you | you, in every project | when you edit it | personal habits, and nothing a teammate depends on |
| project scope | whoever reviews the repository | everyone with a checkout | through review, like code | this codebase's practices. The default, and it should stay the default |
| a plugin you publish | your team | every project that enables it | when you release | three or more repositories need the same capability |
| a plugin you install | somebody else | every project that enables it | when they release. Read the diff | a vendor or a community already solved it well |
A plugin can carry more than capabilities, and the parts that are not capabilities are the ones to read first. Enabling a plugin can enforce rules, grant tool access, and put prose in your window on every turn, none of which is visible from its name.
| Part | Family | What enabling it means |
|---|---|---|
| commands | capability, rung 0 | new names a human can type. Cheap and visible |
| skills | capability, rung 1 | new standing lines, and new competitors for every trigger you own |
| sub-agents | capability, rung 2 | new standing lines, and grants written by somebody else |
| hooks | constraint | code that runs on your events. Read every one before enabling |
| server declarations | capability, rung 3, plus a trust boundary | schemas standing in every turn, and a process holding credentials |
The first is arithmetic. A plugin's standing cost is the sum of everything it ships, not the part you wanted. Enable it for one skill and you are paying for its other six, plus any server it declares. That is the same bill as before and now somebody else decides its size.
The second has no equivalent locally. A plugin author has never seen your catalog, so their descriptions cannot avoid colliding with yours. The sibling test from section 9 now has to run across a boundary you do not control, and the loser of a collision fails silently, which means an update can quietly disable a skill of your own that used to fire.
/context before and after.
Three repositories is my threshold. Below that, project scope is simpler, reviewable in the normal way, and impossible to forget you have. Above it, copying the same skill by hand starts producing four versions that drift, which is the failure a package exists to prevent.
The other good reason is handing someone a working setup rather than instructions. A team that owns a domain, its practices, its checkers and its access can ship all of that as one thing. That is the strongest form of the layer idea in Anatomy of a Charter: the people who own the practice own the file that carries it, and consumers get the next version without being told.
One theme, so a consumer can say what enabling it means. A documented standing cost, in lines and in schemas. Names specific enough not to collide with a local catalog it has never seen. Hooks and server declarations kept separable from the skills, so someone can take the knowledge without the enforcement. And a changelog that treats a description edit as a breaking change, because for triggers it is one.
Placement on this ladder is not a judgment about how important the capability is. It is four facts about the work: who notices the need, whether the work should stay out of this window, whether it needs a process wall, and how often it fires. Answer them in order and stop at the first one that decides.
flowchart TD
Q0{"remove it: can the agent do more"} -->|"yes"| CON["a constraint. Wrong ladder"]
Q0 -->|"no"| Q1{"who notices the need"}
Q1 -->|"a human, when they choose"| CMD["command"]
Q1 -->|"the model, mid-task"| Q2{"is the byproduct bigger than the answer"}
Q2 -->|"yes"| AG["sub-agent"]
Q2 -->|"no"| Q3{"credentials, several clients, a broad surface, a vendor server, or a versioned contract"}
Q3 -->|"yes"| MCP["MCP server"]
Q3 -->|"no"| SK["skill"]
class Q0 dim
class Q1 dim
class Q2 dim
class Q3 dim
class CON block
class CMD warn
class SK warn
class AG pass
class MCP pass
Take the job from section 1: check a branch against the charter. All four rungs can carry it. They carry it at four prices, and they fail four different ways.
| As a | What it costs | What it buys | How it fails | Right when |
|---|---|---|---|---|
| command | nothing standing. The charter and the diff on invocation | a rehearsed prompt, run when a person decides | nobody types it before the pull request | the check is advisory and the author is the reader |
| skill | one line standing. The procedure plus the charter on trigger | the model checks without being asked | the charter is long, and now it is in your window on every conforming change too | the charter is short and the check is part of doing the work |
| sub-agent | one line standing. A task out, a violation list back | the charter and the whole diff read elsewhere. Eight lines come home | the report omits the line numbers you needed | the charter is long or the diff is wide. My default for this job |
| MCP server | a schema standing, on every turn, forever | the same verdict every time, and every client gets it | you have built a linter with extra steps | the rules are decidable. Then it is a gate, and the ladder changes |
The last row is the useful one. When a capability at rung 3 turns out to be fully decidable, it has stopped being a capability. It is a check, and checks belong on the constraint chain where they can refuse things. Noticing that transition early saves you an MCP server and gets you a gate that actually blocks.
These four questions settle the first placement. They do not tell you when to move a capability that is already running, and they do not price the standing line it holds. Keeping Capabilities Honest carries both: the two prices in full, the signals that promote or demote a capability, and the numbers that tell you whether the one you installed ever fires.
A capability placed correctly still fails if nobody finds it, if it may touch the wrong things, or if what comes back is not usable. Five sections, and four of them are about one line of frontmatter and one paragraph about the return.
Placement is the first decision and it is not the one that decides whether the capability works. These are the checklists I run before committing a capability, and the tell at the end of each is the symptom I look for when somebody says theirs does not work. Four rungs, four lists, and one property runs through all of them: say what comes back.
charter-check, not helpers.§1argument-hint, so the person typing it knows what it wants without opening the file.§1! injection collects the diff, the log or the failing test so the prompt arrives complete.§1allowed-tools to the exact commands it injects, because it is running a shell for you.§10The tell of a bad one: it opens with a request for help and ends with the model asking which files you meant. A command that has to ask questions was a note, not a capability.
allowed-tools to its own scripts, so the grant is the narrow one rather than the session's.§10The tell of a bad one: you cannot say in one sentence when it should fire. If you cannot, the model is inferring it from the same words you just failed to summarize.
tools. An omitted grant is the widest one, and the widest one is never what the file meant.§10The tell of a bad one: the parent asks a follow-up question every single time. The wall is in the wrong place, or the report shape was never designed.
The tell of a bad one: it wraps a CLI you already had. You are paying a schema on every
turn for something Bash did for free, and you have lost the ability to trim the
output.
Every list above has a line about what comes back. A command says what not to do, a skill says which file to read, a sub-agent states its report shape, a server returns a field rather than a payload. Capability work is interface work, and the return half of the interface is the half that gets left to chance.
Three of the four rungs are selected by a model reading a description. That description has two jobs and they pull against each other. It has to be short, because it stands in every turn. It has to be specific, because a vague one never matches. Almost every dead capability I have looked at died in that sentence.
Write the description in the words the work arrives in, not the words the implementation uses. A skill named after your internal abstraction, described in terms of your internal abstraction, matches a request nobody makes.
# never matches
description: Helpers for working with the persistence layer.
# It says what the author built. A user says "add a column",
# "the migration failed", "can we drop this index". None of those
# words appear, so nothing matches.
# matches
description: Write, review or roll back a database migration in this
repository. Use when the user mentions a migration, a schema change,
adding or dropping a column, an index, or a backfill. Not for query
performance work, which belongs to the query-tuning skill.
Write, review or roll back rather than handles.
Take every pair of capabilities in scope and say the sentence that tells them apart. If you cannot say it in one sentence, the model cannot infer it from two descriptions. You then have two choices and both are fine: merge them, or add the negative trigger to each. Leaving the overlap is the option that is not fine, because the loser of a collision fails silently and you will conclude that it does not work.
| The collision | What happens | Repair |
|---|---|---|
| a general skill and a specific one | the general one wins, and the specific rules never apply | narrow the general description, and name the specific one in it |
| two skills with the same nouns | selection becomes a coin flip you cannot see | merge, or split along the verbs instead of the nouns |
| a skill and a sub-agent for one job | either could fire, and they cost very different amounts | keep the sub-agent for the reading, and let the skill call it |
| an MCP tool and a bundled script | the tool wins, because a schema is more visible than a step in a file | pick one. Two implementations of one job is a maintenance bug as well |
A description is testable and almost nobody tests it. Write down three requests that should fire the capability and three near misses that should not. Start a fresh session for each and see what happens. Six runs tell you more about whether a skill works than reading its body ever will.
This is the smallest useful eval on the capability side, and it has the property that The Eval Loop asks for: a golden set, a distribution rather than one green run, and a number that moves when you edit the description. Keep the six cases in the skill directory. They are the only regression test a trigger can have.
Name a capability for the job, in the caller's language, with a verb in it where a verb
fits. migration-writer, charter-auditor,
incident-lookup. Never utils, helper,
manager or the name of your internal module. The name is read as part of the
trigger, so a name that says nothing wastes the cheapest tokens you have.
Every capability runs holding some set of tools. That set is the one attribute that is a capability property and a constraint rung at the same time: on the constraint chain a tool grant sits just above prose, because it makes a whole class of action unrepresentable rather than discouraged. Leave the line out and you have written the widest grant available, silently.
A sub-agent told not to write files is a sub-agent that may write files. A sub-agent granted
Read, Grep, Glob cannot write one. The difference is the difference between prose
and a mechanism, and it costs one line of frontmatter, which is why
Constraint Engineering puts the tool grant on the
constraint chain rather than in the settings.
| Rung | Declared as | If you omit it | Worth narrowing when |
|---|---|---|---|
| command | allowed-tools in frontmatter | the session's own permissions apply | the command injects shell output. Then narrow to the exact commands |
| skill | allowed-tools in frontmatter | the session's own permissions apply | the skill bundles scripts. Grant those scripts and nothing else |
| sub-agent | tools in frontmatter | it inherits, which is almost never what you meant | always. A reviewer that cannot write is a different component |
| MCP server | permission rules per tool, and the scope you declared it in | every declared tool is callable | the server has write or destructive tools beside its read ones |
Capabilities call capabilities. A command can name a skill. A skill can tell the model to dispatch a sub-agent. A sub-agent can hold MCP tools and load skills of its own. What does not cross is knowledge of what happened: the parent gets the report and nothing else, which is the property you bought and also the one that bites.
flowchart TD H["a human types a command"] --> C["command · a rehearsed prompt"] C --> M["the parent turn"] M -->|"description matched"| S["skill · procedure plus references"] S --> SC["a bundled script · deterministic, no standing cost"] M -->|"dispatch, with a declared grant"| A["sub-agent · its own window"] A --> T["MCP tools, if granted"] A -->|"a report in the stated shape"| M T -->|"untrusted content"| A class H dim class C warn class M dim class S warn class SC pass class A pass class T block
Two rules keep that graph from turning into a maze. First, a capability may compose downward and never sideways: a skill dispatching a sub-agent is fine, and two skills that call each other are one skill with a naming problem. Second, the grant travels with the callee, not with the call. If a sub-agent needs a write tool for one task and not for another, that is two sub-agents, and the second one is the safe default.
A sub-agent file with no tools line is the most common real defect on this
page. It reads as tidy and it means everything the parent had. The auditor can
write. The researcher can push. Nothing goes wrong until the day something does, and the
transcript then shows a component doing exactly what you never granted it and never
forbade.
Eleven primitives carry capability. Three more package them. Six things sit next to them and belong to other families, and knowing which is which is most of what keeps a charter readable. The standing column is the one to read down.
| Primitive | Rung | Invoked by | Lives in | Standing cost | Boundary |
|---|---|---|---|---|---|
| the four rungs | |||||
| command | 0 | a human, by name | .claude/commands/ | a line, or none | none |
| namespaced command | 0 | a human, by path | .claude/commands/<group>/ | a line, or none | none |
| MCP prompt | 0 | a human, by name | the server | a line | none. Someone else's rung 0 |
| skill · project | 1 | the model, or a human | .claude/skills/ | a line | convention |
| skill · personal | 1 | the model, or a human | ~/.claude/skills/ | a line, in every project | convention |
| bundled reference | 1 | the model, when the body says to | a skill directory | none | none. It is a file |
| bundled script | 1 | the model, by running it | a skill directory | none | process, for free |
| sub-agent | 2 | the parent turn | .claude/agents/ | a line | context |
| MCP tool | 3 | the model, by schema | the server | a full schema, every turn | process |
| MCP resource | 3 | a human or the model, by reference | the server | none per item | process |
| server instructions | 3 | nobody. It arrives | the server | whatever the author wrote | none, and that is the problem |
| packaging · not capability, but it decides who has one | |||||
| plugin | — | enabled per project | a marketplace or a repository | the sum of what it ships | — |
| project scope | — | everyone with the checkout | .claude/, .mcp.json | charged to the whole team | — |
| user scope | — | you, everywhere | ~/.claude/ | charged in every project | — |
| adjacent · reaches for the same file and belongs to another family | |||||
| hook | — | an event | settings | none in the window | constraint. See the other page |
| permission rule | — | a tool call | settings | none in the window | constraint, and the cheapest one |
memory · CLAUDE.md | — | nobody. It is always there | the repository, or the account | the whole file, every turn | state. It needs an expiry |
| output style | — | a session setting | settings | small | setting |
| model and effort | — | a session or a callee | settings, or frontmatter | none | setting |
| scheduled and background work | — | a clock, or a parent job | the factory | none in this window | factory. See The Factory |
Two rows in the adjacent group deserve their reputation for causing trouble. Server instructions are prose you did not write, standing in your window, indistinguishable from your charter. A memory file is state with no layer, no scope and no expiry, and it governs work it has never seen. Both are the cheapest ways to end up with a rule nobody agreed to.
None of these are failures of effort. Each one is what you get when a capability was installed with three of its four attributes designed. Read the symptom column as a diagnosis: it names which of trigger, rung, grant or return was left to chance.
| Name | What it looks like | Symptom of | Repair |
|---|---|---|---|
| the unread skill | installed, described in the author's vocabulary, never fires on its own | no trigger design | rewrite the description in the caller's words. Then test it six times |
| the kitchen sink | one skill for six unrelated jobs, so it matches everything and helps with nothing | no trigger design | split along the verbs. One job, one description |
| the twin | two capabilities whose descriptions overlap. The general one always wins | no trigger design | negative triggers in both, or merge them |
| the rule in a skill's clothing | a skill that only says always do X, and gets ignored on the turns that matter | wrong ladder | put the rule on the constraint chain. Keep the knowledge in the skill |
| the context bomb | a skill body that loads a reference manual on trigger | wrong rung for the weight | procedure in the body, tables in bundled files |
| the standing tax | a server enabled in every project for a tool used monthly | no cost accounting | narrow the scope, or defer the schemas |
| the API mirror | thirty tools shaped like upstream endpoints, so the model must assemble the task itself | no return design | five task-shaped tools. Let the server do the assembling |
| the chatty return | a tool that answers a question with the payload the answer arrived in | no return design | return the field. The caller cannot refuse what you send |
| the blind parent | a sub-agent report that omits what the parent needed, so the parent reads it all again | no return design | state the return shape in the agent file, fields included |
| the grant leak | a sub-agent file with no tools line, holding everything the parent held | no grant design | declare the grant. A reviewer that cannot write is a different component |
| the server that should be a script | one stdio server, one tool, your language, no credentials, no other client | rung inflation | a bundled script. No standing cost, same determinism |
| the undeletable capability | nobody knows whether it fires, so nobody dares remove it. The catalog only grows | no evidence | count invocations, then hold a deletion day |
Count the symptom column and the distribution is the point. Four are trigger failures, three are return failures, and only two are about the mechanism at all. Capability work is mostly interface work, done in one line of frontmatter and one paragraph about what comes back.
Everything above is what a capability is and how to write a good one. None of it tells you what the set of them costs you next quarter, whether the ones you wrote still fire, or how to get a repeatable answer without buying a rung to hold it. Keeping Capabilities Honest is that half: two prices, five kinds of rot, determinism bought at the cheap end, and the four numbers that make a silent failure audible.