Last active
April 25, 2026 23:21
-
-
Save pawanpoudel/170bc62e6508d8fa1e4a30b6851826ae to your computer and use it in GitHub Desktop.
Claude Code and Agents Short Guide Book
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>The Claude Code & Agents Book — A Field Guide</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.5/babel.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-toml.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-diff.min.js"></script> | |
| <style> | |
| body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; background: #09090b; } | |
| #loader { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #09090b; color: #71717a; font-size: 13px; z-index: 100; gap: 12px; } | |
| #loader.hide { display: none; } | |
| .pulse { width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, #f59e0b, #8b5cf6); animation: pulse 1.5s ease-in-out infinite; } | |
| @keyframes pulse { 0%, 100% { transform: scale(1); opacity: 0.7; } 50% { transform: scale(1.2); opacity: 1; } } | |
| /* Custom Prism theme - matches our dark aesthetic */ | |
| pre[class*="language-"], code[class*="language-"] { | |
| color: #e4e4e7; | |
| background: none; | |
| font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; | |
| font-size: 12.5px; | |
| line-height: 1.6; | |
| text-shadow: none; | |
| direction: ltr; | |
| text-align: left; | |
| white-space: pre; | |
| word-spacing: normal; | |
| word-break: normal; | |
| word-wrap: normal; | |
| tab-size: 2; | |
| hyphens: none; | |
| } | |
| pre[class*="language-"] { | |
| padding: 1rem 1.1rem; | |
| margin: 0; | |
| overflow: auto; | |
| background: #0a0a0d; | |
| border-radius: 0.5rem; | |
| } | |
| :not(pre) > code[class*="language-"] { | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| background: #18181b; | |
| color: #fbbf24; | |
| font-size: 12px; | |
| } | |
| .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #6b7280; font-style: italic; } | |
| .token.punctuation { color: #a1a1aa; } | |
| .token.namespace { opacity: 0.7; } | |
| .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #f472b6; } | |
| .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #86efac; } | |
| .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #fde047; } | |
| .token.atrule, .token.attr-value, .token.keyword { color: #c4b5fd; } | |
| .token.function, .token.class-name { color: #fbbf24; } | |
| .token.regex, .token.important, .token.variable { color: #f87171; } | |
| .token.important, .token.bold { font-weight: bold; } | |
| .token.italic { font-style: italic; } | |
| /* Smooth scroll for in-page nav */ | |
| html { scroll-behavior: smooth; } | |
| /* Custom scrollbar in code blocks */ | |
| pre[class*="language-"]::-webkit-scrollbar { height: 8px; width: 8px; } | |
| pre[class*="language-"]::-webkit-scrollbar-track { background: #18181b; } | |
| pre[class*="language-"]::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 4px; } | |
| pre[class*="language-"]::-webkit-scrollbar-thumb:hover { background: #52525b; } | |
| /* Marker for "active" chapter side nav */ | |
| .nav-active-bar { | |
| position: absolute; | |
| left: 0; | |
| width: 2px; | |
| background: linear-gradient(180deg, #fbbf24, #a78bfa, #22d3ee); | |
| transition: top 0.3s, height 0.3s; | |
| } | |
| /* Drop cap on first paragraph of each chapter */ | |
| .drop-cap > p:first-of-type::first-letter { | |
| float: left; | |
| font-size: 3.4em; | |
| line-height: 0.85; | |
| margin: 0.06em 0.08em 0 0; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #fbbf24, #a78bfa); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| } | |
| /* Subtle entrance animation */ | |
| .fade-in { animation: fadeIn 0.5s ease-out; } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } | |
| /* Hover glow on nav items */ | |
| .nav-glow:hover { box-shadow: inset 2px 0 0 currentColor; } | |
| </style> | |
| </head> | |
| <body class="bg-zinc-950"> | |
| <div id="loader"> | |
| <div class="pulse"></div> | |
| <div>Compiling the book…</div> | |
| </div> | |
| <div id="root"></div> | |
| <script type="text/babel" data-presets="react"> | |
| const { useState, useEffect, useRef, useMemo, Fragment } = React; | |
| // ===== Inline SVG icons ===== | |
| const mk = (children) => ({ size = 16, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| {children} | |
| </svg> | |
| ); | |
| const ChevronLeft = mk(<polyline points="15 18 9 12 15 6"/>); | |
| const ChevronRight = mk(<polyline points="9 18 15 12 9 6"/>); | |
| const ChevronDown = mk(<polyline points="6 9 12 15 18 9"/>); | |
| const ChevronUp = mk(<polyline points="18 15 12 9 6 15"/>); | |
| const Menu = mk(<><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></>); | |
| const X = mk(<><path d="M18 6 6 18"/><path d="m6 6 12 12"/></>); | |
| const Layers = mk(<><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></>); | |
| const Repeat = mk(<><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></>); | |
| const GitBranch = mk(<><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></>); | |
| const Shield = mk(<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>); | |
| const Database = mk(<><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></>); | |
| const Wrench = mk(<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>); | |
| const Zap = mk(<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>); | |
| const Users = mk(<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>); | |
| const Box = mk(<><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></>); | |
| const Brain = mk(<><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/></>); | |
| const FileCheck = mk(<><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><polyline points="9 15 11 17 15 13"/></>); | |
| const BookOpen = mk(<><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></>); | |
| const Network = mk(<><rect x="16" y="16" width="6" height="6" rx="1"/><rect x="2" y="16" width="6" height="6" rx="1"/><rect x="9" y="2" width="6" height="6" rx="1"/><path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3"/><path d="M12 12V8"/></>); | |
| const AlertTriangle = mk(<><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></>); | |
| const Sparkles = mk(<><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></>); | |
| const Check = mk(<polyline points="20 6 9 17 4 12"/>); | |
| const XCircle = mk(<><circle cx="12" cy="12" r="10"/><line x1="15" x2="9" y1="9" y2="15"/><line x1="9" x2="15" y1="9" y2="15"/></>); | |
| const Compass = mk(<><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></>); | |
| const Terminal = mk(<><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></>); | |
| const Settings = mk(<><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></>); | |
| const Lock = mk(<><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></>); | |
| const Lightbulb = mk(<><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></>); | |
| const Bug = mk(<><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></>); | |
| const DollarSign = mk(<><line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></>); | |
| const Rocket = mk(<><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></>); | |
| const FileText = mk(<><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></>); | |
| const Activity = mk(<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>); | |
| const Eye = mk(<><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></>); | |
| const MapIcon = mk(<><polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21 3 6"/><line x1="9" x2="9" y1="3" y2="18"/><line x1="15" x2="15" y1="6" y2="21"/></>); | |
| const Award = mk(<><circle cx="12" cy="8" r="6"/><polyline points="15.477 12.89 17 22 12 19 7 22 8.523 12.89"/></>); | |
| const ArrowRight = mk(<><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></>); | |
| const Hash = mk(<><line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/></>); | |
| const Folder = mk(<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>); | |
| const Cloud = mk(<path d="M17.5 19a4.5 4.5 0 1 0 0-9h-1.8A7 7 0 1 0 4 16.7"/>); | |
| const PlayCircle = mk(<><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></>); | |
| const Star = mk(<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>); | |
| // ===== Highlighted code block ===== | |
| function CodeBlock({ language = "javascript", children, label }) { | |
| const ref = useRef(null); | |
| useEffect(() => { | |
| if (ref.current && window.Prism) window.Prism.highlightElement(ref.current); | |
| }, [children, language]); | |
| return ( | |
| <div className="my-4 rounded-lg overflow-hidden border border-zinc-800 bg-[#0a0a0d]"> | |
| <div className="flex items-center justify-between px-3 py-1.5 border-b border-zinc-800 bg-zinc-900/50"> | |
| <div className="flex items-center gap-1.5"> | |
| <span className="w-2.5 h-2.5 rounded-full bg-rose-500/40" /> | |
| <span className="w-2.5 h-2.5 rounded-full bg-amber-500/40" /> | |
| <span className="w-2.5 h-2.5 rounded-full bg-emerald-500/40" /> | |
| </div> | |
| <div className="text-xs text-zinc-500 font-mono">{label || language}</div> | |
| <div className="w-10" /> | |
| </div> | |
| <pre className={`language-${language}`}><code ref={ref} className={`language-${language}`}>{children}</code></pre> | |
| </div> | |
| ); | |
| } | |
| function InlineCode({ children }) { | |
| return <code className="text-xs px-1.5 py-0.5 rounded bg-zinc-800/80 text-amber-300 font-mono">{children}</code>; | |
| } | |
| // ===== Reusable display components ===== | |
| const Box1 = ({ tone = "amber", title, children, icon: Ic }) => { | |
| const tones = { | |
| amber: "from-amber-500/10 to-orange-500/5 border-amber-500/30", | |
| violet: "from-violet-500/10 to-purple-500/5 border-violet-500/30", | |
| emerald: "from-emerald-500/10 to-teal-500/5 border-emerald-500/30", | |
| rose: "from-rose-500/10 to-pink-500/5 border-rose-500/30", | |
| cyan: "from-cyan-500/10 to-sky-500/5 border-cyan-500/30", | |
| zinc: "from-zinc-500/10 to-zinc-500/5 border-zinc-700", | |
| }; | |
| const titleTones = { amber: "text-amber-400", violet: "text-violet-400", emerald: "text-emerald-400", rose: "text-rose-400", cyan: "text-cyan-400", zinc: "text-zinc-300" }; | |
| return ( | |
| <div className={`bg-gradient-to-br ${tones[tone]} border rounded-xl p-5 my-4`}> | |
| {title && (<div className={`flex items-center gap-2 font-semibold mb-2 ${titleTones[tone]}`}>{Ic && <Ic size={16} />}{title}</div>)} | |
| <div className="text-zinc-300 text-sm leading-relaxed">{children}</div> | |
| </div> | |
| ); | |
| }; | |
| const Tag = ({ tone, children }) => { | |
| const c = { | |
| amber: "bg-amber-500/15 text-amber-300 border-amber-500/30", | |
| violet: "bg-violet-500/15 text-violet-300 border-violet-500/30", | |
| emerald: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30", | |
| rose: "bg-rose-500/15 text-rose-300 border-rose-500/30", | |
| cyan: "bg-cyan-500/15 text-cyan-300 border-cyan-500/30", | |
| }; | |
| return <span className={`inline-block text-xs px-2 py-0.5 rounded-full border ${c[tone]} font-medium`}>{children}</span>; | |
| }; | |
| const Pill = ({ children, tone = "zinc" }) => { | |
| const c = { zinc: "bg-zinc-800 text-zinc-300", good: "bg-emerald-500/20 text-emerald-300", bad: "bg-rose-500/20 text-rose-300", warn: "bg-amber-500/20 text-amber-300" }; | |
| return <span className={`text-xs font-mono px-2 py-0.5 rounded ${c[tone]}`}>{children}</span>; | |
| }; | |
| const Insight = ({ title = "KEY INSIGHT", children, tone = "cyan" }) => { | |
| const c = { | |
| cyan: "border-cyan-400 bg-cyan-500/5 text-cyan-400", | |
| amber: "border-amber-400 bg-amber-500/5 text-amber-400", | |
| rose: "border-rose-400 bg-rose-500/5 text-rose-400", | |
| violet: "border-violet-400 bg-violet-500/5 text-violet-400", | |
| }; | |
| const [bcol, ...rest] = c[tone].split(" "); | |
| return ( | |
| <div className={`border-l-4 ${c[tone].split(" ")[0]} ${c[tone].split(" ")[1]} pl-4 py-3 my-4 rounded-r-lg`}> | |
| <div className={`${c[tone].split(" ")[2]} text-xs font-bold tracking-widest mb-1`}>{title}</div> | |
| <div className="text-zinc-200 text-sm leading-relaxed">{children}</div> | |
| </div> | |
| ); | |
| }; | |
| const Compare = ({ good, bad, goodLabel = "Do this", badLabel = "Not this" }) => ( | |
| <div className="grid md:grid-cols-2 gap-3 my-4"> | |
| <div className="bg-emerald-500/5 border border-emerald-500/30 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-emerald-400 font-semibold text-sm mb-2"><Check size={14} /> {goodLabel}</div> | |
| <div className="text-zinc-300 text-sm">{good}</div> | |
| </div> | |
| <div className="bg-rose-500/5 border border-rose-500/30 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-rose-400 font-semibold text-sm mb-2"><XCircle size={14} /> {badLabel}</div> | |
| <div className="text-zinc-300 text-sm">{bad}</div> | |
| </div> | |
| </div> | |
| ); | |
| const Quote = ({ children, attribution }) => ( | |
| <blockquote className="my-5 pl-5 border-l-2 border-zinc-700 italic text-zinc-300"> | |
| {children} | |
| {attribution && <div className="text-xs text-zinc-500 mt-2 not-italic">— {attribution}</div>} | |
| </blockquote> | |
| ); | |
| const Aside = ({ title, children }) => { | |
| const [open, setOpen] = useState(false); | |
| return ( | |
| <div className="my-4 border border-zinc-800 rounded-lg overflow-hidden"> | |
| <button onClick={() => setOpen(!open)} className="w-full flex items-center justify-between px-4 py-3 bg-zinc-900/50 hover:bg-zinc-900 transition text-left"> | |
| <div className="flex items-center gap-2 text-zinc-300 text-sm font-semibold"> | |
| <Lightbulb size={14} className="text-amber-400" /> | |
| {title} | |
| </div> | |
| {open ? <ChevronUp size={14} /> : <ChevronDown size={14} />} | |
| </button> | |
| {open && <div className="px-4 py-4 text-sm text-zinc-300 leading-relaxed">{children}</div>} | |
| </div> | |
| ); | |
| }; | |
| const Stat = ({ value, label, tone = "amber" }) => { | |
| const c = { amber: "text-amber-400", violet: "text-violet-400", emerald: "text-emerald-400", cyan: "text-cyan-400", rose: "text-rose-400" }; | |
| return ( | |
| <div className="text-center"> | |
| <div className={`text-3xl md:text-4xl font-black ${c[tone]}`}>{value}</div> | |
| <div className="text-xs text-zinc-400 mt-1">{label}</div> | |
| </div> | |
| ); | |
| }; | |
| // ============================================================================ | |
| // DIAGRAMS | |
| // ============================================================================ | |
| const SixLayerDiagram = () => { | |
| const layers = [ | |
| { n: "Verifiers", d: "Validation that's testable & auditable", c: "#34d399" }, | |
| { n: "Subagents", d: "Context-isolated workers", c: "#22d3ee" }, | |
| { n: "Hooks", d: "Enforced behaviors, no model judgment", c: "#a78bfa" }, | |
| { n: "Skills", d: "On-demand methodologies", c: "#f472b6" }, | |
| { n: "Tools / MCP", d: "What Claude can do", c: "#fb923c" }, | |
| { n: "CLAUDE.md / rules / memory", d: "What this project is", c: "#fbbf24" }, | |
| ]; | |
| return ( | |
| <div className="my-6 max-w-md mx-auto"> | |
| {layers.map((l, i) => ( | |
| <div key={i} className="relative" style={{ marginLeft: `${i * 8}px` }}> | |
| <div className="rounded-lg p-3 mb-1 border-2 flex items-center justify-between transition hover:translate-x-1" style={{ background: `${l.c}18`, borderColor: `${l.c}66` }}> | |
| <div> | |
| <div className="font-bold text-sm" style={{ color: l.c }}>{l.n}</div> | |
| <div className="text-xs text-zinc-400">{l.d}</div> | |
| </div> | |
| <div className="text-zinc-600 text-xs font-mono">L{6 - i}</div> | |
| </div> | |
| </div> | |
| ))} | |
| <div className="text-center text-zinc-500 text-xs mt-2">↑ each layer builds on the ones below</div> | |
| </div> | |
| ); | |
| }; | |
| const AgentLoopDiagram = () => { | |
| const cx = 200, cy = 130, r = 90; | |
| const stages = [ | |
| { a: -90, t: "Perceive", c: "#fbbf24", d: "Gather context" }, | |
| { a: 0, t: "Decide", c: "#fb923c", d: "Pick next action" }, | |
| { a: 90, t: "Act", c: "#a78bfa", d: "Use a tool" }, | |
| { a: 180, t: "Verify", c: "#34d399", d: "Did it work?" }, | |
| ]; | |
| return ( | |
| <div className="my-6 flex justify-center"> | |
| <svg viewBox="0 0 400 280" className="w-full max-w-md"> | |
| <defs> | |
| <radialGradient id="glow" cx="50%" cy="50%"> | |
| <stop offset="0%" stopColor="#a78bfa" stopOpacity="0.4" /> | |
| <stop offset="100%" stopColor="#a78bfa" stopOpacity="0" /> | |
| </radialGradient> | |
| </defs> | |
| <circle cx={cx} cy={cy} r={r + 30} fill="url(#glow)" /> | |
| <circle cx={cx} cy={cy} r={r} fill="none" stroke="#52525b" strokeDasharray="3 4" /> | |
| {stages.map((s, i) => { | |
| const rad = (s.a * Math.PI) / 180; | |
| const x = cx + r * Math.cos(rad); | |
| const y = cy + r * Math.sin(rad); | |
| return ( | |
| <g key={i}> | |
| <circle cx={x} cy={y} r="32" fill={`${s.c}20`} stroke={s.c} strokeWidth="2" /> | |
| <text x={x} y={y - 2} textAnchor="middle" fill={s.c} fontSize="11" fontWeight="bold">{s.t}</text> | |
| <text x={x} y={y + 11} textAnchor="middle" fill="#a1a1aa" fontSize="8">{s.d}</text> | |
| </g> | |
| ); | |
| })} | |
| <marker id="ar" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> | |
| <path d="M0,0 L6,4 L0,8" fill="#71717a" /> | |
| </marker> | |
| {[0, 1, 2, 3].map((i) => { | |
| const a1 = (stages[i].a * Math.PI) / 180; | |
| const a2 = (stages[(i + 1) % 4].a * Math.PI) / 180; | |
| const x1 = cx + (r - 32) * Math.cos(a1 + 0.4); | |
| const y1 = cy + (r - 32) * Math.sin(a1 + 0.4); | |
| const x2 = cx + (r - 32) * Math.cos(a2 - 0.4); | |
| const y2 = cy + (r - 32) * Math.sin(a2 - 0.4); | |
| return <path key={i} d={`M${x1} ${y1} Q${cx} ${cy} ${x2} ${y2}`} fill="none" stroke="#71717a" strokeWidth="1.5" markerEnd="url(#ar)" />; | |
| })} | |
| <text x={cx} y={cy - 4} textAnchor="middle" fill="#e4e4e7" fontSize="11" fontWeight="bold">until</text> | |
| <text x={cx} y={cy + 9} textAnchor="middle" fill="#e4e4e7" fontSize="11" fontWeight="bold">done</text> | |
| </svg> | |
| </div> | |
| ); | |
| }; | |
| const ContextBarDiagram = () => ( | |
| <div className="my-6"> | |
| <div className="text-xs text-zinc-500 mb-2 flex justify-between"> | |
| <span>0K</span> | |
| <span className="text-zinc-300 font-bold">200K total context window</span> | |
| <span>200K</span> | |
| </div> | |
| <div className="flex h-14 rounded-lg overflow-hidden border border-zinc-700"> | |
| <div className="bg-rose-500/40 flex items-center justify-center text-rose-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "10%" }}>Fixed 15-20K</div> | |
| <div className="bg-amber-500/30 flex items-center justify-center text-amber-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "5%" }}>Semi 5-10K</div> | |
| <div className="bg-emerald-500/20 flex items-center justify-center text-emerald-200 text-xs font-semibold" style={{ width: "85%" }}>Dynamically available ~160-180K</div> | |
| </div> | |
| <div className="grid md:grid-cols-3 gap-2 mt-3 text-xs"> | |
| <div className="bg-rose-500/10 border border-rose-500/30 rounded p-2"> | |
| <div className="font-bold text-rose-400">Fixed overhead</div> | |
| <div className="text-zinc-400">System prompt · tool defs · skill descriptors · LSP. MCP tools alone can be 10-20K.</div> | |
| </div> | |
| <div className="bg-amber-500/10 border border-amber-500/30 rounded p-2"> | |
| <div className="font-bold text-amber-400">Semi-fixed</div> | |
| <div className="text-zinc-400">CLAUDE.md and persistent memory.</div> | |
| </div> | |
| <div className="bg-emerald-500/10 border border-emerald-500/30 rounded p-2"> | |
| <div className="font-bold text-emerald-400">Dynamic</div> | |
| <div className="text-zinc-400">Conversation, files read, tool outputs. Where the real work happens.</div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| const WorkflowVsAgent = () => ( | |
| <div className="my-6 grid md:grid-cols-2 gap-4"> | |
| <div className="bg-cyan-500/5 border border-cyan-500/30 rounded-xl p-4"> | |
| <div className="text-cyan-400 font-bold mb-3 flex items-center gap-2"><GitBranch size={16} /> Workflow</div> | |
| <svg viewBox="0 0 300 100" className="w-full"> | |
| <defs><marker id="aa1" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L6,4 L0,8" fill="#22d3ee" /></marker></defs> | |
| {[40, 110, 180, 250].map((x, i) => ( | |
| <g key={i}> | |
| <rect x={x - 18} y="40" width="36" height="20" rx="4" fill="#22d3ee20" stroke="#22d3ee" /> | |
| <text x={x} y="54" textAnchor="middle" fill="#22d3ee" fontSize="10">step {i + 1}</text> | |
| </g> | |
| ))} | |
| {[0, 1, 2].map((i) => <line key={i} x1={58 + i * 70} y1="50" x2={92 + i * 70} y2="50" stroke="#22d3ee" markerEnd="url(#aa1)" />)} | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2">A railway. Same input always rides the same track.</div> | |
| </div> | |
| <div className="bg-violet-500/5 border border-violet-500/30 rounded-xl p-4"> | |
| <div className="text-violet-400 font-bold mb-3 flex items-center gap-2"><Sparkles size={16} /> Agent</div> | |
| <svg viewBox="0 0 300 100" className="w-full"> | |
| <defs><marker id="aa2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L6,4 L0,8" fill="#a78bfa" /></marker></defs> | |
| <circle cx="40" cy="50" r="14" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <text x="40" y="54" textAnchor="middle" fill="#a78bfa" fontSize="10">start</text> | |
| <circle cx="120" cy="25" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="120" cy="75" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="25" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="50" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="75" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="270" cy="50" r="14" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <text x="270" y="54" textAnchor="middle" fill="#a78bfa" fontSize="10">end</text> | |
| <line x1="54" y1="46" x2="106" y2="29" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="54" y1="54" x2="106" y2="71" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="25" x2="186" y2="25" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="33" x2="186" y2="46" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="75" x2="186" y2="75" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="25" x2="256" y2="46" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="50" x2="256" y2="50" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="75" x2="256" y2="54" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2">A driver picking the route. Each turn the model decides.</div> | |
| </div> | |
| </div> | |
| ); | |
| const PatternIcons = () => { | |
| const ps = [ | |
| { n: "Prompt Chaining", d: "Linear: A → B → C", c: "#fbbf24", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><circle cx="60" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><circle cx="100" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><line x1="30" y1="40" x2="50" y2="40" stroke="#fbbf24" /><line x1="70" y1="40" x2="90" y2="40" stroke="#fbbf24" /></> }, | |
| { n: "Routing", d: "Classify, then pick a path", c: "#fb923c", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#fb923c40" stroke="#fb923c" /><circle cx="80" cy="20" r="10" fill="#fb923c40" stroke="#fb923c" /><circle cx="80" cy="60" r="10" fill="#fb923c40" stroke="#fb923c" /><line x1="30" y1="38" x2="70" y2="22" stroke="#fb923c" /><line x1="30" y1="42" x2="70" y2="58" stroke="#fb923c" /></> }, | |
| { n: "Parallelization", d: "Run several at once", c: "#a78bfa", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="15" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="40" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="65" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="110" cy="40" r="10" fill="#a78bfa40" stroke="#a78bfa" /></> }, | |
| { n: "Orchestrator-Workers", d: "Boss + delegated workers", c: "#22d3ee", | |
| svg: <><circle cx="60" cy="20" r="11" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="20" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="60" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="100" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><line x1="55" y1="30" x2="25" y2="56" stroke="#22d3ee" /><line x1="60" y1="31" x2="60" y2="56" stroke="#22d3ee" /><line x1="65" y1="30" x2="95" y2="56" stroke="#22d3ee" /></> }, | |
| { n: "Evaluator-Optimizer", d: "Generate → critique → loop", c: "#34d399", | |
| svg: <><circle cx="30" cy="40" r="11" fill="#34d39940" stroke="#34d399" /><circle cx="90" cy="40" r="11" fill="#34d39940" stroke="#34d399" /><path d="M40 35 Q60 15 80 35" fill="none" stroke="#34d399" /><path d="M40 45 Q60 65 80 45" fill="none" stroke="#34d399" /></> }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-3 gap-3 my-4"> | |
| {ps.map((p, i) => ( | |
| <div key={i} className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <svg viewBox="0 0 130 80" className="w-full h-16">{p.svg}</svg> | |
| <div className="font-bold text-sm" style={{ color: p.c }}>{p.n}</div> | |
| <div className="text-xs text-zinc-500">{p.d}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const HarnessQuadrant = () => ( | |
| <div className="my-6 max-w-md mx-auto"> | |
| <svg viewBox="0 0 320 280" className="w-full"> | |
| <line x1="40" y1="240" x2="290" y2="240" stroke="#52525b" strokeWidth="1.5" /> | |
| <line x1="40" y1="240" x2="40" y2="20" stroke="#52525b" strokeWidth="1.5" /> | |
| <text x="165" y="270" textAnchor="middle" fill="#a1a1aa" fontSize="10">→ goal clarity</text> | |
| <text x="20" y="130" textAnchor="middle" fill="#a1a1aa" fontSize="10" transform="rotate(-90 20 130)">→ verify automation</text> | |
| <rect x="45" y="125" width="115" height="110" fill="#71717a20" stroke="#52525b" rx="4" /> | |
| <text x="102" y="180" textAnchor="middle" fill="#a1a1aa" fontSize="9">vague +</text> | |
| <text x="102" y="192" textAnchor="middle" fill="#a1a1aa" fontSize="9">manual</text> | |
| <text x="102" y="208" textAnchor="middle" fill="#71717a" fontSize="8">nothing works</text> | |
| <rect x="165" y="125" width="120" height="110" fill="#fbbf2420" stroke="#fbbf24" rx="4" /> | |
| <text x="225" y="180" textAnchor="middle" fill="#fbbf24" fontSize="9">vague +</text> | |
| <text x="225" y="192" textAnchor="middle" fill="#fbbf24" fontSize="9">automatic</text> | |
| <text x="225" y="208" textAnchor="middle" fill="#fbbf24" fontSize="8">runs wrong fast</text> | |
| <rect x="45" y="25" width="115" height="95" fill="#22d3ee20" stroke="#22d3ee" rx="4" /> | |
| <text x="102" y="65" textAnchor="middle" fill="#22d3ee" fontSize="9">clear +</text> | |
| <text x="102" y="77" textAnchor="middle" fill="#22d3ee" fontSize="9">manual</text> | |
| <text x="102" y="93" textAnchor="middle" fill="#22d3ee" fontSize="8">capped by humans</text> | |
| <rect x="165" y="25" width="120" height="95" fill="#34d39930" stroke="#34d399" rx="4" /> | |
| <text x="225" y="60" textAnchor="middle" fill="#34d399" fontSize="11" fontWeight="bold">SWEET SPOT</text> | |
| <text x="225" y="78" textAnchor="middle" fill="#34d399" fontSize="9">clear + automatic</text> | |
| <text x="225" y="94" textAnchor="middle" fill="#34d399" fontSize="8">agents thrive here</text> | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2 text-center">A harness pushes tasks into the top-right.</div> | |
| </div> | |
| ); | |
| const SkillsLoadDiagram = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 460 220" className="w-full max-w-2xl mx-auto"> | |
| <defs><marker id="sa" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L8,5 L0,10" fill="#a78bfa" /></marker></defs> | |
| <rect x="20" y="20" width="200" height="180" rx="8" fill="#fbbf2410" stroke="#fbbf24" /> | |
| <text x="120" y="40" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">System Prompt (always loaded)</text> | |
| <text x="120" y="60" textAnchor="middle" fill="#a1a1aa" fontSize="9">just the descriptors:</text> | |
| {["deploy: use when shipping", "review: use for PR checks", "migrate: use for schema changes"].map((t, i) => ( | |
| <g key={i}> | |
| <rect x="35" y={75 + i * 30} width="170" height="22" rx="3" fill="#fbbf2420" stroke="#fbbf2466" /> | |
| <text x="42" y={90 + i * 30} fill="#fde68a" fontSize="9" fontFamily="monospace">{t}</text> | |
| </g> | |
| ))} | |
| <text x="120" y="190" textAnchor="middle" fill="#71717a" fontSize="8">~20 tokens total</text> | |
| <path d="M225 135 Q260 135 290 135" stroke="#a78bfa" strokeWidth="2" strokeDasharray="4 3" fill="none" markerEnd="url(#sa)" /> | |
| <text x="255" y="125" textAnchor="middle" fill="#a78bfa" fontSize="9">on demand</text> | |
| <rect x="290" y="20" width="155" height="50" rx="6" fill="#a78bfa15" stroke="#a78bfa" strokeDasharray="3 2" /> | |
| <text x="367" y="40" textAnchor="middle" fill="#a78bfa" fontSize="10">deploy/SKILL.md</text> | |
| <text x="367" y="55" textAnchor="middle" fill="#71717a" fontSize="8">full body, only when triggered</text> | |
| <rect x="290" y="80" width="155" height="50" rx="6" fill="#a78bfa10" stroke="#a78bfa66" strokeDasharray="3 2" /> | |
| <text x="367" y="100" textAnchor="middle" fill="#a78bfa99" fontSize="10">review/SKILL.md</text> | |
| <text x="367" y="115" textAnchor="middle" fill="#71717a" fontSize="8">sleeping</text> | |
| <rect x="290" y="140" width="155" height="50" rx="6" fill="#a78bfa10" stroke="#a78bfa66" strokeDasharray="3 2" /> | |
| <text x="367" y="160" textAnchor="middle" fill="#a78bfa99" fontSize="10">migrate/SKILL.md</text> | |
| <text x="367" y="175" textAnchor="middle" fill="#71717a" fontSize="8">sleeping</text> | |
| </svg> | |
| </div> | |
| ); | |
| const ToolGenDiagram = () => { | |
| const gens = [ | |
| { n: "Gen 1", t: "API Wrappers", d: "One tool per endpoint. Forces the agent to chain 5 calls to do one thing.", c: "#f87171" }, | |
| { n: "Gen 2", t: "ACI", d: "One tool per agent goal. update_post(id, title, md) in one shot.", c: "#fbbf24" }, | |
| { n: "Gen 3", t: "Advanced", d: "Search tools on demand. Programmatic calling. Examples per tool.", c: "#34d399" }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-3 gap-3 my-6"> | |
| {gens.map((g, i) => ( | |
| <div key={i} className="relative bg-zinc-900/50 border-2 rounded-xl p-4" style={{ borderColor: `${g.c}66` }}> | |
| <div className="text-xs font-bold tracking-widest mb-1" style={{ color: g.c }}>{g.n}</div> | |
| <div className="font-bold text-zinc-100 mb-2">{g.t}</div> | |
| <div className="text-zinc-400 text-xs">{g.d}</div> | |
| {i < 2 && <div className="hidden md:block absolute -right-3 top-1/2 text-zinc-600">→</div>} | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const HookTimeline = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 500 130" className="w-full max-w-2xl mx-auto"> | |
| <line x1="20" y1="65" x2="480" y2="65" stroke="#52525b" strokeWidth="2" /> | |
| {[ | |
| { x: 60, n: "SessionStart", d: "inject env" }, | |
| { x: 170, n: "PreToolUse", d: "block / approve" }, | |
| { x: 280, n: "PostToolUse", d: "format / lint" }, | |
| { x: 390, n: "Notification", d: "ping you" }, | |
| { x: 470, n: "Stop", d: "" }, | |
| ].map((s, i) => ( | |
| <g key={i}> | |
| <circle cx={s.x} cy="65" r="6" fill="#a78bfa" stroke="#1e1b4b" strokeWidth="2" /> | |
| <text x={s.x} y="40" textAnchor="middle" fill="#a78bfa" fontSize="10" fontWeight="bold">{s.n}</text> | |
| <text x={s.x} y="92" textAnchor="middle" fill="#a1a1aa" fontSize="8">{s.d}</text> | |
| </g> | |
| ))} | |
| <text x="20" y="115" fill="#71717a" fontSize="9">session begins</text> | |
| <text x="480" y="115" textAnchor="end" fill="#71717a" fontSize="9">session ends</text> | |
| </svg> | |
| </div> | |
| ); | |
| const SubagentDiagram = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 460 240" className="w-full max-w-xl mx-auto"> | |
| <rect x="170" y="20" width="120" height="50" rx="8" fill="#fbbf2420" stroke="#fbbf24" strokeWidth="2" /> | |
| <text x="230" y="42" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">Main Agent</text> | |
| <text x="230" y="58" textAnchor="middle" fill="#a1a1aa" fontSize="9">full context, all tools</text> | |
| {[ | |
| { x: 60, c: "#22d3ee", n: "Explore", d: "read-only scan", s: "Haiku" }, | |
| { x: 200, c: "#a78bfa", n: "Review", d: "scoped review", s: "Opus" }, | |
| { x: 340, c: "#34d399", n: "Test runner", d: "test only", s: "Sonnet" }, | |
| ].map((s, i) => ( | |
| <g key={i}> | |
| <rect x={s.x} y="140" width="100" height="80" rx="8" fill={`${s.c}15`} stroke={s.c} strokeDasharray="4 2" /> | |
| <text x={s.x + 50} y="160" textAnchor="middle" fill={s.c} fontSize="11" fontWeight="bold">{s.n}</text> | |
| <text x={s.x + 50} y="178" textAnchor="middle" fill="#a1a1aa" fontSize="9">{s.d}</text> | |
| <text x={s.x + 50} y="200" textAnchor="middle" fill="#71717a" fontSize="8">limited tools</text> | |
| <text x={s.x + 50} y="212" textAnchor="middle" fill="#71717a" fontSize="8">model: {s.s}</text> | |
| <line x1="230" y1="70" x2={s.x + 50} y2="138" stroke={s.c} strokeWidth="1.5" strokeDasharray="3 2" /> | |
| <text x={(230 + s.x + 50) / 2 + 5} y={104 + i * 4} fill={s.c} fontSize="8">summary →</text> | |
| </g> | |
| ))} | |
| </svg> | |
| <div className="text-zinc-400 text-xs text-center mt-2">Each subagent has its own context. Only summaries flow back.</div> | |
| </div> | |
| ); | |
| const CacheLayout = () => ( | |
| <div className="my-6"> | |
| <div className="text-xs text-zinc-500 mb-2">Request prefix → suffix (order matters!)</div> | |
| <div className="flex h-12 rounded-lg overflow-hidden border border-zinc-700"> | |
| <div className="bg-emerald-500/30 flex items-center px-3 text-emerald-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "30%" }}>System Prompt 🔒</div> | |
| <div className="bg-emerald-500/20 flex items-center px-3 text-emerald-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "30%" }}>Tool Definitions 🔒</div> | |
| <div className="bg-amber-500/20 flex items-center px-3 text-amber-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "25%" }}>Chat History</div> | |
| <div className="bg-rose-500/20 flex items-center px-3 text-rose-200 text-xs font-semibold" style={{ width: "15%" }}>New Input</div> | |
| </div> | |
| <div className="grid md:grid-cols-4 gap-2 mt-2 text-xs"> | |
| <div className="text-emerald-400">cached, locked</div> | |
| <div className="text-emerald-400">cached, locked</div> | |
| <div className="text-amber-400">grows over time</div> | |
| <div className="text-rose-400">always fresh</div> | |
| </div> | |
| </div> | |
| ); | |
| const MemoryDiagram = () => { | |
| const ts = [ | |
| { n: "Working", c: "#fbbf24", w: "context window", e: "current task only", l: "messages[] in RAM" }, | |
| { n: "Procedural", c: "#a78bfa", w: "Skills", e: "how to do things", l: ".claude/skills/*" }, | |
| { n: "Episodic", c: "#22d3ee", w: "Session logs", e: "what happened", l: "JSONL on disk" }, | |
| { n: "Semantic", c: "#34d399", w: "MEMORY.md", e: "stable facts", l: "injected each start" }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-2 gap-3 my-6"> | |
| {ts.map((t, i) => ( | |
| <div key={i} className="border-2 rounded-xl p-4" style={{ borderColor: `${t.c}55`, background: `${t.c}10` }}> | |
| <div className="font-bold mb-1" style={{ color: t.c }}>{t.n}</div> | |
| <div className="text-zinc-300 text-sm font-semibold">{t.w}</div> | |
| <div className="text-zinc-400 text-xs mt-1">{t.e}</div> | |
| <div className="text-zinc-500 text-xs mt-2 font-mono">{t.l}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const MultiAgentTopo = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 480 280" className="w-full max-w-2xl mx-auto"> | |
| <rect x="180" y="15" width="120" height="50" rx="8" fill="#fbbf2425" stroke="#fbbf24" strokeWidth="2" /> | |
| <text x="240" y="38" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">Orchestrator</text> | |
| <text x="240" y="54" textAnchor="middle" fill="#a1a1aa" fontSize="9">writes JSONL inbox</text> | |
| <rect x="80" y="100" width="120" height="40" rx="6" fill="#27272a" stroke="#52525b" /> | |
| <text x="140" y="125" textAnchor="middle" fill="#e4e4e7" fontSize="10" fontFamily="monospace">.team/inbox/</text> | |
| <rect x="280" y="100" width="120" height="40" rx="6" fill="#27272a" stroke="#52525b" /> | |
| <text x="340" y="125" textAnchor="middle" fill="#e4e4e7" fontSize="10" fontFamily="monospace">.tasks/graph.json</text> | |
| <line x1="220" y1="65" x2="160" y2="98" stroke="#fbbf24" strokeDasharray="3 2" /> | |
| <line x1="260" y1="65" x2="320" y2="98" stroke="#fbbf24" strokeDasharray="3 2" /> | |
| {[ | |
| { x: 30, c: "#22d3ee", n: "Worker A" }, | |
| { x: 180, c: "#a78bfa", n: "Worker B" }, | |
| { x: 330, c: "#f472b6", n: "Worker C" }, | |
| ].map((w, i) => ( | |
| <g key={i}> | |
| <rect x={w.x} y="190" width="120" height="80" rx="8" fill={`${w.c}15`} stroke={w.c} strokeWidth="2" /> | |
| <text x={w.x + 60} y="212" textAnchor="middle" fill={w.c} fontSize="11" fontWeight="bold">{w.n}</text> | |
| <rect x={w.x + 10} y="225" width="100" height="36" rx="4" fill="#18181b" stroke={`${w.c}77`} strokeDasharray="2 2" /> | |
| <text x={w.x + 60} y="240" textAnchor="middle" fill="#a1a1aa" fontSize="8">.worktrees/{["a","b","c"][i]}</text> | |
| <text x={w.x + 60} y="253" textAnchor="middle" fill="#71717a" fontSize="8">isolated files</text> | |
| <line x1="140" y1="140" x2={w.x + 60} y2="188" stroke={w.c} strokeDasharray="3 2" /> | |
| </g> | |
| ))} | |
| </svg> | |
| <div className="text-zinc-400 text-xs text-center mt-2">Protocol → Isolation → Then collaboration. Never the other way.</div> | |
| </div> | |
| ); | |
| // Interactive: 6-layer explorer | |
| function InteractiveLayerExplorer() { | |
| const [selected, setSelected] = useState(5); | |
| const layers = [ | |
| { n: "Verifiers", d: "Validation that's testable & auditable", c: "#34d399", | |
| ex: "make test, lint, exit codes, eval rubrics, manual review checklists", | |
| why: "Without them, 'done' is whatever the model says. With them, 'done' means something." }, | |
| { n: "Subagents", d: "Context-isolated workers", c: "#22d3ee", | |
| ex: "Explore subagent (read-only scan), Review subagent (scoped review), Test-runner", | |
| why: "When a task would dump too much intermediate noise into the main thread, push it down here." }, | |
| { n: "Hooks", d: "Enforced behaviors, no model judgment", c: "#a78bfa", | |
| ex: "Block edits to .env, auto-format on Edit, ping you when long task finishes", | |
| why: "If a rule should always hold, encode it. Don't beg the model to remember it." }, | |
| { n: "Skills", d: "On-demand methodologies", c: "#f472b6", | |
| ex: "Deploy procedure, code review checklist, incident triage flowchart", | |
| why: "Heavy procedure docs that should only load when relevant. Progressive disclosure saves the window." }, | |
| { n: "Tools / MCP", d: "What Claude can do", c: "#fb923c", | |
| ex: "Bash, Edit, Write, Read, plus MCP servers like GitHub, Sentry, Jira", | |
| why: "Capability surface. Choose carefully — every tool burns context." }, | |
| { n: "CLAUDE.md / rules / memory", d: "What this project is", c: "#fbbf24", | |
| ex: "Build commands, code conventions, NEVER list, retention priorities", | |
| why: "The contract. Stable facts that should be in scope every session." }, | |
| ]; | |
| const cur = layers[selected]; | |
| return ( | |
| <div className="my-6 bg-zinc-900/40 border border-zinc-800 rounded-xl overflow-hidden"> | |
| <div className="grid md:grid-cols-[260px_1fr]"> | |
| <div className="border-b md:border-b-0 md:border-r border-zinc-800 p-3 space-y-1"> | |
| {layers.map((l, i) => ( | |
| <button key={i} onClick={() => setSelected(i)} | |
| className={`w-full text-left px-3 py-2 rounded-lg transition flex items-center gap-2 ${selected === i ? 'bg-zinc-800' : 'hover:bg-zinc-800/50'}`}> | |
| <span className="w-2 h-2 rounded-full" style={{ background: l.c }} /> | |
| <span className="font-semibold text-sm" style={{ color: selected === i ? l.c : "#d4d4d8" }}>{l.n}</span> | |
| <span className="ml-auto text-xs text-zinc-600 font-mono">L{6 - i}</span> | |
| </button> | |
| ))} | |
| </div> | |
| <div className="p-5 fade-in" key={selected}> | |
| <div className="text-xs font-bold tracking-widest" style={{ color: cur.c }}>LAYER {6 - selected}</div> | |
| <div className="text-xl font-bold text-zinc-100 mt-1">{cur.n}</div> | |
| <div className="text-zinc-400 text-sm mt-1">{cur.d}</div> | |
| <div className="mt-4"> | |
| <div className="text-xs text-zinc-500 font-bold tracking-widest mb-1">EXAMPLES</div> | |
| <div className="text-sm text-zinc-300">{cur.ex}</div> | |
| </div> | |
| <div className="mt-4"> | |
| <div className="text-xs text-zinc-500 font-bold tracking-widest mb-1">WHY IT MATTERS</div> | |
| <div className="text-sm text-zinc-300">{cur.why}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Interactive: Agent loop simulator | |
| function AgentLoopSim() { | |
| const [step, setStep] = useState(0); | |
| const trace = [ | |
| { who: "user", msg: "Find all files using deprecated API and list them" }, | |
| { who: "claude", msg: "I'll search the codebase. Let me use Grep.", action: "thinking" }, | |
| { who: "tool", msg: 'Grep(pattern: "oldApi.fetch")', action: "tool_call" }, | |
| { who: "tool_result", msg: "Found 7 matches across 4 files: api.ts, sync.ts, jobs.ts, worker.ts" }, | |
| { who: "claude", msg: "Found 4 files. Let me check each for context.", action: "thinking" }, | |
| { who: "tool", msg: 'Read(file: "api.ts")', action: "tool_call" }, | |
| { who: "tool_result", msg: "[~120 lines of source code]" }, | |
| { who: "claude", msg: "Here's the report:\n\n• api.ts:42 — direct call in handler\n• sync.ts:88, 91 — two callers\n• jobs.ts:15 — scheduled task\n• worker.ts:203 — background queue\n\nAll need updating to newApi.fetch.", action: "stop" }, | |
| ]; | |
| const cur = trace[step]; | |
| return ( | |
| <div className="my-6 bg-black/50 border border-zinc-800 rounded-xl overflow-hidden"> | |
| <div className="px-4 py-2 bg-zinc-900/70 border-b border-zinc-800 flex items-center justify-between"> | |
| <div className="text-xs text-zinc-400 font-mono">▸ agent loop · step {step + 1}/{trace.length}</div> | |
| <div className="flex gap-2"> | |
| <button onClick={() => setStep(Math.max(0, step - 1))} disabled={step === 0} | |
| className="text-xs px-2 py-1 rounded bg-zinc-800 hover:bg-zinc-700 disabled:opacity-30">prev</button> | |
| <button onClick={() => setStep(Math.min(trace.length - 1, step + 1))} disabled={step === trace.length - 1} | |
| className="text-xs px-2 py-1 rounded bg-violet-700 hover:bg-violet-600 disabled:opacity-30">next →</button> | |
| <button onClick={() => setStep(0)} className="text-xs px-2 py-1 rounded bg-zinc-800 hover:bg-zinc-700">reset</button> | |
| </div> | |
| </div> | |
| <div className="p-4 space-y-2 max-h-80 overflow-y-auto"> | |
| {trace.slice(0, step + 1).map((t, i) => ( | |
| <div key={i} className="fade-in"> | |
| {t.who === "user" && ( | |
| <div className="bg-zinc-800/60 rounded-lg p-3 text-sm text-zinc-200"><span className="text-amber-400 font-bold text-xs">USER</span><div className="mt-1">{t.msg}</div></div> | |
| )} | |
| {t.who === "claude" && ( | |
| <div className="bg-violet-500/10 border border-violet-500/30 rounded-lg p-3 text-sm text-zinc-200"><span className="text-violet-400 font-bold text-xs">CLAUDE</span><div className="mt-1 whitespace-pre-line">{t.msg}</div></div> | |
| )} | |
| {t.who === "tool" && ( | |
| <div className="bg-cyan-500/5 border border-cyan-500/30 rounded-lg p-3 font-mono text-xs text-cyan-200 ml-6"><span className="text-cyan-400 font-bold text-xs">TOOL CALL</span><div className="mt-1">{t.msg}</div></div> | |
| )} | |
| {t.who === "tool_result" && ( | |
| <div className="bg-emerald-500/5 border border-emerald-500/30 rounded-lg p-3 font-mono text-xs text-emerald-200 ml-6"><span className="text-emerald-400 font-bold text-xs">TOOL RESULT</span><div className="mt-1">{t.msg}</div></div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| <div className="px-4 py-2 bg-zinc-900/70 border-t border-zinc-800 text-xs text-zinc-500"> | |
| {step === trace.length - 1 ? "✓ Loop terminated — model returned plain text, no tool call." : "→ Loop continues until model returns text without tool calls."} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Interactive: tool design good vs bad | |
| function ToolDesignDemo() { | |
| const [view, setView] = useState("bad"); | |
| return ( | |
| <div className="my-6"> | |
| <div className="flex gap-2 mb-3"> | |
| <button onClick={() => setView("bad")} | |
| className={`px-3 py-1.5 rounded-md text-sm font-semibold transition ${view === "bad" ? "bg-rose-500/20 text-rose-300 border border-rose-500/40" : "bg-zinc-800 text-zinc-400 border border-zinc-800"}`}> | |
| ✗ Bad design | |
| </button> | |
| <button onClick={() => setView("good")} | |
| className={`px-3 py-1.5 rounded-md text-sm font-semibold transition ${view === "good" ? "bg-emerald-500/20 text-emerald-300 border border-emerald-500/40" : "bg-zinc-800 text-zinc-400 border border-zinc-800"}`}> | |
| ✓ Good design | |
| </button> | |
| </div> | |
| {view === "bad" ? ( | |
| <CodeBlock language="json" label="bad tool definition">{`{ | |
| "name": "query", | |
| "description": "Run a query on the system.", | |
| "parameters": { | |
| "id": { "type": "string" }, | |
| "name": { "type": "string" }, | |
| "type": { "type": "string" }, | |
| "value": { "type": "string" } | |
| } | |
| }`}</CodeBlock> | |
| ) : ( | |
| <CodeBlock language="json" label="good tool definition">{`{ | |
| "name": "jira_issue_get", | |
| "description": "Fetch a single Jira issue. Use when the user references an issue by ID (e.g. PROJ-123) or asks about its status, comments, or assignee.", | |
| "parameters": { | |
| "issue_key": { | |
| "type": "string", | |
| "description": "Jira issue key like PROJ-123. Must be uppercase project + dash + number.", | |
| "pattern": "^[A-Z]+-[0-9]+$" | |
| }, | |
| "include_comments": { | |
| "type": "boolean", | |
| "description": "If true, include the most recent 20 comments. Default false to keep response small.", | |
| "default": false | |
| } | |
| }, | |
| "errors": { | |
| "ISSUE_NOT_FOUND": "Returned when issue_key is invalid. Suggest: call jira_search to find similar keys.", | |
| "AUTH_REQUIRED": "Returned when token is missing or expired." | |
| } | |
| }`}</CodeBlock> | |
| )} | |
| <div className={`text-xs mt-2 ${view === "bad" ? "text-rose-400" : "text-emerald-400"}`}> | |
| {view === "bad" | |
| ? "Vague name. Untyped params. No errors documented. Model has to guess what 'query' does." | |
| : "Specific name. Typed pattern. Output-size hint via include_comments. Errors guide recovery."} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Interactive: context window simulator | |
| function ContextSim() { | |
| const [items, setItems] = useState({ system: 5, tools: 8, claude_md: 3, mcp: 0, skills_desc: 1 }); | |
| const total = 200; | |
| const used = items.system + items.tools + items.claude_md + items.mcp + items.skills_desc; | |
| const free = total - used; | |
| return ( | |
| <div className="my-6 bg-zinc-900/40 border border-zinc-800 rounded-xl p-5"> | |
| <div className="text-sm text-zinc-300 mb-3">Try toggling MCP servers on and watch your free context shrink:</div> | |
| <div className="grid md:grid-cols-2 gap-3 text-sm"> | |
| <label className="flex justify-between items-center bg-zinc-800/50 rounded p-2"> | |
| <span className="text-zinc-300">5 MCP servers connected (~20K each)</span> | |
| <input type="range" min="0" max="100" step="20" value={items.mcp} onChange={e => setItems({...items, mcp: Number(e.target.value)})} className="w-24" /> | |
| </label> | |
| <label className="flex justify-between items-center bg-zinc-800/50 rounded p-2"> | |
| <span className="text-zinc-300">CLAUDE.md size (3=lean, 30=bloated)</span> | |
| <input type="range" min="3" max="30" step="3" value={items.claude_md} onChange={e => setItems({...items, claude_md: Number(e.target.value)})} className="w-24" /> | |
| </label> | |
| </div> | |
| <div className="mt-4"> | |
| <div className="text-xs text-zinc-500 mb-1 flex justify-between"><span>Used: {used}K</span><span>Free: <span className={free < 50 ? "text-rose-400" : free < 100 ? "text-amber-400" : "text-emerald-400"}>{free}K</span></span></div> | |
| <div className="flex h-8 rounded overflow-hidden border border-zinc-700"> | |
| <div className="bg-rose-500/40" style={{ width: `${(items.system/total)*100}%` }} title="system prompt" /> | |
| <div className="bg-orange-500/40" style={{ width: `${(items.tools/total)*100}%` }} title="builtin tools" /> | |
| <div className="bg-amber-500/40" style={{ width: `${(items.claude_md/total)*100}%` }} title="CLAUDE.md" /> | |
| <div className="bg-violet-500/40" style={{ width: `${(items.mcp/total)*100}%` }} title="MCP servers" /> | |
| <div className="bg-pink-500/40" style={{ width: `${(items.skills_desc/total)*100}%` }} title="skill descriptors" /> | |
| <div className="bg-emerald-500/15" style={{ width: `${(free/total)*100}%` }} title="free" /> | |
| </div> | |
| <div className="text-xs text-zinc-500 mt-2">Just connecting 5 typical MCP servers eats 20% of your window before you say a word.</div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Quiz checkpoint | |
| function Checkpoint({ q, options, correct, explain }) { | |
| const [picked, setPicked] = useState(null); | |
| return ( | |
| <div className="my-6 bg-gradient-to-br from-amber-500/10 to-violet-500/10 border border-zinc-700 rounded-xl p-5"> | |
| <div className="text-xs font-bold tracking-widest text-amber-400 mb-2">CHECKPOINT</div> | |
| <div className="text-zinc-100 font-semibold mb-3">{q}</div> | |
| <div className="space-y-2"> | |
| {options.map((o, i) => ( | |
| <button key={i} onClick={() => setPicked(i)} | |
| className={`w-full text-left px-3 py-2 rounded-md text-sm border transition ${ | |
| picked === null ? "bg-zinc-900/40 border-zinc-700 hover:border-zinc-500" | |
| : i === correct ? "bg-emerald-500/15 border-emerald-500/50 text-emerald-200" | |
| : i === picked ? "bg-rose-500/15 border-rose-500/50 text-rose-200" | |
| : "bg-zinc-900/40 border-zinc-800 opacity-60" | |
| }`}> | |
| <span className="text-xs font-mono mr-2 opacity-60">{String.fromCharCode(65 + i)}.</span> | |
| {o} | |
| {picked !== null && i === correct && <span className="ml-2 text-xs">✓ correct</span>} | |
| {picked !== null && i === picked && i !== correct && <span className="ml-2 text-xs">✗</span>} | |
| </button> | |
| ))} | |
| </div> | |
| {picked !== null && <div className="mt-3 text-sm text-zinc-300 bg-zinc-900/40 rounded p-3">{explain}</div>} | |
| </div> | |
| ); | |
| } | |
| // ============================================================================ | |
| // CHAPTER CONTENT | |
| // ============================================================================ | |
| const Ch = ({ children }) => <div className="drop-cap text-zinc-300 leading-relaxed space-y-4">{children}</div>; | |
| // PART 1: FOUNDATIONS | |
| const Ch01 = () => (<Ch> | |
| <p>If you're reading this, something probably caught your eye about Claude Code. Maybe you saw a developer ship a feature in an afternoon that should have taken a week. Maybe a teammate tossed off "I just had Claude refactor the whole thing" like it was nothing. Maybe you opened a terminal, typed <InlineCode>claude</InlineCode>, and ran into the same wall everyone runs into: it works, it's fast, it's exciting — and then halfway through a real task it does something baffling, and you have no idea why.</p> | |
| <p>This book is for that second moment. The "it works but I don't know why, and now it's not working and I really don't know why" moment. We're going to take you from that confusion to a clean mental model — one that holds up whether you're using Claude Code casually for solo projects or building production agent systems with millions of users.</p> | |
| <p>It's distilled from two long, dense articles by Tw93 (linked at the end), reorganized for a reader who is meeting these ideas for the first time and wants to walk away with a complete framework, not a pile of tips.</p> | |
| <Box1 tone="amber" title="What you'll get from this book" icon={BookOpen}> | |
| <ul className="space-y-1 text-sm list-disc list-inside"> | |
| <li>A complete mental model for how Claude Code is layered together</li> | |
| <li>The single most important diagram in agent design: workflows vs agents</li> | |
| <li>How to think about context as an engineering resource, not infinite RAM</li> | |
| <li>Concrete patterns for skills, hooks, subagents, and tools that actually scale</li> | |
| <li>How to verify, evaluate, and debug agent systems without losing your mind</li> | |
| <li>A catalogue of the most common ways teams shoot themselves in the foot</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Who this is for</h3> | |
| <p>Three audiences, in order of priority. If you're <strong className="text-zinc-100">a developer using Claude Code daily</strong>, you'll find the framework that makes the system stop feeling magical and start feeling diagnosable. If you're <strong className="text-zinc-100">building agentic products</strong>, you'll find the patterns and pitfalls that separate prototypes from production. If you're <strong className="text-zinc-100">a tech-curious lead</strong> trying to understand whether agents are real, this book gives you the vocabulary and the honest tradeoffs.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">How to read this</h3> | |
| <p>The book has six parts. Part 1 builds foundations — what an agent is, how the loop works, when to use one. Part 2 dives into context engineering, the single largest source of confusion. Part 3 walks through every layer of Claude Code (skills, tools, hooks, subagents, caching). Part 4 tackles multi-agent systems. Part 5 is about production: verification, evaluation, debugging. Part 6 is reference: anti-patterns, a cheat sheet, and the path to mastery.</p> | |
| <p>You can read straight through (about three hours), bounce around by topic, or use the chapter sidebar like an index. Every chapter is standalone enough to dip into, but the conceptual scaffolding does build.</p> | |
| <Insight tone="violet" title="ONE THING TO HOLD ONTO"> | |
| Most "Claude is acting weird" problems are not model problems. They're <strong>missing engineering constraints</strong> dressed up as model failures. By the time you're done with this book, you'll have the vocabulary to tell which is which — and the toolkit to fix the engineering ones. | |
| </Insight> | |
| <p>Ready? Let's go.</p> | |
| </Ch>); | |
| const Ch02 = () => (<Ch> | |
| <p>Most people meet Claude Code as a "chat with code superpowers" and immediately get tangled up. Context turns into a junk drawer. The rules file balloons. Tools multiply while quality drops. The fix isn't a longer prompt. It's a better mental model.</p> | |
| <p>The whole system collapses neatly into <span className="text-amber-400 font-semibold">six layers</span>. Each layer answers a different question, and the trick is to put information in the right one. Stuff something in the wrong layer and you'll feel it bite you weeks later.</p> | |
| <SixLayerDiagram /> | |
| <p>Click around the explorer below to feel out what each layer is good for:</p> | |
| <InteractiveLayerExplorer /> | |
| <Insight> | |
| Over-index on any single layer and the system gets unstable. CLAUDE.md grows into a wiki and pollutes every session. Too many MCP tools eat your context window. Too many subagents drift. Skip verification and you can't tell where things broke. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The five diagnostic surfaces</h3> | |
| <p>When something goes wrong, you don't debug "the prompt." You ask which surface failed. There are five:</p> | |
| <Box1 tone="zinc"> | |
| <ul className="space-y-2 text-sm"> | |
| <li>🧠 <span className="text-amber-300 font-semibold">Context surface</span> — what's loaded vs. what isn't. If Claude doesn't know something it should know, this is usually why.</li> | |
| <li>🔧 <span className="text-orange-300 font-semibold">Action surface</span> — what tools are reachable. If Claude can't do something it should do, check here first.</li> | |
| <li>🛡️ <span className="text-rose-300 font-semibold">Control surface</span> — what's blocked, sandboxed, audited. If Claude did something it shouldn't have, you missed a guardrail.</li> | |
| <li>📦 <span className="text-cyan-300 font-semibold">Isolation surface</span> — what gets its own scratch space. If the main thread is polluted with junk, things should have been delegated.</li> | |
| <li>✅ <span className="text-emerald-300 font-semibold">Verification surface</span> — how you know it actually worked. If you can't answer "how do we know this is correct?", this surface is missing.</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Why this organization works</h3> | |
| <p>The six layers aren't arbitrary categories from the marketing team. They emerge from what actually changes <em>how often</em> in a real project. CLAUDE.md changes occasionally — when conventions shift. Tools change rarely — when capabilities expand. Skills change when methodologies improve. Hooks are nearly fixed once written. Subagents are configured at the project level. Verifiers ride along with the codebase.</p> | |
| <p>That's a stability gradient: stable things at the bottom, dynamic things at the top. When you make a change, you can ask "which layer should hold this?" and the answer is almost always determined by how often it changes and who needs to see it.</p> | |
| <Aside title="Wait, why six layers? Why not three? Why not ten?"> | |
| Six is empirically what shakes out. Anything fewer and you collapse meaningfully different things together (skills are not the same as tools, hooks are not the same as subagents). Anything more and you start splitting hairs. The Tw93 articles arrive at the same six independently. If you find yourself wanting to add a seventh, it's almost always a sub-category of one of these. | |
| </Aside> | |
| <Checkpoint | |
| q="Your team has a rule that Claude should never push to main. Where does this rule belong?" | |
| options={[ | |
| "In CLAUDE.md as a NEVER instruction", | |
| "As a hook that blocks the git push command", | |
| "Both — CLAUDE.md tells Claude to avoid it, and a hook hard-blocks if it tries", | |
| "In a skill file titled 'safe git practices'" | |
| ]} | |
| correct={2} | |
| explain="The 3-layer enforcement pattern (intent in CLAUDE.md, methodology optionally in a skill, hard enforcement in a hook) is dramatically more robust than any single layer. CLAUDE.md alone can be ignored. A hook alone leaves Claude confused about why. Together: belt and suspenders." | |
| /> | |
| <p>The rest of the book is a tour through these layers, what each is good for, and the traps that live in each.</p> | |
| </Ch>); | |
| const Ch03 = () => (<Ch> | |
| <p>Strip away the marketing and an "agent" is shockingly simple. Under the hood, it's a four-step loop running until the model decides it's done. That's it.</p> | |
| <AgentLoopDiagram /> | |
| <p>The actual implementation is genuinely about 20 lines of code. Get a model response — if it called a tool, run the tool, append the result, ask again. If it returned plain text, you're done.</p> | |
| <CodeBlock language="javascript" label="agent.js — the entire loop">{`async function agent(userMessage, tools) { | |
| const messages = [{ role: "user", content: userMessage }]; | |
| while (true) { | |
| const resp = await model.messages.create({ | |
| model: "claude-opus-4-7", | |
| tools, | |
| messages, | |
| }); | |
| // Append whatever the model said | |
| messages.push({ role: "assistant", content: resp.content }); | |
| // Did it call a tool? Run it and loop. | |
| if (resp.stop_reason === "tool_use") { | |
| const toolResults = await Promise.all( | |
| resp.content | |
| .filter(b => b.type === "tool_use") | |
| .map(async b => ({ | |
| type: "tool_result", | |
| tool_use_id: b.id, | |
| content: await runTool(b.name, b.input), | |
| })) | |
| ); | |
| messages.push({ role: "user", content: toolResults }); | |
| continue; | |
| } | |
| // Plain text? We're done. | |
| return resp.content.find(b => b.type === "text").text; | |
| } | |
| }`}</CodeBlock> | |
| <p>That's the engine. The whole thing. Walk through it in the simulator below — each click advances the loop one step:</p> | |
| <AgentLoopSim /> | |
| <Insight> | |
| That loop barely changes from a toy demo to a billion-dollar production system. New capabilities don't rewrite the loop — they layer on top. Subagents, skills, hooks, memory: all of it sits <em>around</em> this core, never inside it. Once you see this, the whole stack stops feeling magical. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The four stages, unpacked</h3> | |
| <p><strong className="text-amber-300">Perceive.</strong> The model reads the conversation so far — your messages, prior tool results, system prompt, attached files. This is "what do I know right now?"</p> | |
| <p><strong className="text-orange-300">Decide.</strong> Given what it knows, what should it do next? Either call a tool or finish and respond. The decision is made in the model's logits — not in your code.</p> | |
| <p><strong className="text-violet-300">Act.</strong> If a tool was chosen, your code executes it and pushes the result back into the conversation as a new "user" message (yes, tool results are conventionally framed as user input).</p> | |
| <p><strong className="text-emerald-300">Verify.</strong> The model reads the tool result and decides if it's enough. If not, back to Decide. If yes, return text and stop.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Why this matters for everything else</h3> | |
| <p>Once you internalize that the loop never changes, everything else snaps into place:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Tools are how the model <em>acts</em> on the world. Not "features" — capabilities you grant.</li> | |
| <li>Hooks fire <em>around</em> the loop, never inside it. They observe and gate, they don't reason.</li> | |
| <li>Subagents are recursive instances of the same loop, with isolated state.</li> | |
| <li>Skills are documents the model reads as part of Perceive — not extra logic.</li> | |
| <li>Memory is files the model can read or have injected.</li> | |
| </ul> | |
| <Box1 tone="cyan" title="The mental rule" icon={Repeat}> | |
| The model reasons. External systems track state and enforce boundaries. Once that division is clean, the loop never needs touching again — you grow the system by adding tools, files, and constraints, not by fiddling with the engine. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Failure modes of the loop itself</h3> | |
| <p>The loop is simple but not foolproof. Three things can break it:</p> | |
| <Box1 tone="rose" title="Loop pathologies" icon={AlertTriangle}> | |
| <div className="space-y-2 text-sm"> | |
| <div><strong>Infinite tool-call loops</strong> — the model keeps calling tools because results are unsatisfying or ambiguous. Cap iteration count. Always.</div> | |
| <div><strong>Premature termination</strong> — the model returns text before the task is actually done. Verification surface is missing.</div> | |
| <div><strong>Context exhaustion</strong> — each turn appends tool results, conversation grows, eventually you hit the window limit. Compaction or subagents are the answer.</div> | |
| </div> | |
| </Box1> | |
| <p>The next chapters address all three. But first, a question that trips everyone up: when should you even use an agent at all?</p> | |
| </Ch>); | |
| const Ch04 = () => (<Ch> | |
| <p>Half the things called "agents" today are actually <span className="text-cyan-400 font-semibold">workflows</span> in costume. The line is sharp:</p> | |
| <Box1 tone="cyan" title="The defining question" icon={GitBranch}> | |
| Who decides the next step — your <em>code</em>, or the <em>model</em>? | |
| <br /> | |
| Code → workflow. Model → agent. Neither is "better." The task picks the right one. | |
| </Box1> | |
| <WorkflowVsAgent /> | |
| <p>Workflows win when the path is fixed and you can verify each step in code. Agents win when the next step depends on what just happened. Most real systems are a mix of two or three patterns chained together. <strong>Full agentic autonomy is rarely what you actually want.</strong></p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">A worked example</h3> | |
| <p>Suppose you want to build something that summarizes a research paper. Two paths:</p> | |
| <Compare | |
| goodLabel="Workflow" | |
| badLabel="Agent" | |
| good={<> | |
| <ol className="space-y-1 text-xs list-decimal list-inside"> | |
| <li>Extract the abstract.</li> | |
| <li>Extract figures and captions.</li> | |
| <li>Summarize each section in parallel.</li> | |
| <li>Compose the parts into one document.</li> | |
| </ol> | |
| <div className="text-xs text-zinc-500 mt-2">Predictable, fast, debuggable. Each step is a function call. Easy to evaluate.</div> | |
| </>} | |
| bad={<> | |
| <ol className="space-y-1 text-xs list-decimal list-inside"> | |
| <li>Hand the model a paper and a tool to read pages.</li> | |
| <li>Let it decide which sections to read deeply, which to skim.</li> | |
| <li>Trust it to compose a summary when it feels ready.</li> | |
| </ol> | |
| <div className="text-xs text-zinc-500 mt-2">More flexible — but more expensive, slower, and harder to evaluate. Usually overkill for a known pipeline.</div> | |
| </>} | |
| /> | |
| <p>The workflow is the right answer here. Agents shine when you can't enumerate the steps in advance — like "fix this flaky test" or "investigate why staging is slow."</p> | |
| <h3 className="text-xl font-bold text-violet-400 mt-8 mb-3">The 5 control patterns to know</h3> | |
| <PatternIcons /> | |
| <p>These five patterns cover the vast majority of real-world agentic systems. Memorize them; they'll show up everywhere.</p> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-6 mb-2">1. Prompt Chaining</h4> | |
| <p>Output of step A becomes input of step B becomes input of step C. Use when the path is fixed and you can verify each step.</p> | |
| <CodeBlock language="javascript" label="prompt chaining">{`const outline = await llm("Write an outline for: " + topic); | |
| const draft = await llm("Expand this outline:\\n" + outline); | |
| const polished = await llm("Polish this draft:\\n" + draft);`}</CodeBlock> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-6 mb-2">2. Routing</h4> | |
| <p>Classify the input, then dispatch to a specialized handler. The classifier might be an LLM call, but the dispatch is in code.</p> | |
| <CodeBlock language="javascript" label="routing">{`const category = await classify(userMessage); // "billing" | "tech" | "sales" | |
| switch (category) { | |
| case "billing": return billingAgent(userMessage); | |
| case "tech": return techSupportAgent(userMessage); | |
| case "sales": return salesAgent(userMessage); | |
| }`}</CodeBlock> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-6 mb-2">3. Parallelization</h4> | |
| <p>Two flavors: <strong>sectioning</strong> (split a task into independent parts, run in parallel, combine), and <strong>voting</strong> (run the same task N times, take consensus).</p> | |
| <CodeBlock language="javascript" label="parallelization">{`// Sectioning | |
| const [a, b, c] = await Promise.all([ | |
| summarize(part1), | |
| summarize(part2), | |
| summarize(part3), | |
| ]); | |
| return combine(a, b, c); | |
| // Voting | |
| const verdicts = await Promise.all( | |
| Array(5).fill().map(() => classify(input)) | |
| ); | |
| return mostFrequent(verdicts);`}</CodeBlock> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-6 mb-2">4. Orchestrator-Workers</h4> | |
| <p>One agent breaks a problem into subtasks, dispatches each to a worker (often an isolated agent), and assembles the results. The decomposition itself is dynamic — the orchestrator decides how to split based on the input.</p> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-6 mb-2">5. Evaluator-Optimizer</h4> | |
| <p>Generate output, then have a separate evaluator critique it, then loop. The classic pattern for tasks where quality is hard to specify in code (translation, creative writing, complex code) but easier to recognize.</p> | |
| <CodeBlock language="javascript" label="evaluator-optimizer">{`let draft = await generate(prompt); | |
| for (let i = 0; i < 3; i++) { | |
| const review = await evaluate(draft); | |
| if (review.score >= 8) break; | |
| draft = await revise(draft, review.feedback); | |
| } | |
| return draft;`}</CodeBlock> | |
| <Box1 tone="emerald" title="Picking the right pattern"> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li>📐 Fixed flow + code-verifiable → <Tag tone="cyan">Prompt Chaining</Tag></li> | |
| <li>🔀 Inputs split into clear branches → <Tag tone="cyan">Routing</Tag></li> | |
| <li>🤔 Reasoning + clear pass/fail → <Tag tone="violet">Single Agent ReAct</Tag></li> | |
| <li>🧩 Decomposable subtasks, summary-only result → <Tag tone="violet">Orchestrator-Workers</Tag></li> | |
| <li>✍️ Quality hard to codify (translation, creative) → <Tag tone="emerald">Evaluator-Optimizer</Tag></li> | |
| <li>⚖️ High-stakes, multi-perspective → <Tag tone="emerald">Voting (Parallelization)</Tag></li> | |
| </ul> | |
| </Box1> | |
| <Insight> | |
| Run the single-agent ceiling first. Coordination overhead in multi-agent setups usually dwarfs the parallelism you gain. Multi-agent should be the answer when isolation is required, not when one agent is "slow." | |
| </Insight> | |
| <Checkpoint | |
| q="You're building a code review bot that runs against PRs. The work is: lint check, type check, test run, security scan, then summary. Each step has a clear pass/fail. What's the right pattern?" | |
| options={[ | |
| "Single agent with all five tools, let it figure out the order", | |
| "Prompt chaining or workflow with parallelization for the independent checks", | |
| "Orchestrator-workers — one agent per check", | |
| "Evaluator-optimizer loop on the summary" | |
| ]} | |
| correct={1} | |
| explain="The order is fixed and each step is code-verifiable. There's no reason to spend tokens letting an LLM rediscover that order on every run. Workflow with parallelization (run the four checks concurrently, then summarize) is faster, cheaper, and more reliable. Reserve agents for the steps where the path actually depends on what the model finds." | |
| /> | |
| </Ch>); | |
| const Ch05 = () => (<Ch> | |
| <p>Here's the spicy take from the agent article: <span className="text-rose-400 font-semibold">the model isn't usually the bottleneck</span>. The harness is.</p> | |
| <p>A "harness" sounds boring. It's not. It's the four-piece scaffolding around the agent that determines whether you can ship anything: <Tag tone="amber">acceptance baselines</Tag> <Tag tone="amber">execution boundaries</Tag> <Tag tone="amber">feedback signals</Tag> <Tag tone="amber">fallback mechanisms</Tag>.</p> | |
| <HarnessQuadrant /> | |
| <p>The 2×2 above is the whole game. Tasks where the goal is clear <em>and</em> verification is automated belong in the green box. Agents thrive there. Outside it, you're either capped by humans (top-left) or running confidently in the wrong direction (bottom-right).</p> | |
| <Insight> | |
| A harness is the machinery that pushes a task from anywhere on this grid <em>into the green quadrant</em>. It replaces "humans must look at this" with "the system can decide pass/fail." | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The four pieces, in detail</h3> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-4 mb-2">1. Acceptance baselines</h4> | |
| <p>What does "done correctly" look like? Don't start coding the agent until you can answer this. Concrete examples:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>"All unit tests pass" (cheapest, most reliable)</li> | |
| <li>"Lint clean and types check" (cheap)</li> | |
| <li>"Integration test suite passes" (medium)</li> | |
| <li>"Screenshot diff under threshold" (medium)</li> | |
| <li>"Reviewer approves PR" (expensive, last resort)</li> | |
| </ul> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-4 mb-2">2. Execution boundaries</h4> | |
| <p>What can the agent touch, and what's off-limits? Permissions, sandbox, allowedTools list, hooks. Without these, runaway agents can corrupt state or do real damage.</p> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-4 mb-2">3. Feedback signals</h4> | |
| <p>How does the agent know if it's on track <em>during</em> the loop? Test runner output, type check results, lint warnings, error messages. The faster and more specific the signal, the faster the agent can self-correct.</p> | |
| <h4 className="text-lg font-bold text-zinc-100 mt-4 mb-2">4. Fallback mechanisms</h4> | |
| <p>What happens when the agent gets stuck? Time out and ask a human? Roll back to a known-good state? Defer to a different model? Without a fallback, "stuck" turns into "burning tokens forever."</p> | |
| <Box1 tone="emerald" title="The OpenAI Codex example" icon={Zap}> | |
| Three engineers shipped a million lines of code in five months — roughly 10× normal velocity. The model wasn't the secret. The harness was: every project keeps an <InlineCode>AGENTS.md</InlineCode> index of about 100 lines, constraints are encoded into linters and CI (not docs), and the agent owns the entire pipeline including PRs and merges. Code review moved from humans to machines, but it didn't disappear. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The diagnostic principle</h3> | |
| <p>When something breaks, walk the cheap-to-expensive ladder. Check tool descriptions first. Check whether task state is externalized. Check whether the eval itself is broken. <em>Then</em>, maybe, touch the prompt.</p> | |
| <CodeBlock language="bash" label="diagnosis order">{`# 1. CHEAP: are tool descriptions accurate and unambiguous? | |
| $ grep -A 3 'description' tools.json | |
| # 2. CHEAP: is task state externalized? | |
| $ ls .tasks/ # if empty, agent is keeping state in its head | |
| # 3. MEDIUM: is the eval system itself broken? | |
| $ ./run-eval.sh --baseline # does it match expected baseline? | |
| # 4. MEDIUM: should this be a workflow, not an agent? | |
| # (deterministic parts shouldn't be inside the loop) | |
| # 5. EXPENSIVE: rewrite the prompt / swap models`}</CodeBlock> | |
| <Aside title="Why is 'rewrite the prompt' last on the list?"> | |
| Because it's the highest-effort, lowest-feedback intervention. Prompt changes are non-local: tweaking one phrase can affect dozens of behaviors in unpredictable ways. Tool descriptions, task externalization, and eval fixes are surgical — you change one thing and you can measure the effect. Get good at fixing the cheap stuff first; the expensive stuff is rarely the actual problem. | |
| </Aside> | |
| <p>The harness is also why "just use a smarter model" stops mattering as systems mature. Better models still need clear acceptance criteria, still need execution boundaries, still need feedback. A weaker model with a great harness consistently beats a stronger model with no harness.</p> | |
| </Ch>); | |
| // PART 2: CONTEXT ENGINEERING | |
| const Ch06 = () => (<Ch> | |
| <p>Most context problems aren't about <em>capacity</em>. They're about <span className="text-rose-400 font-semibold">noise</span>. A 200K window sounds enormous until you watch how much of it is gone before your conversation even starts.</p> | |
| <ContextBarDiagram /> | |
| <Insight> | |
| A typical MCP server like GitHub exposes 20–30 tool definitions at ~200 tokens each — that's 4-6K. Connect five servers and you're spending around 25K tokens (12.5% of your window) on fixed overhead before anyone says a word. This is invisible until you check. | |
| </Insight> | |
| <p>Try toggling the connections below to see for yourself:</p> | |
| <ContextSim /> | |
| <p>The fix is layering. Don't ask "how big is my context?" Ask "what belongs in each layer?"</p> | |
| <Box1 tone="amber" title="The 5 context layers" icon={Layers}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="amber">Permanent</Tag> Identity, conventions, prohibitions. Short, hard, actionable. Lives in CLAUDE.md.</div> | |
| <div><Tag tone="violet">On-Demand</Tag> Skills. Descriptors stay resident; full bodies load when triggered.</div> | |
| <div><Tag tone="cyan">Runtime injection</Tag> Time, channel ID, current branch. Appended each turn.</div> | |
| <div><Tag tone="emerald">Memory</Tag> Cross-session experience in MEMORY.md. Read on demand, not always loaded.</div> | |
| <div><Tag tone="rose">System</Tag> Deterministic logic via hooks. <strong>Never</strong> in context.</div> | |
| </div> | |
| </Box1> | |
| <Box1 tone="rose" title="The hard rule" icon={AlertTriangle}> | |
| Anything that can be expressed by a hook, a code rule, or a tool constraint <strong>does not belong in the prompt</strong>. Don't make the model re-read it every turn. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The hidden killer: tool output noise</h3> | |
| <p>There's a second invisible budget killer beyond fixed overhead: tool output. A single <InlineCode>cargo test</InlineCode> can dump thousands of lines into the window. The model only needs "did it pass, and if not, where?" — everything else is dead weight crowding out conversation history.</p> | |
| <p>Tools that filter command output before it reaches the model (like RTK and similar wrappers) work because they stop the noise at its source instead of begging the model to ignore it.</p> | |
| <CodeBlock language="bash" label="taming tool output">{`# Bad: dumps 2000 lines into context | |
| $ cargo test | |
| # Better: only what's actionable | |
| $ cargo test 2>&1 | tail -50 | |
| # Best: filtered to errors only | |
| $ cargo test 2>&1 | grep -E '^(test|error|FAILED)' | head -30`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The /context command</h3> | |
| <p>Stop guessing about what's in your context. Use the <InlineCode>/context</InlineCode> command to see exactly what's loaded:</p> | |
| <CodeBlock language="bash" label="terminal">{`> /context | |
| System prompt: 4,200 tokens | |
| Tool definitions: 18,400 tokens (5 MCP servers, 11 builtin) | |
| CLAUDE.md: 1,200 tokens | |
| Skill descriptors: 340 tokens | |
| LSP context: 2,100 tokens | |
| Conversation: 12,800 tokens | |
| ───────────────────────────────── | |
| Total used: 39,040 tokens (19.5% of 200K window) | |
| Available: 160,960 tokens`}</CodeBlock> | |
| <p>If your fixed overhead is over 30K, you have too much loaded by default. Tighten the MCP server list, slim CLAUDE.md, audit skill descriptors.</p> | |
| <Aside title="A common shock: connecting 'just one' big MCP server"> | |
| Some MCP servers are heavyweight. The official GitHub MCP exposes ~50 tools at ~200 tokens each — that's 10K just for that one connection. The Atlassian one is similar. If you're not actively using all those tools, the bare cost of having them connected is paid every single turn forever. Audit periodically. | |
| </Aside> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Layering by stability</h3> | |
| <p>The simple heuristic for "where does this go?" is: <strong>how often does it change?</strong></p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Never changes (project identity, NEVER list) → CLAUDE.md</li> | |
| <li>Changes per task type (deploy, review) → Skills</li> | |
| <li>Changes per session (current branch, time) → Runtime injection</li> | |
| <li>Changes per project history (cross-session lessons) → MEMORY.md</li> | |
| <li>Pure logic (file format, lint rules) → Hooks (zero context cost)</li> | |
| </ul> | |
| <p>The art of context engineering is moving things <em>out</em> of always-on layers and into more selective ones — without losing the constraint they were enforcing.</p> | |
| </Ch>); | |
| const Ch07 = () => (<Ch> | |
| <p>The biggest thing to internalize about CLAUDE.md: <span className="text-amber-400 font-semibold">it's a contract, not a wiki</span>. Not team docs. Not a knowledge base. Only the things that must hold across <em>every</em> session belong here.</p> | |
| <p>This is the file Claude Code reads at the start of every session in your project. It gets loaded into context permanently. So every byte you put here costs you for the rest of the session — multiplied by every session anyone on your team ever runs.</p> | |
| <Box1 tone="amber" title="The startup heuristic" icon={BookOpen}> | |
| Start with nothing. Use Claude Code first. Add an entry only when you notice yourself repeating the same instruction. Type <Pill>#</Pill> in the chat to append the current conversation, or just say "add this to the project's CLAUDE.md." | |
| </Box1> | |
| <Compare | |
| good={<ul className="space-y-1 text-xs"><li>✓ Build / test / lint commands</li><li>✓ Key directory structure</li><li>✓ Code style constraints</li><li>✓ Non-obvious env dependencies</li><li>✓ NEVER list (high-risk ops)</li><li>✓ Compact Instructions</li></ul>} | |
| bad={<ul className="space-y-1 text-xs"><li>✗ Long background intros</li><li>✗ Full API documentation</li><li>✗ "Write high-quality code"</li><li>✗ Anything obvious from the repo</li><li>✗ Background materials (→ skills)</li><li>✗ Anything enforceable in code</li></ul>} | |
| /> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">A high-quality template</h3> | |
| <p>Here's what a good CLAUDE.md actually looks like — short, specific, action-oriented:</p> | |
| <CodeBlock language="markdown" label="CLAUDE.md">{`# Project Contract | |
| ## Build And Test | |
| - Install: pnpm install | |
| - Test: pnpm test | |
| - Lint: pnpm lint | |
| - Type check: pnpm typecheck | |
| ## Architecture Boundaries | |
| - HTTP handlers in src/http/handlers/ | |
| - Domain logic in src/domain/ | |
| - No persistence in handlers (use repository pattern) | |
| - All API responses go through src/http/serialize.ts | |
| ## Conventions | |
| - ESM only, no CommonJS | |
| - Avoid default exports | |
| - Tests live next to source: foo.ts → foo.test.ts | |
| ## NEVER | |
| - Modify .env, .env.* or any lockfiles without approval | |
| - Commit without running pnpm test && pnpm lint | |
| - Push directly to main | |
| - Add new dependencies without team consent | |
| ## Verification | |
| - Backend changes: pnpm test && pnpm lint | |
| - API changes: also update contracts/ and run pnpm contracts:validate | |
| - DB changes: must include migration + rollback | |
| ## Compact Instructions | |
| When compressing context, preserve in priority order: | |
| 1. Architecture decisions (NEVER summarize) | |
| 2. Modified files and key changes | |
| 3. Verification status (pass/fail) | |
| 4. Open TODOs and rollback notes | |
| 5. Tool outputs may be deleted (keep pass/fail only)`}</CodeBlock> | |
| <p>That's about 30 lines. It compiles to maybe 800 tokens. It would be hard to write a less-useful CLAUDE.md if you tried, but it would be very easy to write a more bloated one.</p> | |
| <Insight> | |
| Let Claude maintain its own CLAUDE.md. After correcting a mistake, say: <em>"Update your CLAUDE.md so you don't make that mistake again."</em> Claude is genuinely good at writing these rules for itself, and repeated mistakes drop off over time. Just review the file periodically — entries go stale, and constraints that helped once may stop earning their context cost. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Three nested levels</h3> | |
| <p>CLAUDE.md actually exists at three levels, each loaded only when relevant:</p> | |
| <Box1 tone="zinc"> | |
| <ul className="space-y-2 text-sm"> | |
| <li><InlineCode>~/.claude/CLAUDE.md</InlineCode> — your <strong>personal</strong> conventions across all projects (tab width, preferred phrasing, "always explain before editing")</li> | |
| <li><InlineCode>./CLAUDE.md</InlineCode> — the <strong>project</strong> contract, checked in to git</li> | |
| <li><InlineCode>./subdir/CLAUDE.md</InlineCode> — <strong>directory-specific</strong> rules that load only when you're working in that subtree</li> | |
| </ul> | |
| </Box1> | |
| <p>Use the directory-level one for monorepos: each package can have its own conventions without bloating the root file.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The NEVER list is special</h3> | |
| <p>Most CLAUDE.md content is "do this" advice. The NEVER list is the opposite: explicit prohibitions on high-risk actions. These get extra weight because they're framed as red lines.</p> | |
| <CodeBlock language="markdown" label="NEVER list pattern">{`## NEVER | |
| - NEVER run \`rm -rf\` on anything outside ./tmp | |
| - NEVER commit files with .env or secret in their name | |
| - NEVER push to main, master, or any release/* branch | |
| - NEVER modify generated files in src/__generated__/ | |
| - NEVER add binary files larger than 1MB to git`}</CodeBlock> | |
| <p>For truly critical prohibitions, layer hooks underneath. CLAUDE.md tells Claude not to do it. The hook makes sure Claude can't even if it tries.</p> | |
| <Aside title="Should I check CLAUDE.md into git?"> | |
| Yes for the project-level file. It's a team artifact — anyone running Claude Code in your repo benefits from the same conventions. The personal one in <InlineCode>~/.claude/</InlineCode> stays out of git for obvious reasons. For monorepos, directory-level CLAUDE.md files belong with the package. | |
| </Aside> | |
| <Checkpoint | |
| q="Your project requires that all SQL queries go through the repository pattern, not raw db calls in handlers. Where does this rule belong?" | |
| options={[ | |
| "CLAUDE.md, in the architecture section", | |
| "A skill called 'database-conventions'", | |
| "A lint rule that fails CI on raw db imports in handlers/", | |
| "All three together, in order of importance" | |
| ]} | |
| correct={2} | |
| explain="The lint rule alone is best. It's a deterministic constraint that can be checked by code, so it doesn't need any context budget. CLAUDE.md works as a backup, but if the lint rule exists, the CLAUDE.md entry is redundant noise. Always prefer code enforcement over documentation enforcement when possible — 'encode constraints, don't document them.'" | |
| /> | |
| </Ch>); | |
| const Ch08 = () => (<Ch> | |
| <p>When context fills up, the system has to throw stuff out. The default algorithm has a nasty habit: it deletes early tool outputs and file reads first. Sounds reasonable — until you realize that's exactly where the <span className="text-rose-400 font-semibold">architectural decisions</span> were captured. Two hours later you change something and have no idea why the original choice was made.</p> | |
| <Box1 tone="rose" title="The compression trap" icon={AlertTriangle}> | |
| Compaction optimizes for "re-readability" — keeping things the model could find again. Decisions and constraint reasoning <em>look</em> like they could be reconstructed, so they get summarized away. They cannot be reconstructed. That's where bugs are born. | |
| </Box1> | |
| <p>The fix is to override the algorithm with explicit retention priorities. Drop this in CLAUDE.md and you take back control:</p> | |
| <CodeBlock language="markdown" label="CLAUDE.md compact instructions">{`## Compact Instructions | |
| When compressing, preserve in priority order: | |
| 1. Architectural decisions (NEVER summarize) | |
| 2. Modified files and key changes | |
| 3. Current verification status (pass/fail) | |
| 4. Open TODOs and rollback notes | |
| 5. Tool outputs (delete, keep pass/fail only)`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Five ways to handle a session</h3> | |
| <p>Compaction is reactive. The Claude Code team identifies <span className="text-cyan-400 font-semibold">five proactive ways</span> to manage a session — knowing which to pick is half the skill:</p> | |
| <Box1 tone="cyan" title="Session management toolkit"> | |
| <div className="space-y-2 text-sm"> | |
| <div>💬 <strong>continue</strong> — most natural, most abused. Default action.</div> | |
| <div>⏪ <strong>rewind</strong> — double-tap ESC. Often beats correction. If Claude went down a wrong path, undoing the bad turns beats appending "that didn't work, try X."</div> | |
| <div>🧹 <strong>clear</strong> — start fresh with a brief you wrote. Most effort, most control.</div> | |
| <div>📦 <strong>compact</strong> — let the model summarize. Convenient. May drop things you cared about.</div> | |
| <div>🔍 <strong>subagents</strong> — delegate the next chunk to its own clean context. Only the result returns.</div> | |
| </div> | |
| </Box1> | |
| <p><strong>Rewind is underused.</strong> When Claude misunderstands you and goes off in a wrong direction, the instinct is to type "no, that's wrong, do X instead." That works, but it leaves the wrong path in context. Rewind (double-tap ESC) actually removes those turns. The model starts fresh from your earlier prompt, so it doesn't drag the misconception forward.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Plan Mode</h3> | |
| <p>Plan Mode (double-tap Shift+Tab) is the other big lever — it separates exploration from execution. Claude reads, asks clarifying questions, and proposes a plan; nothing on disk changes until you confirm. For complex refactors and migrations this beats "edit immediately and hope" every single time.</p> | |
| <CodeBlock language="bash" label="terminal">{`> [Shift+Tab x2 enters Plan Mode] | |
| > refactor the auth module to use the new session library | |
| [Plan Mode active. No edits will be made.] | |
| I'll analyze the auth module first. Let me read the current implementation | |
| and identify integration points. | |
| Read(src/auth/session.ts) | |
| Read(src/auth/middleware.ts) | |
| Grep(pattern: "currentSession\\(\\)") | |
| Here's my proposed plan: | |
| 1. Replace OldSession with NewSession in src/auth/session.ts | |
| 2. Update middleware to use new lifecycle methods | |
| 3. Migrate 14 callers in src/api/handlers/ | |
| 4. Update tests | |
| Files I'd modify: 17. Tests I'd update: 9. Estimated risk: medium (touches | |
| the auth boundary). Rollback: revert in single PR. | |
| Approve to execute, or reply with changes you'd like.`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The handoff pattern</h3> | |
| <p>One pattern is gold for long work: write a <Pill tone="good">HANDOFF.md</Pill> before a session ends. Have Claude record what it tried, what worked, what failed, and what's next. The next session starts from that file instead of relying on whatever survived compression. It's lossy compression you control, written in your voice.</p> | |
| <CodeBlock language="markdown" label="HANDOFF.md">{`# Session Handoff — 2026-04-23 | |
| ## What I was doing | |
| Migrating the user search from Postgres FTS to OpenSearch. | |
| ## What's done | |
| - [x] OpenSearch client wrapper in src/search/opensearch.ts | |
| - [x] Index schema in infra/opensearch/users-mapping.json | |
| - [x] Backfill script (idle-tested, not run on prod) | |
| ## What's not done | |
| - [ ] Cutover flag (currently hardcoded to old path) | |
| - [ ] Removing old FTS code | |
| - [ ] Production backfill | |
| ## Decisions to remember | |
| - Chose nested fields over flat for tags array — querying needs term-level | |
| scoring per tag, not document-level | |
| - Backfill is batched 500 at a time; observed ~3K/sec on staging | |
| ## Watch out | |
| - src/search/legacy.ts still has live imports — must remove only AFTER cutover | |
| - Mapping has a known bug with very long emails (>250 chars), not yet fixed | |
| ## Where I left off | |
| About to add the feature flag check in src/search/index.ts. Next session should | |
| start there.`}</CodeBlock> | |
| <Insight> | |
| The bigger the task, the more important HANDOFF.md becomes. For multi-session work, treat the handoff file as a first-class deliverable. It's far more reliable than trusting Claude's auto-compression to keep the right things. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Identifiers must survive compression</h3> | |
| <p>One specific thing the compression algorithm gets wrong with high frequency: paraphrasing identifiers. Commit hashes, UUIDs, container IDs, file paths, ports — these need to be preserved <em>verbatim</em>. If the model summarizes "we deployed commit a1b2c3d4..." as "we deployed the recent commit," every subsequent tool call that needs that hash is broken.</p> | |
| <Box1 tone="rose" title="Things compaction must never alter" icon={Lock}> | |
| <ul className="space-y-1 text-sm list-disc list-inside"> | |
| <li>Commit hashes, branch names, tags</li> | |
| <li>UUIDs, request IDs, trace IDs</li> | |
| <li>File paths and line numbers</li> | |
| <li>Hostnames, ports, IPs, URLs</li> | |
| <li>Email addresses, usernames</li> | |
| <li>Version numbers, timestamps</li> | |
| </ul> | |
| Add a clause to your Compact Instructions: <em>"Never paraphrase or alter identifiers (hashes, UUIDs, paths, ports, URLs, version numbers)."</em> | |
| </Box1> | |
| </Ch>); | |
| const Ch09 = () => (<Ch> | |
| <p>Plan Mode deserves its own chapter because it changes how you collaborate with Claude in ways that aren't obvious until you live with it for a while. The basic mechanic is simple: double-tap Shift+Tab and Claude switches to read-only mode. But the cultural change is bigger.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">Why Plan Mode exists</h3> | |
| <p>The default agent loop has a natural bias toward action. The model reads your request, picks a tool, runs it, sees the result. By the time you notice it's misunderstood you, three files are already edited.</p> | |
| <p>Plan Mode breaks this bias by making "no, you can't write to disk" a hard constraint. The model can still read, search, and reason — but it can't act. So instead of half-doing the wrong thing, it talks through what it would do and waits for you to confirm.</p> | |
| <Box1 tone="violet" title="When to reach for Plan Mode" icon={Compass}> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li>🔧 Any refactor touching more than ~5 files</li> | |
| <li>🗄️ Schema or data migration</li> | |
| <li>🚢 Deploy / release / cutover steps</li> | |
| <li>🐛 Debugging investigations where you don't know the cause yet</li> | |
| <li>🆕 New feature work where the design isn't settled</li> | |
| <li>🚨 Any task involving production or shared infra</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The cache-friendly design</h3> | |
| <p>You might think Plan Mode would swap out write-tools for read-only versions. It doesn't — and that's deliberate. Tool changes mid-session are catastrophic for prompt caching (we cover this in detail in Chapter 13). Instead, Plan Mode is implemented as a system-level mode flag the model is told about. The tool list stays identical; the model just declines to use the write ones.</p> | |
| <p>This is a tiny detail but a great example of design under real constraints. The "obvious" implementation would have looked cleaner but cost users 10× more on every Plan Mode session.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">A worked example</h3> | |
| <CodeBlock language="bash" label="without plan mode">{`> Update all our enum types to use the new pattern from CONVENTIONS.md | |
| [Claude reads CONVENTIONS.md] | |
| [Claude reads src/types/index.ts] | |
| [Edits 4 files] | |
| [Three of the four are wrong because Claude misread one of the conventions] | |
| You: "Wait, the second pattern is for nested enums only..." | |
| You: spend 20 minutes correcting and partially reverting`}</CodeBlock> | |
| <CodeBlock language="bash" label="with plan mode">{`> [Shift+Tab x2] | |
| > Update all our enum types to use the new pattern from CONVENTIONS.md | |
| [Claude reads CONVENTIONS.md] | |
| [Claude reads src/types/index.ts] | |
| [Claude reads src/types/auth.ts] | |
| I see two patterns in the conventions doc: | |
| - Pattern A (line 23): for top-level enums | |
| - Pattern B (line 41): for nested enums inside discriminated unions | |
| I count 14 top-level enums and 6 nested enums. I'd apply Pattern A to the | |
| 14 and Pattern B to the 6. Confirm? | |
| You: "Confirmed, but skip src/legacy/ — those are deprecated." | |
| [Claude proceeds with the corrected scope]`}</CodeBlock> | |
| <p>Same outcome, half the time, no broken intermediate state. Plan Mode is the difference between "let me try" and "let me think first."</p> | |
| <Aside title="When NOT to use Plan Mode"> | |
| For trivial edits ("add a console.log here"), Plan Mode is overhead. The mental model is roughly: if you can describe the task in one sentence and the answer is obvious, skip it. If the task involves any decisions or branches, use it. | |
| </Aside> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">/clear, /compact, and how to mix them</h3> | |
| <p>The two slash commands you'll use most often:</p> | |
| <CodeBlock language="bash" label="terminal">{`> /clear | |
| # Wipes conversation history. Keeps CLAUDE.md and tools. | |
| # Use when switching to a fundamentally different task. | |
| > /compact | |
| # Asks Claude to summarize the conversation, replacing detail with summary. | |
| # Use when context is getting full but task continues. | |
| > /context | |
| # Shows what's actually loaded right now. | |
| # Use whenever you suspect things are bloated.`}</CodeBlock> | |
| <Box1 tone="emerald" title="Session strategy by task type" icon={PlayCircle}> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li><strong>Quick edit:</strong> just continue. Don't overthink it.</li> | |
| <li><strong>Multi-step task:</strong> Plan Mode → execute → /compact halfway through if needed</li> | |
| <li><strong>Switching to unrelated task:</strong> /clear, restart fresh</li> | |
| <li><strong>Heavy investigation:</strong> use a subagent so the noise stays out of main</li> | |
| <li><strong>Multi-day work:</strong> end each session with HANDOFF.md, /clear next time</li> | |
| </ul> | |
| </Box1> | |
| </Ch>); | |
| const Ch10 = () => (<Ch> | |
| <p>Out of the box, agents have no memory across sessions. End the conversation, context vanishes, the next startup begins blank. Memory has to be designed in deliberately — and the trick is realizing it's not <em>one</em> thing. Four different problems, four different solutions:</p> | |
| <MemoryDiagram /> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Working memory</h3> | |
| <p>The conversation context itself. Lives in messages while the session runs, gone when it ends. This is the only memory most agents have without explicit design. Everything else has to be added.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Procedural memory</h3> | |
| <p>"How to do things." Skills are procedural memory: you write down a deployment procedure once, and Claude can pull it up the next time someone asks about deploys. The skill library survives across sessions and across users on the team.</p> | |
| <p>The defining property of procedural memory: it's relatively stable. You don't change a deployment procedure mid-conversation. You write it once, refine it occasionally.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Episodic memory</h3> | |
| <p>"What happened." Session logs, JSONL transcripts, the <InlineCode>.claude/</InlineCode> history directory. The raw record of past interactions. Most production systems don't load these directly — they're too long. Instead, episodic memory is a queryable archive that can be searched on demand.</p> | |
| <CodeBlock language="bash" label="searching episodic memory">{`# Find every session where we touched the auth module | |
| $ claude history search "auth" --since "30 days" | |
| # Show the full transcript of one session | |
| $ claude history show 2026-04-12-deploy-fix | |
| # Replay a session as starting context | |
| $ claude --from 2026-04-12-deploy-fix`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Semantic memory</h3> | |
| <p>Stable facts. "We use ESM only." "Our prod DB is in us-east-1." "Eric is the on-call this week." The kind of information that's true now and likely to remain true.</p> | |
| <p>Semantic memory often lives in <InlineCode>MEMORY.md</InlineCode> or similar — a markdown file that gets injected at session start. It differs from CLAUDE.md in scope: CLAUDE.md is project conventions; MEMORY.md is broader factual context (people, infra, business decisions, recent events).</p> | |
| <Box1 tone="cyan" title="The hybrid retrieval pattern" icon={Database}> | |
| Many production systems use 70% vector similarity + 30% keyword weight when search becomes necessary. But until you cross several thousand records and genuinely need semantic matching, structured Markdown plus keyword search wins on debuggability and cost. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">No vector DB? Really?</h3> | |
| <p>Yes, really, for most cases. ChatGPT's memory system, by Anthropic's own analysis, is around 33 user facts plus 15 conversation summaries plus a sliding window over the current chat. No RAG. No embeddings. Markdown files plus keyword search get you very far before semantic similarity becomes worth the operational cost.</p> | |
| <Quote attribution="A common pattern in production"> | |
| We started with vector search because that's what everyone said to do. Two months in we replaced it with grep over markdown and the results got <em>better</em>. The retrieval was already 90% determined by keyword overlap; embeddings were just adding ranking noise on top. | |
| </Quote> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Reversible consolidation</h3> | |
| <Insight> | |
| Memory consolidation must be <strong>reversible</strong>. The system moves a pointer; it never deletes raw messages. If summarization fails, raw messages write to an archive — nothing is lost. Triggering at ~50% token usage gives you headroom; waiting until you're full is panic-driven compression. | |
| </Insight> | |
| <p>The classic mistake is treating compression as a one-way street. You compress, the original is gone, the model loses access to detail it later turns out to need. Always preserve the raw message in an archive. Compression should be a routing decision, not a delete.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The filesystem is your friend</h3> | |
| <p>For long-running tasks, the filesystem itself makes a great memory layer. When tool calls return huge JSON, write the result to a file and let the agent <Pill>grep</Pill> it on demand. Cursor's "Dynamic Context Discovery" experiments cut MCP-task token usage by 46.9% by syncing tool descriptions to folders and querying definitions only when needed.</p> | |
| <CodeBlock language="bash" label="filesystem as memory">{`# Bad: dump 10K lines of JSON into context | |
| $ kubectl get pods -A -o json | |
| # Better: dump to disk, let the agent search | |
| $ kubectl get pods -A -o json > /tmp/pods.json | |
| $ jq '.items[] | select(.status.phase != "Running") | .metadata.name' /tmp/pods.json`}</CodeBlock> | |
| <p>The pattern generalizes: anytime a tool produces output that's "big but might be needed in part," write to file, query on demand. The model can use grep, jq, sed, awk — those are also tools.</p> | |
| </Ch>); | |
| // PART 3: THE LAYERS | |
| const Ch11 = () => (<Ch> | |
| <p>Skills are easy to misunderstand. They're not templates. They're not saved prompts. They're <span className="text-amber-400 font-semibold">on-demand workflow packages</span>: short descriptors that stay in context, with the full body loading only when the model decides it needs them.</p> | |
| <SkillsLoadDiagram /> | |
| <p>This is called <span className="text-cyan-400 font-semibold">progressive disclosure</span>. The system prompt is an index. The model scans the index every turn, decides which skill matches, and pulls the body in only when it does. Smart and cheap.</p> | |
| <Insight> | |
| The single thing that determines whether your skill works: <em>the description</em>. It tells the model <strong>when to use the skill</strong>, not what's inside. "Use when deploying to production or rolling back" is a routing condition. "Helps with deployment" triggers on every backend question and pollutes everything. | |
| </Insight> | |
| <Compare | |
| good={<><div className="font-mono text-xs mb-1">description: Use when deploying to production or rolling back.</div><div className="text-xs text-zinc-400">~9 tokens. Specific. Routing condition.</div></>} | |
| bad={<><div className="font-mono text-xs mb-1">description: This skill handles the complete deployment process to production. It covers environment checks, rollback procedures, and post-deploy verification...</div><div className="text-xs text-zinc-400">~45 tokens. Bloats every request.</div></>} | |
| /> | |
| <Box1 tone="emerald" title="The data backs this up" icon={Sparkles}> | |
| Adding counter-examples ("don't use when...") to skill descriptions raises routing accuracy from 73% to 85% and cuts response time by 18%. Without counter-examples, accuracy drops as low as 53%. That's the difference between a working skill and a confused one. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Anatomy of a SKILL.md</h3> | |
| <CodeBlock language="markdown" label=".claude/skills/deploy/SKILL.md">{`--- | |
| description: | | |
| Use when deploying to production, staging, or rolling back a release. | |
| Don't use for local dev runs or feature-branch builds. | |
| disable-model-invocation: false | |
| --- | |
| # Deploy Procedure | |
| ## Pre-flight checks | |
| 1. Confirm we're on main: \`git rev-parse --abbrev-ref HEAD\` returns "main" | |
| 2. Confirm clean working tree: \`git status --porcelain\` returns empty | |
| 3. Confirm CI is green on the latest commit | |
| 4. Confirm no incident is open in the runbook | |
| ## Environment selection | |
| Ask the user which environment unless explicitly specified: | |
| - \`prod\` → production cluster (us-east-1, us-west-2) | |
| - \`stg\` → staging cluster | |
| - \`canary\` → canary slice (5% of prod traffic) | |
| ## Deploy steps | |
| 1. Tag: \`git tag -a v$(date +%Y.%m.%d-%H%M)\` | |
| 2. Push tag: \`git push --tags\` | |
| 3. Trigger pipeline: \`gh workflow run deploy.yml -f env=$ENV\` | |
| 4. Watch: \`gh run watch\` | |
| 5. Verify: hit /healthz and /version | |
| ## Rollback | |
| If anything looks wrong in step 5: | |
| 1. \`gh workflow run rollback.yml -f env=$ENV\` | |
| 2. Confirm /version shows previous tag | |
| 3. Notify in #incidents`}</CodeBlock> | |
| <p>That's a complete skill. The description routes it. The body is the procedure. The frontmatter controls invocation. When Claude detects "deploy" in your message, it reads the body in. When you're talking about anything else, the body stays asleep.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Three skill archetypes</h3> | |
| <div className="grid md:grid-cols-3 gap-3 my-3"> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-cyan-400 font-bold text-sm">Checklist</div> | |
| <div className="text-xs text-zinc-400 mt-1">Quality gates. Pre-release: build passes, lint clean, version bumped, changelog updated.</div> | |
| </div> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-violet-400 font-bold text-sm">Workflow</div> | |
| <div className="text-xs text-zinc-400 mt-1">Standardized ops with rollback. Config migration: backup → dry run → apply → verify.</div> | |
| </div> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-emerald-400 font-bold text-sm">Domain Expert</div> | |
| <div className="text-xs text-zinc-400 mt-1">Decision frameworks. Diagnose runtime issues by collecting evidence on a fixed path.</div> | |
| </div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Splitting big skills</h3> | |
| <p>If a SKILL.md grows past about 100 lines, split it. The frontmatter description still routes traffic, but the body can reference companion files:</p> | |
| <CodeBlock language="markdown" label=".claude/skills/incident/SKILL.md">{`--- | |
| description: Use when an alert fires or the user mentions an incident. | |
| --- | |
| # Incident Response | |
| For step-by-step playbook by alert type: | |
| - pager-duty alerts → see ./playbook-pd.md | |
| - AWS health alerts → see ./playbook-aws.md | |
| - error rate spike → see ./playbook-errors.md | |
| For postmortem template, see ./postmortem-template.md`}</CodeBlock> | |
| <p>The supporting files only get read when Claude actually needs them. The SKILL.md stays small and easy to scan.</p> | |
| <Box1 tone="rose" title="Skill anti-patterns to dodge" icon={XCircle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🟥 Description too short ("help with backend") — fires for anything backend-shaped</li> | |
| <li>🟥 Body too long — hundreds of lines of manual stuffed into SKILL.md (split into supporting files)</li> | |
| <li>🟥 One skill doing five jobs — review, deploy, debug, docs, incident response</li> | |
| <li>🟥 Side-effect skills with model auto-invocation — set <InlineCode>disable-model-invocation: true</InlineCode></li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Where skills live</h3> | |
| <p>Three locations, in increasing scope:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li><InlineCode>~/.claude/skills/</InlineCode> — your personal skills, available everywhere</li> | |
| <li><InlineCode>./.claude/skills/</InlineCode> — project skills, checked into git, shared with team</li> | |
| <li>plugin/marketplace skills — installable bundles others have published</li> | |
| </ul> | |
| <p>Project skills are the highest-leverage form. They turn institutional knowledge into context Claude can pull up on demand — instead of living in a wiki nobody reads.</p> | |
| </Ch>); | |
| const Ch12 = () => (<Ch> | |
| <p>Tools for agents and APIs for humans look similar but aren't the same thing. APIs optimize for feature completeness — every endpoint, every option. Agent tools should optimize for one question only: <span className="text-amber-400 font-semibold">can the model pick the right one and use it correctly on the first try?</span></p> | |
| <ToolGenDiagram /> | |
| <p>The <span className="text-emerald-400 font-semibold">ACI</span> (Agent-Computer Interface) idea is the inflection point. Instead of dropping <InlineCode>get_post</InlineCode> + <InlineCode>update_content</InlineCode> + <InlineCode>update_title</InlineCode> on the model and asking it to choreograph, you give it <InlineCode>update_post(id, title, content)</InlineCode> — one tool that maps to the agent's actual goal.</p> | |
| <Insight> | |
| Tool design shapes agent behavior the way HCI shapes human behavior. Bad parameters waste turns. Opaque errors send the loop into "let me try again" hell. Good tools give structured errors with recovery hints: <em>"POST_NOT_FOUND. Call list_posts first to get a valid id."</em> | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Good vs bad, side by side</h3> | |
| <ToolDesignDemo /> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The seven tool design principles</h3> | |
| <Box1 tone="cyan"> | |
| <ol className="space-y-2 text-sm list-decimal list-inside"> | |
| <li><strong>Name the goal, not the endpoint.</strong> <InlineCode>jira_issue_get</InlineCode> beats <InlineCode>http_request</InlineCode>.</li> | |
| <li><strong>Type and constrain parameters.</strong> Use JSON schema. Give patterns. Give examples.</li> | |
| <li><strong>Be specific in descriptions.</strong> Include "use when X" and "don't use when Y."</li> | |
| <li><strong>Return what's needed for the next decision.</strong> Strip metadata, IDs, internal fields the model won't use.</li> | |
| <li><strong>Bound output size.</strong> Either truncate by default or include a <InlineCode>response_format: "summary" | "full"</InlineCode> param.</li> | |
| <li><strong>Errors must guide recovery.</strong> Don't return <InlineCode>"error: failed"</InlineCode>. Return what failed and what to try next.</li> | |
| <li><strong>One tool per goal.</strong> If the model has to call three tools to do one thing, you have three tools where one belongs.</li> | |
| </ol> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The AskUserQuestion lesson</h3> | |
| <Box1 tone="violet" title="A real Anthropic example" icon={Wrench}> | |
| Anthropic tried three ways to let Claude pause and ask the user something. Adding a <InlineCode>question</InlineCode> param to existing tools? Claude ignored it. Asking Claude to emit special markdown? Brittle and unenforced. The winner: a dedicated <InlineCode>AskUserQuestion</InlineCode> tool. Calling the tool <em>is</em> the pause signal. Structured, explicit, hard to skip. | |
| <div className="mt-2"><strong>Lesson:</strong> if you want a behavior, give it a tool — flags and conventions are easy to drift past.</div> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Generation 3 features</h3> | |
| <p>The third generation of tool design goes further than ACI. Three techniques worth knowing:</p> | |
| <h4 className="text-md font-bold text-zinc-100 mt-4 mb-1">Tool Search</h4> | |
| <p>Stop loading every definition by default. The model gets a small index; when it needs a tool, it searches for it on demand. Context retention rates hit 95%, and Anthropic measured Opus 4 accuracy jumping from 49% to 74% on long-tail tool tasks.</p> | |
| <h4 className="text-md font-bold text-zinc-100 mt-4 mb-1">Programmatic Tool Calling</h4> | |
| <p>The model writes a small program that calls multiple tools in sequence — instead of going one tool call at a time. Intermediate JSON never enters context. On big chains, token usage drops from ~150K to ~2K.</p> | |
| <CodeBlock language="javascript" label="programmatic tool calling">{`// Instead of this (5 tool calls, all results in context): | |
| // 1. list_users() → 5K tokens | |
| // 2. get_user(id1) → 2K tokens | |
| // 3. get_user(id2) → 2K tokens | |
| // 4. ... | |
| // Total: 15K+ in context | |
| // The model writes: | |
| const users = await list_users(); | |
| const inactive = await Promise.all( | |
| users.filter(u => u.last_login < cutoff) | |
| .map(u => get_user(u.id)) | |
| ); | |
| return inactive.map(u => u.email); | |
| // Only the final array hits context. ~200 tokens.`}</CodeBlock> | |
| <h4 className="text-md font-bold text-zinc-100 mt-4 mb-1">Tool Use Examples</h4> | |
| <p>Include 1-5 example invocations per tool, with realistic input and expected output. Anthropic measured accuracy going from 72% to 90% just from adding good examples.</p> | |
| <CodeBlock language="json" label="tool with examples">{`{ | |
| "name": "search_logs", | |
| "description": "Search application logs by time range and filter.", | |
| "examples": [ | |
| { | |
| "input": { "service": "api", "since": "1h", "level": "error" }, | |
| "output": "Found 14 errors in last hour. Top: 'DB connection timeout' (8 occurrences)" | |
| }, | |
| { | |
| "input": { "service": "worker", "since": "10m", "trace_id": "abc123" }, | |
| "output": "Trace abc123: 1 entry at 14:32:01 — 'Job started'. No further log lines." | |
| } | |
| ] | |
| }`}</CodeBlock> | |
| <p>Examples ground the model. They convert abstract type signatures into concrete usage patterns the model has actually "seen."</p> | |
| <Aside title="When something goes wrong, debug tools BEFORE suspecting the model"> | |
| Most "wrong tool" issues trace back to imprecise descriptions, not missing capability. Read your tool description out loud. If you can think of three reasonable user requests that could plausibly trigger it, your description is too broad. Tighten it. | |
| </Aside> | |
| <Checkpoint | |
| q="Your agent keeps calling list_files() before every file operation, even when it already knows the path. What's the most likely root cause?" | |
| options={[ | |
| "The model is too cautious — needs a stronger prompt to be efficient", | |
| "list_files()'s description doesn't make clear when NOT to call it", | |
| "You should switch to a faster model", | |
| "Add a hook that blocks list_files() unless explicitly needed" | |
| ]} | |
| correct={1} | |
| explain="Tools that fire too often are almost always a description problem. The agent can't read your mind about when 'efficient enough' kicks in. Adding 'Don't call this if you already have the path' to the description is dramatically more effective than prompting around it. Hook-blocking would work but is the heavy hammer for what's really an information problem." | |
| /> | |
| </Ch>); | |
| const Ch13 = () => (<Ch> | |
| <p>MCP — Model Context Protocol — is the open standard for connecting external services to Claude as tools. GitHub, Sentry, Linear, Postgres, your internal services — all can be exposed via MCP servers and used by Claude as if they were built-in tools.</p> | |
| <p>It's a powerful capability surface. It's also the single biggest source of context bloat in real-world Claude Code setups. Both can be true at once.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">What MCP actually is</h3> | |
| <p>An MCP server is just a process that speaks a small JSON-RPC protocol. You start it; Claude connects; Claude asks "what tools do you offer?"; the server replies with a list of tool definitions; Claude can then call those tools as needed.</p> | |
| <CodeBlock language="json" label=".claude/settings.json">{`{ | |
| "mcpServers": { | |
| "github": { | |
| "command": "npx", | |
| "args": ["@modelcontextprotocol/server-github"], | |
| "env": { "GITHUB_TOKEN": "$GITHUB_TOKEN" } | |
| }, | |
| "postgres": { | |
| "command": "npx", | |
| "args": ["@modelcontextprotocol/server-postgres", "$DATABASE_URL"] | |
| }, | |
| "internal-api": { | |
| "command": "node", | |
| "args": ["./mcp/internal-api.js"] | |
| } | |
| } | |
| }`}</CodeBlock> | |
| <p>That's it. Claude sees three new categories of tools the next time it starts. The GitHub one might add 30 tools. Postgres might add 8. Your internal one adds whatever you wrote.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The cost of "just connect it"</h3> | |
| <Box1 tone="rose" title="The MCP overhead trap" icon={AlertTriangle}> | |
| Every MCP tool definition lives in your context window from session start. A typical "rich" MCP server (GitHub, Linear, Atlassian) exposes 25-50 tools. At ~200 tokens each, that's 5-10K per server. Connect five and you've spent 25-50K tokens before saying a word — even if you only use one tool from one of them this session. | |
| </Box1> | |
| <p>The instinct is "more is more — connect everything you might need!" The reality is that every tool you don't use is paying rent for nothing. Be ruthless: connect the servers you use weekly. Disconnect the rest. You can always reconnect when you need them.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Three principles for working with MCP</h3> | |
| <Box1 tone="cyan"> | |
| <ol className="space-y-2 text-sm list-decimal list-inside"> | |
| <li><strong>Connect what you'll use this week.</strong> Not "this month." Not "ever." This week.</li> | |
| <li><strong>Prefer focused servers over kitchen-sink ones.</strong> A server that does 8 things well beats one that does 80.</li> | |
| <li><strong>Periodically audit.</strong> Run <InlineCode>/context</InlineCode>. If your fixed overhead from MCP is over 15K, prune.</li> | |
| </ol> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">When to write your own MCP server</h3> | |
| <p>Write your own when none of these are true:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>An official MCP server already exists for the service</li> | |
| <li>The interaction is one-off (use Bash + curl instead)</li> | |
| <li>The interaction is read-only and you can use a cli tool</li> | |
| <li>Your team has fewer than 3 people who'd use it</li> | |
| </ul> | |
| <p>If you do write one, treat it like any production code: version it, test it, document it. The tool descriptions will be read by every agent on your team — they're not personal preferences.</p> | |
| <Aside title="Should I use MCP or just give Claude bash + a CLI?"> | |
| For ad-hoc operations, the CLI is often faster to set up and easier to debug. <InlineCode>gh pr list</InlineCode> in Bash works the same as a GitHub MCP tool, but you can run it yourself to verify. The advantage of MCP is structured output and typed parameters — the model knows exactly what it'll get back. The advantage of CLI is no overhead and total flexibility. Both are fine; pick based on whether you need structure. | |
| </Aside> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Auth and secrets</h3> | |
| <p>MCP servers are local processes — they run on your machine, not in the cloud. Secrets get passed as environment variables, exactly like any other local dev tool. There's nothing magical about this:</p> | |
| <CodeBlock language="bash" label=".env">{`# Loaded by your MCP server's process when Claude Code starts it | |
| GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxx | |
| LINEAR_API_KEY=lin_api_xxxxxxxxxxx | |
| DATABASE_URL=postgres://localhost/myproj_dev`}</CodeBlock> | |
| <p>Don't ever commit settings.json with real tokens. Reference env vars instead. Your CI shouldn't be running Claude Code anyway.</p> | |
| </Ch>); | |
| const Ch14 = () => (<Ch> | |
| <p>Hooks are easy to file under "scripts that auto-run." That undersells them. The real point: <span className="text-violet-400 font-semibold">move work out of the model's on-the-fly judgment and into deterministic processes</span>. Things like "should this format?", "can I edit this protected file?", "did the task finish — notify me?" — none of those should depend on the model remembering the rule every time.</p> | |
| <HookTimeline /> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The lifecycle events</h3> | |
| <Box1 tone="violet"> | |
| <ul className="space-y-2 text-sm"> | |
| <li><strong>SessionStart</strong> — fires once when a session begins. Inject env vars, log the start, set up a worktree.</li> | |
| <li><strong>PreToolUse</strong> — fires before any tool. Block, approve, or rewrite the call. Most powerful.</li> | |
| <li><strong>PostToolUse</strong> — fires after a tool. Format files, run linters, attach metadata.</li> | |
| <li><strong>Notification</strong> — fires on user-visible events. Send a desktop notification when long task finishes.</li> | |
| <li><strong>Stop</strong> — fires when a session ends. Clean up, write a session log.</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">When to use hooks (and when not to)</h3> | |
| <Box1 tone="emerald" title="Suitable for hooks" icon={Check}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🛡️ Block edits to protected files (.env, lockfiles, generated code)</li> | |
| <li>🎨 Auto-format, lint, light validation after Edit</li> | |
| <li>🌱 Inject env state at SessionStart (git branch, current user, vars)</li> | |
| <li>📣 Push notifications when long tasks complete</li> | |
| <li>📝 Audit logging — record every tool call to a file for later review</li> | |
| </ul> | |
| </Box1> | |
| <Box1 tone="rose" title="Unsuitable for hooks" icon={XCircle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🧠 Complex semantic judgment requiring context</li> | |
| <li>⏱️ Long-running business processes</li> | |
| <li>🔀 Multi-step reasoning with tradeoffs</li> | |
| <li>🎲 Anything you'd describe as "it depends"</li> | |
| </ul> | |
| Those belong in skills or subagents, not hooks. | |
| </Box1> | |
| <Insight> | |
| The real magic is the three-layer stack: <strong>CLAUDE.md</strong> declares "tests must pass before commit." A <strong>skill</strong> tells Claude how to run tests and read failures. A <strong>hook</strong> hard-blocks on critical paths. Any single layer has gaps. CLAUDE.md gets ignored. Hooks can't do judgment. All three together is what actually holds up. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Hook configuration</h3> | |
| <CodeBlock language="json" label=".claude/settings.json">{`{ | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "Bash", | |
| "pattern": ".*git push.*main.*", | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "echo 'BLOCKED: never push to main directly' >&2 && exit 1" | |
| }] | |
| } | |
| ], | |
| "PostToolUse": [ | |
| { | |
| "matcher": "Edit", | |
| "pattern": "*.rs", | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "cargo fmt 2>&1 | head -10" | |
| }] | |
| }, | |
| { | |
| "matcher": "Edit", | |
| "pattern": "*.ts", | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "pnpm prettier --write \"$CLAUDE_FILE_PATH\" && pnpm tsc --noEmit 2>&1 | head -30" | |
| }] | |
| } | |
| ], | |
| "Notification": [ | |
| { | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "osascript -e 'display notification \"Claude is waiting\" with title \"claude-code\"'" | |
| }] | |
| } | |
| ] | |
| } | |
| }`}</CodeBlock> | |
| <p>Note <InlineCode>| head -30</InlineCode> on the type-check hook — keep hook output small. Hooks that dump thousands of lines back into context just trade one problem for another.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">A real-world hook recipe</h3> | |
| <p>One pattern worth borrowing: a "review-before-commit" hook that runs your full local check before Claude ever calls <InlineCode>git commit</InlineCode>:</p> | |
| <CodeBlock language="json" label="block bad commits">{`{ | |
| "hooks": { | |
| "PreToolUse": [{ | |
| "matcher": "Bash", | |
| "pattern": ".*git commit.*", | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "pnpm lint && pnpm test && pnpm typecheck || (echo 'Quality gate failed' >&2; exit 1)" | |
| }] | |
| }] | |
| } | |
| }`}</CodeBlock> | |
| <p>Now Claude can attempt to commit, but if any of lint/test/typecheck fails, the commit is blocked and the model gets a clear "fix this first" signal. The rule lives in code; the model doesn't have to remember it.</p> | |
| <Aside title="What's in $CLAUDE_FILE_PATH and the other env vars?"> | |
| Claude Code injects useful environment variables when it runs hook commands: <InlineCode>$CLAUDE_FILE_PATH</InlineCode> for file ops, <InlineCode>$CLAUDE_TOOL_NAME</InlineCode> for which tool was called, <InlineCode>$CLAUDE_BASH_COMMAND</InlineCode> for bash invocations. Use them to keep your hooks generic. | |
| </Aside> | |
| </Ch>); | |
| const Ch15 = () => (<Ch> | |
| <p>A subagent is "another Claude" that branches off your main conversation with its own context and only the tools you allow. People reach for them looking for parallelism. The real prize is <span className="text-violet-400 font-semibold">isolation</span>.</p> | |
| <SubagentDiagram /> | |
| <p>Codebase scans, test runs, and review passes generate huge amounts of intermediate output. If that all lands in the main thread, you've polluted your context with stuff that was only useful for one decision. Send those tasks to a subagent and the main thread receives only the conclusion.</p> | |
| <Box1 tone="cyan" title="Built-in subagent types" icon={Box}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🔍 <strong>Explore</strong> — read-only scan, runs Haiku to save cost</li> | |
| <li>🗺️ <strong>Plan</strong> — research and planning, broader context</li> | |
| <li>🛠️ <strong>General-purpose</strong> — flexible utility, full toolkit</li> | |
| <li>🎯 <strong>Custom</strong> — define your own with explicit constraints</li> | |
| </ul> | |
| </Box1> | |
| <Insight> | |
| The Explore→Main pattern is the killer move. Heavy exploration done by Explore (cheap, isolated). The summary comes back. Implementation happens in Main with a clean context. You get the benefits of investigation without paying its context tax. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Defining a custom subagent</h3> | |
| <CodeBlock language="markdown" label=".claude/agents/security-reviewer.md">{`--- | |
| name: security-reviewer | |
| description: | | |
| Use to review code for security issues. Triggers on diffs, PRs, or when | |
| the user explicitly asks for a security review. Returns a list of findings, | |
| not a fix. | |
| tools: [Read, Grep, Glob] | |
| model: claude-opus-4-7 | |
| maxTurns: 12 | |
| --- | |
| # Role | |
| You are a security-focused code reviewer. You only read; you never modify. | |
| Your job is to spot risk, not to fix it. | |
| # Output format | |
| Return findings as JSON: | |
| \`\`\`json | |
| [ | |
| { "severity": "high" | "medium" | "low", | |
| "file": "path/to/file.ts", | |
| "line": 42, | |
| "category": "auth" | "injection" | "secrets" | "crypto" | "validation", | |
| "issue": "Short description", | |
| "recommendation": "What to do about it" } | |
| ] | |
| \`\`\` | |
| # Things to look for | |
| - SQL string concatenation that could be injection | |
| - Missing auth checks on sensitive endpoints | |
| - Hardcoded secrets, even in test files | |
| - Crypto using weak algorithms (MD5, SHA-1, DES) | |
| - User input flowing to file paths or shell commands without sanitization | |
| If you find nothing, return an empty array.`}</CodeBlock> | |
| <p>Now from your main conversation: "Review this PR with security-reviewer." Claude spawns the subagent, gives it the diff, the subagent does its scan in isolation, and only the JSON findings come back. Your main context stays clean.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The configuration knobs that matter</h3> | |
| <Box1 tone="zinc"> | |
| <ul className="space-y-2 text-sm"> | |
| <li><InlineCode>tools / disallowedTools</InlineCode> — limit reach. Don't grant the same broad permissions as the main thread.</li> | |
| <li><InlineCode>model</InlineCode> — exploration → Haiku/Sonnet. Important reviews → Opus. Saves real money.</li> | |
| <li><InlineCode>maxTurns</InlineCode> — prevent runaway. Always set this. 10-15 is reasonable for most subagents.</li> | |
| <li><InlineCode>isolation: worktree</InlineCode> — filesystem isolation when files get modified.</li> | |
| </ul> | |
| </Box1> | |
| <Box1 tone="rose" title="Subagent anti-patterns" icon={AlertTriangle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🟥 Same broad permissions as main thread → isolation is theater</li> | |
| <li>🟥 Output format unspecified → main thread can't consume the result</li> | |
| <li>🟥 Strong dependencies between subtasks → not what subagents are for</li> | |
| <li>🟥 No depth limit → subagents spawning subagents spawning subagents</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Minimal system prompts</h3> | |
| <p>Give subagents <span className="text-violet-400 font-semibold">minimal system prompts</span>. Strip Memory and Skills. Keep only Tooling, Workspace, Runtime. Otherwise you're just leaking the main agent's privileges into the isolated workspace and defeating the point.</p> | |
| <p>This is sometimes called "least context" — the subagent should know just enough to do its narrow job, no more. If the security-reviewer subagent doesn't need to know about your deploy procedures, don't load that skill into its context.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">When NOT to use subagents</h3> | |
| <p>Subagents have overhead — process spawn, context setup, summary generation. For small tasks, the overhead exceeds the benefit. The threshold is roughly: if the task would generate more than ~5K tokens of intermediate output, a subagent pays off. Less than that, just do it inline.</p> | |
| <Checkpoint | |
| q="You're about to ask Claude to find every place in the codebase that uses a deprecated function. The codebase is 200K lines. What's the right approach?" | |
| options={[ | |
| "Just ask in the main thread — Grep is fast", | |
| "Use the Explore subagent — it'll do the scan and return only the list", | |
| "Make it a workflow with a deterministic grep first", | |
| "All three could work; depends on what comes next" | |
| ]} | |
| correct={3} | |
| explain="If the next step is 'now explain the design choice in each one,' option 1 needs the file contents anyway, so Explore would just delay things. If the next step is 'plan a refactor,' Explore is perfect — clean list comes back, main thread plans without scan noise. If you don't need any reasoning at all, the workflow option (just shell out to grep) is cheapest. The right answer really does depend on what you do with the list." | |
| /> | |
| </Ch>); | |
| const Ch16 = () => (<Ch> | |
| <p>Permissions sound boring until something goes wrong. Then they're the difference between "Claude made a typo in a file" and "Claude pushed something terrible to production at 2am." The control surface is one of the five diagnostic surfaces from Chapter 2 — and getting it right is mostly about being deliberate up front.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">The permission model</h3> | |
| <p>Claude Code has a few overlapping mechanisms for what's allowed:</p> | |
| <Box1 tone="cyan"> | |
| <ul className="space-y-2 text-sm"> | |
| <li><strong>Tool gating</strong> — which tools are even available (allowedTools, disallowedTools).</li> | |
| <li><strong>Per-tool prompting</strong> — ask the user before some tool calls (default for write/destructive tools).</li> | |
| <li><strong>Sandbox modes</strong> — limit filesystem writes to a sandboxed dir, network to specific hosts.</li> | |
| <li><strong>Hooks</strong> — fully programmable PreToolUse blocking.</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The three useful settings</h3> | |
| <CodeBlock language="json" label=".claude/settings.json">{`{ | |
| "permissions": { | |
| "allow": [ | |
| "Bash(pnpm test)", | |
| "Bash(pnpm lint)", | |
| "Bash(pnpm typecheck)", | |
| "Bash(git status)", | |
| "Bash(git diff:*)", | |
| "Read", | |
| "Grep", | |
| "Glob", | |
| "Edit", | |
| "WebFetch(domain:docs.anthropic.com)" | |
| ], | |
| "deny": [ | |
| "Bash(rm -rf:*)", | |
| "Bash(* push:* main:*)", | |
| "Bash(* push:* master:*)" | |
| ], | |
| "ask": [ | |
| "Bash(npm install:*)", | |
| "Bash(pnpm add:*)", | |
| "Write(.env*)", | |
| "Write(*.lock)" | |
| ] | |
| } | |
| }`}</CodeBlock> | |
| <p>Three lists: <strong>allow</strong> (run without asking), <strong>deny</strong> (never run), <strong>ask</strong> (prompt the user). Patterns support glob and parameter matching. The denylist is your safety net for the "what if Claude does something insane" scenarios.</p> | |
| <Box1 tone="rose" title="The accumulation trap" icon={AlertTriangle}> | |
| Every time you say "yes, allow it" to a one-off operation, that approval can accumulate in your settings. <InlineCode>rm -rf</InlineCode> approved during a one-time cleanup stays approved forever. Audit your <InlineCode>permissions.allow</InlineCode> list at least quarterly. If you wouldn't add it today, take it out today. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Sandbox modes</h3> | |
| <p>Sandbox modes give a stronger constraint than permission lists: they limit what's even physically reachable. Files are restricted to a sandbox directory; network is limited to allowed hosts. Even if Claude tries to escape the rules, the OS-level constraint prevents it.</p> | |
| <p>Use sandbox modes when:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>You're letting Claude run autonomously for long stretches without supervision</li> | |
| <li>The task involves untrusted input (e.g. analyzing a third-party repo)</li> | |
| <li>You're running multiple agents in parallel and want to be sure they can't trample each other</li> | |
| </ul> | |
| <p>For most interactive sessions, the permission lists plus hooks are enough. Sandbox modes are the heavier hammer reserved for autonomous operation.</p> | |
| <Aside title="Should I run Claude Code in a Docker container?"> | |
| For high-autonomy work, yes — it's the cleanest isolation boundary. A container with the project mounted, no host network, and limited memory gives you a reliable "blast radius" cap. For interactive day-to-day use, it's overkill. Many teams use containers for the long-running agent jobs (overnight refactors, large migrations) and run interactively on the host. | |
| </Aside> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Designing the boundary together</h3> | |
| <p>Permissions, sandbox, hooks — these aren't independent systems. They're layers of the same control surface. Use them together:</p> | |
| <Box1 tone="emerald" title="The defense-in-depth pattern"> | |
| <ol className="space-y-1 text-sm list-decimal list-inside"> | |
| <li><strong>CLAUDE.md</strong> tells Claude the rules in plain English (intent layer)</li> | |
| <li><strong>permissions.deny</strong> blocks the obvious bad commands (rule layer)</li> | |
| <li><strong>hooks</strong> add programmable PreToolUse checks (logic layer)</li> | |
| <li><strong>sandbox</strong> caps physical access if everything else fails (physics layer)</li> | |
| </ol> | |
| Each layer catches a different class of mistake. Don't rely on any single one. | |
| </Box1> | |
| </Ch>); | |
| const Ch17 = () => (<Ch> | |
| <p>This is the topic that quietly explains half of Claude Code's design. Prompt caching isn't a perf optimization tucked in the corner — it's a <span className="text-amber-400 font-semibold">first-class architectural constraint</span>. Cache hits cut cost ~90%, slash latency, and loosen rate limits. Anthropic treats low cache hit rates as incident-level signals.</p> | |
| <CacheLayout /> | |
| <p>Caching is <span className="text-cyan-400 font-semibold">prefix matching</span>. The system caches everything from the start of the request up to a cache-control breakpoint. <em>Order matters absolutely.</em> One byte different in the prefix and you're rebuilding from scratch.</p> | |
| <Insight> | |
| A counterintuitive consequence: <strong>switching to a smaller, cheaper model mid-session is often more expensive</strong>. If you've chatted 100K tokens with Opus and switch to Haiku for a quick question, Haiku has to rebuild the entire 100K-token cache for itself. You'd pay less by staying on Opus. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The math of cache hits</h3> | |
| <p>Cache writes cost about 25% more than a normal write. Cache reads cost about 90% less than a normal read. The breakeven is hit on the second use — anything used twice or more is a net win. In a typical multi-turn session, the system prompt and tool definitions are read on every turn, so the cache pays off immediately and keeps paying.</p> | |
| <CodeBlock language="javascript" label="cache cost example">{`// Without caching: | |
| // Turn 1: 20K input tokens × $3/M = $0.06 | |
| // Turn 2: 22K input tokens × $3/M = $0.066 | |
| // Turn 3: 25K input tokens × $3/M = $0.075 | |
| // ...10 turns total: ~$0.85 | |
| // With caching (system prompt + tools = 18K cached): | |
| // Turn 1: 18K cache write @ $3.75/M + 2K @ $3/M = $0.073 | |
| // Turn 2: 18K cache read @ $0.30/M + 4K @ $3/M = $0.017 | |
| // Turn 3: 18K cache read @ $0.30/M + 7K @ $3/M = $0.026 | |
| // ...10 turns total: ~$0.20 | |
| // 4x cheaper. And gets cheaper the longer the session runs.`}</CodeBlock> | |
| <Box1 tone="rose" title="Common cache busters" icon={AlertTriangle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>⏰ <strong>Timestamps in the system prompt</strong> — changes every request, voids everything. Put dynamic info in a later message.</li> | |
| <li>🔀 <strong>Non-deterministic tool ordering</strong> — sort your tools.</li> | |
| <li>➕ <strong>Adding/removing tools mid-session</strong> — even a small edit breaks the prefix. Use stubs and lazy-load fuller schemas instead.</li> | |
| <li>🔁 <strong>Switching models mid-session</strong> — cache is per-model.</li> | |
| <li>🎲 <strong>Random ordering of CLAUDE.md sections</strong> — keep section order stable.</li> | |
| </ul> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Why Plan Mode doesn't swap tools</h3> | |
| <p>Earlier we noted that Plan Mode doesn't replace write-tools with read-only versions, even though that would be the obvious design. Now you can see why: swapping tools would smash the cache. Instead, the model is told via system instruction that it's in Plan Mode and shouldn't write — the tool list (and thus the prefix) stays stable.</p> | |
| <p>This is a recurring theme. Many Claude Code design choices that look slightly weird are actually cache-aware decisions. Once you know to look for it, you see it everywhere.</p> | |
| <Box1 tone="emerald" title="The non-obvious takeaway" icon={Sparkles}> | |
| A <em>large but stable</em> system prompt can cost less than a <em>small but changing</em> one. You pay the write cost once. Subsequent reads come at up to a 90% discount. "Keep the prefix short" misses the point — keep the prefix <strong>stable</strong>. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Designing for cacheability</h3> | |
| <p>If you're building agent systems on top of the API, design for cache hits from day one:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Put dynamic content (timestamps, user IDs, request IDs) in the latest user message, never in system</li> | |
| <li>Sort tool lists deterministically</li> | |
| <li>Resist "small edits" to the system prompt during a session</li> | |
| <li>Use <InlineCode>cache_control</InlineCode> breakpoints explicitly to mark where the prefix ends</li> | |
| <li>Monitor cache hit rate — under 80% on a long session is a red flag</li> | |
| </ul> | |
| </Ch>); | |
| // PART 4: MULTI-AGENT | |
| const Ch18 = () => (<Ch> | |
| <p>Multi-agent looks like a parallelism problem. It isn't. It's an <span className="text-violet-400 font-semibold">isolation and coordination</span> problem that <em>happens</em> to allow parallelism. Two operating modes are worth distinguishing:</p> | |
| <Box1 tone="cyan" title="Director vs Orchestrator" icon={Users}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="cyan">Director Mode</Tag> Synchronous. Human in close turn-by-turn collaboration with one agent. Session ends → output is ephemeral.</div> | |
| <div><Tag tone="violet">Orchestrator Mode</Tag> Asynchronous. Human sets the goal at the start, multiple agents work in parallel, human reviews at the end. The intermediate output becomes durable artifacts (branches, PRs).</div> | |
| </div> | |
| </Box1> | |
| <p>Orchestrator Mode is where multi-agent <em>earns its complexity</em>. Not "multiple models running simultaneously" — that's just expensive. The point is shifting continuous human involvement into review of tangible artifacts.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">When to scale up</h3> | |
| <p>Don't go multi-agent until you've genuinely hit the ceiling of a single agent. Coordination overhead routinely outweighs whatever parallelism you'd gain. The honest signals that you're at the ceiling:</p> | |
| <Box1 tone="emerald"> | |
| <ul className="space-y-1 text-sm"> | |
| <li>✅ Tasks naturally decompose into independent units (each can run without waiting on others)</li> | |
| <li>✅ Each unit generates lots of intermediate output that would pollute a shared thread</li> | |
| <li>✅ Different units want different tools, models, or contexts</li> | |
| <li>✅ The total work would exceed a single 200K context window even with compression</li> | |
| <li>✅ You actually need wall-clock parallelism — sequential would be too slow</li> | |
| </ul> | |
| If fewer than 3 of these are true, stay single-agent and use subagents for isolation. | |
| </Box1> | |
| <Insight tone="rose"> | |
| <strong>Hallucinations amplify in multi-agent systems.</strong> Agent A drifts off course. Agent B reinforces the bias. Agent C builds on it. They converge on a wrong answer with high confidence. This is exactly when cross-validation pays off — a second independent agent, unit tests, compilers, manual review. Anything that breaks the chain. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The build order</h3> | |
| <Box1 tone="rose" title="The order matters — don't skip steps" icon={AlertTriangle}> | |
| <ol className="space-y-1 text-sm list-decimal list-inside"> | |
| <li><strong>Persistent task graphs</strong> (.tasks/) — the source of truth for what's being done</li> | |
| <li><strong>Teammate identities</strong> — each agent knows who it is and what role it plays</li> | |
| <li><strong>Structured communication protocol</strong> — JSONL inbox, append-only</li> | |
| <li><strong>Cross-validation</strong> — independent verification, not agent-to-agent peer review</li> | |
| </ol> | |
| Skip steps and you get coordination chaos. Reverse the order and you build on quicksand. | |
| </Box1> | |
| <p>The biggest temptation is to start with collaboration ("agents talk to each other!") before establishing protocol and isolation. That order produces systems where bugs are essentially undebuggable — you can't tell where state actually lives.</p> | |
| </Ch>); | |
| const Ch19 = () => (<Ch> | |
| <p>Once you're committed to multi-agent, the protocol question becomes everything. How do agents communicate? How do you avoid race conditions? How do you debug after the fact? The answer that consistently wins: <span className="text-violet-400 font-semibold">append-only JSONL files on disk</span>, plus a task graph.</p> | |
| <MultiAgentTopo /> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Why JSONL?</h3> | |
| <p>JSONL (one JSON object per line) is boring. That's the point. It's:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li><strong>Append-only</strong> — writers never conflict, no locking needed</li> | |
| <li><strong>Human-readable</strong> — you can <InlineCode>tail -f</InlineCode> the inbox during a run</li> | |
| <li><strong>Tool-friendly</strong> — grep, jq, awk all work natively</li> | |
| <li><strong>Replayable</strong> — re-run from any point by replaying the log</li> | |
| <li><strong>Debuggable</strong> — every action has a timestamp and an author</li> | |
| </ul> | |
| <CodeBlock language="json" label=".team/inbox/worker-a.jsonl">{`{"ts":"2026-04-23T14:01:02Z","from":"orchestrator","to":"worker-a","type":"task","id":"t1","payload":{"action":"refactor","file":"src/auth.ts"}} | |
| {"ts":"2026-04-23T14:01:18Z","from":"worker-a","to":"orchestrator","type":"started","id":"t1"} | |
| {"ts":"2026-04-23T14:03:42Z","from":"worker-a","to":"orchestrator","type":"complete","id":"t1","payload":{"branch":"refactor-auth-1","files_changed":3,"tests_pass":true}}`}</CodeBlock> | |
| <p>Three messages, one task, complete audit trail. If something goes wrong, you can read this log and reconstruct exactly what happened.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The task graph</h3> | |
| <p>JSONL captures conversation. The task graph captures structure: which tasks exist, which depend on which, who owns what.</p> | |
| <CodeBlock language="json" label=".tasks/graph.json">{`{ | |
| "tasks": { | |
| "t1": { | |
| "title": "Refactor auth module", | |
| "owner": "worker-a", | |
| "status": "complete", | |
| "depends_on": [], | |
| "produces": ["branch:refactor-auth-1"] | |
| }, | |
| "t2": { | |
| "title": "Update callers of auth", | |
| "owner": "worker-b", | |
| "status": "in_progress", | |
| "depends_on": ["t1"], | |
| "produces": ["branch:update-auth-callers-1"] | |
| }, | |
| "t3": { | |
| "title": "Add integration tests", | |
| "owner": "worker-c", | |
| "status": "blocked", | |
| "depends_on": ["t1", "t2"], | |
| "produces": ["branch:auth-integration-tests-1"] | |
| } | |
| } | |
| }`}</CodeBlock> | |
| <p>The orchestrator updates this file as work progresses. Workers read it to know what they should pick up next. The graph and the JSONL together form the entire system state — kill every process and restart, and you can resume from exactly where you were.</p> | |
| <Box1 tone="cyan" title="Append-only is non-negotiable" icon={Lock}> | |
| Why? Because mutable state is where multi-agent debugging goes to die. If agents can edit each other's messages, you lose the ability to reproduce a bug after the fact. With append-only, the log <em>is</em> the truth, and the truth is always there. | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Cross-validation, not peer review</h3> | |
| <p>A common pitfall: making agents review each other's work. Agent A writes code, agent B reviews, agent C tests. Sounds reasonable, but it has a fatal flaw: <strong>agents trained similarly tend to make similar mistakes</strong>. They can rubber-stamp each other's bad work with high confidence.</p> | |
| <p>Cross-validation breaks this by using <em>independent</em> signals: compilers, type checkers, unit tests, integration tests, lint rules, runtime metrics. These can't be charmed by a confident-sounding explanation. Always include at least one non-agent verifier in the loop.</p> | |
| </Ch>); | |
| const Ch20 = () => (<Ch> | |
| <p>When multiple agents are modifying files, you have a basic problem: agent A's edits collide with agent B's edits and the working tree becomes incoherent. The clean answer is git worktrees — a feature most developers ignore.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">What worktrees are</h3> | |
| <p>A git worktree is a separate working directory that shares the same <InlineCode>.git</InlineCode> repo. You can have multiple branches checked out simultaneously, in different folders, with one repo backing them all. No clones, no extra disk for the history.</p> | |
| <CodeBlock language="bash" label="git worktrees">{`# Inside your repo: | |
| $ git worktree add ../worker-a feature/auth-refactor | |
| $ git worktree add ../worker-b feature/auth-callers | |
| $ git worktree add ../worker-c feature/auth-tests | |
| # Now you have: | |
| # ./ main branch | |
| # ../worker-a feature/auth-refactor branch | |
| # ../worker-b feature/auth-callers branch | |
| # ../worker-c feature/auth-tests branch | |
| # All sharing one .git history`}</CodeBlock> | |
| <p>For a multi-agent system, point each worker at its own worktree. They edit files freely without colliding. Merging back to main happens via PRs, just like with humans.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Configuring isolation</h3> | |
| <CodeBlock language="markdown" label=".claude/agents/worker-a.md">{`--- | |
| name: worker-a | |
| description: Implements assigned tasks from the orchestrator | |
| tools: [Read, Edit, Write, Bash, Grep] | |
| isolation: | |
| type: worktree | |
| path: ../worktrees/worker-a | |
| branch: workers/a-base | |
| maxTurns: 50 | |
| --- | |
| # Role | |
| You are worker A. You receive tasks via .team/inbox/worker-a.jsonl. | |
| Read the latest task, do the work in your assigned worktree, push your | |
| branch when complete, and write a "complete" message back to the orchestrator.`}</CodeBlock> | |
| <p>The worker can't reach outside its worktree to corrupt other workers' files. The orchestrator coordinates via the JSONL inbox, never by direct file access. PRs become the merge protocol.</p> | |
| <Box1 tone="emerald" title="The isolation triangle" icon={Shield}> | |
| Three things together give you robust multi-agent isolation: | |
| <ul className="space-y-1 text-sm list-disc list-inside mt-2"> | |
| <li><strong>Filesystem</strong> — separate worktrees per agent</li> | |
| <li><strong>Permissions</strong> — minimal toolsets, restricted Bash patterns</li> | |
| <li><strong>Communication</strong> — JSONL only, no direct calls</li> | |
| </ul> | |
| </Box1> | |
| <p>The pragmatic rule from Chapter 18 bears repeating: don't go multi-agent until you've genuinely hit the ceiling of a single agent. But when you do, this is the architecture that holds up.</p> | |
| </Ch>); | |
| // PART 5: PRODUCTION | |
| const Ch21 = () => (<Ch> | |
| <p>"Claude says it's done" has zero engineering value. What matters is whether it's <em>actually</em> correct, whether you can roll back if it's not, and whether the trail is auditable. The verification loop is what turns an agent from a demo into something you'd let near production.</p> | |
| <Box1 tone="emerald" title="The 3 verifier tiers" icon={FileCheck}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="emerald">Lowest</Tag> Exit codes, lint, typecheck, unit tests. Cheap, fast, executable.</div> | |
| <div><Tag tone="cyan">Middle</Tag> Integration tests, screenshot diffs, contract tests, smoke tests.</div> | |
| <div><Tag tone="violet">Higher</Tag> Production logs, monitoring metrics, manual review checklists.</div> | |
| </div> | |
| </Box1> | |
| <Insight> | |
| A simple test: if you can't clearly explain "how does Claude know it's done correctly?" — the task probably isn't ready for autonomous execution. Define acceptance criteria <em>before</em> you write the prompt: which commands must pass, what to check first if they fail, what screenshots and logs prove success. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Building the loop</h3> | |
| <p>A verification loop is just an evaluator-optimizer pattern wired to your acceptance criteria. The agent generates, the verifier checks, failures feed back, the agent revises. Loop until pass or budget exhausted.</p> | |
| <CodeBlock language="javascript" label="verification loop">{`async function verify(task, maxAttempts = 3) { | |
| for (let i = 0; i < maxAttempts; i++) { | |
| const result = await agent.run(task); | |
| // Tier 1: cheap and definitive | |
| const lint = await run("pnpm lint"); | |
| if (!lint.ok) { task.feedback = lint.errors; continue; } | |
| const types = await run("pnpm typecheck"); | |
| if (!types.ok) { task.feedback = types.errors; continue; } | |
| // Tier 2: medium but reliable | |
| const tests = await run("pnpm test"); | |
| if (!tests.ok) { task.feedback = tests.failures; continue; } | |
| return { status: "pass", attempts: i + 1 }; | |
| } | |
| return { status: "fail", reason: "max attempts exceeded" }; | |
| }`}</CodeBlock> | |
| <p>Notice the order: tier 1 first, tier 2 second. Tier 3 (manual review, prod metrics) doesn't go in the loop — it's the final gate before merge. The agent shouldn't burn cycles trying to pass things it can't iterate on quickly.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Acceptance criteria as artifacts</h3> | |
| <p>Write acceptance criteria <em>before</em> the agent runs. They become an artifact you can hand to anyone — humans, agents, future you — and get the same answer about "is this done?"</p> | |
| <CodeBlock language="markdown" label="ACCEPTANCE.md">{`# Acceptance — refactor auth module | |
| ## Must pass | |
| - [ ] All existing tests still pass: \`pnpm test\` | |
| - [ ] Type check clean: \`pnpm typecheck\` | |
| - [ ] No new lint errors: \`pnpm lint\` | |
| - [ ] Auth integration tests pass: \`pnpm test:integration auth\` | |
| - [ ] /healthz endpoint returns 200 in local stack | |
| ## Must not regress | |
| - [ ] Login p99 latency stays under 200ms (run benchmark) | |
| - [ ] No new dependencies added without explicit approval | |
| ## Manual checks (after auto) | |
| - [ ] Code review by @alice or @bob | |
| - [ ] Staging smoke test: log in, log out, refresh token | |
| ## Rollback plan | |
| Single PR. Revert if any acceptance check fails post-merge.`}</CodeBlock> | |
| <p>This file lives in the repo. It's the contract. The agent reads it; the human reads it; CI reads it. Everyone agrees on "done."</p> | |
| </Ch>); | |
| const Ch22 = () => (<Ch> | |
| <p>Eval is harder than agent debugging. It's also the place where teams commonly lose months. The instinct is to "tune the agent" when scores drop. Often the eval itself is broken and the agent is fine.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">Two metrics, often confused</h3> | |
| <div className="grid md:grid-cols-2 gap-3 my-4"> | |
| <div className="bg-cyan-500/5 border border-cyan-500/30 rounded-lg p-4"> | |
| <div className="text-cyan-400 font-bold">Pass@k</div> | |
| <div className="text-xs text-zinc-300 mt-2">"At least one of k runs is correct."</div> | |
| <div className="text-xs text-zinc-400 mt-1">Use when exploring capability ceilings — "could this agent ever do this?"</div> | |
| </div> | |
| <div className="bg-rose-500/5 border border-rose-500/30 rounded-lg p-4"> | |
| <div className="text-rose-400 font-bold">Pass^k</div> | |
| <div className="text-xs text-zinc-300 mt-2">"All k runs are correct."</div> | |
| <div className="text-xs text-zinc-400 mt-1">Use for regression — "did we break something on this change?"</div> | |
| </div> | |
| </div> | |
| <p>If you're shipping to production, you mostly care about Pass^k. Pass@k is for research. Confusing them gets you "the eval went up!" reports while users see degraded reliability.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Three kinds of graders</h3> | |
| <Box1 tone="cyan" title="Grader hierarchy" icon={Check}> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li>💻 <strong>Code graders</strong> — string match, unit tests, structural diff. Most certain. Use when there's a clear right answer.</li> | |
| <li>🤖 <strong>Model graders</strong> — rubric scoring, A/B comparison. Medium reliability. Use for semantic quality.</li> | |
| <li>👤 <strong>Human graders</strong> — slow but reliable. Calibrate the auto-judges against these.</li> | |
| </ul> | |
| </Box1> | |
| <p>The pyramid: many code grades, fewer model grades, occasional human grades. Use human grades to calibrate model grades; use model grades where code can't capture quality.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Transcript vs outcome</h3> | |
| <Insight> | |
| Cover both <strong>transcript</strong> (what the agent said happened) and <strong>outcome</strong> (what the system actually looks like now). Anthropic's airline-booking example: Opus exploited a policy loophole to find the user a cheaper option. Score against the pre-programmed path → fail. Score against outcome → win. You only see the truth if you cover both. | |
| </Insight> | |
| <p>Outcome-only evals miss process problems (the agent did the right thing for the wrong reason and will fail differently next time). Transcript-only evals miss creativity (the agent found a better path you didn't anticipate). Score both.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Fix the eval before tuning the agent</h3> | |
| <Box1 tone="rose" title="The eval is often the bug" icon={AlertTriangle}> | |
| When scores drop, teams instinctively touch the agent. But infrastructure errors (resource limits killing processes), grader bugs (failing correct answers), and aggregate scores hiding category-level failures all <em>look identical to model degradation</em>. Tuning the agent against a broken eval breaks the parts that worked. | |
| </Box1> | |
| <p>Symptoms that the eval is the problem, not the agent:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Score drops sharply on one specific category, while others are flat — investigate that category's grader</li> | |
| <li>Score drops correlate with infrastructure events — probably timeout or OOM</li> | |
| <li>Manual review of "failed" outputs shows the answer is correct — grader bug</li> | |
| <li>The eval set hasn't been refreshed in months — possibly contaminated by training data</li> | |
| </ul> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Building an eval set</h3> | |
| <p>Start with real traffic. The best eval cases are real failures users hit. Every time something breaks in production:</p> | |
| <ol className="space-y-1 text-sm list-decimal list-inside ml-2 text-zinc-300"> | |
| <li>Capture the input</li> | |
| <li>Capture the wrong output</li> | |
| <li>Write the correct output</li> | |
| <li>Add it to the eval set</li> | |
| </ol> | |
| <p>Over months, your eval set becomes a record of every class of failure your system has seen. Regressions get caught immediately. New failure modes get added. The set grows in lockstep with your understanding of the problem.</p> | |
| </Ch>); | |
| const Ch23 = () => (<Ch> | |
| <p>Cost optimization in agent systems is mostly about cache hit rate, model selection, and not spawning subagents you don't need. In that order.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">The cache hit rate is everything</h3> | |
| <p>If you're running on the API, your cache hit rate dominates your bill. A long session with 90% hit rate costs roughly a quarter of the same session at 50%. Anthropic publicly treats cache hit rate as a primary product metric — and you should too.</p> | |
| <Stat value="~90%" label="cost reduction on cache hits vs writes" tone="emerald" /> | |
| <p>Practical tactics for high cache hit rates:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Stable, deterministic system prompts</li> | |
| <li>Sorted tool lists</li> | |
| <li>Dynamic content (timestamps, IDs) in user messages, not system</li> | |
| <li>Avoid switching models mid-session</li> | |
| <li>Set <InlineCode>cache_control</InlineCode> breakpoints explicitly</li> | |
| </ul> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Model selection by task</h3> | |
| <Box1 tone="cyan" title="Which model when" icon={DollarSign}> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li><strong>Haiku</strong> — fast, cheap exploration. Read-only scans, classification, routing.</li> | |
| <li><strong>Sonnet</strong> — workhorse. Most coding tasks, most refactors, default choice.</li> | |
| <li><strong>Opus</strong> — high-stakes reasoning. Architecture decisions, security review, complex debugging.</li> | |
| </ul> | |
| </Box1> | |
| <p>The trick is to do most of the volume on Sonnet, push exploration down to Haiku via subagents, and reserve Opus for the moments that genuinely need it. A common production setup runs Sonnet as the main agent, with Haiku-backed Explore subagents for codebase scans.</p> | |
| <Insight> | |
| "Just use Opus for everything" sounds safer but is often wrong. The cost difference can be 5-10x for tasks Sonnet handles equally well. And Opus on a task it doesn't need to think about hard isn't more accurate — it just costs more and is slower. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Subagents save real money</h3> | |
| <p>The classic example: a "codebase analysis" task where the agent has to read 30 files. Done in the main thread, those 30 files (call it 50K tokens) sit in context for the rest of the session. Done in a Haiku-backed Explore subagent, only the 1K-token summary comes back.</p> | |
| <CodeBlock language="javascript" label="cost comparison">{`// Main thread reads 30 files | |
| // 50K tokens added to context | |
| // Every subsequent turn: 50K extra @ Sonnet rate | |
| // 10 more turns: 500K extra tokens read | |
| // Cost: ~$1.50 just for that residual | |
| // Explore subagent reads same 30 files, summarizes | |
| // 50K tokens read once @ Haiku rate (~3x cheaper) | |
| // 1K-token summary returns to main | |
| // Subsequent turns: only 1K extra residual | |
| // Cost: ~$0.15 for the read + ~$0.03 residual = $0.18 | |
| // 8x cheaper, plus a cleaner main-thread context.`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Tool output costs</h3> | |
| <p>Cheap tools are expensive when they spam context. A <InlineCode>find /</InlineCode> that returns 10K paths costs you that 10K every subsequent turn. Filter at the source:</p> | |
| <CodeBlock language="bash" label="cheap vs expensive tool calls">{`# Expensive (every turn pays the cost forever): | |
| $ find . -type f # 5K lines | |
| # Cheaper (filtered at source): | |
| $ find . -type f -name "*.ts" | head -100 # 100 lines | |
| # Cheapest (write to disk, reference on demand): | |
| $ find . -type f > /tmp/files.txt # 0 tokens in context | |
| $ wc -l /tmp/files.txt # confirm count | |
| $ grep "auth" /tmp/files.txt # query later`}</CodeBlock> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Watch your /context</h3> | |
| <p>Run <InlineCode>/context</InlineCode> regularly. If your fixed overhead is climbing without anything new being added, audit. If conversation tokens are building up but you're not making progress, /compact or /clear. Every token saved is money saved, multiplied across every session and every team member.</p> | |
| </Ch>); | |
| const Ch24 = () => (<Ch> | |
| <p>When something goes wrong with an agent system, the failure mode is almost never "the model is dumb." It's something specific in your stack — and there's a reliable order to check. Walk this ladder from cheap to expensive:</p> | |
| <Box1 tone="cyan" title="The diagnosis ladder" icon={Bug}> | |
| <ol className="space-y-2 text-sm list-decimal list-inside"> | |
| <li>Are tool descriptions accurate and unambiguous?</li> | |
| <li>Is task state externalized to files (not just in the model's head)?</li> | |
| <li>Is the eval system itself distorted?</li> | |
| <li>Should the deterministic parts move to a workflow?</li> | |
| <li>Is the prompt or model wrong?</li> | |
| </ol> | |
| Most problems get fixed in steps 1–2. There's almost never a need to jump straight to "rewrite the prompt" or "swap models." | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Symptom → root cause cheat sheet</h3> | |
| <Box1 tone="zinc"> | |
| <div className="space-y-3 text-sm"> | |
| <div><strong className="text-rose-300">Symptom:</strong> Agent picks the wrong tool. <br /><strong className="text-emerald-300">Root cause:</strong> 90% of the time, vague tool description. Read description aloud — if multiple plausible queries match, tighten it.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Agent loops on a failing command. <br /><strong className="text-emerald-300">Root cause:</strong> Error messages aren't actionable. Add recovery hints to the error.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Quality degrades over a long session. <br /><strong className="text-emerald-300">Root cause:</strong> Compaction dropped key context. Add Compact Instructions; consider HANDOFF.md.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Agent forgets a constraint stated in CLAUDE.md. <br /><strong className="text-emerald-300">Root cause:</strong> CLAUDE.md is bloated; key instruction is buried. Slim it down or move enforcement to a hook.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Different sessions get inconsistent results on the same task. <br /><strong className="text-emerald-300">Root cause:</strong> Task state isn't externalized. Write a task graph, use HANDOFF.md.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Cost is much higher than expected. <br /><strong className="text-emerald-300">Root cause:</strong> Cache hit rate is low. Look for cache busters: timestamps, model swaps, tool list changes.</div> | |
| <div><strong className="text-rose-300">Symptom:</strong> Tool returns huge output, agent struggles. <br /><strong className="text-emerald-300">Root cause:</strong> Filter tool output at source, write to file and grep on demand.</div> | |
| </div> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">When you really do need to look at the prompt</h3> | |
| <p>Sometimes it actually is the prompt. The signs:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>The first 5 ladder steps came up clean</li> | |
| <li>The behavior is consistent — same input, same wrong output</li> | |
| <li>Other models (different families) make the same mistake</li> | |
| <li>Reading the prompt out loud, you can see why it's ambiguous</li> | |
| </ul> | |
| <p>When you do change the prompt, change one thing. Re-run the eval. Promote the change only if the eval improves and nothing else regresses. Prompt edits are non-local — small changes can cascade in surprising ways.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Logs are your friend</h3> | |
| <p>Claude Code keeps session logs in <InlineCode>~/.claude/history/</InlineCode>. When something weird happens, the log is your replay. Don't rely on memory — pull up the JSONL:</p> | |
| <CodeBlock language="bash" label="reading session logs">{`# List recent sessions | |
| $ ls -lt ~/.claude/history/ | head | |
| # Tail a session | |
| $ tail -f ~/.claude/history/2026-04-23-1402.jsonl | |
| # Find every tool call in a session | |
| $ jq 'select(.type == "tool_use")' ~/.claude/history/2026-04-23-1402.jsonl | |
| # Find every error | |
| $ jq 'select(.type == "tool_result" and .content[0].is_error)' ~/.claude/history/2026-04-23-1402.jsonl`}</CodeBlock> | |
| <p>Half of agent debugging is just being willing to read the logs. They're verbose but they don't lie.</p> | |
| </Ch>); | |
| const Ch25 = () => (<Ch> | |
| <p>Three case studies worth knowing in detail. Each illustrates a principle from earlier chapters in the wild, with concrete numbers.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-4 mb-3">Case 1: OpenAI Codex's harness</h3> | |
| <Box1 tone="emerald" title="The 10x velocity claim" icon={Rocket}> | |
| Three engineers shipped a million lines of code in five months — roughly 10× normal velocity. The model wasn't the secret. The harness was. | |
| </Box1> | |
| <p>What they actually built:</p> | |
| <ul className="space-y-1 text-sm list-disc list-inside ml-2 text-zinc-300"> | |
| <li>Each project carries an <InlineCode>AGENTS.md</InlineCode> index of about 100 lines</li> | |
| <li>Constraints encoded as linters and CI rules, not docs</li> | |
| <li>The agent owns the entire pipeline: branch, code, PR, merge</li> | |
| <li>Code review largely moved to machines, but didn't disappear — it became automated checks</li> | |
| <li>Humans still reviewed merge-conflicts and high-risk diffs, but most PRs auto-merged on green</li> | |
| </ul> | |
| <p>The lesson: this is what "harness pushing tasks into the green quadrant" actually looks like. Clear acceptance criteria + automated verification + execution boundaries → autonomy that holds up.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Case 2: Anthropic's AskUserQuestion</h3> | |
| <p>Anthropic wanted Claude to be able to pause mid-task and ask the user a question. They tried three approaches:</p> | |
| <Compare | |
| goodLabel="What worked" | |
| badLabel="What didn't" | |
| good={<> | |
| <div className="font-bold mb-1">A dedicated AskUserQuestion tool.</div> | |
| <div className="text-xs">Calling the tool <em>is</em> the pause. Structured input (question, options). Hard to skip. Reliable across sessions.</div> | |
| </>} | |
| bad={<> | |
| <div className="font-bold mb-1">First attempt: a "question" parameter on existing tools.</div> | |
| <div className="text-xs mb-2">Claude often ignored it.</div> | |
| <div className="font-bold mb-1">Second attempt: a special markdown convention.</div> | |
| <div className="text-xs">Brittle — couldn't enforce, easy to drift past.</div> | |
| </>} | |
| /> | |
| <p>The lesson: if you want a behavior, give it a tool. Flags and conventions are too easy to skip. Tools are first-class — the model thinks in terms of them.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">Case 3: Cursor's Dynamic Context Discovery</h3> | |
| <p>Cursor experimented with a problem common in MCP-heavy setups: tool definitions eat huge amounts of context. Their approach: don't load all definitions upfront. Sync them to disk, load on demand based on intent.</p> | |
| <Stat value="46.9%" label="reduction in MCP-task token usage" tone="emerald" /> | |
| <p>The mechanism is conceptually simple: when the agent decides it needs a tool, it searches a local index of definitions, pulls in only the matching one, and uses it. The full catalog never enters the system prompt.</p> | |
| <p>This is an early example of what Anthropic later formalized as Tool Search — the third generation tool design pattern. Same idea, same payoff.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-8 mb-3">The pattern across all three</h3> | |
| <Insight> | |
| None of these wins came from a smarter model. All came from <strong>better engineering around the model</strong>. Harness design (Codex), tool-as-behavior (AskUserQuestion), context management (Cursor). The bottleneck on agent capability today is rarely the model. | |
| </Insight> | |
| </Ch>); | |
| // PART 6: REFERENCE | |
| const Ch26 = () => { | |
| const aps = [ | |
| { n: "CLAUDE.md as wiki", s: "Pollutes context every load. Key instructions get diluted.", f: "Keep only the contract. Move materials to skills and rules." }, | |
| { n: "Skill grab-bag", s: "Description triggers unreliably. Workflows conflict.", f: "One skill = one job. Be explicit about side-effect control." }, | |
| { n: "Too many tools, vague descriptions", s: "Wrong tool selection. Schemas overwhelm context.", f: "Merge overlapping tools. Clear namespacing." }, | |
| { n: "No verification loop", s: "Claude has no way to know if it's actually done.", f: "Bind a verifier to every task type." }, | |
| { n: "Over-autonomy", s: "Unbounded multi-agent fan-out. Hard to stop once it goes wrong.", f: "Minimize roles, permissions, worktrees. Always set maxTurns." }, | |
| { n: "No context segmentation", s: "Research, implementation, and review all pile onto main thread.", f: "/clear for task switches. /compact for phase switches. Subagents for heavy exploration." }, | |
| { n: "Wide autonomy + weak governance", s: "Tools open, but permission and recovery boundaries weak.", f: "Combine permissions, sandbox, hooks, subagents into one boundary." }, | |
| { n: "Approved commands pile up", s: "rm -rf etc. stay in settings.json. Once triggered: irreversible.", f: "Regularly review allowedTools list." }, | |
| { n: "Documented constraints, not enforced", s: "Agent selectively ignores rules in docs.", f: "Move rules into linters, hooks, tool validations. Encode, don't document." }, | |
| { n: "Disconnected memory", s: "Decision quality craters past ~20 turns.", f: "Monitor token usage. Auto-trigger compression at thresholds." }, | |
| { n: "Premature multi-agent", s: "Coordination overhead eclipses parallelism gains.", f: "Establish task graphs. Validate single-agent ceiling first." }, | |
| { n: "Zero evaluation", s: "Single fix introduces unknown regressions.", f: "Convert every real-world failure into an automated test case." }, | |
| ]; | |
| return (<Ch> | |
| <p>The greatest hits of agent disasters. Most of these aren't model limits. They're <span className="text-rose-400 font-semibold">missing engineering constraints</span> dressed up as model failures.</p> | |
| <Box1 tone="cyan" title="The diagnosis ladder (cheap → expensive)" icon={Brain}> | |
| <ol className="space-y-1 text-sm list-decimal list-inside"> | |
| <li>Are tool descriptions accurate?</li> | |
| <li>Is task state externalized to files?</li> | |
| <li>Is the eval system itself distorted?</li> | |
| <li>Should the deterministic parts move to a workflow?</li> | |
| </ol> | |
| Most problems get fixed in steps 1–2. There's almost never a need to jump straight to "rewrite the prompt" or "swap models." | |
| </Box1> | |
| <div className="space-y-3 mt-6"> | |
| {aps.map((ap, i) => ( | |
| <div key={i} className="grid md:grid-cols-[1fr_2fr] gap-3 bg-zinc-900/50 border border-zinc-800 rounded-lg p-4 hover:border-zinc-700 transition"> | |
| <div> | |
| <div className="text-xs text-zinc-500 font-mono">#{String(i + 1).padStart(2, "0")}</div> | |
| <div className="font-bold text-rose-400">{ap.n}</div> | |
| </div> | |
| <div className="text-sm"> | |
| <div className="text-zinc-400"><span className="text-rose-300 font-semibold">Symptom:</span> {ap.s}</div> | |
| <div className="text-zinc-300 mt-1"><span className="text-emerald-400 font-semibold">Fix:</span> {ap.f}</div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </Ch>); | |
| }; | |
| const Ch27 = () => (<Ch> | |
| <p>The cheat sheet. Bookmark this page; everything else in the book builds toward these one-liners.</p> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The six layers</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>L1 <Tag tone="amber">CLAUDE.md / rules / memory</Tag> — what this project IS</div> | |
| <div>L2 <Tag tone="amber">Tools / MCP</Tag> — what Claude CAN DO</div> | |
| <div>L3 <Tag tone="violet">Skills</Tag> — methodologies on demand</div> | |
| <div>L4 <Tag tone="violet">Hooks</Tag> — enforced rules, no judgment</div> | |
| <div>L5 <Tag tone="cyan">Subagents</Tag> — context isolation</div> | |
| <div>L6 <Tag tone="emerald">Verifiers</Tag> — how we KNOW it works</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The five surfaces</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>🧠 Context · 🔧 Action · 🛡️ Control · 📦 Isolation · ✅ Verification</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The five session controls</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>continue · rewind · /clear · /compact · subagent</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The five control patterns</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Chaining · Routing · Parallelization · Orchestrator-Workers · Evaluator-Optimizer</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The four memory types</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Working · Procedural · Episodic · Semantic</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The four harness pieces</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Acceptance · Boundaries · Feedback · Fallback</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Useful slash commands</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div><InlineCode>/context</InlineCode> — see what's loaded</div> | |
| <div><InlineCode>/clear</InlineCode> — wipe conversation, keep config</div> | |
| <div><InlineCode>/compact</InlineCode> — summarize and continue</div> | |
| <div><InlineCode>#</InlineCode> — append current chat to CLAUDE.md</div> | |
| <div><InlineCode>Shift+Tab x2</InlineCode> — toggle Plan Mode</div> | |
| <div><InlineCode>ESC x2</InlineCode> — rewind</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Diagnosis order</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>1. Tool descriptions · 2. Task state externalization · 3. Eval correctness · 4. Workflow vs agent · 5. Prompt/model</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Cache busters to avoid</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Timestamps in system · shuffled tool order · adding/removing tools mid-session · model swap mid-session</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Three skill archetypes</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Checklist (gates) · Workflow (procedures) · Domain Expert (decision frameworks)</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Three verifier tiers</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Lowest (lint/types/tests) · Middle (integration/screenshot) · Higher (prod metrics/manual)</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">Two eval metrics</h3> | |
| <div className="text-sm text-zinc-300 space-y-1 ml-2"> | |
| <div>Pass@k (capability ceiling) · Pass^k (regression)</div> | |
| </div> | |
| <h3 className="text-xl font-bold text-amber-400 mt-6 mb-3">The one rule that matters</h3> | |
| <Box1 tone="violet"> | |
| If you can't clearly describe what "done" looks like, the task isn't ready for autonomous execution. Define acceptance criteria <em>before</em> writing the prompt. | |
| </Box1> | |
| </Ch>); | |
| const Ch28 = () => (<Ch> | |
| <p>Three stages mark the journey from beginner to mastery — and the gap between them is huge:</p> | |
| <div className="my-6 space-y-3"> | |
| <div className="bg-zinc-900/50 border-l-4 border-zinc-600 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-zinc-400 text-xs font-bold tracking-widest">STAGE 1</div> | |
| <div className="text-zinc-200 font-bold">Tool User</div> | |
| <div className="text-zinc-400 text-sm">"How do I use this feature?" — helpful but limited.</div> | |
| </div> | |
| <div className="bg-amber-500/5 border-l-4 border-amber-500 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-amber-400 text-xs font-bold tracking-widest">STAGE 2</div> | |
| <div className="text-zinc-200 font-bold">Process Optimizer</div> | |
| <div className="text-zinc-400 text-sm">"How do I make collaboration smoother?" — intentional CLAUDE.md and skills. Significant lift.</div> | |
| </div> | |
| <div className="bg-violet-500/10 border-l-4 border-violet-500 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-violet-400 text-xs font-bold tracking-widest">STAGE 3</div> | |
| <div className="text-zinc-200 font-bold">System Designer</div> | |
| <div className="text-zinc-400 text-sm">"How do I make the agent operate autonomously under constraints?" — qualitative change in capability.</div> | |
| </div> | |
| </div> | |
| <Insight> | |
| The single test that matters: <em>if you can't clearly articulate what "done" looks like, the task isn't ready for autonomous execution.</em> No acceptance criteria → no notion of correctness → no agent will save you, no matter how capable the model is. | |
| </Insight> | |
| <h3 className="text-xl font-bold text-emerald-400 mt-8 mb-3">The 10 take-home rules</h3> | |
| <Box1 tone="emerald"> | |
| <ol className="space-y-2 text-sm list-decimal list-inside text-zinc-300"> | |
| <li>The loop never changes. Add capabilities by layering, not by rewriting.</li> | |
| <li>The harness matters more than the model. Acceptance baselines, boundaries, feedback, fallback.</li> | |
| <li>Context engineering is about noise, not capacity. Layer information by stability and access frequency.</li> | |
| <li>Tools are designed for agents, not APIs for humans. ACI principles. Give Claude a dedicated tool when you want a behavior.</li> | |
| <li>Memory is four things, not one. Don't conflate them.</li> | |
| <li>Long tasks externalize state to files. The filesystem is your friend.</li> | |
| <li>Don't multi-agent until you've genuinely hit the single-agent ceiling. Protocols → isolation → collaboration.</li> | |
| <li>Pass@k tests boundaries. Pass^k tests regression. Mixing them lies to you.</li> | |
| <li>Fix the eval before tuning the agent.</li> | |
| <li>Encode constraints, don't document them. Linters and hooks beat README files.</li> | |
| </ol> | |
| </Box1> | |
| <h3 className="text-xl font-bold text-emerald-400 mt-8 mb-3">Where to go from here</h3> | |
| <p>If you've made it this far, you have the framework. The next thing isn't more reading — it's applying. Pick one project. Write the CLAUDE.md you wish you had. Add one skill, one hook, one subagent. Watch what happens. You'll learn more in two weeks of doing than two months of reading.</p> | |
| <p>And keep an eye on the field. The patterns in this book are stable, but the tools change quickly. The articles linked below are themselves only a few months old. The fundamentals — loop, layers, harness, context — will still be true in five years. The specific commands and config formats might not.</p> | |
| <Quote> | |
| The bottleneck on agent capability today is rarely the model. It's the system around the model. That system is mostly engineering, not AI — and it's something you already know how to build. | |
| </Quote> | |
| <p>Go build something.</p> | |
| <div className="mt-8 p-5 bg-gradient-to-br from-amber-500/10 via-violet-500/10 to-cyan-500/10 border border-zinc-700 rounded-xl text-center"> | |
| <div className="text-zinc-300 text-sm mb-2">Original sources distilled here:</div> | |
| <div className="text-amber-400 text-xs mt-2 font-mono">tw93.fun/en/2026-03-12/claude.html</div> | |
| <div className="text-violet-400 text-xs font-mono">tw93.fun/en/2026-03-21/agent.html</div> | |
| <div className="text-zinc-500 text-xs mt-3">Both worth a re-read once you've internalized the framing here.</div> | |
| </div> | |
| </Ch>); | |
| // ============================================================================ | |
| // CHAPTER REGISTRY & APP | |
| // ============================================================================ | |
| const CHAPTERS = [ | |
| { id: 0, part: 1, title: "Welcome", subtitle: "Why this book exists", icon: BookOpen, comp: Ch01 }, | |
| { id: 1, part: 1, title: "The Big Picture", subtitle: "Six layers and five surfaces", icon: Layers, comp: Ch02 }, | |
| { id: 2, part: 1, title: "The Agent Loop", subtitle: "20 lines of code, infinite range", icon: Repeat, comp: Ch03 }, | |
| { id: 3, part: 1, title: "Workflows vs Agents", subtitle: "Plus the 5 control patterns", icon: GitBranch, comp: Ch04 }, | |
| { id: 4, part: 1, title: "The Harness", subtitle: "Why scaffolding beats the model", icon: Shield, comp: Ch05 }, | |
| { id: 5, part: 2, title: "Context Engineering", subtitle: "Where your tokens really go", icon: Database, comp: Ch06 }, | |
| { id: 6, part: 2, title: "CLAUDE.md", subtitle: "The collaboration contract", icon: FileText, comp: Ch07 }, | |
| { id: 7, part: 2, title: "The Compression Trap", subtitle: "And how to take back control", icon: Box, comp: Ch08 }, | |
| { id: 8, part: 2, title: "Plan Mode & Sessions", subtitle: "Five ways to handle a session", icon: Eye, comp: Ch09 }, | |
| { id: 9, part: 2, title: "Memory Systems", subtitle: "Four types, four solutions", icon: Brain, comp: Ch10 }, | |
| { id: 10, part: 3, title: "Skills", subtitle: "On-demand workflow packages", icon: Sparkles, comp: Ch11 }, | |
| { id: 11, part: 3, title: "Tool Design", subtitle: "Three generations + ACI", icon: Wrench, comp: Ch12 }, | |
| { id: 12, part: 3, title: "MCP Servers", subtitle: "Power and pitfalls", icon: Cloud, comp: Ch13 }, | |
| { id: 13, part: 3, title: "Hooks", subtitle: "Deterministic guardrails", icon: Zap, comp: Ch14 }, | |
| { id: 14, part: 3, title: "Subagents", subtitle: "Isolation, not parallelism", icon: Users, comp: Ch15 }, | |
| { id: 15, part: 3, title: "Permissions & Sandboxing", subtitle: "The control surface", icon: Lock, comp: Ch16 }, | |
| { id: 16, part: 3, title: "Prompt Caching", subtitle: "The hidden architectural force", icon: Repeat, comp: Ch17 }, | |
| { id: 17, part: 4, title: "Single → Multi-Agent", subtitle: "When to scale up", icon: Network, comp: Ch18 }, | |
| { id: 18, part: 4, title: "Coordination Protocols", subtitle: "JSONL inboxes and task graphs", icon: Hash, comp: Ch19 }, | |
| { id: 19, part: 4, title: "Worktrees & Isolation", subtitle: "Filesystem-level safety", icon: Folder, comp: Ch20 }, | |
| { id: 20, part: 5, title: "Verification Loops", subtitle: "Trust but verify", icon: FileCheck, comp: Ch21 }, | |
| { id: 21, part: 5, title: "Evaluation Systems", subtitle: "Fix the eval before the agent", icon: Activity, comp: Ch22 }, | |
| { id: 22, part: 5, title: "Cost & Performance", subtitle: "Cache hit rate is everything", icon: DollarSign, comp: Ch23 }, | |
| { id: 23, part: 5, title: "Debugging", subtitle: "The diagnosis ladder", icon: Bug, comp: Ch24 }, | |
| { id: 24, part: 5, title: "Case Studies", subtitle: "Codex, AskUserQuestion, Cursor", icon: Star, comp: Ch25 }, | |
| { id: 25, part: 6, title: "Anti-Patterns", subtitle: "The greatest hits of failure", icon: AlertTriangle, comp: Ch26 }, | |
| { id: 26, part: 6, title: "Quick Reference", subtitle: "The cheat sheet", icon: MapIcon, comp: Ch27 }, | |
| { id: 27, part: 6, title: "Path to Mastery", subtitle: "The 10 take-home rules", icon: Award, comp: Ch28 }, | |
| ]; | |
| const PART_INFO = { | |
| 1: { name: "FOUNDATIONS", c: "text-amber-400", bg: "from-amber-500 to-orange-500", desc: "Mental models & core concepts" }, | |
| 2: { name: "CONTEXT ENGINEERING", c: "text-orange-400", bg: "from-orange-500 to-rose-500", desc: "Where your tokens really go" }, | |
| 3: { name: "THE LAYERS", c: "text-violet-400", bg: "from-violet-500 to-purple-500", desc: "Skills, tools, hooks, subagents" }, | |
| 4: { name: "MULTI-AGENT", c: "text-pink-400", bg: "from-pink-500 to-rose-500", desc: "Coordination > parallelism" }, | |
| 5: { name: "PRODUCTION", c: "text-cyan-400", bg: "from-cyan-500 to-sky-500", desc: "Verification, cost, debugging" }, | |
| 6: { name: "REFERENCE", c: "text-emerald-400", bg: "from-emerald-500 to-teal-500", desc: "Anti-patterns & take-home rules" }, | |
| }; | |
| function App() { | |
| const [ch, setCh] = useState(-1); | |
| const [navOpen, setNavOpen] = useState(false); | |
| useEffect(() => { | |
| const el = document.getElementById('loader'); | |
| if (el) el.classList.add('hide'); | |
| }, []); | |
| // Re-run Prism on chapter change (in case any code blocks need it) | |
| useEffect(() => { | |
| if (window.Prism) setTimeout(() => window.Prism.highlightAll(), 50); | |
| window.scrollTo({ top: 0, behavior: 'instant' }); | |
| }, [ch]); | |
| // Keyboard shortcuts | |
| useEffect(() => { | |
| const handler = (e) => { | |
| if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; | |
| if (ch < 0) return; | |
| if (e.key === 'j' || e.key === 'ArrowRight') { | |
| if (ch < CHAPTERS.length - 1) setCh(ch + 1); | |
| } else if (e.key === 'k' || e.key === 'ArrowLeft') { | |
| if (ch > 0) setCh(ch - 1); | |
| } else if (e.key === 'Escape') { | |
| setCh(-1); | |
| } | |
| }; | |
| window.addEventListener('keydown', handler); | |
| return () => window.removeEventListener('keydown', handler); | |
| }, [ch]); | |
| const cur = ch >= 0 ? CHAPTERS[ch] : null; | |
| const partInfo = cur ? PART_INFO[cur.part] : null; | |
| const Home = () => ( | |
| <div className="max-w-5xl mx-auto"> | |
| <div className="text-center pt-12 pb-8"> | |
| <div className="inline-block mb-4 px-3 py-1 rounded-full border border-zinc-700 text-zinc-400 text-xs tracking-widest">A FIELD GUIDE — BOOK EDITION</div> | |
| <h1 className="text-5xl md:text-7xl font-black tracking-tight"> | |
| <span className="bg-gradient-to-r from-amber-400 via-rose-400 to-violet-400 bg-clip-text text-transparent">Claude Code</span> | |
| <br /> | |
| <span className="text-zinc-200">& Agents</span> | |
| </h1> | |
| <p className="text-zinc-400 mt-6 max-w-2xl mx-auto"> | |
| A book-length, opinionated walkthrough of how Claude Code and agentic systems actually work — distilled from the Tw93 articles, expanded into a guide your team can read together. | |
| </p> | |
| <div className="flex flex-wrap gap-3 justify-center mt-8"> | |
| <button onClick={() => setCh(0)} className="px-6 py-3 bg-gradient-to-r from-amber-500 to-violet-500 rounded-lg text-white font-bold hover:scale-105 transition shadow-lg shadow-violet-500/20"> | |
| Start reading → | |
| </button> | |
| <button onClick={() => setCh(26)} className="px-6 py-3 bg-zinc-800 hover:bg-zinc-700 rounded-lg text-zinc-200 font-semibold transition"> | |
| Jump to cheat sheet | |
| </button> | |
| </div> | |
| <div className="text-xs text-zinc-600 mt-4">28 chapters · 6 parts · ~2 hours · keyboard: j / k / Esc</div> | |
| </div> | |
| <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-3 mt-12"> | |
| {[1, 2, 3, 4, 5, 6].map((p) => { | |
| const chs = CHAPTERS.filter((c) => c.part === p); | |
| const info = PART_INFO[p]; | |
| return ( | |
| <div key={p} className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-5 hover:border-zinc-700 transition"> | |
| <div className={`text-xs font-bold tracking-widest ${info.c}`}>PART {p} · {info.name}</div> | |
| <div className="text-zinc-500 text-xs mb-3">{info.desc}</div> | |
| <div className="text-zinc-300 text-sm space-y-1"> | |
| {chs.map((c) => ( | |
| <button key={c.id} onClick={() => setCh(c.id)} className="block text-left hover:text-zinc-100 hover:translate-x-1 transition"> | |
| <span className="text-zinc-600 font-mono text-xs mr-2">{String(c.id + 1).padStart(2, '0')}</span> | |
| {c.title} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| <div className="mt-10 mb-6 grid md:grid-cols-3 gap-3"> | |
| <div className="bg-zinc-900/30 border border-zinc-800 rounded-xl p-5"> | |
| <div className="text-amber-400 font-bold text-sm mb-1">📖 Read it through</div> | |
| <div className="text-zinc-400 text-xs">Each chapter builds on the last. Two hours straight through.</div> | |
| </div> | |
| <div className="bg-zinc-900/30 border border-zinc-800 rounded-xl p-5"> | |
| <div className="text-violet-400 font-bold text-sm mb-1">🎯 Use it as reference</div> | |
| <div className="text-zinc-400 text-xs">Chapters are standalone. Bounce around by topic via the sidebar.</div> | |
| </div> | |
| <div className="bg-zinc-900/30 border border-zinc-800 rounded-xl p-5"> | |
| <div className="text-cyan-400 font-bold text-sm mb-1">👥 Share with your team</div> | |
| <div className="text-zinc-400 text-xs">It's one HTML file. Email it. Drop it in a Slack message. No deps.</div> | |
| </div> | |
| </div> | |
| <div className="mt-6 mb-12 p-5 bg-gradient-to-br from-amber-500/5 via-violet-500/5 to-cyan-500/5 border border-zinc-800 rounded-xl"> | |
| <div className="text-zinc-400 text-sm"> | |
| <span className="text-zinc-200 font-semibold">Source material:</span> distilled from two articles by Tw93 — <span className="font-mono text-amber-400">tw93.fun/en/2026-03-12/claude.html</span> and <span className="font-mono text-violet-400">tw93.fun/en/2026-03-21/agent.html</span>. Both worth re-reading once you've internalized the framing here. | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| return ( | |
| <div className="min-h-screen bg-zinc-950 text-zinc-100 antialiased"> | |
| <div className="sticky top-0 z-30 bg-zinc-950/90 backdrop-blur-md border-b border-zinc-800"> | |
| <div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between"> | |
| <button onClick={() => setCh(-1)} className="flex items-center gap-2 hover:opacity-80 transition"> | |
| <div className="w-7 h-7 rounded-md bg-gradient-to-br from-amber-400 to-violet-500" /> | |
| <div className="font-bold text-sm tracking-tight">The Field Guide · Book</div> | |
| </button> | |
| {ch >= 0 && ( | |
| <> | |
| <div className="hidden md:flex items-center gap-1 text-xs text-zinc-500"> | |
| <span>Chapter {ch + 1} / {CHAPTERS.length}</span> | |
| <span className="mx-2">·</span> | |
| <span className={partInfo.c}>{partInfo.name}</span> | |
| </div> | |
| <button onClick={() => setNavOpen(!navOpen)} className="md:hidden p-2 text-zinc-400 hover:text-zinc-100"> | |
| {navOpen ? <X size={18} /> : <Menu size={18} />} | |
| </button> | |
| </> | |
| )} | |
| {ch < 0 && <div className="text-xs text-zinc-500 hidden sm:block">distilled from tw93.fun</div>} | |
| </div> | |
| {ch >= 0 && ( | |
| <div className="h-0.5 bg-zinc-800"> | |
| <div className={`h-full bg-gradient-to-r ${partInfo.bg} transition-all duration-500`} style={{ width: `${((ch + 1) / CHAPTERS.length) * 100}%` }} /> | |
| </div> | |
| )} | |
| </div> | |
| {ch < 0 ? ( | |
| <div className="px-4 pb-16"><Home /></div> | |
| ) : ( | |
| <div className="max-w-7xl mx-auto px-4 flex gap-8"> | |
| <aside className={`${navOpen ? "block" : "hidden"} md:block fixed md:sticky top-20 md:top-16 left-0 right-0 md:right-auto z-20 md:z-0 bg-zinc-950 md:bg-transparent w-full md:w-64 md:flex-shrink-0 h-screen md:h-[calc(100vh-4rem)] overflow-y-auto p-4 md:p-0 md:py-6 border-r md:border-0 border-zinc-800`}> | |
| {[1, 2, 3, 4, 5, 6].map((p) => { | |
| const chs = CHAPTERS.filter((c) => c.part === p); | |
| const info = PART_INFO[p]; | |
| return ( | |
| <div key={p} className="mb-6"> | |
| <div className={`text-[10px] font-bold tracking-widest mb-2 ${info.c}`}>{info.name}</div> | |
| <div className="space-y-0.5"> | |
| {chs.map((c) => { | |
| const Ic = c.icon; | |
| const active = c.id === ch; | |
| return ( | |
| <button key={c.id} onClick={() => { setCh(c.id); setNavOpen(false); }} | |
| className={`w-full text-left flex items-center gap-2 px-2 py-1.5 rounded text-xs transition ${active ? "bg-zinc-800 text-zinc-100" : "text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900"}`}> | |
| <Ic size={12} className={active ? info.c : ""} /> | |
| <span className="flex-1 truncate">{c.title}</span> | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| <div className="mt-8 px-2 text-[10px] text-zinc-600"> | |
| Keyboard:<br /> | |
| <span className="font-mono">j</span> next · <span className="font-mono">k</span> prev · <span className="font-mono">Esc</span> home | |
| </div> | |
| </aside> | |
| <main className="flex-1 min-w-0 py-6 md:py-12 max-w-3xl"> | |
| <div className="mb-8 fade-in" key={ch}> | |
| <div className={`text-xs font-bold tracking-widest mb-2 ${partInfo.c}`}> | |
| CHAPTER {String(ch + 1).padStart(2, "0")} · {partInfo.name} | |
| </div> | |
| <h2 className="text-3xl md:text-5xl font-black tracking-tight text-zinc-100"> | |
| {cur.title} | |
| </h2> | |
| <p className="text-zinc-400 mt-2 text-lg">{cur.subtitle}</p> | |
| </div> | |
| <div className="prose prose-invert max-w-none fade-in" key={`body-${ch}`}> | |
| <cur.comp /> | |
| </div> | |
| <div className="mt-12 pt-6 border-t border-zinc-800 flex items-center justify-between gap-3"> | |
| {ch > 0 ? ( | |
| <button onClick={() => setCh(ch - 1)} className="flex items-center gap-2 px-4 py-3 rounded-lg bg-zinc-900 hover:bg-zinc-800 text-zinc-300 transition flex-1 group"> | |
| <ChevronLeft size={18} className="group-hover:-translate-x-1 transition" /> | |
| <div className="text-left"> | |
| <div className="text-[10px] text-zinc-500 tracking-widest">PREVIOUS</div> | |
| <div className="text-sm font-semibold">{CHAPTERS[ch - 1].title}</div> | |
| </div> | |
| </button> | |
| ) : <div className="flex-1" />} | |
| {ch < CHAPTERS.length - 1 ? ( | |
| <button onClick={() => setCh(ch + 1)} className="flex items-center gap-2 px-4 py-3 rounded-lg bg-gradient-to-r from-amber-500/20 to-violet-500/20 border border-zinc-700 hover:border-zinc-500 text-zinc-100 transition flex-1 group justify-end"> | |
| <div className="text-right"> | |
| <div className="text-[10px] text-zinc-500 tracking-widest">NEXT</div> | |
| <div className="text-sm font-semibold">{CHAPTERS[ch + 1].title}</div> | |
| </div> | |
| <ChevronRight size={18} className="group-hover:translate-x-1 transition" /> | |
| </button> | |
| ) : ( | |
| <button onClick={() => setCh(-1)} className="flex items-center gap-2 px-4 py-3 rounded-lg bg-gradient-to-r from-amber-500 to-violet-500 text-white transition flex-1 group justify-end font-semibold"> | |
| <span>Back to start</span> | |
| <ChevronRight size={18} className="group-hover:translate-x-1 transition" /> | |
| </button> | |
| )} | |
| </div> | |
| </main> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| ReactDOM.createRoot(document.getElementById('root')).render(<App />); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>The Claude Code & Agents Field Guide</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.5/babel.min.js"></script> | |
| <style> | |
| body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; background: #09090b; } | |
| /* Loading screen */ | |
| #loader { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: #09090b; color: #71717a; font-size: 13px; z-index: 100; } | |
| #loader.hide { display: none; } | |
| </style> | |
| </head> | |
| <body class="bg-zinc-950"> | |
| <div id="loader">Loading the field guide…</div> | |
| <div id="root"></div> | |
| <script type="text/babel" data-presets="react"> | |
| const { useState, useEffect } = React; | |
| // ===== Inline SVG icon helpers (replacing lucide-react) ===== | |
| const mk = (children) => ({ size = 16, className = "" }) => ( | |
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}> | |
| {children} | |
| </svg> | |
| ); | |
| const ChevronLeft = mk(<polyline points="15 18 9 12 15 6"/>); | |
| const ChevronRight = mk(<polyline points="9 18 15 12 9 6"/>); | |
| const Menu = mk(<><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></>); | |
| const X = mk(<><path d="M18 6 6 18"/><path d="m6 6 12 12"/></>); | |
| const Layers = mk(<><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></>); | |
| const Repeat = mk(<><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></>); | |
| const GitBranch = mk(<><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></>); | |
| const Shield = mk(<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>); | |
| const Database = mk(<><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></>); | |
| const Wrench = mk(<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>); | |
| const Zap = mk(<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>); | |
| const Users = mk(<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>); | |
| const Box = mk(<><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></>); | |
| const Brain = mk(<><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/></>); | |
| const FileCheck = mk(<><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><polyline points="9 15 11 17 15 13"/></>); | |
| const BookOpen = mk(<><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></>); | |
| const Network = mk(<><rect x="16" y="16" width="6" height="6" rx="1"/><rect x="2" y="16" width="6" height="6" rx="1"/><rect x="9" y="2" width="6" height="6" rx="1"/><path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3"/><path d="M12 12V8"/></>); | |
| const AlertTriangle = mk(<><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></>); | |
| const Sparkles = mk(<><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></>); | |
| const Check = mk(<polyline points="20 6 9 17 4 12"/>); | |
| const XCircle = mk(<><circle cx="12" cy="12" r="10"/><line x1="15" x2="9" y1="9" y2="15"/><line x1="9" x2="15" y1="9" y2="15"/></>); | |
| // ===== Reusable bits ===== | |
| const Box1 = ({ tone = "amber", title, children, icon: Ic }) => { | |
| const tones = { | |
| amber: "from-amber-500/10 to-orange-500/5 border-amber-500/30", | |
| violet: "from-violet-500/10 to-purple-500/5 border-violet-500/30", | |
| emerald: "from-emerald-500/10 to-teal-500/5 border-emerald-500/30", | |
| rose: "from-rose-500/10 to-pink-500/5 border-rose-500/30", | |
| cyan: "from-cyan-500/10 to-sky-500/5 border-cyan-500/30", | |
| zinc: "from-zinc-500/10 to-zinc-500/5 border-zinc-700", | |
| }; | |
| const titleTones = { | |
| amber: "text-amber-400", violet: "text-violet-400", emerald: "text-emerald-400", | |
| rose: "text-rose-400", cyan: "text-cyan-400", zinc: "text-zinc-300", | |
| }; | |
| return ( | |
| <div className={`bg-gradient-to-br ${tones[tone]} border rounded-xl p-5 my-4`}> | |
| {title && ( | |
| <div className={`flex items-center gap-2 font-semibold mb-2 ${titleTones[tone]}`}> | |
| {Ic && <Ic size={16} />} | |
| {title} | |
| </div> | |
| )} | |
| <div className="text-zinc-300 text-sm leading-relaxed">{children}</div> | |
| </div> | |
| ); | |
| }; | |
| const Tag = ({ tone, children }) => { | |
| const c = { | |
| amber: "bg-amber-500/15 text-amber-300 border-amber-500/30", | |
| violet: "bg-violet-500/15 text-violet-300 border-violet-500/30", | |
| emerald: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30", | |
| rose: "bg-rose-500/15 text-rose-300 border-rose-500/30", | |
| cyan: "bg-cyan-500/15 text-cyan-300 border-cyan-500/30", | |
| }; | |
| return <span className={`inline-block text-xs px-2 py-0.5 rounded-full border ${c[tone]} font-medium`}>{children}</span>; | |
| }; | |
| const Pill = ({ children, tone = "zinc" }) => { | |
| const c = { | |
| zinc: "bg-zinc-800 text-zinc-300", | |
| good: "bg-emerald-500/20 text-emerald-300", | |
| bad: "bg-rose-500/20 text-rose-300", | |
| }; | |
| return <span className={`text-xs font-mono px-2 py-0.5 rounded ${c[tone]}`}>{children}</span>; | |
| }; | |
| const Code = ({ children }) => ( | |
| <div className="bg-black/60 border border-zinc-800 rounded-lg p-4 my-3 font-mono text-xs text-zinc-300 overflow-x-auto whitespace-pre"> | |
| {children} | |
| </div> | |
| ); | |
| const Insight = ({ children }) => ( | |
| <div className="border-l-4 border-cyan-400 bg-cyan-500/5 pl-4 py-3 my-4 rounded-r-lg"> | |
| <div className="text-cyan-400 text-xs font-bold tracking-widest mb-1">KEY INSIGHT</div> | |
| <div className="text-zinc-200 text-sm leading-relaxed">{children}</div> | |
| </div> | |
| ); | |
| const Compare = ({ good, bad }) => ( | |
| <div className="grid md:grid-cols-2 gap-3 my-4"> | |
| <div className="bg-emerald-500/5 border border-emerald-500/30 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-emerald-400 font-semibold text-sm mb-2"><Check size={14} /> Do this</div> | |
| <div className="text-zinc-300 text-sm">{good}</div> | |
| </div> | |
| <div className="bg-rose-500/5 border border-rose-500/30 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 text-rose-400 font-semibold text-sm mb-2"><XCircle size={14} /> Not this</div> | |
| <div className="text-zinc-300 text-sm">{bad}</div> | |
| </div> | |
| </div> | |
| ); | |
| // ===== Diagrams ===== | |
| const SixLayerDiagram = () => { | |
| const layers = [ | |
| { n: "Verifiers", d: "Validation that's testable & auditable", c: "#34d399" }, | |
| { n: "Subagents", d: "Context-isolated workers", c: "#22d3ee" }, | |
| { n: "Hooks", d: "Enforced behaviors, no model judgment", c: "#a78bfa" }, | |
| { n: "Skills", d: "On-demand methodologies", c: "#f472b6" }, | |
| { n: "Tools / MCP", d: "What Claude can do", c: "#fb923c" }, | |
| { n: "CLAUDE.md / rules / memory", d: "What this project is", c: "#fbbf24" }, | |
| ]; | |
| return ( | |
| <div className="my-6 max-w-md mx-auto"> | |
| {layers.map((l, i) => ( | |
| <div key={i} className="relative" style={{ marginLeft: `${i * 8}px` }}> | |
| <div | |
| className="rounded-lg p-3 mb-1 border-2 flex items-center justify-between transition hover:translate-x-1" | |
| style={{ background: `${l.c}18`, borderColor: `${l.c}66` }} | |
| > | |
| <div> | |
| <div className="font-bold text-sm" style={{ color: l.c }}>{l.n}</div> | |
| <div className="text-xs text-zinc-400">{l.d}</div> | |
| </div> | |
| <div className="text-zinc-600 text-xs font-mono">L{6 - i}</div> | |
| </div> | |
| </div> | |
| ))} | |
| <div className="text-center text-zinc-500 text-xs mt-2">↑ each layer builds on the ones below</div> | |
| </div> | |
| ); | |
| }; | |
| const AgentLoopDiagram = () => { | |
| const cx = 200, cy = 130, r = 90; | |
| const stages = [ | |
| { a: -90, t: "Perceive", c: "#fbbf24", d: "Gather context" }, | |
| { a: 0, t: "Decide", c: "#fb923c", d: "Pick next action" }, | |
| { a: 90, t: "Act", c: "#a78bfa", d: "Use a tool" }, | |
| { a: 180, t: "Verify", c: "#34d399", d: "Did it work?" }, | |
| ]; | |
| return ( | |
| <div className="my-6 flex justify-center"> | |
| <svg viewBox="0 0 400 280" className="w-full max-w-md"> | |
| <defs> | |
| <radialGradient id="glow" cx="50%" cy="50%"> | |
| <stop offset="0%" stopColor="#a78bfa" stopOpacity="0.4" /> | |
| <stop offset="100%" stopColor="#a78bfa" stopOpacity="0" /> | |
| </radialGradient> | |
| </defs> | |
| <circle cx={cx} cy={cy} r={r + 30} fill="url(#glow)" /> | |
| <circle cx={cx} cy={cy} r={r} fill="none" stroke="#52525b" strokeDasharray="3 4" /> | |
| {stages.map((s, i) => { | |
| const rad = (s.a * Math.PI) / 180; | |
| const x = cx + r * Math.cos(rad); | |
| const y = cy + r * Math.sin(rad); | |
| return ( | |
| <g key={i}> | |
| <circle cx={x} cy={y} r="32" fill={`${s.c}20`} stroke={s.c} strokeWidth="2" /> | |
| <text x={x} y={y - 2} textAnchor="middle" fill={s.c} fontSize="11" fontWeight="bold">{s.t}</text> | |
| <text x={x} y={y + 11} textAnchor="middle" fill="#a1a1aa" fontSize="8">{s.d}</text> | |
| </g> | |
| ); | |
| })} | |
| <marker id="ar" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> | |
| <path d="M0,0 L6,4 L0,8" fill="#71717a" /> | |
| </marker> | |
| {[0, 1, 2, 3].map((i) => { | |
| const a1 = (stages[i].a * Math.PI) / 180; | |
| const a2 = (stages[(i + 1) % 4].a * Math.PI) / 180; | |
| const x1 = cx + (r - 32) * Math.cos(a1 + 0.4); | |
| const y1 = cy + (r - 32) * Math.sin(a1 + 0.4); | |
| const x2 = cx + (r - 32) * Math.cos(a2 - 0.4); | |
| const y2 = cy + (r - 32) * Math.sin(a2 - 0.4); | |
| return <path key={i} d={`M${x1} ${y1} Q${cx} ${cy} ${x2} ${y2}`} fill="none" stroke="#71717a" strokeWidth="1.5" markerEnd="url(#ar)" />; | |
| })} | |
| <text x={cx} y={cy - 4} textAnchor="middle" fill="#e4e4e7" fontSize="11" fontWeight="bold">until</text> | |
| <text x={cx} y={cy + 9} textAnchor="middle" fill="#e4e4e7" fontSize="11" fontWeight="bold">done</text> | |
| </svg> | |
| </div> | |
| ); | |
| }; | |
| const ContextBarDiagram = () => ( | |
| <div className="my-6"> | |
| <div className="text-xs text-zinc-500 mb-2 flex justify-between"> | |
| <span>0K</span> | |
| <span className="text-zinc-300 font-bold">200K total context window</span> | |
| <span>200K</span> | |
| </div> | |
| <div className="flex h-14 rounded-lg overflow-hidden border border-zinc-700"> | |
| <div className="bg-rose-500/40 flex items-center justify-center text-rose-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "10%" }}> | |
| Fixed 15-20K | |
| </div> | |
| <div className="bg-amber-500/30 flex items-center justify-center text-amber-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "5%" }}> | |
| Semi 5-10K | |
| </div> | |
| <div className="bg-emerald-500/20 flex items-center justify-center text-emerald-200 text-xs font-semibold" style={{ width: "85%" }}> | |
| Dynamically available ~160-180K | |
| </div> | |
| </div> | |
| <div className="grid md:grid-cols-3 gap-2 mt-3 text-xs"> | |
| <div className="bg-rose-500/10 border border-rose-500/30 rounded p-2"> | |
| <div className="font-bold text-rose-400">Fixed overhead</div> | |
| <div className="text-zinc-400">System prompt · tool defs · skill descriptors · LSP. MCP tools alone can be 10-20K.</div> | |
| </div> | |
| <div className="bg-amber-500/10 border border-amber-500/30 rounded p-2"> | |
| <div className="font-bold text-amber-400">Semi-fixed</div> | |
| <div className="text-zinc-400">CLAUDE.md and persistent memory.</div> | |
| </div> | |
| <div className="bg-emerald-500/10 border border-emerald-500/30 rounded p-2"> | |
| <div className="font-bold text-emerald-400">Dynamic</div> | |
| <div className="text-zinc-400">Conversation, files read, tool outputs. Where the real work happens.</div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| const WorkflowVsAgent = () => ( | |
| <div className="my-6 grid md:grid-cols-2 gap-4"> | |
| <div className="bg-cyan-500/5 border border-cyan-500/30 rounded-xl p-4"> | |
| <div className="text-cyan-400 font-bold mb-3 flex items-center gap-2"><GitBranch size={16} /> Workflow</div> | |
| <svg viewBox="0 0 300 100" className="w-full"> | |
| <defs><marker id="aa1" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L6,4 L0,8" fill="#22d3ee" /></marker></defs> | |
| {[40, 110, 180, 250].map((x, i) => ( | |
| <g key={i}> | |
| <rect x={x - 18} y="40" width="36" height="20" rx="4" fill="#22d3ee20" stroke="#22d3ee" /> | |
| <text x={x} y="54" textAnchor="middle" fill="#22d3ee" fontSize="10">step {i + 1}</text> | |
| </g> | |
| ))} | |
| {[0, 1, 2].map((i) => <line key={i} x1={58 + i * 70} y1="50" x2={92 + i * 70} y2="50" stroke="#22d3ee" markerEnd="url(#aa1)" />)} | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2">A railway. Same input always rides the same track.</div> | |
| </div> | |
| <div className="bg-violet-500/5 border border-violet-500/30 rounded-xl p-4"> | |
| <div className="text-violet-400 font-bold mb-3 flex items-center gap-2"><Sparkles size={16} /> Agent</div> | |
| <svg viewBox="0 0 300 100" className="w-full"> | |
| <defs><marker id="aa2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L6,4 L0,8" fill="#a78bfa" /></marker></defs> | |
| <circle cx="40" cy="50" r="14" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <text x="40" y="54" textAnchor="middle" fill="#a78bfa" fontSize="10">start</text> | |
| <circle cx="120" cy="25" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="120" cy="75" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="25" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="50" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="200" cy="75" r="12" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <circle cx="270" cy="50" r="14" fill="#a78bfa20" stroke="#a78bfa" /> | |
| <text x="270" y="54" textAnchor="middle" fill="#a78bfa" fontSize="10">end</text> | |
| <line x1="54" y1="46" x2="106" y2="29" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="54" y1="54" x2="106" y2="71" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="25" x2="186" y2="25" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="33" x2="186" y2="46" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="132" y1="75" x2="186" y2="75" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="25" x2="256" y2="46" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="50" x2="256" y2="50" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| <line x1="212" y1="75" x2="256" y2="54" stroke="#a78bfa" markerEnd="url(#aa2)" /> | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2">A driver picking the route. Each turn the model decides.</div> | |
| </div> | |
| </div> | |
| ); | |
| const PatternIcons = () => { | |
| const ps = [ | |
| { n: "Prompt Chaining", d: "Linear: A → B → C", c: "#fbbf24", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><circle cx="60" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><circle cx="100" cy="40" r="10" fill="#fbbf2440" stroke="#fbbf24" /><line x1="30" y1="40" x2="50" y2="40" stroke="#fbbf24" /><line x1="70" y1="40" x2="90" y2="40" stroke="#fbbf24" /></> }, | |
| { n: "Routing", d: "Classify, then pick a path", c: "#fb923c", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#fb923c40" stroke="#fb923c" /><circle cx="80" cy="20" r="10" fill="#fb923c40" stroke="#fb923c" /><circle cx="80" cy="60" r="10" fill="#fb923c40" stroke="#fb923c" /><line x1="30" y1="38" x2="70" y2="22" stroke="#fb923c" /><line x1="30" y1="42" x2="70" y2="58" stroke="#fb923c" /></> }, | |
| { n: "Parallelization", d: "Run several at once", c: "#a78bfa", | |
| svg: <><circle cx="20" cy="40" r="10" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="15" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="40" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="70" cy="65" r="8" fill="#a78bfa40" stroke="#a78bfa" /><circle cx="110" cy="40" r="10" fill="#a78bfa40" stroke="#a78bfa" /></> }, | |
| { n: "Orchestrator-Workers", d: "Boss + delegated workers", c: "#22d3ee", | |
| svg: <><circle cx="60" cy="20" r="11" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="20" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="60" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><circle cx="100" cy="65" r="9" fill="#22d3ee40" stroke="#22d3ee" /><line x1="55" y1="30" x2="25" y2="56" stroke="#22d3ee" /><line x1="60" y1="31" x2="60" y2="56" stroke="#22d3ee" /><line x1="65" y1="30" x2="95" y2="56" stroke="#22d3ee" /></> }, | |
| { n: "Evaluator-Optimizer", d: "Generate → critique → loop", c: "#34d399", | |
| svg: <><circle cx="30" cy="40" r="11" fill="#34d39940" stroke="#34d399" /><circle cx="90" cy="40" r="11" fill="#34d39940" stroke="#34d399" /><path d="M40 35 Q60 15 80 35" fill="none" stroke="#34d399" /><path d="M40 45 Q60 65 80 45" fill="none" stroke="#34d399" /></> }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-3 gap-3 my-4"> | |
| {ps.map((p, i) => ( | |
| <div key={i} className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <svg viewBox="0 0 130 80" className="w-full h-16">{p.svg}</svg> | |
| <div className="font-bold text-sm" style={{ color: p.c }}>{p.n}</div> | |
| <div className="text-xs text-zinc-500">{p.d}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const HarnessQuadrant = () => ( | |
| <div className="my-6 max-w-md mx-auto"> | |
| <svg viewBox="0 0 320 280" className="w-full"> | |
| <line x1="40" y1="240" x2="290" y2="240" stroke="#52525b" strokeWidth="1.5" /> | |
| <line x1="40" y1="240" x2="40" y2="20" stroke="#52525b" strokeWidth="1.5" /> | |
| <text x="165" y="270" textAnchor="middle" fill="#a1a1aa" fontSize="10">→ goal clarity</text> | |
| <text x="20" y="130" textAnchor="middle" fill="#a1a1aa" fontSize="10" transform="rotate(-90 20 130)">→ verify automation</text> | |
| <rect x="45" y="125" width="115" height="110" fill="#71717a20" stroke="#52525b" rx="4" /> | |
| <text x="102" y="180" textAnchor="middle" fill="#a1a1aa" fontSize="9">vague +</text> | |
| <text x="102" y="192" textAnchor="middle" fill="#a1a1aa" fontSize="9">manual</text> | |
| <text x="102" y="208" textAnchor="middle" fill="#71717a" fontSize="8">nothing works</text> | |
| <rect x="165" y="125" width="120" height="110" fill="#fbbf2420" stroke="#fbbf24" rx="4" /> | |
| <text x="225" y="180" textAnchor="middle" fill="#fbbf24" fontSize="9">vague +</text> | |
| <text x="225" y="192" textAnchor="middle" fill="#fbbf24" fontSize="9">automatic</text> | |
| <text x="225" y="208" textAnchor="middle" fill="#fbbf24" fontSize="8">runs wrong fast</text> | |
| <rect x="45" y="25" width="115" height="95" fill="#22d3ee20" stroke="#22d3ee" rx="4" /> | |
| <text x="102" y="65" textAnchor="middle" fill="#22d3ee" fontSize="9">clear +</text> | |
| <text x="102" y="77" textAnchor="middle" fill="#22d3ee" fontSize="9">manual</text> | |
| <text x="102" y="93" textAnchor="middle" fill="#22d3ee" fontSize="8">capped by humans</text> | |
| <rect x="165" y="25" width="120" height="95" fill="#34d39930" stroke="#34d399" rx="4" /> | |
| <text x="225" y="60" textAnchor="middle" fill="#34d399" fontSize="11" fontWeight="bold">SWEET SPOT</text> | |
| <text x="225" y="78" textAnchor="middle" fill="#34d399" fontSize="9">clear + automatic</text> | |
| <text x="225" y="94" textAnchor="middle" fill="#34d399" fontSize="8">agents thrive here</text> | |
| </svg> | |
| <div className="text-zinc-400 text-xs mt-2 text-center">A harness pushes tasks into the top-right.</div> | |
| </div> | |
| ); | |
| const SkillsLoadDiagram = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 460 220" className="w-full max-w-2xl mx-auto"> | |
| <defs><marker id="sa" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0,0 L8,5 L0,10" fill="#a78bfa" /></marker></defs> | |
| <rect x="20" y="20" width="200" height="180" rx="8" fill="#fbbf2410" stroke="#fbbf24" /> | |
| <text x="120" y="40" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">System Prompt (always loaded)</text> | |
| <text x="120" y="60" textAnchor="middle" fill="#a1a1aa" fontSize="9">just the descriptors:</text> | |
| {["deploy: use when shipping", "review: use for PR checks", "migrate: use for schema changes"].map((t, i) => ( | |
| <g key={i}> | |
| <rect x="35" y={75 + i * 30} width="170" height="22" rx="3" fill="#fbbf2420" stroke="#fbbf2466" /> | |
| <text x="42" y={90 + i * 30} fill="#fde68a" fontSize="9" fontFamily="monospace">{t}</text> | |
| </g> | |
| ))} | |
| <text x="120" y="190" textAnchor="middle" fill="#71717a" fontSize="8">~20 tokens total</text> | |
| <path d="M225 135 Q260 135 290 135" stroke="#a78bfa" strokeWidth="2" strokeDasharray="4 3" fill="none" markerEnd="url(#sa)" /> | |
| <text x="255" y="125" textAnchor="middle" fill="#a78bfa" fontSize="9">on demand</text> | |
| <rect x="290" y="20" width="155" height="50" rx="6" fill="#a78bfa15" stroke="#a78bfa" strokeDasharray="3 2" /> | |
| <text x="367" y="40" textAnchor="middle" fill="#a78bfa" fontSize="10">deploy/SKILL.md</text> | |
| <text x="367" y="55" textAnchor="middle" fill="#71717a" fontSize="8">full body, only when triggered</text> | |
| <rect x="290" y="80" width="155" height="50" rx="6" fill="#a78bfa10" stroke="#a78bfa66" strokeDasharray="3 2" /> | |
| <text x="367" y="100" textAnchor="middle" fill="#a78bfa99" fontSize="10">review/SKILL.md</text> | |
| <text x="367" y="115" textAnchor="middle" fill="#71717a" fontSize="8">sleeping</text> | |
| <rect x="290" y="140" width="155" height="50" rx="6" fill="#a78bfa10" stroke="#a78bfa66" strokeDasharray="3 2" /> | |
| <text x="367" y="160" textAnchor="middle" fill="#a78bfa99" fontSize="10">migrate/SKILL.md</text> | |
| <text x="367" y="175" textAnchor="middle" fill="#71717a" fontSize="8">sleeping</text> | |
| </svg> | |
| </div> | |
| ); | |
| const ToolGenDiagram = () => { | |
| const gens = [ | |
| { n: "Gen 1", t: "API Wrappers", d: "One tool per endpoint. Forces the agent to chain 5 calls to do one thing.", c: "#f87171" }, | |
| { n: "Gen 2", t: "ACI", d: "One tool per agent goal. update_yuque_post(id, title, md) in one shot.", c: "#fbbf24" }, | |
| { n: "Gen 3", t: "Advanced", d: "Search tools on demand. Programmatic calling. Examples per tool.", c: "#34d399" }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-3 gap-3 my-6"> | |
| {gens.map((g, i) => ( | |
| <div key={i} className="relative bg-zinc-900/50 border-2 rounded-xl p-4" style={{ borderColor: `${g.c}66` }}> | |
| <div className="text-xs font-bold tracking-widest mb-1" style={{ color: g.c }}>{g.n}</div> | |
| <div className="font-bold text-zinc-100 mb-2">{g.t}</div> | |
| <div className="text-zinc-400 text-xs">{g.d}</div> | |
| {i < 2 && <div className="hidden md:block absolute -right-3 top-1/2 text-zinc-600">→</div>} | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const HookTimeline = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 500 130" className="w-full max-w-2xl mx-auto"> | |
| <line x1="20" y1="65" x2="480" y2="65" stroke="#52525b" strokeWidth="2" /> | |
| {[ | |
| { x: 60, n: "SessionStart", d: "inject env" }, | |
| { x: 170, n: "PreToolUse", d: "block / approve" }, | |
| { x: 280, n: "PostToolUse", d: "format / lint" }, | |
| { x: 390, n: "Notification", d: "ping you" }, | |
| { x: 470, n: "Stop", d: "" }, | |
| ].map((s, i) => ( | |
| <g key={i}> | |
| <circle cx={s.x} cy="65" r="6" fill="#a78bfa" stroke="#1e1b4b" strokeWidth="2" /> | |
| <text x={s.x} y="40" textAnchor="middle" fill="#a78bfa" fontSize="10" fontWeight="bold">{s.n}</text> | |
| <text x={s.x} y="92" textAnchor="middle" fill="#a1a1aa" fontSize="8">{s.d}</text> | |
| </g> | |
| ))} | |
| <text x="20" y="115" fill="#71717a" fontSize="9">session begins</text> | |
| <text x="480" y="115" textAnchor="end" fill="#71717a" fontSize="9">session ends</text> | |
| </svg> | |
| </div> | |
| ); | |
| const SubagentDiagram = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 460 240" className="w-full max-w-xl mx-auto"> | |
| <rect x="170" y="20" width="120" height="50" rx="8" fill="#fbbf2420" stroke="#fbbf24" strokeWidth="2" /> | |
| <text x="230" y="42" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">Main Agent</text> | |
| <text x="230" y="58" textAnchor="middle" fill="#a1a1aa" fontSize="9">full context, all tools</text> | |
| {[ | |
| { x: 60, c: "#22d3ee", n: "Explore", d: "read-only scan", s: "Haiku" }, | |
| { x: 200, c: "#a78bfa", n: "Review", d: "scoped review", s: "Opus" }, | |
| { x: 340, c: "#34d399", n: "Test runner", d: "test only", s: "Sonnet" }, | |
| ].map((s, i) => ( | |
| <g key={i}> | |
| <rect x={s.x} y="140" width="100" height="80" rx="8" fill={`${s.c}15`} stroke={s.c} strokeDasharray="4 2" /> | |
| <text x={s.x + 50} y="160" textAnchor="middle" fill={s.c} fontSize="11" fontWeight="bold">{s.n}</text> | |
| <text x={s.x + 50} y="178" textAnchor="middle" fill="#a1a1aa" fontSize="9">{s.d}</text> | |
| <text x={s.x + 50} y="200" textAnchor="middle" fill="#71717a" fontSize="8">limited tools</text> | |
| <text x={s.x + 50} y="212" textAnchor="middle" fill="#71717a" fontSize="8">model: {s.s}</text> | |
| <line x1="230" y1="70" x2={s.x + 50} y2="138" stroke={s.c} strokeWidth="1.5" strokeDasharray="3 2" /> | |
| <text x={(230 + s.x + 50) / 2 + 5} y={104 + i * 4} fill={s.c} fontSize="8">summary →</text> | |
| </g> | |
| ))} | |
| </svg> | |
| <div className="text-zinc-400 text-xs text-center mt-2">Each subagent has its own context. Only summaries flow back.</div> | |
| </div> | |
| ); | |
| const CacheLayout = () => ( | |
| <div className="my-6"> | |
| <div className="text-xs text-zinc-500 mb-2">Request prefix → suffix (order matters!)</div> | |
| <div className="flex h-12 rounded-lg overflow-hidden border border-zinc-700"> | |
| <div className="bg-emerald-500/30 flex items-center px-3 text-emerald-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "30%" }}> | |
| System Prompt 🔒 | |
| </div> | |
| <div className="bg-emerald-500/20 flex items-center px-3 text-emerald-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "30%" }}> | |
| Tool Definitions 🔒 | |
| </div> | |
| <div className="bg-amber-500/20 flex items-center px-3 text-amber-200 text-xs font-semibold border-r border-zinc-700" style={{ width: "25%" }}> | |
| Chat History | |
| </div> | |
| <div className="bg-rose-500/20 flex items-center px-3 text-rose-200 text-xs font-semibold" style={{ width: "15%" }}> | |
| New Input | |
| </div> | |
| </div> | |
| <div className="grid md:grid-cols-4 gap-2 mt-2 text-xs"> | |
| <div className="text-emerald-400">cached, locked</div> | |
| <div className="text-emerald-400">cached, locked</div> | |
| <div className="text-amber-400">grows over time</div> | |
| <div className="text-rose-400">always fresh</div> | |
| </div> | |
| <div className="bg-rose-500/10 border border-rose-500/30 rounded-lg p-3 mt-3 text-xs text-zinc-300"> | |
| <span className="text-rose-400 font-semibold">⚠ Cache busters:</span> putting timestamps in the system prompt, shuffling tool order, adding/removing tools mid-session, switching models. One byte of prefix change → entire cache rebuilt. | |
| </div> | |
| </div> | |
| ); | |
| const MemoryDiagram = () => { | |
| const ts = [ | |
| { n: "Working", c: "#fbbf24", w: "context window", e: "current task only", l: "messages[] in RAM" }, | |
| { n: "Procedural", c: "#a78bfa", w: "Skills", e: "how to do things", l: ".claude/skills/*" }, | |
| { n: "Episodic", c: "#22d3ee", w: "Session logs", e: "what happened", l: "JSONL on disk" }, | |
| { n: "Semantic", c: "#34d399", w: "MEMORY.md", e: "stable facts", l: "injected each start" }, | |
| ]; | |
| return ( | |
| <div className="grid md:grid-cols-2 gap-3 my-6"> | |
| {ts.map((t, i) => ( | |
| <div key={i} className="border-2 rounded-xl p-4" style={{ borderColor: `${t.c}55`, background: `${t.c}10` }}> | |
| <div className="font-bold mb-1" style={{ color: t.c }}>{t.n}</div> | |
| <div className="text-zinc-300 text-sm font-semibold">{t.w}</div> | |
| <div className="text-zinc-400 text-xs mt-1">{t.e}</div> | |
| <div className="text-zinc-500 text-xs mt-2 font-mono">{t.l}</div> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| const MultiAgentTopo = () => ( | |
| <div className="my-6"> | |
| <svg viewBox="0 0 480 280" className="w-full max-w-2xl mx-auto"> | |
| <rect x="180" y="15" width="120" height="50" rx="8" fill="#fbbf2425" stroke="#fbbf24" strokeWidth="2" /> | |
| <text x="240" y="38" textAnchor="middle" fill="#fbbf24" fontSize="11" fontWeight="bold">Orchestrator</text> | |
| <text x="240" y="54" textAnchor="middle" fill="#a1a1aa" fontSize="9">writes JSONL inbox</text> | |
| <rect x="80" y="100" width="120" height="40" rx="6" fill="#27272a" stroke="#52525b" /> | |
| <text x="140" y="125" textAnchor="middle" fill="#e4e4e7" fontSize="10" fontFamily="monospace">.team/inbox/</text> | |
| <rect x="280" y="100" width="120" height="40" rx="6" fill="#27272a" stroke="#52525b" /> | |
| <text x="340" y="125" textAnchor="middle" fill="#e4e4e7" fontSize="10" fontFamily="monospace">.tasks/graph.json</text> | |
| <line x1="220" y1="65" x2="160" y2="98" stroke="#fbbf24" strokeDasharray="3 2" /> | |
| <line x1="260" y1="65" x2="320" y2="98" stroke="#fbbf24" strokeDasharray="3 2" /> | |
| {[ | |
| { x: 30, c: "#22d3ee", n: "Worker A" }, | |
| { x: 180, c: "#a78bfa", n: "Worker B" }, | |
| { x: 330, c: "#f472b6", n: "Worker C" }, | |
| ].map((w, i) => ( | |
| <g key={i}> | |
| <rect x={w.x} y="190" width="120" height="80" rx="8" fill={`${w.c}15`} stroke={w.c} strokeWidth="2" /> | |
| <text x={w.x + 60} y="212" textAnchor="middle" fill={w.c} fontSize="11" fontWeight="bold">{w.n}</text> | |
| <rect x={w.x + 10} y="225" width="100" height="36" rx="4" fill="#18181b" stroke={`${w.c}77`} strokeDasharray="2 2" /> | |
| <text x={w.x + 60} y="240" textAnchor="middle" fill="#a1a1aa" fontSize="8">.worktrees/{w.n.toLowerCase()[7]}</text> | |
| <text x={w.x + 60} y="253" textAnchor="middle" fill="#71717a" fontSize="8">isolated files</text> | |
| <line x1="140" y1="140" x2={w.x + 60} y2="188" stroke={w.c} strokeDasharray="3 2" /> | |
| </g> | |
| ))} | |
| </svg> | |
| <div className="text-zinc-400 text-xs text-center mt-2">Protocol → Isolation → Then collaboration. Never the other way.</div> | |
| </div> | |
| ); | |
| // ===== Chapter content ===== | |
| const Ch1 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Most people meet Claude Code as a "chat with code superpowers" and immediately get tangled up. Context turns into a junk drawer, the rule files balloon, tools multiply while quality drops. The fix isn't a longer prompt. It's a better mental model. | |
| </p> | |
| <p className="text-zinc-300 leading-relaxed mt-3"> | |
| The whole system collapses neatly into <span className="text-amber-400 font-semibold">six layers</span>. Each layer answers a different question, and the trick is to put information in the right one. Stuff something in the wrong layer and you'll feel it bite you weeks later. | |
| </p> | |
| <SixLayerDiagram /> | |
| <Insight> | |
| Over-index on any single layer and the system gets unstable. CLAUDE.md grows into a wiki and pollutes every session. Too many MCP tools eat your context window. Too many subagents drift. Skip verification and you can't tell where things broke. | |
| </Insight> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| When something goes wrong, you don't debug "the prompt." You ask which surface failed: | |
| </p> | |
| <Box1 tone="zinc" title="The 5 diagnostic surfaces"> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🧠 <span className="text-amber-300 font-semibold">Context surface</span> — what's loaded vs. what isn't</li> | |
| <li>🔧 <span className="text-orange-300 font-semibold">Action surface</span> — what tools are reachable</li> | |
| <li>🛡️ <span className="text-rose-300 font-semibold">Control surface</span> — what's blocked, sandboxed, audited</li> | |
| <li>📦 <span className="text-cyan-300 font-semibold">Isolation surface</span> — what gets its own scratch space</li> | |
| <li>✅ <span className="text-emerald-300 font-semibold">Verification surface</span> — how you know it actually worked</li> | |
| </ul> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed mt-3"> | |
| The rest of the guide is just a tour through these layers, what each one is good for, and the traps that live in each. | |
| </p> | |
| </> | |
| ); | |
| const Ch2 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Strip away the marketing and an "agent" is shockingly simple. Under the hood, it's a four-step loop running until the model decides it's done. That's it. | |
| </p> | |
| <AgentLoopDiagram /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The actual implementation is genuinely about 20 lines of code. Get a model response — if it called a tool, run the tool, append the result, ask again. If it returned plain text, you're done. | |
| </p> | |
| <Code>{`while (true) { | |
| const resp = await model.messages.create({ model, tools, messages }); | |
| if (resp.stop_reason === "tool_use") { | |
| const results = await runTools(resp.content); | |
| messages.push({ role: "assistant", content: resp.content }); | |
| messages.push({ role: "user", content: results }); | |
| } else { | |
| return resp.content.find(b => b.type === "text").text; | |
| } | |
| }`}</Code> | |
| <Insight> | |
| That loop barely changes from a toy demo to a billion-dollar production system. New capabilities don't rewrite the loop — they layer on top. Subagents, skills, hooks, memory: all of it sits <em>around</em> this core, never inside it. Once you see this, the whole stack stops feeling magical. | |
| </Insight> | |
| <Box1 tone="cyan" title="The mental rule" icon={Repeat}> | |
| The model reasons. External systems track state and enforce boundaries. Once that division is clean, the loop never needs touching again — you grow the system by adding tools, files, and constraints, not by fiddling with the engine. | |
| </Box1> | |
| </> | |
| ); | |
| const Ch3 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Half the things called "agents" today are actually <span className="text-cyan-400 font-semibold">workflows</span> in costume. The line is sharp: | |
| </p> | |
| <Box1 tone="cyan" title="The defining question" icon={GitBranch}> | |
| Who decides the next step — your <em>code</em>, or the <em>model</em>? | |
| <br /> | |
| Code → workflow. Model → agent. Neither is "better." The task picks the right one. | |
| </Box1> | |
| <WorkflowVsAgent /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Workflows win when the path is fixed and you can verify each step in code. Agents win when the next step depends on what just happened. Most real systems are a mix of two or three patterns chained together. Full agentic autonomy is rarely what you want. | |
| </p> | |
| <h4 className="text-violet-400 font-bold mt-6 mb-2">The 5 control patterns to know</h4> | |
| <PatternIcons /> | |
| <Box1 tone="emerald" title="Picking the right pattern"> | |
| <ul className="space-y-1.5 text-sm"> | |
| <li>📐 Fixed flow + code-verifiable → <Tag tone="cyan">Prompt Chaining</Tag></li> | |
| <li>🔀 Inputs split into clear branches → <Tag tone="cyan">Routing</Tag></li> | |
| <li>🤔 Reasoning + clear pass/fail → <Tag tone="violet">Single Agent ReAct</Tag></li> | |
| <li>🧩 Decomposable subtasks, summary-only result → <Tag tone="violet">Orchestrator-Workers</Tag></li> | |
| <li>✍️ Quality hard to codify (translation, creative) → <Tag tone="emerald">Evaluator-Optimizer</Tag></li> | |
| <li>⚖️ High-stakes, multi-perspective → <Tag tone="emerald">Voting (Parallelization)</Tag></li> | |
| </ul> | |
| </Box1> | |
| <Insight> | |
| Run the single-agent ceiling first. Coordination overhead in multi-agent setups usually dwarfs the parallelism you gain. Multi-agent should be the answer when isolation is required, not when one agent is "slow." | |
| </Insight> | |
| </> | |
| ); | |
| const Ch4 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Here's the spicy take from the agent article: <span className="text-rose-400 font-semibold">the model isn't usually the bottleneck</span>. The harness is. | |
| </p> | |
| <p className="text-zinc-300 leading-relaxed mt-3"> | |
| A "harness" sounds boring. It's not. It's the four-piece scaffolding around the agent that determines whether you can ship anything: <Tag tone="amber">acceptance baselines</Tag> <Tag tone="amber">execution boundaries</Tag> <Tag tone="amber">feedback signals</Tag> <Tag tone="amber">fallback mechanisms</Tag>. | |
| </p> | |
| <HarnessQuadrant /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The 2×2 above is the whole game. Tasks where the goal is clear <em>and</em> verification is automated belong in the green box. Agents thrive there. Outside it, you're either capped by humans (top-left) or running confidently in the wrong direction (bottom-right). | |
| </p> | |
| <Insight> | |
| A harness is the machinery that pushes a task from anywhere on this grid <em>into the green quadrant</em>. It replaces "humans must look at this" with "the system can decide pass/fail." | |
| </Insight> | |
| <Box1 tone="emerald" title="The OpenAI Codex example" icon={Zap}> | |
| Three engineers shipped a million lines of code in five months — roughly 10× normal velocity. The model wasn't the secret. The harness was: every project keeps an <span className="font-mono text-xs">AGENTS.md</span> index of about 100 lines, constraints are encoded into linters and CI (not docs), and the agent owns the entire pipeline including PRs and merges. Code review moved from humans to machines, but it didn't disappear. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The diagnostic principle that flows from this: when something breaks, walk the cheap-to-expensive ladder. Check tool descriptions first. Check whether task state is externalized. Check whether the eval itself is broken. <em>Then</em>, maybe, touch the prompt. | |
| </p> | |
| </> | |
| ); | |
| const Ch5 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Most context problems aren't about <em>capacity</em>. They're about <span className="text-rose-400 font-semibold">noise</span>. A 200K window sounds enormous until you watch how much of it is gone before your conversation even starts. | |
| </p> | |
| <ContextBarDiagram /> | |
| <Insight> | |
| A typical MCP server like GitHub exposes 20–30 tool definitions at ~200 tokens each — that's 4-6K. Connect five servers and you're spending around 25K tokens (12.5% of your window) on fixed overhead before anyone says a word. This is invisible until you check. | |
| </Insight> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The fix is layering. Don't ask "how big is my context?" Ask "what belongs in each layer?" | |
| </p> | |
| <Box1 tone="amber" title="The 5 context layers" icon={Layers}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="amber">Permanent</Tag> Identity, conventions, prohibitions. Short, hard, actionable. Lives in CLAUDE.md.</div> | |
| <div><Tag tone="violet">On-Demand</Tag> Skills. Descriptors stay resident; full bodies load when triggered.</div> | |
| <div><Tag tone="cyan">Runtime injection</Tag> Time, channel ID, current branch. Appended each turn.</div> | |
| <div><Tag tone="emerald">Memory</Tag> Cross-session experience in MEMORY.md. Read on demand, not always loaded.</div> | |
| <div><Tag tone="rose">System</Tag> Deterministic logic via hooks. <strong>Never</strong> in context.</div> | |
| </div> | |
| </Box1> | |
| <Box1 tone="rose" title="The hard rule" icon={AlertTriangle}> | |
| Anything that can be expressed by a hook, a code rule, or a tool constraint <strong>does not belong in the prompt</strong>. Don't make the model re-read it every turn. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed mt-4"> | |
| There's a second hidden killer beyond fixed overhead: <span className="text-amber-400 font-semibold">tool output noise</span>. A single <code className="text-xs bg-zinc-800 px-1 rounded">cargo test</code> can dump thousands of lines into the window. The model only needs "did it pass, and if not, where?" — everything else is dead weight crowding out conversation history. Tools that filter command output before it reaches the model (like RTK) work because they stop the noise at its source instead of begging the model to ignore it. | |
| </p> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| And use the <Pill>/context</Pill> command. Stop guessing. | |
| </p> | |
| </> | |
| ); | |
| const Ch6 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| When context fills up, the system has to throw stuff out. The default algorithm has a nasty habit: it deletes early tool outputs and file reads first. Sounds reasonable — until you realize that's exactly where the <span className="text-rose-400 font-semibold">architectural decisions</span> were captured. Two hours later you change something and have no idea why the original choice was made. | |
| </p> | |
| <Box1 tone="rose" title="The compression trap" icon={AlertTriangle}> | |
| Compaction optimizes for "re-readability" — keeping things the model could find again. Decisions and constraint reasoning <em>look</em> like they could be reconstructed, so they get summarized away. They cannot be reconstructed. That's where bugs are born. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The fix is to override the algorithm with explicit retention priorities. Drop this in CLAUDE.md and you take back control: | |
| </p> | |
| <Code>{`## Compact Instructions | |
| When compressing, preserve in priority order: | |
| 1. Architectural decisions (NEVER summarize) | |
| 2. Modified files and key changes | |
| 3. Current verification status (pass/fail) | |
| 4. Open TODOs and rollback notes | |
| 5. Tool outputs (delete, keep pass/fail only)`}</Code> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| But compaction is reactive. The Claude Code team identifies <span className="text-cyan-400 font-semibold">five proactive ways</span> to manage a session — knowing which to pick is half the skill: | |
| </p> | |
| <Box1 tone="cyan" title="5 ways to handle a session"> | |
| <div className="space-y-1.5 text-sm"> | |
| <div>💬 <strong>continue</strong> — most natural, most abused. Default action.</div> | |
| <div>⏪ <strong>rewind</strong> — double-tap ESC. Often beats correction. If Claude went down a wrong path, undoing the bad turns beats appending "that didn't work, try X."</div> | |
| <div>🧹 <strong>clear</strong> — start fresh with a brief you wrote. Most effort, most control.</div> | |
| <div>📦 <strong>compact</strong> — let the model summarize. Convenient. May drop things you cared about.</div> | |
| <div>🔍 <strong>subagents</strong> — delegate the next chunk to its own clean context. Only the result returns.</div> | |
| </div> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed mt-4"> | |
| One pattern is gold for long work: write a <Pill tone="good">HANDOFF.md</Pill> before a session ends. Have Claude record what it tried, what worked, what failed, and what's next. The next session starts from that file instead of relying on whatever survived compression. It's lossy compression you control, written in your voice. | |
| </p> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Plan Mode (double-tap Shift+Tab) is the other big lever — it separates exploration from execution. Claude reads, asks clarifying questions, and proposes a plan; nothing on disk changes until you confirm. For complex refactors and migrations this beats "edit immediately and hope" every single time. | |
| </p> | |
| </> | |
| ); | |
| const Ch7 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Skills are easy to misunderstand. They're not templates. They're not saved prompts. They're <span className="text-amber-400 font-semibold">on-demand workflow packages</span>: short descriptors that stay in context, with the full body loading only when the model decides it needs them. | |
| </p> | |
| <SkillsLoadDiagram /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| This is called <span className="text-cyan-400 font-semibold">progressive disclosure</span>. The system prompt is an index. The model scans the index every turn, decides which skill matches, and pulls the body in only when it does. Smart and cheap. | |
| </p> | |
| <Insight> | |
| The single thing that determines whether your skill works: <em>the description</em>. It tells the model <strong>when to use the skill</strong>, not what's inside. "Use when deploying to production or rolling back" is a routing condition. "Helps with deployment" triggers on every backend question and pollutes everything. | |
| </Insight> | |
| <Compare | |
| good={<><div className="font-mono text-xs mb-1">description: Use when deploying to production or rolling back.</div><div className="text-xs text-zinc-400">~9 tokens. Specific. Routing condition.</div></>} | |
| bad={<><div className="font-mono text-xs mb-1">description: This skill handles the complete deployment process to production. It covers environment checks, rollback procedures, and post-deploy verification...</div><div className="text-xs text-zinc-400">~45 tokens. Bloats every request.</div></>} | |
| /> | |
| <Box1 tone="emerald" title="The data backs this up" icon={Sparkles}> | |
| Adding counter-examples ("don't use when...") to skill descriptions raises routing accuracy from 73% to 85% and cuts response time by 18%. Without counter-examples, accuracy drops as low as 53%. That's the difference between a working skill and a confused one. | |
| </Box1> | |
| <h4 className="text-amber-400 font-bold mt-6 mb-2">Three skill archetypes</h4> | |
| <div className="grid md:grid-cols-3 gap-3 my-3"> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-cyan-400 font-bold text-sm">Checklist</div> | |
| <div className="text-xs text-zinc-400 mt-1">Quality gates. Pre-release: build passes, lint clean, version bumped, changelog updated.</div> | |
| </div> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-violet-400 font-bold text-sm">Workflow</div> | |
| <div className="text-xs text-zinc-400 mt-1">Standardized ops with rollback. Config migration: backup → dry run → apply → verify.</div> | |
| </div> | |
| <div className="bg-zinc-900/50 border border-zinc-800 rounded-lg p-3"> | |
| <div className="text-emerald-400 font-bold text-sm">Domain Expert</div> | |
| <div className="text-xs text-zinc-400 mt-1">Decision frameworks. Diagnose runtime issues by collecting evidence on a fixed path.</div> | |
| </div> | |
| </div> | |
| <Box1 tone="rose" title="Skill anti-patterns to dodge" icon={XCircle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🟥 Description too short ("help with backend") — fires for anything backend-shaped</li> | |
| <li>🟥 Body too long — hundreds of lines of manual stuffed into SKILL.md (split into supporting files)</li> | |
| <li>🟥 One skill doing five jobs — review, deploy, debug, docs, incident response</li> | |
| <li>🟥 Side-effect skills with model auto-invocation — set <Pill>disable-model-invocation: true</Pill></li> | |
| </ul> | |
| </Box1> | |
| </> | |
| ); | |
| const Ch8 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Tools for agents and APIs for humans look similar but aren't the same thing. APIs optimize for feature completeness — every endpoint, every option. Agent tools should optimize for one question only: <span className="text-amber-400 font-semibold">can the model pick the right one and use it correctly on the first try?</span> | |
| </p> | |
| <ToolGenDiagram /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The <span className="text-emerald-400 font-semibold">ACI</span> (Agent-Computer Interface) idea is the inflection point. Instead of dropping <code className="text-xs bg-zinc-800 px-1 rounded">get_post</code> + <code className="text-xs bg-zinc-800 px-1 rounded">update_content</code> + <code className="text-xs bg-zinc-800 px-1 rounded">update_title</code> on the model and asking it to choreograph, you give it <code className="text-xs bg-zinc-800 px-1 rounded">update_post(id, title, content)</code> — one tool that maps to the agent's actual goal. | |
| </p> | |
| <Insight> | |
| Tool design shapes agent behavior the way HCI shapes human behavior. Bad parameters waste turns. Opaque errors send the loop into "let me try again" hell. Good tools give structured errors with recovery hints: <em>"POST_NOT_FOUND. Call list_posts first to get a valid id."</em> | |
| </Insight> | |
| <h4 className="text-violet-400 font-bold mt-6 mb-2">Good vs bad tool design</h4> | |
| <div className="grid md:grid-cols-2 gap-3 my-3 text-xs"> | |
| <div className="bg-emerald-500/5 border border-emerald-500/30 rounded-lg p-4"> | |
| <div className="text-emerald-400 font-bold mb-2">Good</div> | |
| <ul className="space-y-1.5 text-zinc-300"> | |
| <li>📛 <strong>Name:</strong> <Pill tone="good">jira_issue_get</Pill> <Pill tone="good">sentry_errors_search</Pill></li> | |
| <li>🎚 <strong>Params:</strong> typed, described, formatted</li> | |
| <li>📤 <strong>Returns:</strong> what's needed for the next decision</li> | |
| <li>⚠️ <strong>Errors:</strong> include suggestions to fix</li> | |
| <li>📏 <strong>Output:</strong> truncatable, response_format flag</li> | |
| </ul> | |
| </div> | |
| <div className="bg-rose-500/5 border border-rose-500/30 rounded-lg p-4"> | |
| <div className="text-rose-400 font-bold mb-2">Bad</div> | |
| <ul className="space-y-1.5 text-zinc-300"> | |
| <li>📛 <strong>Name:</strong> <Pill tone="bad">query</Pill> <Pill tone="bad">do_action</Pill></li> | |
| <li>🎚 <strong>Params:</strong> id, name, target — vague</li> | |
| <li>📤 <strong>Returns:</strong> raw blobs, UUIDs, internal fields</li> | |
| <li>⚠️ <strong>Errors:</strong> "Error" — opaque</li> | |
| <li>📏 <strong>Output:</strong> dumps everything every time</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <Box1 tone="cyan" title="The AskUserQuestion lesson" icon={Wrench}> | |
| Anthropic tried three ways to let Claude pause and ask the user something. Adding a <span className="font-mono text-xs">question</span> param to existing tools? Claude ignored it. Asking Claude to emit special markdown? Brittle and unenforced. The winner: a dedicated <span className="font-mono text-xs">AskUserQuestion</span> tool. Calling the tool <em>is</em> the pause signal. Structured, explicit, hard to skip. Lesson: if you want a behavior, give it a tool — flags and conventions are easy to drift past. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed mt-4"> | |
| The third generation goes further. <span className="text-emerald-400 font-semibold">Tool Search</span> stops loading every definition by default — context retention rates hit 95%, Opus 4 accuracy jumped from 49% to 74%. <span className="text-emerald-400 font-semibold">Programmatic Tool Calling</span> lets the model orchestrate calls in code so intermediate JSON never enters context — token usage can drop from ~150K to ~2K on big chains. <span className="text-emerald-400 font-semibold">Tool Use Examples</span> — 1-5 real invocations per tool — push accuracy from 72% to 90%. | |
| </p> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| When something goes wrong, debug tools <em>before</em> suspecting the model. Most "wrong tool" issues trace back to imprecise descriptions, not missing capability. | |
| </p> | |
| </> | |
| ); | |
| const Ch9 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Hooks are easy to file under "scripts that auto-run." That undersells them. The real point: <span className="text-violet-400 font-semibold">move work out of the model's on-the-fly judgment and into deterministic processes</span>. Things like "should this format?", "can I edit this protected file?", "did the task finish — notify me?" — none of those should depend on the model remembering the rule every time. | |
| </p> | |
| <HookTimeline /> | |
| <Box1 tone="violet" title="Suitable for hooks" icon={Check}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🛡️ Block edits to protected files</li> | |
| <li>🎨 Auto-format, lint, light validation after Edit</li> | |
| <li>🌱 Inject env state at SessionStart (git branch, vars)</li> | |
| <li>📣 Push notifications when long tasks complete</li> | |
| </ul> | |
| </Box1> | |
| <Box1 tone="rose" title="Unsuitable for hooks" icon={XCircle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🧠 Complex semantic judgment requiring context</li> | |
| <li>⏱️ Long-running business processes</li> | |
| <li>🔀 Multi-step reasoning with tradeoffs</li> | |
| </ul> | |
| Those belong in skills or subagents, not hooks. | |
| </Box1> | |
| <Insight> | |
| The real magic is the three-layer stack: <strong>CLAUDE.md</strong> declares "tests must pass before commit." A <strong>skill</strong> tells Claude how to run tests and read failures. A <strong>hook</strong> hard-blocks on critical paths. Any single layer has gaps. CLAUDE.md gets ignored. Hooks can't do judgment. All three together is what actually holds up. | |
| </Insight> | |
| <Code>{`{ | |
| "hooks": { | |
| "PostToolUse": [{ | |
| "matcher": "Edit", | |
| "pattern": "*.rs", | |
| "hooks": [{ | |
| "type": "command", | |
| "command": "cargo check 2>&1 | head -30" | |
| }] | |
| }] | |
| } | |
| }`}</Code> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Note <code className="text-xs bg-zinc-800 px-1 rounded">| head -30</code> — keep hook output small. Hooks that dump thousands of lines back into context just trade one problem for another. | |
| </p> | |
| </> | |
| ); | |
| const Ch10 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| A subagent is "another Claude" that branches off your main conversation with its own context and only the tools you allow. People reach for them looking for parallelism. The real prize is <span className="text-violet-400 font-semibold">isolation</span>. | |
| </p> | |
| <SubagentDiagram /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Codebase scans, test runs, and review passes generate huge amounts of intermediate output. If that all lands in the main thread, you've polluted your context with stuff that was only useful for one decision. Send those tasks to a subagent and the main thread receives only the conclusion. | |
| </p> | |
| <Box1 tone="cyan" title="Built-in subagent types" icon={Box}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🔍 <strong>Explore</strong> — read-only scan, runs Haiku to save cost</li> | |
| <li>🗺️ <strong>Plan</strong> — research and planning</li> | |
| <li>🛠️ <strong>General-purpose</strong> — flexible utility</li> | |
| <li>🎯 <strong>Custom</strong> — define your own with explicit constraints</li> | |
| </ul> | |
| </Box1> | |
| <Insight> | |
| The Explore→Main pattern is the killer move. Heavy exploration done by Explore (cheap, isolated). The summary comes back. Implementation happens in Main with a clean context. You get the benefits of investigation without paying its context tax. | |
| </Insight> | |
| <h4 className="text-violet-400 font-bold mt-6 mb-2">Configuration that actually does something</h4> | |
| <Box1 tone="zinc" title=""> | |
| <ul className="space-y-1 text-sm"> | |
| <li><Pill>tools / disallowedTools</Pill> Limit reach. Don't grant the same broad permissions as the main thread.</li> | |
| <li><Pill>model</Pill> Exploration → Haiku/Sonnet. Important reviews → Opus.</li> | |
| <li><Pill>maxTurns</Pill> Prevent runaway. Always set this.</li> | |
| <li><Pill>isolation: worktree</Pill> Filesystem isolation when files get modified.</li> | |
| </ul> | |
| </Box1> | |
| <Box1 tone="rose" title="Subagent anti-patterns" icon={AlertTriangle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>🟥 Same broad permissions as main thread → isolation is theater</li> | |
| <li>🟥 Output format unspecified → main thread can't consume the result</li> | |
| <li>🟥 Strong dependencies between subtasks → not what subagents are for</li> | |
| <li>🟥 No depth limit → subagents spawning subagents spawning subagents</li> | |
| </ul> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| And give subagents <span className="text-violet-400 font-semibold">minimal system prompts</span>. Strip Memory and Skills. Keep only Tooling, Workspace, Runtime. Otherwise you're just leaking the main agent's privileges into the isolated workspace and defeating the point. | |
| </p> | |
| </> | |
| ); | |
| const Ch11 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| This is the topic that quietly explains half of Claude Code's design. Prompt caching isn't a perf optimization tucked in the corner — it's a <span className="text-amber-400 font-semibold">first-class architectural constraint</span>. Cache hits cut cost ~90%, slash latency, and loosen rate limits. Anthropic treats low cache hit rates as incident-level signals. | |
| </p> | |
| <CacheLayout /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Caching is <span className="text-cyan-400 font-semibold">prefix matching</span>. The system caches everything from the start of the request up to a cache-control breakpoint. <em>Order matters absolutely.</em> One byte different in the prefix and you're rebuilding from scratch. | |
| </p> | |
| <Insight> | |
| A counterintuitive consequence: <strong>switching to a smaller, cheaper model mid-session is often more expensive</strong>. If you've chatted 100K tokens with Opus and switch to Haiku for a quick question, Haiku has to rebuild the entire 100K-token cache for itself. You'd pay less by staying on Opus. | |
| </Insight> | |
| <Box1 tone="rose" title="Common cache busters" icon={AlertTriangle}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>⏰ <strong>Timestamps in the system prompt</strong> — changes every request, voids everything. Put dynamic info in a later message.</li> | |
| <li>🔀 <strong>Non-deterministic tool ordering</strong> — sort your tools.</li> | |
| <li>➕ <strong>Adding/removing tools mid-session</strong> — even a small edit breaks the prefix. Use stubs and lazy-load fuller schemas instead.</li> | |
| <li>🔁 <strong>Switching models mid-session</strong> — cache is per-model.</li> | |
| </ul> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| This is also why Plan Mode doesn't swap to a different read-only toolset, even though that would be the obvious design — swapping tools would smash the cache. Instead, Anthropic uses a tool-driven approach where the model enters plan mode while the underlying tool prefix stays stable. | |
| </p> | |
| <Box1 tone="emerald" title="The non-obvious takeaway" icon={Sparkles}> | |
| A <em>large but stable</em> system prompt can cost less than a <em>small but changing</em> one. You pay the write cost once. Subsequent reads come at up to a 90% discount. "Keep the prefix short" misses the point — keep the prefix <strong>stable</strong>. | |
| </Box1> | |
| </> | |
| ); | |
| const Ch12 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Out of the box, agents have no memory across sessions. End the conversation, context vanishes, the next startup begins blank. Memory has to be designed in deliberately — and the trick is realizing it's not <em>one</em> thing. Four different problems, four different solutions: | |
| </p> | |
| <MemoryDiagram /> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Notice none of these are vector databases by default. ChatGPT's whole memory system is around 33 user facts, 15 conversation summaries, and a sliding window over the current chat. No RAG. No embeddings. Markdown files plus keyword search get you very far before semantic similarity becomes worth the operational cost. | |
| </p> | |
| <Box1 tone="cyan" title="The hybrid retrieval pattern" icon={Database}> | |
| Many production systems use 70% vector similarity + 30% keyword weight when search becomes necessary. But until you cross several thousand records and genuinely need semantic matching, structured Markdown plus keyword search wins on debuggability and cost. | |
| </Box1> | |
| <Insight> | |
| Memory consolidation must be <strong>reversible</strong>. The system moves a pointer; it never deletes raw messages. If summarization fails, raw messages write to an archive — nothing is lost. Triggering at ~50% token usage gives you headroom; waiting until you're full is panic-driven compression. | |
| </Insight> | |
| <Box1 tone="rose" title="What gets lost most easily" icon={AlertTriangle}> | |
| LLMs default to dropping content that "looks retrievable." Early tool outputs go first — but with them go architectural decisions, constraint reasoning, and failed approaches you don't want to repeat. <strong>Never alter identifiers</strong> during compression: UUIDs, hashes, IPs, ports, URLs, filenames. One wrong character in a commit hash breaks every subsequent tool call. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The filesystem itself makes a great memory layer. When tool calls return huge JSON, write the result to a file and let the agent <Pill>grep</Pill> it on demand. Cursor's "Dynamic Context Discovery" experiments cut MCP-task token usage by 46.9% by syncing tool descriptions to folders and querying definitions only when needed. | |
| </p> | |
| </> | |
| ); | |
| const Ch13 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| "Claude says it's done" has zero engineering value. What matters is whether it's <em>actually</em> correct, whether you can roll back if it's not, and whether the trail is auditable. The verification loop is what turns an agent from a demo into something you'd let near production. | |
| </p> | |
| <Box1 tone="emerald" title="The 3 verifier tiers" icon={FileCheck}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="emerald">Lowest</Tag> Exit codes, lint, typecheck, unit tests. Cheap, fast, executable.</div> | |
| <div><Tag tone="cyan">Middle</Tag> Integration tests, screenshot diffs, contract tests, smoke tests.</div> | |
| <div><Tag tone="violet">Higher</Tag> Production logs, monitoring metrics, manual review checklists.</div> | |
| </div> | |
| </Box1> | |
| <Insight> | |
| A simple test: if you can't clearly explain "how does Claude know it's done correctly?" — the task probably isn't ready for autonomous execution. Define acceptance criteria <em>before</em> you write the prompt: which commands must pass, what to check first if they fail, what screenshots and logs prove success. | |
| </Insight> | |
| <h4 className="text-amber-400 font-bold mt-6 mb-2">Evaluation: trust but verify (and verify the verifier)</h4> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Eval is harder than agent debugging. It's also the place where teams commonly lose months. Two metrics to keep separate: | |
| </p> | |
| <div className="grid md:grid-cols-2 gap-3 my-4"> | |
| <div className="bg-cyan-500/5 border border-cyan-500/30 rounded-lg p-4"> | |
| <div className="text-cyan-400 font-bold">Pass@k</div> | |
| <div className="text-xs text-zinc-300 mt-2">"At least one of k runs is correct."</div> | |
| <div className="text-xs text-zinc-400 mt-1">Use when exploring capability ceilings — "could this agent ever do this?"</div> | |
| </div> | |
| <div className="bg-rose-500/5 border border-rose-500/30 rounded-lg p-4"> | |
| <div className="text-rose-400 font-bold">Pass^k</div> | |
| <div className="text-xs text-zinc-300 mt-2">"All k runs are correct."</div> | |
| <div className="text-xs text-zinc-400 mt-1">Use for regression — "did we break something on this change?"</div> | |
| </div> | |
| </div> | |
| <Box1 tone="cyan" title="Three kinds of graders" icon={Check}> | |
| <ul className="space-y-1 text-sm"> | |
| <li>💻 <strong>Code graders</strong> — string match, unit tests, structural diff. Most certain. Use when there's a clear right answer.</li> | |
| <li>🤖 <strong>Model graders</strong> — rubric scoring, A/B comparison. Medium reliability. Use for semantic quality.</li> | |
| <li>👤 <strong>Human graders</strong> — slow but reliable. Calibrate the auto-judges against these.</li> | |
| </ul> | |
| </Box1> | |
| <Insight> | |
| Cover both <strong>transcript</strong> (what the agent said happened) and <strong>outcome</strong> (what the system actually looks like now). Anthropic's airline-booking example: Opus exploited a policy loophole to find the user a cheaper option. Score against the pre-programmed path → fail. Score against outcome → win. You only see the truth if you cover both. | |
| </Insight> | |
| <Box1 tone="rose" title="Fix the eval before tuning the agent" icon={AlertTriangle}> | |
| When scores drop, teams instinctively touch the agent. But infrastructure errors (resource limits killing processes), grader bugs (failing correct answers), and aggregate scores hiding category-level failures all <em>look identical to model degradation</em>. Tuning the agent against a broken eval breaks the parts that worked. | |
| </Box1> | |
| </> | |
| ); | |
| const Ch14 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The biggest thing to internalize about CLAUDE.md: <span className="text-amber-400 font-semibold">it's a contract, not a wiki</span>. Not team docs. Not a knowledge base. Only the things that must hold across <em>every</em> session belong here. | |
| </p> | |
| <Box1 tone="amber" title="The startup heuristic" icon={BookOpen}> | |
| Start with nothing. Use Claude Code first. Add an entry only when you notice yourself repeating the same instruction. Type <Pill>#</Pill> to append the current conversation, or just say "add this to the project's CLAUDE.md." | |
| </Box1> | |
| <Compare | |
| good={<ul className="space-y-1 text-xs"><li>✓ Build / test / lint commands</li><li>✓ Key directory structure</li><li>✓ Code style constraints</li><li>✓ Non-obvious env dependencies</li><li>✓ NEVER list (high-risk ops)</li><li>✓ Compact Instructions</li></ul>} | |
| bad={<ul className="space-y-1 text-xs"><li>✗ Long background intros</li><li>✗ Full API documentation</li><li>✗ "Write high-quality code"</li><li>✗ Anything obvious from the repo</li><li>✗ Background materials (→ skills)</li></ul>} | |
| /> | |
| <h4 className="text-amber-400 font-bold mt-6 mb-2">A high-quality template</h4> | |
| <Code>{`# Project Contract | |
| ## Build And Test | |
| - Install: pnpm install | |
| - Test: pnpm test | |
| - Lint: pnpm lint | |
| ## Architecture Boundaries | |
| - HTTP handlers in src/http/handlers/ | |
| - Domain logic in src/domain/ | |
| - No persistence in handlers | |
| ## NEVER | |
| - Modify .env or lockfiles without approval | |
| - Commit without running tests | |
| ## Verification | |
| - Backend: make test + make lint | |
| - API changes: update contracts/ | |
| ## Compact Instructions | |
| Preserve in priority order: | |
| 1. Architecture decisions (NEVER summarize) | |
| 2. Modified files and key changes | |
| 3. Verification status | |
| 4. Open TODOs and rollback notes`}</Code> | |
| <Insight> | |
| Let Claude maintain its own CLAUDE.md. After correcting a mistake, say: <em>"Update your CLAUDE.md so you don't make that mistake again."</em> Claude is genuinely good at writing these rules for itself, and repeated mistakes drop off over time. Just review the file periodically — entries go stale, and constraints that helped once may stop earning their context cost. | |
| </Insight> | |
| </> | |
| ); | |
| const Ch15 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Multi-agent looks like a parallelism problem. It isn't. It's an <span className="text-violet-400 font-semibold">isolation and coordination</span> problem that <em>happens</em> to allow parallelism. Two operating modes are worth distinguishing: | |
| </p> | |
| <Box1 tone="cyan" title="Director vs Orchestrator" icon={Users}> | |
| <div className="space-y-2 text-sm"> | |
| <div><Tag tone="cyan">Director Mode</Tag> Synchronous. Human in close turn-by-turn collaboration with one agent. Session ends → output is ephemeral.</div> | |
| <div><Tag tone="violet">Orchestrator Mode</Tag> Asynchronous. Human sets the goal at the start, multiple agents work in parallel, human reviews at the end. The intermediate output becomes durable artifacts (branches, PRs).</div> | |
| </div> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Orchestrator Mode is where multi-agent <em>earns its complexity</em>. Not "multiple models running simultaneously" — that's just expensive. The point is shifting continuous human involvement into review of tangible artifacts. | |
| </p> | |
| <MultiAgentTopo /> | |
| <Insight> | |
| <strong>Hallucinations amplify in multi-agent systems.</strong> Agent A drifts off course. Agent B reinforces the bias. Agent C builds on it. They converge on a wrong answer with high confidence. This is exactly when cross-validation pays off — a second independent agent, unit tests, compilers, manual review. Anything that breaks the chain. | |
| </Insight> | |
| <Box1 tone="rose" title="The order to build things" icon={AlertTriangle}> | |
| <ol className="space-y-1 text-sm list-decimal list-inside"> | |
| <li>Persistent task graphs (.tasks/)</li> | |
| <li>Teammate identities (who is each agent?)</li> | |
| <li>Structured communication protocol (JSONL inbox, append-only)</li> | |
| <li>Cross-validation / external feedback</li> | |
| </ol> | |
| Skip steps and you get coordination chaos. Reverse the order and you build on quicksand. | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Sub-agents in this setup need two restrictions: a <span className="text-violet-400 font-semibold">depth limit</span> (or you get infinite recursive spawning) and <span className="text-violet-400 font-semibold">minimal system prompts</span> — Tooling, Workspace, Runtime only. Strip Skills and Memory to prevent privilege escalation. | |
| </p> | |
| <Box1 tone="emerald" title="The pragmatic rule" icon={Check}> | |
| Don't go multi-agent until you've genuinely hit the ceiling of a single agent. Coordination overhead routinely outweighs whatever parallelism you'd gain. | |
| </Box1> | |
| </> | |
| ); | |
| const Ch16 = () => { | |
| const aps = [ | |
| { n: "CLAUDE.md as wiki", s: "Pollutes context every load. Key instructions get diluted.", f: "Keep only the contract. Move materials to skills and rules." }, | |
| { n: "Skill grab-bag", s: "Description triggers unreliably. Workflows conflict.", f: "One skill = one job. Be explicit about side-effect control." }, | |
| { n: "Too many tools, vague descriptions", s: "Wrong tool selection. Schemas overwhelm context.", f: "Merge overlapping tools. Clear namespacing." }, | |
| { n: "No verification loop", s: "Claude has no way to know if it's actually done.", f: "Bind a verifier to every task type." }, | |
| { n: "Over-autonomy", s: "Unbounded multi-agent fan-out. Hard to stop once it goes wrong.", f: "Minimize roles, permissions, worktrees. Always set maxTurns." }, | |
| { n: "No context segmentation", s: "Research, implementation, and review all pile onto main thread.", f: "/clear for task switches. /compact for phase switches. Subagents for heavy exploration." }, | |
| { n: "Wide autonomy + weak governance", s: "Tools open, but permission and recovery boundaries weak.", f: "Combine permissions, sandbox, hooks, subagents into one boundary." }, | |
| { n: "Approved commands pile up", s: "rm -rf etc. stay in settings.json. Once triggered: irreversible.", f: "Regularly review allowedTools list." }, | |
| { n: "Documented constraints, not enforced", s: "Agent selectively ignores rules in docs.", f: "Move rules into linters, hooks, tool validations. Encode, don't document." }, | |
| { n: "Disconnected memory", s: "Decision quality craters past ~20 turns.", f: "Monitor token usage. Auto-trigger compression at thresholds." }, | |
| { n: "Premature multi-agent", s: "Coordination overhead eclipses parallelism gains.", f: "Establish task graphs. Validate single-agent ceiling first." }, | |
| { n: "Zero evaluation", s: "Single fix introduces unknown regressions.", f: "Convert every real-world failure into an automated test case." }, | |
| ]; | |
| return ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| The greatest hits of agent disasters. Most of these aren't model limits. They're <span className="text-rose-400 font-semibold">missing engineering constraints</span> dressed up as model failures. | |
| </p> | |
| <Box1 tone="cyan" title="The diagnosis ladder (cheap → expensive)" icon={Brain}> | |
| <ol className="space-y-1 text-sm list-decimal list-inside"> | |
| <li>Are tool descriptions accurate?</li> | |
| <li>Is task state externalized to files?</li> | |
| <li>Is the eval system itself distorted?</li> | |
| <li>Should the deterministic parts move to a workflow?</li> | |
| </ol> | |
| Most problems get fixed in steps 1–2. There's almost never a need to jump straight to "rewrite the prompt" or "swap models." | |
| </Box1> | |
| <div className="space-y-3 mt-6"> | |
| {aps.map((ap, i) => ( | |
| <div key={i} className="grid md:grid-cols-[1fr_2fr] gap-3 bg-zinc-900/50 border border-zinc-800 rounded-lg p-4 hover:border-zinc-700 transition"> | |
| <div> | |
| <div className="text-xs text-zinc-500 font-mono">#{String(i + 1).padStart(2, "0")}</div> | |
| <div className="font-bold text-rose-400">{ap.n}</div> | |
| </div> | |
| <div className="text-sm"> | |
| <div className="text-zinc-400"><span className="text-rose-300 font-semibold">Symptom:</span> {ap.s}</div> | |
| <div className="text-zinc-300 mt-1"><span className="text-emerald-400 font-semibold">Fix:</span> {ap.f}</div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </> | |
| ); | |
| }; | |
| const Ch17 = () => ( | |
| <> | |
| <p className="text-zinc-300 leading-relaxed"> | |
| Three stages mark the journey from beginner to mastery — and the gap between them is huge: | |
| </p> | |
| <div className="my-6 space-y-3"> | |
| <div className="bg-zinc-900/50 border-l-4 border-zinc-600 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-zinc-400 text-xs font-bold tracking-widest">STAGE 1</div> | |
| <div className="text-zinc-200 font-bold">Tool User</div> | |
| <div className="text-zinc-400 text-sm">"How do I use this feature?" — helpful but limited.</div> | |
| </div> | |
| <div className="bg-amber-500/5 border-l-4 border-amber-500 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-amber-400 text-xs font-bold tracking-widest">STAGE 2</div> | |
| <div className="text-zinc-200 font-bold">Process Optimizer</div> | |
| <div className="text-zinc-400 text-sm">"How do I make collaboration smoother?" — intentional CLAUDE.md and skills. Significant lift.</div> | |
| </div> | |
| <div className="bg-violet-500/10 border-l-4 border-violet-500 pl-4 py-3 rounded-r-lg"> | |
| <div className="text-violet-400 text-xs font-bold tracking-widest">STAGE 3</div> | |
| <div className="text-zinc-200 font-bold">System Designer</div> | |
| <div className="text-zinc-400 text-sm">"How do I make the agent operate autonomously under constraints?" — qualitative change in capability.</div> | |
| </div> | |
| </div> | |
| <Insight> | |
| The single test that matters: <em>if you can't clearly articulate what "done" looks like, the task isn't ready for autonomous execution.</em> No acceptance criteria → no notion of correctness → no agent will save you, no matter how capable the model is. | |
| </Insight> | |
| <h4 className="text-emerald-400 font-bold mt-6 mb-2">The take-home rules</h4> | |
| <Box1 tone="emerald"> | |
| <ol className="space-y-2 text-sm list-decimal list-inside text-zinc-300"> | |
| <li>The loop never changes. Add capabilities by layering, not by rewriting.</li> | |
| <li>The harness matters more than the model. Acceptance baselines, boundaries, feedback, fallback.</li> | |
| <li>Context engineering is about noise, not capacity. Layer information by stability and access frequency.</li> | |
| <li>Tools are designed for agents, not APIs for humans. ACI principles. Give Claude a dedicated tool when you want a behavior.</li> | |
| <li>Memory is four things, not one. Don't conflate them.</li> | |
| <li>Long tasks externalize state to files. Filesystem is your friend.</li> | |
| <li>Don't multi-agent until you've genuinely hit the single-agent ceiling. Protocols → isolation → collaboration.</li> | |
| <li>Pass@k tests boundaries. Pass^k tests regression. Mixing them lies to you.</li> | |
| <li>Fix the eval before tuning the agent.</li> | |
| <li>Encode constraints, don't document them. Linters and hooks beat README files.</li> | |
| </ol> | |
| </Box1> | |
| <p className="text-zinc-300 leading-relaxed mt-4"> | |
| That's it. Two articles' worth of hard-won lessons in 17 chapters. Go build something. | |
| </p> | |
| <div className="mt-8 p-5 bg-gradient-to-br from-amber-500/10 via-violet-500/10 to-cyan-500/10 border border-zinc-700 rounded-xl text-center"> | |
| <div className="text-zinc-300 text-sm">Sources distilled here:</div> | |
| <div className="text-amber-400 text-xs mt-2 font-mono">tw93.fun/en/2026-03-12/claude.html</div> | |
| <div className="text-violet-400 text-xs font-mono">tw93.fun/en/2026-03-21/agent.html</div> | |
| <div className="text-zinc-500 text-xs mt-3">Both worth a re-read once you've internalized the framing here.</div> | |
| </div> | |
| </> | |
| ); | |
| // ===== Chapter registry ===== | |
| const CHAPTERS = [ | |
| { id: 0, part: 1, title: "The Big Picture", subtitle: "Six layers and five surfaces", icon: Layers, comp: Ch1 }, | |
| { id: 1, part: 1, title: "The Agent Loop", subtitle: "20 lines of code, infinite range", icon: Repeat, comp: Ch2 }, | |
| { id: 2, part: 1, title: "Workflows vs Agents", subtitle: "Plus the 5 control patterns", icon: GitBranch, comp: Ch3 }, | |
| { id: 3, part: 1, title: "The Harness", subtitle: "Why scaffolding beats the model", icon: Shield, comp: Ch4 }, | |
| { id: 4, part: 2, title: "Context Engineering", subtitle: "Where your tokens really go", icon: Database, comp: Ch5 }, | |
| { id: 5, part: 2, title: "The Compression Trap", subtitle: "And how to take back control", icon: Box, comp: Ch6 }, | |
| { id: 6, part: 2, title: "Skills", subtitle: "On-demand workflow packages", icon: Sparkles, comp: Ch7 }, | |
| { id: 7, part: 2, title: "Tool Design", subtitle: "Three generations + ACI", icon: Wrench, comp: Ch8 }, | |
| { id: 8, part: 2, title: "Hooks", subtitle: "Deterministic guardrails", icon: Zap, comp: Ch9 }, | |
| { id: 9, part: 2, title: "Subagents", subtitle: "Isolation, not parallelism", icon: Users, comp: Ch10 }, | |
| { id: 10, part: 2, title: "Prompt Caching", subtitle: "The hidden architectural force", icon: Repeat, comp: Ch11 }, | |
| { id: 11, part: 2, title: "Memory Systems", subtitle: "Four types, four solutions", icon: Brain, comp: Ch12 }, | |
| { id: 12, part: 3, title: "Verification & Eval", subtitle: "Trust nothing, verify everything", icon: FileCheck, comp: Ch13 }, | |
| { id: 13, part: 3, title: "CLAUDE.md", subtitle: "The collaboration contract", icon: BookOpen, comp: Ch14 }, | |
| { id: 14, part: 3, title: "Multi-Agent", subtitle: "Coordination > parallelism", icon: Network, comp: Ch15 }, | |
| { id: 15, part: 3, title: "Anti-Patterns", subtitle: "The greatest hits of failure", icon: AlertTriangle, comp: Ch16 }, | |
| { id: 16, part: 3, title: "Conclusion", subtitle: "The 10 take-home rules", icon: Sparkles, comp: Ch17 }, | |
| ]; | |
| const PART_INFO = { | |
| 1: { name: "FOUNDATIONS", c: "text-amber-400", bg: "from-amber-500 to-orange-500" }, | |
| 2: { name: "THE LAYERS", c: "text-violet-400", bg: "from-violet-500 to-purple-500" }, | |
| 3: { name: "PRACTICE", c: "text-cyan-400", bg: "from-cyan-500 to-sky-500" }, | |
| }; | |
| // ===== Main ===== | |
| function App() { | |
| const [ch, setCh] = useState(-1); | |
| const [navOpen, setNavOpen] = useState(false); | |
| useEffect(() => { | |
| const el = document.getElementById('loader'); | |
| if (el) el.classList.add('hide'); | |
| }, []); | |
| const cur = ch >= 0 ? CHAPTERS[ch] : null; | |
| const partInfo = cur ? PART_INFO[cur.part] : null; | |
| const Home = () => ( | |
| <div className="max-w-4xl mx-auto"> | |
| <div className="text-center pt-12 pb-8"> | |
| <div className="inline-block mb-4 px-3 py-1 rounded-full border border-zinc-700 text-zinc-400 text-xs tracking-widest">A FIELD GUIDE</div> | |
| <h1 className="text-5xl md:text-7xl font-black tracking-tight"> | |
| <span className="bg-gradient-to-r from-amber-400 via-rose-400 to-violet-400 bg-clip-text text-transparent">Claude Code</span> | |
| <br /> | |
| <span className="text-zinc-200">& Agents</span> | |
| </h1> | |
| <p className="text-zinc-400 mt-6 max-w-2xl mx-auto"> | |
| Two long, dense articles by Tw93 about how Claude Code and agentic systems actually work — distilled into something you can stay awake through. Same depth, way more fun. | |
| </p> | |
| <button | |
| onClick={() => setCh(0)} | |
| className="mt-8 px-6 py-3 bg-gradient-to-r from-amber-500 to-violet-500 rounded-lg text-white font-bold hover:scale-105 transition shadow-lg shadow-violet-500/20" | |
| > | |
| Start the journey → | |
| </button> | |
| </div> | |
| <div className="grid md:grid-cols-3 gap-3 mt-12"> | |
| {[1, 2, 3].map((p) => { | |
| const chs = CHAPTERS.filter((c) => c.part === p); | |
| const info = PART_INFO[p]; | |
| return ( | |
| <div key={p} className="bg-zinc-900/50 border border-zinc-800 rounded-xl p-5"> | |
| <div className={`text-xs font-bold tracking-widest ${info.c}`}>PART {p} · {info.name}</div> | |
| <div className="text-zinc-300 text-sm mt-3 space-y-1"> | |
| {chs.map((c) => ( | |
| <button | |
| key={c.id} | |
| onClick={() => setCh(c.id)} | |
| className="block text-left hover:text-zinc-100 hover:translate-x-1 transition" | |
| > | |
| {c.id + 1}. {c.title} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| <div className="mt-10 mb-6 p-5 bg-zinc-900/30 border border-zinc-800 rounded-xl"> | |
| <div className="text-zinc-400 text-sm"> | |
| <span className="text-zinc-200 font-semibold">How to read this:</span> chapters are short and standalone — pick any one, or click "Start" and walk through them in order. Each chapter has a custom diagram and a key insight callout. Total reading time is about 30-45 minutes if you go straight through. | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| return ( | |
| <div className="min-h-screen bg-zinc-950 text-zinc-100 antialiased"> | |
| <div className="sticky top-0 z-30 bg-zinc-950/90 backdrop-blur-md border-b border-zinc-800"> | |
| <div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between"> | |
| <button onClick={() => setCh(-1)} className="flex items-center gap-2 hover:opacity-80 transition"> | |
| <div className="w-7 h-7 rounded-md bg-gradient-to-br from-amber-400 to-violet-500" /> | |
| <div className="font-bold text-sm tracking-tight">The Field Guide</div> | |
| </button> | |
| {ch >= 0 && ( | |
| <> | |
| <div className="hidden md:flex items-center gap-1 text-xs text-zinc-500"> | |
| <span>Chapter {ch + 1} / {CHAPTERS.length}</span> | |
| <span className="mx-2">·</span> | |
| <span className={partInfo.c}>{partInfo.name}</span> | |
| </div> | |
| <button onClick={() => setNavOpen(!navOpen)} className="md:hidden p-2 text-zinc-400 hover:text-zinc-100"> | |
| {navOpen ? <X size={18} /> : <Menu size={18} />} | |
| </button> | |
| </> | |
| )} | |
| {ch < 0 && <div className="text-xs text-zinc-500">distilled from tw93.fun</div>} | |
| </div> | |
| {ch >= 0 && ( | |
| <div className="h-0.5 bg-zinc-800"> | |
| <div | |
| className={`h-full bg-gradient-to-r ${partInfo.bg} transition-all duration-500`} | |
| style={{ width: `${((ch + 1) / CHAPTERS.length) * 100}%` }} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| {ch < 0 ? ( | |
| <div className="px-4 pb-16"> | |
| <Home /> | |
| </div> | |
| ) : ( | |
| <div className="max-w-7xl mx-auto px-4 flex gap-8"> | |
| <aside className={`${navOpen ? "block" : "hidden"} md:block fixed md:sticky top-20 md:top-16 left-0 right-0 md:right-auto z-20 md:z-0 bg-zinc-950 md:bg-transparent w-full md:w-64 md:flex-shrink-0 h-screen md:h-[calc(100vh-4rem)] overflow-y-auto p-4 md:p-0 md:py-6 border-r md:border-0 border-zinc-800`}> | |
| {[1, 2, 3].map((p) => { | |
| const chs = CHAPTERS.filter((c) => c.part === p); | |
| const info = PART_INFO[p]; | |
| return ( | |
| <div key={p} className="mb-6"> | |
| <div className={`text-[10px] font-bold tracking-widest mb-2 ${info.c}`}>{info.name}</div> | |
| <div className="space-y-0.5"> | |
| {chs.map((c) => { | |
| const Ic = c.icon; | |
| const active = c.id === ch; | |
| return ( | |
| <button | |
| key={c.id} | |
| onClick={() => { setCh(c.id); setNavOpen(false); }} | |
| className={`w-full text-left flex items-center gap-2 px-2 py-1.5 rounded text-xs transition ${active ? "bg-zinc-800 text-zinc-100" : "text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900"}`} | |
| > | |
| <Ic size={12} className={active ? info.c : ""} /> | |
| <span className="flex-1 truncate">{c.title}</span> | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </aside> | |
| <main className="flex-1 min-w-0 py-6 md:py-12 max-w-3xl"> | |
| <div className="mb-8"> | |
| <div className={`text-xs font-bold tracking-widest mb-2 ${partInfo.c}`}> | |
| CHAPTER {String(ch + 1).padStart(2, "0")} · {partInfo.name} | |
| </div> | |
| <h2 className="text-3xl md:text-5xl font-black tracking-tight text-zinc-100"> | |
| {cur.title} | |
| </h2> | |
| <p className="text-zinc-400 mt-2 text-lg">{cur.subtitle}</p> | |
| </div> | |
| <div className="prose prose-invert max-w-none"> | |
| <cur.comp /> | |
| </div> | |
| <div className="mt-12 pt-6 border-t border-zinc-800 flex items-center justify-between gap-3"> | |
| {ch > 0 ? ( | |
| <button | |
| onClick={() => setCh(ch - 1)} | |
| className="flex items-center gap-2 px-4 py-3 rounded-lg bg-zinc-900 hover:bg-zinc-800 text-zinc-300 transition flex-1 group" | |
| > | |
| <ChevronLeft size={18} className="group-hover:-translate-x-1 transition" /> | |
| <div className="text-left"> | |
| <div className="text-[10px] text-zinc-500 tracking-widest">PREVIOUS</div> | |
| <div className="text-sm font-semibold">{CHAPTERS[ch - 1].title}</div> | |
| </div> | |
| </button> | |
| ) : <div className="flex-1" />} | |
| {ch < CHAPTERS.length - 1 ? ( | |
| <button | |
| onClick={() => setCh(ch + 1)} | |
| className="flex items-center gap-2 px-4 py-3 rounded-lg bg-gradient-to-r from-amber-500/20 to-violet-500/20 border border-zinc-700 hover:border-zinc-500 text-zinc-100 transition flex-1 group justify-end" | |
| > | |
| <div className="text-right"> | |
| <div className="text-[10px] text-zinc-500 tracking-widest">NEXT</div> | |
| <div className="text-sm font-semibold">{CHAPTERS[ch + 1].title}</div> | |
| </div> | |
| <ChevronRight size={18} className="group-hover:translate-x-1 transition" /> | |
| </button> | |
| ) : ( | |
| <button | |
| onClick={() => setCh(-1)} | |
| className="flex items-center gap-2 px-4 py-3 rounded-lg bg-gradient-to-r from-amber-500 to-violet-500 text-white transition flex-1 group justify-end font-semibold" | |
| > | |
| <span>Back to start</span> | |
| <ChevronRight size={18} className="group-hover:translate-x-1 transition" /> | |
| </button> | |
| )} | |
| </div> | |
| </main> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| ReactDOM.createRoot(document.getElementById('root')).render(<App />); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment