← tools

Claude Code Playbook

The setup I run every day as Chief Agent Officer at the Marketing + Media Alliance, written for experienced users and for anyone configuring Claude Code for a team. Architecture, the settings that matter and why, context cost, routing work to Codex and Gemini, subagents, hooks, security, and 34 lessons that each cost something to learn.

This page mirrors the GitHub repo, which is the source of truth. Version-sensitive claims carry the version and date they were checked. Found something stale? Open an issue.


If you change three things this week, change these.

  1. 01 Give it a check it can run Tests, a build, a screenshot, a re-query. Claude stops when the work looks done, so hand it a pass/fail signal and ask for the evidence, not the claim. read the section →
  2. 02 Spend context like money Every connected MCP server loads its tool schemas every turn. Profile the tool roster per project and move reference material out of CLAUDE.md into skills. read the section →
  3. 03 Pin every worker's model Ad-hoc and general-purpose subagents fall back to the session model unless something pins them, so an Opus session runs grep work on Opus. Pin model and effort in agent definitions. read the section →

  1. [01] Operating principles Five rules the rest follows from: one interface, persistent context, proactive surfacing, and two more.
  2. [02] Three-layer architecture Personal repo, team template, org plugin. Which knowledge lives in which layer, and which way changes flow.
  3. [03] Configuration layout and key settings What sits in ~/.claude and <repo>/.claude, and the settings.json keys worth changing, with the reason for each.
  4. [04] MCP strategy Connectors versus local servers, what tool schemas cost per turn, and the URL collision that hides a second account.
  5. [05] Adaptive MCP profiles per project A SessionStart hook that swaps the tool roster by project, so a coding session carries no mail tools.
  6. [06] Multi-model orchestration Codex through a flag-owning wrapper, Gemini for big reads, quota-aware routing, and cross-family review.
  7. [07] Skills How skills load, descriptions that trigger when they should, and how to retire one without deleting it.
  8. [08] Subagents When to spawn, pinning model and effort, the built-in-agent trap, return formats, and nesting limits.
  9. [09] Rules, skills, and hooks as enforcement layers Four enforcement layers from soft to hard. A rule is a nudge; a hook is a gate.
  10. [10] Permissions, secrets, and the security model Auto mode plus an allowlist, org-wide rules in server-managed settings, secrets in a password manager.
  11. [11] Statusline for rate limits and context Real 5-hour and weekly rate-limit use, context in use, and effort level, on two lines.
  12. [12] Remote control, notifications, and agent teams Steer a desk session from a phone, get pinged when agents finish, and run named agent teams.
  13. [13] Memory and session continuity Auto-memory as the continuity layer, why end-of-session rituals went, and an append-only HANDOFF.md.
  14. [14] Verification and adversarial review Give the model a way to see the result. Then a reviewer, ideally from another model family, tries to refute it.
  15. [15] Lessons learned 34 dated lessons across architecture, multi-model work, MCP, subagent economics, security, and effort.

Operating principles

I treat Claude Code as an operating layer that sits between me and every tool I use: email, calendar, task tracking, web research, drafting, code, and automation. The goal is one interface and no context switching. Five principles shape everything below:

  • One interface: email, chat, tasks, documents, research, and code are all reachable from the same terminal session through MCP servers and CLIs.
  • Context persists: Claude Code’s auto-memory (a MEMORY.md index plus one file per fact) carries preferences and project facts between sessions, and a task tracker carries active work. I re-explain nothing.
  • Proactive surfacing: a daily-briefing skill pulls new mail, unanswered sent mail, the calendar, and open commitments into one ranked list before I ask.
  • Parallel by default: independent tasks go to subagents in one message, and the agent that researches a piece of content also writes it.
  • Several model families: Claude orchestrates; GPT (through the Codex CLI) and Gemini handle cross-family review, large-file synthesis, and Google Search grounding.

Three-layer architecture: personal repo, team template, org plugin

The pattern serves three audiences, and each gets its own layer:

Personal assistant repo (full integrations, private)
  |
  |-- sync, strip private detail --> Team template (public, for power users)
  |
  |-- sync, strip private detail --> Org plugin marketplace (distributed through the Claude admin console)
  • Personal assistant repo: my working instance. About 22 local MCP servers plus about 40 claude.ai connectors, auto-memory, multi-model CLI access, and auto-mode permissions with a curated allowlist. It is a git repo with a CLAUDE.md, a .claude/rules/ folder of auto-loaded rules, project skills, and slash commands.
  • Team template: a public GitHub template (mine is MAVEN) that a colleague clones to get org context, brand rules, and a set of skills, with a setup script. It bundles no integrations; each person adds their own.
  • Org plugin marketplace: a private plugin repo that an admin can distribute to staff through the Claude organization admin console. It holds skills with org-specific substance that staff cannot easily reproduce, plus org-defined subagents so everyone delegates to the same pinned targets. Generic drafting skills stay out of it; people build those for themselves with the skill-creator.

Two-branch context model: the personal layer holds operational detail (file-share paths, channel IDs, integration configs). The outer layers get the same org facts with that detail removed, because staff do not have the same tools and a path they cannot open wastes their context and confuses the model. Changes flow personal-first, then outward, scrubbed on the way.

Configuration layout and key settings

Configuration lives at two levels.

User level: ~/.claude/

~/.claude/
  CLAUDE.md              # Communication style, safety rules, delegation policy
  settings.json          # Plugins, permissions, hooks, effort, statusline, env flags
  settings.local.json    # Machine-local allowlist and skill overrides (not synced)
  remote-settings.json   # Cached copy of server-managed org settings (written by Claude Code)
  keybindings.json       # Custom key bindings
  agents/                # Shared subagents, each pinning model and effort
  hooks/                 # PreToolUse guards (blocked tools, git push, attribution, large reads)
  scripts/
    mcp-profile.sh       # SessionStart hook: detect project type, toggle MCPs and plugins
    mcp-profiles.conf    # Directory-to-profile overrides
    quota-probe.sh       # Prints the QUOTA routing line each turn
    statusline.sh        # Two-line statusline
  mcp-servers/           # Custom MCP server code (thin API wrappers)
  skills/                # User-level skills available in every project
  projects/              # Per-project auto-memory directories

