Build your own coding agent
Why I built my own coding agent and lessons learned
Here’s an idea that will upset a lot of people. It doesn’t really matter what coding agent you use - they’re all basically the same.
And they’re easier to build than you might think.
I routinely switch between different agents, but don’t have a strong affinity to one over another (besides the Codex desktop app which is just a great interface).
Coding agents can look complicated. They edit files, run commands, inspect a codebase, review changes, and open pull requests. Tools like Claude Code, Codex, and Pi make the machinery underneath feel like magic. But the core parts of a coding agent are straightforward and easy to understand.
So let’s break down what a coding agent is, how they work, and implement a minimal coding agent of our own. Once you have a basic agent with a robust foundation, it’s easy to start adding in new features.
Neo
My own coding agent, Neo, is designed to support my personal development process. This largely means making AI development workflows a first-class concept in the harness.
In my latest video, I built a small version in 1,000 lines of code.
Micro Neo is a deliberately small version of Neo, my open-source coding agent. It’s a Go application that connects to an existing AI model and gives it three abilities: reading files, editing files, and running shell commands.
I called it “Micro” Neo because it strips the idea down to the smallest version I could make while still being useful. The UI is deliberately minimalist and the whole agent lives in one main Go file.
The point isn’t to compete with Claude Code or Codex. It’s to make the machinery behind those tools easier to understand.
The Loop
The main part of any coding agent is the primary loop. A language model can’t read a file or run a command on your computer by itself. It can only ask the surrounding program to do those things. That surrounding program is the agent harness.
The basic loop looks like this:
The user gives the agent a task (“review the code”).
The harness sends the system prompt, conversation, and available tools to the model via an API call.
The model either returns a response or requests a tool call.
The harness runs the requested tool calls on the local machine.
The harness sends the tool results back to the model.
The loop continues until the model returns a final response.
Most of the apparent intelligence comes from the model deciding what to do next. The harness provides the environment, executes the actions, and keeps the conversation moving.
In order to keep this loop from growing in complexity over time, I use an event based pattern. This is the same idea used by Claude Code hooks. The inner loop emits events, and the harness reacts to these events (e.g., running a permissions approval check before executing a tool call”).
Design First
When building any project, I spend most of my time on the design, architecture, and planning of tasks. This is where most of the leverage is for AI engineers today. AI agents execute the code. Humans define the work and design - this is the same thing a senior engineer does in any software team (planning, design, architecture).
Once we have a working architecture, it’s simply a case of delegating tasks to agents. I use a single skill that runs a multi-stage process to complete each task (read the task, create a new branch and worktree, code, review, test, open a PR, iterate).
Read docs/architecture.md and tasks.md.
Complete all of the tasks using $task-to-pr.Events
One of the more useful ideas I discovered while building Neo is that events are a good way to keep the agent loop simple.
It’s tempting to put everything inside the loop. You start by printing tool calls to the terminal. Then you add timing, logging, permission checks, metrics, hooks, and error messages. Eventually the most important part of the agent becomes difficult to understand.
A better pattern is to give the loop one job: call the model, run its requested tools, add the results to the conversation, and continue.
Whenever something interesting happens, the loop emits an event.
An event is just a small piece of data describing what happened. Our agent emits events when the model produces text, when a tool starts, when a tool finishes, when the task completes, and when something fails.
Here’s a simplified version:
func (a *Agent) executeTool(ctx context.Context, call ToolCall) string {
a.emit(Event{
Kind: "tool_start",
ToolName: call.Function.Name,
})
result, err := a.runTool(ctx, call)
a.emit(Event{
Kind: "tool_finish",
ToolName: call.Function.Name,
Result: result,
Err: err,
})
return result
}A terminal interface can listen for tool_start and display that a command is running. When it receives tool_finish, it can show a tick or an error. A different interface could send the same events to a web application, write them to a log, or expose them as JSON.
The agent loop doesn’t need to know which interface is being used.
This also makes the code easier to test. A test can collect the events and check that a tool started, finished, and produced the expected result without needing to inspect terminal output.
Going Further
Micro Neo deliberately stops at the foundation.
It has one model provider, three tools, an in-memory transcript, a serial agent loop, and a basic event-driven interface. This is enough to inspect a project, change code, run tests, and explain the result.
The cool thing is, once you have a basic agent, you can use the agent itself to build the rest of the project.
I’ve made the complete code, build prompts, test project, and architecture guide available in the Micro Neo repository.
You can also explore the full version of Neo to see how the same foundation grows into a larger coding-agent harness.
Hope this inspires you to build something great.
Check out the video to go more in depth.
Thanks.

