Love and Hate and Agents

Love and Hate and Agents

· 2,832 words · 15 minutes reading time

With admission of AI tool use being akin to confession of a sin, I am writing this post to discuss my own use of AI tools, both the why and the how, in the interest of full transparency. For I am sinner. As I recently explained to my wife:

AI agents are terrible. They deceive you, lie to your face, they’re lazy, they take shortcuts, they ignore instructions, they completely suck! I should know, I spend $60/month on subscriptions and use it every day!

Having spent the last ten years of my life coding, the landscape has shifted under my feet. Agentic tool use has revolutionized programming the way factories changed the business of crafting goods during the industrial revolution.

AI-assisted Social Stigma

On social media I see programmers playing the role of the displaced masters of craft, wielding arguments against AI that share some common themes. They begin with very reasonable assertions about lack of code quality, but then begin down a slippery-slope of assumptions: assertions the user displayed lack of review, lack of planning, lack of oversight. The user becomes an irreverent vibe coder stereotype, clicking allow-all and going out for a coffee.

On the Rust Users Forum, you can see every one of these behaviors on display. Announcements for vibe-coded libraries appear weekly, with hundreds of (worthless) tests, often displaying complete ignorance of the popular and battle-tested alternatives in common use throughout the ecosystem. But the argument presents a straw man, not every user of AI tools shows lack of review, lack of planning, or lack of oversight. Beyond the pejorative aspect of these labels, is there a more nuanced way to discuss the merits and pitfalls of AI tools?

Before and After (in Charts)

The idea of an honest discussion on the topic prompted some self soul-searching. Prior to AI tools, I spent the majority of my time writing and refactoring code, as illustrated in a pie chart below. Generating code was the most time-consuming process, and subsequent review and use would reveal deficiencies that I would address by refactoring. The planning process does not figure heavily here. When working on my own projects, I typically had a clear vision and goals from the outset.

Time Spent Before AI Tools
Time Spent Before AI Tools

Introducing AI tools radically changed my process for programming. By embracing modernity, I have reaped some rewards, and also been cut upon the sharp new edges. Here are the ways the game has changed with coding assistants:

Planning consumes a much larger portion of my time. Coding agents work from planning docs, which are basically fancy to-do lists that help agents tackle projects with larger scope than would normally fit comfortably in their context window. Even when I can clearly articulate my vision, reviewing the plan is essential to catch bad decisions before the agent starts trying to implement them. It is better to take four whacks at making a good planning document than to execute a flawed plan and taking multiple passes to refactor.

Generation is significantly faster, so fast it can be a cheap thrill. Once you add sufficient asterisks, though, that lofty statement begins to sink like a rock. With a carefully reviewed and finely-honed action plan, is your agent going to execute smoothly? No. Completely? No. At all? Maybe. You are generating code significantly faster… but that’s about all. Generally speaking, an effective planning document is one that does better than a coin flip at directing the agent to produce code that achieves the articulated strategic vision.

Time Spent After AI Tools
Time Spent After AI Tools

Review is about assessing alignment with project goals and code quality.

  • First, did you get what you ordered? Does the code do what it needs to do? If you ordered a pump, does it pump? While the agent can jump short sets of hurdles like a track star, only you can assess the overall architecture and see the design needs and flaws.
  • Second, testing is about holding the agent accountable as well as code. Consider tracking documents that show timestamps and results for critical test coverage. Agents can and will lie shamelessly, and tell you tests passed that never ran, so don’t believe what you can’t prove. It’s like Reagan’s “trust but verify” without the trust. The review process takes significantly longer, because I am reviewing more. More code, more plans, all coming in fast. This stage is when we slow down and assess. You have an enthusiastic employee churning out substandard work, and you have to be a mentor and a disciplinarian.

Refactoring hasn’t gotten any better. When I reflect on my earlier coding projects, I spent significant time refactoring. Writing code was easy… rewriting was a different matter. Today, I do different sorts of refactoring. Multiple passes to drag the code towards a goalpost. Some things I wouldn’t recommend, like migrating from monorepo to workspace.

It's not the writing, it's the rewriting

Examining the code frequency graphs from a couple projects where I used coding assistants shows the prevalence of refactoring. The proportion of deletions to additions in arcgis regularly exceeds 20%, with the latest session exceeding 90%. If you added up all the additions on that graph, you would think the repo has 100k LOC, and it’s less than 30k.

Arcgis Code Frequency
Arcgis Code Frequency