MCP server definitions live in ~/.claude.json, which Claude Code writes when you run claude mcp add.

Project level: <repo>/.claude/

<repo>/.claude/
  rules/                 # Auto-loaded every session: small, focused files
  skills/                # On-demand project skills
  commands/              # Slash commands
  agents/                # Project-scoped subagents

Rules files hold short always-on guardrails (naming, voice, routing). Long reference material goes in skills, which load only when relevant.

Key settings in settings.json

{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
    "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "70",
    "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "1000000"
  },
  "permissions": { "defaultMode": "auto", "allow": ["..."], "deny": ["..."] },
  "effortLevel": "medium",
  "tui": "fullscreen",
  "theme": "auto",
  "teammateMode": "auto",
  "remoteControlAtStartup": true,
  "agentPushNotifEnabled": true,
  "awaySummaryEnabled": false,
  "autoDreamEnabled": true,
  "statusLine": { "type": "command", "command": "~/.claude/scripts/statusline.sh" }
}

What each one buys:

  • permissions.defaultMode: auto: runs everything on the curated allow list without a prompt, hard-blocks the deny list, and lets the auto-mode classifier decide the rest. See Permissions.
  • effortLevel: medium: I ran xhigh, then high, and reverted to medium on 2026-08-03. High effort on Opus 5 made ordinary work worse: it over-planned bounded tasks, added scope nobody asked for, and revised correct first answers. Raise effort per task with /effort; agents that need depth pin effort: high in their own frontmatter, which survives the global default.
  • Model: set with /model; there is no model key in my settings.json. On 2.1.286 the aliases opus, fable, sonnet, and haiku resolve to Opus 5.5 (claude-opus-5-5, my orchestrator), Fable 5.1 (claude-fable-5-1), Sonnet 5.5 (claude-sonnet-5-5), and Haiku 4.5 (claude-haiku-4-5-20251001). No subagent runs on Fable.
  • CLAUDE_CODE_AUTO_COMPACT_WINDOW=1000000: compaction math tracks the 1M-token window instead of the 200K default.
  • CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=70: compaction starts at 70% of the window, leaving headroom before the summary runs.
  • Commit attribution: I also set CLAUDE_CODE_SUPPRESS_SESSION_ATTRIBUTION=1, which is undocumented (absent from the official env-var reference on 2026-09-30). The enforcement is a git-attribution-guard.py hook that blocks commits or PRs carrying Claude attribution.
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 and teammateMode: auto: named, addressable teammate agents that render in split panes when the terminal supports it. See Agent teams.
  • tui: fullscreen: works around a rendering glitch where scrolling duplicated text indefinitely.
  • remoteControlAtStartup and agentPushNotifEnabled: every session is steerable from web and mobile from its first prompt, and I get a push when a background agent finishes or blocks.
  • autoDreamEnabled: background consolidation of auto-memory between sessions.

MCP strategy: connectors, local servers, and context cost

Two kinds of MCP server reach a Claude Code session, and they behave differently.

  • claude.ai connectors (remote): configured once in claude.ai and available on web, mobile, desktop, and Claude Code with no local install. Good defaults for mainstream SaaS (mail, chat, docs, meeting transcripts, search).
  • Local servers (claude mcp add): run on your machine, can be scoped per project, and can do things a connector will not. My clearest case: the hosted Microsoft 365 connector strips style=, <span>, and <img> from mail HTML, so a styled email signature is impossible through it. A local Graph server (Softeria’s ms365) keeps all three, so drafting and sending go through the local server and the connector handles search.

Rules I follow:

  • Pick one route per job and deny the other: when a connector and a local server both offer a write tool for the same system, put the connector’s write tools in permissions.deny so the model cannot choose the weaker path.
  • Never run the same server twice: a hand-written mcpServers entry plus the same package installed as a managed connector produced one healthy instance and one crash-looping one. Prefer the managed copy; diagnose duplicates by server-name casing in the logs.
  • Route GPT through the subscription CLI: an API-key GPT MCP wrapper billed per token while the same models sat on a flat ChatGPT subscription through Codex. The wrapper is unregistered and a PreToolUse hook blocks any mcp__openai__* call.
  • Keep names lowercase and hyphenated: every non-alphanumeric character in a server name becomes _ in tool names, so Notion (Personal) surfaces as mcp__Notion__Personal___*.

Tool-schema cost and cold starts

Every enabled server costs context. On 2.1.286, MCP tool schemas are deferred: the session sees tool names, and full JSON schemas load on demand through the ToolSearch tool. That makes a large roster affordable, but the name list itself still grows with each server, and plugins still load their commands, agents, and skill descriptions into every session. Two consequences:

  • Prune plugins aggressively: I keep 6 marketplace plugins enabled (feature-dev, code-review, code-simplifier, commit-commands, claude-md-management, claude-code-setup) and toggle the rest per project. LSP plugins (TypeScript, Pyright, and others) stay off by default and turn on only in coding profiles.
  • Strip MCP from headless worker processes: a Codex run inheriting my MCP and plugin config took minutes to cold-start. The fix is --ignore-user-config (see the Codex section); the obvious-looking -c 'mcp_servers={}' does nothing.

Two accounts on one service: the URL collision

Claude Code identifies a remote MCP server by its exact URL string. If a local server points at the same URL as a claude.ai connector, both are treated as one server and the local copy wins: the connector goes inactive in that project. I verified this on 2026-07-30 with two Notion accounts, one through the connector and one through a local server, both on https://mcp.notion.com/mcp.

Any distinguishing query parameter makes the strings differ, and both stay live:

