Here's how I think about AI coding agents now, after spending weeks building my AIHomeLab in one. The category includes Claude Code, Cursor, Cline, Codex, Antigravity, and whatever you build with one of the agent SDKs. They all wrap a frontier model in a stateful loop with tools, memory, and procedures. My examples here are from Claude Code because that's the one I learned in, but the mental model travels. If you use any of these systems, the same five insights apply.

I wrote these notes for myself, to remember what stuck rather than transcribe everything I did.


TL;DR

If you have 2 minutes, here's what I learned compressed into five things.

I've worked hands-on with Claude Code building my AIHomeLab. The same architecture shows up across the whole AI coding agent category (Cursor, Cline, Codex, Antigravity, custom SDK builds): a frontier model wrapped by a harness. The model is the brain, stateless and forgetful between calls. The harness is the body, the program that gives the model a loop with tools, memory, and governance. Almost everything I could shape, and almost every frustration I hit, lived in the harness configuration layer, not in the model.

The five things I had to internalize:

  1. Model vs. harness diagnostic. When something misbehaves: was it the model (wrong code, weak reasoning) or the harness (wrong tool, missing config)? Different layers, different fixes.
  2. Tool results dominate context. Heavy file reads, noisy bash output, and web fetches eat most of the token budget. I learned to be deliberate, and to delegate to subagents for anything read-heavy.
  3. Descriptions are the universal discovery surface. Tools, skills, subagents, MCP tools, all rely on descriptions to tell the model when to use them. When something I built wasn't getting used, the fix was always the description, not the body.
  4. Pick the right primitive. New capability → MCP server. Bounded autonomous work → subagent. Procedure with user interaction → skill. One-off action → built-in tool.
  5. Soft vs hard governance. Safety and security-critical things go in permissions and hooks (deterministic). Conventions and style go in CLAUDE.md or skills (suggestions). Match the mechanism to the stakes.

What stuck most: the harness is a configurable system, whichever agent you're using. Most frustrations resolve once you know which knob to turn for which problem.

Document map (jump to whichever pulls you in):


The stack

People say "Claude" to mean four different things: the model, the API, the harness, and the SDK. I conflated them at first too. The lesson that stuck: when something goes wrong, you can't fix it until you know which layer is broken.

The stack, from bottom to top

05
Youhuman surface
prompts · slash commands · edits
input
04
Harness configurationyour knobs
CLAUDE.md · skills · hooks · memory · MCP · subagents · permissions
shapes behavior of
03
Harness programvendor-built loop
Claude Code · Cursor · Cline · Aider · Antigravity · custom
Agent SDK, kit for building your own harness. Not a layer, a way to author one.
inference + tool-call protocol
02
Anthropic APIHTTP contract
messages + tool defs ↔ response + tool calls
runs the model
01
Claude modelstateless
Sonnet · Opus · Haiku · tokens → next-token distribution

Bottom-up: model (stateless function) → API (HTTP contract) → harness (the agent loop). Above the harness sit two user-facing layers: your configuration (CLAUDE.md, skills, hooks, memory) and you (prompts, slash commands). The SDK is a related concept, the kit for building harnesses, shown as a callout, not a layer in the running stack.

Claude is the model. A neural network, a function from tokens to a next-token distribution. Stateless. Each call is independent; the model has no memory of yesterday's chat. "Sonnet 4.6," "Opus 4.7," "Haiku 4.5" are snapshots of model weights, different brains in the same family.

The Anthropic API is the contract. An HTTP endpoint in front of the model. You POST messages and tool definitions; you get back text and tool-call requests. The API does not execute tools. It does not remember the conversation. If you want an agent, you build the loop.

The harness is the agent loop. A program that wraps the API and gives the model a body: Claude Code, Cursor, Aider, Antigravity, or anything custom-built. It maintains history, assembles the system prompt, executes tool calls (when the model says "run ls," the harness actually runs it), manages context, enforces permissions, spawns subagents. The model is the brain; the harness is everything else.

The SDK is for building harnesses. Same primitives Anthropic uses, packaged as a library. If Claude Code is "the harness Anthropic ships," the SDK is "build your own."

The practical rule when something misbehaves: was it the model or the harness? "Claude didn't run my skill" is almost always a harness problem: discovery, permissions, or configuration. "Claude wrote a bug" is almost always a model problem. Different layers, different fixes.

Harness and model are independent

ClaudeAnthropic GPTOpenAI GeminiGoogle Open-sourceLlama · Qwen · GPT-OSS
Claude CodeAnthropic
CodexOpenAI
AntigravityGoogle
Cursor
Cline · Aider · Continue.dev
CustomSDK-built
default / first-party pairing supported

Harness and model are independent dimensions. An AI coding tool is a (harness, model) pair.

The conflation worth killing: "Claude Code = Claude." It doesn't. The harness and the model are independent choices.

Anthropic ships Claude Code, OpenAI ships Codex, Google ships Antigravity, each defaults to its own model. Third-party harnesses are mostly model-agnostic: Cursor, Cline, Aider, Continue.dev let you point at Claude, GPT, Gemini, or open-source models. Build your own harness with the Agent SDK and pick whatever model you want.

So when you compare "Claude Code vs. Cursor," you're comparing two harnesses, not two models. The same Claude weights feel different across them because the harness shapes the experience: what system prompt the model sees, what tools it has, how context is managed, what gets cached, how permissions work. Model is raw capability. Harness is workflow.

Inside the harness — where you live

Vendor-built surface

fixed code
  • Agent loop & context management
  • Built-in toolsRead · Edit · Bash · Glob · Grep · WebFetch
  • Base system prompt
  • Permissions engine
  • Extension pointsskills · hooks · MCP · subagents · memory

Your harness engineering

you author
  • CLAUDE.mdappended to base prompt
  • Skillsloaded on demand
  • Hooksdeterministic event handlers
  • MCP serversexternal tools
  • Memoryfacts across sessions
  • Custom subagentsscoped child Claudes
  • Permissions allowlistpre-approved actions

