---
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-11-01T00:00:00Z
lang: en
author: Vivek Chopra
description: How to steer coding agents with a small set of documents (architecture, ADR, plan, tasks) so your team makes the design decisions instead of the agent.
generator: vivekchopra.com-gen
---
# Plan before you prompt

Structuring a Repo for AI Coding Tools

## Key takeaways

- Create four documents, each with a single 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.
- The ADR is reviewed in its own PR, and the agent does not start coding until a human marks the ADR as Accepted.
- Turn rules into checks. Each task in TASKS.md carries a verify command, and CI runs the same commands on every PR, so a Do not … task enforces the ADR through a check the agent cannot skip.

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: documents for the architecture, technical design (ADRs), the implementation plan, and the tasks, used to steer the coding agent.[¹](https://www.vivekchopra.com/posts/plan-before-you-prompt#notes)

This is the workflow I use for complex changes, and it is borrowed from many, many sources (see the **References** section). It is also one workflow among several! 

You do not need this level of rigor for a bug fix or a small feature. OpenAI's September 2026 guidance for GPT-6 Astra already warns that "requiring a stack of docs or a full repo map before every edit is excessive for a typo fix," and that pointing to docs helps "so long as it is contextual."

> **What should outlast any particular set of files is the engineering intent behind a change, its constraints, and the evidence that it works, carried across people, agents, and sessions.**

The files in this post (an architecture doc, decision records, a plan, a task list) are how I express that intent today. I expect the set to shrink as models get better at finding context on their own; the decisions underneath will not shrink, because someone still has to make them, write them down, and check them before the agent writes the code.

## Documents that steer the agent

| 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; replaced by a new ADR, never edited |
| `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 a technical 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.** 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 possible path a helpful model might take. Writing down what is out of scope constrains the agent.

**A task has a completion condition you can check.** "Improve API handling" is a vibe. A task needs to be more precise:

```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 checked by a command rather than left to memory, and when CI runs the same command, the branch cannot merge while the check fails.

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.

**Match the documents to the change.** The full set is for decisions that are expensive to undo; most changes need less.

| Change | What it needs |
| --- | --- |
| Bug fix or small, reversible change | A clear prompt, a test, and a reviewed diff |
| New feature inside existing boundaries | `PLAN.md` with out-of-scope items, `TASKS.md`, acceptance criteria |
| Change to a contract, boundary, lifecycle, or owner | All of the above, plus an accepted ADR and an updated `ARCHITECTURE.md` |

## Where the documents live

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-rate-limit-wait-order.md
│   │   ├── 2026-10-30-split-github-client-into-collaborators.md
│   │   └── 2026-10-30-token-from-environment.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/
```

The tree follows three conventions:

**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 has a separate folder.** `docs/features/<slug>/` holds the plan, the task list, the acceptance criteria, and the bounded prompts for that feature. The plan links to its ADRs; the ADRs never link back to a plan, because an ADR outlives any one plan.

**There is one consolidated architecture doc at the top of `docs/`.** One system, one description. 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 TD
    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 is a lightweight requirements document with the scope, what is not in scope, and success measure(s). It lives in the tracker (e.g. Jira, Linear), and not the repo.
2. The engineer opens a PR that adds one ADR with `Status: Proposed`. The agent can draft it from the spec and the code, alternatives included; and a human reviews and decides. The PR contains no code at this point.
3. `/plan docs/adr/<id>.md` reads the accepted ADR and the spec, inspects the code, and drafts the plan and the tasks. It reports gaps instead of filling them, because a filled gap is a decision that needs human review.
4. Each prompt maps to one phase: which tasks to implement, what not to implement, which tests to run, and where to stop. Phase 0 is foundation/plumbing and each later phase is one user-visible slice with its own independent check.
5. Humans review the changes at the phase boundary.
6. The PR that merges the feature also updates `ARCHITECTURE.md`. The PR template asks for it.

## Example codebase: [rate-limited-cli](https://github.com/vivekchopra/rate-limited-cli)

The example repo builds an initial v1 of a small Python CLI, then adds a new feature to it. The CLI command fetches every public repository for a GitHub user, follows pagination, and prints a summary: repo count, stars, forks, and remaining API quota.

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

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.

**Major feature additions and changes need an ADR.** 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 raises three hard-to-reverse decisions, so the branch gets three ADRs. They are separate files because each can be accepted, rejected, or later superseded without reopening the others.

The first ADR splits `GitHubClient` into four collaborators: `AuthConfig`, `RateLimitPolicy`, `RetryPolicy`, and `CacheStore`. Inlining everything in the client lost because it is untestable, and a plugin framework lost because nothing needs one. The second sets the 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. Always sleeping until reset lost, because a user who hits the limit twenty minutes past the hour would wait 40 minutes with no output. The third reads the token from `GITHUB_TOKEN`, because a `--token` flag puts a secret in shell history.

The split ADR also decides how the retry code gets tested: inject `sleep` and `now` rather than calling `time.sleep` and `time.time` directly.

```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. Its 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 harness 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 |

There are four things I've found useful in making this workflow reliable.

First, **make sure the agent reads the architecture and design decisions before it starts planning.** The code shows how the system works today, but not why certain decisions were made or what has been ruled out. `AGENTS.md` specifies the reading order: `docs/ARCHITECTURE.md`, the relevant ADRs, then the feature folder. Without that context, the agent may propose changes that conflict with the existing architecture.

Second, **give the agent a stopping point**. 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 already exists. Better to stop and report a problem than guess and continue.

Third, **keep a record of progress between sessions**. The model does not retain its working context from one session to the next. `TASKS.md` gives the next session a list of unfinished work, with a verify command for each task. It can pick up where the previous session stopped, check its work, and mark the task complete. Anthropic describes a similar approach for long-running agents: an initial session creates a feature list and progress file, and subsequent sessions work through the list.

Finally, **instructions are not enforcement**. The agent can ignore something written in `AGENTS.md`. If a rule matters, enforce it outside the prompt. CI runs the tests and the ADR's Confirmation checks, and branch protection prevents a merge when they fail. The grep check for `time.sleep` in the example is a start, but Ruff's banned-API rule is stronger: it also catches `from time import sleep`. Claude Code hooks can run tests after edits, and permission rules can block writes to files the agent should not change. The agent writes the code. The checks decide whether it is acceptable.

## What to keep after the merge

The plan and the task list are useful while the branch is open, and can either be kept or removed after the merge. The changes to `ARCHITECTURE.md` and the ADRs are enough to preserve the record of why a change happened and what that change was.

## Companion repo

The companion repo, [vivekchopra/rate-limited-cli](https://github.com/vivekchopra/rate-limited-cli), is an example of the structure I use. It has the templates, the `/plan` command, the agent instruction files, and the plans, tasks, prompts, and ADRs for both passes; the ADRs are still `Proposed`, and the code is not yet written. You are welcome to clone it or have your , delete the sample feature, and keep the structure.

## Notes

¹ **Model, harness, and agent.** The *model* is the LLM itself (Claude, GPT, Gemini). It reads text and writes text, and it keeps nothing from one session to the next. The *harness* is the program that runs the model against your repo: Claude Code, Cursor, Codex, or OpenCode. It loads the instruction files, gives the model tools to read files and run commands, and starts and ends each session. "AI coding tools" in the subtitle and the opening means harnesses. The *agent* is the model and the harness working together. This post says "agent" for whatever reads the documents and writes the code, "harness" for the program that loads the instruction files, and "model" only for behavior that comes from the model itself.

## 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.
- OpenAI, [Rethinking skills and prompts for GPT-6 Astra](https://developers.openai.com/blog/rethinking-skills-and-prompts-for-gpt-6-astra) (September 2026). More capable models need fewer, more contextual instructions; requiring docs before every edit wastes context.
- [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.
