Speakeasy Logo
Skip to Content

Engineering

Preparing your repo for AI development: Lessons from Gram

Ryan Albert

Ryan Albert

November 20, 2025 - 5 min read

Engineering

When we started building Gram , our goal wasn’t specifically to make the codebase “AI-friendly.” Initially, we just wanted a codebase where any developer could jump in and be productive quickly, no tribal knowledge required.

Turns out, what makes a codebase easy for new developers also makes it easy for AI agents.

Think of AI as a developer with broad experience, but shallow context”

The best mental model we’ve found: AI coding agents are developers with a breadth of experience who lack knowledge depth, specifically in the context of your project. They excel at pattern matching but they don’t build on context overtime the same way a developer would.

This reframe changes how you think about structuring code. If a new developer would struggle to find where API schemas are defined or how to generate migrations, a coding agent will too.

Contract-first, generated code everywhere

The core principle behind Gram’s structure is contract-first design with extensive code generation. We use:

  • Goa  for API contracts and server stub generation
  • SQLC  for type-safe database queries
  • Speakeasy  for SDK generation
  • Atlas  for database migrations

Here’s what that looks like in practice.

To add a new API endpoint, you define it in the design package :

var _ = Service("projects", func() { Description("Manages projects in Gram.") Method("listProjects", func() { Description("List all projects for an organization.") Payload(ListProjectsPayload) Result(ListProjectsResult) HTTP(func() { GET("/rpc/projects.list") Param("organization_id") }) Meta("openapi:operationId", "listProjects") Meta("openapi:extension:x-speakeasy-name-override", "list") Meta("openapi:extension:x-speakeasy-react-hook", `{"name": "ListProjects"}`) }) })

Run mise gen:goa-server to generate the server stubs and OpenAPI spec.

For the database, it works similarly. When adding a new database table, you write a schema in server/database/schema.sql:

CREATE TABLE IF NOT EXISTS projects ( id uuid NOT NULL DEFAULT generate_uuidv7(), name TEXT NOT NULL CHECK (name <> '' AND CHAR_LENGTH(name) <= 40), slug TEXT NOT NULL CHECK (slug <> '' AND CHAR_LENGTH(slug) <= 40), organization_id TEXT NOT NULL, created_at timestamptz NOT NULL DEFAULT clock_timestamp(), updated_at timestamptz NOT NULL DEFAULT clock_timestamp(), deleted_at timestamptz, deleted boolean NOT NULL GENERATED ALWAYS AS (deleted_at IS NOT NULL) stored, CONSTRAINT projects_pkey PRIMARY KEY (id) );

Instead of writing migration files manually, run mise db:diff add_organization_metadata and Atlas generates the migration SQL for you.

Then write your queries using SQLC syntax in module-specific queries.sql files:

-- name: ListProjectsByOrganization :many SELECT * FROM projects WHERE organization_id = @organization_id AND deleted IS FALSE ORDER BY id ASC;

Run mise gen:sqlc-server and SQLC generates type-safe Go repository that you can connect to your server implementation. No room for deviation, no need to look at five different examples to figure out “the right way.”

Lastly, running mise gen:sdk updates the Speakeasy SDK, complete with React Query hooks for use in the client.

Mise: A recipe book for agents

Mise  is a polyglot development environment manager and task runner. Beyond managing tool versions (Go, Node.js, pnpm), it provides a discoverable task system that has become central to how we work.

All our development tasks live in .mise-tasks as executable scripts organized hierarchically:

.mise-tasks/ ├── db/ ├── diff.sh # Generate migrations ├── migrate.sh # Run migrations └── rewind.mts # Rewind migrations ├── gen/ ├── goa-server.sh # Generate Goa code └── sqlc-server.sh # Generate SQLC code └── start/ ├── server.sh # Start server └── worker.sh # Start worker

Agents can run mise tasks to see what’s available, then execute mise db:migrate or mise gen:sqlc-server. Progressive disclosure at its best, the agent discovers commands as needed rather than needing everything upfront.

Everything in one place: The monorepo advantage

Gram is a true monorepo: server, web app, CLI, and our NPM functions framework all live together in speakeasy-api/gram.

This matters because when an agent needs to make changes that touch multiple surfaces (say, adding a new CLI feature that calls a server endpoint), it can see both sides of the interaction. No context switching between repos, no hunting for the right version of a shared type.

Zero to productive in one command

We have a mise zero command that sets up everything: installs dependencies, pulls Docker images, starts local services. One command, and you’re ready to develop.

This isn’t revolutionary, it’s just thoughtful automation. But it eliminates a whole class of “getting started” friction that would otherwise require back-and-forth with an agent or digging through README files.

Keep agent instructions minimal

We maintain a CLAUDE.md (symlinked to AGENTS.md, GEMINI.md, etc.) with basic onboarding info. But we keep it lean. Rather than documenting every workflow, we point to general patterns and where a developer would find more information:

## Available commands All development commands are in Mise. Run `mise tasks` to see available commands, or execute them directly: - `mise db:*` - Database operations - `mise gen:*` - Code generation - `mise start:*` - Start services locally

The goal is day-one context, that a developer or a coding agent can explore from there.

What works well, what doesn’t

AI agents are great at:

  • Formulaic work (adding new API endpoints, database tables)
  • Bug investigation when you can describe the general area of the problem
  • Changes that generally are heavy on pattern matching

They struggle with:

  • Designing net new patterns or architectures
  • Understanding subtle business logic without extensive context
  • Making opinionated decisions about design

Our approach: let AI handle the boilerplate, while developers own the design and review. For simple things we describe the structure we want, and the agent writes it out faster than we could. This saves us time to work on the complex.

The real win

Building Gram this way didn’t just make it easier to use AI. It made the codebase easier to work with, period. New team members ramp up faster. Code reviews focus on logic, not style. Everyone benefits.

If you’re building a new project and want it to work well with AI tools, don’t overthink it. Focus on clear patterns, good discoverability, and minimizing cognitive load. The AI-friendliness will follow.


Want to see these principles in action? Check out the Gram repo  or try building an MCP server with Gram Functions.

Last updated on

Organize your
dev universe,

faster and easier.

Try Speakeasy Now