cd ~/personal-project
claude mcp add --transport http notion-personal 'https://mcp.notion.com/mcp?account=personal'
claude.ai Notion: https://mcp.notion.com/mcp - ✔ Connected
notion-personal:  https://mcp.notion.com/mcp?account=personal (HTTP) - ✔ Connected

Notion ignores the unknown parameter. If a provider validates the canonical URL and rejects it, two other ways to break the match:

  • mcp-remote bridge: claude mcp add notion-personal --env MCP_REMOTE_CONFIG_DIR=$HOME/.mcp-auth/notion-personal -- npx -y mcp-remote https://mcp.notion.com/mcp registers a stdio command with no url field, and each account gets its own token directory.
  • Provider token over stdio: @notionhq/notion-mcp-server with an integration token created inside the target workspace. One token maps to exactly one account; keep the token in your secrets store, never in ~/.claude.json.

Three details that bite:

  • Scope the second account per project: claude mcp add run inside a project writes local scope, so the personal server loads only there.
  • The browser picks the identity: OAuth consent authorizes whichever account is signed in. Run it in a private window signed in as the account you want, then read a page only that account can see to confirm.
  • A legacy endpoint exists: https://mcp.notion.com/sse also answers, as a fallback with a different URL.

Adaptive MCP profiles per project

A SessionStart hook (mcp-profile.sh) detects what kind of project I opened and switches the tool roster to match, so a coding session does not carry mail tools and a writing session does not carry LSPs.

How it works:

  1. On session start the script checks mcp-profiles.conf for an explicit directory-to-profile mapping (prefix match).
  2. If none matches, it detects the type from file signatures: package.json plus tsconfig.json, pyproject.toml, firebase.json, Cargo.toml, go.mod.
  3. It toggles user-scope MCP servers from per-profile MCP_ON / MCP_OFF lists, and plugins (context7, playwright, frontend-design, LSPs, security-guidance) the same way.
  4. It injects a system message naming the active profile, so the model knows which tools to expect.
ProfileTriggerTools on
orgWork repos (explicit mapping)Mail and calendar, web search, scraping, Gemini, task tracker, meeting transcripts, browser automation. Coding plugins off.
codingCode file signaturesWeb search, scraping, Gemini, browser automation; context7, frontend-design, TypeScript and Python LSPs, security-guidance.
contentContent reposWeb search, scraping, Gemini, workflow automation, page fetch, social scrapers, semantic search.
noneNotes vaultsEvery toggled server off.
defaultEverything elseWeb search, Gemini, and browser automation only.

Example mcp-profiles.conf:

~/assistant=org
~/org-plugin=org
~/side-project=coding
~/content=content
~/notes=none

The limit: the script toggles user-scope servers only. A server added with local scope inside a project (like the second Notion account above) is outside its reach, which is the point of local scope.

Multi-model orchestration: Codex, Gemini, and quota routing

Claude Code orchestrates four model families: Claude (subscription), GPT through the Codex CLI (ChatGPT subscription), Gemini through gemini -p (free API tier, Flash only) and Google’s Antigravity CLI agy (Google subscription), and Grok through an MCP wrapper (API-billed). Subscriptions are rate-limited, so routing follows a quota signal described below.

Three tiers of access:

  • MCP tools, inline: Gemini chat with Google Search grounding, Gemini image and video generation (API-billed), Grok for X context. Structured, low overhead.
  • CLI workers, file-heavy: a CLI model reads files itself, reasons independently, and returns only a summary. When gemini -p reads 15 files and returns 200 words, Claude ingests 200 words.
  • Combined workshopping: the same prompt through several models, then a synthesis of where they diverge. Useful for strategy calls, voice comparisons in drafting, and tiebreaks.
# Gemini CLI, Flash on the free API tier. @file refs go INSIDE the prompt
# and must sit within the current workspace.
gemini -p "summarize @doc1.md and @doc2.md into 5 bullets"

# Antigravity CLI: Gemini 3.1 Pro and other models on a Google subscription.
# Tight weekly quota, so use it for second opinions.
agy --model "Gemini 3.1 Pro (High)" -p "<prompt>"

# Codex CLI: always through the wrapper, which owns every flag.
codex-run.sh review "<full-context prompt>"
# The last stdout line is OUT=<file>. Read that file. Piping Codex stdout through
# head or tail once silently truncated a real finding.

Gemini free-tier limits: Google deprecated the free Code Assist login that gemini -p used, and on 2026-07-01 it began erroring. The fix was setting security.auth.selectedType to gemini-api-key in ~/.gemini/settings.json. The free API tier is Flash-only and trains on prompts, with human review possible, so confidential data stays out of it unless billing is on.

Codex CLI through a wrapper

The model classifies the task into one enum; a script owns every flag. codex-run.sh <class> maps each class to a tier and effort, and a resolver picks the newest model of each family from Codex’s live catalog (~/.codex/models_cache.json) at call time. Full scripts are in the delegation kit.

ClassTier / effortModel on 2026-09-30
reviewfrontier / mediumGPT-6 Astra (gpt-6-astra); its Codex credit rate is 5x GPT-6.1 Sol per uncached token
hardestfrontier / highGPT-6 Astra
implement, explore, ingest, prosestandard / mediumGPT-6.1 Sol (gpt-6.1-sol)
commitfast / lowGPT-6 Luna (gpt-6-luna); I override it to Sol with CODEX_FAST_FAMILY=sol

Details that each cost a failure to learn:

  • Resolve by family name: resolving tiers by catalog rank swapped frontier and standard on 2026-09-22, when the catalog listed Sol above Astra.
  • Escalate on evidence: --escalate lifts a class one rung. Medium is the default; going past high needs a specific observed failure.
  • Refuse the priority service tier: it bills 2.5x included subscription usage (2x on purchased credits), and OpenAI publishes no Fast-mode speedup figure for GPT-6 (Astra Ultrafast is documented at up to 8x). The wrapper pins service_tier="default" and exits with an error on --priority. Answers are pinned to low verbosity.
  • --ignore-user-config is the cold-start fix: TOML table overrides merge, so -c 'mcp_servers={}' changes nothing, and codex mcp list -c 'mcp_servers={}' still lists every server (found 2026-07-15). --ignore-user-config skips $CODEX_HOME/config.toml, so the MCP servers and plugins defined there never start, while auth still resolves through CODEX_HOME. The side effect: Codex workers have no MCP tools, so the prompt must carry full context. It is not a filesystem boundary; the sandbox flag is what restricts writes.
  • Block the raw path: a PreToolUse hook (codex-guard.sh) denies any Bash call to codex exec that bypasses the wrapper.