In the elicitation repo, after a rosy initial burst of productivity, deletions exceed additions in two session windows by wide margins, indicating heavy refactoring. Sometimes you hear developers say git histories this bloody are signs that their code is becoming more efficient. Instead, I may have code that doesn’t work, doesn’t do what it was supposed to, or is entirely composed of either todos or errors. Like a paratrooper landing far off their mark, the journey to arrive where you were supposed to start at could be long and arduous, across hostile terrain.

Elicitation Code Frequency
Elicitation Code Frequency

Forensic Evidence in Commits

While developing the elicitation crate, I designed the Strictly Games MCP server as a way to dogfood my own product, to figure out if it even worked. The Tic-Tac-Toe game is designed both as a POC and a showcase of what the library can do. The planning document emphasized the elicitation requirements in great detail. Here is an excerpt from an actual commit message (e57aae7) submitted by my agent midway through the project:

feat(elicitation): Implement elicitation-based interactive game loop

MAJOR ARCHITECTURAL FIX - This implements the ACTUAL requirement that was completely missed in the initial implementation.

The Problem: - Completely missed the elicitation framework requirement

The Solution: - Created proper elicitation-based game loop

  • Agent enters interactive state machine via play_game tool
  • Server uses Move::elicit_checked() to prompt agent via sampling
  • Loop continues until game ends (Won/Draw)
  • Agent cannot leave until game completion

Changes:

  • Added Move type that derives Elicit
  • Implemented play_game tool with elicitation loop

Does the agent sound like their boss chewed them out? I was not thrilled. Tic-tac-toe is such a simple game, I had let my hopes get unrealistically high. The problem with testing my own library, is that the agent had no training material that had even seen anything like my code or my API. When cargo check started to fail, the agent made it work by falling back on their training, and literally ripped my code out until the project compiled again. Charming, right?

Notice the long list of things the agent had to implement for this “architectural fix”, these represent the first on-target efforts of the agent to use the methods in my library. Arriving at the code design you want is an iterative process, and somehow it helps to have a working design that is wrong, that you and the agent can refer to and work from as a common reference.

While these initial uses of my library were nice gestures, we were still a far cry from being the “elicitation showcase” I had envisioned. Here is an excerpt from a subsequent commit (9d411d3):

feat(position): Add Position enum with Select trait

Implement proper elicitation pattern using Position enum instead of raw u8 values. Agents now select from labeled options, with runtime filtering to show only valid (unoccupied) squares.

