---
altdown: 1
canonical: https://www.vivekchopra.com/posts/plan-before-you-prompt
title: Plan before you prompt
published: 2026-10-03T00:00:00Z
updated: 2026-10-03T00:00:00Z
lang: en
author: Vivek Chopra
description: How to steer coding agents with four small documents (architecture, ADR, plan, tasks) so your team makes the design decisions instead of the agent.
signals: search=yes, ai-input=yes, ai-train=no
generator: vivekchopra.com-gen
---
# Plan before you prompt

Structuring a Repo for AI Coding Tools

## Key takeaways

- Create four documents, each with a singular purpose. ARCHITECTURE.md describes the system as it exists today, an ADR (architecture decision record) explains one hard-to-reverse decision, and PLAN.md and TASKS.md scope the current branch.
- Accept the decision before any code exists. The ADR is reviewed in its own PR, and the agent starts nothing until a human marks it Accepted.
- Turn rules into checks. Each task in TASKS.md carries a verify command, so a Do not … task enforces the ADR through a test rather than memory.

You can one-shot a trivial application (the so-called "build me a web application and don't make mistakes" prompt). Anything more complex, work that has to be maintained and built upon, needs a more structured workflow.

The same discipline as in the days before AI coding tools applies: define clear product requirements and acceptance criteria, think through the technical design, write down the architecture, break the work into smaller tasks, and implement incrementally with checks and reviews. This post covers that approach: four documents for the architecture, one ADR, a plan, and the tasks, used to steer the coding agent.

## The four documents

Each document has a singular purpose:

| Document | The question it answers | Lifespan |
| --- | --- | --- |
| `ARCHITECTURE.md` | What does the system look like right now? | Permanent, updated in every branch that changes a boundary |
| ADR | Why did we make this one hard-to-reverse decision, and why did the alternatives lose? | Permanent, immutable once accepted |
| `PLAN.md` | How will this specific piece of work happen, and what is out of scope? | Lives while the branch is open |
| `TASKS.md` | What are the checkable steps, in order, with a verify command for each? | Lives while the branch is open |

**The architecture document describes what exists today.** Not where the system is headed. Not what was considered and rejected. The moment it mixes current state with future plans, it stops being a reference. Keep the diagram as text (PlantUML or Mermaid) so it lives in git and an agent can update it in the same commit as the code.

**An ADR records one decision.** The context states the constraints. The decision is one sentence. For example: "We will inject `sleep` and `now` into the client." Every serious alternative appears, including the obvious shortcut, with the reason it loses. Consequences include the cost. A Confirmation section lists checks a later reviewer or agent can run. Not every change needs an ADR. Write one when a change alters a contract, a service boundary, a lifecycle, or an owner, or when it locks in something hard to reverse. Bug fixes do not qualify.

**The plan describes the work and its boundaries.** The boundaries matter as much as the stack. A plan that only lists what to build accumulates scope as the agent reads it, because "why not add auth while I'm here" is a natural inference for a model. Writing down what is out of scope closes that door.

**A task is verifiable or it is not a task.** "Improve API handling" is a mood. This is a task:

```text
- [ ] T021 Parse rel="next" from the GitHub Link header — src/rate_limited_cli/pagination.py — verify: uv run pytest tests/test_pagination.py
```

You can read that checklist after the agent runs and know whether the work happened without opening a single source file. Add `Do not …` tasks where the work could drift past an ADR, and make the verify step the ADR's own Confirmation check. The rule is then enforced by a test rather than by memory.

Acceptance criteria round out the set. Define "done" before the agent starts: a 404 prints "user not found," not a traceback; three mocked pages produce exactly three requests; tests pass with no network. The agent then checks against a list you wrote while you were thinking clearly, not one it invents while trying to make tests green.

## Where everything lives

The layout separates the permanent record from the per-feature working set. Permanent documents sit at the top of `docs/`. Everything tied to one feature sits in one folder named after that feature.

```text
.
├── AGENTS.md                      # the one file every agent reads first
├── CLAUDE.md                      # one line: "Read AGENTS.md"
├── .cursor/rules/                 # Cursor rules, scoped by glob
├── .claude/commands/plan.md       # /plan: turn an accepted ADR into a plan and tasks
├── .github/PULL_REQUEST_TEMPLATE.md
├── docs/
│   ├── ARCHITECTURE.md            # the system as it exists today
│   ├── adr/
│   │   ├── README.md              # index, status legend, when an ADR is required
│   │   └── 2026-10-30-split-github-client-into-collaborators.md
│   ├── features/
│   │   ├── v1-repos-command/      # PLAN.md, TASKS.md, ACCEPTANCE.md, PROMPTS.md
│   │   └── resilient-github-client/
│   └── templates/                 # ADR, PLAN, TASKS, ACCEPTANCE, PROMPTS sources
├── src/
└── tests/
```

Three choices in that tree are deliberate.

**ADRs use a date-slug filename, not a sequence number.** `2026-10-30-split-github-client-into-collaborators.md` is the ADR's ID. Two engineers on two branches can each add an ADR without a merge conflict over who gets `0007`. The filename sorts chronologically and reads as a sentence in a PR title.

