Recep ŞEN

TB1REC

CTO @Taptoweb

I design the architecture behind Easyapp.ai at Taptoweb and work on distributed systems and AI agents.

Download CV
Recep ŞEN - CTO profile photo
← Back to Blog
Stop Writing Code, Start Managing Systems: The Real Potential of AI-Powered Development
· 46 min read

Stop Writing Code, Start Managing Systems: The Real Potential of AI-Powered Development

What months of running Claude Code in production across several projects taught me: the shift from writing code to orchestrating AI systems, covering CLAUDE.md strategy, context management, hooks, agents, model routing, multi-agent workflows, and why verification is the real bottleneck.

Introduction: I Was Already Using AI Tools

I won't open with what Claude Code is or how to install it. If you're reading this, you probably already use some AI tool, whether that's Cursor, Copilot, Windsurf, or ChatGPT in a browser tab, and you have your own opinion of where they help and where they get in the way.

That was me too. I've written software for years, and lately I've leaned on AI tools heavily. Cursor came first, and it showed me how comfortable AI-assisted work inside an IDE can be. Then my project grew and the answers got worse. An IDE tool is built as an assistant: you write, it completes. That holds up well to a certain size.

Claude Code felt different from the first week. It runs in the terminal, reads your files, runs commands, handles git, and can start other Claude instances to work in parallel. It behaves less like an assistant and more like a development environment, and what you get out of it depends heavily on how you set it up.

I've used it in production for months across several projects and stacks: .NET backends, React Native apps, Go services, Next.js frontends. This article collects what I learned there, including the things I got wrong.

Read it as a way of approaching the work rather than a list of buttons to press. The commands will change. The reasoning behind them has lasted a lot longer.

Section 1: You Need a Mindset Shift

Think Like a CTO, Not a Junior Developer

The most common mistake I see is treating AI as something that writes code for you. "Write this function," "fix this bug," "add this endpoint" are fine requests, but they cover a small part of a project.

A CTO looks at a project from start to finish, and that view is the one that pays off here:

  • Planning: What are we building and why?
  • Architecture: How will we structure it?
  • Development: How will we implement it?
  • Testing: How will we verify it works correctly?
  • Security: How will we detect vulnerabilities?
  • Performance: Is it fast enough?
  • Deployment: How will we ship it?

Claude Code can help at each of these stages, and most of this article is about how.

Look at what's missing from that list: typing the code. What carries over into this way of working is knowing how software behaves. If you know where systems break and what a given test actually proves, you can direct a model without reading every line it writes. If your main strength is syntax, you're competing with the model on the thing it does most cheaply.

Learn to Think Probabilistically

AI isn't deterministic. The same prompt can give different results on two runs. That's simply how these models work, so plan around it:

  • Expect to iterate. Review the first output, say what's wrong, run it again.
  • Build ways to verify. Claude saying "done" tells you very little. Check that the build works, the tests pass, and the security scans come back clean.
  • Add deterministic layers. Hooks, linters, and test suites put certainty on top of a probabilistic model.

Most of the later sections build on this.

Learn to Work at the Speed of Thought

Once the model is fast, the slow part of the loop is you. You spot a bug, switch to the terminal, describe it, take a screenshot, paste it, and switch back. Each switch costs a little focus, and by the end you've half forgotten what you meant to say next.

So I set things up to get input to the model the moment I think of it. Speech-to-text sits on a hotkey, because talking is faster than typing and a prompt doesn't need clean grammar. A clipboard manager keeps everything I've copied recently. Screenshots of visual bugs go straight into the conversation. It ends up feeling like working next to a person: "look, this part is broken, fix that too, oh, and there's this other thing."

Section 2: CLAUDE.md: What Every Session Reads First

What It Is

CLAUDE.md is the instruction file Claude Code reads at the start of every conversation. Claude remembers nothing from earlier sessions, so this file is how it "knows" your project each time it starts over.

This file shapes your day-to-day experience with Claude Code more than any setting does. A good one won't make up for missing engineering skill, but a bad one will frustrate even an experienced developer.

Less is More: The 100-Line Rule

My first CLAUDE.md ran past 160 lines. I had written out every rule and every command in detail, and Claude started missing the things that mattered. The longer the file, the thinner its attention got.

Now I keep CLAUDE.md under 100 lines and move the details into separate files, which Claude opens when it needs them:

CLAUDE.md (70-100 lines)              - Always read
.claude/rules/architecture.md         - Referenced when needed
.claude/rules/security.md             - Referenced when needed
docs/claude/patterns.md               - Referenced when needed

The usual name for this is "progressive disclosure": the model sees information as it becomes relevant.

The WHAT / WHY / HOW Structure

I split CLAUDE.md into three parts.

WHAT: What is the project? Tech stack, architecture, core concepts. One paragraph is enough.

WHY: Why did we make these decisions? This is the part people skip, and the one that helps most in edge cases. Instead of "use the PaymentClient wrapper," write "use the PaymentClient wrapper, because it adds idempotency keys and a raw HTTP retry can charge a customer twice." Claude then understands that the rule also covers the new refund endpoint nobody mentioned.

HOW: How do we work? Commands, test patterns, naming conventions. Show examples here; one good snippet explains a convention better than three paragraphs about it.

The "Don't" Section, and Where Its Rules Belong

