# How to Build an Agent - Amp
Thorsten Ball, April 15, 2025
It’s not that hard to build a fully functioning, code-editing agent.
It seems like it would be. When you look at an agent editing files, running commands, wriggling itself out of errors, retrying different strategies - it seems like there has to be a secret behind it.
There isn’t. It’s an LLM, a loop, and enough tokens. It’s what we’ve been saying on the podcast from the start. The rest, the stuff that makes Amp so addictive and impressive? Elbow grease.
But building a small and yet highly impressive agent doesn’t even require that. You can do it in less than 400 lines of code, most of which is boilerplate.
I’m going to show you how, right now. We’re going to write some code together and go from zero lines of code to “oh wow, this is… a game changer.”
I urge you to follow along. No, really. You might think you can just read this and that you don’t have to type out the code, but it’s less than 400 lines of code. I need you to feel how little code it is and I want you to see this with your own eyes in your own terminal in your own folders.
Here’s what we need:
- Go
- Anthropic API key that you set as an environment variable, ANTHROPIC_API_KEY
Pencils out!
Let’s dive right in and get ourselves a new Go project set up in four easy commands:
mkdir code-editing-agent cd code-editing-agent go mod init agent touch main.go
Now, let’s open main.go and, as a first step, put a skeleton of things we need in it:
package main
import ( "bufio" "context" "fmt" "os" "github.com/anthropics/anthropic-sdk-go" )
func main() { client := anthropic.NewClient() scanner := bufio.NewScanner(os.Stdin) getUserMessage := func() (string, bool) { if !scanner.Scan() { return "", false } return scanner.Text(), true } agent := NewAgent(&client, getUserMessage) err := agent.Run(context.TODO()) if err != nil { fmt.Printf("Error: %s\n", err.Error()) } }
func NewAgent(client *anthropic.Client, getUserMessage func() (string, bool)) *Agent { return &Agent{client: client, getUserMessage: getUserMessage,} }
type Agent struct { client *anthropic.Client getUserMessage func() (string, bool) }
Yes, this doesn’t compile yet. But what we have here is an Agent that has access to an anthropic.Client (which, by default, looks for ANTHROPIC_API_KEY) and that can get a user message by reading from stdin on the terminal.
Now let’s add the missing Run() method:
// main.go
func (a *Agent) Run(ctx context.Context) error { conversation := [0]anthropic.MessageParam{} fmt.Println("Chat with Claude (use 'ctrl-c' to quit)") for { fmt.Print("\u001b[94mYou\u001b[0m: ") userInput, ok := a.getUserMessage() if !ok { break } userMessage := anthropic.NewUserMessage(anthropic.NewTextBlock(userInput)) conversation = append(conversation, userMessage) message, err := a.runInference(ctx, conversation) if err != nil { return err } conversation = append(conversation, message.ToParam()) for _, content := range message.Content { switch content.Type { case "text": fmt.Printf("\u001b[93mClaude\u001b[0m: %s\n", content.Text) } } } return nil }
It’s not a lot, is it? This loop in Run() lets us talk to Claude. But that’s already the heartbeat of this program.
And for a heartbeat, it’s pretty straightforward: we first print a prompt, ask the user to type something, add it to the conversation, send it to Claude, add Claude’s response to the conversation, print the response, and around we go.
This is every AI chat application you’ve ever used, except it’s in the terminal.
Let’s run it:
export ANTHROPIC_API_KEY="this is the last time i will tell you to set this"
Download the dependencies
go mod tidy
Run it
go run main.go
Then you can just talk to Claude, like this:
$ go run main.go Chat with Claude (use 'ctrl-c' to quit) You : Hey! I'm Thorsten! How are you? Claude : Hi Thorsten! I'm doing well, thanks for asking. It's nice to meet you. How are you doing today? Is there something I can help you with? You : Can you come up with any horse-related nicknames that make fun of my first name? Claude : I can try to come up with some playful horse-related nicknames based on "Thorsten":
- Thorough-bred Thorsten
- Trotsten
- Thorsten the Stallion
- Neighsten
- Horseten
- Thorsten the Galloper
- Saddle-sten
- Thorsten of the Mane
- Canter-sten
- Thorsteed
These are meant to be lighthearted - I hope they give you a chuckle!
You