Prompt & Done · MINIAGENT pack
An AI agent is a 30-line loop. Free, on your Mac.
A model that can only talk becomes an agent when you give it tools and call it in a loop. This page has the exact 30 lines from the reel, a tiny buggy project to try it on, and what we measured on 24 Oct 2026 with Qwen3.5 9B on Ollama.
1 Setup
# Python 3 and Ollama (https://ollama.com); we tested on a Mac
ollama pull qwen3.5 # 6.6 GB, Apache-2.0, supports tools
ollama serve # if the Ollama app is not already runningModel: ollama.com/library/qwen3.5 (Apache-2.0, the default 9B build is about 6.6 GB). Any Ollama model with the tools capability can be swapped in on the "model" line.
2 The 30 lines (agent.py)
import json, subprocess, sys, pathlib, urllib.request
ROOT = pathlib.Path(sys.argv[1])
def spec(name, doc, *args):
props = {k: {"type": "string"} for k in args}
return {"type": "function", "function": {"name": name, "description": doc,
"parameters": {"type": "object", "properties": props, "required": list(props)}}}
TOOLS = [spec("list_files", "List files"), spec("read_file", "Read a file", "path"),
spec("write_file", "Overwrite a file", "path", "content"), spec("run_tests", "Run the tests")]
def act(name, a):
if name == "list_files": return "\n".join(p.name for p in ROOT.glob("*.py"))
if name == "read_file": return (ROOT / a["path"]).read_text()
if name == "write_file": return (ROOT / a["path"]).write_text(a["content"]) and "ok"
if name == "run_tests":
r = subprocess.run(["python3", "-m", "unittest"], cwd=ROOT, capture_output=True, text=True)
return (r.stdout + r.stderr)[-1500:]
msgs = [{"role": "system", "content": "You are a coding agent. Use tools. Stop when the tests pass."},
{"role": "user", "content": "Make the tests pass. Don't edit the tests."}]
for step in range(12):
body = json.dumps({"model": "qwen3.5", "messages": msgs, "tools": TOOLS, "stream": False, "think": False})
reply = json.load(urllib.request.urlopen(urllib.request.Request(
"http://localhost:11434/api/chat", body.encode(), {"Content-Type": "application/json"})))["message"]
msgs.append(reply)
if not reply.get("tool_calls"): print("DONE:", reply["content"]); break
for c in reply["tool_calls"]:
f = c["function"]; print(f"[{step}] {f['name']}", flush=True)
msgs.append({"role": "tool", "tool_name": f["name"], "content": act(f["name"], f["arguments"])})Four tools (list_files, read_file, write_file, run_tests) and one loop: ask the model, run the tools it calls, hand the results back, repeat until it stops calling tools (at most 12 rounds). The one-line system prompt matters: without "Use tools" our first version answered with a plan and called nothing.
3 Something to fix
# cart.py (planted bugs: the discount multiplies by 10, free shipping uses > instead of >=)
def total(items, coupon=None):
"""items: list of (name, price_cents, qty). coupon: 'SAVE10' = 10% off, 'FREESHIP' = nothing off items."""
subtotal = sum(price * qty for name, price, qty in items)
if coupon == "SAVE10":
subtotal = subtotal - subtotal * 10
return subtotal
def shipping(subtotal_cents):
"""Free shipping at 5000 cents or more, otherwise 499."""
if subtotal_cents > 5000:
return 0
return 499
# test_cart.py
import unittest
from cart import total, shipping
class CartTests(unittest.TestCase):
def test_total_plain(self):
self.assertEqual(total([("pen", 200, 3), ("book", 1500, 1)]), 2100)
def test_total_save10(self):
self.assertEqual(total([("book", 2000, 2)], "SAVE10"), 3600)
def test_freeship_boundary(self):
self.assertEqual(shipping(5000), 0)
def test_shipping_small(self):
self.assertEqual(shipping(1200), 499)
if __name__ == "__main__":
unittest.main()# save the 30 lines as agent.py, then put cart.py and test_cart.py in a folder called cart/ next to it
python3 agent.py cartRun it on a copy of a folder. The loop can write any file path it is given and runs python3 -m unittest in that folder, so never point it at a project you have not backed up.
4 What we measured (24 Oct 2026)
Apple M4 Pro, 24 GB, Ollama 0.40.6, qwen3.5 9B (Q4_K_M), think off, no network.
| Test | Result |
|---|---|
| Plain chat, same sentence, no tools or files | Asked us to paste the test files and the source code (2.6 s) |
| Loop on cart.py (2 of 4 tests failing) | list, read, read, run (FAILED), write, run (OK): 26.6 s |
| Loop on textkit.py (3 of 5 tests failing) | same six calls, all green: 27.1 s |
| All our loop runs | 13 of 13 ended with every test passing (cart 9, textkit 4); times 18 to 78 s |
One small model, tiny files, bugs we planted ourselves. It rewrites whole files (the textkit fix works but is wordy). We did not test big codebases, other languages or other models, and this is not a replacement for Claude Code or any paid coding agent. The full message logs of the two filmed runs are in the pack's runs/ folder.
5 Licenses
Qwen3.5 is Apache-2.0 (printed by ollama show qwen3.5 --license); Ollama is MIT. The 30 lines are ours to share: use them freely.