Claude has known tendencies, and a "Don't" section in CLAUDE.md is the usual place to name them:

  • Tendency to over-engineer (extra abstractions, unnecessary interfaces)
  • Tendency to create too many files (splits things that could be solved in a single file)
  • Tendency to add error handling for impossible scenarios
  • Tendency to add docstrings and comments (even when not asked)
  • Tendency to create stub files (file exists but is filled with return null, return {}, TODO, console.log placeholders; the existence of a file doesn't mean real implementation)

Name them, but keep the list short and ask where each item really belongs. Current models follow instructions closely, almost literally, so every "never" and "always" gets applied everywhere, including places you didn't have in mind. Give a model a page of prohibitions and it turns cautious. It hedges, asks permission for trivia, and plays it safe on exactly the work where you wanted ideas. On design, naming, copy, or a new approach, a long rulebook mostly makes the output duller.

So sort the list. If a rule can be checked mechanically (stub files, console.log leftovers, a forbidden import), take it out of the prompt and put it in a hook or a linter (Section 6), where it runs every time and costs no context. If it's a matter of taste, state the goal once and give the model room. Prompt rules are worth keeping in the few areas where a mistake is expensive: migrations, money, security. Even there I treat the line in CLAUDE.md as a reminder and rely on the hook for enforcement.

3-Layer Boundary Setting

Tell Claude plainly what it can do on its own, when it should ask, and what it must never do:

  • Always do: Write tests, run tests, follow naming conventions
  • Ask first: Database migrations, adding new dependencies, large refactoring
  • Never do: Committing secrets, editing lock files, deleting tests

With these three layers Claude knows where it can move freely and where it has to stop and check with you. The "Never" list also makes a good first set of hook candidates, since almost everything on it can be blocked outright (Section 6).

Global CLAUDE.md

~/.claude/CLAUDE.md holds instructions that apply to every project. Mine has my communication preferences (summaries in Turkish, technical terms in English), the CLI tools I use often, and a few general working principles, so I don't repeat them per project.

Path-specific Rules: Right Rule, Right File

Say you want the same rules for all your test files, and those files are spread across 50 directories. Putting a CLAUDE.md in each one would be absurd.

Instead, define rules by file pattern in .claude/rules/:

---
paths: ["**/*.test.tsx"]
---
Use AAA (Arrange-Act-Assert) pattern in test files.
Prefer real database connections over mocks.

**/*.test.tsx matches every test file in the codebase, wherever it lives. The same works for **/*.api.ts on API endpoints or terraform/**/* on Terraform files, each with its own rules.

These rules also save context, because they load only when Claude is editing a matching file.

Section 3: Context Window: The Concept to Understand First

What It Is and Why It Matters

The context window is Claude's working memory. Every message, every file it reads and every command output lands in the same shared budget. On current Claude models in Claude Code that budget is a million tokens by default. That's a lot of room. A bigger window doesn't change how the model degrades, though; it just gives the degradation more space.

Most developers assume things will keep working fine until context hits 100%.

In practice quality does slip as a session grows, though newer models hold up far longer than the ones I started with. You'll recognize the symptoms: Claude forgets earlier instructions, repeats mistakes it already made, edits the wrong files. I set my original rule on a 200k window: act around 40%, never wait for 60%. On a million-token window a percentage is the wrong unit, since 40% is already 400k tokens of history, and current models often stay stable well past that. What hurts the model is the sheer amount of history and how much of it is stale or off-topic, so that's what I watch now. When finished subtasks and old debug output take up a large share of it, I act, whatever the bar says.

Context also goes bad as it fills. People call this "context rot."

Old tool outputs, debug traces from bugs you already fixed, earlier versions of files you've since refactored: all of it stays in context, and the model can't tell what's current from what's outdated. So stale information does more than take up space. The model treats it as current and builds on it, referencing variables that were renamed and following patterns that changed.

That's why so many multi-agent setups fall apart after a handful of tasks. The model hasn't gotten any worse. Its context has.

This picture keeps improving, to be fair. Each model release holds up better in long sessions, and Claude Code keeps getting smarter about what it compacts, what it summarizes and what it saves to memory. Today's terminal agents stay coherent in situations that would have derailed them a year ago, sometimes even near a full window. So treat the advice in this section as a way of working rather than a set of laws. It makes sessions noticeably better, but none of it is a hard limit.

One Conversation = One Task

One habit holds at any window size: one conversation, one task. Auth system, database schema, UI components, tests: don't try to do them all in a single conversation. Give each its own session.

You might object that you just explained everything and don't want to explain it again. You won't have to. CLAUDE.md knows your project, and session memory holds the current state (the next subsection covers how), so a new session doesn't start from zero.

Memory Between Sessions

Claude is stateless; every conversation starts from scratch. Claude Code now covers part of this itself with auto memory: Claude writes notes from your corrections and preferences into a per-repository memory folder, and the index of that folder loads at the start of each session. For more structured project knowledge I also use the project-memory feature of the OMC (oh-my-claudecode) plugin. It's a project-scoped store that keeps tech stack, architectural decisions, conventions, notes and directives in JSON, and every session reads it automatically.

The tool matters less than the habit: save what matters when a session ends, read it when the next one starts.

One version of this pays for itself quickly. When a session solves something that took real effort (a build quirk, a dependency conflict, a config that finally worked), have the model write the solution down before you close the window. Otherwise it disappears with the context and some future session pays the full price again.

Practical Context Management

Use a HUD (Heads-Up Display): Claude Code's status line is scriptable (/statusline), and I run the OMC plugin's HUD in it. It shows:

  • Active model
  • Session duration
  • Tokens spent
  • Current cost and hourly cost rate
  • Cache hit rate
  • Permission mode; cycle with Shift+Tab

Context fill bar: glance at it, but on a million-token window it climbs slowly and doesn't tell you much by itself. Read it alongside the question above: how much of what's in there still matters?

/compact timing: Run it before quality starts slipping, as soon as context starts filling up with finished work.

Clean start with /clear: Start a new session when a task is done or the topic changes.

Information ordering matters: Models handle the beginning and end of long inputs well and can miss things buried in the middle. If you're sending a long analysis or an error log, put the important part first. Opening with "there's a null pointer in this function, relevant log output below" works far better than leaving the clue in the middle of a 500-line log.

Red flags: if the symptoms from the top of this section show up, you're already late with the cleanup.

What Goes In Matters as Much as How Much

Managing context also means choosing what goes in. Before I feed anything to the model I ask three things:

  • Recency: Is this data current? Stale API docs or an outdated schema waste space and also mislead. If something might be out of date, have Claude fetch the current version instead of pasting the old one.
  • Relevance: Does it actually bear on the task? A 500-line log where 20 lines matter is 480 lines of noise the model has to see past.
  • Your own signal: Tell the model where you stand. "I know this domain well, skip the basics" and "I've never touched Kubernetes, explain your choices" get different answers, each pitched at the right level.

Instructions and Data Are Not the Same Thing

There's a related problem. Everything you type into the prompt carries your authority, and the model reads all of it as "this is what the user wants." That goes wrong when half of it is reference material: a long log, an API doc, someone else's error report. Sentences inside that material can be taken as directives, and the model starts acting on text you only meant as background.

For short material, wrap it in explicit markers (a fenced block, an XML-style tag) and label it: "the following is reference data, not instructions." Anything long, don't paste at all. Save it to a file, even a temp one, and give Claude the path along with what you want done with it. The content then enters context as a tool result, which the model treats as data to interpret rather than commands to follow, and it decides for itself which parts to keep. Your prompt is left holding only what you actually want.

When Claude Is Stuck in a Loop

Sometimes the model keeps hitting the same error and tries small variations of the same broken fix. More prompting rarely rescues a session at that point, because the failed attempts are still in context and pull every new attempt back toward them. What works for me:

  1. Revert the broken changes with git.
  2. Ask the current session for a short summary: what was tried, what failed, what the error messages said.
  3. Start a fresh session and hand it that summary.

The same model that flailed for an hour will often solve the problem in minutes with a clean context, because its own failed attempts are no longer in front of it.

The Hidden Cost of CLAUDE.md Size

CLAUDE.md loads in every session and sits in front of every message. Prompt caching takes most of the sting out of the bill: after the first request those tokens come from the cache at a small fraction of the normal input price. What caching doesn't fix is attention. Every line competes with your actual task on every turn, which is the real reason to keep the file short (and to prune it, as Section 12 argues).

Section 4: Deep Research: Read Before You Build

Where It Pays Off

Deep research has saved me more time than almost anything else. One pass before starting a project or making a complicated decision saves a lot of trial and error.

My Workflow

I don't tie deep research to a single tool. Claude.ai, ChatGPT and Gemini all have a deep research mode or something close to it, and each is good at different things. The process matters more than which one you pick:

Step 1: Start with a conversation. Chat with the AI about the topic, and instead of only asking questions, ask it to question you. Something like "ask me questions about this topic so we understand my requirements." This shows you what information the AI is missing.

Step 2: Answer the AI's questions. Expect 10-15 of them. Answering clarifies your own thinking and gives the AI a lot of context to work with.

Step 3: Generate the deep research prompt. At the end, tell it "now write me a deep research prompt based on this conversation." You'll get a detailed, focused prompt that carries everything you discussed.

Step 4: Send it to deep research. Run that prompt through Claude.ai's Research mode, ChatGPT's deep research or Gemini Deep Research, and compare what comes back.

Step 5: Bring the results to Claude Code. Take the findings into your Claude Code session before you write any code.

When Should You Use Deep Research?

  • Choosing a new technology or framework
  • Complex architectural decisions (monolith vs microservices, event-driven vs request-driven)
  • Performance optimization strategies
  • Determining security approach
  • Strategy before a major refactoring
  • Best practices in an area you're unfamiliar with

What to Do with the Output

The AI pulls together a large amount of material and writes a detailed analysis for your specific situation, with sources. Save the output as a document in the repo (under docs/, say) and point CLAUDE.md at it instead of pasting it in. The 100-line rule stays intact, and later sessions read the research when they need it.

Section 5: Permission Modes and the Plan-Execute-Verify Loop

Permission Modes

Claude Code has six permission modes, and Shift+Tab cycles through the everyday ones:

Manual (default): Asks for approval on every file edit and command execution. Safest, slowest.

Accept Edits: Approves file edits automatically, which amounts to telling Claude "you know what you're doing, carry on." It still asks before shell commands.

Auto: Runs everything, but a second model, a classifier, reviews each action in your place and stops the risky ones. On Pro, Max, and Team plans it's now the default for new sessions.

Bypass: Approves everything with no review at all. It speeds up trusted, well-defined tasks, and it only makes sense once your hook safety net exists (Section 6), because then the deterministic guardrails are doing the watching for you. Without hooks, Bypass is just Manual mode with the safety taken off.

I think of these modes as a trust ladder. Start a new project in Manual, even if your install defaults to Auto. After you've watched the model work in your codebase and seen where it stumbles, move to Accept Edits, then to Auto. Auto is a good middle rung: the classifier catches actions that are dangerous anywhere, and your hooks catch the ones that are dangerous in your project. Give Bypass only to task types that have earned it, with hooks covering the risky edges. People who hand over full autonomy on day one tend to end up with deleted files and force-pushed branches.

Plan Mode: A Separate Working Mode

Plan Mode is in the same Shift+Tab cycle but behaves differently. Claude can't change anything; it analyzes and proposes a plan. I use it before any big change.

Before Plan Mode, though, comes alignment. Tell an AI to "build an auth system" and it makes 30 decisions in the first 5 minutes: JWT or session, email verification or not, OAuth or password-only, what the error messages look like. You find out which way it went only when you see the result.

So before planning, have the AI find the grey areas and ask you concrete questions about them. Something like: "Before planning this feature, identify the grey areas and points where multiple reasonable approaches exist, then ask me." Ask it to lay out each ambiguous point as options: "Session management: A) JWT refresh rotation, B) Redis server-side session, C) your preference."

A 10-minute alignment session costs far less, in tokens and in time, than changing direction halfway through the implementation.

The Plan -> Execute -> Verify Loop

Of everything I've tried, this pattern pays off most reliably, and it's what the CTO mindset looks like in practice:

  1. Switch to Plan Mode: Let Claude analyze the project and plan what to do
  2. Review the plan: Does it make sense? Is anything missing? Request corrections if needed
  3. Switch to Accept Edits or Auto: Let Claude implement the plan
  4. Verify: Does the build work? Do tests pass? Is the expected behavior achieved? Ideally checked by a fresh session, not the one that wrote the code (Section 11).

"All steps completed" doesn't count as verification. You verify by observing outcomes, and it helps to think about them on three levels:

  • Behavioral: "Can the user log in?" You need to run it and observe.
  • Structural: "Is auth.ts a real implementation, or a stub filled with TODOs and return null?" The file existing isn't enough; the content has to be real.
  • Connective: "Does the login handler actually import and use the function from the auth module?" Files can exist independently while not being connected to each other.

The name for this is "goal-backward verification." Instead of "what did I do?" you ask "what was supposed to happen, and is it happening?" It matters a lot with AI-written code, because that's how it most often fails: the files exist, the build passes, and the pieces aren't wired together.

I reach for this loop mostly on changes that touch several files. Seeing what Claude intends to do before approving it saves time and catches mistakes early.

Section 6: Hooks: Deterministic Safety Nets

Philosophy

Claude is a probability machine, and probabilities sometimes miss. A hook is a mechanical lock: whatever the model decides, the forbidden action can't happen.

That's how you put deterministic guarantees around a probabilistic system. Most of the safety in the rest of this article is built on it.

Where do you draw the line? If a single failure can cost money or open a security hole, you need a deterministic guarantee. Prompts can handle the rest. Cheap hooks that remove daily friction are still nice to have; they just aren't mandatory in the way the safety ones are.

Hook Types

Claude Code exposes more than thirty hook events, from session start to compaction to a subagent finishing. Five of them cover most of what you'll need:

  • PreToolUse: Before a tool is called (ability to block)
  • PostToolUse: After a tool is called (ability to validate)
  • SessionStart: When a session starts (environment setup)
  • UserPromptSubmit: When a user sends a message (input control)
  • Stop: When the agent stops (cleanup)

Start with these and add others only when a specific failure calls for one.

3 Hooks to Start With

Don't try to build a whole hook ecosystem at once. These three are enough to begin with:

1. Dangerous Git Protection (PreToolUse:Bash): Blocks destructive git commands like git push --force, git reset --hard and git checkout .. This one hook has saved me more times than I can count. Auto mode's classifier now stops many of these by itself; the hook makes sure of it.

2. Conventional Commits (PreToolUse:Bash): Makes commit messages follow the feat(scope): description format. Without it, Claude now and then writes something meaningless like "fix stuff."

3. Convention Guard (PostToolUse:Edit|Write): Checks your project-specific rules on every file write. In a TypeScript project that might be any types, leftover console.log calls, or @ts-ignore. Keep the division of labor in mind. Your linter already catches generic style mechanically, so don't spend context teaching Claude what Biome or ESLint enforces for free. This hook is for rules only your project knows about.

Advanced: Architecture Guard

In my backend project this is the hook I'd keep if I could keep only one. On every file change it checks for Clean Architecture layer violations. An EF Core import in the Domain layer or an Infrastructure reference in the Application layer gets caught and blocked.

Section 7: Custom Agents: Specialists You Define Once

What Is an Agent?

An agent is a Claude instance set up for one kind of task. Agents live as markdown files in the .claude/agents/ directory, and each one has a system prompt, a set of allowed tools and a model.

Why Should You Use Custom Agents?

People tend to put the effort into the persona, and on current models that buys little. A system prompt that opens with "you are a world-class security expert" barely changes the output. What an agent file gets you is mostly mechanical:

  • Isolation: it runs in its own context, so its file reads and dead ends never reach your main session
  • Tool limits: a reviewer that only has Read and Grep can't "helpfully" fix what it finds
  • A pinned model: the agent runs on the tier you chose for that job, not on whatever the main session happens to use

The prompt still matters, just not as a role to play. A short checklist of what this project counts as a security problem does more than three paragraphs about what makes a good security reviewer. Write agent prompts the way you write CLAUDE.md: short, specific, about your project.

Essential Agent Strategy

I'd set up at least these two in every project:

Code-reviewer agent: Checks code quality, pattern conformance, naming conventions, and whatever your project's checklist says. Give it read-only tools and Opus. Review is judgment work, and that's where the stronger model is worth what it costs (Section 9).

Debugger agent: Does error analysis, root cause identification and log reading. Limit it to the Read and Bash tools and approve the fix yourself.

Section 8: Multi-Agent Work: Keeping the Main Thread Clean

Subagents and the Context Advantage

Subagents are independent Claude instances spun up for short, focused tasks. Each one gets its own context, separate from yours.

So even when your main session's context is crowded, a subagent starts clean. Delegate research, a code review or a file search, and the file reads and intermediate output stay inside the subagent. Only its summary comes back, and your main context doesn't bloat.

Keep the Main Thread on the Main Task

People usually ask how many subagents they can run in parallel. I spend more time deciding where each side trip should go.

Mid-task, questions come up that have nothing to do with the plan. How does this library handle retries? Why did that unrelated test start failing? Is there a better pattern for this? Answer them in the main session and their file reads and dead ends end up in the context you need for the real work, and the model's attention drifts along with them.

I keep the plan and the main work in the main session and send everything off-plan somewhere else:

  • A side question you need answered now goes to a subagent, which reports back a summary.
  • A quick question about the current session goes to /btw, which answers it without adding it to the conversation history.
  • Something you'll need later goes to a background agent, and you keep working while it runs.
  • A risky experiment goes to an agent in its own git worktree, which you can throw away whole.

There's an easy way to tell how you're doing. The main session's context should be mostly about the task you opened it for. If half of it is a detour, that detour should have been delegated.

Teams: Persistent Coordination

Agent Teams let you run several Claude Code instances as a team. Team members differ from subagents in three ways: they persist, they can message each other, and they share a task list.

When to use subagents?

  • One-off tasks: file search, quick code review, format checking
  • Tasks where you need to wait for the result before continuing

When to use teams?

  • Comprehensive PR review (security + quality + performance + test coverage in parallel)
  • Large feature development (backend + frontend + test in parallel)
  • Complex refactoring (only when the modules are genuinely independent; see Section 14)

Practical Scenario: Comprehensive Code Review

Say you need to review a PR. "Is the code nice?" is too narrow a question. I look at it from several angles:

Team Lead (you): Coordination
Teammate 1: Security review (OWASP, input validation, auth)
Teammate 2: Code quality (patterns, SOLID, cleanliness)
Teammate 3: Performance analysis (N+1 queries, memory leaks, caching)
Teammate 4: Test coverage (missing tests, edge cases)

The 4 agents run in parallel, each with its own checklist and its own clean context, and their results get combined into one review report. The finding phase takes minutes instead of an afternoon.

The catch is that four agents produce four reports, and somebody still has to reconcile them. Parallelism speeds up discovery and multiplies the review output at the same time. Plan for a synthesis step, or better, have one agent merge and deduplicate the findings before they reach you. Section 11 explains why that reconciliation cost is what limits your throughput.

Section 9: Model Routing: The Right Model for the Right Job

Four Tiers, Two Dials

Haiku: Fast, and at about a twentieth of Sonnet's price, nearly free. Good for searches, finding files, format checks and quick questions. Keep it on read-only work.

Sonnet: Balanced. Standard implementation, debugging, and most day-to-day work.

Opus: Deep reasoning. Complex architectural decisions, tough bugs, large refactoring, security audits, code review. Roughly twice Sonnet's price, and more consistent from run to run.

Fable: The most expensive tier, at two and a half times Opus, and that price doesn't buy the top scores. On Anthropic's own launch table Opus 5.5 is ahead of Fable 5.1 on every published benchmark, and independent indexes put the two close together. I don't treat Fable as an upgrade. I keep it as a second opinion for long-horizon problems where Opus has already fallen short.

The second dial is effort, meaning how long the model thinks before it answers, from low to max. It moves cost almost as much as the choice of model, and it doesn't always buy quality. More effort helps on a hard, ambiguous problem. On a clear, well-specified task you mostly get a slower, more expensive copy of the same answer. A good spec does more than extra effort: give the model paths, acceptance criteria and an output format, and low effort usually matches max. In one hands-on comparison I collected, Sonnet got full marks on a set of everyday tasks at low effort for $1.60, and the same marks at max effort for $10.

Strategy

Here's how I route today. Sonnet drives the session, Opus sits beside it as an advisor, and Haiku handles read-only search. The advisor is a Claude Code feature (/advisor opus) where the main model decides when to consult a stronger one. I tell it to do that at decision points: before committing to a plan, when the same error comes back, and before declaring the work done. It runs on the Anthropic API only, so you won't find it on Bedrock or Vertex. Each consultation re-reads the whole transcript at Opus prices, so it has a cost. In return you get Opus-level judgment at the moments that need it while Sonnet does the typing, which is the producer/verifier split from Section 11 built into the session. When a session is mostly judgment (architecture, a tricky root cause), I run Opus as the main model instead.

Subagents get the cheapest tier that can do their job. Mechanical sweeps (file scans, renames, extraction) go to Haiku, well-specified code and docs to Sonnet, ambiguous debugging and reviews to Opus. I also override one default. In current versions the built-in Explore agent runs on your main model, so every codebase search is billed at main-model prices. A user-level agent named exactly Explore, with Haiku and read-only tools, moves those searches to the cheap tier. The full setup, with prices and the benchmarks behind it, is in my claude-model-advisor skill.

Task length matters as much as complexity. On long autonomous runs a model's per-step reliability counts for more than its raw intelligence, and Section 13 does the math on why.

Then there's how much initiative the model takes. Deep-reasoning models second-guess their input. Tell one "the test suite already ran and passed, do the final check and commit," and it may decide to check your claim first, re-running work you already did and sometimes overwriting results you wanted kept. On an open-ended problem that skepticism is what you're paying for. In a well-specified automation step it gets in the way, and the model you want is the one that follows the checklist as written.

I wouldn't hold any of this too tightly. The gaps between frontier models are smaller than the marketing suggests, they shift with every release, and a model that wins at one kind of task loses at another. Choosing a model per task is mechanical dispatch, the kind of thing the harness should do for you, and it's already heading there: the advisor and pinned agent models are early versions of it. Until then, a simple tiebreaker works. When in doubt, the cheaper model is usually enough. Measure cost per finished task rather than per request, though, because a cheap run you have to redo ends up costing more.

Measure It Instead of Guessing

Since I stopped running everything on Opus, my daily spend has dropped by about half, by my own rough estimate. Impressions are easy to get wrong, so I wanted real numbers. Claude Code exports logs, metrics, and traces over OpenTelemetry, and I wrote a gist that sends them to a local Aspire Dashboard with a single command. You can hand the gist to Claude Code and let it do the setup. You get token counts and an estimated cost for every request, split by model and by main agent versus subagent. Every message also gets a trace showing how long each API call and tool took, and how long Claude sat waiting for you to approve something. That last number is a good check on the trust ladder in Section 5. The cost figures use API list prices, so on a subscription they show usage rather than your bill.

Section 10: Cross-Model Orchestration: Using Different AIs Together

A Single Model Isn't Enough

Using models from different vendors together has paid off more than I expected.

Claude: My home base. Sonnet does the daily work, and Opus brings the judgment, as advisor or as the main model on hard sessions (Section 9).

OpenAI Codex/GPT: Code analysis, checking plans, pushing back. Most useful to me as a different point of view.

Google Gemini: A third model family, handy as a second reader on large passes across many files. Claude has a million tokens too now, so what I pick Gemini for now is its different blind spots. A model can't flag its own blind spots, and a model from another family often trips over them right away. (Section 3's selection rules apply on both sides.)

