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

Setting up a coding agent for a Python project

The twenty minutes of setup that decide whether an agent is useful on your Python codebase or an expensive way to generate rework.

Most people try a coding agent on a Python repo, get plausible-looking code that fails in a way they only discover twenty minutes later, and conclude the tool is overhyped. Usually the tool was fine. The setup was missing.

An agent is a loop: propose an edit, run something, read the result, adjust. If there is nothing meaningful for it to run, the loop collapses into a single guess with no correction step, and you are back to copy-pasting from a chat window. Everything below exists to give the loop something real to push against.

1. Make the environment reproducible in one command#

The single biggest cause of wasted agent turns is an environment the agent cannot recreate. If your project needs a specific Python version, a virtualenv someone activated by hand three weeks ago, and two environment variables that live in a colleague's shell, the agent will spend its turns debugging your machine instead of your code.

Use uv. It resolves, installs and pins in one place, and it is fast enough that an agent can afford to run it repeatedly.

shell
# one-time, in the repo root
uv init --python 3.12
uv add --dev pytest pytest-cov ruff mypy
uv sync

Now the entire environment is two files — pyproject.toml and uv.lock — and any agent, on any machine, gets an identical one with uv sync.

2. Give it exactly four commands#

Agents do better with a small, named set of verbs than with a README paragraph describing your workflow. Put them somewhere executable — a Makefile, a justfile, or [tool.uv] scripts — and name them predictably.

Makefile
.PHONY: install test lint typecheck check

install:
	uv sync

test:
	uv run pytest -q

lint:
	uv run ruff check --fix . && uv run ruff format .

typecheck:
	uv run mypy src

check: lint typecheck test

make check is now the whole contract. The agent does not have to guess whether you use black or ruff format, whether tests live in tests/ or beside the source, or whether type checking is expected to pass. One command, one exit code.

3. Decide what it may run without asking#

Every serious agent has a permission model. Configure it once; otherwise you spend your session clicking Allow, which trains you to click Allow without reading — the single most dangerous habit in agentic development.

An allowlist is the coarse control. For policy that reacts to what is being run — deny this shape, ask before that one — you want a hook; there is a full reference in harness hooks. And for the containment that catches what no policy anticipated — a bad turn, an injected instruction — see sandboxing a coding agent.

The rule of thumb: auto-allow anything that only reads or only affects the working tree; always confirm anything that touches the network, the package index, or another machine.

.claude/settings.json
{
  "permissions": {
    "allow": [
      "Bash(uv run pytest:*)",
      "Bash(uv run ruff:*)",
      "Bash(uv run mypy:*)",
      "Bash(make test)",
      "Bash(make lint)",
      "Bash(make check)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)"
    ],
    "ask": [
      "Bash(uv add:*)",
      "Bash(pip install:*)",
      "Bash(git push:*)",
      "Bash(gh pr create:*)"
    ],
    "deny": [
      "Bash(curl:*)",
      "Bash(rm -rf:*)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ]
  }
}

Cursor and Windsurf have equivalent allowlists in their settings UI; Aider has --yes-always (don't) and per-command confirmation (do).

4. Work in a sandbox you are willing to lose#

The best posture for agentic work is one where a bad turn costs you nothing. In practice:

  • A branch, always. git switch -c agent/thing. Never point an agent at a dirty main.
  • Commit before you start. An uncommitted working tree is the one thing an agent can genuinely destroy.
  • Consider a worktree for longer runs, so you can keep working in the main checkout: git worktree add ../proj-agent -b agent/thing.
  • Containers for anything untrusted. If you are letting an agent run code it wrote against a real database, put it in a container with a throwaway copy.

5. Point it at the tests, not at the code#

The most common bad instruction is "add feature X to service.py". The most common good one is "here's a failing test that describes feature X; make it pass without changing the test."

The second version gives the loop a termination condition that the model cannot talk itself out of. It also forces you to say what you actually want precisely enough to assert on — which is where half the value comes from, agent or not.

tests/test_pricing.py
def test_bulk_discount_applies_at_ten_units():
    cart = Cart([Line(sku="A1", qty=10, unit_price=Decimal("5.00"))])
    assert cart.total() == Decimal("45.00")   # 10% off at 10+


def test_bulk_discount_does_not_apply_at_nine():
    cart = Cart([Line(sku="A1", qty=9, unit_price=Decimal("5.00"))])
    assert cart.total() == Decimal("45.00")   # 9 x 5.00, no discount

Note the second test. A single test is an invitation to hardcode; the pair pins the boundary. Writing the boundary case yourself is worth more than any prompt engineering you will ever do.

6. Write the AGENTS.md last, not first#

Everyone's instinct is to start by writing a long instructions file. Don't. You do not yet know which mistakes your agent makes on your codebase, so you will write generic advice the model already follows, and you will bloat the context for nothing.

Run three or four real tasks first. Every time you find yourself correcting the same thing twice, that correction is a line in AGENTS.md. Everything else is noise.

We wrote a whole page on getting this file right: Writing an AGENTS.md for Python that agents actually follow.

What this costs#

Agent sessions are billed, and the bill is driven by context size more than by how much you ask for. The levers — prompt caching, pruning unused MCP servers, starting a fresh session when the task changes — are in what tokens actually cost.

The twenty-minute checklist#

[ ] uv-managed environment, uv.lock committed
[ ] make install / test / lint / typecheck / check all work from a clean clone
[ ] make check genuinely fails on a broken tree
[ ] permission allowlist configured; .env and secrets/ denied
[ ] working on a branch, tree committed before each run
[ ] at least one test file the agent can run in under 10 seconds
[ ] AGENTS.md deliberately left empty until you have evidence for it

If all seven are true, you have a working loop. Everything after this is refinement.

Common questions#

Do I need a different setup for Claude Code, Cursor and Codex?#

No. The substance — reproducible environment, a small set of named commands, a permission allowlist, a fast test — is identical across tools. Only the configuration file names differ: .claude/settings.json and CLAUDE.md for Claude Code, .cursor/rules for Cursor, AGENTS.md for Codex and increasingly for everything else. Write AGENTS.md and symlink the tool-specific names to it.

Should the agent be allowed to install packages?#

Put package installation behind a confirmation, not on the allowlist. Language models hallucinate package names at a measurable rate, and attackers register the hallucinated ones — see Hallucinated packages and slopsquatting. A one-second glance at uv add <name> before it runs is the cheapest security control available to you.

Is it worth doing all this for a small script?#

No. For a fifty-line script, ask in a chat window and read the output. This setup pays off on a codebase you will still be maintaining in six months, where the cost of a wrong edit is measured in debugging time rather than in re-reading a screen.

What about test coverage — should I make the agent chase a number?#

Avoid it. Coverage targets are trivially gamed by tests that execute code without asserting anything useful, and an agent optimising for a number will produce exactly that. Ask for tests that pin behaviour at boundaries, then read them yourself.

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.