AI-Native Updated 2026-09 10 min read View as Markdown

From issue to pull request: running a feature with an agent

The end-to-end workflow, including the two steps everybody skips and then pays for later.

Here is the loop that works, on a real Python codebase, for a change of meaningful size. It is not complicated. The discipline is in doing steps two and five, which are the ones everyone drops first and misses most.

1. Write the spec as tests, or as a sentence you could test#

The prompt that produces good output is not a longer description. It is a description with a checkable finish line.

Weak: "Add rate limiting to the API."

Strong: "Add per-API-key rate limiting to the FastAPI app: 100 requests per minute, sliding window, backed by the existing Redis connection in adapters/cache.py. Over the limit returns 429 with a Retry-After header in seconds. Health check endpoints are exempt. Limits are configurable per key, defaulting to 100. Write the tests first; I want to read them before you touch src/."

The second version is not longer because verbosity helps. It is longer because it makes six decisions that would otherwise be made silently and wrongly: the scope of the key, the algorithm, where state lives, the response shape, the exemption, the default.

If you cannot write that paragraph, you do not yet know what you want, and an agent will not discover it for you — it will pick something plausible and you will find out at review.

2. Make it plan before it edits#

The step people skip. Ask for the approach first, with no code.

Before writing anything: which files will you change, what is the
approach for the sliding window, and what are you unsure about?
Do not edit yet.

This costs thirty seconds and catches the expensive class of error — right code, wrong design — while it is still free to fix. It also surfaces the thing you forgot: agents are good at noticing "you did not say what happens when Redis is unavailable", and that question is much cheaper now than in review.

Most tools have a first-class version of this (a plan or ask mode). Use it. If yours does not, the instruction above works fine.

3. Tests first, and read them before the implementation#

Have it write the tests, then stop and read them yourself before any implementation exists.

This is the point where you find out whether it understood you. A test suite is a specification you can read in ninety seconds, and reading it before the implementation exists means you are judging the understanding rather than being anchored by working code.

tests/api/test_rate_limit.py
async def test_allows_up_to_limit(client, api_key): ...
async def test_blocks_over_limit(client, api_key): ...
async def test_429_includes_retry_after_seconds(client, api_key): ...
async def test_window_slides(client, api_key, frozen_time): ...
async def test_health_endpoint_is_exempt(client): ...
async def test_limits_are_per_key(client, api_key, other_key): ...
async def test_redis_unavailable_fails_open(client, api_key, broken_redis): ...

That last test is the one you argue about — fail open or fail closed? — and it is much better to have that argument here than after an incident.

4. Let it implement, and leave it alone#

With a plan agreed and tests written, this part is genuinely hands-off. Let it iterate against the suite. Interrupting mid-loop to make suggestions usually makes things worse, because you are injecting a change of direction into a process that was converging.

Watch for two things only:

  • It changed the tests. Sometimes legitimate; always worth reading.
  • It has been going for a long time without the suite getting greener. That is the signal to stop and re-plan, not to wait longer. Repeated failed attempts pollute the context with dead ends.

5. Review the diff properly#

The other step people skip. Some specific things to look for in Python, beyond the general failure-mode catalogue:

shell
git diff --stat                       # is the size what you expected?
git diff -- tests/                    # read the test changes first, separately
git diff -- src/
git diff | grep -nE 'type: ignore|noqa|except Exception|TODO|pass$'
uv run ruff check . && uv run mypy src

Read the test diff before the source diff. If the tests changed, you need to know that before you form an opinion about whether the source is correct.

Questions worth asking every time:

  • Is the diff bigger than the change? Reformatting, renamed variables, "while I was here" refactors. Ask for them to be removed or split out. A large diff is not reviewed; it is skimmed.
  • Did it add a dependency? Check the name character by character — see hallucinated packages — and ask whether the stdlib already does it.
  • Did it duplicate something? The most common structural failure. Search for one distinctive phrase from the new code; if a near-identical function exists elsewhere, the agent did not find it and you now have two.
  • Does it match the surrounding code? Not just style: error handling, logging, how config is read. Local consistency matters more than global correctness for maintainability.

6. Commit history that a human can read#

Ask for small commits with real messages, and write the PR description yourself — or at least edit it hard. A generated PR description tends to describe what the diff contains, which the reviewer can see. What they cannot see is why you chose this approach, what you rejected, and what you are unsure about.

Commits: one per behavioural change. Message says why, not what.
PR description: I'll write it. Give me three bullets on anything you
were uncertain about or any decision you made that I did not specify.

Those three bullets are the most valuable output of the entire session.

7. Before merge#

shell
make check                            # lint, types, full suite
uv run pytest -q                      # including integration
git diff main --stat

And one question that no tool answers: would I be comfortable being paged for this at 3am? If the honest answer is no because you do not really understand a part of it, go back and understand that part. Shipping code you cannot debug is the actual risk in all of this, and it is not a technical problem.

Common questions#

How big a change can I hand over in one go?#

Roughly: one that you could review in twenty minutes. Beyond that, review quality collapses and you are approving rather than reviewing. For anything larger, break it at a natural seam and run the loop twice — the second run starts from a codebase that already has the first half in it, so it is not twice the work.

Should I let it commit and push?#

Commit, yes — small commits are useful and reversible. Push and open a PR, only behind a confirmation. The asymmetry is that a local commit is invisible to everyone else and trivially undone, while a push is visible and a PR notifies people.

It keeps failing the same test. What now?#

Stop. Repeated failure means the context now contains several wrong approaches, which makes the next attempt worse rather than better. Start a fresh session with a summary of what did not work, or do that piece yourself — often the fastest path once you have seen three failed attempts is to write the tricky ten lines and hand back the rest.

Is the planning step really worth it on small changes?#

No. For a change you could make yourself in five minutes, skip straight to the diff and review it. The planning step earns its cost somewhere around "I would need to think for a bit before starting", which is also roughly where handing it over starts to be worth doing at all.

Get the Python agent pack

A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for Python. One email, then occasional updates when the tooling shifts. No course pitch.

Unsubscribe in one click. We never sell the list. Or just take the AGENTS.md now — no email needed.

Disclosure: some links on this page are affiliate links. If you buy something through one, we earn a commission at no extra cost to you. We only list tools we would tell a friend to use, and we say so when we have not used something ourselves. This is how the site stays free and ad-light.