Section 11: Verification Is the Bottleneck

Generating Is Cheap, Checking Is Not

When I started orchestrating AI, nobody warned me that generating code would stop being the constraint. Claude produces in an hour what used to take me a day. Properly verifying that hour of output, though (reading the diff, running the flows, checking the edge cases), can eat several hours of my attention.

Attention doesn't scale the way agents do. Once review costs more than generation, you start skimming, and skimming slides into trusting. Sooner or later something broken reaches production and it turns out "done" was never verified.

Seen this way, the earlier sections are all about verification. Hooks (Section 6) are verification that runs automatically. Tests are verification that runs while your attention is elsewhere. The Plan-Execute-Verify loop from Section 5 works because the Verify step is a routine, so nobody has to rescue it with heroics. Generation stopped being my limit a while ago; what limits me now is how much I can verify before I burn out.

The Question to Ask First: Where Is My Oracle?

Some projects feel this bottleneck far less than others. When there's a mechanical reference that decides what's correct (a test suite, a reference implementation, a formal spec), verification stops costing attention. Testing literature calls this an oracle. With one in place the instruction itself changes: instead of reviewing output, you say "iterate until the check passes" and walk away.

That's why agents look almost magical on migrations and ports. The old system defines correct behavior down to the last edge case, so the agent can grind toward a fixed target without a human grading each attempt. For rewrites I'd borrow one tactic from this: point the agent at the old system's behavior rather than its source, so it reproduces what the code does without inheriting how it does it.

