How to use this course
Modules are cumulative and ordered deliberately: you learn to use each mechanism before you learn to build it, and you build before you govern. Skipping ahead mostly works, but Modules 7–17 assume 0–6.
Every module carries a time estimate split into read and practice, sized for someone learning this for the first time and actually doing the labs. Each ends with a Lab — do it, the labs are where the learning is — and a gated Checkpoint.
How the checkpoints work
- Questions are drawn at random from a larger bank and the answer options are reshuffled every attempt — you can't pass by memorising "it's the third one".
- You need 80% to pass (85% on the capstone and the certification exam).
- Every question shows an explanation on both right and wrong answers, so a checkpoint is a teaching pass, not just a score.
- Fail and you get a breakdown of which areas you missed plus a retake with different questions. Retakes are unlimited.
- The next module stays locked until you pass — with an "unlock anyway" escape hatch if you deliberately want to skip ahead.
- Pass every module checkpoint to unlock the final certification exam: 30 questions across all twenty-two modules, 85% to pass, printable certificate.
The standard the questions are written to: if a colleague asked you about any of this in a meeting, you could answer without looking it up.
Prerequisites: a paid Claude plan (Pro/Max/Team/Enterprise), the Claude desktop app, Python 3.10+, Node 18+, git, and a terminal you're comfortable in.
Course map & time budget
| # | Module | Read | Practice | Total |
|---|---|---|---|---|
| Tier 0 · Orientation — build the mental model (40m) | ||||
| 0 | The landscape: what all these things are | 20m | 20m | 40m |
| Tier 1 · Foundations — use Claude well before extending it (3h 25m) | ||||
| 1 | Claude Desktop: Chat, Cowork, Code | 20m | 45m | 1h 5m |
| 2 | Context engineering & prompting that scales | 30m | 35m | 1h 5m |
| 3 | CLAUDE.md, rules & memory in depth | 35m | 40m | 1h 15m |
| Tier 2 · Power user — get real leverage from what already exists (3h 5m) | ||||
| 4 | Cowork deep dive: agent mode in anger | 25m | 60m | 1h 25m |
| 5 | MCP as a user: connectors that earn their keep | 20m | 30m | 50m |
| 6 | Skills: what they are and how to use them | 18m | 30m | 50m |
| Tier 3 · Builder — extend and distribute (12h 30m) | ||||
| 7 | Authoring skills I: design and write | 20m | 50m | 1h 10m |
| 8 | Authoring skills II: scripts, evals, iteration | 25m | 60m | 1h 25m |
| 9 | Building MCP servers in Python | 30m | 75m | 1h 45m |
| 10 | Plugins & marketplaces: distribution | 25m | 50m | 1h 15m |
| 11 | Claude Code: interactive mastery | 40m | 70m | 1h 50m |
| 12 | The claude CLI: headless, scripting & automation loops | 40m | 90m | 2h 10m |
| 13 | Hooks in depth: deterministic control | 35m | 55m | 1h 30m |
| 14 | settings.json & the configuration system | 35m | 50m | 1h 25m |
| Tier 4 · Expert — build systems, not sessions (7h 55m) | ||||
| 15 | Claude Agent SDK in Python | 45m | 90m | 2h 15m |
| 16 | The Claude API & platform | 35m | 55m | 1h 30m |
| 17 | Tokens, context, performance & limits | 40m | 60m | 1h 40m |
| 18 | Agent architecture patterns & evaluation | 30m | 50m | 1h 20m |
| 19 | Security, governance, and cost | 30m | 40m | 1h 10m |
| Tier 5 · Director — make it an org capability (1h 15m + capstone) | ||||
| 20 | Director's playbook: rollout & org design | 30m | 45m | 1h 15m |
| 21 | Capstone, 30/60/90, and reference | — | 6–10h | 6–10h |
The landscape: what all these things are
Before touching anything, get the map straight. Most confusion about "Claude vs Claude Code vs Cowork vs skills vs MCP" comes from conflating models, surfaces, and extension mechanisms. They're three different axes.
Axis 1 — Models
The model is the reasoning engine. As of mid-2026 the current line is Claude Opus 5 (deepest reasoning, hardest problems), Claude Sonnet 5 (the workhorse — most agentic and coding work), Claude Haiku 4.5 (fast/cheap, high-volume classification and simple tool loops), and Claude Fable 5. API strings: claude-opus-5, claude-sonnet-5, claude-haiku-4-5-20251001, claude-fable-5.
Axis 2 — Surfaces (where you talk to Claude)
| Surface | What it is | Use it when |
|---|---|---|
| Claude apps (web / desktop / mobile) — Chat | Conversational Claude with Projects, Artifacts, file upload, connectors. | Thinking, writing, one-shot analysis, reviewing. |
| Claude Cowork | Agent mode inside the desktop app. State a goal; Claude reads/writes local files, runs a sandboxed Linux shell, uses connectors, drives your computer/browser, runs on a schedule. Available on paid plans (macOS, Windows, Linux beta). | Multi-step work with deliverables: research → analysis → docx/xlsx/pptx. |
| Claude Code | Terminal-native agentic coding, also embedded in the desktop app and IDEs. | Working inside a repo: features, refactors, reviews, CI. |
| Claude API / Platform | Messages API, tool use, Tool Runner, prompt caching, Managed Agents. | You're building product on top of Claude. |
| Claude Agent SDK | The harness behind Claude Code, exposed as a Python/TS library — agent loop, tools, hooks, subagents, permissions. | You want Claude-Code-grade agents in your own app or pipeline. |
| Claude in Chrome / Excel, Claude Tag (Slack) | Claude embedded where the work already lives. | Browser automation, spreadsheet work, team-visible answers. |
Cowork and Claude Code are the same engine underneath — local, agentic, capable of spawning subagents and sustaining long tasks. Cowork is that engine pointed at documents and business workflows; Claude Code is it pointed at a codebase.
Axis 3 — Extension mechanisms (how you make Claude yours)
This is where the leverage is, and where most people stop too early. Five mechanisms, each answering a different question:
| Mechanism | Answers | Loaded | Format |
|---|---|---|---|
| CLAUDE.md / project instructions | "What should you always know here?" | Always, every turn | Markdown |
| Agent Skills | "How do we do this kind of task?" | On demand, when the description matches | SKILL.md + bundled files/scripts |
| MCP servers (connectors) | "What can you reach?" | Tool schemas listed; called as needed | JSON-RPC server |
| Subagents | "Who does this in a clean context?" | Dispatched via the Task tool | Markdown agent def |
| Hooks | "What must happen deterministically?" | Fired at lifecycle events | Shell commands / functions |
Plugins are the packaging layer: one installable bundle that can ship skills + subagents + slash commands + hooks + MCP server definitions together. Marketplaces are how plugins get distributed.
One real scenario, all five mechanisms
Your platform team gets paged at 02:00. Someone has to write the postmortem, and quality varies wildly depending on who's on call. Here's how each mechanism plays a distinct part:
| Mechanism | What it contributes here |
|---|---|
| CLAUDE.md | "Incidents are tracked in Linear. Sev definitions live in docs/sev.md. Never name individuals as causes." |
Skill incident-postmortem | The actual procedure: gather timeline → ask for anything missing → draft from the template → require an owner and date on every action item. |
| MCP server | Reaches PagerDuty for the alert timeline, Datadog for the metric window, and GitHub for what deployed in the preceding hour — so the timeline isn't reconstructed from memory. |
| Subagent | An auditor that independently re-derives the timeline from the raw data and flags any claim in the draft it can't corroborate. |
| Hook | A validation script that refuses to let the turn end while any action item lacks an owner or a date. |
| Plugin | All of the above shipped as platform-eng@1.4.0, installed by 40 engineers with one command. |
Notice that none of these substitutes for another. Understanding why each piece is a different mechanism is the whole point of this module.
SKILL.md loads only when the task matches; bundled files and scripts load only when the instructions reach for them. Design everything you build this way.The decision tree you'll use constantly
Need Claude to behave differently?
├─ Always, in this project? → CLAUDE.md / project instructions
├─ Only for a recurring task type? → Skill
├─ It needs external data/actions? → MCP server (connector)
├─ It needs an isolated context or
│ parallelism? → Subagent
├─ It must happen 100% of the time,
│ no model judgment? → Hook
└─ Ship several of the above to
a team as one unit? → Plugin (+ marketplace)
Lab 0 — Draw your map
- Open the Claude desktop app. Identify the three modes: Chat, Cowork, Code. Note which your account has access to.
- In a scratch file, list five recurring tasks from your actual week (e.g. "review a design doc", "triage on-call alerts", "write a weekly eng update").
- For each, use the decision tree to guess which mechanism fits. Keep this file — you'll build against it in Modules 7, 10 and 19.
Claude Desktop: Chat, Cowork, Code
The desktop app is one window with three modes. Knowing which mode a task belongs in is half the skill.
Chat
Standard conversational Claude, plus:
- Projects — a persistent workspace with its own instructions and knowledge files. Project instructions are prepended to every conversation in that project. This is your first "always-on context" lever.
- Artifacts — substantial code/documents rendered in a side panel. Special rendering for
.md,.html,.jsx,.mermaid,.svg,.pdf. - Connectors — MCP servers (Gmail, Calendar, Drive, GitHub, Slack, Linear, Notion…) enabled per-account in settings.
- Extended thinking — toggle deeper reasoning for hard problems.
Cowork
Agent mode. You give an outcome, not a question. Claude then plans, uses tools, and produces files. Its distinguishing capabilities:
- Local file access to a folder you explicitly select. Read, write, edit, reorganize.
- A sandboxed Linux shell (separate VM from your machine) with Python, Node, and CLI tools — for running code, data processing, conversions.
- Document generation skills — real
.docx,.xlsx,.pptx,.pdfoutput, not markdown pretending to be one. - Computer use — screenshot and control native desktop apps, with per-app permission tiers.
- Claude in Chrome — DOM-aware browser automation.
- Scheduled tasks — recurring or one-off future runs.
- Live artifacts — persistent HTML pages that re-fetch from your connectors each time you open them.
- Subagents — parallel workers with isolated context.
Code
Claude Code in the desktop app (or terminal, or IDE). Repo-aware: it greps, reads, edits, runs tests, makes commits, opens PRs. Module 11 goes deep.
Choosing the mode
| Task | Mode | Why |
|---|---|---|
| "Explain the tradeoffs of event sourcing here" | Chat | Thinking, no artifacts to produce |
| "Turn these 40 vendor PDFs into a comparison spreadsheet" | Cowork | Local files + shell + xlsx output |
| "Add retry-with-backoff to the ingest service and its tests" | Code | Repo context, test loop |
| "Every Monday 7am, summarize last week's Linear + GitHub activity" | Cowork (scheduled) | Recurring, connector-driven |
Setup checklist
- Install the desktop app; sign in on a paid plan.
- Create a dedicated working folder (e.g.
~/Desktop/Claude_Cowork) and select it in Cowork. Never point Claude at your entire home directory on day one. - Connect two connectors you actually use daily (Calendar + one work system).
- Install the Claude in Chrome extension if you do any browser-based work.
- Install Claude Code CLI:
npm install -g @anthropic-ai/claude-code, then runclaudein a repo.
Lab 1 — One task, three modes
Pick a real question from your work, e.g. "How is our on-call load trending?"
- Chat: ask it cold. Note what Claude can't know.
- Cowork: select a folder, drop in a CSV export of incidents, and ask for an analysis + chart + one-page summary as a docx. Observe the tool calls it makes.
- Code: open a repo and ask Claude Code to find every place a retry policy is defined. Observe how it searches.
- Write three sentences on where each mode's leverage actually came from. This intuition is what you're building.
Context engineering & prompting that scales
"Prompt engineering" undersells it. What you're really doing is context engineering: deciding what information is in the window at the moment the model decides. For agents, this matters more than clever wording.
The five levers
- Task specification — what "done" looks like, concretely.
- Context supply — the facts, files, and schemas needed, and nothing else.
- Constraints — format, length, what to avoid, what to never do.
- Examples — positive and, critically, negative.
- Reasoning scaffold — step order, checkpoints, self-verification.
Techniques that actually move the needle
1. Be specific and detailed — but about the right things
Vague: "Review this PR."
Strong: "Review this PR for (a) N+1 query risk, (b) unhandled error paths in the async branch, (c) backward-compatibility of the API change. For each finding give file:line, severity, and a concrete fix. Ignore style."
2. Give role and audience
"You're reviewing for a team that deploys 12x/day and cannot take downtime" changes the entire risk calculus of the output.
3. Use XML tags for structure
<context>
Service handles 40k rps. Postgres 15. Deploy = blue/green.
</context>
<task>
Propose a migration plan for adding a NOT NULL column to `orders`.
</task>
<constraints>
- Zero downtime, no table rewrite lock > 100ms
- Must be revertible at every step
</constraints>
<output_format>
Numbered steps. Each step: SQL, expected duration, rollback.
</output_format>
Tags make boundaries unambiguous and let you reuse the template programmatically. This is the single formatting habit worth adopting today.
4. Encourage step-by-step reasoning
"Before answering, list the assumptions you're making and the information you'd need to be more confident." Then answer. For hard problems, ask Claude to enumerate options and reject them explicitly before choosing.
5. Negative examples beat more positive examples
<good_example>
"auth: reject expired refresh tokens (#4412)"
</good_example>
<bad_example reason="describes the diff, not the intent">
"changed if statement in token.py"
</bad_example>
6. Prefill and format-forcing
Specify exact output shape, and say what not to include ("no preamble, no summary of what you did").
7. Ask for the failure mode
"What's the most likely way this plan is wrong?" is the cheapest quality upgrade available. Use it on every architecture answer.
Agentic prompting is different
In agent mode you're not writing one prompt — you're setting up a loop. What matters shifts:
| Single-turn | Agentic |
|---|---|
| Wording precision | Clear stopping condition ("done when X exists and Y passes") |
| All context up front | Tell it where to find context; let it fetch |
| Output format | Deliverable location + format ("save to ./out/report.docx") |
| Examples inline | Examples in a skill or reference file |
| — | Verification step: "then run the tests / re-read the file / check the numbers" |
Context rot and how to avoid it
- Long conversations degrade. Start fresh sessions at natural boundaries rather than pushing a 200-turn thread.
- Don't dump whole repos or 300-page PDFs. Point at them; let Claude search. Retrieval beats stuffing.
- Isolate noisy work in subagents so the exploration transcript doesn't pollute the main context — you get back only the conclusion.
- Prompt caching (API): put stable content — system prompt, schemas, long docs — at the front so it can be cached and reused cheaply across calls.
Lab 2 — Rewrite and measure
- Take a prompt you used this week that gave a mediocre result.
- Rewrite it with: XML sections, explicit audience, one negative example, an output format, and a verification clause.
- Run both in fresh conversations. Diff the outputs.
- Save the winning version as a template file — in Module 7 you'll turn it into a Skill.
CLAUDE.md, rules & memory in depth
Every session starts with an empty context window. Everything Claude "knows" about your project on turn one got there because you or Claude put it there. Four mechanisms do that, and they behave very differently.
The memory hierarchy
| Mechanism | Who writes it | Loads | Best for |
|---|---|---|---|
| CLAUDE.md | You | Every session, in full | Standards, commands, architecture, always-true rules |
.claude/rules/ | You | Every session, or only when matching files are touched | Modular instructions, path-scoped conventions |
| Auto memory | Claude | Every session (index only) | Learnings Claude discovers — build quirks, your preferences |
| Skills | You | Only when triggered | Task procedures (Modules 7–8) |
Where CLAUDE.md lives — and the load order
Listed broadest to most specific. Later entries appear later in context, so instructions closest to where you launched Claude are read last.
| Scope | Location | Shared with |
|---|---|---|
| Managed policy | /Library/Application Support/ClaudeCode/CLAUDE.md (macOS) · /etc/claude-code/CLAUDE.md (Linux/WSL) · C:\Program Files\ClaudeCode\CLAUDE.md | Everyone in the org — cannot be excluded |
| User | ~/.claude/CLAUDE.md | Just you, all projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md | Your team, via source control |
| Local | ./CLAUDE.local.md | Just you, this project — gitignore it |
Claude walks up the directory tree from your working directory, loading every CLAUDE.md and CLAUDE.local.md it finds, root-down. Files in subdirectories aren't loaded at launch — they load on demand when Claude reads a file in that directory.
monorepo/services/payments/. Claude loads monorepo/CLAUDE.md (another team's 400-line file about their frontend conventions), then monorepo/services/CLAUDE.md, then yours. Two of those are pure noise costing you context on every turn. The fix is claudeMdExcludes in .claude/settings.local.json:
{ "claudeMdExcludes": ["**/monorepo/CLAUDE.md",
"/abs/path/monorepo/other-team/.claude/rules/**"] }
Patterns match absolute paths and merge across settings layers. Managed policy CLAUDE.md can never be excluded.Writing a CLAUDE.md that actually works
- Size: target under 200 lines. Longer files consume more context and measurably reduce adherence. This is the single most-violated rule in practice.
- Structure with headers and bullets. Claude scans structure the way readers do; dense paragraphs get skimmed.
- Be verifiable. "Use 2-space indentation" beats "format code properly". "Run
npm testbefore committing" beats "test your changes". - Hunt contradictions. If two files disagree, Claude may pick either one arbitrarily. Review nested CLAUDE.md files and rules periodically.
- Use HTML comments for humans. Block-level
<!-- maintainer note -->comments are stripped before injection — they cost zero tokens.
When to add something
Treat CLAUDE.md as the place you write down what you'd otherwise re-explain. Add when: Claude makes the same mistake twice · code review catches something Claude should have known · you type the same correction you typed last session · a new teammate would need the same context.
# Project: payments-gateway
## What this is
Go service handling card auth + capture. gRPC in, Kafka out.
## Commands
- build: make build
- test: make test # must pass before any commit
- lint: golangci-lint run
## Conventions
- Errors: wrap with %w, never swallow.
- Money is always int64 minor units. Never float.
- No new deps without an ADR in docs/adr/.
## Gotchas
- `internal/ledger` is append-only. Never write an UPDATE against it.
- Integration tests need `docker compose up -d` first.
/init generates a starting CLAUDE.md by analysing your codebase; if one exists it proposes improvements rather than overwriting. /doctor can propose trims — it cuts what Claude can derive from the code itself (directory layouts, dependency lists) and keeps pitfalls, rationale, and conventions that differ from tool defaults.
Imports with @path
See @README for project overview and @package.json for npm commands.
# Additional instructions
- git workflow @docs/git-instructions.md
- personal prefs @~/.claude/my-project-instructions.md
- Relative paths resolve against the importing file, not the working directory. Recursive imports allowed, max depth 4.
- Imports inside backticks or fenced code blocks are not expanded — write
`@README`to mention a path literally. - Imports do not save context. Imported files are expanded and loaded at launch, same as inline text. Use them for organization, use rules to actually reduce load.
- An import in a project file that resolves outside your working directory is "external" and triggers a one-time approval dialog — protection against files someone commits to a shared repo.
AGENTS.md
Claude Code reads CLAUDE.md, not AGENTS.md. If your repo already has one, bridge them:
@AGENTS.md
## Claude Code
Use plan mode for changes under `src/billing/`.
A symlink works too (ln -s AGENTS.md CLAUDE.md), except on Windows where you need Developer Mode. /init also reads Cursor and Copilot rule files; /import brings across another agent's config wholesale.
.claude/rules/ — modular and path-scoped
your-project/
├── .claude/
│ ├── CLAUDE.md
│ └── rules/
│ ├── code-style.md
│ ├── testing.md
│ └── frontend/accessibility.md
All .md files are discovered recursively. Rules without paths frontmatter load at launch with the same priority as .claude/CLAUDE.md. Rules with it load only when Claude reads a matching file:
---
paths:
- "src/api/**/*.ts"
- "tests/**/*.test.ts"
---
# API development rules
- All endpoints must include input validation
- Use the standard error response format
Glob notes: brace expansion works (src/**/*.{ts,tsx}) but a rule's whole paths list shares a budget of 1,000 expanded patterns; over-budget patterns are used unexpanded and match nothing. [ starts a bracket expression — escape it as \[ to match literally. Symlinks in .claude/rules/ are resolved, so you can link a shared company rules directory into many repos. User-level rules live in ~/.claude/rules/ and load before project rules.
Auto memory — the notes Claude keeps for itself
On by default. Claude decides what's worth remembering — build commands, debugging insights, your preferences — and writes it without being asked. Storage is per-repository and machine-local:
~/.claude/projects/<project>/memory/
├── MEMORY.md # concise index — loaded every session
├── debugging.md # topic file — loaded on demand
└── api-conventions.md
| Property | Detail |
|---|---|
| What loads at startup | First 200 lines or 25 KB of MEMORY.md, whichever comes first. Topic files load on demand |
| Over the limit | The write succeeds but everything past the limit is dropped on next load; Claude Code returns an error telling Claude to rewrite the index |
| Scope | Per git repository — shared across all worktrees and subdirectories, not across machines |
| Disable | autoMemoryEnabled: false in settings, the /memory toggle, or CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 |
| Relocate | autoMemoryDirectory (absolute or ~/ path) |
| Subagents | Don't inherit the main conversation's auto memory; they can have their own via the subagent memory field |
Frontmatter and block HTML comments are stripped before the index loads, so they don't count toward the limits. Claude records a modified ISO timestamp in frontmatter so both of you can see how current a fact is. "Saved 2 memories" / "Recalled 2 memories" in the UI is this system working.
What survives /compact
| Mechanism | After compaction |
|---|---|
| System prompt and output style | Unchanged — not part of message history |
| Project-root CLAUDE.md and unscoped rules | Re-injected from disk |
| Auto memory | Re-injected from disk |
Rules with paths: frontmatter | Lost until a matching file is read again |
| Nested CLAUDE.md in subdirectories | Lost until a file there is read again |
| Invoked skill bodies | Re-injected, capped at 5,000 tokens/skill and 25,000 total; oldest dropped first |
| Hooks | N/A — hooks run as code, not context |
Because skill bodies truncate from the end, put the most important instructions near the top of every SKILL.md. And if a rule must survive compaction, drop its paths: frontmatter or move it into the project-root CLAUDE.md.
Organization-wide memory
Deploy a managed CLAUDE.md to the policy path via MDM/Group Policy/Ansible, or put the content inline in managed-settings.json under the claudeMd key. Only honored from managed or policy settings.
{ "claudeMd": "Always run `make lint` before committing.\nNever push directly to main." }
| Concern | Configure in |
|---|---|
| Block tools, commands, or file paths | Managed settings permissions.deny |
| Enforce sandbox isolation | Managed settings sandbox.enabled |
| Auth method / org lock | forceLoginMethod, forceLoginOrgUUID |
| Code style, compliance reminders, behavioural guidance | Managed CLAUDE.md |
Settings are enforced by the client regardless of what Claude decides. CLAUDE.md shapes behaviour but is not enforcement. Keep that line clear when you write policy.
Debugging "Claude isn't following my CLAUDE.md"
/context— check the Memory files list. If your file isn't there, Claude cannot see it. This is the answer about half the time./memory— open and edit any memory file, toggle auto memory, browse what Claude saved.- Make the instruction concrete and verifiable.
- Look for a contradicting instruction in an ancestor CLAUDE.md or a rule.
- Still unreliable? It isn't a memory problem — make it a hook.
- Need it at system-prompt level?
--append-system-prompt, though that must be passed every invocation, so it suits scripts more than interactive use. - Use the
InstructionsLoadedhook to log exactly which instruction files loaded, when, and why — the precise tool for debugging path-scoped rules.
CLAUDE.md content is delivered as a user message after the system prompt, not as part of it. That's why specificity matters so much.
Cowork: files and artifacts
- Uploads land in an uploads folder; text, CSV, markdown, images and PDFs often arrive directly in context, others must be read from disk.
- Outputs must go to your selected folder — the temp scratchpad doesn't persist between sessions.
- Chat artifacts are single-file renders; never use
localStorageorsessionStoragein them (unsupported — use in-memory state). - Live artifacts persist and re-call your connectors on open. If you just answered a connector question as a table, that's a live-artifact candidate.
Lab 3 — Build your memory layer ~40 min
- (10 min) Run
/initin a real repo, then trim the result to under 60 lines. Delete anything Claude could derive from the code itself. - (10 min) Move two workflow-specific sections out of CLAUDE.md into
.claude/rules/withpaths:frontmatter. Confirm with/contextthat they no longer load at startup, then read a matching file and confirm they do. - (5 min) Run
/contextbefore and after. Record the token delta — that's your standing saving on every single turn. - (5 min) Run
/memory, open the auto memory folder, and read what Claude has saved about this repo. Delete anything stale. - (5 min) Add a deliberately contradictory instruction in a nested CLAUDE.md and watch what happens. Then remove it.
- (5 min) Add an
InstructionsLoadedhook that logs every instruction file load with a timestamp.
Cowork deep dive: agent mode in anger
The anatomy of a Cowork run
A good Cowork task has five parts. Learn to supply all five and your hit rate jumps:
- Goal — the outcome, not the steps.
- Inputs — where the source material is.
- Constraints — format, audience, length, what to exclude.
- Deliverable — exact file(s) and where to save them.
- Verification — how it should check itself.
Goal: A vendor comparison for the platform team's caching decision.
Inputs: ./vendors/ has 6 PDFs of pricing + SLA docs.
./current-usage.csv has our last 90 days of cache traffic.
Constraints: Audience is engineering leadership. Max 2 pages of prose
plus one table. Assume they know what Redis is. No marketing language.
Call out anything the vendor docs are silent on.
Deliverable: ./out/caching-vendor-analysis.docx plus a supporting
./out/cost-model.xlsx with the per-vendor monthly cost at our volumes.
Verify: recompute the top-line cost figure two different ways and tell
me if they disagree. List any claim you could not source to a document.
Tool tiers — pick the right one
Cowork can reach the outside world four ways, in descending order of preference:
- A dedicated MCP connector for the app (Gmail, Calendar, Linear, GitHub). Fast, structured, reliable.
- Claude in Chrome for web apps with no connector. DOM-aware, far better than pixel-clicking.
- Computer use for native desktop apps and cross-app workflows.
- Web fetch/search for public content.
Computer-use permission tiers are worth knowing because they explain surprising failures: browsers are granted read (visible, not clickable — use Chrome tools instead), terminals and IDEs are click (no typing — use the shell tool instead), everything else is full.
Scheduled tasks
Anything you'd do on a cadence — a morning brief, a weekly digest, a nightly repo health check, a "tell me if X changes" watcher — should be a scheduled task rather than a habit. Cron for recurring, a single fire time for one-offs.
Every weekday at 07:30:
1. Pull yesterday's merged PRs and closed incidents.
2. Pull today's calendar.
3. Flag anything where I'm the blocker.
4. Write a < 300 word brief and save it to ./briefs/YYYY-MM-DD.md
Subagents in Cowork
Spawn them when work is (a) genuinely parallel or (b) context-polluting. Each starts cold, so a subagent must be given everything it needs in its prompt. Typical wins: fan-out research across 8 sources, reviewing 5 documents independently, or a dedicated verification agent that re-checks the primary agent's numbers without having seen its reasoning.
Live artifacts
A saved HTML page that calls your connectors on load. Inside the page you get, roughly:
window.cowork.callMcpTool(name, args)— call any connector tool you declared.window.cowork.askClaude(prompt, data[])— cheap inference over data you just fetched (summaries, classification).window.cowork.runScheduledTask(id)— trigger a scheduled task.
Build order that works: call the tool once in chat first, look at the actual response shape, then write the page's parser against what you observed. MCP wrappers reshape upstream APIs; assumptions will bite you.
Skills you already have
Cowork ships document skills — docx, xlsx, pptx, pdf — plus skill-creator, schedule, and whatever plugins you've installed. Two rules:
- Research first, format second. Gather all facts before invoking a document skill; reading the format instructions early anchors the model on mechanics instead of substance.
- Skills compose. "Generate an image and put it in the report" = imagegen skill + docx skill.
Lab 4 — A real multi-step deliverable
- Put 4–8 real source documents in your selected folder.
- Write a task using all five parts above. Include a verification clause.
- Run it. Watch the tool calls — note every point where you'd have specified something differently.
- Now schedule a lightweight version of it to run weekly.
- Take one connector-driven table from the output and ask for it as a live artifact you can reopen.
MCP as a user: connectors that earn their keep
MCP — the Model Context Protocol — is the open standard for connecting models to tools and data. In the Claude apps you meet it as connectors. You'll build servers in Module 9; first, learn to use them well, because that teaches you what a good server looks like.
The mental model
┌─────────────┐ JSON-RPC ┌──────────────┐
│ HOST │ ◄───────────────────► │ MCP SERVER │
│ (Claude app)│ │ (Linear, │
│ + CLIENT │ stdio or HTTP+SSE │ your API) │
└─────────────┘ └──────────────┘
Server offers: tools · resources · prompts
Client offers: sampling · roots · elicitation
- Tools — actions the model can invoke (
create_issue,search_threads). - Resources — read-only addressable data the client can pull in.
- Prompts — reusable templates the server exposes, often surfaced as slash commands.
- Sampling — the server asks the client for a model completion. Lets servers be smart without holding API keys.
- Roots — the client tells the server which filesystem/URI boundaries it may operate in.
- Elicitation — the server asks the user a question mid-flow.
get_service_status, recent_deploys, who_is_oncall — actions with arguments. Resource: runbook://payments-gateway — the on-call runbook, read-only and addressable, so Claude pulls it in rather than calling a tool that returns a static document. Prompt: /rollback-plan — a template the server exposes so every engineer produces the same shape of rollback plan. Elicitation: before apply_rollback runs, the server asks the human "confirm rollback of payments-gateway 1.42.3 → 1.42.2?" — the confirmation lives in the protocol, not in a prompt you hope the model honours.Transports: stdio for local, single-user servers; Streamable HTTP for remote, multi-client servers over HTTPS. Recent spec work has moved server→client requests (sampling, elicitation) toward multi-round-trip requests rather than requiring persistent bidirectional streams, and added ttlMs/cacheScope on list/read responses so clients can cache tool and resource listings.
Using connectors well
- Enable narrowly. Every connected server's tool schemas consume context and expand your attack surface. Ten well-chosen connectors beat forty.
- Know each tool's granularity. "Search Slack" vs "get thread" vs "list channels" produce wildly different token costs. Ask Claude which tool it will use before an expensive run.
- Auth is per-server. OAuth-based connectors need an interactive authorization; they simply won't work in a background/scheduled run until authorized.
- Discoverability. If a task implies an external service, search the connector registry before falling back to browser automation.
Security posture (start caring now)
Reading a tool result like an engineer
When a connector misbehaves, the debugging ladder is: (1) does the tool exist and is the server authorized? (2) call it with minimal args and inspect raw output; (3) check whether the wrapper renamed parameters; (4) check pagination and limits — most "the data is missing" bugs are actually "you got page 1 of 12".
Lab 5 — Audit your connector surface
- List every connector you have enabled. For each, write: what it can read, what it can write, and what the blast radius is if it were manipulated by injected text.
- Disable anything you haven't used in 30 days.
- Pick one connector and call three of its tools directly with minimal arguments. Record the actual response shapes in a notes file.
- Write one sentence per tool describing when Claude should reach for it — you're practicing writing tool descriptions, which is Module 9's core skill.
Skills: what they are and how to use them
An Agent Skill is a folder containing a SKILL.md — YAML frontmatter plus markdown instructions — and optionally scripts, templates, and reference files. It's an open format: the same skill works in Claude apps, Cowork, Claude Code, and the Agent SDK.
The three-stage load
- Discovery — at session start, Claude sees only every skill's
nameanddescription. Cheap. - Activation — when a task matches a description, the full
SKILL.mdbody enters context. - Execution — the instructions may then read
reference.md, runscripts/convert.py, or open a template. Loaded only as reached.
adr skill and nobody's ADRs improved. The description said "Creates architecture decision records." Real users typed "write this up as a decision doc", "we need to document why we picked Kafka", and "capture this tradeoff somewhere". None matched. Adding those exact phrasings to the description — and nothing else — took the fire rate from roughly 2 in 10 to 10 in 10. The instructions were never the problem.Anatomy
my-skill/
├── SKILL.md # frontmatter + instructions (keep < ~500 lines)
├── reference.md # deep detail, loaded on demand
├── templates/
│ └── report.docx
└── scripts/
└── validate.py # deterministic work — don't make the model do math
---
name: incident-postmortem
description: Write a blameless postmortem after an incident is resolved.
Use when the user mentions a postmortem, RCA, incident writeup, or says
an incident has been resolved and needs documenting. Not for live
incident triage.
---
# Incident postmortem
## When to use
After resolution only. For an active incident, use `incident-response`.
## Procedure
1. Gather: timeline, detection time, mitigation time, customer impact.
2. Ask for anything missing before drafting. Do not invent timestamps.
3. Draft using templates/postmortem.md.
4. Contributing factors, never individual blame.
5. Every action item needs an owner and a date.
## Output
Markdown at docs/postmortems/YYYY-MM-DD-<slug>.md
Where skills come from
- Built-in — document skills (
docx,xlsx,pptx,pdf),skill-creator,schedule, and others. - Plugins — installing a plugin installs its skills (you likely have engineering/data/productivity bundles).
- Your own — personal or team skills, in a skills directory or shipped in a plugin.
Invoking
Skills mostly fire automatically from their descriptions. You can also name one directly (many surface as /name). Custom slash commands and skills have converged — a slash command is essentially a prompt-template skill.
Skill vs the alternatives — the honest comparison
| Use a skill when… | Use something else when… |
|---|---|
| The procedure is reused across sessions/people | It's a one-off → just prompt |
| It's task-triggered, not always relevant | It's always relevant → CLAUDE.md |
| There's a right way that people get wrong | It needs external data → MCP |
| It benefits from bundled templates/scripts | It must be enforced deterministically → hook |
Lab 6 — Read the source
- Find the skills installed on your machine and open two
SKILL.mdfiles (the built-inxlsxandpdfones are excellent teachers). - For each, note: how long is the description? What triggers does it enumerate? What does it explicitly say not to trigger on? How does it split content into referenced files?
- Run a task that should trigger one, and one that shouldn't. Confirm the boundary behaves as the description implies.
- Write down three description-writing patterns you'll steal.
Authoring skills I: design and write
Step 1 — Pick something that deserves a skill
Good candidates share three traits: recurring, opinionated (there's a right way people get wrong), and bounded (you can describe done). Bad candidates: one-offs, pure knowledge lookups, and "be smarter."
Step 2 — Write the description first
Seriously. Before any instructions. The description is the classifier. A strong one has four elements:
- What it does, in one clause.
- When to use it — concrete triggers, using the vocabulary users actually type.
- What form the input/output takes, if it's distinguishing.
- When not to use it — the negative boundary.
description: >
Review a pull request or diff for security, performance, and
correctness issues. Use when the user shares a PR URL or a diff,
asks "review this before I merge", or asks whether a change is safe,
or mentions N+1 queries, injection risk, missing error handling, or
race conditions. Do NOT use for style/formatting-only feedback, for
writing new code, or for reviewing an entire codebase.
Step 3 — Structure the body
---
name: kebab-case-name
description: <the classifier, per above>
---
# Human-readable title
## When to use / when not to use
<restate the boundary for the model that has now loaded you>
## Inputs you need
<what to gather; explicitly say "ask if missing, don't invent">
## Procedure
1. Numbered, imperative steps.
2. Decision points as explicit conditionals.
3. Point at scripts/ for anything deterministic.
4. Point at reference.md for detail you don't always need.
## Output
<exact format and location>
## Failure modes
<what commonly goes wrong and what to do instead>
Step 4 — Apply progressive disclosure ruthlessly
- Keep
SKILL.mdunder ~500 lines. If it's longer, you're inlining reference material. - Move exhaustive detail (API field lists, style guides, edge-case catalogs) to
reference.mdand link it: "For the full field mapping, read reference.md." - Move determinism to
scripts/. Models are excellent at orchestrating scripts and mediocre at being one. - Ship
templates/rather than describing a template in prose.
Writing style that models follow well
| Do | Don't |
|---|---|
Imperative: "Run make test before committing." | Passive: "Tests should generally be run." |
| Explicit conditionals: "If X is missing, ask. Do not infer." | "Use judgment." |
| Concrete paths, commands, filenames | "the appropriate directory" |
Negative examples with a reason | Ten positive examples |
| State the failure mode and the recovery | Assume the happy path |
Where to put it
- Personal — your user-level skills directory; available everywhere you work.
- Project —
.claude/skills/<name>/SKILL.mdin the repo; versioned with the code, available to everyone who clones it. This is usually the right default for team skills. - Distributed — inside a plugin (Module 10).
In Cowork you can also have Claude save a skill for you directly — describe the workflow and ask it to create one. Use skill-creator to scaffold, then edit by hand; the scaffold is a starting point, not the deliverable.
Lab 7 — Ship skill #1
- Take the winning prompt template from Lab 2.
- Write the description first. Include at least three real trigger phrasings and one negative boundary.
- Write the body with the six sections above. Keep it under 120 lines.
- Install it at project scope in a real repo.
- Start a fresh session and phrase a request three different ways. Does it fire all three times? If not, fix the description — not the body.
Authoring skills II: scripts, evals, iteration
Bundling scripts
A skill script is ordinary code Claude runs. Design it like a CLI someone else will use: clear args, useful errors, exit codes, and output the model can parse.
# scripts/validate_postmortem.py
"""Validate a postmortem markdown file against team requirements.
Usage: python validate_postmortem.py <path>
Exit 0 = valid. Exit 1 = problems (printed to stdout, one per line).
"""
import re, sys, pathlib
REQUIRED = ["## Summary", "## Timeline", "## Contributing factors",
"## Action items", "## Detection"]
def main(path: str) -> int:
text = pathlib.Path(path).read_text(encoding="utf-8")
problems: list[str] = []
for section in REQUIRED:
if section not in text:
problems.append(f"MISSING SECTION: {section}")
# every action item needs an owner and a date
for line in text.splitlines():
if line.strip().startswith("- [ ]"):
if "@" not in line:
problems.append(f"ACTION ITEM WITHOUT OWNER: {line.strip()}")
if not re.search(r"\d{4}-\d{2}-\d{2}", line):
problems.append(f"ACTION ITEM WITHOUT DATE: {line.strip()}")
# blameless check
for name in re.findall(r"\b[A-Z][a-z]+ (?:caused|broke|forgot)\b", text):
problems.append(f"POSSIBLE BLAME LANGUAGE: {name}")
for p in problems:
print(p)
return 1 if problems else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: validate_postmortem.py <path>"); sys.exit(2)
sys.exit(main(sys.argv[1]))
Then in SKILL.md:
5. Validate the draft:
`python scripts/validate_postmortem.py <path>`
If it exits non-zero, fix every reported problem and re-run.
Do not present the postmortem to the user until it exits 0.
Evaluating a skill
You're an engineering leader — you know an unmeasured system drifts. Skills are no different. Evaluate two things separately:
A. Triggering accuracy
Build a small labelled set: 10–15 prompts that should fire the skill (varied phrasing, including terse and oblique), and 10–15 that shouldn't (especially ones that should fire a neighbouring skill). Run each in a fresh session; record fired/didn't. Anything below ~90% on the positive set means the description is under-specified; false positives mean it's over-broad.
B. Output quality
Define 4–6 binary criteria for a good output, then grade. Binary beats 1–5 scales — "does every action item have an owner?" is checkable; "is it well-written?" isn't.
# evals/cases.yaml
- id: pm-basic
prompt: "We resolved the checkout outage. Write it up."
should_trigger: incident-postmortem
criteria:
- has_all_required_sections
- every_action_item_has_owner_and_date
- no_individual_named_as_cause
- asked_for_missing_timeline_rather_than_inventing
- id: pm-negative
prompt: "Checkout is down right now, help me triage."
should_trigger: incident-response # NOT postmortem
Run the suite whenever you change a description. Note that model outputs vary — run each case 3× and look at the pass rate, not a single sample.
The iteration loop
observe failure → classify it
├─ didn't fire → fix the DESCRIPTION
├─ fired on wrong task → add NEGATIVE BOUNDARY
├─ fired, wrong steps → fix the PROCEDURE (more explicit conditionals)
├─ fired, right steps,
│ bad artifact → add a TEMPLATE or SCRIPT + gate
└─ inconsistent across
runs → replace judgment with determinism
Team skills as code
- Version them in the repo under
.claude/skills/. PR review applies. - Treat description changes like API changes — they alter routing behaviour for everyone.
- Keep a
CHANGELOGfor non-trivial skills; people will wonder why behaviour shifted. - Add the eval suite to CI if the skill is load-bearing.
Lab 8 — Harden skill #1
- Add a
scripts/validator to the skill you shipped in Lab 7, plus a hard gate line in the procedure. - Write 10 positive and 10 negative trigger cases. Run them. Record the confusion matrix.
- Fix the description until you're ≥ 9/10 positive and ≤ 1/10 false positive.
- Define 5 binary quality criteria and grade three real outputs against them.
Building MCP servers in Python
Now the other half of extension: giving Claude reach. We'll use the official Python SDK's FastMCP, which handles the JSON-RPC plumbing so you write plain functions.
Hello, server
pip install "mcp[cli]" httpx
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("deploy-tools")
@mcp.tool()
def get_service_status(service: str, env: str = "prod") -> dict:
"""Get current deploy status for a service.
Use when the user asks whether a service is healthy, what version
is deployed, or when it last shipped. Returns version, health, and
last deploy timestamp.
Args:
service: Service slug, e.g. "payments-gateway".
env: One of "prod", "staging", "dev". Defaults to "prod".
"""
# ... call your real internal API here ...
return {
"service": service,
"env": env,
"version": "1.42.3",
"healthy": True,
"deployed_at": "2026-08-07T14:02:11Z",
}
if __name__ == "__main__":
mcp.run() # stdio transport
query(sql: str) — one flexible tool, described as "Run a query against the warehouse." Claude wrote plausible SQL against tables that didn't exist, and every failure cost a round trip. They replaced it with four narrow tools — list_tables(), describe_table(name), revenue_by_period(start, end, granularity), run_readonly_sql(sql) — each documenting exactly when to reach for it. Same underlying warehouse; selection errors dropped to near zero, because the schema of the toolset now taught the model how the domain works.Resources and prompts
@mcp.resource("runbook://{service}")
def runbook(service: str) -> str:
"""The on-call runbook for a service, as markdown."""
return (RUNBOOKS / f"{service}.md").read_text()
@mcp.prompt()
def rollback_plan(service: str, from_version: str) -> str:
"""Template: produce a rollback plan for a bad deploy."""
return f"""Produce a rollback plan for {service} from {from_version}.
Include: blast radius, DB migration reversibility, feature flags to
flip, and the verification step after rollback."""
Rule: tools do, resources read, prompts template. Don't make a tool that just returns a static document — make it a resource.
Designing tools models use well
| Principle | Why |
|---|---|
| Few, well-named tools beat many granular ones | Every tool schema costs context and adds selection error |
| Return structured, compact results | Don't dump 4000-token JSON; return the fields that matter, paginate the rest |
Encode enums in the type (Literal["prod","staging"]) | Invalid calls become impossible rather than corrected after the fact |
| Errors are messages to the model | "service 'payments' not found; did you mean 'payments-gateway'?" is recoverable; a stack trace isn't |
| Idempotent where possible; explicit when destructive | Name it delete_, document irreversibility, and require confirmation |
| Separate read and write servers | Lets you grant read broadly and write narrowly |
Async, HTTP calls, and remote transport
import httpx
from typing import Literal
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("deploy-tools")
@mcp.tool()
async def recent_deploys(
service: str,
env: Literal["prod", "staging", "dev"] = "prod",
limit: int = 10,
) -> list[dict]:
"""List recent deploys for a service, newest first.
Use for questions like "when did X last ship" or "what changed
in the last N deploys". limit is capped at 50.
"""
limit = min(limit, 50)
async with httpx.AsyncClient(timeout=10) as c:
r = await c.get(
f"{API}/deploys",
params={"service": service, "env": env, "limit": limit},
headers={"Authorization": f"Bearer {TOKEN}"},
)
if r.status_code == 404:
return [{"error": f"No service named {service!r}. "
f"Call list_services to see valid names."}]
r.raise_for_status()
return [
{"version": d["version"], "at": d["finished_at"],
"author": d["author"], "sha": d["sha"][:8]}
for d in r.json()["items"]
]
if __name__ == "__main__":
mcp.run(transport="streamable-http") # remote, multi-client
Connecting it
For local development, register the stdio server with your client (Claude Desktop config, or claude mcp add for Claude Code). For remote servers, deploy behind HTTPS with OAuth and register the URL. Inside a plugin, ship a .mcp.json and it's installed with the plugin (Module 10).
{
"mcpServers": {
"deploy-tools": {
"command": "python",
"args": ["/abs/path/to/server.py"],
"env": { "DEPLOY_API_TOKEN": "..." }
}
}
}
Testing
- Use the MCP Inspector (
mcp dev server.py) to list and call tools without a model in the loop. - Unit-test the underlying functions directly — they're just Python.
- Then test selection: give Claude the server and 10 natural questions; check it picks the right tool with the right args. Bad selection is almost always a docstring problem.
Security
- Least privilege. The server's token is the ceiling on damage. Scope it.
- Never interpolate model output into shell or SQL. Parameterize.
- Validate and bound everything — limits, ids, paths. Reject path traversal explicitly.
- Treat upstream content as hostile. If your tool returns user-authored text (tickets, emails, PRs), it can contain injected instructions. Consider labelling it clearly as untrusted data in the return value.
- Log every tool call with args and caller. You will need this.
- Confirm destructive actions at the protocol level (elicitation) or by splitting into
plan_+apply_tools.
Lab 9 — Ship a server
- Pick one internal system you query constantly. Build a 3-tool read-only server against it (or a realistic mock).
- Write the docstrings as if they're the only documentation — because they are.
- Add one resource and one prompt.
- Test with the Inspector, then connect it to Claude and run 10 natural-language questions. Log which tool it chose each time.
- Fix every mis-selection by editing docstrings only. Note how far that gets you.
Plugins & marketplaces: distribution
You now have skills and an MCP server. A plugin is how you hand all of it to 200 people as one install, and update it centrally afterwards. For a director, this is the module that turns individual productivity into org capability.
What a plugin can contain
my-plugin/
├── .claude-plugin/
│ └── plugin.json # required manifest
├── .mcp.json # MCP server definitions (optional)
├── skills/
│ └── incident-postmortem/
│ ├── SKILL.md
│ └── scripts/validate_postmortem.py
├── agents/
│ └── security-reviewer.md # subagent definitions
├── commands/
│ └── ship.md # slash commands
├── hooks/
│ └── hooks.json # lifecycle automation
└── README.md
plugin.json
{
"name": "platform-eng",
"displayName": "Platform Engineering",
"version": "1.4.0",
"description": "Skills, connectors and guardrails for the platform org: postmortems, ADRs, deploy checks, and deploy-tools MCP access.",
"author": { "name": "Platform Eng", "email": "platform@example.com" },
"keywords": ["incident", "adr", "deploy", "postmortem", "review"]
}
name is an immutable slug. Once published, people have it installed under that name — don't change it. To change the label users see, set displayName. Version with semver and mean it: a description change in a shipped skill is a behavioural change for every user.Bundling your MCP server
{
"mcpServers": {
"deploy-tools": {
"command": "python",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp/server.py"],
"env": { "DEPLOY_API_URL": "https://deploy.internal.example.com" }
}
}
}
Secrets don't go in the plugin. Read them from the user's environment or an OAuth flow.
Subagent definitions
---
name: security-reviewer
description: Reviews a diff for security issues only. Use when
explicitly asked for a security review, or before a release.
tools: Read, Grep, Glob, Bash
model: opus
---
You review code changes for security defects and nothing else.
Focus, in order: injection (SQL/command/template), authn/authz gaps,
secrets in code or logs, unsafe deserialization, SSRF, path traversal,
missing rate limits on unauthenticated endpoints.
For each finding output: file:line · severity (crit/high/med/low) ·
the concrete exploit path · the minimal fix.
Report "no findings in scope" rather than padding with style notes.
Hooks
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/guard_bash.sh"
}]
}],
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scan_secrets.sh"
}]
}]
}
}
A PreToolUse hook receives the tool name and input on stdin and decides by exit code: 0 allow, 1 allow with a warning, 2 deny. This is your enforcement point — secret scanning, blocking rm -rf patterns, forbidding writes outside allowed paths, requiring a ticket reference on commits.
Marketplaces
A marketplace is a git repo with .claude-plugin/marketplace.json listing plugins. Users add the marketplace once, then install and update plugins from it.
{
"name": "example-internal",
"owner": { "name": "Developer Experience" },
"plugins": [
{ "name": "platform-eng", "source": "./plugins/platform-eng",
"description": "Platform org skills, connectors and guardrails." },
{ "name": "data-platform", "source": "./plugins/data-platform",
"description": "Warehouse context, SQL conventions, dbt helpers." }
]
}
Internal marketplace = a private git repo your org can reach. That gives you review, versioning, rollback, and a single place to see what's deployed — the same properties you'd demand of any internal platform.
Design guidance for org plugins
- One plugin per team or domain, not one giant plugin. Installing should be a statement about what work you do.
- Keep skill counts modest. Every installed skill's description competes in the classifier. 6 sharp skills beat 30 fuzzy ones.
- Separate "guardrails" from "capabilities." A mandatory security/hooks plugin everyone installs, plus optional capability plugins.
- Document the trigger phrases in the README so people know what to say.
- Own it like a service. Owner, changelog, issue tracker, deprecation policy.
Lab 10 — Package and distribute
- Create a plugin containing the skill from Lab 7/8 and the MCP server from Lab 9.
- Add one subagent definition and one
PreToolUsehook that blocks something you actually care about. - Create a marketplace repo with a single plugin in it. Install it from a clean machine or profile.
- Bump the version, change one skill description, reinstall, and confirm the behaviour change propagates.
- Write the README as if for a new hire: what's inside, what to say to trigger each thing, who owns it.
Claude Code: interactive mastery
Claude Code is the agentic coding surface: terminal, IDE, desktop app, and CI. Everything you learned about context engineering applies, plus a repo-specific toolkit.
The seven levers
There are seven ways to steer Claude Code. Knowing which to reach for is the whole skill:
| Lever | Nature | Reach for it when |
|---|---|---|
CLAUDE.md | Always-on context | Facts true for all work in this repo |
| Rules | Hard constraints | Non-negotiables you want stated, not inferred |
| Skills | On-demand procedures | Recurring task types |
| Subagents | Delegated work | Isolation or parallelism |
| Hooks | Deterministic automation | Must happen every time, no judgment |
| Output styles | Global behaviour change | You want a different mode of working overall |
| System prompt append | Global additions | Programmatic/SDK contexts |
Daily workflow that works
- Plan before code. Ask for a plan; approve or correct it; then let it implement. Planning is cheap, wrong implementations aren't.
- Small, verifiable increments. "Implement X and make
make testpass" beats "implement the whole feature." - Let it read first. Point at files; let it grep. Don't paste.
- Tight feedback loops. If tests are slow, give it a fast subset command in CLAUDE.md.
- Fresh sessions at boundaries. New feature, new session.
- Review the diff, always. You're the approver, not the audience.
Subagents in a repo
The highest-value pattern for engineering teams: a coordinator that dispatches focused agents.
Explore agent → "find every place we construct a DB session" (read-only,
returns a list, doesn't dump 40 files into main context)
Plan agent → "given these findings, design the migration" (opus)
Implement → main session does the edits
Review agent → independent security + correctness pass on the diff
Test agent → runs the suite, reports failures
The review agent is the one people underuse. An agent that hasn't seen your reasoning catches things the author-agent rationalized.
Hooks worth having on day one
#!/usr/bin/env bash
# hooks/guard_bash.sh — PreToolUse matcher: Bash
input=$(cat)
cmd=$(printf '%s' "$input" | python3 -c \
'import sys,json; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))')
deny() { echo "BLOCKED: $1" >&2; exit 2; }
case "$cmd" in
*"rm -rf /"*) deny "destructive rm" ;;
*"git push --force"*) deny "force push" ;;
*"curl "*"| sh"*) deny "pipe-to-shell" ;;
*"AWS_SECRET"*) deny "secret in command" ;;
esac
exit 0
Others to consider: PostToolUse formatter/linter on every edit; secret scan before commit; a hook that appends the current ticket ID to commit messages; a hook that refuses writes outside the repo root.
Slash commands
Custom slash commands have merged into skills — a command is a prompt template invoked as /name. Ship the ones your team retypes constantly: /adr, /standup, /review, /postmortem.
Permission modes — the dial you'll touch most
Shift+Tab cycles modes mid-session (Alt+M on some Windows setups). Start a session in one with --permission-mode.
| Mode | Behaviour | Use for |
|---|---|---|
default (aka manual) | Prompts for each non-allowlisted action | Unfamiliar repos |
acceptEdits | Auto-accepts file edits, still prompts for the rest | Tight implement/test loops |
plan | Reads and plans, executes nothing | Design and scoping before any change |
auto | A classifier decides; you can inspect and tune its rules (claude auto-mode defaults, claude auto-mode config) | Day-to-day flow with fewer prompts |
dontAsk | Never prompts; denies rather than asking | Semi-autonomous runs where you'd rather fail than be interrupted |
bypassPermissions | No checks at all | Disposable containers only. Never on a machine holding credentials you care about |
--permission-mode plan, review the plan, then switch. It costs 30 seconds and prevents the class of failure where Claude confidently implements the wrong thing across nine files.Permission rules and settings
Permissions are rules, not just modes. The rule syntax is Tool(pattern):
// .claude/settings.json (committed — team-wide)
{
"permissions": {
"allow": [
"Bash(git status)", "Bash(git diff *)", "Bash(git log *)",
"Bash(make test)", "Read", "Grep", "Glob"
],
"deny": [
"Bash(git push --force *)",
"Bash(rm -rf *)",
"Read(./.env)", "Read(./secrets/**)"
],
"additionalDirectories": ["../shared-lib"]
},
"model": "claude-sonnet-5",
"env": { "MAKEFLAGS": "-j8" }
}
Settings resolve in layers, later overriding earlier: user (~/.claude/settings.json) → project (.claude/settings.json, committed) → local (.claude/settings.local.json, gitignored) → --settings flag. Managed policy settings sit above all of them and can't be overridden — that's your enterprise control point.
Interactive mode: the shortcuts that matter
| Key | Does |
|---|---|
Shift+Tab | Cycle permission modes |
Esc | Interrupt Claude / close a dialog |
Esc Esc | Open the rewind menu — restore code and conversation to an earlier checkpoint |
Ctrl+O | Toggle the transcript viewer |
Ctrl+T | Toggle Claude's task checklist |
Ctrl+B | Background the running task |
Ctrl+R | Reverse-search prompt history |
Ctrl+X Ctrl+K (×2) | Stop all running background subagents |
Ctrl+G / Ctrl+X Ctrl+E | Edit the prompt in $EDITOR |
Shift+Enter / Ctrl+J | Newline without submitting |
Ctrl+S | Stash / restore the current prompt |
Ctrl+C / Ctrl+D | Interrupt or clear input / exit |
Input prefixes
@path/to/file— mention a file (with autocomplete) so Claude reads it.!— shell mode.! npm testruns directly and drops the output into the transcript; Claude responds to it automatically.#— comment lines above your prompt that Claude Code strips on save; also the memory shorthand for adding a fact./— skills and commands./help,/doctor,/context,/compact,/resume,/rename,/agents,/hooks,/mcp,/add-dir,/autocompact,/import.
Checkpointing and rewind
Claude Code checkpoints as it works. Esc Esc opens the rewind menu, which can restore code, conversation, or both to a prior point. This changes how you should work: be willing to let Claude attempt an aggressive change, because backing it out is one keystroke — not a git reset archaeology session.
Worktrees, background sessions, and parallelism
claude -w feature-authstarts in an isolated git worktree at<repo>/.claude/worktrees/<name>. Add--tmuxfor panes.-w '#1234'branches from a PR.claude --bg "investigate the flaky test"launches a background session and returns immediately with an ID.- Manage them from the shell:
claude agents(the agent view),claude agents --json,claude logs <id>,claude attach <id>,claude stop <id>,claude respawn <id>,claude rm <id>. claude --bg --exec 'pytest -x'runs a plain shell command as a PTY-backed background job.
Diagnostics and recovery
claude doctor— install and settings diagnostics without starting a session.claude --safe-mode— start with all customizations disabled (CLAUDE.md, skills, plugins, hooks, MCP, commands, output styles). The first thing to try when behaviour goes strange.claude --debug='mcp,startup'— category-filtered debug output./context— see what's actually occupying the window. Run this when quality degrades.
Claude Code in CI
Headless mode makes Claude a pipeline step: auto-review every PR against your team's checklist, triage failing tests, draft release notes from merged PRs, or open a follow-up issue when a TODO ages past 90 days. Keep permissions minimal and make the output a comment, not a commit, until you trust it. Module 12 covers this in full.
Lab 11 — Codify your team's standards
- Write
CLAUDE.mdfor your primary repo (reuse Lab 3, tighten it). - Add a
security-reviewersubagent and run it on a real recent diff. Compare its findings to what human review caught. - Add the bash guard hook. Deliberately try a blocked command and confirm the denial.
- Create a
/adrslash command that produces your team's exact ADR format. - Run one full feature end to end: plan → implement → test agent → review agent. Time it, and note where you had to intervene.
The claude CLI: headless, scripting & automation loops
Interactive Claude Code makes you faster. Headless Claude Code makes your systems smarter. This is the module that turns an assistant into infrastructure.
claude -p: (1) a PR check that reads the diff and fails CI when a migration lacks a rollback path — 14 lines of YAML; (2) a 02:00 cron job that runs npm audit and reports only the vulnerabilities actually reachable from an entrypoint, cutting the weekly triage list from ~40 to ~4; (3) a bounded retry loop that attempts flaky-test fixes overnight and opens a PR when the suite goes green 20 times in a row. None of these required the SDK, a server, or a platform. They required -p, a JSON schema, and a spend cap.The command surface
| Command | What it does |
|---|---|
claude | Interactive session |
claude "query" | Interactive, seeded with a prompt |
claude -p "query" | Print/headless mode — run the agent loop, print a result, exit |
cat f.log | claude -p "explain" | Process piped stdin |
claude -c / claude -c -p "…" | Continue the most recent conversation in this directory |
claude -r "<id-or-name>" "query" | Resume a specific session |
claude mcp … | Manage MCP servers (add, list, login <name>, logout) |
claude plugin … | Manage plugins and marketplaces |
claude agents / attach / logs / stop / respawn / rm | Background session management |
claude setup-token | Long-lived OAuth token for CI and scripts |
claude doctor | Read-only install/settings diagnostics |
claude update / claude install <version> | Version management |
claude project purge [path] | Delete local state for a project (transcripts, logs, history) |
claude --help deliberately doesn't list every flag. Absence from --help doesn't mean a flag doesn't exist — the CLI reference is the source of truth.Flags that matter for automation
| Flag | Why you care |
|---|---|
-p, --print | Non-interactive. The foundation of everything below |
--output-format text|json|stream-json | json for a single parseable result; stream-json for newline-delimited events you can consume live |
--input-format text|stream-json | Feed a multi-turn conversation in programmatically |
--json-schema '<schema>' | Validated structured output. Stop parsing prose — declare the shape you need |
--max-turns N | Hard cap on agentic turns. Non-negotiable in unattended runs |
--max-budget-usd N | Hard dollar cap, including subagent spend. Your circuit breaker |
--allowedTools / --disallowedTools | Permission rules: "Bash(git diff *)", "Read". Deny with patterns or remove tools entirely |
--tools "Bash,Edit,Read" | Restrict which built-in tools exist at all |
--permission-mode | plan, acceptEdits, dontAsk, etc. |
--permission-prompt-tool | Route permission decisions to an MCP tool — how you put a human or a policy service in the loop non-interactively |
--append-system-prompt / --system-prompt | Extend or replace the system prompt (see below) |
--agents '{"reviewer":{…}}' | Define subagents inline as JSON — no files needed |
--mcp-config ./mcp.json + --strict-mcp-config | Exactly the servers you intend, nothing inherited |
--settings ./ci.json / --setting-sources | Deterministic config for CI, ignoring developer machines' settings |
--bare | Skip auto-discovery of hooks/skills/plugins/MCP/CLAUDE.md. Fastest cold start for simple scripted calls |
--safe-mode | All customizations off. Debugging, not automation |
--fork-session | Resume without mutating the original session |
--session-id <uuid> | Deterministic session identity for orchestration |
--worktree, -w | Isolated git worktree — parallel-safe runs |
--bg / --exec | Background session / background shell job |
--model, --fallback-model sonnet,haiku, --effort low|medium|high|xhigh|max | Cost and quality routing, plus resilience when a model is overloaded |
--exclude-dynamic-system-prompt-sections | Improves prompt-cache reuse across machines/users running the same task. Real money at scale |
--include-partial-messages, --forward-subagent-text, --include-hook-events | Richer event streams for building your own UI or observability |
--verbose | Required alongside stream-json for full turn-by-turn output |
System prompt flags — choosing correctly
| Flag | Effect |
|---|---|
--append-system-prompt "…" | Adds to the default. Use this by default — you keep tool guidance and safety instructions |
--append-system-prompt-file ./rules.txt | Same, from a file |
--system-prompt "…" | Replaces everything. You now own all guidance the task needs |
--system-prompt-file ./p.txt | Same, from a file |
Replace only when the agent isn't a coding assistant at all — e.g. a classification step in a pipeline no human watches. Otherwise append.
Pattern 1 — structured output instead of prose parsing
claude -p --bare \
--output-format json \
--json-schema '{
"type":"object",
"required":["risk","findings"],
"properties":{
"risk":{"type":"string","enum":["low","medium","high"]},
"findings":{"type":"array","items":{
"type":"object",
"required":["file","line","severity","issue"],
"properties":{
"file":{"type":"string"},"line":{"type":"integer"},
"severity":{"type":"string","enum":["crit","high","med","low"]},
"issue":{"type":"string"}}}}}}' \
--max-turns 8 --max-budget-usd 1.00 \
--allowedTools "Read" "Grep" "Glob" "Bash(git diff *)" \
"Review the staged diff for security issues." \
| jq -r '.risk'
Pattern 2 — the self-correcting loop
Give Claude a checkable goal and let it iterate until the check passes or the budget runs out. This is the shape behind most successful agentic automation.
#!/usr/bin/env bash
set -euo pipefail
MAX=5
for i in $(seq 1 $MAX); do
if make test >/tmp/test.log 2>&1; then
echo "✅ green on attempt $i"; exit 0
fi
echo "❌ attempt $i failed — dispatching Claude"
claude -p \
--permission-mode acceptEdits \
--allowedTools "Read" "Edit" "Grep" "Glob" "Bash(make test)" \
--max-turns 20 --max-budget-usd 2.00 \
--append-system-prompt "Fix only the failing tests. Do not change test
assertions to make them pass. Do not add dependencies." \
"The test suite fails. Here is the output:
$(tail -100 /tmp/test.log)
Diagnose the root cause and fix the source. Re-run \`make test\` to verify."
done
echo "still failing after $MAX attempts — escalating"; exit 1
Note the three guardrails: a bounded loop, an external verifier (make test, not Claude's opinion), and an explicit anti-cheat instruction. Agentic loops without an external verifier drift.
Pattern 3 — streaming events into your own harness
import json, subprocess
cmd = [
"claude", "-p", "--output-format", "stream-json", "--verbose",
"--include-partial-messages", "--forward-subagent-text",
"--max-turns", "25", "--max-budget-usd", "3.00",
"--allowedTools", "Read", "Grep", "Glob",
"Audit this service for unbounded queries.",
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, bufsize=1)
tool_calls, cost = [], 0.0
for line in proc.stdout: # newline-delimited JSON
if not line.strip():
continue
ev = json.loads(line)
kind = ev.get("type")
if kind == "assistant":
for block in ev["message"]["content"]:
if block.get("type") == "tool_use":
tool_calls.append(block["name"])
print("→", block["name"])
elif kind == "result":
cost = ev.get("total_cost_usd", 0.0)
print("done:", ev.get("subtype"), f"${cost:.4f}")
print("tools used:", tool_calls)
This is how you build dashboards, cost attribution, and trajectory evals (Module 16) on top of the CLI without touching the SDK.
Pattern 4 — multi-turn scripted conversations
# stage 1: plan, capture the session id
SID=$(uuidgen)
claude -p --session-id "$SID" --permission-mode plan \
--output-format json \
"Plan the migration to add tenant_id to all queries." | jq -r '.result' > plan.md
# human reviews plan.md here, or a second agent critiques it
# stage 2: resume that exact session to execute
claude -p -r "$SID" --permission-mode acceptEdits \
--max-turns 40 --max-budget-usd 10 \
"The plan is approved. Implement step 1 only, then stop."
--fork-session lets you branch from a resumed session without mutating the original — useful for trying two implementations from the same approved plan.
Pattern 5 — fan-out with worktrees
for svc in auth billing search; do
claude --bg -w "bump-$svc" \
--max-turns 30 --max-budget-usd 4 \
"In services/$svc, upgrade the http client to v3, fix all
call sites, and make the tests pass. Do not touch other services."
done
claude agents --json | jq -r '.[] | "\(.id) \(.status) \(.name)"'
Three isolated worktrees, three background sessions, no interference. Poll claude agents --json, collect with claude logs <id>, attach with claude attach <id> when one needs a human.
Pattern 6 — CI integration
# .github/workflows/claude-review.yml
name: Claude review
on: pull_request
jobs:
review:
runs-on: ubuntu-latest
permissions: { contents: read, pull-requests: write }
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: npm install -g @anthropic-ai/claude-code
- name: Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/${{ github.base_ref }}...HEAD > /tmp/diff.patch
claude -p --bare \
--settings .claude/ci-settings.json \
--strict-mcp-config \
--tools "Read,Grep,Glob" \
--max-turns 12 --max-budget-usd 1.50 \
--output-format json \
--json-schema "$(cat .claude/review-schema.json)" \
--append-system-prompt-file .claude/review-rules.md \
"Review /tmp/diff.patch against our checklist." \
| jq -r '.result' > review.json
- run: gh pr comment ${{ github.event.number }} \
--body-file <(jq -r '.summary' review.json)
--settings + --strict-mcp-config + a narrow --tools list makes the run reproducible regardless of what any developer has installed locally. --bare skips discovery for a faster, more predictable start. Use claude setup-token for subscription auth, or an API key for Console billing. And comment, don't commit, until the escape rate justifies more autonomy.Pattern 7 — scheduled automations
# crontab: nightly dependency-risk triage, 02:00
0 2 * * * cd /srv/repo && /usr/local/bin/claude -p --bare \
--tools "Read,Grep,Glob,Bash" \
--allowedTools "Bash(npm audit *)" "Bash(git log *)" \
--max-turns 15 --max-budget-usd 1 \
--output-format json \
"Run npm audit. For each high/critical finding, check whether the
vulnerable path is actually reachable from our entrypoints. Output
only genuinely reachable issues." \
>> /var/log/dep-triage.jsonl 2>&1
Other high-value cron jobs: flaky-test detection across last night's runs, stale-PR nudges, changelog drafting, docs drift checks (does the README still match the CLI?), and log-anomaly triage.
Setup hooks for CI
--init and --maintenance (print mode) and --init-only fire Setup hooks before the session — the right place for one-time environment preparation in a pipeline (warm caches, install deps, fetch fixtures) without baking it into your prompt.
Lab 12 — Build three automations ~90 min
- Structured reviewer (25 min). Write a JSON schema for review findings and a
claude -p --json-schemacall. Pipe tojqand exit non-zero when any finding iscrit. - Self-correcting loop (30 min). Break a test in a real repo. Write the bounded fix loop above. Verify it stops at the cap rather than looping forever, and that it doesn't "fix" the test by weakening the assertion.
- Streaming harness (20 min). Run the Python consumer against a real task. Log every tool call and the final cost. You now have trajectory data.
- Fan-out (15 min). Launch two background sessions in separate worktrees. Poll
claude agents --json, collect both results. - Record the per-task cost of each. That's your unit economics baseline.
Hooks in depth: deterministic control
Hooks are where you stop hoping and start enforcing. Every other mechanism asks the model to behave; hooks make behaviour structurally impossible to violate.
Stop hook that exits 2 while the linter fails. Compliance went to 100% that afternoon, and the CLAUDE.md line came out. That's the whole thesis of this module: if you find yourself repeating an instruction, you've found a hook.The three levels of nesting
{
"hooks": {
"PreToolUse": [ // 1. the EVENT
{
"matcher": "Bash", // 2. the MATCHER GROUP
"hooks": [ // 3. the HANDLERS
{ "type": "command",
"command": "./.claude/hooks/guard.sh",
"if": "Bash(git *)",
"timeout": 10,
"statusMessage": "Checking git safety…" }
]
}
]
}
}
Where hooks can live
| Location | Scope | Shareable |
|---|---|---|
~/.claude/settings.json | All your projects | No |
.claude/settings.json | One project | Yes — commit it |
.claude/settings.local.json | One project | No (gitignored) |
| Managed policy settings | Organization-wide | Yes — admin controlled |
Plugin hooks/hooks.json | While the plugin is enabled | Yes — bundled |
| Skill or agent frontmatter | While that component is active | Yes — in the component |
Hooks merge across levels rather than replacing each other, and they also fire inside subagents (the input carries agent_id and agent_type). Enterprise admins can set allowManagedHooksOnly to block user, project, and plugin hooks entirely — the governance lever from Module 17.
The event catalogue
Far more than the usual four. Grouped by what they're actually for:
Session lifecycle
SessionStart | Session begins or resumes. Matchers: startup, resume, clear, compact, fork |
Setup | Fires with --init-only, or --init/--maintenance in -p mode. One-time CI/script preparation |
SessionEnd | Session terminates. Shares a ~1.5s budget — keep it fast |
Prompt and turn
UserPromptSubmit | Before Claude sees your prompt. Can block and erase it. Inject context, redact secrets, enforce ticket refs |
UserPromptExpansion | A typed command expands into a prompt. Can block the expansion |
Stop | Claude finishes responding. Can refuse to let the turn end and hand control back with a reason — this is how you build "keep going until the build is green" |
StopFailure | Turn ended from an API error. Matchers include rate_limit, overloaded, billing_error |
MessageDisplay | While assistant text is displayed |
Tools and permissions
PreToolUse | Before a tool call. Can block |
PermissionRequest | A call needs a permission decision. Can deny |
PermissionDenied | Auto-mode denied a call. Return {retry:true} to let the model try again |
PostToolUse / PostToolUseFailure | After success / after failure. Stderr goes to Claude |
PostToolBatch | After a whole parallel batch resolves. Can stop the loop before the next model call |
Agents, tasks, and teams
SubagentStart / SubagentStop | Matcher is the agent type — Explore, Plan, your custom names, or ^my-plugin:reviewer$ |
TaskCreated / TaskCompleted | Can roll back a task creation, or refuse a completion until criteria are met |
TeammateIdle | An agent-team teammate is about to idle. Can keep it working |
Environment and context
InstructionsLoaded | A CLAUDE.md or .claude/rules/*.md loads |
ConfigChange | Config changed mid-session. Can block it |
CwdChanged | Working directory changed. Great with direnv |
DirectoryAdded | A directory was added via /add-dir |
FileChanged | A watched file changed. The matcher names the files, e.g. .envrc|.env |
WorktreeCreate / WorktreeRemove | Replace default git worktree behaviour |
PreCompact / PostCompact | Around context compaction. PreCompact can block it |
Elicitation / ElicitationResult | An MCP server asks the user something. Can deny or block the response |
Notification | Claude Code sends a notification |
Five handler types — not just shell scripts
| Type | What runs | Reach for it when |
|---|---|---|
command | A shell command; JSON on stdin, results via exit code + stdout | Default. Fast, local, testable |
http | POSTs the event JSON to a URL; the response body carries the decision | Centralized policy service, org-wide audit sink |
mcp_tool | Calls a tool on an already-connected MCP server | Reuse an existing integration as a guardrail |
prompt | Single-turn model evaluation returning a yes/no JSON decision | Judgment calls a regex can't make ("is this commit message meaningful?") |
agent | Spawns a subagent with Read/Grep/Glob to verify before deciding (experimental) | Checks needing evidence from the codebase |
All matching handlers run in parallel. Common fields on every type: timeout, statusMessage, once (skill frontmatter only), and if.
if uses permission-rule syntax — "Bash(git *)", "Edit(*.ts)" — and only applies on tool events. It's best-effort: it fails open when a Bash command can't be parsed. So use if to scope work, and the permission system to enforce a hard allow/deny.Command handlers also support async: true (runs in the background, doesn't block) and asyncRewake: true (background, but wakes Claude on exit code 2 and shows the output as a system reminder — how you surface a long-running background failure).
Two ways to signal a decision
A. Exit codes (simple)
0 → success, stdout added as context
1 → non-blocking error, execution continues
2 → BLOCKING error, stderr goes to Claude
What exit 2 blocks depends on the event: PreToolUse blocks the call · UserPromptSubmit erases the prompt · Stop/SubagentStop refuses to end the turn · PreCompact blocks compaction · TaskCompleted refuses the completion · PostToolUse can't block (the tool already ran) but its stderr reaches Claude.
B. JSON on stdout (precise)
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
Universal fields: continue (false stops Claude entirely, overriding event-specific decisions), stopReason, suppressOutput, systemMessage, and terminalSequence (allowlisted OSC escapes for desktop notifications and window titles — hooks have no /dev/tty). Event-specific control goes in hookSpecificOutput with a hookEventName.
Recipes worth stealing
1. Block reads of secrets
#!/usr/bin/env bash
# PreToolUse, matcher: "Read|Edit|Write"
p=$(jq -r '.tool_input.file_path // ""')
case "$p" in
*.env|*.env.*|*/secrets/*|*id_rsa*|*.pem)
echo "BLOCKED: $p is a secret-bearing path" >&2; exit 2 ;;
esac
exit 0
2. Auto-format after every edit
{ "PostToolUse": [{ "matcher": "Edit|Write",
"hooks": [{ "type": "command", "async": true,
"command": "jq -r '.tool_input.file_path' | xargs -r prettier --write" }] }] }
3. Don't let the turn end on a red build
#!/usr/bin/env bash
# Stop hook — exit 2 hands control back to Claude with a reason
if ! make test >/tmp/t.log 2>&1; then
echo "Tests are failing. Fix them before finishing:" >&2
tail -40 /tmp/t.log >&2
exit 2
fi
exit 0
This is the deterministic version of "verify your work" — and unlike an instruction, the model can't decide to skip it.
4. Inject fresh context on every prompt
#!/usr/bin/env bash
# UserPromptSubmit — stdout on exit 0 becomes context
echo "Current branch: $(git branch --show-current)"
echo "Uncommitted files: $(git status --porcelain | wc -l)"
echo "Open incidents: $(curl -s "$STATUS_API/count")"
exit 0
5. Model-judged commit messages (prompt hook)
{ "PreToolUse": [{ "matcher": "Bash",
"hooks": [{ "type": "prompt", "if": "Bash(git commit *)",
"prompt": "Does this git command have a commit message that explains WHY, not just what? Answer with a decision. Input: $ARGUMENTS",
"model": "haiku" }] }] }
6. Central audit sink (http hook)
{ "PostToolUse": [{ "hooks": [{ "type": "http",
"url": "https://audit.internal.example.com/claude-events",
"headers": { "Authorization": "Bearer ${AUDIT_TOKEN}" },
"allowedEnvVars": ["AUDIT_TOKEN"] }] }] }
HTTP hooks signal through the response: 2xx + JSON body uses the same schema as command hooks; non-2xx or a timeout is a non-blocking error. They can't block via status code alone — return 2xx with a decision in the body. Note the org-level allowlists allowedHttpHookUrls and httpHookAllowedEnvVars.
Managing hooks
/hooks— inspect and manage hooks in-session.claude -p --output-format stream-json --verbose --include-hook-events— see every hook firing in the event stream. Essential for debugging.disableAllHooks— kill switch (can't disable managed hooks from outside managed settings).claude --safe-mode— start with hooks and everything else off.
Lab 13 — Build a guardrail set ~55 min
- (15 min) Write the secret-path blocker. Verify Claude is genuinely denied, not merely discouraged.
- (15 min) Write a
Stophook that refuses to end the turn while your linter fails. Watch Claude self-correct in response. - (10 min) Write a
UserPromptSubmithook injecting branch + open-incident context. - (10 min) Add a
PostToolUseasyncformatter and confirm it doesn't slow the loop. - (5 min) Run with
--include-hook-eventsand confirm each fires exactly when you expect. - Package all four into the guardrail plugin from Module 11.
settings.json & the configuration system
CLAUDE.md shapes behaviour. Settings enforce it. This is the layer where an engineering leader actually gets control — and the layer most people never open.
The five scopes
| Scope | Location | Applies to | Committed? |
|---|---|---|---|
| Managed | Server-managed settings, MDM plist / registry, or system managed-settings.json | Everyone in the org / on the machine | Deployed by IT |
| Command line | --settings ./x.json or inline JSON | One session | — |
| Local | .claude/settings.local.json | You, in this repo | No — gitignored |
| Project | .claude/settings.json | Everyone on the repo | Yes |
| User | ~/.claude/settings.json | You, everywhere | No |
Precedence runs managed > command line > local > project > user. Managed can't be overridden by anything below it. Scalars override; permission rules merge across scopes rather than replacing each other — remember that, it surprises people.
File-based managed settings also support a managed-settings.d/ drop-in directory (systemd convention): the base file merges first, then *.json fragments alphabetically. Later files win on scalars, arrays concatenate and de-duplicate, objects deep-merge. This lets security and platform teams ship independent policy without editing one shared file.
A project settings file worth committing
// .claude/settings.json
{
"model": "claude-sonnet-5",
"permissions": {
"allow": [
"Bash(git status)", "Bash(git diff *)", "Bash(git log *)",
"Bash(make test)", "Bash(make lint)",
"Read", "Grep", "Glob"
],
"ask": [ "Bash(git push *)" ],
"deny": [
"Bash(rm -rf *)", "Bash(git push --force *)",
"Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"
],
"additionalDirectories": ["../shared-lib"],
"defaultMode": "acceptEdits"
},
"env": { "MAKEFLAGS": "-j8", "CI": "0" },
"hooks": { "PreToolUse": [ /* Module 13 */ ] },
"autoCompactWindow": "500k",
"claudeMdExcludes": ["**/monorepo/other-team/CLAUDE.md"],
"includeGitInstructions": true,
"cleanupPeriodDays": 30
}
Tool(pattern). A bare tool name in deny removes the tool from Claude's context entirely ("Edit" removes Edit; "mcp__*" removes every MCP tool). A scoped rule like Bash(rm *) leaves the tool available and denies only matching calls. Three lists: allow (runs without prompting), ask (always prompt even in permissive modes), deny (never).Settings worth knowing by name
Behaviour & models
model | Default model. Overridden by --model and ANTHROPIC_MODEL |
fallbackModel | Chain tried in order when the primary is overloaded or retired |
effortLevel | Persist low/medium/high/xhigh across sessions |
alwaysThinkingEnabled | Extended thinking on by default |
outputStyle | Adjust the system prompt with a named style |
agent | Run the main thread as a named subagent |
includeGitInstructions | Default true. Set false to drop built-in commit/PR guidance and the git status snapshot from the system prompt — a real token saving in non-git work |
Context & memory
autoCompactEnabled | Default true |
autoCompactWindow | How full context gets before auto-compaction, 100k–1M |
autoMemoryEnabled / autoMemoryDirectory | Auto memory on/off and where it's stored |
claudeMd | (Managed only) org-wide CLAUDE.md content inline |
claudeMdExcludes | Glob/absolute paths of CLAUDE.md files to skip |
skillListingBudgetFraction | Default 0.01 — fraction of the context window reserved for the skill listing |
skillListingMaxDescChars | Default 1536 — per-skill cap on description text |
skillOverrides | Per-skill visibility: on, name-only, user-invocable-only, off |
Safety & guardrails
permissions | allow / ask / deny / additionalDirectories / defaultMode |
hooks | Lifecycle automation (Module 13) |
disableAllHooks | Kill switch — can't disable managed hooks from outside managed settings |
fileCheckpointingEnabled | Default true — snapshots that make /rewind work |
disableAutoMode | Remove auto mode from the Shift+Tab cycle |
allowedHttpHookUrls / httpHookAllowedEnvVars | Constrain what HTTP hooks may reach and interpolate |
Enterprise levers (managed settings only)
allowManagedHooksOnly | Only managed/SDK/force-enabled-plugin hooks load |
allowManagedPermissionRulesOnly | Users and projects can't define permission rules |
allowedMcpServers / deniedMcpServers / allowManagedMcpServersOnly | MCP allowlist/denylist. Denylist wins |
strictKnownMarketplaces / blockedMarketplaces | Which plugin marketplaces may be used |
strictPluginOnlyCustomization | Skills, agents, hooks and MCP servers may come only from plugins |
disableSideloadFlags | Reject --plugin-dir, --plugin-url, --agents, --mcp-config at startup |
availableModels / enforceAvailableModels | Restrict selectable models |
forceLoginMethod / forceLoginOrgUUID | Lock authentication to your org |
requiredMinimumVersion / requiredMaximumVersion / minimumVersion | Version floors and ceilings |
policyHelper | Admin-deployed executable that computes managed settings dynamically at startup |
allowedMcpServers is enforced as an empty allowlist — no MCP servers admitted until you fix it. Same for availableModels. Validate your policy JSON in CI before it reaches machines, because the failure mode is "everyone's tooling stops working", not "the setting is ignored".The env block and environment variables
{ "env": {
"ANTHROPIC_MODEL": "claude-sonnet-5",
"MAX_THINKING_TOKENS": "8000",
"ENABLE_PROMPT_CACHING_1H": "1",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "500000",
"BASH_DEFAULT_TIMEOUT_MS": "120000"
} }
Applied to every session and to subprocesses Claude Code spawns. Set a variable to "" to override a shell export. Environment variables generally take precedence over the equivalent settings key — useful in CI, dangerous if a developer's shell profile is quietly overriding your project policy.
Debugging configuration
| Symptom | Tool |
|---|---|
| "Is my settings file even valid?" | claude doctor — reports settings-file validation errors without starting a session |
| "Which files actually loaded?" | /context — Memory files section |
| "Is a customization breaking things?" | claude --safe-mode — everything off, then bisect |
| "Which scope set this?" | --setting-sources user,project to load selectively and compare |
| "What are auto mode's rules?" | claude auto-mode defaults · claude auto-mode config |
| "Reset a mess" | claude auto-mode reset, or claude project purge --dry-run |
permissions.deny in managed settings — the hard floor nobody can lower; (2) strictPluginOnlyCustomization plus strictKnownMarketplaces — customization arrives only through your reviewed marketplace; (3) allowManagedHooksOnly — audit and secret-scanning hooks can't be switched off locally. Set those three and you can be permissive about everything else, which is what actually drives adoption.Lab 14 — Build your configuration stack ~50 min
- (15 min) Write a committed
.claude/settings.jsonfor a real repo: allow your safe read/test commands,askon pushes,denysecrets and destructive commands. Verify each deny actually blocks. - (10 min) Add a
.claude/settings.local.jsonwith a personal override and confirm precedence behaves as documented. - (10 min) Write a
ci-settings.jsonfor headless runs: minimal tools, no plugins, fixed model. Use it withclaude -p --settings ci-settings.json --strict-mcp-config. - (5 min) Deliberately corrupt a managed-style key and observe the fail-closed behaviour.
- (5 min) Run
claude doctorandclaude --safe-mode; note what each tells you. - (5 min) Draft the three managed settings you'd propose for your org, with a one-line justification each.
Claude Agent SDK in Python
The Agent SDK is the harness behind Claude Code, exposed as a library. You get the agent loop, the file/bash/search tools, permissions, subagents, hooks, and MCP integration — so you build the agent, not the plumbing.
pip install claude-agent-sdk
# requires the Claude Code CLI on PATH:
npm install -g @anthropic-ai/claude-code
Level 1 — one-shot: query()
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(
system_prompt="You are a release engineer. Be terse and precise.",
allowed_tools=["Read", "Grep", "Glob"],
cwd="/path/to/repo",
permission_mode="acceptEdits",
model="claude-sonnet-5",
)
async for message in query(
prompt="Summarize what changed in the last 20 commits, grouped by subsystem.",
options=options,
):
print(message)
anyio.run(main)
query() is stateless and streaming — perfect for scripts, CI steps, and batch jobs.
Level 2 — conversational + custom tools + hooks: ClaudeSDKClient
ClaudeSDKClient keeps a session alive and, importantly, is the only way to use custom in-process tools and hooks.
import anyio
from claude_agent_sdk import (
ClaudeSDKClient, ClaudeAgentOptions, tool, create_sdk_mcp_server
)
@tool("cost_estimate", "Estimate monthly cost for a service at a given RPS. "
"Use when the user asks what something will cost to run.",
{"service": str, "rps": int})
async def cost_estimate(args):
monthly = args["rps"] * 2_592_000 * 0.0000004
return {"content": [{
"type": "text",
"text": f"{args['service']}: ~${monthly:,.2f}/month at {args['rps']} rps",
}]}
tools_server = create_sdk_mcp_server(
name="finops", version="1.0.0", tools=[cost_estimate]
)
async def main():
options = ClaudeAgentOptions(
mcp_servers={"finops": tools_server},
allowed_tools=["Read", "Grep", "mcp__finops__cost_estimate"],
system_prompt="You are a platform cost analyst.",
)
async with ClaudeSDKClient(options=options) as client:
await client.query("What would the search service cost at 8000 rps?")
async for msg in client.receive_response():
print(msg)
# follow-up in the same session — context is retained
await client.query("And if we cut it to 3000?")
async for msg in client.receive_response():
print(msg)
anyio.run(main)
create_sdk_mcp_server runs tools in your Python process — no subprocess, no IPC, direct access to your app's objects and connection pools. Use it for tools only your agent needs. Use a real MCP server when other clients (the desktop app, teammates, other agents) should also reach the capability.Level 3 — hooks: deterministic control of the loop
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher
BLOCKED = ("rm -rf", "git push --force", "DROP TABLE")
async def guard(input_data, tool_use_id, context):
cmd = input_data.get("tool_input", {}).get("command", "")
if any(b in cmd for b in BLOCKED):
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked by policy: {cmd!r}",
}
}
return {}
async def audit(input_data, tool_use_id, context):
log.info("tool_call", extra={"tool": input_data.get("tool_name"),
"args": input_data.get("tool_input")})
return {}
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash"],
hooks={
"PreToolUse": [HookMatcher(matcher="Bash", hooks=[guard]),
HookMatcher(hooks=[audit])],
"PostToolUse": [HookMatcher(matcher="Write", hooks=[audit])],
},
)
Hooks require ClaudeSDKClient (not query()), and the Python SDK doesn't support SessionStart, SessionEnd, or Notification hooks. Use them for policy enforcement, audit logging, redaction, and injecting fresh context before a tool runs.
Level 4 — subagents and orchestration
options = ClaudeAgentOptions(
agents={
"researcher": {
"description": "Gathers facts from the codebase and docs. Read-only.",
"prompt": ("You gather evidence. Cite file:line for every claim. "
"Never speculate; say 'not found' instead."),
"tools": ["Read", "Grep", "Glob", "WebSearch"],
"model": "haiku",
},
"auditor": {
"description": "Independently verifies another agent's conclusions.",
"prompt": ("You are given a conclusion and the evidence. Attempt to "
"falsify it. Report every claim you could not verify."),
"tools": ["Read", "Grep"],
"model": "opus",
},
},
allowed_tools=["Task", "Read", "Grep"],
)
Model routing per subagent is where cost and quality get decided: Haiku for fan-out gathering, Sonnet for the main loop, Opus for the calls that are expensive to get wrong.
Permissions
| Mode | Behaviour | Use for |
|---|---|---|
default | Prompts for permission | Interactive dev |
acceptEdits | Auto-accepts file edits | Trusted repos, tight loops |
plan | Plans without executing | Review-before-act workflows |
bypassPermissions | No prompts | Isolated sandboxes only — never on a machine with credentials you care about |
You can also supply a can_use_tool callback for programmatic, per-call decisions — e.g. allow writes only under ./out/, or require a human approval for anything touching production.
Production concerns
- Streaming — consume messages incrementally; don't block on the whole run.
- Session resumption — persist session IDs so long tasks survive restarts.
- Timeouts and turn caps — always bound
max_turns. An unbounded agent loop is an unbounded bill. - Structured output — ask for JSON to a file and parse the file, rather than parsing prose.
- Observability — log every tool call, token count, and turn. Hooks are the natural place.
- Idempotency — assume any run may be retried. Design tools accordingly.
Lab 15 — Build a real agent ~90 min
Build a PR triage agent:
- Input: a repo path and a diff. Output: JSON at
./out/triage.json. - Use a
researchersubagent (Haiku) to find related code and prior incidents. - Main loop (Sonnet) produces findings; an
auditorsubagent (Opus) attempts to falsify each finding. - Custom in-process tool:
lookup_owner(path)reading your CODEOWNERS. - PreToolUse hook: deny any Bash command containing a network call; audit-log everything.
- Cap
max_turns. Log total tokens. Run it on 5 real PRs and measure precision of findings.
The Claude API & platform
The Agent SDK gives you an agent. The API gives you the primitives — and when you're building product, you often want the primitives. This module is the layer beneath everything else in the course.
Messages API — the base
from anthropic import Anthropic
client = Anthropic()
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=2000,
system="You are a release engineer. Be terse.",
messages=[{"role": "user", "content": "Summarize this changelog: ..."}],
)
print(resp.content[0].text)
print(resp.usage.input_tokens, resp.usage.output_tokens)
Everything else is a variation: streaming (client.messages.stream), extended thinking, vision (image blocks), and tool use.
Tool use — the manual loop
tools = [{
"name": "get_deploy_status",
"description": ("Get current deploy status for a service. Use when asked "
"whether a service is healthy or what version is live."),
"input_schema": {
"type": "object",
"properties": {
"service": {"type": "string", "description": "Service slug"},
"env": {"type": "string", "enum": ["prod", "staging", "dev"]},
},
"required": ["service"],
},
}]
messages = [{"role": "user", "content": "Is payments-gateway healthy?"}]
while True:
resp = client.messages.create(
model="claude-sonnet-5", max_tokens=2000,
tools=tools, messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
break
results = []
for block in resp.content:
if block.type == "tool_use":
out = dispatch(block.name, block.input) # your code
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(out),
})
messages.append({"role": "user", "content": results})
print(resp.content[-1].text)
Tool Runner (client.beta.messages.tool_runner) runs this loop over your own Python functions so you don't hand-roll it. Managed Agents go further: server-hosted agents with a managed sandbox, when you'd rather not operate the harness at all.
Choosing your altitude
| You want… | Use |
|---|---|
| Full control of every token and turn | Messages API + manual loop |
| The loop handled, your own tools | Tool Runner |
| Claude-Code-grade agent in your app: files, bash, subagents, hooks, permissions | Agent SDK (Module 14) |
| Server-hosted agent with a managed sandbox | Managed Agents |
| A pipeline step, no code | claude -p (Module 12) |
Go one level lower than the default only when you have a concrete reason. Most teams over-build here.
Prompt caching — the biggest cost lever after routing
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=1500,
system=[
{"type": "text", "text": "You are a compliance analyst."},
{"type": "text",
"text": POLICY_HANDBOOK, # 60k tokens, stable
"cache_control": {"type": "ephemeral"}}, # ← cache breakpoint
],
messages=[{"role": "user", "content": question}],
)
Rules that matter: put stable content first (system prompt, schemas, long documents), volatile content last. Cache hits are dramatically cheaper than fresh input tokens, and cache writes cost slightly more than normal input — so caching pays off from roughly the second call onward. In Claude Code, --exclude-dynamic-system-prompt-sections exists precisely to keep the cacheable prefix identical across machines.
Batch, files, and long context
- Message Batches — submit many requests for asynchronous processing at a substantial discount. Use for backfills, bulk classification, and eval suites where latency doesn't matter.
- Files API — upload once, reference across many requests instead of re-sending bytes.
- Citations — have the model ground claims in supplied documents with references you can verify. Pairs well with the "require citations" pattern from Module 16.
- Structured outputs / JSON schema — same discipline as
--json-schema: declare the shape, don't parse prose.
Model routing, concretely
| Model | ID | Route here when |
|---|---|---|
| Opus 5 | claude-opus-5 | Architecture, ambiguous debugging, long-horizon planning, final adjudication |
| Sonnet 5 | claude-sonnet-5 | The main agent loop, coding, most tool use — your default |
| Haiku 4.5 | claude-haiku-4-5-20251001 | Fan-out gathering, extraction, classification, routing, high-volume |
| Fable 5 | claude-fable-5 | Where its profile fits your workload — measure before committing |
Also relevant: fallback chains (--fallback-model sonnet,haiku in the CLI, retry logic in your own code) so an overloaded model degrades rather than fails.
Production hygiene
- Retries with jitter on 429/529. Respect
retry-after. - Streaming for anything a human waits on.
- Token accounting per request, attributed to a feature or team. You cannot manage spend you can't attribute.
- Idempotency keys on anything that triggers a side effect.
- Never put untrusted content in the system prompt. User and tool content belongs in messages, clearly demarcated.
- Deployment options — the API directly, or through Amazon Bedrock, Google Cloud's agent platform, or Microsoft Foundry when procurement or data residency requires it.
Lab 16 — Three altitudes, one task ~55 min
- (20 min) Implement a small tool-using task with the raw Messages API loop. Count the turns and tokens yourself.
- (10 min) Reimplement with Tool Runner. Note what disappeared.
- (10 min) Reimplement with the Agent SDK. Note what you gained (file tools, permissions, hooks) and what it cost in control.
- (15 min) Add prompt caching to the first version with a large stable prefix. Measure input-token cost across three calls, before and after.
- Write two sentences on which altitude you'd choose for a real product feature, and why.
Tokens, context, performance & limits
This is the module that separates people who use Claude from people who can operate it. Everything here is measurable, and almost all of it is under your control.
Where your context actually goes
Before you type a single character, the window already contains: the system prompt, CLAUDE.md and rules, auto memory, MCP tool names, skill descriptions, and any output style or --append-system-prompt text. Then as Claude works: every file read, every tool result, every path-scoped rule that matched, every hook output.
/context right now, in your real repo. It gives a live breakdown by category with optimization suggestions and lists exactly which CLAUDE.md and auto memory files loaded. Most people discover 20–40% of their standing context is something they'd happily delete. You cannot optimize what you haven't measured.Compaction
When the conversation approaches the limit, Claude Code summarizes older history to free space. A full window doesn't end your session — but compaction is itself a large, expensive request, because it reads everything it summarizes.
Controlling it
| Lever | Effect |
|---|---|
/compact focus on the auth bug fix | Summarize with your priorities, not its guess |
/autocompact 500k | Compact earlier; saved to autoCompactWindow in user settings |
claude --autocompact 500k | One launch only; not preempted by managed settings |
CLAUDE_CODE_AUTO_COMPACT_WINDOW | Takes precedence over all of the above — for scripts and cloud |
/clear | Fresh start, costs nothing. Use between unrelated tasks |
| Compact instructions in CLAUDE.md | A # Compact instructions section steers every summarization |
Accepted window forms: a plain count (200000), a suffix (500k, 1M), or a bare 100–1000 meaning thousands. Range 100K–1M, capped at the model's window. Fable 5, Sonnet 5, Opus 4.6+ and Sonnet 4.6 support a 1M-token context window.
/clear vs /compact. Compaction preserves continuity but costs a large request. Clearing costs nothing but loses everything. If you're switching to unrelated work, /clear is strictly better — and it's the habit that most reduces surprise usage. Use /rename first so you can /resume the old session later.Why usage climbs in a long session
A session open for hours can consume far more than your activity suggests. The causes, in rough order of impact:
| Cause | What's happening | Fix |
|---|---|---|
| Long context | Your full conversation is re-sent with every request, and each tool batch is another request carrying it again | /clear between tasks; delegate to subagents |
| Cache misses | Your first message after a break longer than the cache lifetime reprocesses everything. Lifetime is ~1 hour on a subscription, 5 minutes on usage credits or an API key | ENABLE_PROMPT_CACHING_1H=1; resume from a summary |
| Compaction itself | Reads the whole conversation it summarizes | Prefer /clear when you don't need continuity |
| Scheduled tasks | Fire on their interval even while idle, sending full context each time | Audit what's scheduled |
| Agent teammates | Each keeps consuming until it exits | Shut them down when done |
| Cross-session messages | Delivered as a new turn when idle, sending full context | crossSessionInbound: "hold" |
Background usage is small but nonzero — conversation summarization for --resume and status commands typically run under $0.04 per session.
Measuring: /usage
Total cost: $0.55
Total duration (API): 6m 20s
Total duration (wall): 6h 33m 10s
Usage by model:
claude-sonnet-…: 1.2k input, 5.3k output, 940.0k cache read, 50.0k cache write
Note the shape of a real session: 940k cache reads against 1.2k fresh input. That ratio is the whole game — caching is doing almost all the work, and a cache miss is what makes a cheap turn suddenly expensive.
- On paid plans,
/usagealso shows attribution — recent usage broken down by skill, subagent, plugin and individual MCP server — and behavior flags for anything accounting for ≥10% of usage, like long context or cache misses. Pressd/wto toggle 24 hours vs 7 days. - The dollar figure is computed locally at list rates, so it won't match a contracted bill. For authoritative numbers use the Console usage page.
- Totals reset on
/clear. - Configure your status line to show context usage continuously so you never have to ask.
The optimization playbook, ordered by impact
- Clear between unrelated tasks. Free, and the single biggest lever on a long day.
- Match the model to the job. Sonnet handles most coding; reserve Opus for architecture and multi-step reasoning;
model: haikufor simple subagents. Opus-left-as-default is one of the two classic causes of surprise spend. - Move workflow instructions out of CLAUDE.md into skills, and detail into path-scoped rules. Standing context is paid on every turn.
- Delegate verbose operations to subagents — test runs, log processing, doc fetching. The output stays in their window; only a summary returns.
- Cut MCP overhead. Tool definitions are deferred by default so only names load until used, but names still cost. Disable unused servers via
/mcp. Prefer CLI tools (gh,aws,gcloud) where they exist — they add no per-tool listing at all. - Tune extended thinking. Thinking tokens bill as output and the default budget can be tens of thousands per request. Lower with
/effort, disable in/config, or setMAX_THINKING_TOKENS=8000on fixed-budget models. Adaptive-reasoning models ignore nonzero budgets — use effort levels there. - Preprocess with hooks. The highest-craft move in this list.
- Write specific prompts. "Improve this codebase" triggers broad scanning; "add input validation to the login function in auth.ts" doesn't.
- Use plan mode for complex tasks. Preventing one wrong implementation pays for a lot of planning.
- Install code intelligence plugins for typed languages — "go to definition" replaces a grep plus several speculative file reads.
The hook that pays for itself
Instead of Claude reading a 10,000-line log to find errors, a PreToolUse hook rewrites the command so only failures come back — tens of thousands of tokens down to hundreds:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
if [[ "$cmd" =~ ^(npm test|pytest|go test) ]]; then
filtered="$cmd 2>&1 | grep -A 5 -E '(FAIL|ERROR|error:)' | head -100"
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\
\"permissionDecision\":\"allow\",\"updatedInput\":{\"command\":\"$filtered\"}}}"
else
echo "{}"
fi
Verify with /hooks, then claude --debug and run a test command — the log shows modified tool input keys: [command] when the rewrite fires.
Limits: which ceiling did you hit?
| Message | What it is | What to do |
|---|---|---|
| "You've hit your session limit" / "weekly limit" | A seat-based usage window on a subscription plan, rolling 5-hour and weekly, shared across models and across Claude chat, Code and Cowork | Switching model with /model won't restore it. Wait for the reset shown, or /usage-credits |
| "You've hit your Opus limit" | Model-specific | Switch model and keep working |
| A context or auto-compact warning | Not a usage limit — the conversation is approaching the compaction threshold | Apply the playbook above |
| Unexpectedly high API/cloud spend | Usually long sessions never cleared, or Opus left as default | The first two items of the playbook |
Cost at organization scale
- Published enterprise averages: roughly $13 per developer per active day and $150–250 per developer per month, with 90% of users under $30 per active day. Pilot small, measure, then extrapolate — don't budget from a vendor page.
- Budget more for a coding seat than a chat seat. Each Claude Code turn carries file contents, tool calls and multi-step reasoning; one debugging session can exceed a day of chat.
- Agent teams use roughly 7× the tokens of a standard session when teammates run in plan mode — each is a separate instance with its own window. Keep teams small, use Sonnet for teammates, keep spawn prompts tight, shut them down when done.
Where to see and cap spend
| Setup | See spend | Cap spend | Per-user reporting |
|---|---|---|---|
| Teams / Enterprise | Spend report in org analytics | Seat allowance + spend limits on usage credits | Spend report CSV; Enterprise Analytics API |
| Console (API) | Console usage page | Workspace spend limits | Console dashboard; Claude Code Analytics API |
| Bedrock / GCP / Foundry | Your cloud billing console | Cloud budget controls | OpenTelemetry or an LLM gateway |
OpenTelemetry export works on every setup and is the only option that streams per-user token and cost metrics into your own observability stack in near real time. If you're serious about attribution, start there.
Rate limit sizing (per user, API organizations)
| Team size | TPM per user | RPM per user |
|---|---|---|
| 1–5 | 200k–300k | 5–7 |
| 5–20 | 100k–150k | 2.5–3.5 |
| 20–50 | 50k–75k | 1.25–1.75 |
| 50–100 | 25k–35k | 0.62–0.87 |
| 100–500 | 15k–20k | 0.37–0.47 |
| 500+ | 10k–15k | 0.25–0.35 |
Per-user allocation drops as the org grows because concurrency falls. Limits apply at the organization level, so individuals can burst above their share. Budget extra for live training sessions, where concurrency spikes hard. Also consider a workspace rate limit on the auto-created "Claude Code" workspace so it can't starve your production workloads.
/context showed 31k tokens of standing context before anyone typed: an 800-line CLAUDE.md, eleven enabled MCP servers, and 40 skills from a monolithic plugin. They moved workflow sections into skills and detail into path-scoped rules (CLAUDE.md → 90 lines), disabled six unused connectors, split the plugin by domain, and set Sonnet as the default instead of Opus. Standing context fell to about 9k, and per-task cost dropped roughly 4×. No prompts were rewritten and no one changed how they worked.Lab 17 — Measure, then optimize ~60 min
- (10 min) Run
/contextin your main repo. Write down the total and the breakdown by category. This is your baseline. - (15 min) Apply three optimizations: trim CLAUDE.md into path-scoped rules, disable unused MCP servers, and set a sensible default model. Re-run
/contextand record the delta. - (10 min) Run a representative task before and after; compare
/usagetotals. Compute per-task unit cost both ways. - (15 min) Write the test-output filtering hook. Run a failing test suite with and without it and compare the token cost of that single tool call.
- (5 min) Set
/autocompactto a deliberate value and explain to yourself why that number. - (5 min) Check
/usageattribution: which skill, subagent, plugin or MCP server is actually consuming your budget? Is that where you'd want it?
Agent architecture patterns & evaluation
Patterns, and when each is right
1. Single agent with tools
One loop, a handful of tools. Default choice. Most "we need a multi-agent system" instincts are premature. Reach for more structure only when this demonstrably fails.
2. Router
A cheap classifier (Haiku) picks a specialist path. Good when you have distinct task families with different tools or system prompts, and cost matters.
3. Orchestrator + workers (fan-out/fan-in)
A coordinator decomposes, dispatches N workers in parallel, synthesizes. Good for genuinely independent subtasks — research across sources, per-file analysis, multi-vendor comparison. Cost scales with N; make sure the parallelism is real.
4. Evaluator–optimizer
Generator produces, a separate critic scores against explicit criteria, loop until pass or budget exhausted. Excellent where quality is checkable and iteration is cheap: code that must pass tests, documents that must satisfy a rubric.
5. Pipeline / prompt chaining
Fixed stages with validation between them. Use when the steps are known and stable — you get determinism and debuggability that an open loop can't give you.
Context management at scale
- Compaction — summarize completed phases; keep the summary, drop the transcript.
- Externalize state — write findings to files, not to context. The filesystem is your long-term memory and it's free.
- Structured handoffs — subagents should return a defined shape (JSON), not prose. You control what re-enters the parent's context.
- Prompt caching — put stable prefixes (system prompt, schemas, long reference docs) first so they're cacheable across calls.
- Tool result trimming — cap tool output size at the tool, not in the prompt.
Evaluation, properly
You can't manage what you don't measure, and agent quality drifts with every prompt, skill, model, and tool change. Build the eval before you scale the agent.
Four layers
- Unit — tools and scripts, tested as ordinary code.
- Selection — given a prompt, does it choose the right tool/skill/subagent? Cheap, catches most regressions.
- Trajectory — did it take a sane path? Turn count, tool-call count, redundant calls, dead ends.
- Outcome — is the final artifact correct? Binary criteria, graded programmatically where possible, by an LLM judge with a rubric where not.
Metrics to track per release
| Metric | Why it matters |
|---|---|
| Task success rate (binary, n≥3 runs/case) | The headline number |
| Tool-selection accuracy | Leading indicator of description rot |
| Median & p95 turns | Efficiency and runaway detection |
| Tokens in / out per task | Unit economics |
| Human-intervention rate | The real automation level |
| Escape rate (bad output that shipped) | The number your stakeholders care about |
Failure modes to design against
| Failure | Mitigation |
|---|---|
| Loops / repeated identical tool calls | Turn cap; detect repeats in a hook and inject a nudge |
| Confident fabrication | Require citations (file:line, URL); an auditor pass that tries to falsify |
| Context exhaustion mid-task | Externalize state to files; compact between phases |
| Silent partial completion | Explicit done-criteria + a verification step that checks them |
| Tool errors swallowed | Return actionable error text; never return empty on failure |
| Prompt injection via tool results | Label untrusted content; least-privilege tokens; human gate on writes |
Lab 18 — Evaluate the Lab 15 agent ~50 min
- Write 12 eval cases: 8 typical, 4 adversarial (ambiguous diff, huge diff, diff with a planted injection string in a comment).
- Run each 3×. Record success rate, tool-selection accuracy, median turns, tokens.
- Change one thing (model routing, or a tool description). Re-run. Did the numbers move as you predicted?
- Write a one-page eval report — the kind you'd put in front of a VP to justify rolling this out.
Security, governance, and cost
Threat model for agentic systems
| Threat | Vector | Control |
|---|---|---|
| Prompt injection | Untrusted text in a tool result, file, web page, ticket, email | Least-privilege tokens; human gate on writes; label untrusted content; never auto-run injected commands; separate read/write servers |
| Excessive agency | Agent has more permission than the task needs | Scoped tokens per server; allowed_tools allowlists; can_use_tool callbacks; path restrictions |
| Data exfiltration | Agent reads sensitive data and writes it somewhere reachable | Deny network egress in hooks; restrict write paths; DLP scan on outputs; audit logging |
| Supply chain | A third-party plugin or MCP server | Internal marketplace only; review before publishing; pin versions; read the server source |
| Secret leakage | Secrets in context, logs, or committed files | PostToolUse secret scan; never put secrets in plugin manifests; redact in hook logging |
| Unbounded cost | Runaway loops, unnecessary Opus, huge contexts | Turn caps; model routing; prompt caching; per-team budgets and alerts |
.env, a hook denies reads of secret-bearing paths outright, and comment bodies are posted only after a human approves. Note that "use a better prompt" appears nowhere in that list.Governance you can actually operate
- Tiered autonomy. Tier 0: suggests only. Tier 1: acts in a sandbox. Tier 2: acts in prod with human approval. Tier 3: acts autonomously with audit. Promote workflows between tiers on evidence, not enthusiasm.
- An internal marketplace as the control point. Everything the org uses ships through it; that gives you review, versioning, and an inventory.
- A mandatory guardrail plugin everyone installs: hooks for secret scanning, dangerous-command blocking, and audit logging.
- Audit trail. Log tool calls, args, and outcomes centrally. If you can't answer "what did the agent do last Tuesday," you're not ready for tier 3.
- Data classification. Be explicit about which connectors may touch which classes of data, and enforce it via which plugins a group can install.
- Human accountability stays human. Someone signs off. "The agent did it" is not a control.
Cost engineering
- Model routing is the biggest lever. Haiku for fan-out and extraction, Sonnet for the main loop, Opus reserved for high-stakes reasoning. A well-routed system is often 5–10× cheaper than an all-Opus one with no measurable quality loss.
- Prompt caching for stable prefixes — large system prompts, schemas, reference docs.
- Context discipline — retrieval over stuffing; compaction between phases; trimmed tool results.
- Bound the loop — turn caps and timeouts.
- Measure per-task unit cost, not monthly spend. "$0.34 per PR reviewed" is a number you can reason about and defend; "$9k/month" isn't.
Measuring value honestly
Resist "hours saved" — it's unfalsifiable and everyone knows it. Better instruments:
- Cycle time on a defined workflow, before and after.
- Escaped-defect rate for reviews the agent participated in.
- Throughput of a queue that was previously the bottleneck.
- Adoption depth — % of the team using it weekly for a defined workflow, not % who tried it.
- Human-intervention rate trending down over releases.
Lab 19 — Threat model and budget one workflow ~40 min
- Take the Lab 15 agent. Write a one-page threat model using the table above: what could an attacker who can write into your inputs achieve?
- Implement two controls you don't currently have.
- Instrument token counts. Compute per-task unit cost. Then re-route one subagent to a cheaper model and re-measure both cost and success rate.
- Assign the workflow an autonomy tier and write the criteria for promoting it to the next one.
Director's playbook: rollout & org design
Everything above makes you effective. This module is about making an organization effective, which is a different problem with different failure modes.
The maturity curve
| Stage | Looks like | Ceiling | Move to next by… |
|---|---|---|---|
| 0 · Individual | People use chat ad hoc; prompts shared in Slack | Highly variable; no compounding | Identifying 3 workflows worth codifying |
| 1 · Codified | CLAUDE.md in repos, a handful of team skills | Skills rot; nobody owns them | Putting skills in git with owners and review |
| 2 · Distributed | Internal marketplace, plugins per domain, guardrail plugin | Adoption plateaus without enablement | Measurement + enablement program |
| 3 · Integrated | MCP layer over internal systems; agents in CI; evals in CI | Governance and cost pressure | Autonomy tiers, budgets, audit |
| 4 · Platform | AI capability is a platform team's product with SLOs | — | — |
postmortem skill with an eval suite, and one guardrail plugin — then shipped them through an internal marketplace. Six months later they could say "postmortems now land within 24 hours instead of 5 days, 90% of the team uses the skill weekly, and it costs $0.60 each." Org B didn't have better people. They built a platform instead of running a class.The three artifacts that unlock stage 2→3
- An internal marketplace repo. One git repo, plugins per domain, PR review. This is the single highest-leverage thing you can fund. It gives you distribution, versioning, rollback, and inventory in one move.
- A guardrail plugin everyone installs. Secret scanning, dangerous-command blocking, audit logging. Non-optional. It's what lets you say yes to everything else.
- An MCP layer over your top 5 internal systems. Deploy status, service catalog, incident history, ticket system, metrics. Read-only first. This is where "Claude knows about our company" actually comes from — not from a bigger context window.
Where to start: picking the first three workflows
Score candidates on four axes; pick the highest total, not the flashiest:
| Axis | Question |
|---|---|
| Frequency | Does this happen weekly or more? |
| Standardization | Is there a known right way people get wrong? |
| Verifiability | Can you tell programmatically whether the output is good? |
| Blast radius | If it's wrong, is it recoverable and visible? |
Strong first candidates for a software org: PR review against a checklist · incident postmortems · ADR drafting · on-call handoff summaries · dependency-upgrade triage · release notes · test-gap analysis. Weak first candidates: anything customer-facing, anything unverifiable, anything requiring write access to prod.
Roles you need
- Skill/plugin owner per domain (part-time, embedded). Owns descriptions, evals, changelog.
- Platform owner for the marketplace, MCP layer, and guardrails. This should be a real, staffed responsibility, not a volunteer.
- An evaluation habit — whoever owns a skill owns its eval suite. No eval, no promotion to org-wide.
Enablement that works
- Teach mechanisms, not prompts. A person who understands skill-vs-hook-vs-MCP will out-perform one with a prompt library forever.
- Ship the workflow, then teach it. "Here's
/postmortem, it produces our exact format" beats a prompting workshop. - Make trigger phrases discoverable. READMEs that say what to type.
- Office hours > documentation in the first 90 days.
- Celebrate a codified workflow, not a clever prompt. You're shaping what people invest in.
Anti-patterns to kill early
| Anti-pattern | Why it fails |
|---|---|
| The 3000-line CLAUDE.md | Burns context every turn; nobody maintains it; quality drops for unrelated work |
| 40 skills in one plugin | Descriptions collide; routing precision collapses |
| A "prompt library" wiki page | Decays instantly; no versioning; no triggering |
| Enabling every connector for everyone | Context bloat plus a wide, ungoverned attack surface |
| Autonomy before evaluation | You can't tell whether it's working, so you can't defend it when it fails |
| Measuring "hours saved" | Unfalsifiable; erodes credibility with finance and with engineers |
| Mandating usage | Produces compliance theatre. Make it obviously better instead |
Talking to your leadership
Frame it as platform investment with unit economics, not as a productivity miracle:
- "We codified our review checklist as an agent. Escaped defects on participating PRs are down X%. Unit cost is $Y per PR."
- "We built an MCP layer over deploy + incident data. Time to answer 'what changed' went from 20 minutes to 40 seconds."
- "Autonomy is tiered. Nothing writes to production without a human approval. Here's the audit trail."
Lab 20 — Write the plan ~45 min
- Score 8 candidate workflows on the four axes. Pick three.
- Draft the internal marketplace structure: which plugins, which owners.
- Specify the guardrail plugin: exactly which hooks, and what each blocks.
- Name the 5 internal systems for the MCP layer, in priority order, with the read-only tool list for #1.
- Define the autonomy tiers and the promotion criteria between them.
- Define 3 metrics you'll report monthly. None of them may be "hours saved."
Capstone, 30/60/90, and reference
Capstone: ship an end-to-end internal capability
Build one thing that exercises every layer. Suggested: an engineering intelligence capability for your org.
- MCP server (Module 9) — read-only tools over your deploy/incident/service-catalog data. At least 4 tools, 1 resource, 1 prompt. Docstrings written as the sole documentation.
- Skills (Modules 7–8) — three:
incident-postmortem,service-health-brief,adr. Each with a validating script and a hard gate. - Subagent (Modules 11 & 15) — an independent auditor that falsifies claims in generated briefs.
- Hooks (Module 13) — secret scan, dangerous-command block, tool-call audit log.
- Plugin + marketplace (Module 10) — package all of it; install from a clean profile.
- Scheduled task (Module 4) — Monday 07:00 service health brief saved to the team folder.
- Live artifact (Module 4) — a page the team opens to see current state.
- Eval suite (Modules 8 & 18) — trigger accuracy + outcome criteria, run 3× per case, results in a report.
- Threat model + unit cost (Modules 17 & 19) — one page each.
- Rollout plan (Module 20) — owners, autonomy tier, metrics.
Your 30/60/90
Days 1–30 — become the proof
- Complete Modules 0–8. Ship two personal skills you use daily.
- Write
CLAUDE.mdfor your main repo. Build your memory layer. - Audit and prune connectors.
- Pick the three org workflows (Lab 15) and socialize the list.
Days 31–60 — build the platform floor
- Complete Modules 9–14. Ship the first read-only MCP server over one internal system.
- Stand up the internal marketplace with one plugin.
- Ship the guardrail plugin; make it the default install.
- Codify workflow #1 as a skill with an eval suite. Get 3–5 people using it.
Days 61–90 — measure and scale
- Complete Modules 15–20. Put evals in CI for load-bearing skills.
- Ship workflows #2 and #3; name owners for each domain plugin.
- Define autonomy tiers; promote one workflow with evidence.
- Report three real metrics upward, with unit economics.
Reference — the decision tree, condensed
Always true here? → CLAUDE.md
Recurring task procedure? → Skill
Needs external data/actions? → MCP server
Needs isolation/parallelism? → Subagent
Must happen 100% of the time? → Hook
Ship to a team as one unit? → Plugin + marketplace
Runs on a cadence? → Scheduled task
Re-checked over time? → Live artifact
Embedded in your own product? → Agent SDK
Reference — prompt skeletons
Agentic task
Goal: <outcome>
Inputs: <where the material is>
Constraints: <audience, format, length, exclusions>
Deliverable: <exact file(s) and path>
Verify: <concrete self-check>, and report what you verified.
Technical decision
<context>scale, stack, constraints</context>
<task>the decision to make</task>
<constraints>hard requirements</constraints>
<output_format>options table, recommendation, consequences, what would
change my mind</output_format>
Before recommending, list the 2 strongest arguments against your choice.
Review
Review <target> for: (a) ..., (b) ..., (c) ...
For each finding: location · severity · concrete fix.
Ignore <out of scope>. Report "no findings in scope" rather than padding.
Reference — skill frontmatter template
---
name: kebab-case-name
description: <what it does>. Use when <trigger 1>, <trigger 2>,
<trigger 3>, or when the user says <literal phrasing>.
Do NOT use for <negative boundary 1> or <negative boundary 2>.
---
Reference — official docs
- docs.claude.com — product and API documentation
- support.claude.com — Claude apps & Cowork help center
- code.claude.com/docs — Claude Code, plugins, marketplaces
- Agent SDK overview
- Prompt engineering guide
- modelcontextprotocol.io — MCP spec
- claude-agent-sdk-python
- anthropics/skills — open-source reference skills
- claude-plugins-official
- Steering Claude Code: CLAUDE.md, skills, hooks, subagents
Final knowledge check
Final certification exam
This is the end-to-end check. Questions are drawn at random across all twenty-two modules and the options are reshuffled every attempt, so passing means you actually hold the material rather than having memorised an ordering.