Plan: capture intent.md
The plan stage of the AI-native lifecycle turns an idea, a ticket, or a production finding into a committed intent.md: the problem, the outcome, the constraints, and the open questions. An agent interviews the originator and writes the file, but stops before any design or code. The product owner accepts it by approving the pull request that adds it.
This page is for the developer or product owner who captures the intent and the tech lead who owns the gate after it. The situation it fixes: support forwards “customers want notifications” to engineering, a developer pastes it into an agent, and by Friday there is a working email feature that nobody asked for. The customers wanted in-app alerts, and they wanted them only for comments on their own documents. Nothing written down said so.
Traditional: an idea passes through backlog entries, user stories, and refinement meetings before anyone acts on it. Ownership changes at each handoff, so what reaches engineering is several steps removed from what the originator meant.
AI-native: the originator talks the idea through with an agent that asks the analyst’s questions, and the result lands in the repository as intent.md within the same working session. Design, build, and review agents all read that one file.
What you get from capturing intent this way
Section titled “What you get from capturing intent this way”- A template and a filled example of
intent.md, with a rule for what stays out of it. - Three copy-paste prompts: interview the originator, draft an intent from a ticket and the code, and attack a draft in a fresh session.
- A
capture-intentskill you can commit, so every originator gets the same interview in Claude Code, Codex, and Cursor. - A CI shape check and a CODEOWNERS gate, so nobody proofreads intents for structure and nothing is accepted without the product owner’s approval.
- The recovery for each way this stage usually goes wrong.
Before you capture intent
Section titled “Before you capture intent”- A home for intent. Keep an
intent/folder in the product repository, one folder per change:intent/notifications/intent.md. The design stage writesspec.mdnext to it. A separate intent repository pays off only when one intent spans many repositories; the artifact chain covers that choice and the Jira or Linear alternatives. - The template. Commit
intent/TEMPLATE.mdfrom the next section. - A named acceptor. One product owner per area, written into
CODEOWNERS(see the verification section). - A path for people who do not use git. A product manager or support engineer can run the interview in claude.ai or Claude Cowork, then add the file through the GitHub web editor as a pull request. An engineer can also commit it for them. The file matters; the tool the originator used does not.
This is the first stage of the AI-native lifecycle, so it has no prerequisite stage.
What goes into intent.md?
Section titled “What goes into intent.md?”intent.md states what is wanted and why. It never states how. Table names, endpoints, libraries, and file paths belong in spec.md and plan.md, where someone who has read the code decides them. The template is the one the artifact chain uses:
# Intent: INTENT_TITLEAuthor: AUTHOR_NAME (TEAM). Status: draft | accepted. Ticket: TICKET_ID
## ProblemWHAT_IS_BROKEN_OR_MISSING
## Proposed outcomeWHAT_BETTER_LOOKS_LIKE
## Affected users and systemsUSERS_AND_SYSTEMS
## ConstraintsCONSTRAINTS
## Open questionsOPEN_QUESTIONSHere is the notifications example after a 20-minute interview. Notice what the agent was not allowed to decide:
# Intent: Tell document owners when a teammate commentsAuthor: Maria Nowak (Support). Status: draft. Ticket: SUP-412
## ProblemDocument owners miss comments on their documents. Support gets about tentickets a week that read "I didn't know anyone had replied". Owners onlysee comments when they reopen the document.
## Proposed outcomeAn owner learns about a new comment on their document within a minute,without opening it, and can turn this off. Success: those support ticketsstop.
## Affected users and systemsDocument owners on all plans. The comments feature and the accountsettings screen. Commenters see no change.
## ConstraintsIn-app first. Email is out of scope for this change (originator preference:customers asked for in-app). Must respect the existing "do not disturb"setting. No new third-party service without a security review.
## Open questions- Should owners hear about replies inside a thread they already follow? (Product owner to decide.)- What volume do the largest workspaces generate per hour? (Data team.)| Section | It is right when | Red flag |
|---|---|---|
| Problem | It names who cannot do what today, in the originator’s words | It describes a feature (“add a notifications table”) |
| Proposed outcome | A tester could observe it happening | “Better”, “faster”, or “improved” with no event or number |
| Affected users and systems | It names users and existing systems, including who sees no change | Empty, or “everyone” |
| Constraints | It lists deadlines, regulations, budgets, and systems that must not change | A design choice presented as a constraint without the word “preference” |
| Open questions | Each question names who can answer it | Empty on anything larger than a small fix: the agent guessed |
Skip intent.md only for changes nobody outside the team can observe: a typo, a dependency bump, a refactor with no behaviour change. Anything a user or another system can notice gets one, however small it feels.
Capture intent in five steps
Section titled “Capture intent in five steps”-
Start from what you have. An idea, a ticket, a customer email, or a screenshot is enough. The originator does not need structured language; the interview produces the structure.
-
Run the interview. The agent asks one question at a time and offers a recommended answer, so the originator reacts instead of writing from scratch. Put anything that cannot be answered under Open questions instead of guessing.
When an engineer starts from a ticket, the agent can read the ticket and the code first and arrive at the interview with a draft and a shorter list of questions.
-
Correct the draft as the originator. The originator reads the file, which is short, and fixes anything the agent misunderstood. This pass belongs to the originator, not to the engineer who ran the session.
-
Attack the draft in a fresh session. A new session has none of the interview’s assumptions, so it reads the file the way the design agent will.
-
Open a pull request and let the product owner accept it. The pull request contains only
intent/notifications/intent.md. The product owner comments, asks for changes, or approves. Before approving, the product owner (or the author, on request) changesStatus: drafttoStatus: acceptedin the same pull request. The merge after that approval is the acceptance, and it sends the intent into Design: write spec.md. Closing the pull request is a recorded rejection.
How do you run the interview in Claude Code, Codex, and Cursor?
Section titled “How do you run the interview in Claude Code, Codex, and Cursor?”The interview is the same in every tool. What differs is how you keep the agent from editing product code while it asks questions.
- Start the session read-only with
claude --permission-mode plan, or switch with Shift+Tab or/plan. From v2.1.283 (thelatestchannel), interactive sessions start in auto mode, so switch deliberately. - Plan mode blocks all edits, including
intent.md. When Claude presents the plan to writeintent.md, choose Yes, manually approve edits; then approve the write tointent/notifications/intent.mdand reject any other file. - A product owner without a terminal can run the same prompt in claude.ai or Claude Cowork, then open the pull request from the GitHub web editor.
- Enter plan mode with
/planin the CLI, then paste the interview prompt. When the interview is done, leave plan mode and ask Codex to write onlyintent/notifications/intent.md. - Attach a screenshot or whiteboard photo to the first prompt with
codex "$(cat prompt.txt)" -i sketch.png. Put the prompt before-i, because-itakes every following argument as an image path (codex-cli 0.157.1). - The Codex desktop app runs the same prompt for people who do not work in a terminal.
- Use Plan Mode, which “creates detailed implementation plans before writing any code” (Cursor docs, checked 2026-08-28), for the interview.
- Treat its output as the draft of
intent.md, not a plan. Plan Mode does not write product files, so when the interview is done, switch the chat to Agent mode and ask it to write onlyintent/notifications/intent.mdfromintent/TEMPLATE.md. Do this before you close the chat, so the design session and the other tools read the same file. - Review the proposed file change in the review panel before you accept it, and reject any other file.
Make the interview a skill every originator gets
Section titled “Make the interview a skill every originator gets”A prompt pasted from a wiki drifts: each person trims it differently. A skill in the repository gives every originator the same interview and the same template. Commit this as capture-intent/SKILL.md in each tool’s skills folder (.claude/skills/ for Claude Code, .agents/skills/ for Codex and Cursor; the design stage lists the locations and how to keep one source copy):
---name: capture-intentdescription: Use when someone describes an idea, ticket, bug report, or incident and wants it written up as intent.md, or asks to capture intent before design. Interviews the originator and writes intent/FEATURE/intent.md from intent/TEMPLATE.md. Never designs or writes code.---
# Capture intent
1. Ask one question at a time and give your recommended answer with each.2. Cover, in order: the problem today and for whom, how often it happens, an observable outcome, constraints, and what is out of scope.3. If the codebase can answer a question, read the code instead of asking. Do not edit any file while reading.4. Stop asking when every template section has content or a named open question.5. Write intent/FEATURE/intent.md from intent/TEMPLATE.md with Status: draft. Keep the originator's words in Problem.6. Never name tables, endpoints, libraries, or files. A design idea the originator insists on goes under Constraints, marked "originator preference".7. Every unanswered question goes under Open questions with who can answer it.Invoke it by name when you want to be sure it loads: /capture-intent in Claude Code and Cursor, $capture-intent in Codex. Building custom skills covers testing a skill before the team relies on it.
Draft intent from a ticket without starting a session
Section titled “Draft intent from a ticket without starting a session”When tickets arrive faster than anyone can interview their authors, let a headless job write the first draft and put the interview questions in the pull request. The author answers there, and the agent revises.
# Terminal or CI job. TICKET.md holds the exported ticket text and is never committed.FEATURE=notificationsPROMPT="Use the capture-intent skill. Read TICKET.md and the code it refers to. \Write intent/$FEATURE/intent.md with Status: draft. Do not ask questions: \put every question under Open questions with who can answer it."
# Before the agent runs: take the base commit and the checker from the trusted base.# Both stay in this shell's memory, where the agent cannot rewrite them.BASE_SHA=$(git rev-parse origin/main) || exit 1CHECK=$(git show "$BASE_SHA:scripts/check-intent.sh") && [ -n "$CHECK" ] || exit 1
# Claude Code: headless, file edits accepted, no interactive promptsclaude -p "$PROMPT" --permission-mode acceptEdits
# or Codex: headless, sandbox limited to the workspacecodex exec -s workspace-write "$PROMPT"
# The job leaves its edits uncommitted: stage them, without the ticket, so the shape check sees themgit add -A -- . ':!TICKET.md'printf '%s\n' "$CHECK" | REQUIRE_INTENT=1 RANGE="--cached $BASE_SHA" shTicket text is input from outside the team, so treat it like any untrusted input: run the job without deploy credentials or production secrets, and let the shape check below fail any run that touched a file outside intent/. Run the checker as it exists on origin/main, never the workspace copy: the agent can edit any file in the workspace, including scripts/check-intent.sh, and a checker rewritten to exit 0 passes everything. That is why the job reads the script and the base commit before the agent starts, and fails if the base has no checker. The job does not commit, so stage its output before the check, as the last two lines do; a check that only reads committed history would see no changes and pass. REQUIRE_INTENT=1 makes the check fail closed: a run that wrote no intent.md but changed src/ or .github/ fails instead of being waved through as a non-intent change, and a base the script cannot diff against exits with an error rather than a pass. The ':!TICKET.md' pathspec keeps the exported ticket out of the index, so the check does not report it as a stray file. A production incident follows the same pattern with a read-only triage agent in front of it, which Maintain: close the loop describes.
How do you verify an intent.md without reading it line by line?
Section titled “How do you verify an intent.md without reading it line by line?”An intent is short, and the originator and the product owner read it. Nobody else should. Split the checking three ways: a script checks the shape, the fresh-session review checks the substance, and the product owner makes the one decision that needs a human.
The shape check. Save it as scripts/check-intent.sh. Run it in CI on every pull request; it exits early unless the pull request changes an intent/*/intent.md file, so design and build pull requests that add spec.md and plan.md to the same folder pass untouched. Also run it after the headless job, from the copy on the base as shown above, with RANGE pointed at the staged changes and REQUIRE_INTENT=1, so a run that produced no intent.md fails:
# Pull request CI compares the branch with its merge base (the default below).# CI: check out with fetch-depth: 0 (or git fetch origin main) so origin/main...HEAD resolves.# After a headless job, stage its output and run:# REQUIRE_INTENT=1 RANGE="--cached $BASE_SHA" sh, with this file from the base on stdinBASE=origin/mainRANGE=${RANGE:-"$BASE...HEAD"}status=0
# Fail closed: a range git cannot resolve is an error, not "nothing changed".changed=$(git diff --name-only $RANGE) || { echo "git diff $RANGE failed"; exit 2; }intents=$(printf '%s\n' "$changed" | grep '^intent/.*/intent\.md$')
if [ -z "$intents" ]; then # A headless intent job must produce an intent.md; anything else it changed fails. if [ -n "$REQUIRE_INTENT" ]; then [ -n "$changed" ] && printf '%s\n' "$changed" echo "No intent/*/intent.md changed"; exit 1 fi # Pull requests that change no intent.md (spec and build pull requests) pass. exit 0fi
# An intent pull request changes only intent.md files.if printf '%s\n' "$changed" | grep -v -e '^intent/.*/intent\.md$' -e '^$'; then echo "Files above are not intent.md files"; status=1fi
for f in $(git diff --name-only --diff-filter=AM $RANGE -- 'intent/*/intent.md'); do grep -Eq '^Author: .+Status: (draft|accepted)' "$f" || { echo "$f: no Author/Status line"; status=1; } for s in '## Problem' '## Proposed outcome' '## Affected users and systems' '## Constraints' '## Open questions'; do grep -qx "$s" "$f" || { echo "$f: missing section $s"; status=1; } done # Code blocks and source paths belong in spec.md or plan.md. if grep -nE '^`{3}|(src|app|lib)/[A-Za-z0-9_./-]+\.(ts|tsx|js|py|go|rb|php|ex)' "$f"; then echo "$f: code or file paths above belong in spec.md or plan.md"; status=1 fidoneexit $statusThe substance check. Paste the fresh-session review’s table into the pull request. A NOT READY verdict goes back to the originator, not to the product owner.
The human gate. Nothing merges without the product owner’s approval. Put the owner in CODEOWNERS and turn on “Require review from Code Owners” in the branch protection rule for your default branch. That makes the approval mandatory, but anyone with write access can still press merge after it; if the merge itself must belong to the product owner, also restrict who can push to the branch (a ruleset or the “Restrict who can push to matching branches” option). A minimal CODEOWNERS:
# .github/CODEOWNERS/intent/**/intent.md @acme/product-owners/intent/billing/**/intent.md @acme/billing-productThe patterns match only intent.md, so spec.md and plan.md in the same folder go through their own owners, not the product owners.
The indicators. Two numbers from git history show whether the stage works, both defined in Metrics for an AI-native SDLC:
- Leading: time from the first request (the ticket’s creation date, or the branch’s first commit) to the merged
intent.md. A working stage measures it in hours or days, not weeks. - Lagging: the share of intents accepted into design rather than closed, and how often
intent.mdstill changes after the firstspec.mdcommit for the same change. Late intent edits mean the interview missed something.
What goes wrong when an agent captures intent, and how to recover
Section titled “What goes wrong when an agent captures intent, and how to recover”The intent contains the solution. Problem says “add a notifications table” and Proposed outcome describes a settings screen. The shape check catches file paths; the fresh-session review (item 1) catches the rest. Move each design idea under Constraints as an originator preference, or delete it, and rewrite the outcome as something a tester can observe.
Open questions is empty on a real feature. The agent filled every gap with a plausible guess, and the guesses now look like requirements. Rerun the fresh-session review and ask it to turn each unsourced statement into a question. Add “anything I cannot answer goes under Open questions” to the skill if it is missing.
An engineer wrote the intent alone. The file reads well, but it describes what the engineer would build, not what the originator needs. Send the pull request back to the originator for step 3. The pull request author is visible in git, so this pattern shows up in the history.
The interview never ends. Forty questions in, the originator gives up. Tell the agent to stop when every template section has content or a named open question, which step 4 of the skill already says, and move the remaining questions to the file.
One intent hides three changes. “Notifications, digests, and a mobile push service” in one file produces a spec nobody can accept. The fresh-session review (item 5) proposes the split. Accept one intent per independently shippable outcome.
The intent lives outside the repository. It sits in a chat thread, a slide, or a web portal the agent cannot read, and the design session starts from a paraphrase. Keep the file in git. If an existing system such as Jira must stay the record, link the two as the artifact chain describes.
Claude Code cannot save the file in plan mode. Plan mode blocks every edit. When Claude presents the plan, choose Yes, manually approve edits; each edit then prompts separately, so approve the write to intent.md and reject any other file.
The intent changes after the spec exists. Someone edits intent.md during build to match what was built. Freeze accepted intents: a change of intent is a new pull request with its own acceptance, and it sends the spec back through design. The lagging indicator above counts these edits.