Skip to main content

Why I built a reference Go project (and taught an AI agent to clone it)

·3 mins

I get asked about how to structure a Go project often. Not the big architectural questions about microservices versus monoliths, but the mundane stuff: where does the config go, why isn’t there a pkg/ directory, what’s the difference between internal/books/ and internal/database/books/.

I used to answer with a wall of text. Then I started linking to specific repos I’d worked on, which helped but came with baggage — real projects accumulate weird one-off decisions that don’t generalise. So I built a reference instead: example-project-structure, a minimal Go HTTP/RPC service backed by PostgreSQL. A fake bookstore with just enough domain (books, authors, genres) to show joins and type conversions without drowning in features.

That was reason one. Reason two turned out to be more interesting.

Structure as a document an agent can read #

I’ve been spending time with AI coding agents lately, and I kept running into the same friction: every time I start a new project, I want it in a structure I already know and like. But AI output is non-deterministic. Ask an agent to scaffold a project twice and you’ll get two different structures with the odd similarity. One run gives you models/, the next gives you entities/, neither matches what you actually want. So I thought: why not give it a concrete example to bootstrap from instead of hoping it guesses right?

So I wrote the reference repo with a second reader in mind. The docs/ directory isn’t just for humans — layout.md maps every directory and explains what lives where, design-decisions.md captures the reasoning behind choices like “Service not Repository” and “package-by-feature not package-by-layer”, and adding-an-entity.md is a step-by-step walkthrough an agent can follow mechanically.

Then I took it a step further. The repo includes a BOOTSTRAP.md file that’s written specifically for an AI agent. It assumes the agent has already cloned the repo and copied its contents into a new project directory. From there it walks through every rename, every file to strip, every conditional based on what the user actually wants (OpenAPI only? ConnectRPC only? No database?). Each step lists what to change and every place it appears so the agent doesn’t need to rediscover the structure on its own.

I’ve paired this with a Claude Code skill (not open source yet, still refining it) that asks the user a handful of questions (project name, module path, API style, database choice) and then follows BOOTSTRAP.md to scaffold a new project. When paired with some details about what you’re building, the AI can produce a fairly decent starting point for a project.

What the structure actually looks like #

The project is small by design — just enough to show the patterns without drowning in features:

.
├── api/              # protobuf & OpenAPI schemas
├── apigen/           # generated API stubs
├── cmd/bookstore/    # binary entry point
└── internal/
    ├── books/        # domain package (one per feature)
    ├── database/     # data access, separated from transport
    └── server/       # HTTP/RPC wiring

There’s no pkg/, no models/, no handlers/ directory. Each of those absences is a deliberate choice, and I’ve written up the reasoning in the repo’s docs.

Rather than cramming everything into one post, I’m breaking the details into separate pieces:

  • The structure itself — the directory tree, what each package owns, the layering rules, and what’s deliberately missing
  • Design decisions — why Service over Repository, why package-by-feature wins over time, why manual DI in runner.go
  • The BOOTSTRAP.md approach — writing documentation that serves both humans and AI agents, how the Claude Code skill works, and what I’ve learnt about making repos agent-friendly

For now, the repo is public and the docs are thorough. Have a look at example-project-structure — I’ll dig into the design decisions and the BOOTSTRAP.md approach in follow-up posts.