**Each feature owns one folder.** `docs/features/<slug>/` holds the plan, the task list, the acceptance criteria, and the bounded prompts for that feature. When the branch merges, the folder stays as a record of how the change happened. Nobody reads it by default, and that is fine. The plan links to its ADRs; the ADRs never link back to a plan, because an ADR outlives any one plan.

**The architecture doc is singular and lives at the top.** There is one current system, so there is one description of it. Every feature branch that moves a boundary updates it in the same PR, and the PR template asks for that update by name.

The product spec sits outside the repo, in whatever tracker the team uses. The ADR and plan link to it. Scope, deferred items, and the success measure are copied from the spec into the plan, never inferred.

## From decision to merge

```mermaid
flowchart LR
    A[Spec in the tracker] --> B[ADR opened: Proposed]
    B --> C[Reviewed in a PR]
    C --> D{{Status: Accepted}}
    D --> E["/plan: plan and tasks"]
    E --> F[One phase per prompt]
    F --> G[Review the diff]
    G -->|next phase| F
    G --> H[Merge, with ARCHITECTURE.md updated]
```

The decision is reviewed before any code exists. The code is reviewed one phase at a time.

1. The spec names the problem, the v1 scope, the deferred list, and one success measure. It lives in the tracker, not the repo.
2. The engineer opens a PR that adds one ADR with `Status: Proposed`. The PR contains no code.
3. A reviewer accepts or rejects from the file alone. On rejection the reason goes under Decision and the status becomes `Rejected`. The file stays as a record.
4. On agreement the status becomes `Accepted`. Open questions must be empty first.
5. `/plan docs/adr/<id>.md` reads the accepted ADR and the spec, inspects the anchors in the code, and drafts the plan and the tasks. It reports gaps instead of filling them. A gap reported is useful. A gap filled with a guess is an unreviewed architectural decision.
6. Each prompt names one phase: which tasks to implement, what not to implement, which tests to run, and where to stop. Phase 0 is foundation and blocks the rest. Each later phase is one user-visible slice with its own independent check.
7. You review the diff at the phase boundary. A diff that spans five decisions is a diff whose reasoning you have lost.
8. The PR that merges the feature also updates `ARCHITECTURE.md`. The PR template asks for it.

The gate is step 4. Nothing downstream begins until a human has accepted the decision in writing.

## A worked example: [rate-limited-cli](https://github.com/vivekchopra/rate-limited-cli)

To make this concrete, the template repo builds a small Python CLI twice. The first pass is v1. The second is a feature branch that adds the parts v1 deliberately left out.

```bash
rate-limited-cli repos octocat
```

The command fetches every public repository for a GitHub user, follows pagination, and prints a summary: repo count, stars, forks, and remaining API quota. GitHub is a good sample API because its edge cases are real. Unauthenticated requests are capped at 60 an hour. Rate limit state arrives in `x-ratelimit-*` headers. List endpoints paginate through a `Link` header, and GitHub tells you to follow `rel="next"` rather than increment a page number yourself.

**v1 needs no ADR.** It is the first version of a small tool, and nothing in it is hard to reverse. It needs a plan, a task list, and acceptance criteria. The plan fixes the stack (Python 3.12, uv, Typer, httpx, pytest with respx, Ruff) and the surface (`repos USER --limit --json`). It also says what v1 excludes: no authentication, no retry engine, no persistent cache. The client does the minimum correctly. It follows `Link: rel="next"` until the link disappears and stops early when `--limit` is reached.

**The second pass is where ADRs earn their keep.** The feature branch adds token authentication, rate-limit handling, retry with backoff, ETag caching, and clearer diagnostics. The design question has changed. It is no longer "how do I call GitHub." It is how five concerns interact without turning `GitHubClient` into one large method that does all of them badly. That is a hard-to-reverse decision, so it gets an ADR.

The ADR records the priority order for rate limits. If `Retry-After` is present, honor it. If the primary limit is exhausted, wait until reset, but only up to a bound. Otherwise treat it as a secondary limit and back off exponentially. It records the split of `GitHubClient` into four collaborators: `AuthConfig`, `RateLimitPolicy`, `RetryPolicy`, and `CacheStore`. And it records what lost. Inlining everything in the client is untestable. Always sleeping until reset means a CLI hangs for 40 minutes because that is when the quota resets. A `--token` flag puts a secret in shell history, so `GITHUB_TOKEN` from the environment wins. A plugin framework is not needed, so it is not built.

One detail from the ADR is worth keeping concrete, because it decides whether retry code is testable or miserable. Do not call `time.sleep` and `time.time` directly. Inject them.

```python
class GitHubClient:
    def __init__(self, *, sleep=time.sleep, now=time.time):
        ...
```

A test passes `sleep=delays.append` and a fixed `now`, then asserts `delays == [5]` after a mocked 429 with `Retry-After: 5`. The suite never waits five seconds. The ADR's Confirmation section turns this into a check an agent can run: grep the client for `time.sleep(` and expect zero matches. A `Do not …` task in `TASKS.md` carries that same check as its verify step.