Inside one harness. Vendor builds the sockets: loop, built-in tools, base prompt, permissions engine, extension points. You author what plugs in.

A single harness instance has two surfaces, and the boundary between them is where productivity is won or lost.

The vendor's surface is fixed. Anthropic decides what the agent loop does, which tools are built in (Read, Edit, Bash, Glob, Grep, WebFetch), how context is managed, how permissions work, what the base system prompt says. You don't change any of this, but the vendor also wires extension points: a skills runtime, a hooks runtime, an MCP client, a subagent spawner, a memory loader. The vendor builds the sockets.

Your surface is what plugs into them. CLAUDE.md gets appended to the base prompt. Skills get loaded on demand. Hooks fire on events. MCP servers add new tools. Memory persists facts across sessions. Custom subagents are scoped child Claudes you've defined. The permissions allowlist tells the engine what's pre-approved.

This is harness engineering. Your .claude/ directory and your settings files are the visible shape of it. When you ask yourself "should I write a skill or a hook or a slash command for this?", you're asking which socket to plug into.

A useful test: if you can change something by editing a file in your repo or your ~/.claude/ directory, you're in the user surface. If changing it would require Anthropic to ship a new release, you're hitting the vendor surface. Most frustrations live at that boundary, and most of them are solvable on the user side once you know which socket to use.

What a conversation actually is

Three things to get straight before "context fills up too fast" can have concrete handles. First, what counts as a unit. Second, what's actually in the bucket. Third, what makes re-using the bucket cheap.

The unit: tokens

The model doesn't see characters and it doesn't see words. It sees tokens: chunks of text decided by the tokenizer that ships with the model.

  • English prose: ~4 characters per token, ~0.75 words per token. "Hello, world!" is about 4 tokens.
  • Code: tokenizes differently. Whitespace, indentation, and punctuation all count. A line of Python costs more than a line of English of the same length.
  • Filenames, paths, hashes: often each character is its own token. SHA hashes are token-heavy.
  • Image inputs: also tokenized. A typical screenshot is ~1500 tokens.

The numbers matter because everything is metered in tokens: context window, prompt cache, API bill. When someone says "Sonnet has a 200K context window," they mean 200,000 tokens.

The bucket: the context window

Every model has a maximum input size: the context window. Hard ceiling.

  • Sonnet 4.6, 200K tokens
  • Haiku 4.5, 200K tokens
  • Opus 4.7, 1M tokens

The window holds everything the model sees on a single API call: base system prompt + CLAUDE.md + loaded skills + memory + conversation history + tool results + this turn's message. When the sum approaches the limit, autocompact kicks in: the harness summarizes earlier turns to free space. You stay below the ceiling but you lose fidelity.

So "context fills up too fast" really means: the things you've put in the window are accumulating faster than you've stopped putting them in.

What actually fills the window

I learned the hard way: context doesn't fill because a conversation is long. It fills because some kinds of content are expensive.

System 10K
Mem 3K
Skl 2K
History 15K
Tool results 50K
Available 120K
0 50K 100K 150K 200K (Sonnet max)

Typical Claude Code turn. Tool results usually dominate, system overhead is fixed; Reads, Bash output, and WebFetch grow with what you ask for.

In a typical turn:

  • Base system prompt: vendor preamble + tool definitions for all built-in tools + MCP tool definitions. ~5–15K tokens, mostly fixed.
  • CLAUDE.md + memory files: your project instructions + auto-memory. ~1–5K tokens.
  • Loaded skills: when a skill's description triggers, its full content gets concatenated. ~100–1000 tokens per skill.
  • Conversation history: your messages and Claude's responses. Grows linearly with turns.
  • Tool results: almost always the biggest line item:
    • Read of a 1000-line file ≈ 5–10K tokens
    • A noisy bash command (logs, builds) → 10–100K tokens
    • A web fetch → 5–50K tokens depending on the page
    • An image attachment ≈ 1–2K tokens

One careless cat huge-log.txt, one verbose npm install, one fetched page, half your window is gone.

Assembly: how the system prompt gets built

Every API call, the harness concatenates many sources into a single system message.

Vendor preambleClaude Code's built-in instructions
CLAUDE.mdwalked up the directory tree
Memory filesMEMORY.md + auto-memory entries
Available subagentsnames + descriptions for delegation
Loaded skillsskills whose triggers match
Tool definitionsbuilt-in + MCP
System message
sent on every API call

Every API call, the harness glues these sources together into one system message. CLAUDE.md changes apply on the next turn, not the next session.

Two consequences worth banking:

  1. CLAUDE.md changes take effect on the next turn: not the next session. It's re-loaded and re-concatenated every call.
  2. The full system prompt is re-sent every turn. Nothing is stored on Anthropic's side persistently. Which is why the next piece matters.

The accelerator: prompt cache

Without optimization, every turn would reprocess the entire system prompt + history from scratch. The prompt cache fixes this:

  • Anthropic's inference servers cache intermediate computations (attention key/value states) for the prefix of your messages.
  • When you re-POST the same prefix on the next turn, the servers reuse the cached work and only compute the new part.
  • Cache hits are ~10x cheaper and noticeably faster than cold processing.
System
prompt
msg 1
asst 1
msg 2
asst 2
new msg
Cached prefix, server reuses prior computation New tail, full processing

The cache works on prefixes. Each turn extends the prefix; only the new tail is full-price. Appending at the end keeps the cache warm; changing anything early invalidates everything after.

Two practical implications:

  1. Cache TTL is ~5 minutes by default. Pause longer and the next turn is full-price.
  2. Order matters because cache is prefix-based. Append at the end → cache warm. Change something early → cache busted from that point on.

What this means for "context fills up too fast"

