Today we are building an autonomous test-driven development agent that turns a plain-English specification into a passing Python module and pytest suite. It writes code, runs tests, reads failures, and repairs its own mistakes. Because Oxlo.ai uses flat per-request pricing, you can feed long tracebacks back into the model for multiple correction loops without the cost scaling with input length.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
- pytest:
pip install pytest
Step 1: Configure the Oxlo.ai client
I create a small module that holds the client and selects a coding-capable model. Oxlo.ai exposes a fully OpenAI-compatible endpoint, so the import pattern is identical to what you already know. Because Oxlo.ai uses flat per-request pricing, feeding long tracebacks back into the model for several correction loops does not scale costs the way token-based billing would. See https://oxlo.ai/pricing for details.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.getenv("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
)
MODEL = "deepseek-v3.2" # strong coding model, free tier available on Oxlo.ai
Step 2: Define the agent's system prompt
This prompt constrains the model to return JSON with exactly two keys: the implementation and the tests. Keeping the output structured means we can parse it without brittle regex.
SYSTEM_PROMPT = '''You are an expert Python engineer practicing strict test-driven development.
When given a feature specification, return a JSON object with exactly two keys:
- "implementation": a string containing the full content of the implementation file. Name the module task_queue. Do not include a file path, only the code.
- "tests": a string containing the full content of the test file. Import from task_queue using `from task_queue import ...` or `import task_queue`. Use pytest.
Rules:
- Use Python 3.10+ syntax.
- Include type hints and docstrings.
- If previous test output is provided, analyze the failures and fix the implementation or tests.
- Do not write markdown or explanations outside the JSON object.'''
Step 3: Add file I/O helpers
The helper below expects a JSON object from the model and writes the two files to disk. I keep everything in the working directory so pytest resolves imports without package gymnastics.
import json
import pathlib
IMPL_FILE = pathlib.Path("task_queue.py")
TEST_FILE = pathlib.Path("test_task_queue.py")
def write_files(artifacts: dict[str, str]) -> None:
IMPL_FILE.write_text(artifacts.get("implementation", ""), encoding="utf-8")
TEST_FILE.write_text(artifacts.get("tests", ""), encoding="utf-8")
Step 4: Generate the implementation and tests
I send the feature specification to Oxlo.ai and ask for the implementation and test suite in one shot. I use response_format={"type": "json_object"} to enforce valid JSON.
FEATURE_SPEC = """
Build a PriorityTaskQueue with the following behavior:
- Tasks are tuples of (priority: int, task_id: str, payload: dict).
- Lower integer means higher priority.
- Supports insertion, peeking at the highest priority item, and popping it.
- If priorities are equal, preserve FIFO order.
- Include a method to mark a task_id as cancelled so it is skipped on pop.
"""
def generate_artifacts(spec: str, previous_output: str = "") -> dict[str, str]:
user_msg = f"Specification:\n{spec}\n"
if previous_output:
user_msg += f"\nPrevious test output:\n{previous_output}\n\nFix the code and tests."
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
response_format={"type": "json_object"},
temperature=0.2,
)
content = response.choices[0].message.content
return json.loads(content)
Step 5: Execute the test suite
A thin wrapper around subprocess captures stdout and the return code. Non-zero exit codes mean the agent needs another pass.
import subprocess
def run_tests() -> tuple[int, str]:
result = subprocess.run(
["python", "-m", "pytest", str(TEST_FILE), "-v"],
capture_output=True,
text=True
)
return result.returncode, result.stdout + result.stderr
Step 6: Build the self-correction loop
If tests fail, I feed the traceback back into the same conversation context and request a corrected JSON payload. I cap retries at three to avoid runaway loops.
MAX_RETRIES = 3
def iterate_until_passing(spec: str) -> None:
previous = ""
for attempt in range(1, MAX_RETRIES + 1):
print(f"\n--- Attempt {attempt} ---")
artifacts = generate_artifacts(spec, previous)
write_files(artifacts)
code, output = run_tests()
print(output)
if code == 0:
print("\nAll tests passed.")
return
previous = output
print("\nMax retries reached. Review generated files manually.")
Step 7: Orchestrate the full pipeline
The main entry point stitches everything together: generate, write, test, and optionally repair until green.
if __name__ == "__main__":
iterate_until_passing(FEATURE_SPEC)
Run it
Save the script as tdd_agent.py, export your key, and run it.
export OXLO_API_KEY="your_key_here"
python tdd_agent.py
You should see output similar to this:
--- Attempt 1 ---
============================= test session starts ==============================
collected 5 items
test_task_queue.py::test_insert_and_peek PASSED
test_task_queue.py::test_fifo_for_equal_priority PASSED
test_task_queue.py::test_pop_highest_priority PASSED
test_task_queue.py::test_cancel_task PASSED
test_task_queue.py::test_peek_empty_queue PASSED
============================== 5 passed in 0.02s ==============================
All tests passed.
If a test fails on the first attempt, the agent automatically prints the traceback and requests a fix from the model. On Oxlo.ai, that retry costs the same flat per-request rate as the first turn.
Wrap-up
Two concrete next steps. First, swap in kimi-k2.6 or qwen-3-32b for larger architectural specs that require advanced reasoning or agentic planning across multiple files. Second, add a file watcher with watchdog so the agent re-runs whenever you edit the spec in a local markdown file, turning it into a live coding companion.
Top comments (0)