Quota-aware routing

A UserPromptSubmit hook injects one line per turn, and the wrapper prints a fresh one after every Codex call:

QUOTA claude 5h 44%/4h00m 7d 57%/15h50m H=0.70 TIGHT | codex 33%/3d22h H=1.19 OPEN age=0m | prefer=codex
  • H is a pace ratio: remaining quota divided by the remaining fraction of the window, capped at 3. Below 1 means running hot. A platform’s H is its minimum across buckets, so Claude’s usually comes from the 5-hour bucket.
  • States: OPEN; TIGHT (under 25% left in some bucket, or H below 0.7); CLOSED (under 10% left); UNKNOWN.
  • prefer=codex: divertible subagent work (summarization, review, bulk edits, repo search) moves to Codex or gemini -p, and the remaining Claude subagents run on Sonnet or Haiku. prefer=claude keeps Codex for review and hardest only. A platform must lead by 1.25x to flip prefer, which stops it flapping turn to turn.
  • Pinned work ignores quota: anything in my voice, anything needing live MCP, and anything needing the conversation’s context stays on Claude, because Codex workers have none of those.
  • Blind spot: Claude Code exposes only the five_hour and seven_day buckets, and the per-model weekly limit that /usage shows is invisible to the CLI, so Claude’s H is optimistic. Past 70% on 7d, check /usage before a large fan-out.

Cross-family review

Consequential checks (adversarial reviews, second opinions, research that feeds a decision) run twice with the same brief: one Claude agent and one Codex run (review for critique, explore --web for research). Where they agree, confidence goes up. Where they disagree, the disagreement is the finding, because two models from one family tend to agree on the same mistakes.

Skills: loading, descriptions, and retirement

Skills are folders with a SKILL.md that load into context only when relevant. Rules load every session; skills cost almost nothing until used. That split decides where knowledge lives: org context, brand guidelines, and program detail go in skills, and only the short guardrails (naming, voice, security) stay in always-on rules.

How skills load

A skill enters context when:

  1. A slash command invokes it by name.
  2. Its description matches the current task, and the model calls the Skill tool.

Enabling a plugin makes its skills discoverable: their names and descriptions join the listing, and each body loads only when invoked.

At session start the model sees each skill’s name and description. That listing is the whole trigger surface, which makes the description the most important line in the skill.

Writing descriptions that trigger correctly

  • Lead with the job in plain verbs: “Draft, reply to, and send Outlook email as the user” beats a paragraph of background.
  • Name the trigger phrases people type: “Use for ‘hand this off’ or ‘write this down’.”
  • Name the boundary and the sibling: “For summaries use summarizer; not for making changes.” Overlapping skills with no boundary line trigger unpredictably.
  • Keep mechanism out of the description: scripts, flags, and paths go in the body, which loads only after the skill fires.

Retiring a skill

Skills are discovered by directory presence under a skills/ path, and there are three levels of off:

  1. Name-only: skillOverrides in .claude/settings.local.json (managed from the /skills dialog) keeps the name in the listing but suppresses the description. It costs a few tokens and stops description-match triggering, while /name still works.
  2. Hidden: use the visibility overrides that settings support (the /skills dialog writes them to skillOverrides). Moving the directory out of the discovered path, for example into .claude/_disabled-skills/, also removes it from the listing, and moving it back restores it.
  3. Delete: only when it is dead. Git keeps the history.

Whole plugins toggle through enabledPlugins in settings.json. In a 2026-07-12 audit I cut 9 project skills, and on 2026-09-15 I merged two overlapping writing-style skills into one skill plus a linter script.

Subagents: model pinning, return formats, and nesting

When to spawn one

Spawn when the task would flood the main context with intermediate noise:

  • 3 or more web searches.
  • Content over about 500 words that needs research first.
  • 3 or more integration API calls.
  • A genuinely independent subtask where only the result matters.

Handle it directly for 1 or 2 lookups, edits to existing content, and anything with several decision points for the user. The saving is compression: a researcher that runs 5 searches and returns a 300-word brief costs the parent 300 words.

Never split research from writing: the agent that gathers the material also produces the output. Handing research notes to a separate writer is a telephone game, and the nuance dies in the handoff.

Model tiers

Route by the judgment the task needs. The model: and effort: frontmatter fields pin each custom agent.

  • haiku (Haiku 4.5): high-token, low-judgment work where any competent reader would produce the same output and the decision rule fits in the prompt as an explicit threshold. Briefs must be mechanical: exact calls, numeric thresholds, a hard call budget, raw values next to computed ones, and an instruction to return results through SendMessage (in my runs through September 2026, Haiku otherwise often ended its turn without reporting).
  • sonnet (Sonnet 5.5): the default for delegation. Review, search, docs lookup, summarization, classification, multi-step retrieval.
  • opus (Opus 5.5): research that feeds a decision, docs, tests, critiques, red-teaming, architecture calls, high-stakes prose. Opus 5.5 at low effort now does most judgment work, because effort is a stronger cost lever than tier.

The built-in-agent trap

Custom agents (in ~/.claude/agents/, a project’s .claude/agents/, or a plugin) pin their model in frontmatter. Most built-in types have no pin of their own, per the docs on 2026-09-30:

  • Explore and Plan: inherit the session model, capped at Opus. Dispatch Explore to grep for a symbol from an Opus session and the grep runs on Opus.
  • general-purpose: uses the per-call model parameter, then frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session model.
  • Fixed built-ins: claude-code-guide runs on Haiku and statusline-setup runs on Sonnet.

