Raw capture ā raw/addy-osmanis-workflow.md
url: https://addyosmani.com/blog/ai-coding-workflow/ date_fetched: 2026-07-05 backfilled: true
AI coding assistants became game-changers in 2025, but harnessing them effectively takes skill and structure. These tools dramatically increased what LLMs can do for real-world coding, and many developers (myself included) embraced them.
At Anthropic, for example, engineers adopted Claude Code so heavily that today ~90% of the code for Claude Code is written by Claude Code itself. Yet, using LLMs for programming is not a push-button magic experience - itās ādifficult and unintuitiveā and getting great results requires learning new patterns. Critical thinking remains key. Over a year of projects, Iāve converged on a workflow similar to what many experienced devs are discovering: treat the LLM as a powerful pair programmer that requires clear direction, context and oversight rather than autonomous judgment.
In this article, Iāll share how I plan, code, and collaborate with AI going into 2026, distilling tips and best practices from my experience and the communityās collective learning. Itās a more disciplined āAI-assisted engineeringā approach - leveraging AI aggressively while staying proudly accountable for the software produced.
If youāre interested in more on my workflow, see āThe AI-Native Software Engineerā, otherwise letās dive straight into some of the lessons I learned.
Start with a clear plan (specs before code)
Donāt just throw wishes at the LLM - begin by defining the problem and planning a solution.
One common mistake is diving straight into code generation with a vague prompt. In my workflow, and in many othersā, the first step is brainstorming a detailed specification with the AI, then outlining a step-by-step plan, before writing any actual code. For a new project, Iāll describe the idea and ask the LLM to iteratively ask me questions until weāve fleshed out requirements and edge cases. By the end, we compile this into a comprehensive spec.md - containing requirements, architecture decisions, data models, and even a testing strategy. This spec forms the foundation for development.
Next, I feed the spec into a reasoning-capable model and prompt it to generate a project plan: break the implementation into logical, bite-sized tasks or milestones. The AI essentially helps me do a mini ādesign docā or project plan. I often iterate on this plan - editing and asking the AI to critique or refine it - until itās coherent and complete. Only then do I proceed to coding. This upfront investment might feel slow, but it pays off enormously. As Les Orchard put it, itās like doing a āwaterfall in 15 minutesā - a rapid structured planning phase that makes the subsequent coding much smoother.
Having a clear spec and plan means when we unleash the codegen, both the human and the LLM know exactly what weāre building and why. In short, planning first forces you and the AI onto the same page and prevents wasted cycles. Itās a step many people are tempted to skip, but experienced LLM developers now treat a robust spec/plan as the cornerstone of the workflow.
Break work into small, iterative chunks
Scope management is everything - feed the LLM manageable tasks, not the whole codebase at once.
A crucial lesson Iāve learned is to avoid asking the AI for large, monolithic outputs. Instead, we break the project into iterative steps or tickets and tackle them one by one. This mirrors good software engineering practice, but itās even more important with AI in the loop. LLMs do best when given focused prompts: implement one function, fix one bug, add one feature at a time. For example, after planning, I will prompt the codegen model: āOkay, letās implement Step 1 from the planā. We code that, test it, then move to Step 2, and so on. Each chunk is small enough that the AI can handle it within context and you can understand the code it produces.
This approach guards against the model going off the rails. If you ask for too much in one go, itās likely to get confused or produce a ājumbled messā thatās hard to untangle. Developers report that when they tried to have an LLM generate huge swaths of an app, they ended up with inconsistency and duplication - ālike 10 devs worked on it without talking to each other,ā one said. Iāve felt that pain; the fix is to stop, back up, and split the problem into smaller pieces. Each iteration, we carry forward the context of whatās been built and incrementally add to it. This also fits nicely with a test-driven development (TDD) approach - we can write or generate tests for each piece as we go (more on testing soon).
Several coding-agent tools now explicitly support this chunked workflow. For instance, I often generate a structured āprompt planā file that contains a sequence of prompts for each task, so that tools like Cursor can execute them one by one. The key point is to avoid huge leaps. By iterating in small loops, we greatly reduce the chance of catastrophic errors and we can course-correct quickly. LLMs excel at quick, contained tasks - use that to your advantage.
Provide extensive context and guidance
LLMs are only as good as the context you provide - show them the relevant code, docs, and constraints.
When working on a codebase, I make sure to feed the AI all the information it needs to perform well. That includes the code it should modify or refer to, the projectās technical constraints, and any known pitfalls or preferred approaches. Modern tools help with this: for example, Anthropicās Claude can import an entire GitHub repo into its context in āProjectsā mode, and IDE assistants like Cursor or Copilot auto-include open files in the prompt. But I often go further - I will either use an MCP like Context7 or manually copy important pieces of the codebase or API docs into the conversation if I suspect the model doesnāt have them.
Expert LLM users emphasize this ācontext packingā step. For example, doing a ābrain dumpā of everything the model should know before coding, including: high-level goals and invariants, examples of good solutions, and warnings about approaches to avoid. If Iām asking an AI to implement a tricky solution, I might tell it which naive solutions are too slow, or provide a reference implementation from elsewhere. If Iām using a niche library or a brand-new API, Iāll paste in the official docs or README so the AI isnāt flying blind. All of this upfront context dramatically improves the quality of its output, because the model isnāt guessing - it has the facts and constraints in front of it.
There are now utilities to automate context packaging. Iāve experimented with tools like gitingest or repo2txt, which essentially ādumpā the relevant parts of your codebase into a text file for the LLM to read. These can be a lifesaver when dealing with a large project - you generate an output.txt bundle of key source files and let the model ingest that. The principle is: donāt make the AI operate on partial information. If a bug fix requires understanding four different modules, show it those four modules. Yes, we must watch token limits, but current frontier models have pretty huge context windows (tens of thousands of tokens). Use them wisely. I often selectively include just the portions of code relevant to the task at hand, and explicitly tell the AI what not to focus on if something is out of scope (to save tokens).
I think Claude Skills have potential because they turn what used to be fragile repeated prompting into something durable and reusable by packaging instructions, scripts, and domain specific expertise into modular capabilities that tools can automatically apply when a request matches the Skill. This means you get more reliable and context aware results than a generic prompt ever could and you move away from one off interactions toward workflows that encode repeatable procedures and team knowledge for tasks in a consistent way. A number of community-curated Skills collections exist, but one of my favorite examples is the frontend-design skill which can āendā the purple design aesthetic prevalent in LLM generated UIs. Until more tools support Skills officially, workarounds exist.
Finally, guide the AI with comments and rules inside the prompt. I might precede a code snippet with: āHere is the current implementation of X. We need to extend it to do Y, but be careful not to break Z.ā These little hints go a long way. LLMs are literalists - theyāll follow instructions, so give them detailed, contextual instructions. By proactively providing context and guidance, we minimize hallucinations and off-base suggestions and get code that fits our projectās needs.
Choose the right model (and use multiple when needed)
Not all coding LLMs are equal - pick your tool with intention, and donāt be afraid to swap models mid-stream.
In 2025 weāve been spoiled with a variety of capable code-focused LLMs. Part of my workflow is choosing the model or service best suited to each task. Sometimes it can be valuable to even try two or more LLMs in parallel to cross-check how they might approach the same problem differently.
Each model has its own āpersonalityā. The key is: if one model gets stuck or gives mediocre outputs, try another. Iāve literally copied the same prompt from one chat into another service to see if it can handle it better. This āmodel musical chairsā can rescue you when you hit a modelās blind spot.
Also, make sure youāre using the best version available. If you can, use the newest āproā tier models - because quality matters. And yes, it often means paying for access, but the productivity gains can justify it. Ultimately, pick the AI pair programmer whose āvibeā meshes with you. I know folks who prefer one model simply because they like how its responses feel. Thatās valid - when youāre essentially in a constant dialogue with an AI, the UX and tone make a difference.
Personally I gravitate towards Gemini for a lot of coding work these days (with the disclosure that I work on Gemini at Google, so Iām naturally more familiar with it) because the interaction feels more natural and it often understands my requests on the first try. But I will not hesitate to switch to another model if needed; sometimes a second opinion helps the solution emerge. In summary: use the best tool for the job, and remember you have an arsenal of AIs at your disposal.
Leverage AI coding across the lifecycle
Supercharge your workflow with coding-specific AI help across the SDLC.
On the command-line, new AI agents emerged. Claude Code, OpenAIās Codex CLI and Googleās Gemini CLI are CLI tools where you can chat with them directly in your project directory - they can read files, run tests, and even multi-step fix issues. Iāve used Googleās Jules and GitHubās Copilot Agent as well - these are asynchronous coding agents that actually clone your repo into a cloud VM and work on tasks in the background (writing tests, fixing bugs, then opening a PR for you). Itās a bit eerie to witness: you issue a command like ārefactor the payment module for Xā and a little while later you get a pull request with code changes and passing tests. We are truly living in the future. You can read more about this in conductors to orchestrators.
That said, these tools are not infallible, and you must understand their limits. They accelerate the mechanical parts of coding - generating boilerplate, applying repetitive changes, running tests automatically - but they still benefit greatly from your guidance. For instance, when I use an agent like Claude or Copilot to implement something, I often supply it with the plan or to-do list from earlier steps so it knows the exact sequence of tasks. If the agent supports it, Iāll load up my spec.md or plan.md in the context before telling it to execute. This keeps it on track.
Weāre not at the stage of letting an AI agent code an entire feature unattended and expecting perfect results. Instead, I use these tools in a supervised way: Iāll let them generate and even run code, but I keep an eye on each step, ready to step in when something looks off. There are also orchestration tools like Conductor that let you run multiple agents in parallel on different tasks (essentially a way to scale up AI help) - some engineers are experimenting with running 3-4 agents at once on separate features. Iāve dabbled in this āmassively parallelā approach; itās surprisingly effective at getting a lot done quickly, but itās also mentally taxing to monitor multiple AI threads! For most cases, I stick to one main agent at a time and maybe a secondary one for reviews (discussed below).
Just remember these are power tools - you still control the trigger and guide the outcome.
Keep a human in the loop - verify, test, and review everything
AI will happily produce plausible-looking code, but you are responsible for quality - always review and test thoroughly. One of my cardinal rules is never to blindly trust an LLMās output. As Simon Willison aptly says, think of an LLM pair programmer as
āover-confident and prone to mistakesā. It writes code with complete conviction - including bugs or nonsense - and wonāt tell you something is wrong unless you catch it. So I treat every AI-generated snippet as if it came from a junior developer: I read through the code, run it, and test it as needed.
You absolutely have to test what it writes- run those unit tests, or manually exercise the feature, to ensure it does what it claims. Read more about this in vibe coding is not an excuse for low-quality work.
In fact, I weave testing into the workflow itself. My earlier planning stage often includes generating a list of tests or a testing plan for each step. If Iām using a tool like Claude Code, Iāll instruct it to run the test suite after implementing a task, and have it debug failures if any occur. This kind of tight feedback loop (write code ā run tests ā fix) is something AI excels at as long as the tests exist. Itās no surprise that those who get the most out of coding agents tend to be those with strong testing practices. An agent like Claude can āflyā through a project with a good test suite as safety net. Without tests, the agent might blithely assume everything is fine (āsure, all good!ā) when in reality itās broken several things. So, invest in tests - it amplifies the AIās usefulness and confidence in the result.
Even beyond automated tests, do code reviews - both manual and AI-assisted. I routinely pause and review the code thatās been generated so far, line by line. Sometimes Iāll spawn a second AI session (or a different model) and ask it to critique or review code produced by the first. For example, I might have Claude write the code and then ask Gemini, āCan you review this function for any errors or improvements?ā This can catch subtle issues. The key is to not skip the review just because an AI wrote the code. If anything, AI-written code needs extra scrutiny, because it can sometimes be superficially convincing while hiding flaws that a human might not immediately notice.
I also use Chrome DevTools MCP, built with my last team, for my debugging and quality loop to bridge the gap between static code analysis and live browser execution. It āgives your agent eyesā. It lets me grant my AI tools direct access to see what the browser can, inspect the DOM, get rich performance traces, console logs or network traces. This integration eliminates the friction of manual context switching, allowing for automated UI testing directly through the LLM. It means bugs can be diagnosed and fixed with high precision based on actual runtime data.
The dire consequences of skipping human oversight have been documented. One developer who leaned heavily on AI generation for a rush project described the result as an inconsistent mess - duplicate logic, mismatched method names, no coherent architecture. He realized heād been ābuilding, building, buildingā without stepping back to really see what the AI had woven together. The fix was a painful refactor and a vow to never let things get that far out of hand again. Iāve taken that to heart. No matter how much AI I use, I remain the accountable engineer.
In practical terms, that means I only merge or ship code after Iāve understood it. If the AI generates something convoluted, Iāll ask it to add comments explaining it, or Iāll rewrite it in simpler terms. If something doesnāt feel right, I dig in - just as I would if a human colleague contributed code that raised red flags.
Itās all about mindset: the LLM is an assistant, not an autonomously reliable coder. I am the senior dev; the LLM is there to accelerate me, not replace my judgment. Maintaining this stance not only results in better code, it also protects your own growth as a developer. (Iāve heard some express concern that relying too much on AI might dull their skills - I think as long as you stay in the loop, actively reviewing and understanding everything, youāre still sharpening your instincts, just at a higher velocity.) In short: stay alert, test often, review always. Itās still your codebase at the end of the day.
Commit often and use version control as a safety net. Never commit code you canāt explain.
Frequent commits are your save points - they let you undo AI missteps and understand changes.
When working with an AI that can generate a lot of code quickly, itās easy for things to veer off course. I mitigate this by adopting ultra-granular version control habits. I commit early and often, even more than I would in normal hand-coding. After each small task or each successful automated edit, Iāll make a git commit with a clear message. This way, if the AIās next suggestion introduces a bug or a messy change, I have a recent checkpoint to revert to (or cherry-pick from) without losing hours of work. One practitioner likened it to treating commits as āsave points in a gameā - if an LLM session goes sideways, you can always roll back to the last stable commit. Iāve found that advice incredibly useful. Itās much less stressful to experiment with a bold AI refactor when you know you can undo it with a git reset if needed.
Proper version control also helps when collaborating with the AI. Since I canāt rely on the AI to remember everything itās done (context window limitations, etc.), the git history becomes a valuable log. I often scan my recent commits to brief the AI (or myself) on what changed. In fact, LLMs themselves can leverage your commit history if you provide it - Iāve pasted git diffs or commit logs into the prompt so the AI knows what code is new or what the previous state was. Amusingly, LLMs are really good at parsing diffs and using tools like git bisect to find where a bug was introduced. They have infinite patience to traverse commit histories, which can augment your debugging. But this only works if you have a tidy commit history to begin with.
Another benefit: small commits with good messages essentially document the development process, which helps when doing code review (AI or human). If an AI agent made five changes in one go and something broke, having those changes in separate commits makes it easier to pinpoint which commit caused the issue. If everything is in one giant commit titled āAI changesā, good luck! So I discipline myself: finish task, run tests, commit. This also meshes well with the earlier tip about breaking work into small chunks - each chunk ends up as its own commit or PR.
Finally, donāt be afraid to use branches or worktrees to isolate AI experiments. One advanced workflow Iāve adopted (inspired by folks like Jesse Vincent) is to spin up a fresh git worktree for a new feature or sub-project. This lets me run multiple AI coding sessions in parallel on the same repo without them interfering, and I can later merge the changes. Itās a bit like having each AI task in its own sandbox branch. If one experiment fails, I throw away that worktree and nothing is lost in main. If it succeeds, I merge it in. This approach has been crucial when Iām, say, letting an AI implement Feature A while I (or another AI) work on Feature B simultaneously. Version control is what makes this coordination possible. In short: commit often, organize your work with branches, and embrace git as the control mechanism to keep AI-generated changes manageable and reversible.
Customize the AIās behavior with rules and examples
Steer your AI assistant by providing style guides, examples, and even ārules filesā - a little upfront tuning yields much better outputs.
One thing I learned is that you donāt have to accept the AIās default style or approach - you can influence it heavily by giving it guidelines. For instance, I have a CLAUDE.md file that I update periodically, which contains process rules and preferences for Claude (Anthropicās model) to follow (and similarly a GEMINI.md when using Gemini CLI). This includes things like āwrite code in our projectās style, follow our lint rules, donāt use certain functions, prefer functional style over OOP,ā etc. When I start a session, I feed this file to Claude to align it with our conventions. Itās surprising how well this works to keep the model āon trackā as Jesse Vincent noted - it reduces the tendency of the AI to go off-script or introduce patterns we donāt want.
Even without a fancy rules file, you can set the tone with custom instructions or system prompts. GitHub Copilot and Cursor both introduced features to let you configure the AIās behavior globally for your project. Iāve taken advantage of that by writing a short paragraph about our coding style, e.g. āUse 4 spaces indent, avoid arrow functions in React, prefer descriptive variable names, code should pass ESLint.ā With those instructions in place, the AIās suggestions adhere much more closely to what a human teammate might write. Ben Congdon mentioned how shocked he was that few people use Copilotās custom instructions, given how effective they are - he could guide the AI to output code matching his teamās idioms by providing some examples and preferences upfront. I echo that: take the time to teach the AI your expectations.
Another powerful technique is providing in-line examples of the output format or approach you want. If I want the AI to write a function in a very specific way, I might first show it a similar function already in the codebase: āHereās how we implemented X, use a similar approach for Y.ā If I want a certain commenting style, I might write a comment myself and ask the AI to continue in that style. Essentially, prime the model with the pattern to follow. LLMs are great at mimicry - show them one or two examples and theyāll continue in that vein.
The community has also come up with creative ārulesetsā to tame LLM behavior. You might have heard of the āBig Daddyā rule or adding a āno hallucination/no deceptionā clause to prompts. These are basically tricks to remind the AI to be truthful and not overly fabricate code that doesnāt exist. For example, I sometimes prepend a prompt with: āIf you are unsure about something or the codebase context is missing, ask for clarification rather than making up an answer.ā This reduces hallucinations. Another rule I use is: āAlways explain your reasoning briefly in comments when fixing a bug.ā This way, when the AI generates a fix, it will also leave a comment like ā// Fixed: Changed X to Y to prevent Z (as per spec).ā Thatās super useful for later review.
In summary, donāt treat the AI as a black box - tune it. By configuring system instructions, sharing project docs, or writing down explicit rules, you turn the AI into a more specialized developer on your team. Itās akin to onboarding a new hire: youād give them the style guide and some starter tips, right? Do the same for your AI pair programmer. The return on investment is huge: you get outputs that need less tweaking and integrate more smoothly with your codebase.
Embrace testing and automation as force multipliers
Use your CI/CD, linters, and code review bots - AI will work best in an environment that catches mistakes automatically.
This is a corollary to staying in the loop and providing context: a well-oiled development pipeline enhances AI productivity. I ensure that any repository where I use heavy AI coding has a robust continuous integration setup. That means automated tests run on every commit or PR, code style checks (like ESLint, Prettier, etc.) are enforced, and ideally a staging deployment is available for any new branch. Why? Because I can let the AI trigger these and evaluate the results. For instance, if the AI opens a pull request via a tool like Jules or GitHub Copilot Agent, our CI will run tests and report failures. I can feed those failure logs back to the AI: āThe integration tests failed with XYZ, letās debug this.ā It turns bug-fixing into a collaborative loop with quick feedback, which AIs handle quite well (theyāll suggest a fix, we run CI again, and iterate).
Automated code quality checks (linters, type checkers) also guide the AI. I actually include linter output in the prompt sometimes. If the AI writes code that doesnāt pass our linter, Iāll copy the linter errors into the chat and say āplease address these issues.ā The model then knows exactly what to do. Itās like having a strict teacher looking over the AIās shoulder. In my experience, once the AI is aware of a toolās output (like a failing test or a lint warning), it will try very hard to correct it - after all, it āwantsā to produce the right answer. This ties back to providing context: give the AI the results of its actions in the environment (test failures, etc.) and it will learn from them.
AI coding agents themselves are increasingly incorporating automation hooks. Some agents will refuse to say a code task is ādoneā until all tests pass, which is exactly the diligence you want. Code review bots (AI or otherwise) act as another filter - I treat their feedback as additional prompts for improvement. For example, if CodeRabbit or another reviewer comments āThis function is doing X which is not idealā I will ask the AI, āCan you refactor based on this feedback?ā
By combining AI with automation, you start to get a virtuous cycle. The AI writes code, the automated tools catch issues, the AI fixes them, and so forth, with you overseeing the high-level direction. It feels like having an extremely fast junior dev whose work is instantly checked by a tireless QA engineer. But remember, you set up that environment. If your project lacks tests or any automated checks, the AIās work may slip through with subtle bugs or poor quality until much later.
So as we head into 2026, one of my goals is to bolster the quality gates around AI code contribution: more tests, more monitoring, perhaps even AI-on-AI code reviews. It might sound paradoxical (AIs reviewing AIs), but Iāve seen it catch things one model missed. Bottom line: an AI-friendly workflow is one with strong automation - use those tools to keep the AI honest.
Continuously learn and adapt (AI amplifies your skills)
Treat every AI coding session as a learning opportunity - the more you know, the more the AI can help you, creating a virtuous cycle.
One of the most exciting aspects of using LLMs in development is how much I have learned in the process. Rather than replacing my need to know things, AIs have actually exposed me to new languages, frameworks, and techniques I might not have tried on my own.
This pattern holds generally: if you come to the table with solid software engineering fundamentals, the AI will amplify your productivity multifold. If you lack that foundation, the AI might just amplify confusion. Seasoned devs have observed that LLMs āreward existing best practicesā - things like writing clear specs, having good tests, doing code reviews, etc., all become even more powerful when an AI is involved. In my experience, the AI lets me operate at a higher level of abstraction (focusing on design, interface, architecture) while it churns out the boilerplate, but I need to have those high-level skills first. As Simon Willison notes, almost everything that makes someone a senior engineer (designing systems, managing complexity, knowing what to automate vs hand-code) is what now yields the best outcomes with AI. So using AIs has actually pushed me to up my engineering game - Iām more rigorous about planning and more conscious of architecture, because Iām effectively āmanagingā a very fast but somewhat naĆÆve coder (the AI).
For those worried that using AI might degrade their abilities: Iād argue the opposite, if done right. By reviewing AI code, Iāve been exposed to new idioms and solutions. By debugging AI mistakes, Iāve deepened my understanding of the language and problem domain. I often ask the AI to explain its code or the rationale behind a fix - kind of like constantly interviewing a candidate about their code - and I pick up insights from its answers. I also use AI as a research assistant: if Iām not sure about a library or approach, Iāll ask it to enumerate options or compare trade-offs. Itās like having an encyclopedic mentor on call. All of this has made me a more knowledgeable programmer.
The big picture is that AI tools amplify your expertise. Going into 2026, Iām not afraid of them ātaking my jobā - Iām excited that they free me from drudgery and allow me to spend more time on creative and complex aspects of software engineering. But Iām also aware that for those without a solid base, AI can lead to Dunning-Kruger on steroids (it may seem like you built something great, until it falls apart). So my advice: continue honing your craft, and use the AI to accelerate that process. Be intentional about periodically coding without AI too, to keep your raw skills sharp. In the end, the developer + AI duo is far more powerful than either alone, and the developer half of that duo has to hold up their end.
Conclusion
Iāve fully embraced AI in my development workflow - but in a considered, expert-driven way. My approach is essentially āAI-augmented software engineeringā rather than AI-automated software engineering.
Iāve learned: the best results come when you apply classic software engineering discipline to your AI collaborations. It turns out all our hard-earned practices - design before coding, write tests, use version control, maintain standards - not only still apply, but are even more important when an AI is writing half your code.
Iām excited for whatās next. The tools keep improving and my workflow will surely evolve alongside them. Perhaps fully autonomous āAI dev internsā will tackle more grunt work while we focus on higher-level tasks. Perhaps new paradigms of debugging and code exploration will emerge. No matter what, I plan to stay in the loop - guiding the AIs, learning from them, and amplifying my productivity responsibly.
The bottom line for me: AI coding assistants are incredible force multipliers, but the human engineer remains the director of the show.
Iām excited to share Iāve released a new AI-assisted engineering book with OāReilly. There are a number of free tips on the book site in case interested.