So before starting anything sizable, I ask where my oracle is. Sometimes one already exists. Sometimes you can build a partial one cheaply: characterization tests around current behavior, golden files, a contract for the API. Each piece turns human verification into machine verification. The same holds outside code: machine search moves fastest in fields where candidate answers are cheap to check, and a proof checker is about as pure an oracle as exists. Every check you automate moves your own project a little closer to that end.

The Producer Should Never Grade Its Own Work

My most important verification habit is simple: never let the session that wrote the code judge it.

Try it. Have Claude write something genuinely complex, a change that touches several areas at once. When it declares success, open a fresh session, point it at the same diff and say: "You are reviewing this change. Your job is to find problems." The model that just told you everything works will hand you a list of real issues.

Companies separate developers from testers for the same reason. The author's context is full of the intent to finish, and a reviewer starting from a clean context has a single goal, finding what's wrong.

Scale the rigor to the stakes:

  • Throwaway work: no review at all. A one-off script that did its job has already passed the only test that matters
  • Routine change: one fresh-context review pass
  • Important change: a second pass with a different model, for the blind-spot reasons in Section 10
  • Money or security on the line: repeat until several consecutive passes come back clean

There are limits to this. Reviewer agents can be swayed by surface signals: confident naming, tidy comments, a description that says "tested and ready." None of that is evidence, but a model reads it as if it were. That weakness is the best argument I know for multiple passes and more than one model instead of a single verdict you trust.

