An agent feature often starts as a sentence. Then it picks up nouns.
Should this be a skill, a subagent, an MCP server, or a hook?
That question comes a step too late. These are not interchangeable containers. They stop different failures. Ask instead: what job does this logic do, and where does its failure need to stop?
Put a repeatable method in a skill. Give context-sensitive work to a fresh worker with a bounded brief. Put a capability behind an interface when several clients need to call it, using MCP when that protocol boundary is worth the cost. Put rules that must survive a bad model decision in code or policy at the boundary that owns the data.
One feature may need all four. That is not duplication. Each layer has a different job.
Put a repeatable method in a skill
A skill fits when the durable thing is the way the work gets done.
Take an editorial review procedure. First inspect whether the argument says what it means. Then inspect its construction: section order, sentence density, repetition, and missing transitions. Finally, run a mechanical scan for forbidden punctuation and formatting. The subject changes from article to article, and the worker may change too. The procedure should not.
That belongs in a SKILL.md-style asset: ordered instructions, supporting references or scripts when useful, and a clear completion check. Claude Code documents SKILL.md as a format for reusable skill instructions, with related files and host-specific controls. Other hosts have their own loading rules, so the filename is not a universal protocol.
It is easy to confuse a skill with a workflow. The Bureau, for example, has reusable workflows that assign work to specialist roles and move an artifact through stages. A workflow is reusable procedure in the ordinary sense, but it is not automatically a SKILL.md or a host-loaded skill. A workflow coordinates who does what and when. A skill gives a worker a method to apply.
The two can work together. A workflow can assign a review to a fresh worker and give that worker an editorial skill. Claude Code's extension overview describes skills and subagents as features that can be combined. The worker owns this run. The skill supplies the method for the next one, too.
A practical test: if you replaced the worker tomorrow, would you still want it to follow the same procedure? If so, the method needs a reusable home.
Put context-sensitive work in a fresh worker
Some work gets worse when it brings too much history along.
A reviewer who watched an argument take shape may remember the intended meaning and read it into a weak paragraph. A specialist asked to inspect one subsystem may spend its attention on an unrelated conversation in its context. In those cases, the answer is not another instruction file. It is a fresh execution with a clean brief and selected inputs.
That is a job for a subagent or another fresh worker. In Claude Code, subagents have their own context and configurable tools and permissions, and selected skills can be available to them. Those are Claude Code features, not promises about every agent host.
"Fresh" needs a careful definition. A fresh model context means the worker does not inherit the parent's full conversation. That can reduce the pull of earlier guesses, arguments, and irrelevant detail. It is not an operating-system sandbox. On its own, it does not stop the worker from reading another file, calling another tool, or reaching another service.
Those limits need separate controls. A tool allowlist can narrow what the host exposes. Process or container isolation can narrow filesystem and network access. A bounded input manifest can say what the worker should inspect, but it remains an instruction unless the runtime enforces it. The controls can support one another. They are not substitutes.
The Bureau uses this context boundary in a limited way: a workflow can give a fresh specialist a role and selected artifacts instead of the whole warm conversation. That keeps the task focused. It does not create security properties the host has not enforced.
Use a fresh worker when prior context could skew the work, when a task has a clean handoff, or when a specialist should return one inspectable artifact. If that worker also needs a repeatable method, give it a skill. See subagent for the role and critic agent for a bounded-review example.
Put shared capability behind an interface when the boundary earns it
Sometimes the durable part is neither a method nor a clean context. It is an operation that clients need to call: fetch a record, create a ticket, search a store, or make an approved state change.
That is where people often reach for MCP. Sometimes it is the right move. The MCP tools specification defines how a server advertises tools and how a client calls them. Compatible clients get a discoverable contract instead of each one needing a private function signature. MCP has primitives beyond tools, but tools are the relevant example here.
An MCP server has a cost: protocol surface, lifecycle concerns, schemas, errors, and compatibility work. If one local process is the only caller, a function, CLI, or ordinary API may be clearer. Reuse alone is not enough. MCP needs a real client, process, or interoperability boundary to justify the protocol.
There is a more important distinction than transport: exposing a capability is not the same as authorizing its effect.
MCP has an authorization specification for protocol- and transport-layer access. That does not replace domain authorization in the service. The service still decides whether a caller may change an account, read a tenant, or attach memory to a conversation. It still validates domain input and protects the data model.
An MCP tool named write_memory can make an operation available to several clients. Its name and input schema can help a model call it correctly. Neither proves that a requested write is allowed. Protocol access gets the caller to the tool. The service that owns the data decides whether the operation may happen.
This separation makes the tool easier to reason about. The MCP layer should expose a small contract with useful errors, as discussed in MCP tool design is API design. The domain layer should enforce rules that cannot depend on a well-behaved client.
Put non-negotiable rules where the model cannot waive them
Model instructions can steer a choice. They are a poor place to keep invariants.
"Use the conversation that contains the source turn" may help a model choose an argument. It does not stop a buggy or hostile client from sending inconsistent identifiers. "Never write across tenants" states a policy. It does not enforce tenant isolation.
If a rule must hold when a prompt is missed, the model is confused, or the caller is hostile, enforce it in service code or policy at the data-owning boundary. Schema validation, authentication, domain authorization, server-derived identifiers, transactions, and database constraints stop different failures. They are not interchangeable.
Imagine a memory service designed around a narrow version of this pattern. Its server would validate tool input before calling the memory operation. For a memory write, the caller would supply a source-turn identifier but not a separate chat identifier. The server would look up the source turn and derive chat_id from it. The mutation could then run in a database transaction.
That server-side derivation would prevent one particular caller-supplied pairing: the caller could not send a source-turn identifier and independently pair it with an arbitrary chat_id in the same tool input. It would not prove the caller chose the right source turn. It would not establish per-user authorization or tenant isolation. Those rules would need their own enforcement.
That is the standard for every control: name the exact failure it stops, and no more.
Hooks sit nearby, but do not replace the data boundary. A host hook can run on a known event and block or transform an action under host policy. That is stronger than hoping a model remembers an instruction. But a hook outside the data-owning service may lack the facts or authority for a business decision. The service is still responsible for domain authorization and data integrity. See agent hook for the narrower event-driven concept.
The dividing line is not AI code versus normal code. It is choice versus invariant. Let the model choose among safe actions. Reject unsafe states at a boundary the model cannot waive.
Compose the layers at their seams
Consider the memory-writing example end to end.
A skill can explain how to identify a durable fact, classify it, and preserve the supporting evidence. A fresh worker can inspect a bounded conversation without inheriting an earlier worker's conclusions. An MCP tool can expose the write operation to compatible clients. The service can authenticate and authorize the caller, validate the payload, derive identifiers from trusted records, and commit the change in a transaction.
The feature is not "implemented in MCP." MCP owns one seam. The skill does not secure the database. The fresh worker does not create filesystem isolation. A server-side lookup does not prove who is authorized to use the source turn it found. And the service does not tell the model which facts are worth remembering.
The Bureau follows the same separation at the workflow level. Its reusable workflow instructions coordinate fresh specialist runs and deterministic checks. Its limits are useful to see clearly: the workflow is not automatically a SKILL.md, and a clean handoff does not grant security properties the host has not enforced.
Explicit seams make change cheaper. You can revise the method without changing the service contract. You can replace a worker without moving the invariant. You can add a client without teaching it the database's internal rules. The surrounding execution system still matters, but that is a separate topic. The harness is the product covers that wider frame.
A short placement test
When new logic appears, ask four questions:
- Would another run need the same method? Put the method in a skill.
- Could prior context distort the result? Give the job to a fresh worker with explicit inputs.
- Does another client or process need the capability? Put a stable interface around it, using MCP when interoperability justifies the protocol.
- Must the rule hold if the model ignores it? Enforce it in service code or policy at the boundary that owns the data.
More than one answer may be yes. Compose the layers. Then inspect every seam: what crosses it, who validates it, and where a failure stops.