It's pretty wild that I can now describe a feature to an AI agent, point it at a repo, and get back an implementation that would've taken hours to write myself.
And some of the results are seriously impressive.
Of course, there's the occasional solution involving Redis, three extra abstractions, and a background worker when the problem could've been handled by a single function.
Nothing against Redis. I'm a fan. I just don't think it needs to be invited to every party.
Cursor, Claude Code, and other AI tools are now part of how I work across frontend apps, backend services, APIs, and distributed systems. At this point, they've changed my development workflow quite a bit.
What's changed even more, though, is where I spend my attention. As agents get better at producing code, figuring out which code actually belongs in the system becomes a bigger part of the job.
I use AI to question my ideas, compare approaches, and sometimes point out that my preferred solution isn't as clever as I thought. That's useful. What I'm not doing is rubber-stamping whatever the model suggests because the explanation sounds fancy.
And no, this isn't another take on the "will AI replace devs?" debate. I'm more interested in making these tools useful right now: getting genuine speed-ups, keeping the codebase sane, and not trading a quick win today for a debugging nightmare next week.
Here's what that looks like in my day-to-day work.
1. AI is my engineering partner, not my architect#
I probably spend more time arguing with AI than most people would consider healthy.
And I actually enjoy it.
Before I build something substantial, I'll have an agent inspect the existing code, suggest a few ways forward, and explain what we'd gain or lose with each option. Then I start poking holes in the suggestions.
Sometimes it pokes holes in mine.
That's the whole point.
This is roughly how I open that conversation:
Read the code around <feature> first. Don't write any code yet.
Give me 2–3 ways to build it. For each: what changes, what it costs,
what could break, and which one you'd pick and why.
Flag anything that adds a new service, dependency or queue.I don't particularly care who wins the argument. I'd rather lose a twenty-minute architecture debate than win several hours of debugging later.
Atlas World Sports is a good example.
I'd spent a lot of time on that platform before AI entered my workflow, so I already knew the system pretty well. I was familiar with its external sports-data providers, rate limits, caching, and some approaches that had caused issues in the past.
Every now and then, an agent would suggest something perfectly reasonable from a technical standpoint. The problem was that we'd already tried something similar, and I knew why it hadn't worked out.
That previous experience made it much easier to push back before committing to an implementation that could've caused hours of unnecessary debugging.
There have also been times when I was convinced my approach was better, only for the agent to make a solid argument for something else. Fine by me. If it's the better solution, let's use it.
That's the kind of collaboration I'm after.
An agent can scan a repo and build a decent understanding of how things work. What it can't magically recover is every battle scar behind the architecture. Some decisions came from previous incidents, awkward integrations, or business requirements that aren't written down anywhere obvious.
That's where knowing the system makes a difference.
I enjoy architecture discussions, but I have no interest in overengineering something just to make it look impressive.
If we're adding another microservice, it better solve an actual problem. I don't need another network hop and a retry policy just because we got bored of function calls.
2. The fastest way to build is to plan first#
Some of the biggest gains I've had from AI have come from startups and products built from scratch.
It's not just about writing features faster. It's being able to experiment with ideas, try different approaches, and get a working foundation in place without spending ages on the same setup work I've already done on previous projects.
That doesn't happen because I throw a half-baked idea into Claude and wait for a finished application to appear.
The trick is getting the groundwork right before the agent starts hammering out code.
I often begin a new project by showing the agent an older project that's already proven itself. If I'm building something in React, for instance, I might use an existing app to demonstrate the folder structure, data fetching, state management, API patterns, components, and testing setup I prefer.
That's a much better starting point than explaining every convention from zero.
Still, I don't want an exact clone.
We look at the existing choices, question whether they fit the new product, and discuss alternatives. I'm comfortable with React, TanStack Query, and Zustand, but a stack shouldn't become a religion just because it worked on the last project.
Lexa, a legal case and hearing-management product I've worked on, is the kind of startup where this approach matters. When forms, documents, and different workflows depend on each other, keeping the implementation consistent is a big deal. Generating another feature quickly isn't much use if it doesn't fit with everything around it.
Once we've settled on the general direction, I want the plan written down.
Architecture, dependencies, implementation phases, testing, important technical decisions. I want to know what we're building, why we're building it that way, and how we'll know each part is finished.
From there, I hand an agent a defined phase. It implements the work and handles the appropriate automated tests and manual checks. I review the result before moving on.
Naturally, plans change. If implementation exposes a better option, we update the plan. It's a development plan, not a contract signed in blood.
Afterward, I record the meaningful decisions and progress so the next session doesn't need to rediscover everything.
There's a balance to all this. I'm not suggesting a week of planning for two days of development.
I'd just rather spend a little time thinking upfront than three days untangling something that twenty minutes of planning would've caught.
3. Context is more valuable than a perfect prompt#
People seem obsessed with finding the ultimate AI prompt.
I'll take a repo with decent documentation instead, thanks.
Modern agents are pretty good at inspecting a repo. That's not really the issue. The problem is that reading code doesn't necessarily tell them which patterns are deliberate, which approaches have already failed, or why that weird-looking workaround exists in the first place. Without that history, an agent can suggest a perfectly reasonable change that ends up breaking something downstream.
That's why I keep project instructions in places like CLAUDE.md, AGENTS.md, Cursor rules, and dedicated architecture or requirements docs.
Think of them as the house rules for the repo. They tell an agent where things go, which conventions to follow, which libraries are already in use, and what needs to pass before a task is considered done.
A trimmed-down version looks something like this:
# Agent rules
## Stack
Next.js · TanStack Query · axios instance in src/api · Tailwind · Zustand
## Conventions
- Tailwind only, no inline styles.
- Data fetching follows src/hooks/useStudents.ts.
- Comments explain why, never narrate the code.
## Never
- Touch files outside the task without asking.
- Force-push, or leave dev servers running.
## Done means
lint + typecheck + tests + build pass, and you show me the output.I also like giving it real code to reference.
If we've already implemented authentication, API calls, state management, or error handling in a particular way, there's no need to invent a different approach for the next feature.
EDIO is a good example of why proper context matters.
The project uses Git submodules, which means the agent needs to understand the actual repository and dependency boundaries before making changes.
Otherwise, it can suggest an import that crosses a boundary the project deliberately keeps separate.
Cross-submodule imports aren't automatically wrong, but they can bypass intended interfaces or introduce dependencies that don't survive an isolated build.
And that's the annoying kind of issue: everything looks perfectly fine in your local checkout until a different environment disagrees.
A little repo homework beats discovering a broken dependency through CI.
The same idea applies when working with large API payloads or complicated business logic. I'd rather give the agent access to a relevant file or a few good examples than stuff the conversation with mountains of raw JSON.
More context isn't necessarily better context.
What matters is whether the agent has what it needs to make sensible decisions.
And if an architectural decision is important enough to remember, it belongs in the project's documentation, not buried somewhere in a six-hour chat.
4. Claude, Cursor, and the myth that more agents mean more productivity#
I use both Claude Code and Cursor, and I'm not interested in picking a winner as though they're rival football teams.
Different jobs, different tools. Simple enough.
Cursor fits nicely into the way I work inside the editor. It's handy for exploring code, making targeted edits, debugging, and iterating on an implementation while I'm right there.
I often reach for Claude Code when I'm dealing with bigger plans, more involved investigations, or an implementation phase that can be handed off as a complete task.
That's my workflow, not some hard limit on what either tool can do.
One combination I particularly enjoy is using both when I don't have a Figma design to start from.
I've got a decent eye for design, but being a perfectionist can turn a harmless layout adjustment into a surprisingly long discussion about whether something should move six pixels to the left.
Claude helps me explore design options, work out layouts, question visual choices, and settle on consistent styling. Cursor is useful when I get into the implementation and start fine-tuning things.
It works well as long as both tools have the same specifications and aren't pulling the design in different directions.
The other thing I've become careful about is overlapping agent work.
There's a difference between splitting tasks intelligently and having two agents editing the same implementation with no coordination.
For parallel work, I'll separate responsibilities and use branches or Git worktrees where appropriate. That protects the code changes, although shared databases, servers, test accounts, and other resources can still cause trouble.
Two agents in separate worktrees can still step on each other's toes outside Git.
Cost is another consideration, but I don't obsess over shaving a few tokens off the bill.
If a cheaper model ends up needing multiple correction rounds and constant re-explaining, the supposed saving disappears into engineering time.
Sometimes the better move is to give one capable agent a properly specified phase, let it finish the work, and review the outcome.
Adding more agents doesn't automatically speed things up.
Sometimes all you've done is create a team that needs another meeting.
5. If your pipeline is red, your feature isn't done#
Unless you're working with a particularly backwards client where red apparently means "ready to deploy." In that case, the pipeline might not be your biggest problem.
For everyone else, this should sound familiar.
The feature works. The local app looks good. You push the changes.
And then GitHub or GitLab rewards your effort with a red pipeline.
Lovely.
Now you're debugging a failure that, quite often, could've been caught before the push.
Working at Hostelworld, where we had strict GitHub rules, required checks, and CI pipelines, really drilled this into me.
An agent shouldn't call a task finished without running the relevant checks it can run locally.
If CI expects linting, TypeScript checks, automated tests, and a production build, I want those locally reproducible checks to happen as part of the implementation.
The requirements should already be in the project documentation and definition of done, so the agent knows what to run without being reminded every time.
In practice, every task I hand off ends with something like this:
Done means:
- pnpm lint && pnpm typecheck && pnpm test && pnpm build pass locally
- show me the commands and their output
- new behaviour has a test
- checked in the browser at 375 and 1440 px wideThat doesn't mean local verification replaces CI.
Some steps rely on secrets, infrastructure, external services, or specific environments. The actual pipeline is still the final integration gate.
But there's not much excuse for discovering an ordinary lint or type error only after pushing the code.
Of course, getting a green pipeline is only part of the job.
I still want relevant unit and integration tests, plus manual or exploratory testing where it makes sense.
And having AI ride shotgun during browser testing has been ridiculously useful.
If the agent has browser tools, we can walk through scenarios together, inspect what happens, reproduce bugs, and investigate them in the same session.
That's a lot nicer than testing alone and then spending fifteen messages trying to explain a problem to an agent that hasn't seen it.
There are limits, though.
I've run into mobile UI problems that automated browser tests couldn't reproduce, even with different viewport sizes. The issue was still visible on the actual phone.
So real-device testing still matters.
And when the same bug refuses to disappear, I'd rather stop throwing fixes at it and find the root cause first.
Tests tell me whether the cases we've covered work as expected. CI checks whether the change meets the pipeline's requirements.
Neither one can think for us about what we forgot to test.
6. Code review: the part where I find out what actually happened#
An agent can spit out a fairly alarming amount of code before you've finished your coffee.
Then you open the PR.
Now it's your problem.
I don't start by admiring how clean everything looks. First, I want to know whether the change actually does what we asked for.
After that, straight to the diff.
What changed? Why did it change? And why on earth did it also touch that unrelated file?
I'm looking for unnecessary abstractions, sneaky dependencies, repeated logic, and code that doesn't fit the existing architecture. Depending on the change, security, performance, and backward compatibility also deserve a proper look.
On Atlas, for example, changing something around a provider integration or caching can have effects well beyond the bit of code being edited.
I'll sometimes get another AI model to review the implementation, but I give it the original requirements and actual diff.
Review this change against the requirements below, not against
the summary from the agent that wrote it.
Requirements: <ticket and acceptance criteria>
Diff: <git diff main...HEAD>
List bugs, unnecessary abstractions or dependencies, files touched
outside the scope, and missing tests. Most severe first.I don't want it grading the first agent's self-written success report.
Otherwise, it's basically two AIs congratulating each other while I investigate who decided we needed another dependency.
And, naturally, I review the reviewer's findings too.
Tests help verify behavior. Reviewing the code tells me whether the solution itself makes sense and whether I want to maintain it later.
I don't need to personally write every line.
But if a significant piece of code exists and I can't explain why, approving it probably isn't a great idea.
This site was built the same way#
This portfolio is that workflow in practice.
- A written plan first: a design spec and a 15-phase build plan before the first component.
- House rules in the repo: design tokens, plus a script that fails the build on raw colours, off-brand fonts or gradients.
- Checks on every push: lint, types, unit tests and a production build, plus 165 browser tests covering every page in light and dark mode, at phone and desktop width, with automated WCAG 2.2 AA accessibility scans. Lighthouse budgets fail the pipeline if desktop performance drops below 95 or accessibility below 100.
- A real phone anyway: the cookie banner stretched off-screen on my iPhone while every emulated device looked fine. The fix came with a test that simulates what the phone did.
My rules, in short#
- Argue before code: options and trade-offs first.
- Write the plan down, then hand agents one phase at a time.
- Keep the house rules in the repo, not in the chat.
- One owner per task; parallel agents get their own branch, worktree and resources.
- It's not done until the local checks pass and it works on a real device.
- Review the diff against the requirements, not the agent's summary.
The part that actually matters#
AI has already changed my development workflow quite a bit, and I doubt we've seen the best of these tools yet.
Tasks that used to swallow an afternoon can now be handled in minutes. I can try different approaches, scrap the bad ones, and move forward without losing half a day.
I genuinely enjoy that.
I'm still doing engineering work. I just don't have to sit there personally writing every repetitive bit of the implementation.
And yet, I do miss the hustle sometimes.
There's something satisfying about a long debugging session, getting stuck on a problem, working through it yourself, and finally finding that one stupid detail that was causing everything.
Same with building something from scratch, line by line, and watching it come together.
I still enjoy getting my hands dirty with code. I don't think AI should take that part of programming away completely.
Would I go back to doing all of it manually, though?
Absolutely not.
I don't particularly care whether I wrote the code or an AI agent did. I care whether it's the right solution, whether it works, and whether I can confidently stand behind it.
Because when something breaks in production, "Claude wrote it" is still a pretty terrible incident report.
_If you want an engineer who works like this on your team, let's talk._

Stefan Matić
Senior full-stack engineer and CTO of DEVLEGION. I write about building systems that keep working. About me