The other limit is responsibility, which you can't hand off. If a change ships under your name and you were never in a position to check it, you've put your name on unchecked output, whatever you call it. Lower the rigor for low-stakes work as much as you like, but don't sign what you couldn't verify.

Section 12: Turn Every Repeated Error Into a System

Claude's statelessness has a consequence most people don't plan for. If the model made a mistake once, it will make it again, because the conditions that produced it haven't changed.

My rule is simple: the second time I see the same error, I turn it into something permanent. Roughly in order of preference:

  1. A hook, if it can be checked mechanically (a blocked command, a forbidden pattern)
  2. A test, if it's a behavior that can regress
  3. A CLAUDE.md note or a skill, if it's a convention or workflow the model keeps missing

The same goes for anything you catch yourself explaining twice. If starting the project takes a sequence (a container first, then seed data, then one service in watch mode) and a second session needs the same walkthrough, you've found a skill nobody has written yet. You don't have to write it either. At the end of a session where the sequence worked, say "turn what we just did into a skill." The model will document a procedure it just ran better than you'd reconstruct it from memory, and all it costs you is a sentence.

You pay once: the first fix and about ten minutes to make it permanent. After that the error stops costing you anything.

Skip this step and you keep paying. A model working in your codebase every day has dozens of recurring failure modes. If each one resurfaces even occasionally, your day goes to fixing mistakes you've already fixed, and it never gets better, because none of those fixes were saved anywhere.