Two fixes. Per call: pass model explicitly on every built-in dispatch. Globally: set CLAUDE_CODE_SUBAGENT_MODEL, which sets the default model for subagents, teammates, and workflow agents not assigned one another way (a per-call model and a definition’s model still win). CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1 (v2.1.257+) forces it over everything, built-ins included. My CLAUDE.md also states the per-call rule, but a rule is compliance; it holds only when the orchestrator remembers.

Agent anatomy

---
name: researcher
description: Gather source-attributed findings across web and local files. Use for research needing three or more searches.
model: opus
effort: low
tools: Read, Grep, Glob, WebSearch, WebFetch
---

Role and context paragraph.

## Process
1. Step one
2. Step two

## Output format
The exact structure to return.

## Rules
- Constraint one

Design choices:

  • Tool restrictions: each agent gets only what it needs. The bug investigator has no edit tools; the reviewer is read-only.
  • Return format baked in: the worker’s final message is re-read by the parent on every later turn, so it must be tight. Ask for evidence, sources, and reasoning in a short brief, and forbid raw dumps. A non-fork subagent sees none of the parent conversation, so the brief must carry full context. A fork subagent inherits the full conversation.
  • Rules section: task guardrails, such as “do NOT edit files” for an investigator or banned-word lists for a content writer.

My shared set is 13 agents: researcher, fact-verifier, red-teamer (opus / high), meeting-prep, content-reviewer, test-writer, doc-writer, bug-investigator (opus / medium), summarizer and a task tracker manager (sonnet / low), and bulk-worker, pr-preparer, dependency-auditor (haiku / low). The unmarked ones run opus / low.

Nesting and parallelism

  • Nesting limit: subagents can spawn their own subagents, by default up to 3 layers below the main conversation, configurable with CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH (v2.1.219+). Use it to keep intermediate output out of the parent: a researcher fans out per-source readers, a reviewer dispatches one verifier per finding. One level covers most work.
  • Run independent work in one message: several Agent calls in a single message run concurrently.
  • Build nothing on partial results: when agents are out for research or review, the deliverable waits until every one has reported. I learned this by building one deck three times, once per late-arriving result.
  • Known failure: an agent told it may delegate sometimes nests an Agent call and returns empty. Tell research agents to research directly, and resume them if they come back blank.

Rules, skills, and hooks as enforcement layers

