AI Coding Project Instructions: Verify Before You Retry
TL;DR: Before buying another AI coding retry, check whether the agent has a working test command and a current project contract. Keep durable instructions in version control, verify the command locally, and record the baseline. A green exit with no discovered tests is weak evidence, even when the assistant sounds confident.
What you’ll be able to do:
- Give a new coding session a short, verifiable handoff instead of a long chat transcript.
- Reproduce an empty test suite and a real failure without an API key, subscription upgrade, or GPU.
- Decide when another model attempt is justified and when to fix the development environment first.
What belongs in project instructions instead of session memory?
Project instructions should contain facts the next contributor needs to reproduce your checks: the working directory, runtime, install method, test entry point, and boundaries of the requested change. Session memory is useful for the history of an investigation, but it is a poor sole location for facts that determine whether a patch is acceptable.
Consider a project whose tests live under checks/. A session remembers that “tests passed,” while the next session runs a default command that discovers nothing. The new assistant has not necessarily made a reasoning error. The handoff omitted the evidence needed to interpret success. Paying for a stronger model does not repair that omission automatically.
This guide focuses on that handoff problem. Our context engineering guide covers the broader organization of instructions, and our test-driven AI coding workflow covers test-first implementation. Here, the deliverable is a small executable acceptance contract that survives a fresh conversation.
The code below was checked locally with Python 3.14 on November 4, 2026. It is a deliberately constructed fixture, not a comparison of model performance. No paid inference was used, and we did not measure token savings or agent compliance.
How do you distinguish an empty suite from a real failure?
Create a new scratch directory and put the following file at checks/check_contract.py. The unusual filename is intentional: this example shows why the command’s discovery pattern belongs in the handoff.
import unittest
def normalize_status(value):
return value.strip().lower()
class StatusContract(unittest.TestCase):
def test_known_status(self):
self.assertEqual(normalize_status(" READY "), "ready")
def test_blank_is_rejected(self):
with self.assertRaises(ValueError):
normalize_status(" ")
From the scratch directory, run these two commands separately. Here, python means the Python 3 interpreter you selected for this project; use its full executable path if your shell resolves a different installation.
python -m unittest discover -s checks -v
python -m unittest discover -s checks -p "check_*.py" -v
The first command discovers zero tests because the fixture’s filename does not match the default test*.py pattern. The second discovers two tests and fails the blank-input case. The function returns an empty string instead of raising an exception. This is the intended baseline, not an installation failure.
The local Python 3.14 fixture produced those two outcomes: zero discovered tests first, then two tests with one failure. The empty discovery returned exit code 5 and NO TESTS RAN; the failing suite returned exit code 1. Do not assume that an empty suite exits successfully, or that every unsuccessful exit means broken application code. Treat the test count as evidence alongside the exit code. We do not publish elapsed time because this example is about discovery and behavior, not performance. Python documents the discovery flags in its unittest reference.
This does not mean every repository needs a custom pattern. If you own the naming convention, renaming files to the standard pattern may be simpler. The transferable lesson is to record the actual command and establish that it reaches the intended tests before asking an agent to iterate on application code.
How can the entry point reject an empty test suite?
For this fixture, put the following at verify.py in the scratch directory. It resolves the checks directory relative to its own file, prints the discovered count, and makes an empty suite an explicit failure. That makes the acceptance command less dependent on the caller remembering a working directory.
from pathlib import Path
import sys
import unittest
root = Path(__file__).resolve().parent
suite = unittest.defaultTestLoader.discover(
start_dir=str(root / "checks"),
pattern="check_*.py",
)
count = suite.countTestCases()
print(f"Discovered {count} tests", flush=True)
if count == 0:
raise SystemExit("No contract tests discovered")
result = unittest.TextTestRunner(verbosity=2).run(suite)
sys.exit(0 if result.wasSuccessful() else 1)
Run python verify.py. It should still fail on the blank-input behavior. Now change only the function in checks/check_contract.py to this version:
def normalize_status(value):
normalized = value.strip().lower()
if not normalized:
raise ValueError("status cannot be blank")
return normalized
Run the same entry point again. The expected result is two tests passing. To check the empty-suite path, use a disposable copy of the fixture and rename the test file so it no longer matches check_*.py; verify.py must exit unsuccessfully. Do not rename real project tests as a routine part of agent work.
Our local checks covered the original failure, the corrected function, invocation from a different working directory, and the empty-suite rejection. They do not establish that the contract is complete. For example, this toy function has no documented policy for non-string input. Add such requirements only when the real product needs them; an agent should not silently invent a broader API while fixing a narrow defect.
What should the reusable instruction file say?
Use a concise file such as CONVENTIONS.md for the fixture. The following is an original template, not a promise that any assistant will obey every line:
# Project acceptance contract
Runtime: Python 3.14; standard library only for this fixture.
Acceptance command from repository root: python verify.py
Expected suite: 2 contract tests; zero discovered tests is a failure.
Before editing, run the command and record the current failure.
For this task, change blank-status handling only.
Do not weaken assertions or change discovery to obtain a green result.
After editing, run the same command and inspect the diff.
Report the command, discovered count, result, and any check not run.
If the runtime or command is unavailable, report that before retrying.
Replace the runtime and command with your actual environment before reusing it. On a team, a committed wrapper should select or document the expected environment consistently. Avoid putting a developer’s private absolute path into a shared contract unless the repository intentionally targets that exact machine.
The file should explain the current contract, not accumulate every past incident. If discovery changes, update the command and the expectation in the same review. If the suite grows, update or remove a fixed count rather than teaching the agent to preserve an obsolete number. The empty-suite guard remains useful even when an exact count becomes maintenance overhead.
For architectural context, link to a maintained document rather than copying its full contents into every instruction file. Our documentation guide explains where those longer explanations fit. Keep secrets, credentials, customer data, and raw production logs out of reusable instructions.
How do Cline and Aider receive the same contract?
Cline’s rules documentation lists .clinerules/ and .cline/rules/ as supported workspace directories. You can put the short contract into .clinerules/testing.md, then confirm it is enabled in the Rules panel. Use one maintained location; copying the same instructions into several supported directories creates another drift problem. Rules consume context, so keep the always-needed portion small.
Aider documents loading a conventions file as read-only with aider --read CONVENTIONS.md or /read CONVENTIONS.md. See its conventions guide. For this fixture, an installed Aider session could be started with:
aider --read CONVENTIONS.md checks/check_contract.py
Inside that session, the manual test command would be:
/test python verify.py
Aider’s testing documentation describes /test, --test-cmd, and --auto-test. A failed test command can trigger an attempted repair, so establish the command’s baseline before enabling repeated automated repairs. These are documented integration examples; our local validation exercised the Python fixture, not a live Cline or Aider model session.
If you need the conventions loaded on every Aider launch, its YAML configuration documentation describes .aider.conf.yml. Keep model credentials outside committed project instructions. Verify the effective configuration after changing tools rather than assuming that a file recognized by one client is automatically active in another.
When is another paid model attempt justified?
Another attempt is most defensible when the environment works, the acceptance condition is clear, and the previous patch failed for a specific code reason. It is less useful when the assistant cannot run the command, is in the wrong directory, or receives a misleading success signal.
| Observed result | Next action | Spending implication |
|---|---|---|
| Interpreter cannot start | Fix interpreter selection | Another generation has no working feedback loop |
| Zero tests discovered | Fix discovery or the documented command | A green label does not justify more automation |
| Known baseline failure remains | Inspect the targeted patch and failure | A bounded retry may be useful |
| New unrelated failures appear | Compare against the recorded baseline | Avoid expanding scope without understanding the change |
| Intended tests pass | Review the diff and relevant broader checks | Stop generating when the acceptance criteria are met |
For this standard-library fixture, local checks incur $0 in model API charges. They still use your computer and your time. A cloud agent, paid subscription, or remote CI runner has its own billing terms; this article makes no claim that those are free. A local model also has hardware and electricity costs even if there is no per-request invoice.
No GPU is needed to run the fixture. The hardware requirement is simply a computer that runs the selected Python interpreter and can store these small files. That is a requirement for the checks, not a claim about hosting a coding model on the same machine. Do not buy more RAM or a GPU to solve an incorrect discovery pattern.
Set a stopping rule before a session. For example, allow one attempted fix after the verified baseline, then inspect the diff before authorizing further work. That is an illustrative budget policy, not a measured optimum. If you evaluate it, record completed tasks, human review time, actual billed usage, and failed checks. A shorter prompt alone does not establish lower total cost.
Honest take: when should you skip this extra setup?
Skip a new wrapper when the repository already has a reliable, documented check entry point that detects missing tests and runs in CI. Reuse that mechanism instead of introducing a second authority. A tiny disposable script may need only one explicit command and a clear expected result.
The extra contract becomes worthwhile when work regularly crosses sessions, developers, or tools. Its value is reproducibility: the next person can distinguish a broken environment, a pre-existing failure, and a regression without reconstructing the whole conversation. It cannot make weak tests comprehensive or turn instructions into a security boundary.
Before accepting the patch, use git diff to inspect what changed, including tests and instruction files. The Git diff reference documents the comparison options. A model may satisfy a test by weakening it; the green check and the code review serve different purposes. Require both when the change affects real behavior.
What should the final session handoff contain?
The handoff should state the changed behavior, the exact verification command, its outcome, and any remaining limitation. For this fixture: blank status now raises ValueError; python verify.py discovered two tests and passed; non-string inputs are outside the stated contract.
Keep the baseline and final result in the task record rather than stuffing them permanently into global rules. Update durable project instructions only when a reusable fact changed. This division lets a new session start with a short contract while still giving a reviewer enough evidence to evaluate the previous session’s work.
Sources
- Python unittest discovery and runners
- Cline workspace rules
- Aider coding conventions
- Aider linting and testing
- Aider YAML configuration
- Git diff reference
Last updated November 4, 2026. Python fixture results are local checks; tool integrations are based on the linked official documentation. No agent benchmark or quantified savings claim is made.
Was this article helpful?
Thanks for the feedback — it helps improve future articles.
Need hands-on help?
I offer 1-on-1 technical consulting for local AI setup, GPU selection, and AI coding tool configuration — same topics covered on this site.
Book a session — $49 / hour →Know which coding tool is worth paying for
Hands-on comparisons of AI coding assistants and what each one costs to run — including the local-model path. Sent only when something changes. Unsubscribe anytime.