The feedback loop that changed how I write code
All Articles
· Engineering AI Tooling Career

The feedback loop that changed how I write code

Agentic development tools didn't replace my workflow — they inverted it. I used to spend 80% of my time writing code and 20% reviewing it. Now the ratio is flipped, and my output is better for it.


I resisted agentic coding tools for longer than I’d like to admit. Not because I’m a luddite — I was an early GitHub Copilot adopter and I’ve been using LLM-based autocomplete for a while. But there’s a meaningful gap between “AI autocompletes my line” and “AI writes my entire feature while I watch.” The first felt like a faster keyboard. The second felt like handing someone my car keys.

Once I started using agentic tools seriously — first for constrained feature work, then for larger refactors — my development workflow changed in a way I did not expect.

The Old Loop

My pre-agent workflow for building a new feature looked roughly like this:

  1. Read the requirements
  2. Sketch the architecture in my head (or on paper for anything non-trivial)
  3. Create the files — models, view models, views, tests
  4. Write the implementation, iterating through compiler errors
  5. Run the tests, fix failures
  6. Self-review, refactor, clean up
  7. Open a PR

The time distribution: steps 3-5 took about 70% of the total effort. The actual typing, the fighting with syntax, the “wait, does this API take a closure or a result type” lookups. All of that mechanical work consumed most of my day.

The New Loop

With an agentic tool, the same feature looks like this:

  1. Read the requirements
  2. Sketch the architecture — more carefully than before, because this is now the primary input
  3. Describe the feature to the agent in precise, constrained language
  4. Review the generated code — structure first, details second
  5. Iterate: point out what’s wrong, what needs to change, what was misunderstood
  6. Run the tests (which the agent often writes first — usually more exhaustive than my first pass)
  7. Final review, adjust naming, verify edge cases
  8. Open a PR

Steps 3-5 take about 30% of the total effort. But steps 1-2 expanded significantly. I now spend more time thinking about what I want than I ever did before, because the quality of my specification directly determines the quality of the output.

This is the inversion: I went from spending 80% of my time writing code and 20% thinking about code, to roughly the opposite.

What “Specification” Actually Means in Practice

When I say “describe the feature in precise language,” I don’t mean a product requirements doc. I mean something like this, typed into an agent prompt:

Build a SearchFeature reducer (TCA pattern, same as our existing 
ArticleFeature). 

State: query string, results array of SearchResult, isLoading bool, 
optional error string.

Actions: queryChanged(String), debounceCompleted, 
searchResponse(Result<[SearchResult], Error>), resultTapped(SearchResult).

The debounce should use ContinuousClock dependency, 300ms delay, 
cancel previous debounce on new input. Empty query clears results 
and cancels pending debounce.

Follow existing patterns in Sources/Features/ — use @Dependency 
for the search client, define a SearchClient protocol with a 
single search(String) async throws -> [SearchResult] method.

Write exhaustive TCA tests using TestStore and TestClock.

That’s it. That prompt, given to an agent that can read the codebase files, usually produces a working feature reducer with tests in a minute or two. The generated code follows the existing conventions because the agent read the existing files. The tests use TestClock correctly because it was specified. The dependency injection matches the existing pattern because the agent was pointed at those conventions.

The first time this worked — and I mean truly worked, where the generated code compiled, the tests passed, and the architecture was correct — I sat back and stared at my screen for a full minute. Not because the code was impressive. Because I realized that the bottleneck had shifted permanently.

The Bottleneck Is Now Taste

Here’s the thing nobody talks about in the “AI will replace developers” discourse: the hard part was never typing the code.

The hard part was always knowing what to build and how to structure it. An agent can produce a functional search feature very quickly. But it can’t tell you whether search should be a standalone feature or composed into an existing feature’s reducer. It can’t tell you whether the debounce should be 300ms or 500ms based on your user research. It can’t tell you that your API’s search endpoint has a known issue with special characters that you need to work around.

The agent doesn’t have taste. It has capability. Those are different things.

I’ve seen this play out concretely: when I give an agent a vague prompt — “add search to the app” — I get generic, over-engineered code that technically works but doesn’t fit the codebase. When I give it a precise, opinionated prompt that encodes my architectural decisions, I get code that looks like I wrote it. The difference isn’t the agent. It’s the specification.

Where Agents Excel (Honestly)

After sustained daily use, here’s where agentic tools genuinely save me time:

Boilerplate with conventions. “Create a new TCA feature matching our existing pattern” is the single highest-value prompt I use. The agent handles the file structure, the import statements, the protocol conformances, the test setup. All the parts that are necessary but not intellectually interesting.

Test writing. This surprised me. Agents write better test coverage than most developers — including me. They don’t get bored. They don’t skip edge cases because it’s Friday afternoon. Given a reducer, an agent will write tests for every action, including the ones I’d have said “I’ll add those later” about.

Refactoring with constraints. “Rename all instances of UserProfile to AccountProfile across the codebase, update tests, update documentation comments.” This is grunt work that developers are often bad at because it’s easy to get sloppy after the 30th file. The agent doesn’t.

Learning unfamiliar APIs. When I need to use an API I haven’t worked with — say, MusicKit or StoreKit 2 — the agent can scaffold a working integration faster than I can read the documentation. I still read the documentation afterwards, but I start with working code instead of starting from zero.

Where Agents Fail (Also Honestly)

Cross-cutting architecture decisions. The agent will happily add a feature that duplicates an existing pattern in a subtly incompatible way. It doesn’t understand the system — it understands the files it can see. When I ask it to add caching, it might create a new cache instead of using the existing one that’s three directories away in a module it didn’t read.

Performance-sensitive code. The agent writes correct code, not fast code. Every agent-generated List implementation I’ve seen creates the data source inline in the view body. It works. It also recalculates on every render. The agent doesn’t know about the performance characteristics I’ve learned from profiling dozens of SwiftUI apps with Instruments.

Knowing when to say no. The most valuable thing a senior engineer does is push back on requirements that don’t make sense. An agent will implement anything you ask for. It won’t tell you that the feature you’re describing creates a UX anti-pattern, or that the data model you’re specifying will cause migration headaches later.

The Uncomfortable Question

The question I keep coming back to: if the mechanical act of writing code is increasingly automated, what exactly am I being paid for?

My answer now: I’m being paid for the same things I’ve always been paid for — understanding the problem, making architectural decisions, anticipating failure modes, and maintaining quality over time. The code was always just the artifact of those decisions. It was never the decision itself.

What’s changed is that the artifact is cheaper to produce. That means I can iterate faster. I can try two approaches and compare them instead of committing to one because I can’t afford the time to build both. I can write more tests because the marginal cost of test writing has dropped to near-zero.

The developers I’m worried about aren’t the senior engineers. They’re the developers who defined their value by volume of code produced. If your primary skill is typing Swift quickly, you have a problem. If your primary skill is knowing which Swift to type, you’re fine.

I’m writing more code than ever. I’m just not typing most of it.