The model doesn't learn between sessions. Your hooks, tests and CLAUDE.md do: they pile up and get sharper, and six months in the setup is barely recognizable. In practice that's what people mean by a self-learning system.

All that accumulation needs a counterweight, because prompt rules go stale. A CLAUDE.md line written to stop a mistake two model versions ago may now be noise, or a constraint the current model follows too literally. Every few months, read through your rules and ask of each one whether it still prevents something you've seen recently. If it doesn't, delete it and watch whether the mistake comes back. Hooks and tests can stay, since they cost nothing when they don't fire; prompt rules cost attention on every message.

There's one boundary on all of this: the rules have to come from your own failures. Plenty of ready-made rule packs and workflow bundles promise the same benefit off the shelf, and you have no way of knowing whether they work. They rarely come with evidence, and even careful self-testing gets muddied because the models and tooling underneath keep changing; the improvement you notice after installing one may have arrived with last week's model update. A rule you wrote after watching a failure yourself comes with its own proof.

Section 13: Long-Running Tasks: Reliability Compounds

Why 99.9% and 99.99% Are Not Almost the Same

Suppose an agent gets each step of a task right 99.9% of the time. That looks like a great number. Now give it a long autonomous task, the overnight kind: a thousand steps of reading, editing, running and checking.

0.999 raised to the 1000th power is about 0.37, so your "excellent" agent finishes a clean run 37% of the time.

