I work with coding agents in two modes. For larger features, I write a detailed spec before any code. For smaller tasks, I skip the spec and set guardrails instead. Both work — they solve different problems.

Full Spec

For new features or anything with non-obvious architectural decisions, I write everything out first — data flow, DB schema, API shape, edge cases. I have a spec skill that interviews me about all of this before any code gets written. What comes out is a document like:

## Data Model
- users table with email, hashed_password, created_at
- sessions table with user_id, token, expires_at
- No soft deletes — hard delete on account removal

## API
- POST /auth/register — validate, hash, insert, return session token
- POST /auth/login — verify, create session, return token
- Auth middleware checks session token on protected routes

## Edge Cases
- Duplicate email returns 409, not a generic error
- Expired sessions return 401, frontend sends user to login

The agent follows this. It doesn’t get to suggest alternatives or rethink things during execution. This eliminates drift — there’s no room to wander.

Guardrails Instead of a Spec

For smaller tasks — bug fixes, refactors, wiring up something that follows an existing pattern — I skip the full spec. I describe the problem and add constraints. A real prompt looks like:

The /auth/login endpoint returns 500 when the email doesn't exist.
It should return 401 with { error: "invalid_credentials" }.

Constraints:
- Do not modify the database schema
- Do not change more than 20 lines
- Keep changes in src/routes/auth.ts
- Run the existing auth tests after fixing

The constraints keep the agent from over-engineering it. “Do not modify the database schema” prevents the agent from deciding it needs a login_attempts table. “Keep changes in src/routes/auth.ts” stops it from refactoring the error handling across three files.

I couple these with validation — linting, type checks, tests — to catch anything that goes outside the boundaries.

When I Use Which

New system, multiple services, design decisions that affect the whole project? Full spec. A bug in a well-understood module, or a refactor that follows an existing pattern? Guardrails and go.

The gray area is medium-sized tasks — adding a feature to an existing system where the pattern is clear but there are a few decisions to make. I usually start with guardrails and tighten them if the agent drifts. If I find myself adding more than four or five constraints, that’s a sign I should write a spec instead.