Module 08
Frame the Task Before Code
On this page
In plain words
If you tell a friend "get me something to eat", he brings whatever he likes, and fast. A coding agent is the same. So before it writes a single line, you write a small task frame: the goal as visible behaviour, two or three facts with file-and-line receipts, which files it may touch, which it must not, and the exact command that proves the work is done. Anything you cannot verify goes in the list of unknowns instead of being guessed.
How it flows
- 1Read the request
- 2Search the code
- 3Collect facts
- 4Set boundaries
- 5Write the proof
- 6List unknowns
A tiny example
frame = {
"goal": "duplicate email signup returns 409",
"facts": ["tests/test_accounts.py:44 expects 409"],
"allowed": ["api/routes/accounts.py"],
"forbidden": ["api/payments/"],
"acceptance": "pytest tests/test_accounts.py -k duplicate",
"unknowns": ["is the match case-insensitive?"],
}
if check(frame):
run_agent(frame)Notice that every fact carries a file and line number, and the unknown is written down instead of being answered by a guess.
What you will learn
- Why the first step of agent coding is not writing code.
- How to turn a vague request into a small, bounded task frame.
- How to separate a real fact from a guess.
- When to stop reading the codebase and start working.
The problem, simply
You message your hostel friend: "Get me something to eat." He is fast and helpful, so he brings biryani. But you already ate biryani at lunch, and you actually wanted something light.
He did nothing wrong. He just filled in the blanks you left, and he filled them fast.
A coding agent is the same. You say "add duplicate email protection to signup" and it starts editing files in ten seconds. But where should that check go: the API layer, the service layer, or the database? Is Priya@gmail.com the same as priya@gmail.com? Which error code does your project already return? Can it add a database migration or not?
You did not say. So the agent decides. And here is the painful part: the code it writes can be clean, tested, and still completely wrong for your system. Wrong code that looks right takes far longer to find than code that simply crashes.
IMPImportant: An agent implements a clear task and an unclear task at the same speed. The speed is the same. The cleanup cost is not.
The idea
The first unit of work is not an edit. It is a task frame — a short written description of the task, backed by evidence you actually checked in the code.
The six fields of a task frame
- Goal — what behaviour a user should see change. Not "edit this file".
- Facts — what you verified in the code, with a receipt (file and line).
- Allowed paths — which files the change may touch.
- Forbidden paths — which files must stay untouched.
- Acceptance evidence — the exact command or observation that proves you are done.
- Unknowns — decisions you have not earned the right to make yet.
- 1Request
- 2Look in repo
- 3Facts with receipts
- 4Bounded frame
- 5Blocking unknown?
- 6Ask or plan
Facts need receipts
This is the whole game. "The API uses 409 for duplicate emails" is not a fact, it is a feeling. It becomes a fact when you can say: "tests/test_accounts.py line 44 asserts a 409 on duplicate signup."
A file path plus a line number is enough. A command output is better when behaviour matters. If you cannot point at something, it is a guess wearing a fact's clothes.
Remember: Every claim in your frame either has a receipt or is listed as an unknown. There is no third option.
A worked example
Suppose Priya gets the ticket: "Signup should reject duplicate emails." Her frame looks like this.
Goal: posting a signup with an email that already exists returns 409 with the existing error shape, and no new user row is created.
Facts: the signup handler is at api/routes/accounts.py line 61; the duplicate-response test already exists at tests/test_accounts.py line 44 and expects 409; the email column has no unique index, which she checked in the migrations folder.
Allowed paths: api/routes/accounts.py, api/services/accounts.py, tests/test_accounts.py.
Forbidden paths: the login flow, the payments module, the shared error formatter. Those are exactly the tempting things an agent will "improve" on the way.
Acceptance evidence: python3 -m pytest tests/test_accounts.py -k duplicate passes, and git status shows only the three allowed files changed.
Unknowns: should the comparison be case-insensitive? That changes what users experience, so Priya does not guess. She asks.
Reconnaissance is a search for limits, not a full read
You do not read the whole repository. You look only for the few things that limit the change: the current behaviour and who calls it, the closest existing test, the public response shape, the project instructions for that folder, the test command, and one similar change done earlier.
Stop when every decision in your plan is backed by evidence, clearly delegated, or written down as an unknown. Reading more after that is usually avoidance.
Four kinds of unknown
An unknown is a controlled gap. An assumption is an uncontrolled answer to that gap. Sort each one:
- Discoverable — the code or the running system can answer it. Go find out.
- Decidable — the task gives the agent authority to pick. Pick and move on.
- Human — it changes product behaviour, money, risk, or a public contract. Stop and ask.
- Deferred — it belongs to a different slice. Write it in non-goals.
- 1Unknown
- 2Discoverable?
- 3Decidable?
- 4Human?
- 5Ask now
- 6Continue
Write the proof before the patch
"Tests pass" is not a proof plan. Name the exact test and the claim it supports. Good acceptance evidence is a focused test command, a browser journey with the expected screen state, an API request with its exact expected response, a timing measurement with a threshold, or a scope check confirming no unrelated file moved.
TipTip: Write acceptance evidence first, before any code is generated. If you cannot describe how you will know it worked, the agent definitely cannot.
Build it
# A tiny task-frame validator. Standard library only.
# It refuses a frame that is not safe to hand to a coding agent.
def validate(frame):
"""Return a list of problems. Empty list means the frame is ready."""
problems = []
if not frame.get("goal"):
problems.append("Goal is missing. Say what behaviour must change.")
facts = frame.get("facts", [])
if len(facts) < 2:
problems.append("Give at least two facts about the code.")
for fact in facts:
# A receipt means a file with a line number, like accounts.py:61
if ":" not in fact:
problems.append("Fact has no receipt: " + fact)
allowed = set(frame.get("allowed", []))
forbidden = set(frame.get("forbidden", []))
if not allowed:
problems.append("No allowed paths. The change has no boundary.")
if not forbidden:
problems.append("No forbidden paths. Negative space is not stated.")
both = allowed & forbidden
if both:
problems.append("Path is allowed and forbidden: " + ", ".join(sorted(both)))
if not frame.get("acceptance"):
problems.append("No acceptance command. You cannot prove you are done.")
return problems
good = {
"goal": "Duplicate signup email returns 409 and creates no user row",
"facts": ["api/routes/accounts.py:61 handles signup",
"tests/test_accounts.py:44 expects 409"],
"allowed": ["api/routes/accounts.py", "tests/test_accounts.py"],
"forbidden": ["api/routes/login.py", "api/payments/"],
"acceptance": "python3 -m pytest tests/test_accounts.py -k duplicate",
"unknowns": ["Is the email match case-insensitive?"],
}
# Same frame, but broken on purpose in four different ways.
broken = dict(good)
broken["goal"] = ""
broken["facts"] = ["the API probably uses 409"]
broken["forbidden"] = ["api/routes/accounts.py"]
broken["acceptance"] = ""
for name, frame in (("good", good), ("broken", broken)):
issues = validate(frame)
print(name.upper(), "->", "READY" if not issues else str(len(issues)) + " problems")
for issue in issues:
print(" -", issue)Run it with python3 file.py. The good frame prints READY. The broken one prints five complaints, and each break is caught for its own reason. Try adding one path to both the allowed and forbidden lists and watch the overlap check fire.
Where you will see this
- Claude Code and Cursor: the plan or spec you write before letting the agent edit is exactly a task frame.
- GitHub Copilot agent mode on a real issue: the issue description becomes the frame, and a vague issue produces vague pull requests.
- Internal agents that open pull requests automatically, which need forbidden paths or they wander into unrelated modules.
- Support bots at Swiggy or a bank, where "unknowns that need a human" become the escalation rule.
- Any team code review checklist that asks "what proves this works?" — same idea, done by humans.
Common mistakes
- Writing the goal as a file change. "Edit accounts.py" tells you nothing about whether the work succeeded. Goals must be observable behaviour.
- Facts with no receipt. "The project follows REST conventions" sounds confident and is unverifiable. The agent will build on it and you will not notice.
- Only listing allowed paths. Without forbidden paths, the agent quietly refactors a neighbouring file because it looked messy, and now your diff is 400 lines.
- Deciding a human unknown yourself. Changing a public error code or payment logic just to unblock yourself is how a small task becomes an incident.
- A frame that fills three screens. That is not one task. Split it into changes that can each be proved separately.
If they ask in an interview
Q: Why does framing matter when the model is already good at coding?
A: Because a strong model fills missing context with plausible guesses and does it fast. The failure is not broken code, it is clean code that does not fit the system, which is much harder to catch in review. Framing removes the guesses before they get written.
Q: How do you decide when you have investigated enough?
A: I stop when every decision in my plan is either backed by evidence I can point to, explicitly delegated to the agent, or written down as an open unknown. Reading more of the codebase after that usually adds tokens, not certainty.
Q: What makes acceptance evidence good?
A: It names the exact command or observation and the claim that it proves, so someone else can reproduce it. "Tests pass" is not evidence. "This test file, this test name, plus no unrelated file changed" is.
Try these
- Take one real bug from your project. Write the six-field frame without proposing any solution. Notice how hard the facts field is.
- Go through your frame and find one line that is actually a guess. Open the code, verify it, and replace it with a file and line number.
- Add one human unknown to your frame — something that would change what a user sees. Write the exact question you would ask your senior.
- Take a broad allowed path like "the whole backend folder" and shrink it to the smallest safe set of files. Then give the frame to a coding agent and see if the diff stays inside.
Words, simply
| Word | Meaning in simple words |
|---|---|
| Task frame | A short written description of the job, with facts and boundaries, written before any code |
| Receipt | The file and line number that proves a claim is true |
| Allowed paths | The files the change is permitted to touch |
| Forbidden paths | The files that must stay exactly as they are |
| Acceptance evidence | The exact command or check that proves the task is finished |
| Unknown | A gap you have written down on purpose instead of guessing |
| Assumption | A gap you filled with a guess without noticing |
| Reconnaissance | A quick, targeted look through the code for the things that limit your change |
Quick recap
- The first unit of agent work is a task frame, not an edit — an unclear task gets built just as fast as a clear one.
- Every claim needs a receipt, and every gap you cannot fill must be written down as an unknown instead of guessed.
- Boundaries and proof come before code: allowed paths, forbidden paths, and the exact command that closes the task.