How I build with AI agents
- #ai
- #agents
- #engineering
Okereke Chinweotito
I want an AI agent to take a useful piece of work off my hands. If I have to watch every command, paste every error back into the conversation, and remind it to run the tests, I haven't handed over much.
The interesting question is how to give it enough room to finish something without losing track of what it is doing.
Kiro's guide to giving agents more room to work helped shape this article. Its central suggestion is to hand over a complete task with a way to check the result. I like that starting point. Below is the workflow I use as a framework for doing that, with a concrete example you can adapt.
It should travel with you. By a harness, I mean the software around the model that lets it read files, edit code, run commands, and report back. Yours might live in an editor, a terminal, or a hosted environment. The controls will differ. The information the agent needs is much the same.
Start with the behavior you want
Suppose you're adding an export button to an orders page. "Add CSV export" sounds clear until you start implementing it.
Does it export the current page or every order matching the filters? Which columns belong in the file? What happens when there are no results? Can someone change a request parameter and download another organisation's records?
Those decisions exist whether a person or an agent writes the code. I want them written down before they become assumptions scattered across an implementation.
A useful brief can be quite short. Here is one I would start with for that feature:
Task
Add a CSV export to the orders page. Export all orders matching the active filters, including results beyond the current page, using the same columns as the table.
Context
Inspect the existing table, list endpoint, access checks, and CSV utilities. Follow the patterns already used in this repository. Point out any missing requirements before implementing them.
Behavior
Keep the active filters intact. Show progress while the export runs and a useful message if it fails. Only include records the signed in user is allowed to view. An empty result should produce a file containing the column headers.
Boundaries
Keep this change limited to the export. Do not replace the table, redesign authentication, add a dependency, or deploy the application. Use local fixtures rather than production data.
Checks and handoff
Cover filtered results across pages, empty results, CSV escaping, and access restrictions. Run the relevant existing checks. Return a summary of the changes, the commands you ran, their results, and anything you could not verify.
In a real task, I would add the relevant file paths and exact commands once I had checked the repository. I would also settle practical limits, such as the largest export we intend to support.
The point is to get the decisions out of my head. The agent can help me find the gaps, too. Asking it to inspect the existing code and list unanswered questions is often a useful first task on its own.
Give it a map of the repository
A working environment matters more than an elaborate prompt. Before handing over implementation, I want to know that the application starts and that the relevant checks can run. If a test already fails, that belongs in the task context. Otherwise, the agent might spend its time repairing an unrelated problem.
I keep a small set of project notes close to the code: how to run the app, how to test it, where the important pieces live, and which conventions are deliberate. A note explaining why an old service still exists is more useful than a paragraph announcing that the code should be clean.
Kiro's discussion of engineering teams makes a related point about shared project context. The part I would carry into any setup is that this knowledge should belong to the repository and the team. It shouldn't depend on one developer remembering which conversation contained the explanation.
Use your harness's project instructions if it has them. Otherwise, ordinary Markdown files are fine. Tell the agent which ones to read. Keep them specific and update them when the code changes.
For the export task, a map might point to the orders page, the query that applies filters, the permission checks, and one existing export elsewhere in the app. That's a useful route through the code. Dumping the entire repository into a prompt gives the agent more material to sort through before it can start.
- YOUDefine the taskBehavior, context, boundaries, and checks.
- AGENTBuild and testInspect, make a change, and check the result.
- YOUReview and shipRead the diff, try the behavior, and decide.
Give it a useful finish line
I want the agent to own a coherent change: inspect the relevant code, implement the behavior, exercise it, and explain the result.
For a bug, that might mean reproducing the failure, adding a check that catches it, fixing the cause, and showing that the check now passes. For an interface, it might mean implementing the states and inspecting them at different screen sizes.
The checks need to fit the task. Our export feature needs evidence that the filters and access rules still apply. A coverage percentage alone wouldn't tell me that. A screenshot could show the button, but it wouldn't tell me what went into the file.
I also make the limits clear. If the agent discovers that the feature needs an API change we haven't agreed on, it should bring that decision back. If it keeps encountering the same failure without new information, it should report what it tried. Repeated attempts are only useful when they change our understanding of the problem.
Some harnesses cannot open a browser or execute every check. In that case, the agent should leave those checks explicitly unfinished and give me enough detail to run them. A generated test and a passing test are two different things.
Parallel work needs an agreement
The frontend and backend of an export feature look like natural candidates for separate agents. They can be, once the interface between them is settled.
Both need to agree on the request parameters, the response, how errors are represented, and what happens when an export takes a while. I would record that contract first. Otherwise, it is easy to end up with two reasonable implementations that disagree at the point where they meet.
Each agent also needs its own working copy or another form of isolation that prevents it from overwriting the other's edits. If the harness doesn't support that, separate branches in separate directories can work. If isolation is awkward, I would run the tasks in sequence.
A second agent can also inspect the test plan while the first implements the feature. That is often a cleaner split than asking two agents to modify the same service.
I still need to integrate the result and run the checks against the combined change. Passing checks on two separate branches doesn't establish that they work together.
Read the change, then use it
When the agent returns, I start with the diff. I want to see whether the change stayed inside the brief, whether an existing abstraction was reused, and whether anything unrelated was removed or rewritten.
Then I look at the evidence. What ran? What failed? What was skipped? Tests written alongside an implementation can repeat its assumptions, so I compare them with the behavior we agreed on at the start.
For the export example, I would open the page, apply filters, download the file, and inspect its contents. I'd check an empty result and an account with fewer permissions. On the interface itself, I'd try the button with a keyboard and look at the loading and failure states.
This is also where technical taste comes in. A change can work and still be unnecessarily complicated. I want to understand why a new layer exists and whether the next person will be able to follow it.
When something needs revision, I describe the observed problem. "The export only contains the first page even though the filter matches more records" gives the agent a clear fact to investigate. "Try again" leaves it guessing.
The release follows the project's normal process. Tool permissions and the task brief should agree about whether the agent may deploy, touch shared environments, or access sensitive data. A sentence in a prompt is not a replacement for those controls.
Leave the next session somewhere sensible to start
Before closing the task, I want a short record of the decisions worth keeping. For our example, that might be why the export happens on the server, which limits apply, and how to test the access rules.
That record belongs with the code. There is no need to preserve an entire conversation when a few lines and a link to the relevant test will do. I also avoid treating the agent's summary as documentation until I've checked that it matches what actually shipped.
This matters when another person picks up the work. It matters just as much when I return weeks later and have forgotten the reasoning.
A workflow worth keeping
I would judge this process by the work it leaves behind. Did the feature meet the brief? Was the review manageable? Did I spend less time repeating context? Can someone else maintain the result?
Those are better questions for me than how long an agent ran or how many files it changed. A small fix may need one short conversation. A larger change may benefit from a plan, several tasks, and more deliberate review.
The setup is doing its job when I can give the agent a clear piece of work, step away with a reasonable expectation of progress, and come back to a result I can evaluate.
For the broader question of what this means for our jobs, I wrote about the future of coding in the age of AI.