The spec
The artifact that carries intent across many sessions without carrying any session’s baggage.
Once work exceeds one session, something has to survive the gap. That something is a spec: a handoff artifact describing the objective of a multi-session piece of work, deliberately free of session-specific detail.
A spec is not documentation and not a plan. It is the shared, stable answer to "what are we building and how will we know it is right", written so that a fresh agent with no history can pick it up.
The shape
# Scheduled CSV exports ## Goal Let account admins schedule a recurring CSV export of their orders, delivered by email. Replaces the manual export they run every Monday. ## Not in scope - Formats other than CSV - Per-user (non-admin) schedules - Changing the existing on-demand export ## Constraints - Must reuse src/export/csv.ts. Do not write a second CSV writer. - Exports can be large: stream, never buffer a whole result set. - Scheduling uses the existing queue (src/queue/), not a new mechanism. - Timezone is the account's, stored in accounts.timezone. UTC at rest. ## Decisions already made - Schedules stored in a new table, not as a JSON column on accounts. (Rejected JSON: we need to query due schedules efficiently.) - Daily/weekly/monthly only. No cron expressions. ## Acceptance - An admin can create, edit, and delete a schedule in the UI. - A due schedule produces an email with a correct CSV attached. - A failed export retries 3 times, then notifies the admin. - Deleting an account removes its schedules. - npm run check passes; new code has tests. ## Open questions - What is the size limit before we link instead of attaching? (ask Dana)
What makes it work
- Not in scope does more than the goal section. It is where you spend the agent’s helpfulness in advance.
- Decisions already made, with the rejected alternative. Without the reason, a later session re-opens the decision and argues for the option you already rejected — usually persuasively.
- Acceptance as observable statements. Each line is something you could check. "Good UX" is not.
- Open questions kept visible, so they do not get silently answered by whichever session hits them first.
Write it by grilling
Do not compose a spec cold. Grill (Lesson 4.2), then have the agent draft the spec from the interview, then edit it yourself. The editing pass matters: you are the one who knows which constraints are real.
Draft a spec from our interview using this structure: Goal, Not in scope, Constraints, Decisions already made (with rejected alternatives and why), Acceptance criteria as checkable statements, Open questions. Only include things I actually said. Put anything you are inferring under Open questions instead of asserting it.
That last instruction is the whole difference between a spec and a hallucinated one.
Watch out
A spec that specifies implementation is too detailed and will fight reality by session three. Constrain the boundaries — interfaces, invariants, what to reuse, what not to touch — and leave the inside to the implementation sessions.
Try it
Write a spec for the big task from Lesson 1.4, using the grill-then-draft-then-edit flow. Then hand it cold to a fresh agent and ask: "What would you need to ask me before starting?" Every question it raises is a hole in the spec.
Takeaways
- A spec carries intent across sessions and contains no session-specific detail.
- Record rejected alternatives with reasons, or they get re-litigated.
- Acceptance criteria must be checkable statements.
- Draft from a grilling interview; anything inferred goes under open questions.
Why does a spec need a "not in scope" section when it already has a goal?
Because agents are helpful by default and will build adjacent things that seem obviously useful. The goal says what to build; only the exclusions stop the work from expanding into things you deliberately decided against. It is the cheapest scope control in a multi-session project.
A course by Pieter Zandbergen