Taking Little Bites

You feel fuller when you focus on one bite at a time.


Have you ever been to a buffet? There’s a place in Pennsylvania called the Shady Maple Smorgasbord that really exemplifies what’s possible. There’s dozens of food stations of all varieties, probably over a hundred tables, a few historical dioramas of Pennsylvania Dutch settlers, and a massive gift shop in the basement. It’s an incredible place to visit and the food is actually very good! I love going there with friends and enjoying the camaraderie.

I always leave full, but I never quite leave satisfied. The point of a buffet is to be all-you-can-eat, and since you’re paying a fixed price you’re incentivized to eat as much as possible to get the best value for yourself! Diametrically opposed to this is Tapas, a Spanish tradition of eating small plates of high quality food, one at a time. It makes you focus on the experience of the dish and savor all of the flavors that you are tasting. How is it balanced? Is it salty? Does it pair well with the acidic dip next to it? I’m never quite full, but I always leave satisfied.


All-You-Can-Generate

I see a similar concept play out with Artificial Intelligence coding agents. It’s possible to ask it to develop “an application that does everything”, but you won’t be satisfied with what you get. AI-powered software development ends up being like a buffet. You can pile on more and more features onto your plate, code around any roadblocks you see, and make massive all-encompassing refactors in a single prompt. But by the end of the meal, you’re going to have a codebase that’s a mix of different flavors that aren’t balanced together.

There is no possible way to actually review some of these changesets. It’s commonplace now to have a pull request of hundreds of changes in dozens of files across entire applications. Previously, we were limited by the amount of typing that we could do – the speed of our fingers and brains working in parallel to implement code. Now that we’re no longer limited by typing our own code, the velocity of development makes it possible to stuff huge amounts of work into a day. Now we’re limited by the amount that we can comprehend. I’ve had workdays where I’ve burned up all of my critical thinking and I can feel my brain leaking out of my ear by two in the afternoon.

So, what do you do? You start to skim the reviews, focus less, and protect your critical thinking for other work. You give generic and vague feedback and start to allow mediocre code through. Rather than fine-tooth-comb the code that the AI is generating, you get a sense for the basic patterns it’s applying, verify that the tests are verbose and passing, then merge it right in.

I’ve seen these tools run wild and burn tokens by generating entire systems that are totally unnecessary and over-engineered. Then, I either need to manually scrape it out or ask the agent to try again. Neither feels great. It’ll include packages that aren’t necessary or have a boated test library that covers every possible scenario, just like you asked it to. If you hear people use the term “AI slop”, this is exactly what they’re referring to. A huge buffet of code that people dump into their projects because it makes them feel full.


Specifying our Desires

What would it look like to be satisfied when using AI coding agents? I don’t think we need to get rid of the AI tools entirely – It’s much better at typing than I am. It’s also very good at translating syntax and applying patterns to codebases. How can you savor the small decisions and cater each problem you solve to your personal palate? By taking little bites.

This is known as Spec-Driven-Development (SDD). The real problem is that we’re asking the AI to do massive amounts of work and then delegating all of those little decisions to it. The solution is to break down the requirement from a massive one into each smallest possible piece. This way we can tackle each piece slowly and be sure that we like and understand the results.


For the past few weeks I’ve been using spec-driven-development to implement a windows automation tool. Many exist already, but the concept is understood enough that I wanted to create my own as a chance to explore SDD. These are the lessons that I’ve learned:

Instead of asking your agent for a whole feature, collaborate with it on designing a specification. Go back and forth conversationally to determine the needs of your project. Do you need to use a database? Work together to specify the exact requirements so that the tool can recommend options that will be successful. There’s nothing worse than getting halfway through a project and realizing that you’ve hit a roadblock and need to redo all of your database queries. I’ve seen projects get totally abandoned because of that. Here is an excerpt from my core-engine design document:

### Task 1: Solution and project scaffold

**What:** Creates the three .NET projects and wires them into a solution. Sets `net10.0-windows10.0.22621.0`, nullable reference types enabled, and adds the required NuGet references.

**Creates:**
- `AutoMancer.slnx`
- `src/AutoMancer.Engine/AutoMancer.Engine.csproj` — `Interop.UIAutomationClient` (Note: do NOT add `Microsoft.Windows.SDK.Contracts` — it is incompatible with .NET 5+; WinRT APIs are provided by the `windows10.x` TFM)
- `src/AutoMancer.Cli/AutoMancer.Cli.csproj` — `System.CommandLine` + Engine project reference
- `tests/AutoMancer.Engine.Tests/AutoMancer.Engine.Tests.csproj` — xunit + Moq + Engine project reference