An agent at 99.99% per step, over the same thousand steps, gets about 90% clean runs. What looks like a rounding error is the difference between an agent you can leave running overnight and one you can't.

This changes two things in practice.

Model choice changes with task length. For a 20-minute task, pick the smartest model. For a 10-hour run, pick the most consistent one, the one that double-checks its own steps. In the comparisons behind my model-advisor skill, Opus varies less from run to run than Sonnet, which at high effort can turn out either excellent or a failure.

Checkpoints break the compounding. The math above assumes a single failure ruins the whole run, so make failures recoverable:

  • Force commits at milestones. Tell the agent to commit after each finished piece. A mistake then costs you a rollback instead of the whole run.
  • Keep a run log. Have the agent maintain a file: what's done, what remains, what went wrong and how it was handled. If the run is interrupted, a fresh agent can pick it up from this file instead of starting over.
  • Set budgets up front. Time, tokens, scope. An instruction like "if tests still fail after three attempts on the same issue, stop and write down what you know" is far better than letting the agent grind all night on a dead end.

Plan long tasks on the assumption that something will go wrong halfway through.

Section 14: Parallelism Done Right

Agent Count Is Not a Multiplier

It's tempting to think 10 agents = 10x speed. Point 10 agents at the same part of a codebase and something else happens. Each agent needs a coherent picture of the code to make a correct change, and while it's building that picture another agent is changing the code underneath it. They overwrite each other's work and break each other's assumptions, and you spend the evening untangling it. Ten agents, and you're slower than with one.

Parallelism works when it follows the dependency graph of the work:

  • Split by genuinely independent areas. Frontend and backend, or two services that don't share code, each in its own git worktree and merged at the end. The speedup is real only to the extent the pieces really are independent.
  • One writer, many readers. A single agent makes the changes while parallel subagents feed it research: file discovery, doc lookups, pattern searches. Writes stay serialized, reads run in parallel, and nothing conflicts.

The Safest Thing to Parallelize Is Discovery

Reading has no side effects, so research is the one place you can parallelize without any ceremony. I keep coming back to one pattern: give the same question to two or three agents separately, then have another agent compare their reports.

Most of what I learn comes from that comparison. If the reports disagree, something there is probably ambiguous or broken, and I go look myself. If they agree, I count it as moderate evidence, not proof, because models trained on similar data share blind spots and can be confidently wrong in the same way.

Also, don't parallelize until the single-agent loop works. If the direction is wrong, more agents only get you to the wrong place sooner. Get the system right first (context, verification, checkpoints), and then parallelism multiplies something that already works.

When Discarding Most of the Output Is Fine