After running out of context too many times, these are the moves I learned:

  • Tool results are usually the culprit. Be deliberate about what you Read, Bash, or Fetch. A big file or noisy command eats tens of thousands of tokens in one shot.
  • Subagents save your main context. Letting an Explore agent read 50 files in its window and return a 200-word summary means your main thread pays for 200 words, not 50 files. Massive leverage when used right.
  • Memory and CLAUDE.md beat re-explanation. Durable facts get re-injected automatically every session, so they don't fight for chat-history budget.
  • /clear between phases. Memory and CLAUDE.md still load, so durable context survives.
  • Cache awareness. Sessions active within 5-minute gaps stay cheap. Long pauses cost.
  • Bigger window ≠ free space. Opus 4.7's 1M context is "more room" not "free room." You still pay per token. Pick the window to fit the workload, not the biggest by default.

Tools and tool calls

The model doesn't do anything. It only recommends actions. Tool calls are how those recommendations become real work.

What a tool definition actually is

A tool definition is a JSON schema with three parts: a name, a description, and an input schema:

{
  "name": "Read",
  "description": "Reads a file from the filesystem and returns its contents with line numbers, up to 2000 lines...",
  "input_schema": {
    "type": "object",
    "properties": {
      "file_path": { "type": "string", "description": "The absolute path..." }
    },
    "required": ["file_path"]
  }
}

The harness sends a list of these to the API on every turn alongside the messages. The model sees only this: name, description, and parameter schema. It does not see the implementation. It doesn't know Read opens a file handle and slurps content. All it knows is what the description says it does.

The load-bearing insight: a tool's description is a prompt to the model, not documentation for humans. Vague description → the model can't tell when to reach for it. Specific, action-verb-led description → clear matching.

The tool call lifecycle

1
Model emits tool_use block
Structured intent: { name, input }
2
Harness intercepts
Looks up the tool in its registry
Permission required?
Yes
Ask user (blocking)
If approved → continue
No (pre-approved)
Skip → execute directly
3
Execute the tool
Subprocess, API call, file I/O, whatever the tool actually does
4
Package tool_result
{ content } on success, or { error: '...' } on failure
5
Append to message list, re-POST
Full conversation re-sent on next API call
6
Model sees result, continues
May emit text, call another tool, or finish the turn

The model recommends; the harness executes. The model never touches your filesystem; it just emits structured intent the harness translates into action.

How the model picks a tool

When you send a message, the model sees: system prompt + conversation history + tool definitions + your new message. Three things shape the choice:

  1. Description match. The model compares your intent against each tool's described purpose. Specific descriptions match better than abstract ones.
  2. Input schema fit. If your request maps cleanly onto one tool's required parameters, that biases the model toward it.
  3. Context examples. If the conversation has used Bash 10 times for similar tasks, the model is more likely to use Bash again. Patterns reinforce themselves.

"Picks the wrong tool" is almost always one of:

  • A description too vague to disambiguate
  • Two tools with overlapping descriptions
  • A tool the model doesn't realize is available (description doesn't match your phrasing)

The fix in all three cases: rewrite the description.

Parallel tool calls

Modern Claude can recommend multiple tool calls in a single response. The model decides which calls are independent and emits them as a batch. The harness executes concurrently and returns all results before the next inference step.

Serial — 3 round-trips

4 model calls · 3 sequential executions
Model call 1asks for tool A
Tool A executes
Model call 2asks for tool B
Tool B executes
Model call 3asks for tool C
Tool C executes
Model call 4final response

Parallel — 1 round-trip

2 model calls · 3 concurrent executions
Model call 1emits 3 tool_use blocks
Tool A
Tool B
Tool C
Model call 2final response

Serial costs 4 model calls and sequential tool execution. Parallel costs 2 model calls and concurrent execution. The round-trip and wall-clock win compounds with more tools.

The model has to recognize independence. "Read A, then based on it, read B" can't parallelize; there's a dependency. "Read A, B, and C" can. You can nudge by phrasing requests as "in parallel" or "independently"; the model picks up the cue.

When tools fail

A tool call can fail: file not found, command exits non-zero, network error. The harness packages the failure into the tool_result with an error indicator. The model gets that error on the next turn and decides what to do: retry, switch tools, or ask you.

The error message you write into a tool's failure path matters. A tool result of "error: failed" tells the model almost nothing. A tool result of "error: file not found at /path/X. Check with ls" gives the model concrete next steps.

This is why MCP servers and custom tools you build need good error surfaces. The harness can't help if the tool itself returns junk in its error message.

Descriptions as the discovery surface

The connection across the agent stack: the model only knows what your descriptions tell it. This applies to:

  • Built-in tools: Anthropic writes them; you can't change. (Read, Edit, Bash, Glob, Grep, WebFetch.)
  • MCP tools: you write the server; you control the description. Good descriptions → the model uses your tool. Vague descriptions → it gets ignored.
  • Skills: same principle. The skill description triggers loading. (More in the next section.)
  • Custom subagents: your subagent description is how the parent decides when to delegate.

The "I built a thing and the model never uses it" problem is almost always a description problem.

Subagents

A subagent is the same Claude model running in a fresh sandbox (its own context window, its own system prompt, its own toolbelt), spawned by the parent to handle a delegated piece of work. The parent pays only for the summary the subagent returns, not the work the subagent did.

This is one of the highest-leverage productivity tools in Claude Code, and it's underused because the model rarely volunteers. You usually have to know when to ask.

What a subagent actually is