Four layers, from soft to hard:

  1. Rules — CLAUDE.md and .claude/rules/*.md, always in context. Hold the policy in a sentence.
  2. Skills — loaded on demand. Hold the mechanism: long procedures, scripts, reference tables.
  3. Definitions — agent frontmatter and Codex TOML roles. Pin model and effort mechanically.
  4. Hooks — shell scripts on lifecycle events. Block the bypass path outright.

A model reading a rule is compliance; a script owning the behavior is determinism. Anything that has failed twice under a rule gets a hook.

Hooks I run:

EventScriptWhat it does
SessionStartmcp-profile.shApplies the MCP profile
SessionStart, UserPromptSubmitquota-probe.sh --linePrints the QUOTA line; refreshes a stale Codex reading in the background without a model call
PreToolUseblock-openai-mcp.pyDenies any mcp__openai__* call
PreToolUsecodex-guard.shDenies raw codex exec; points to the wrapper
PreToolUsegit-push-guard.pyBlocks git push <remote> <branch> when HEAD is on a different branch
PreToolUsegit-attribution-guard.pyBlocks commits or PRs carrying Claude attribution or a foreign author
PreToolUseprecise-read.pyBlocks an unscoped Read of a text file over 64 KB and offers grep-then-range, a summarizer agent, or successive ranges
PreToolUsewriting-style lint.pyLints any Bash command that copies text to the clipboard
PostToolUsewriting-style lint.py --hookLints written prose for banned words and em dashes

Notes on three of them:

  • Clear stray API keys before launch: a stray ANTHROPIC_API_KEY in the environment makes Claude Code bill the API instead of the subscription. A hook subprocess cannot change the parent’s auth environment, so unset it in your shell profile before launching Claude, and confirm with /status which auth is active.
  • The push guard exists because of a 12-day miss: in August 2026 a push run from the wrong branch reported success while the target branch stayed unpublished for 12 days.
  • The clipboard lint scans the whole command: a heredoc that merely mentions the clipboard tool gets linted too, which is a false positive I accept.

CLAUDE.md holds the rule; the skill holds the mechanism: a 2026-08-03 audit of my global CLAUDE.md cut about a quarter of it with no change in behavior, mostly mechanism detail duplicated from skills, dated provenance notes, and history about retired systems. Provenance belongs in git.

Permissions, secrets, and the security model

Auto mode plus an allowlist

I moved off blanket bypassPermissions to defaultMode: auto with a curated allowlist of tool patterns (git, gh, npm, and the read tools of my MCP servers). Anything on allow runs without a prompt, anything on deny is blocked, and the auto-mode classifier decides the rest. Day-to-day friction matches bypass, and a novel command still gets a decision.

{
  "permissions": {
    "defaultMode": "auto",
    "allow": ["Bash(git status*)", "Bash(gh pr view*)", "mcp__ms365__list-mail-messages", "..."],
    "deny": ["mcp__claude_ai_<Connector>__<write_tool>", "..."]
  }
}
  • Deny duplicates of a better path: my local deny list holds a hosted connector’s file-search and write tools, because a local server handles that system with fewer limits.
  • Destructive commands are gated three ways: a CLAUDE.md rule (state what the command does and what could be lost, then wait for approval), the auto-mode classifier, and the git hooks.
  • settings.local.json: a smaller machine-local allowlist that does not sync.
  • Untrusted repos: a .claude/ directory in a cloned repo can carry permissions and hooks. For team deployments use the standard permission modes and review .claude/ before trusting a repo.

Org-wide rules belong in server-managed settings

Rules that must hold for every person and every session (hard deny entries for rm -rf /, reads of .env or ~/.ssh, force pushes) belong in server-managed settings set by an admin in the Claude admin console, which apply to every session. Claude Code caches them locally as ~/.claude/remote-settings.json (or {} when the organization has none), checks for updates at startup and hourly, and deletes the cache on logout. Edit the admin console, never the cache.

Secrets live in a password manager

No .env files. Secrets live in a password-manager environment, never in a repo, and MCP servers read their keys from the shell environment. Rotating a key means editing it in the password manager; nothing in any repo changes.

Safety rules in my global CLAUDE.md:

  1. Confirm before external actions: sending mail, posting to chat, deleting or overwriting files, publishing, and calendar changes. State exactly what will happen and wait for approval of that specific action.
  2. Secrets: never hardcode, echo, print, or commit a credential. If one appears in a conversation or file, flag it and recommend rotation.
  3. Destructive commands: state what the command does and what data could be lost, then wait for approval.
  4. Safe alternatives first: git stash over git reset --hard, move to trash over rm -rf.

Statusline for rate limits and context

A custom script replaces the default statusline. It is the most-glanced-at part of the setup:

~/assistant 🔀main ✎28 | Opus 5.5 medium | 🧠 222k | 5h 🟢 53% · 1h47m | 7d 🟢 6% · 6d17h
💬 audit the best-practices doc, it's outdated…
  • Directory and git: $HOME shown as ~; branch truncated at 24 characters; uncommitted-file count; ahead and behind counts, each shown only when nonzero.
  • Model and effort merged: effort is color-coded (low gray, medium blue, high green, xhigh orange, max red).
  • Context: tokens in use, in thousands.
  • Rate limits: real 5-hour and weekly usage with time to reset. Gauge: 🟢 under 70%, 🟡 70 to 90%, 🔴 90% and up.
  • Line 2: the start of the last message I typed, dimmed, so each block in the scrollback is labeled by its task.

The numbers come straight from stdin: Claude Code 2.1.x passes rate_limits.five_hour and rate_limits.seven_day (each with used_percentage and resets_at in Unix epoch seconds) plus context_window token counts. These are the numbers /usage shows, so the script needs no cost model or calibration. The earlier version estimated usage with ccusage and hand-tuned divisors that drifted.

Staleness: rate_limits refreshes only on an API response, so an idle session’s snapshot freezes. When a window lapses during idle time the script prints now instead of a misleading 0m.

The remote-control badge is core UI: the /rc indicator is drawn by Claude Code, and its state is absent from the statusline’s stdin, so the script cannot recolor or hide it. When it wrapped onto a third line, the fix was compressing line 1: merge model and effort, drop the context percentage, strip “(1M context)” from the model name, and truncate long branch names.

Remote control, notifications, and agent teams

Remote control and push notifications

remoteControlAtStartup: true arms remote control on every session start, so a session begun at the desk can be steered from claude.ai web or the mobile app later. agentPushNotifEnabled: true sends a push when a long-running or background agent finishes or needs input. Together: start a long task, walk away, get pinged when it finishes or blocks, and resolve it from the phone. The leash is useful only when armed automatically; a manual toggle never gets flipped. I keep awaySummaryEnabled: false because the push and the statusline already cover what the away digest would say.

Agent teams and terminal orchestration

Three layers let Claude Code workers collaborate, and they differ in whether workers can talk to each other:

LayerWhat it isWorkers message each other?
Subagents (Agent tool)Helpers spawned inside one sessionNo. They report only to the caller.
Agent teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)A lead plus independent Claude instances sharing a task list and a mailboxYes, by name through SendMessage.
Terminal orchestration (cmux, tmux)Separately launched agent processes, including Codex or GeminiYes, through terminal primitives such as send and read-screen.

Agent teams, as of 2.1.286:

  • Spawn by describing the team: “Spawn three teammates to review PR #142: one on security, one on performance, one on test coverage. Have them challenge each other’s findings.” There has been no setup step since 2.1.178.
  • Steer: in-process mode lists teammates in a panel below the prompt; select one, press Enter to open its transcript and type to it. x stops it; Ctrl+T toggles the task list.
  • Models: teammates inherit the lead’s effort level but not its /model. Say “use Sonnet for each teammate” or set the default teammate model in /config.
  • Guardrails: require plan approval before teammates edit, and enforce quality with the TeammateIdle, TaskCreated, and TaskCompleted hooks.
  • Cost: each teammate is a full Claude instance, so tokens scale linearly with team size. Start with 3 to 5, for research, parallel review, or debugging competing hypotheses. Sequential or same-file work belongs in one session.
  • Limits: one team per session; teammates cannot spawn teammates; /resume and /rewind do not restore in-process teammates.
  • Display: teammateMode is in-process by default (since 2.1.179), auto for split panes inside tmux or iTerm2, or tmux / iterm2 to force a backend. Split panes are unsupported in Ghostty, VS Code’s terminal, and Windows Terminal. The experimental flag name has churned, so check the current docs before relying on it.

I run cmux, a Ghostty-based macOS terminal for parallel agents. Because Ghostty lacks split-pane support, cmux claude-teams installs a shim tmux binary, sets TMUX and TMUX_PANE, and execs claude --teammate-mode auto, so teammates render as cmux panes while Claude Code believes it is talking to tmux. For mixed-vendor swarms, cmux’s socket CLI lets agents coordinate directly: cmux send --surface <id> "message" types into a peer’s prompt, cmux read-screen --surface <id> watches a peer without interrupting it, and cmux hooks setup wires permission requests and notifications into the cmux UI. On real tmux, add set -g focus-events on to ~/.tmux.conf so Claude Code knows which pane has focus.

Memory and session continuity

Auto-memory

Claude Code’s auto-memory stores facts per project under ~/.claude/projects/<project-slug>/memory/:

  • MEMORY.md: an index loaded every session, kept under 200 lines.
  • One file per fact: YAML frontmatter (name, description, type) plus the fact.
  • Four types: user (role, preferences), feedback (corrections and confirmed approaches), project (ongoing work and decisions), reference (pointers to external resources).
  • Exclusions: code patterns (read the code), git history (use git log), fixes (they are in the code), and anything already in a CLAUDE.md.
  • Point in time: memories go stale, so verify against current state before acting on one.

My index covers 105 memory files as of 2026-09-30, and about 45 of them are feedback rules. autoDreamEnabled consolidates them between sessions.

Why I retired end-of-session rituals

I used to run /start, /end, and /update commands that wrote state files. I retired them on 2026-07-09. A manual /end fails exactly when a session dies uncleanly (compaction, a rate limit, an accidental close), and that is when a record matters most. Continuity now runs on auto-memory, a task tracker for active work, and a write-first decision log.

HANDOFF.md: an append-only log written before the work

For work that spans sessions, a handoff skill maintains HANDOFF.md at the project root. Entries are written before the work:

  • Before a decision that constrains later work.
  • Before a destructive or hard-to-reverse operation.
  • Before a long or expensive step.
  • When a fact took real effort to establish.
  • When an approach fails. A recorded dead end is worth as much as a recorded decision.

Structure: a rewritten top block (Goal, Current state, Next steps, Open questions) over an append-only log. Each entry carries Decision / Why / Evidence / Reversible, with evidence labeled observed, reported, or assumed. Failures get Tried / Result / Do not retry unless. Entries are never edited or pruned, including wrong ones: a wrong decision followed by its correction is the record that stops the mistake repeating. Anything derivable from the code, the git log, or CLAUDE.md stays out.

Verification and adversarial review

The highest-impact practice here, and the first practice in Anthropic’s Claude Code best practices: give the model a way to observe the result, then make it use that way. If Claude can close the feedback loop, it iterates until the output is right. If it cannot, it guesses and reports the guess with the same confidence.

The rule in my global CLAUDE.md:

Give yourself a way to observe the result, then use it. If a check exists (test suite, bash command, browser, simulator, log, live app), run it and report what you actually saw. A claim that something works is not evidence that it does.

This applies to delegated work too. A subagent or a Codex run reporting “tests pass” is a claim. Re-run the check yourself before acting on it, and say which you did: observed or reported.

The observed-versus-reported label is the operative part. Collapsing the two is how a bad assumption survives three sessions.

WorkHow to observe it
FrontendClaude in Chrome (/chrome) or Playwright MCP. Would an engineer build a website well without a browser?
CodeTest suite, py_compile, type-check, a timed dry run, diff inspection
Hardware and GUI automationA physical check on the device. Files cannot prove a button works, so name what still needs a human press.
Documents and decksRender the PDF, open the file, take a screenshot
Data and numbersRe-query the source; report raw values next to computed ones

Adversarial review in three tiers

Self-review fails because a model that just wrote something defends its own choices. The fix is a fresh context with an explicitly adversarial framing, ideally on a different model family. Cheapest first:

  1. red-teamer agent (Opus 5.5, effort high): one critic for plans, architecture, and decisions, run before code exists, when flaws are cheap.
  2. war-council skill: named personas critique in parallel, and the orchestrator reconciles their findings. For consequential calls on spend, hiring, positioning, or program direction.
  3. Cross-model review (codex-run.sh review): GPT-6 Astra reviewing Claude’s diff, told to refute it. For consequential calls, run tiers 1 and 3 in parallel on the same brief.

Make the critic terminate: “Find the problems” is unbounded: a critic will always find some, including invented ones, and never signals completion. Since 2026-08-03 both red-teamer and war-council instead state what must be true for the plan to be correct (facts that must hold, people who must act, dependencies that must exist, numbers that must land in a range) and mark each condition supported, unsupported, or unknown. That is a bounded checklist with a stopping rule, and a condition nobody could challenge counts as a green light.

Prompts worth reusing:

  • “Grill me on these changes and don’t make a PR until I pass your test.”
  • “Prove to me this works.” It diffs behavior between main and the branch instead of asserting success.
  • “Knowing everything you know now, scrap this and implement the elegant solution.”

Lessons learned

Architecture

  1. Context is the scarce resource. Adaptive profiles, on-demand skills, subagents, and CLI offloading all exist to protect the context window. Large tool results, exploratory research, and whole-file reads are the biggest consumers.
  2. Split instructions into small rules files. One large CLAUDE.md becomes hard to maintain; five focused files under .claude/rules/ each stay reviewable.
  3. Domain knowledge belongs in skills. Rules load every session and skills load on demand, so org context, brand guidelines, and program detail go in skills; naming, voice, and security stay in rules.
  4. Org deployment needs the two-branch context model. Shipping internal paths and channel IDs to people who cannot open them wastes their context and misleads the model.

Multi-model

  1. Several model families beat one. Gemini brings Google Search grounding and a 1M-token window for large-file synthesis; GPT through Codex catches different bugs in review; in my use through September 2026, Claude had the deepest tool integration.
  2. CLI offloading is context economics. When Gemini reads 15 files and returns 200 words, Claude ingests 200 words. Model quality is a separate question.
  3. Subscriptions are rate-limited. Codex and gemini -p add no marginal charge within included subscription allowances, but every platform has windows that bind, Claude’s 5-hour bucket most often. The QUOTA line turns that into a routing signal.

Workflow

  1. Never split research from writing. A separate writer working from a researcher’s notes loses the nuance the researcher saw.
  2. Spawn subagents to compress. Protecting the main context from noise is the reason; speed is a side benefit.
  3. Continuity must survive an unclean exit. Auto-memory, a task tracker, and HANDOFF.md replaced an /end-plus-state-files design on 2026-07-09, because that design lost everything whenever a session died before /end.

MCP and integration

  1. Use the cheapest search that fits. Native WebSearch and WebFetch for single lookups, a bulk search MCP for breadth and non-US queries, Jina Reader as the default page fetch, and credit-metered tools (Firecrawl at 1 credit per page, Perplexity per query) only for JS rendering, anti-bot, or synthesis.
  2. Some sites block every generic fetcher. As of 2026-07-01, Reddit returned a 403 challenge to Firecrawl, Jina, WebFetch, and curl of the .json endpoints from my machine, and in my tests on the same date Claude’s WebSearch returned no reddit.com results. A dedicated scraper (an Apify actor) or scripted browser works. LinkedIn needs a dedicated MCP for the same reason.
  3. Pin MCP versions when latest breaks. A minor release of a chat MCP server once required an OAuth scope my token lacked; pinning the previous version fixed it. Read the changelog before upgrading.
  4. Custom MCPs are easy to build and easy to overpay for. My Dynalist and Grok servers are thin wrappers over existing APIs. My GPT wrapper had the same shape and billed per token while the same models sat on a flat subscription.

Subagent economics

  1. Pin the model on every subagent. In my use through September 2026, Sonnet followed structured prompts (output formats, brand rules, tool preferences) as reliably as Opus. Explore, Plan, and general-purpose can inherit the session model, so pass model per call or set CLAUDE_CODE_SUBAGENT_MODEL.
  2. Design for context economy. A researcher that runs 5 searches and returns 300 words saves the parent the 5 result pages; that is the whole economic case.
  3. A haiku tier pays off when the brief is mechanical. Bulk edits, PR descriptions, and dependency scans run fine on Haiku 4.5 with explicit thresholds and a call budget. Task tracker CRUD moved back up to Sonnet.

Scraping and data access

  1. Server-side and local IPs get different answers. Reddit’s .json endpoints still returned full comment trees to server-side workers on other IPs in July 2026 while 403-ing my laptop, so the same code can pass in CI and fail at the desk.
  2. Know each scraper’s limits. Firecrawl handles Cloudflare and JS rendering and is blocked on Reddit and LinkedIn; route those to dedicated tools up front.
  3. A database makes a good staging buffer. When an automation platform hands data to Claude, a database both sides can reach by API (I use a Notion database) gives a schema and a status field (pending, triaged, drafted, published) that track the pipeline.

Security

  1. Auto mode plus an allowlist beats blanket bypass. Same low friction for common patterns, and novel commands still get a decision. Team deployments should use standard modes, since a .claude/ folder in an untrusted repo can carry permissions.
  2. Confirm-before-send stays on in every mode. A CLAUDE.md rule requires approval before any email, chat post, task change, or publish. Org-wide hard blocks belong in server-managed settings, which apply to every session.

Operations and visibility

  1. A rate-limit statusline prevents budget surprises. Reading the real 5h and 7d percentages from stdin replaced a cost proxy that needed recalibration. When 7d goes yellow, ease off high effort and parallel agents.
  2. Remote control must be armed automatically. With remoteControlAtStartup: true every session is reachable from the phone from its first prompt.

Claude Code versus Claude Desktop

  1. Never run one MCP server twice. A manual filesystem server (logged as [filesystem], healthy) beside the bundled directory connector (logged as [Filesystem], “process exiting early”) left one instance crash-looping. Removing the manual entry fixed it.
  2. -32601 Method not found on resources/list or prompts/list is normal. A tools-only server declares capabilities: { tools: {} } and rejects that polling correctly. The real failure signatures are transport closed unexpectedly and process exiting early.
  3. Code and Desktop need different MCPs. Claude Code has native file tools and Bash, so a filesystem MCP is redundant and osascript runs through the shell. Filesystem and macOS-control MCPs earn their place only in Claude Desktop.
  4. Pick one macOS-control tier and mind its privileges. A single-tool AppleScript server needs only per-app Automation permission. A GUI-driving server with accessibility clicking, screenshots, and a shell runs unsandboxed with full Accessibility access, so scope it in its config and weigh it against any sensitive data synced to the machine.

Effort, verification, and community advice

  1. Effort is a dial. I ran xhigh for months on the theory that reasoning was free on a subscription. Subscription reasoning is rate-limited, and on Opus 5 high effort made ordinary work worse. medium is the global default, raised per task.
  2. Instructions written for an older model can hurt a newer one. Guardrails that stopped earlier failure modes read to Opus 5 as constraints to satisfy, and satisfying them costs turns. Cutting a quarter of my global CLAUDE.md on 2026-08-03 changed no behavior.
  3. “Find the problems” never terminates. “State what must be true, then check those things” turns an open-ended hunt into a checklist with a stopping rule.
  4. Handoffs are claims. Re-run the check a subagent or Codex run reports and label the result observed or reported. It costs one command and catches confident summaries of code that was never written.
  5. Write the decision log before the work. A summary written at the end is lost when the session has no end.
  6. Audit community advice against what you already run. Of ten well-regarded recommendations I reviewed on 2026-08-03, four were already in place, three were counterproductive for this setup, and three were worth adopting. The most-cited one, a plugin for Claude orchestrating Codex, spent far more tokens on one feature in its own author’s write-up than my wrapper spends on comparable work. Check the numbers in a post before adopting its pattern, and distrust benchmark tables with no cited source.

License

Text © 2026 Alec Foster, licensed under CC BY 4.0. See LICENSE.


> esc
$ keyboard_shortcuts.md esc

// navigation

  • ⌘K or ctrl+K — open command palette
  • ? — toggle this overlay
  • esc — close any overlay
  • / — jump to command palette input

// command palette

  • ↑ ↓ — navigate results
  • ↵ — select / navigate
  • type a tag like #agents — search by tag
  • type a service like workshop — find related pages

// 404 page

  • cd <path> — navigate
  • ls — list pages
  • help — show commands
  • clear — clear history

// agent swarm

  • click anywhere — spawn a task
  • drag a rectangle — dispatch a team
  • hover over a worker — see its model
based on conventions from cmd+k, vercel, linear, and most claude code workflows