**Done when:** `dotnet build AutoMancer.slnx` exits 0.

Create a plan. Once you have a specification file that you’re happy with, create a roadmap that describes every section of development. Each feature in the app should get a small breakdown. Keep breaking down those sections into the little plates that we’re pining for. It should start to feel a little pedantic. What this gives us is tiny changesets that we can carefully review. If one is too small to be satisfying, grab another. When the changeset feels full, commit it. Here’s what my roadmap looks like:

## Phase 1 — C# Proof of Concept

### Stage 1 — Core Types and Contracts
> **Unlocks:** Everything. This is the type vocabulary the entire codebase shares.
> **Estimated time:** 1–2 hours
> **Done when:** `dotnet build AutoMancer.slnx` passes with 0 errors.

| Batch | Work | Plan ref |
|---|---|---|
| 1.1 | Solution scaffold — `AutoMancer.slnx`, three `.csproj` files, NuGet refs, delete stubs | Engine Task 1 |
| 1.2 | `LocatorStrategy`, `Locator`, `ElementHandle`, `Rect`, `ResolverOptions` | Engine Task 2 |
| 1.3 | `IElementProvider` + `ElementSnapshot`; `AppSession` (`LaunchAsync`, `AttachByPid`, `AttachByTitle`) | Engine Task 3 |
| 1.4 | `EngineLogger` + error types (`ElementNotFoundError`, `ElementNotInteractableError`, `AppLaunchError`) | Engine Task 4 |
| 1.5 | `ClosestMatchFinder` + Levenshtein; `DpiHelper` testable overloads | Engine Tasks 5–6 |

Note how the roadmap refers to the task in the above section, and how they work together to inform development.

Be clear, but not too literal. Make sure that you’re mindful of exactly what you mean when you’re asking the agent to implement it. In my original document, I had specified to use C# and .NET for the engine. When I was reviewing the code I caught that it was using .NET version 8. That’s a valid version to use, but version 9 and 10 are both out so I asked it to use the “latest version of .NET”. Hilariously, the agent took that request literally and switched to the latest pre-release version of .NET, which had experimental changes and was unstable. Eventually I settled on having it use “a stable release of .NET 10” and got the results I wanted. Had I just let it go implement the entire program, I would have had to do a major refactor over that silly little mistake.

Setup rules files for the agent to follow. Create a small markdown file that has guardrails for code generation that the agent needs to check back in on. By having your tool read this and refer back to it when coding, it’ll keep your preferences in mind. Here’s what my file looks like:

## Code style

Write the minimum code that satisfies the requirement. No speculative abstractions, no helper methods for single call sites, no defensive error handling for internal code paths. Three similar lines beats a premature abstraction.

**Every function/method gets a one-line topline header comment** that briefly explains its purpose. This applies to constructors, public methods, and private helpers alike. Place it directly above the signature:

```csharp
// Serializes and writes one JSON line; no-ops when level is below the configured minimum.
private void Write(LogLevel level, string message, object? data) { ... }
```

Inline comments only when the *why* is non-obvious — never narrate what the code already says.

Keep iterating and updating documentation. You might notice yourself correcting small mistakes here and there. If so, it could be an issue in your original specification. Although I had included the Apache II license in the codebase, I kept seeing header comments in files that declared this was protected under the MIT license. Weirdly, the specification file had a sentence that mentioned the MIT license that I needed to cut out to avoid the confusion. These little errors are much easier to spot when looking over tiny changesets.

Review and directly challenge the design. I noticed in one section that the agent setup an error handling architecture that was abstracted to a few levels. It included tons of error classes for operations that weren’t going to get created until a few more milestones had passed. Sure, we’ll need them later, but it’s better to review these new errors once their supporting code is in place. Keep challenging and clarifying what is going on when you’re unsure of what is happening. Worst-case you learn something and become a better engineer. Best-case you steer the ship in the right direction.

If you eyes start to glaze over, you’re full. After a few changesets in an hour, I noticed that I wasn’t thinking critically about the code I was looking over. When that happens, the code starts making less sense and I start to just approve whatever I see. That’s the sign that I’m full, my critical thinking is spent, and I need to put it down until I have more to give. Staying mindful of when you’re cognitively spent will keep your code high-quality and keep you aware of what you’re creating.


Try it out! Let your creativity free and see what you can create. Just remember to be the captain of your ship. Seek satisfaction, and stop when you feel full.


Leave a Reply

Your email address will not be published. Required fields are marked *