Mechanically, a subagent is:

  • A fresh Claude inference loop, started by the harness in a child execution context
  • Its own context window (empty at start except for the subagent's system prompt + your task prompt)
  • Its own toolbelt (often restricted, e.g., Explore has only read tools)
  • A single return value: one string the parent gets back as the result of a Task tool call

The parent doesn't see the subagent's intermediate turns. It only sees the final summary. From the parent's perspective, a subagent call is just another tool call, one that happens to take longer and return prose instead of file contents.

The delegation handoff

When the parent decides to delegate:

  1. Parent emits a Task tool_use block: { subagent_type, description, prompt }
  2. Harness recognizes Task, starts the child Claude with the named subagent type
  3. Child loads its own system prompt (subagent definition + the prompt passed in)
  4. Child runs its own loop, tool calls, file reads, reasoning, using its context window
  5. Child returns a single string (its final message) as the tool_result
  6. Parent gets that string, integrates it, continues
Parent Claude
Main conversation, you talk to this thread
↓ Task tool call · ↑ summary string
Explore (built-in)
"Find references to X"
fresh context · 50 file reads
↑ ~30 tokens back
custom subagent
"Review this draft"
fresh context · reads draft + refs
↑ ~100 tokens back
custom subagent
"Audit references"
fresh context · grep + glob
↑ ~50 tokens back

The parent's context stays clean; each child's context absorbs its own work and evaporates after the call returns. Only the summary survives.

Why isolation matters: the token economics

The moment subagents clicked for me: a child's context evaporates after the call returns. That asymmetry is where the leverage lives.

Without subagent

Parent reads 50 files itself
Parent context after the work
~100K tokens
All 50 file contents now live in the parent's conversation history. Heavy. Autocompact looms.

With Explore subagent

Subagent reads 50 files in its sandbox
Parent context after the work
+30
Only the summary returns to the parent. The subagent's context (which absorbed all 50 file reads) evaporates after the call.

Without subagent: 50 file contents live in the parent's conversation forever. With subagent: only the summary survives; the child's context evaporates.

A worked example:

  • Without subagent: Parent reads 50 files to find a pattern. Parent's context now contains 50 file contents ≈ 50–200K tokens. Heavy. Autocompact looms.
  • With subagent: Parent asks Explore: "Find all references to X." Explore reads the 50 files in its context, returns: "X is referenced in src/auth.ts:42, src/api.ts:118, and src/utils/jwt.ts:7. The auth.ts version is canonical." Parent's context grows by ~30 tokens.

The savings compound when:

  • The work involves many file reads (research, audits, codebase exploration)
  • The work involves heavy WebFetches
  • The work has many branching tool calls but a small final answer

This is also why "I ran out of context" is often solvable structurally, by delegating the biggest reads to subagents instead of pulling them into the main thread.

Reading vs. writing subagents

Two flavors that matter:

  • Read-only subagents (like Explore): restricted to read tools. Safe to spawn liberally for research. Can't modify state. Pattern: "Go look at a bunch of stuff and tell me what you found."
  • General-purpose subagents: full toolbelt, can edit files, run commands, modify state. Powerful but riskier. Pattern: "Do this whole subtask and report back."

Most context-saving leverage comes from read-only subagents. Most autonomy leverage comes from general-purpose subagents.

Custom subagents

Like skills, you can define your own. A subagent definition lives in .claude/agents/<name>.md and includes:

  • A name (the slug)
  • A description (the discovery surface, same principle as tools and skills)
  • A model (Haiku for cheap research, Sonnet for complex work, Opus for the hardest)
  • A toolbelt (which tools the subagent can use, restrict aggressively)
  • A system prompt (custom behavior instructions)

The parent sees the subagent's description and decides when to delegate. As with tools, the description is the discovery surface. Vague description means the parent never invokes it.

When NOT to use subagents

Subagents have overhead: spawning a child is a real round trip. They're the wrong tool for:

  • Short, fast tasks (don't pay overhead for a 3-token answer)
  • Tasks where the parent needs the intermediate context in its thread (delegation hides it)
  • Iterative back-and-forth (each turn is a new spawn, expensive)
  • Tasks where you want to learn the steps (you only see the summary)

The leverage rule: delegate when the work is read-heavy and the answer is short. That's the sweet spot.

Skills

A skill is procedural knowledge the model can load into its context on demand. Unlike a tool (a callable function) or a subagent (a separate Claude process), a skill is a text artifact that gets injected into the parent's system prompt when its description matches. The model then reads the skill body and follows the instructions inline.

This is where the "picks the wrong skill" frustration lives, and almost always, the fix is the same as for tools and subagents: the description.

What a skill actually is

Mechanically, a skill is a markdown file at .claude/skills/<skill-name>/SKILL.md with YAML frontmatter:

---
name: my-skill
description: Use when [trigger phrases]. Does [what it does].
---

[Body content with instructions, examples, reference material]

That's the whole thing. Two fields in the frontmatter (name, description), plus a body. The body can be a paragraph or a full procedure with sub-headers, code snippets, references.

A skill can also bundle supplementary files alongside SKILL.md: scripts, templates, examples. These don't auto-load; they're referenced from the body and read on demand.

.claude/skills/<skill-name>/
SKILL.md
name + description
always loaded · ~150 tokens
procedure, examples, references
loaded on demand · ~500-3000 tokens
scripts.sh
referenced from body · read when needed
reference/
supplementary material · read on demand

Skill anatomy. Frontmatter is always loaded (cheap); body loads on demand when the description matches. Bundled files are referenced from the body and read only when the procedure calls for them.

Progressive disclosure — the load-bearing mechanic

This is the most important thing to internalize about skills, and the source of most "why didn't my skill trigger?" confusion.

Skills use two-phase loading:

Phase 1 (always): For every installed skill, only the name + description are loaded into the system prompt. The body is NOT loaded. The harness puts a compact list, descriptions are typically 100–200 tokens each. Even 20 skills cost ~3K tokens.

Phase 2 (on demand): When the model decides a skill is relevant (based on the request and the descriptions it sees), it calls a tool to load that skill's full content into context. Only then does the body become available.

Phase 1 Always loaded, cheap
  • diagramsuse when creating, editing, reviewing diagrams…
  • publication-safetyuse when preparing repo for public sharing…
  • verifyverify changes by running the app…
~150 tokens each · ~500 total for 3 skills
User: "draw a diagram of the system"
Model matches diagrams description → triggers body load
Phase 2 On demand, full body
diagrams body now loaded
Mermaid syntax, color palette, anti-patterns, examples, render commands…
+1500 tokens (just for the matched skill)

Progressive disclosure. Phase 1 is cheap: just descriptions. Phase 2 loads the full body only when the description matches what the user asked for. This is why descriptions matter more than skill content for triggering.

This is why descriptions matter so much. The model is doing pattern matching between what the user asked and what each skill's description says. If the description doesn't surface naturally from the user's phrasing, the skill won't load, even if its body would handle the request perfectly.

Why skills don't trigger when you expect

Almost always one of these:

  1. Description uses different language than the user. User says "make a chart"; description says "produces visualizations." Semantic matching is good but not magic.
  2. Description is too vague. "Handles various data tasks" doesn't trigger on anything specific.
  3. Description doesn't include trigger phrases. Action-verb-led, scenario-led descriptions trigger better than abstract definitions.
  4. Competing skills with overlapping descriptions. The model picks one, maybe the wrong one.
  5. Skill is out of scope. A project skill only loads when working in that project; user skills load everywhere.

Diagnostic: read the description like the model would. Does it specifically tell the model when to use it? Does it use the language users would use?

Description engineering

Good description structure:

description: Use when: [trigger scenarios]. The skill [what it actually does].
[Optional: distinguishing factors vs related skills]

A well-engineered example (the diagrams skill):

description: Use when: creating, editing, or reviewing technical diagrams;
the user mentions diagram, mermaid, visualize, topology, flow chart,
pipeline picture, or asks to draw a system, a data path, or a cold/warm path.

What makes this work:

  • Lists multiple trigger words (diagram, mermaid, visualize, topology…)
  • Includes scenario phrases ("asks to draw a system")
  • Front-loads the trigger conditions ("Use when:") so the model can pattern-match fast
  • Doesn't bury triggers in prose

Bundled resources — when skills get bigger

Skills can come with companion files in the same directory:

.claude/skills/aihomelab-lustre-cluster/
├── SKILL.md
├── lustre-bringup.sh
└── reference/
    └── lustre-modules-checklist.md

The body of SKILL.md references these. The model reads SKILL.md and follows the procedure, reaching for the companion files only when the procedure calls for them.

This keeps the loaded body lean: only the procedure loads, not the supporting material.

Skill scope: user / project / built-in

Three scopes, each with implications for when a skill is available:

  • Project skills: <project-root>/.claude/skills/. Load only when working in that project. Good for project-specific procedures.
  • User skills: ~/.claude/skills/. Load in every project on your machine. Good for cross-cutting procedures (diagrams, publication-safety).
  • Built-in skills: ship with Claude Code, always available (verify, simplify, init, etc.).

Scope matters because triggering happens against currently loaded descriptions. A user skill is invisible when working from a machine that doesn't have it installed.

MCP

MCP (Model Context Protocol) is the standardized way to add new tools to any harness. MCP servers expose tools that the harness routes the model's calls to. This section goes a layer deeper: the protocol, the deferred-tools optimization, and when you'd actually build one.

What MCP actually is

MCP is an open protocol that defines:

  • A standardized way for a "server" (your code) to expose tools: each with a name, description, input schema (same shape as built-in tools)
  • A standardized way for a "client" (the harness) to discover and invoke those tools over a transport (stdio for local processes, HTTP for remote services)
  • Lifecycle hooks: initialization, capability negotiation, resource listing, prompt templates

For the harness, an MCP tool is indistinguishable from a built-in tool: same tool_use structure, same tool_result flow. The harness just routes the call to the right MCP server instead of executing locally.

For your code (the MCP server side), you implement a small program that:

  1. Speaks the MCP protocol on stdio or HTTP
  2. Declares what tools it offers (name + description + schema)
  3. Receives tool invocations and returns results

That's the whole contract. It works the same way across Claude Code, Cursor, Cline, custom SDK harnesses, anything that implements an MCP client.

Model
sees one unified tool list, built-in + MCP indistinguishable
Harness (Claude Code)
Built-in tools
Read · Edit · Bash · Glob · Grep · WebFetch
MCP client
routes calls to MCP servers
computer-use stdio · local
screenshot · click · type · scroll · 22 more
company-jira HTTP · remote
jira_search · jira_create · jira_update · …

MCP architecture. From the model's perspective, built-in tools and MCP tools look identical: same call shape. The harness routes calls to the right server using stdio for local servers and HTTP for remote ones.

Why MCP exists

Before MCP, every harness had its own way of adding extension tools. Cursor had Cursor's way; Claude Code had Anthropic's way; custom SDK code had to roll its own. If you wrote a "talks to GitHub" integration for one harness, you'd have to rewrite it for another.

MCP standardizes the contract. Now:

  • You write one MCP server for "talk to GitHub"
  • It works in Claude Code, Cursor, any MCP-aware harness
  • You don't rewrite when you switch tools
  • The community can share servers: github-mcp, slack-mcp, postgres-mcp, filesystem-mcp

This is the agent-tool equivalent of LSP (Language Server Protocol) for IDEs: same standardization story.

Transports: stdio vs HTTP

Two ways the harness connects to an MCP server:

  • stdio (local): harness spawns the MCP server as a child process and pipes messages over stdin/stdout. Fast, secure, no network. Used for tools that need local access (filesystem, local services, computer-use).
  • HTTP (remote): harness connects to an MCP server over the network. Used for SaaS integrations or shared company services. Authentication happens at the HTTP layer.

You configure each MCP server in your settings: a command + args for stdio, a URL for HTTP.

Deferred tools — same progressive-disclosure trick

Some MCP servers expose many tools. A computer-use server has 26 tools. If every MCP tool's full schema were loaded into every conversation, the system prompt would balloon.

The solution: deferred tools. The harness only loads the names of MCP tools into the always-available list, not the full schemas. When the model wants to use one, it first calls a ToolSearch to fetch the actual schema, then calls the tool.

Phase 1 Always loaded, names only
  • mcp__computer-use__screenshot
  • mcp__computer-use__click
  • mcp__computer-use__type
  • … 23 more deferred names
~5 tokens per name · ~130 tokens for 26 tools
1 Model wants to use click, calls ToolSearch("click")
2 Harness returns the full schema for that tool
3 Model now has the schema, calls the tool with proper arguments
Phase 2 Schema fetched on demand
click full schema (fetched)
params: { x: int, y: int, button: enum }
… full description and constraints
+~150 tokens (just for the one used tool)

Deferred tools: same progressive-disclosure pattern as skills. Names are cheap to keep around (~5 tokens each); full schemas load only on demand via ToolSearch.

This is why having a big MCP server like computer-use connected doesn't blow up your context budget.

When to build an MCP server

The question to ask: does Claude need access to a capability the built-in tools can't provide?

Good MCP server candidates:

  • Talk to your company's internal API (Jira, Confluence, internal services)
  • Query your database with proper authentication
  • Drive a specific external tool (Figma, Linear, your CI system)
  • Wrap a complex CLI tool with structured I/O instead of raw shell parsing

Not-MCP-server candidates (often there's an easier way):

  • "Run a shell command" → Bash already does this
  • "Read a file" → Read already does this
  • "Search the web" → WebFetch for one page, or an existing search MCP server
  • "Write a procedure the model should follow" → that's a skill, not an MCP server

Ecosystem

There's a growing ecosystem of community MCP servers: GitHub, Slack, Postgres, filesystem, browser automation, search, and many more. Most are open source. You install them like any other dependency and configure your harness to connect.

The bottom line

MCP is the extension mechanism for adding capabilities Claude doesn't otherwise have. It's a protocol, not a feature.

  • "Claude needs to be able to do X" where X is a new external interaction → MCP
  • "Claude needs to follow this procedure" → skill
  • "Claude needs to delegate this isolated work and report back" → subagent

We'll formalize the full decision tree in the next section.

Picking the right primitive

You now have four ways to shape what Claude can do:

  • Tool: a callable function the model invokes
  • Skill: procedural knowledge the model loads into context when triggered
  • Subagent: a fresh Claude process spawned to do isolated work
  • MCP server: protocol for exposing new tools (a tool factory, essentially)

They look similar from a distance. They're not. Each occupies a specific role, and choosing the wrong one is the source of most "I built this and it doesn't work" frustration.

Comparison at a glance

Tool Skill Subagent MCP server
What it is Function the model calls Text injected into parent's prompt Fresh Claude with isolated context Protocol for exposing tools
Where it lives Harness (built-in) or via MCP .claude/skills/ .claude/agents/ Separate process or service
What it returns Data (tool_result) Nothing (influences parent inline) A summary string Data (tool_result)
Cost idle Definition in tool list ~150 tok per description ~150 tok per description ~5 tok per name (deferred)
Cost active Same as idle +500–3000 tok (body) Just the summary +~500 tok (schema)
Best for Atomic capabilities Procedures with user decisions Read-heavy isolated work Adding new capabilities

The decision tree

Start at the top and answer each question:

Q1 Need to ADD a capability Claude doesn't have?
└ YES → Build an MCP server
└ NO →
Q2 Bounded + autonomous work? (no user decisions mid-way)
└ YES → Spawn a subagent
└ NO →
Q3 Follow a procedure with user interaction?
└ YES → Write a skill
└ NO → Use a built-in tool

Walk top-down. First question that returns YES wins. Most "I built the wrong primitive" mistakes happen when Q1 and Q2 get mixed up.

Anti-patterns

Skill where you needed a subagent. You wrote a skill: "Read every file under src/ and find references to function X." The skill loads, the parent runs Read 50 times in the main thread, context bloats. → Should have been a subagent with a small return.

Subagent where you needed a skill. You wrote a subagent that walks the user through a deployment checklist: "Did you check X? What's the staging URL? Should we tag the release?" Subagents can't easily interact with the user mid-flight. → This is a skill.

MCP server where you needed a skill. You built an MCP server with a single tool called publication-checklist that returns a multi-step procedure. → A procedure isn't a capability; it's a skill. MCP is for external capabilities, not for stored checklists.

Tool where you needed an MCP server. You're embedding "talk to our company's Jira" as ad-hoc Bash + curl in many sessions. Every session re-invents the wheel. → The structured, schema-typed version belongs in an MCP server.

The complementary pattern

Real workflows often use all four primitives composed together. Here's a hypothetical "publish a release" workflow:

Example: "Publish v2.4" workflow
1
User
"Let's publish v2.4"
2
Skill
release-checklist triggers; parent now has the procedure + user interaction
3
Built-in
Edit bumps version in package.json
4
Subagent
changelog-auditor reads 200 commits, returns 5-line gap report
5
MCP tool
slack__post_message announces the release
6
MCP tool
deploy__mark_released updates the dashboard
7
Skill
Reports completion to user

Composition pattern. Each primitive plays its specific role. Skill orchestrates; subagent does isolated research; MCP provides external capabilities; built-ins handle local actions.

Each primitive plays its role:

  • Skill is the orchestrator: knows the steps, knows when to invoke other primitives, interacts with the user
  • Subagent does bounded research (200 commits → 5-line report, context isolation)
  • MCP servers provide capabilities Claude doesn't natively have (Slack, deployment dashboard)
  • Built-in tools handle local stuff (edit version number, read files)

They don't compete; they complement. Skills tell the parent when to use subagents and tools. Subagents do isolated work. MCP adds new capabilities. Tools are the actual function calls.

"Name the primitive" exercise

For each scenario, the right primitive:

Scenario Primitive
"When the user mentions security review, walk them through these 8 checks"Skill
"Search all 500 files for instances of deprecated API X"Subagent (read-heavy, small answer)
"Let Claude post to our team's status board"MCP server (new capability)
"Read the contents of this file"Tool (built-in Read)
"Audit 30 published artifacts for stale links, report which need updates"Subagent
"Provide a hard-gated procedure for promoting drafts to public"Skill
"Connect Claude to our Postgres for schema queries"MCP server

The unified lesson

Across all four primitives, the same pattern shows up:

  • Tools rely on a clear description for the model to know when to call them
  • Skills rely on a clear description for triggering
  • Subagents rely on a clear description for delegation
  • MCP server tools rely on a clear description (same as built-in tools)

Descriptions are the universal discovery surface. When something you built doesn't get used, the first investigation is always the description, across all four primitives.

Memory, hooks, permissions, plan mode

These are the governance layer: the four knobs that let you shape how Claude Code behaves across sessions, what it can do without asking, when it intervenes deterministically, and how it stays in "design" mode before acting. None of these add capabilities the way MCP or skills do. They shape what's allowed, what's remembered, and what triggers automatically.

This section is shorter than the primitives sections; these are conceptually simpler, but they're the difference between a harness that just works and one that's calibrated to you.

(Other tools have analogous concepts under different names: Cursor's rules system, Cline's custom instructions, similar patterns in agent SDKs. The soft/hard distinction below is what travels.)

Memory system prompt
Shapes what Claude knows across sessions. CLAUDE.md + auto-memory files concatenated into the system prompt every turn.
Hooks event boundary
Deterministic event handlers run by the harness. Fire on UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionStart.
Permissions tool boundary
Allow / Ask / Deny per tool or pattern. The load-bearing safety layer, every tool call passes through here first.
Plan mode session mode
Read-only design mode. Model can think and search; cannot write or run side-effecting commands until approved.

The four governance knobs operate at different layers of the session, memory in system-prompt assembly, hooks at event boundaries, permissions at tool boundaries, plan mode at the session level.

Memory — durable facts across sessions

Skills and CLAUDE.md cover within-session and within-project context. Memory covers cross-session, longer-lived facts the model should know on every interaction.

Two mechanisms in Claude Code:

  • CLAUDE.md files, project-level instructions loaded automatically when working in that directory. Walked up the directory tree (so src/CLAUDE.md + project root CLAUDE.md both apply when editing under src/). Best for project conventions, architecture notes, "always do X here" rules.
  • Auto-memory: Claude can save and recall durable facts about you (preferences, work style, recurring projects) at ~/.claude/projects/<project-slug>/memory/. Each entry is its own markdown file with name, description, content; MEMORY.md indexes them.

Memory files get concatenated into the system prompt on every turn (same assembly mechanic from earlier). They cost tokens but don't fight for conversation budget.

When to add a memory entry vs CLAUDE.md vs skill:

  • Fact about how you personally work (preferences, conventions) → auto-memory
  • Fact about this specific project (architecture, where things live) → CLAUDE.md
  • Procedure to follow when a specific trigger fires → skill

Hooks — deterministic event handlers

A hook is a shell command the harness runs at specific event moments. Not a model decision; the harness fires hooks deterministically when configured events happen.

The hook events I reach for most (the Claude Code docs have the full list):

  • PreToolUse: fires before a tool runs (can block the call)
  • PostToolUse: fires after a tool returns (can inspect/modify the result)
  • UserPromptSubmit: fires when you send a message (can inject context, redirect)
  • Stop: fires when Claude finishes a turn (good for notifications)
  • SessionStart: fires when a session begins (good for setup)

Others worth knowing exist for subagent stops, pre/post-compaction, session end, and desktop notifications.

Session starts
SessionStart
Waiting
User sends prompt
UserPromptSubmit
Model inference
Model emits tool_use
PreToolUse
Tool executes
PostToolUse
Model continues
Turn ends
Stop

Hook event timeline. Each amber band is a deterministic firing point where the harness can run a shell command: block a tool, inject context, format output, notify, etc.

Hooks are configured in your settings as { event, matcher, command }. The harness runs the command, reads the exit code and stdout, and acts accordingly.

What hooks are good for:

  • Safety enforcement: block writes to ~/secrets/ regardless of what the model decides
  • Auto-formatting: run prettier after every Edit
  • Notifications: ping a desktop notification when a long task finishes
  • Context injection: append current git status to every prompt
  • Deterministic automation: "every time the agent stops, run gitleaks"

Hook vs CLAUDE.md instruction: an instruction in CLAUDE.md is a suggestion the model may or may not follow. A hook is enforcement: the harness runs it regardless of model intent. Use hooks for safety/security; use CLAUDE.md for conventions you can live without 100% compliance on.

Permissions — what's pre-approved

Every tool call the model wants to make goes through the permissions engine. Three states per tool/pattern:

  • Allow: auto-approved, no prompt
  • Ask: prompts you each time
  • Deny: blocked outright

You configure these per-tool, per-pattern. Examples:

  • Read → allow (reading files is safe)
  • Bash(npm:*) → allow (npm commands pre-approved)
  • Bash(rm -rf:*) → deny (never)
  • Edit(~/secrets/*) → deny (never touch secrets)
  • Bash(*) → ask (any other shell command requires approval)

Permission patterns use the same matching mechanic as hooks. Specific patterns take precedence over general ones.

The permission engine is the load-bearing safety layer. Skills, hooks, and the model itself all sit behind permissions. Even if a skill says "now run rm -rf /," the permission engine intercepts before the tool runs.

The opposite extremes ("allowlist everything" vs "ask for every command") both have failure modes. Allowing everything trades safety for speed; asking everything makes work tedious. Most productive users converge on a middle ground: allow common safe operations, ask for everything that touches shared state or is hard to reverse.

Plan mode — design before doing

Plan mode is a harness-level state where:

  • The model can read, think, search, ask questions
  • The model cannot write to any file except a designated plan file
  • The model cannot run side-effecting commands
  • At the end, the model presents a plan, and you approve before execution begins

It's a "dry run" mode that scales to complex tasks. Useful when:

  • You want the model to think through a task before acting
  • The task spans many files or has irreversible steps
  • You want to vet the approach before any side effects
  • You want the planning artifact preserved as documentation

Enter via the /plan command or certain workflows. Exit with ExitPlanMode (the model calls this when the plan is ready). Your approval starts execution.

How they fit together

These four knobs operate at different layers of the session:

Knob Layer Acts on
MemorySystem promptWhat Claude knows across sessions
HooksEvent boundaryWhat the harness does deterministically
PermissionsTool boundaryWhat gets approved without prompting
Plan modeSession modeWhat the model can do (read-only design vs. full execution)

They compose: memory shapes Claude's defaults; hooks enforce deterministic behavior; permissions gate every action; plan mode constrains the whole session. Together they let you dial in how Claude Code behaves to match your taste and your safety bar.

Productivity patterns

The final synthesis. Nothing new mechanically: just how to combine the primitives and governance knobs into a usable daily playbook.

Context budgeting — the highest-leverage habit

The single biggest productivity gain comes from treating context as a budget, not an unlimited resource:

  • Watch tool results. They're ~90% of "where did my context go?" One careless cat huge-log.txt or one verbose npm install can eat 50K tokens.
  • Delegate heavy reads to subagents. Anything more than ~5 file reads to find an answer is a candidate for an Explore subagent. Subagent reads 50 files in its context, returns a 30-token summary, your main thread saves ~99% of the cost.
  • Move durable facts to memory. If you're re-explaining the same context across sessions, that's a memory entry waiting to be written.
  • /clear between phases. Memory and CLAUDE.md still load; durable context survives.
  • Cache awareness. Don't let conversations cool beyond ~5 minutes between turns if you can help it.
  • Run /context when something feels heavy. It tells you exactly what's eating budget.

Description-first design

When you build something (a skill, a custom subagent, an MCP tool), the description determines whether it's ever used. Workflow:

  1. Write the description first: before the body/implementation
  2. List 3–5 user phrases that should trigger it. Paste actual things you'd say
  3. Front-load Use when:, the model treats this as a strong signal
  4. Distinguish from neighbors: what else might match? Why is this one right?
  5. Test it: try invoking it, see if it fires. Iterate on description more than body.

When a primitive doesn't fire as expected: edit the description first. Rewrite the body only after the description fix fails.

Picking the right primitive (recap)

  • New capability Claude doesn't have → MCP server
  • Bounded autonomous work, small answer → Subagent
  • Procedure with user interaction → Skill
  • One-off action → built-in tool

If you're tempted to build a custom subagent that needs user input mid-flight, you wanted a skill. If you're tempted to write a skill that reads 50 files autonomously, you wanted a subagent.

Soft vs hard governance — pick the right mechanism

For each requirement, ask: "if the model occasionally ignored this, would it matter?"

Stakes Mechanism
Safety, security, reversibility-criticalHard: permissions deny, hook block
Conventions, style, defaultsSoft: CLAUDE.md, skill
Cross-session personal preferencesMemory
One-time per-session constraintPlan mode

Default failure mode: putting safety-critical rules in CLAUDE.md (soft). They'll be violated. Move them to hard mechanisms.

Session hygiene

  • /clear between unrelated phases. Each phase starts fresh; memory + CLAUDE.md persist.
  • Switch models to fit the task. Haiku for cheap read-heavy research subagents; Sonnet as the workhorse; Opus when the task demands deep reasoning. Same harness, different brain.
  • Plan mode for high-blast-radius work. Multi-file refactors, auth/security/migration work.
  • Pause-and-resume discipline. When stepping away mid-task, write down resume state explicitly. Don't rely on memory of where you were.

Diagnostic flow — when something misbehaves

Knowing which layer to investigate saves more time than any single fix.

Symptom
Layer to investigate
First action
Custom primitive not used
Harness · description
Rewrite the description before the body
Buggy code output
Model
Different model, more context, sharper prompt
Context filled up too fast
Structural · context
/context → find heavy tool result → subagent, /clear, or memory
Slow response after pause
Cache
Cache cooled (>5 min). Tolerate or avoid long pauses
Hook not firing
Harness · config
Check settings.json hook matcher
Tool blocked or prompted
Harness · permissions
Check allow/ask/deny patterns in settings.json

Diagnostic reference. Identify the symptom → know which layer to investigate → take the first action. The model vs. harness diagnostic, applied to common situations.

What to invest in over time

Small effort now, big payoff later. These compound:

Effort now (small)
Description engineering for skills + subagents
Memory entries for cross-session facts
Focused CLAUDE.md per project
1–2 well-chosen hooks for safety
Curated permissions allow/deny list
Payoff over months (big)
Skills fire reliably; no re-prompting
Cross-session context for free
Claude knows your project conventions
Automated guardrails always on
Friction-free common operations

Compounding investments. Each is small effort now; each saves cognitive load on every future session. Six months in, the difference between "I built nothing" and "I tuned these" is orders of magnitude in iteration speed.

  1. Description engineering for your skills and custom subagents. The single highest-leverage investment.
  2. Memory entries for cross-session facts. Each one saves re-explanation in every future session.
  3. A focused CLAUDE.md per project: project conventions Claude should always know.
  4. One or two well-chosen hooks for safety automation (path guards, formatters, security scans).
  5. A permissions allow/deny list that matches your real workflow. If you've chosen to keep prompts on as a learning posture, keep that; otherwise codify common-safe operations once you know them.

The unified lesson

Across everything covered:

  • Model vs harness: different layers, different fixes
  • Descriptions are the universal discovery surface: across tools, skills, subagents, MCP
  • Tool results dominate context: be deliberate about Reads, Bash, WebFetch
  • Soft vs hard governance: match the mechanism to the stakes
  • Composition over piling on: pick the right primitive, don't reach for the same hammer

The harness is a configurable system, whichever one you're using. Most "I'm fighting this tool" frustrations resolve once you know which knob to turn for which problem. The specifics differ across Claude Code, Cursor, Cline, Codex, Antigravity, and custom-built agents, but the mental model travels. Now you have it.