`ARCHITECTURE.md` changes in the same branch. The diagram that showed `CLI -> GitHubClient -> GitHub` now shows the four collaborators. The ADR says *why* the split happened. The architecture doc says *what* the system looks like now. If the same sentence appears in both, one of them is redundant, and it is usually the architecture doc trying to be a decision log.

## Wiring the agent to the documents

The documents only help if the agent reads them. Every tool has its own instruction file, and the fastest way to drift is to maintain four copies of the same rules. Keep one source and point everything else at it.

| File | Read by | What belongs in it |
| --- | --- | --- |
| `AGENTS.md` | Codex, OpenCode, Cursor, and anything that follows the open convention | The project map, the commands to run, the design rules, and the order in which to read `docs/` |
| `CLAUDE.md` | Claude Code | One line: read `AGENTS.md`. Nothing else, so the two never disagree |
| `.cursor/rules/*.mdc` | Cursor | Short rules scoped by glob: `src/**` says where each concern lives; `docs/adr/**` says ADRs are immutable once accepted |
| `.claude/commands/plan.md` | Claude Code as `/plan` | The procedure that turns an accepted ADR into a plan and tasks, with its stop conditions |
| `docs/features/<slug>/PROMPTS.md` | You, pasting one at a time | The bounded prompts for each phase |
| `.github/PULL_REQUEST_TEMPLATE.md` | The reviewer | Which ADR this PR implements, which tasks it closes, whether `ARCHITECTURE.md` changed |

Three rules keep the instruction files useful.

**Tell the agent what to read, in order.** `AGENTS.md` lists `docs/ARCHITECTURE.md`, then the ADRs the current branch cites, then the feature folder. An agent that reads the plan before the architecture doc will propose a plan for a system that does not exist.

**Tell the agent when to stop.** Every bounded prompt ends with "Update only completed task checkboxes. Stop." The `/plan` command stops if the ADR is not `Accepted`, if the spec cannot be opened, or if a plan file already exists. A procedure without a stop condition becomes a procedure that runs until something breaks.

**Treat `TASKS.md` as the agent's memory.** Agents lose context between sessions. A task list with checkboxes and verify commands lets the next session pick up one unfinished task, run its verify step, and mark it done. That is the same pattern Anthropic describes for long-running agents: an initializer turns the request into a feature list and a progress file, and each later session works one item and leaves the file updated for the next.

## What survives the merge

The plan and the task list are useful while the branch is open and mostly useless six months later. Nobody reviewing the code in a year needs `docs/features/resilient-github-client/TASKS.md` to understand why caching works the way it does. They need the README, the current `ARCHITECTURE.md`, and the ADR. The feature folder stays in the repo as a record of how the change happened. It is not the primary documentation.

This is not about producing more documentation. It is about making the design decisions before the agent makes them for you by default. Writing the ADR first is slower than typing "add retry logic with backoff" into a prompt. It is also the difference between reviewing a plan you agree with and reverse-engineering a decision from a diff.

The template repo, with the ADR and plan templates, the `/plan` command, the agent instruction files, and both passes of the sample CLI, is [vivekchopra/rate-limited-cli](https://github.com/vivekchopra/rate-limited-cli). Clone it, delete the sample feature, and keep the structure.

## References

**Decision records**

- Michael Nygard, [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) (2011). The original ADR post: context, decision, status, consequences.
- [MADR: Markdown Architectural Decision Records](https://adr.github.io/madr/). The template this ADR shape most resembles, including decision drivers, considered options, and a Confirmation section.

**Spec, plan, and tasks**

- [GitHub Spec Kit](https://github.com/github/spec-kit). Constitution, spec, plan, tasks, implement. Source of the T001 numbering, `[P]` parallel markers, phased tasks, and a cross-artifact consistency check before implementation.
- [Spec Kit issue #1934](https://github.com/github/spec-kit/issues/1934). Proposes explicit `(depends on T001)` suffixes, because `[P]` alone does not tell an agent which tasks are safe to run together.
- [AWS Kiro: Specs](https://kiro.dev/docs/specs/). Requirements, design, and tasks files. Each task lists the requirement IDs it covers, and independent tasks run in dependency-ordered waves.

**Agents working across sessions**

- Anthropic, [Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents). An initializer turns the request into a feature list, a progress file, and git history; each later session picks one unfinished feature, verifies it, then marks it done.
- Anthropic, [Claude Code: Best practices for agentic coding](https://www.anthropic.com/engineering/claude-code-best-practices). Plan before coding, and keep a checklist in a file for multi-step work.
- [AGENTS.md](https://agents.md/). The open convention for a single agent instruction file that Codex, Cursor, and others read.

**Sample project**

- [vivekchopra/rate-limited-cli](https://github.com/vivekchopra/rate-limited-cli). The sample CLI and the docs that shaped it.
- [GitHub REST API: Using pagination](https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api) and [Rate limits](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api). The upstream behavior the sample client is built against.
