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:

  1. Principle — one sentence
  2. Source evidencepath:line-level references
  3. How to apply it — executable action
  4. 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’s MAX_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_instructions delta 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.tsx lines 622-633 — default path forkContextMessages: 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.ts defines 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’s PERMISSION_MODE_CONFIG7 modes each with title, shortTitle, symbol, color, external
  • Plan mode’s PAUSE_ICON, acceptEdits / bypass’s ⏵⏵ fast-forward symbol — visual reinforcement of state
  • --dangerously-skip-permissionsdeliberately 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 namingbypass / 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.ts rule 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 Read tool 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.ts line 572: SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__' — literal marker, comment says “BOUNDARY MARKER - DO NOT MOVE OR REMOVE”
  • DANGEROUS_uncachedSystemPromptSection factory — deliberately scary name, making cache-breaking sections visible in review
  • services/api/promptCacheBreakDetection.ts’s notifyCompaction — 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’s createCompactBoundaryMessage — compaction is a message-stream event, not a context reset
  • /clear command only wipes conversation; doesn’t touch CLAUDE.md / auto memory / session memory
  • services/compact/compact.ts has 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’s numeric_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 first
  • services/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 telemetry
  • memdir/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 comment
  • services/compact/prompt.ts’s NO_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’s MAX_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 (bypassPermissions can’t bypass deny-list / hooks), fail-closed default
  • utils/permissions/dangerousPatterns.ts: auto-mode automatically strips Bash(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:

#PrincipleDoing it?
1Progressive disclosure (index + on-demand)[ ]
2Subagent as context firewall[ ]
3Hook vs prompt layering[ ]
4Trust as product surface[ ]
5Trust-but-verify operational rule[ ]
6Error messages written like prompts[ ]
7Late binding for cache[ ]
8Short- vs long-term memory separation[ ]
9Data-driven prompt engineering[ ]
10Fail-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:

  1. Agent engineering is a new discipline — three years from now some principles may be overturned. Don’t treat as dogma
  2. Claude Code itself iterates — in three years the source will differ; if it does, that’s them changing
  3. 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:

  1. services/compact/{compact, autoCompact, microCompact, prompt}.ts — the most interesting parts
  2. constants/prompts.ts + utils/systemPrompt.ts — the core of prompt assembly
  3. utils/claudemd.ts + memdir/ — memory system
  4. types/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

Was this page helpful?