Design Lessons
Agent system authors — ten cross-cutting principles distilled from the source-grounded chapters, each tied to specific source files; a checklist for evaluating your own agent's design.
What this chapter is for
The previous 4 chapters (system-prompt / compaction / memory / sandbox) covered what Claude Code does in source. This chapter extracts the “why it does that” into portable principles, each tied to specific source files, so you can directly evaluate and refine your own agent’s design.
Each principle follows the same structure:
- Principle — one sentence
- Source evidence —
path:line-level references - How to apply it — executable action
- Violation signal (smell) — what indicates you’re violating it
1. Progressive disclosure is a universal pattern
Principle: any information that grows unboundedly over time should use “index always-loaded + full content on demand” — never inject everything into the prompt.
Source evidence:
- Auto memory:
memdir/memdir.ts’sMAX_ENTRYPOINT_LINES = 200+MAX_ENTRYPOINT_BYTES = 25_000— dual caps + truncation warnings - Tools: Claude Code uses a deferred tools mechanism — tool names always available, schemas loaded on demand
via
ToolSearch - Skills: L1 metadata in prompt, L2/L3 on demand (see System Prompt Assembly)
- MCP:
mcp_instructionsdelta mechanism — new connecting MCPs send delta only, not full instruction re-send
How to apply:
- For any document / memory / skill: ask “can this be index + on-demand?” If yes, do it
- Index itself needs hard limits (like 200 lines), with user-visible truncation warnings telling them how to fix (“move detail into topic files”)
- Use multi-dimensional limits (lines + bytes) — p97 / p100 data shows single-cap always misses cases
Violation signals: system prompt > 20k tokens and growing; every new skill / memory / tool makes it longer; obvious “old unused entries still present” items.
2. Subagents are context firewalls, not parallelism primitives
Principle: any ReAct loop with 5+ steps should be wrapped as a subagent — not for parallelism, but to prevent exploration details from polluting the main context.
Source evidence:
tools/AgentTool/AgentTool.tsxlines 622-633 — default pathforkContextMessages: undefined— subagent doesn’t inherit parent conversation- Only the fork path inherits parent messages + parent system prompt (special case)
tools/AgentTool/agentMemory.ts— subagents have their own 3-scope persistent memory (user/project/local)
How to apply:
- Identify: which “single tool call” actually contains a 10+ step loop? (Browser, complex queries, exploratory search)
- Wrap them as subagents: one call in, summary out
- Subagents can have independent model (haiku is cheaper), independent permission mode, independent working directory / worktree isolation
- Memory should be isolated too — different subagent types don’t share learned experience
Violation signals: main conversation persistently cluttered with grep / ls residue; per-turn token consumption growing but decision density not.
3. Hooks and prompts answer different questions
Principle: a prompt says what should happen (the model judges); a hook says what must happen (the harness enforces). They aren’t interchangeable.
Source evidence:
utils/hooks.tsdefines 10 hook events + full JSON schema (continue / decision / permissionDecision / updatedInput / systemMessage / additionalContext)TOOL_HOOK_EXECUTION_TIMEOUT_MS = 10 * 60 * 1000— hooks are first-class citizens (10-minute timeout), not quick interceptors- Hooks can modify tool arguments (
updatedInput), inject system messages, break the agent loop (continue: false)
How to apply:
- List all “from now on every time X do Y” requirements, evaluate severity — serious → must be a hook
- Hook API should be three-tiered: plain text (simple) → JSON decision (medium) → updatedInput + systemMessage (advanced)
- Must provide both prompt layer and hook layer — prompt-only silently fails on automation requirements
Violation signals: “I put X in the prompt but the agent often doesn’t do it” — prompt can’t solve this; need a hook.
4. Trust is a product surface, not a config
Principle: user’s trust level must be a UI first-class citizen + per-turn switchable, not a startup flag or a boolean in a config file.
Source evidence:
utils/permissions/PermissionMode.ts’sPERMISSION_MODE_CONFIG— 7 modes each withtitle,shortTitle,symbol,color,external- Plan mode’s
PAUSE_ICON,acceptEdits/bypass’s⏵⏵fast-forward symbol — visual reinforcement of state --dangerously-skip-permissions— deliberately ugly CLI flag
How to apply:
- Permission modes as UI-switchable (per-turn), not startup flag
- Status bar / title bar persistently shows current mode
- Dangerous modes violate UX convention in naming —
bypass/dangerously
Violation signals: “the agent just ran rm” — a startup flag from hours ago was forgotten; config has
auto_approve: true — wrong abstraction.
5. Trust-but-verify is infrastructure against hallucination
Principle: any agent self-report is intent, not fact. Key claims need to be read back or grep-verified.
Source evidence:
- Memory guidance injected by
utils/claudemd.tsrule 3: “A memory that names a specific function, file, or flag is a claim that it existed when the memory was written. It may have been renamed… Before recommending it [verify]” - Edit tool description: “You must use your
Readtool at least once in the conversation before editing” — enforced read-before-edit - Compaction summary section 9: “Optional Next Step: include direct quotes from the most recent conversation” — forced verbatim quotation to prevent intent drift
How to apply:
- After critical tool operations, next step is a forced verification read
- Memory systems need a “verify before citing” operational rule
- Multi-agent systems: subagent summaries must be verifiable by the parent
Violation signals: agent says “done” when not done; memory says “X file at Y” but search finds nothing; logs say “task complete” but deliverables are broken.
6. Error messages are an extension of the prompt
Principle: tool errors aren’t status codes — they’re text teaching the agent what to do next. Write each one carefully.
Source evidence:
- Edit tool’s error text: “This tool will error if you attempt an edit without reading the file” — reading this, the agent naturally calls Read first
- Claude Code’s tool descriptions overall read like tutorials, not schema docs — including failure modes, alignment requirements, uniqueness constraints
- Compaction’s
NO_TOOLS_PREAMBLE+NO_TOOLS_TRAILER— hard constraints repeated at both ends of long prompt (telemetry: Sonnet 4.6 tool attempt rate 2.79% vs 4.5’s 0.01%)
How to apply:
- For every custom tool’s error text, ask: “reading this, does the model know what to do next?”
- Bad:
"Invalid argument"; good:"This tool requires parameter X to be set first. Typical usage: ..." - Long prompts should repeat hard constraints at both ends — models forget middle-of-prompt rules
Violation signals: agent hits error and loops retrying; tool descriptions only have schema types no examples; same error repeatedly goes back to the user.
7. Late binding preserves prompt cache
Principle: order system prompt layers by stability — more stable goes earlier. Timestamps / session state / dynamic content go at the tail.
Source evidence:
constants/prompts.tsline 572:SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'— literal marker, comment says “BOUNDARY MARKER - DO NOT MOVE OR REMOVE”DANGEROUS_uncachedSystemPromptSectionfactory — deliberately scary name, making cache-breaking sections visible in reviewservices/api/promptCacheBreakDetection.ts’snotifyCompaction— proactively notifies cache-miss alerts at compaction to avoid 20% false positives (source BQ data)
How to apply:
- Draw the prompt layer stability table
- Unstable content, no matter how “important,” goes toward the end
- Don’t put timestamps / user IDs at the start to “make sure the model sees them” — prompt cache load cost is unaffordable
- Any cache-breaking section should be explicitly labeled so it’s visible in review
Violation signals: prompt cache hit rate < 50%; every request pays full token rate; prompt length seems modest but token cost is high.
8. Short-term conversation and long-term memory are two layers
Principle: conversation history is short-term (can be wiped at task boundaries); memory layer is long-term (persists cross-session). Don’t mix them.
Source evidence:
utils/messages.ts’screateCompactBoundaryMessage— compaction is a message-stream event, not a context reset/clearcommand only wipes conversation; doesn’t touchCLAUDE.md/ auto memory / session memoryservices/compact/compact.tshas three tiers: auto-compact (defense) //compact(user-initiated) //clear(restart)getMessagesAfterCompactBoundary— only messages after the compact boundary participate in the next compaction; no re-compacting
How to apply:
- Three-tier control: auto defense / user-initiated compact / full clear
- Write “learned preferences” to memory layer, don’t leave them in conversation waiting to be cleared
- Compaction doesn’t touch system prompt — protect cache prefix
- Mark compaction events with explicit boundary messages, not silent message-array mutation
Violation signals: after “reset conversation” the agent forgets basic preferences — preferences should have
been in memory; /clear equivalent to reinstalling product — memory layer design has failed.
9. Data-driven prompt engineering
Principle: every instruction in the prompt should have a “why this wording” answer — instructions without data eventually spiral out of control.
Source evidence:
constants/prompts.ts’snumeric_length_anchors: “Length limits: keep text between tool calls to ≤25 words” — comment literally says “research shows ~1.2% output token reduction vs qualitative ‘be concise’”. Ant-only gradual rollout firstservices/compact/autoCompact.ts:MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000— comment “Based on p99.99 of compact summary output being 17,387 tokens”. Tuned from production telemetrymemdir/memdir.ts:MAX_ENTRYPOINT_BYTES = 25_000— comment “At p97 today; catches long-line indexes… (p100 observed: 197KB under 200 lines)” — p100 data directly in the commentservices/compact/prompt.ts’sNO_TOOLS_PREAMBLE— comment “(2.79% on 4.6 vs 0.01% on 4.5)” — telemetry numbers directly as prompt design basis
How to apply:
- Every prompt instruction / threshold constant should answer “why this number”
- Quantified wordings (“≤25 words”) — verify with data before rolling out, ant-only / canary first
- Treat prompts as A/B testable code, not unordered rule piles
- Product telemetry must feed back into prompt design — p97, p99.99, percentage differences are all valid signals
Violation signals: a prompt instruction no one can explain; threshold constants are “arbitrary”; no quantified comparison before / after prompt changes.
10. Fail-closed + circuit breaker
Principle: critical paths must be fail-closed (reject on error, not allow); unrecoverable failures must circuit-break (stop after N consecutive failures), not retry infinitely.
Source evidence:
services/compact/autoCompact.ts’sMAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3— comment “BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272) in a single session, wasting ~250K API calls/day globally”. No circuit breaker = 250K wasted API calls per day- Permission system: deny always beats allow (
bypassPermissionscan’t bypass deny-list / hooks), fail-closed default utils/permissions/dangerousPatterns.ts: auto-mode automatically stripsBash(python:*)style rules — rules that are “restrictive on surface but actually permissive” — don’t trust user’s loose config
How to apply:
- All retry paths need circuit breakers: stop and log after N consecutive failures, don’t keep consuming resources
- Permission merge algorithm: deny always beats allow; “max permission” mode can’t skip deny
- Dangerous loose configs should be actively stripped at preprocessing, not blocked per-call at runtime
Violation signals: production logs show the same error repeating 100+ times in minutes; a user set “restrict half” config into effectively “allow all”; every “bypass all” actually bypasses everything.
Self-assessment checklist
Quick scorecard for your own agent:
| # | Principle | Doing it? |
|---|---|---|
| 1 | Progressive disclosure (index + on-demand) | [ ] |
| 2 | Subagent as context firewall | [ ] |
| 3 | Hook vs prompt layering | [ ] |
| 4 | Trust as product surface | [ ] |
| 5 | Trust-but-verify operational rule | [ ] |
| 6 | Error messages written like prompts | [ ] |
| 7 | Late binding for cache | [ ] |
| 8 | Short- vs long-term memory separation | [ ] |
| 9 | Data-driven prompt engineering | [ ] |
| 10 | Fail-closed + circuit breaker | [ ] |
More than 2 unchecked → re-read the corresponding chapters in depth.
Landing to code: continue to the next chapter
These 10 items are principles — the abstract layer. If you need to immediately land them in AI SDK code,
the next chapter Applying to Your Agent (AI SDK) maps each
principle to specific embedding points in the streamText lifecycle — telling you which line of code, how to
do it, when it applies and when it doesn’t.
If you’re already building an agent with AI SDK, the next chapter includes a 10-item self-audit checklist — scan your current codebase against it directly.
Not universal truth, just current best practice
Three caveats:
- Agent engineering is a new discipline — three years from now some principles may be overturned. Don’t treat as dogma
- Claude Code itself iterates — in three years the source will differ; if it does, that’s them changing
- The real asset isn’t the 10 principles, it’s the method of “when facing a problem, look at how a good implementation does it” — having dissected Claude Code source once, you can do the same to Devin, Cursor, Codex CLI features as they ship
Source reading guide: all files referenced in this chapter are in the claude-code/ directory. By importance:
services/compact/{compact, autoCompact, microCompact, prompt}.ts— the most interesting partsconstants/prompts.ts+utils/systemPrompt.ts— the core of prompt assemblyutils/claudemd.ts+memdir/— memory systemtypes/permissions.ts+utils/hooks.ts— permission + hook full model
These files together are about 15,000 lines. A full read-through is roughly a week — but every line is condensed from a real production system, far more valuable than any agent paper.
Further reading
- All the chapters this one builds on: Overview · System Prompt · Compaction · Memory · Agent Loop · Permissions · Execution Environment
- Theoretical background: Harness Engineering · Context Engineering
- Practical reference: BUA Architecture — subagent as context firewall in practice