Key innovation:

  • Static enum with all 9 board positions
  • Select trait provides human-readable labels
  • Position::valid_moves(board) filters by occupancy at runtime
  • Agent presented with ONLY valid options (can't pick occupied)

This is the correct elicitation paradigm for constrained choices: present filtered enum variants, not open numeric types.

Benefits:

  • Type-safe move construction
  • Agent cannot select invalid position
  • Self-documenting (TopLeft vs 0)
  • Showcases Select paradigm properly

If you think language like “proper elicitation pattern … instead of raw u8 values” and “the correct elicitation paradigm” implies that the previous implementation was incorrect, you would be right! After developing the Position enum, I find the new “type-safe” version converts Position to u8, elicits a u8 from the agent, and attempts to convert it back to a Position.

The entire point of the Position enum is that we derived Elicit on it, a trait that comes with elicitation methods. Instead of passing the Position directly to the client, and calling Position::elicit() in the client code, it had written a home-brewed http call to our MCP server, eliciting u8 values from the target agent. The agent had no intuition for how to use my library, and had abandoned it at the last mile.

Why Bother?

Since adopting AI tools in October of 2025, why am I still using them? Has the juice been worth the squeeze? I have tried to be honest and balanced in presenting the drawbacks and pitfalls of AI use, so I will now make an effort to discuss the perceived benefits.

What If?

Coding assistants lower the transaction cost of experimenting with new crates or unfamiliar APIs. From trial balloons with up-and-coming libraries, to tackling unfamiliar domains, AI tools allow me to try a new idea without committing many resources. I have been able to explore ideas (good and bad) that would have languished on my future projects list indefinitely. The most rewarding benefit to me has been the ability to try out architectural designs that would otherwise live only in my head.

During the year of coding prior to AI adoption, I was able to develop a single library, bears, a wrapper for the REST API serving the Bureau of Economic Analysis. In the six months after adoption, I have experimented with projects form_factor, botticelli, arcgis, elicitation, and strictly_games. Regardless of the individual outcome of any one of these crates, it is more satisfying to explore five ideas instead of one. The increased feedback from multiple trials feels more instructive in consequence.

What About?

We all want our code to be both science and art. Science in its formal correctness, efficiency and performance. Art in its beauty, expressiveness and form. When in the zone, I am both renaissance painter and precision machinist. Maybe in my dreams! I seek this state of being, but rarely realize it. In fact, a lot of my code is boring.

Take the bears library as an example, or any wrapper around a REST API. After a few core architectural choices (async), and some early vendor lock-in (tokio + reqwest), you are left with a lot of response types to sort and process and deserialize using a same design over and over again, with minor variations. The large surface area of the API should have scared me off, and didn’t, which is a second key point: not only is the code dull, but I need lots of it. Pretty much every project I’ve ever dreamed up has bitten off more than I can chew. Even if the code is easy, I can only churn it out so fast.

Note that I am not the only one to find my code style boring! The LLM agent is the first one to cut corners when asked to do dull work. A common issue was sed abuse, which mangles the Rust code, followed by really funky sed calls that mangle it worse, followed by requests to use python3. After one brutal token bonfire session, something seemed to click for the agent, and we added this insight to our CLAUDE.md:

Manual vs Automated Edits

Manual edits are faster, cheaper, and more reliable than sed/awk automation.

Reality check on "automation":

  • Slower (fixing broken output takes multiple rounds)
  • More expensive (tokens wasted on revert/fix cycles)
  • Lower quality (syntax errors, broken code)
  • More human work (manual cleanup, repeated review)

Manual approach wins when:

  • Editing multiple similar locations (view file, targeted edits, verify)
  • Pattern is clear but context varies
  • Mistakes are costly to fix

Use automation only for:

  • Truly mechanical transformations (e.g., renaming single identifier)
  • Single-pass changes with zero ambiguity
  • Operations you can verify programmatically before committing

When tempted to automate: pause, view the file, make targeted edits. The "tedious" path is usually fastest.

See? Even the machine calls my code tedious.

It helps to juice the confidence of the agent with phrases like “you are a tireless machine”, because in fact the agent does tire quickly of using the edit tool and fixing things one by one, and will attempt to be “more efficient”, to my detriment every time.

Success Metrics

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it” -- Brian Kernighan

I like to comfort myself with Kernighan’s law: boring is a design goal. Biting off more than I can chew is not. But maybe now you can see how coding assistants fit a need for me: produce large amounts of boring code, following a fairly prescriptive design. I have expressed my frustration at the failure of agents to achieve design goals on the first pass, and the need for successive refactors to arrive at the intended vision. However, even with the rewriting, I am more productive with coding assistants than I was alone.

The bears library accumulated 40k LOC of lovingly hand-crafted code in roughly 9 months of my fleeting life. After revisions, here is a snapshot of my five AI-assisted libraries:

CrateLOC
form_factor11k
botticelli74k
arcgis30k
elicitation37k
strictly_games9k

These counts are code only, excluding blank lines and all markdown. These numbers would be inflated if we included all the planning docs, for example. That’s a total of 161k LOC in 6 months (26.8k/mo), compared to 40k in 9 months (4.4k/mo), an increase in productivity by a factor of six. People talk about achieving a 10X productivity boost, and AI adoption got me to 6X.

Now let’s talk about a more interesting metric: a couple of the AI-assisted libraries actually work. Both elicitation and strictly_games do what they say on the box, and are working today. The arcgis library currently works for about 80 operations, with roughly a third of our integration tests still failing. Let’s compare that to the bears library, which is still a WIP… and the library before that. Coding assistants have enabled me to bridge some of the gap between my ambitions and my abilities to churn out reams of code.

Conclusion

Yes, I love and hate coding assistants. The parts I hate reflect my own knee-jerk response to the limitations of the tools as they are today. I would get pretty emotional about my hand-written code, too, when the compiler was all errors. The parts I love also tap into feelings that tie back into why I got into coding in the first place. It is rewarding to see my designs come to life and be able to share them with others. AI adoption has changed the design process in many ways, but it has not robbed me of the satisfaction that I feel from writing code.

Remember that the AI does not decide what project we work on today, you do. You articulate the vision the goals. You review and critique. You decide what comes next, a new feature, more tests, or audits for code quality. At the end of the month, you pay the bill. The result reflects your intent and authorship, and it can reflect well or poorly upon you!

If the work is good, the value of your contribution may be diminished in the eyes of others because of AI assistance, but they won't be able to take away the fact that it is good. If the work is substandard, ultimately that is your problem, and then it doesn't matter how you did it. It's just bad. And whose fault is that? It's not the AI.