Working with AI Agents: Cooking Up Better Prompts by Iteration

05 May, 2025

Cooking Up Better Prompts by Iteration

Let's talk about working effectively with AI Agents. While writing this post, I experimented with different Agents and a variety of prompts. What I found is that the outcome depends far more on the quality of the prompt than on the specific Agent being used. There's no way around that. After many hours of trial and error, I landed on a combination of system instructions and prompt patterns that consistently led to solid results—regardless of the Agent. So let's peel back the layers and see what we can learn from the process.

When you're just getting started with an Agent, asking it to write code based on a simple request and then iteratively adding complexity usually goes pretty smoothly. It feels like AI has superpowers—you get results in hours that might otherwise take days or weeks.

But things get trickier when you need to add features that require restructuring or refactoring existing code. That’s when you start relying more on custom tweaks, carefully reviewing the generated code, and testing everything after each change.

Eventually, you realize that the ideas in your head don’t magically transfer into the machine. There’s no silver bullet—you have to write better prompts. That realization can be eye-opening.

Better prompts usually mean including all the relevant context—copy-pasting key details about your application so the AI has what it needs to generate appropriate code. It works, but it’s tedious. So you start looking for ways to streamline it. That’s when you discover "System Prompts": baseline instructions that are applied to every message you send to the Agent. In VS Code, for instance, you can store these in .github/copilot-instructions.md.

This approach simplifies the workflow significantly. But now you also need to maintain those system instructions so they reflect the current state of your codebase. Whenever you add complexity or want to steer the output in a specific direction, it’s worth updating or refining your Agent's instructions. It's not a hassle—but it is a new habit to form. And it's one we likely won't be able to avoid.

Today's Agents are still just statistical generators. When asked to write code, they draw from patterns they've seen before. They're not analyzing your codebase deeply or understanding your personal coding style. And honestly, that makes sense—imagine working with a human teammate who’s eager to help and jumps on any task you give them. You ask them to write tests for a component, but there are no existing tests in the project. You haven’t explained how you usually structure tests or which testing framework you’re using. So they go ahead and write something that looks like what they’ve seen online. They didn’t ask questions—but is that really their fault? Maybe you shouldn’t have assumed they’d read your mind. Maybe you should’ve given clearer instructions—or at least told them to ask follow-up questions.

To be fair, when it comes to people, the ability to ask the right questions is a valuable skill—and everyone should develop it. But it’s hard to expect that from junior developers. Even mid-level developers sometimes struggle with it. And right now, AI is at a similar stage—not in terms of how much it knows, but in how well it can apply that knowledge. It still needs guidance. No way around that.

The best method I’ve found is iterative refinement. Start simple and build from there. You can begin with a general prompt just to see what the Agent produces and which direction it takes:

Please add a phone number input field to the form.

This will generate something. Will it be exactly what you had in mind? Maybe—especially if your app is relatively simple. But as things grow more complex, you'll notice the output starts diverging from your expectations. And that’s where iteration comes in. Review the code it generates:

  • Did the Agent forget validation? Add that to the prompt.
  • Didn’t follow your class naming conventions? Include those in the prompt too.

Once you get the desired outcome, it might be time to update your system instructions so you don’t have to repeat those details with every request.

And this isn’t just a technical task. You kind of have to think like a product manager. There’s no way around that. What do I mean? Say you want users to be able to add multiple phone numbers. How would you phrase that to the Agent? Maybe something like:

The user should be able to add multiple phone numbers.

Will that work? Maybe. Will it work well or be usable? Probably not. Here’s the thing—AI doesn’t truly understand UI or UX. It just implements stuff. Great user experiences live in the edge cases—not the happy path. So stop for a moment and think about how the feature should really behave. Here’s how to think in PM terms:

  • Define the goal and success criteria. What does “done” look like?
  • Map the happy path and edge cases. How should the feature behave when inputs are missing or invalid?
  • Specify acceptance criteria. Include validation rules, UX behaviors, and performance constraints.
  • Prioritize requirements. Which aspects are critical for MVP, and which can wait?

It could be something like this, which is relatively straightforward:

I want to allow the user to add multiple phone numbers. By default, there should be only one phone input field, but a "+" button should be placed next to it so the user can click to add additional phone numbers. There are no restrictions on how many additional phone numbers a user can add. The "+" button should only appear next to the last phone input in the list. Each phone input should be validated the same way as the existing one.

This is certainly a step in the right direction, but we can push it even further:

You are an experienced frontend engineer. Please build a dynamic phone‑number form in React (using functional components and hooks) with the following requirements:

  1. By default, show exactly one phone‑number input.
  2. Render a “+” button immediately to the right of the last phone input.
  3. Clicking the “+” button appends a new phone‑number input below the existing ones.
  4. There is no limit on how many phone inputs can be added.
  5. Each input must use the same validation rules (e.g. a regex pattern or library of your choice) and display inline error messages when invalid.
  6. Ensure the “+” button always moves next to the newest (last) input.
  7. Provide complete, runnable code (JSX + state management + validation logic) and a brief explanation of your approach.

Output only the code and the short explanation—no extra commentary.

So the best way to approach it is to figure out the result using a structured iteration loop. Something like this:

  1. Start by defining the most basic behavior, based on your current understanding of the feature.
  2. Prompt the Agent based on that. Try to define functionality, but don’t overengineer it.
  3. Test it out. Notice the gaps and missing functionality. Try to identify edge cases.
  4. Refine your product definition and start the loop again.

Prompting is starting to look a lot like learning a new programming language. It has structure, it has rules—it’s just that the documentation hasn’t been fully written yet. We’ve been given the syntax, but the manual is still in progress.

 


You might also be interested in the following posts:

I continue my vibe coding journey by refining the infrastructure of a simple React app. I cover setting up Yarn, adding CSS Modules, improving date formatting, introducing i18n, and writing Copilot instructions. Most changes were guided by AI, with some manual adjustments along the way.

MCP is a powerful piece of tech. The idea of an API for AI unlocks a whole new world of possibilities. While it still feels a bit distant from our everyday workflows, we’re actively exploring how to make it fit—and whether a simpler alternative might work just as well.