There's one more kind of parallelism, and it opens up once you have a good answer to Section 11's question. With a mechanical check in place you can stop treating generated code as precious: launch several attempts at the same problem, keep whatever passes and delete the rest. Throwing away most of the output is deliberate: tokens are cheap now, and your attention isn't.

Everything depends on that check being automated. Run the same play with a human reviewing each attempt and you've multiplied your reading by the number of attempts, which is just the Section 11 bottleneck in a different form.

Section 15: Testing, Security, and Performance: The CTO Checklist

Testing in AI-Powered Development

The easiest trap to fall into when writing code with AI is "let me finish this quickly, I'll write tests later." Without tests you have no way to verify what the AI produced.

So when you have AI write a feature, ask for the tests in the same prompt: "implement this feature and write its tests, then run the tests." Adding verification criteria costs you one more sentence, and what comes back is noticeably different.

Security Scanning

Before every PR, run these security checks:

  1. Hardcoded secret scanning: Searching for API keys, passwords, tokens
  2. Input validation check: Are user inputs being validated?
  3. OWASP Top 10 review: Are there SQL injection, XSS, CSRF risks?
  4. Dependency security: Are there packages with known vulnerabilities?

Performance: Measure Before You Optimize

Performance work with AI has its own trap. The model will happily optimize whatever you point it at, whether or not that's where the problem is. Measure first, then delegate. Ask it to check for N+1 queries, a missing index, a payload that should be streamed instead of loaded whole. Ask for something you can check, like "profile this endpoint and show me where the time goes". "Make it faster" mostly gets you guesses.

Section 16: Do's and Don'ts

Do

  1. Write a good CLAUDE.md for every project. Without this file, Claude doesn't know your project.
  2. Actively manage context. Watch what's in the context, not just the bar. Compact once finished work piles up, start clean with /clear.
  3. Make Plan Mode a habit. See what will be done before big changes, then approve.
  4. Provide verification criteria. Instead of "write tests," say "write tests that cover edge cases and run them with npm test."
  5. Weave a safety net with hooks. Start with at least 3 essential hooks.
  6. Keep the main thread on the main task. Research, review, search, and anything off-plan go to subagents (Section 8).
  7. Start with deep research. On complex tasks, research first, then implement.
  8. Apply model routing. Each subagent gets the cheapest tier that fits its job; save the top tiers and high effort for work that needs them.
  9. Teach by example. Show a code snippet instead of writing three paragraphs about it.
  10. Keep sessions short. One conversation = one task. When context balloons, quality drops with it.
  11. Turn repeated errors into systems. The second time a mistake shows up, it gets a hook, a test, or a CLAUDE.md note (Section 12).
  12. Separate the producer from the verifier. A fresh-context review finds what the writing session can't see (Section 11).

Don't

  1. Don't use Claude as a linter. ESLint, Biome and Prettier already exist. Rules like "use two spaces" waste context.
  2. Don't turn CLAUDE.md into a novel. Keep it under 100 lines. Use progressive disclosure.
  3. Don't trust without verifying. Claude saying "done" isn't enough. Check the build, the tests and security yourself.
  4. Don't let it over-engineer. Say it once in CLAUDE.md ("no extra abstractions, no unnecessary interfaces") and catch the rest in review. Ten variations of the same rule won't help.
  5. Don't delay writing tests. AI output without tests is output nobody has verified.
  6. Don't parallelize before the system works. Ten agents on a broken loop fail ten times faster (Section 14).
  7. Don't import rule packs expecting improvement. A borrowed rule comes with no evidence; one distilled from your own repeated failures does (Section 12).
  8. Don't fix creative work with more rules. On open-ended tasks, a clear goal and room to move work better than a long list of prohibitions (Section 2).

Conclusion: Where This Leaves Me

Claude Code started out as "AI in the terminal" for me and turned into the place where most of my development happens. CLAUDE.md strategy, context management, hook pipelines, custom agents, multi-agent orchestration, using several models together, systems that learn from their own failures: I picked these up gradually, over months.

Most of it comes back to the idea from Section 1, thinking like a CTO: you plan, test, secure and measure the work instead of only writing it, and you do that the same way every time.

None of this is settled methodology. Nobody in the industry knows yet how software should be built this way. What you've read is the set of practices that survived months of my daily use, and some of it will look naive within a year. Oddly, I find that encouraging. Twenty years of experience buys a smaller head start here than it ever has, because everyone is learning this way of working from roughly the same starting line.

You could fairly ask whether smarter models will make all this setup unnecessary. I don't think so. Smarter models raise the ceiling on what you can delegate, and everything in this article (verification, context discipline, checkpoints) is what lets you actually delegate it. So I expect the gap between people who build systems and people who only type prompts to keep growing.

The system does change shape as models improve, though. Smarter models need less instruction: fewer rules, shorter prompts, more room on creative work. They need just as much verification, because more delegation means more output nobody has checked yet. If I had to sum up how my own setup has moved since I first wrote this: the prompt layer shrinks, and the verification layer grows.

To get started:

  1. Write a good CLAUDE.md
  2. Learn context management (one task per session, act before the symptoms)
  3. Set up the 3 essential hooks
  4. Make Plan Mode a habit
  5. Separate the producer from the verifier

Two of the tools I mentioned along the way are public: the model-advisor skill and the telemetry setup. Use them, change them, and keep whatever still holds up after your own failures.

Send email
Send WhatsApp message