Picture a solo founder at 11 p.m. with a working checkout and no budget. The feature request is boring: a nightly batch job that rewrites product blurbs. It is hypothetical, not a customer story. The interesting part is the constraint. The bill must stay at zero.

Most advice for that founder is wrong. It optimizes for throughput, then discovers the invoice. The real design target is smaller. A capped job queue that survives restarts and refusals costs nothing to run badly.

The bill is not compute

For a one-person product, unplanned work is the expensive line item. A batch job that half-finishes is worse than one that never starts. Half-finished means manual cleanup, duplicate emails, and a customer who noticed.

So the first artifact is not a prompt. It is a ledger. Every job gets a stable key, a status, and an attempt count. Rerunning the worker becomes free, because finished work is already marked done.

The second artifact is a hard cap. Free access to a model endpoint is a generous starting position, not a contract. Treat any allowance as a policy that can change without notice. Read the current number in the project's own documentation before planning a launch around it.

MonkeyCode is where this pattern is cheapest to try. The project offers free model access and a free server option, both operator-supplied claims. That combination is unusual for solo work. It means the queue and the model call can live in the same cheap place.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The worker, in one file

The skeleton below is illustrative. Endpoint paths, payload shape, and model identifiers must come from the project's docs. Confirm the current free allowance there too. This code was not executed against the live service.

# worker.py - restart-safe, capped batch worker. Python 3.11+, stdlib only. import json, os, sqlite3, time, urllib.request DB = os.environ.get("WORKER_DB", "worker.db") BASE_URL = os.environ["MONKEYCODE_BASE_URL"] # placeholder: copy the real value from docs API_KEY = os.environ["MONKEYCODE_API_KEY"] # placeholder: never commit this MODEL = os.environ.get("WORKER_MODEL", "") # placeholder: only if docs require it DAILY_CALL_CAP = int(os.environ.get("DAILY_CALL_CAP", "50")) SCHEMA = """ CREATE TABLE IF NOT EXISTS jobs ( key TEXT PRIMARY KEY, payload TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, result TEXT); CREATE TABLE IF NOT EXISTS usage ( day TEXT PRIMARY KEY, calls INTEGER NOT NULL DEFAULT 0); """ def db(): conn = sqlite3.connect(DB) conn.executescript(SCHEMA) return conn def charge(conn, day, cap=DAILY_CALL_CAP): conn.execute("INSERT OR IGNORE INTO usage(day, calls) VALUES (?, 0)", (day,)) used = conn.execute("SELECT calls FROM usage WHERE day=?", (day,)).fetchone()[0] if used >= cap: return False conn.execute("UPDATE usage SET calls=calls+1 WHERE day=?", (day,)) conn.commit() return True def call_model(prompt, timeout=60): body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}]}).encode() req = urllib.request.Request(BASE_URL, data=body, method="POST", headers={"Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}"}) with urllib.request.urlopen(req, timeout=timeout) as r: data = json.loads(r.read()) return data["choices"][0]["message"]["content"] def run_once(conn, day): if os.environ.get("WORKER_ENABLED", "1") != "1" or os.path.exists("STOP"): return "halted" row = conn.execute("SELECT key, payload, attempts FROM jobs " "WHERE status='pending' ORDER BY attempts, key LIMIT 1").fetchone() if row is None: return "idle" key, payload, attempts = row if attempts >= 3: conn.execute("UPDATE jobs SET status='dead' WHERE key=?", (key,)) conn.commit() return "dead" if not charge(conn, day): return "capped" try: out = call_model(payload) except Exception: conn.execute("UPDATE jobs SET attempts=attempts+1 WHERE key=?", (key,)) conn.commit() return "retry" conn.execute("UPDATE jobs SET status='done', result=? WHERE key=?", (out, key)) conn.commit() return "done" if __name__ == "__main__": conn = db() while True: result = run_once(conn, time.strftime("%Y-%m-%d")) time.sleep(5 if result in ("done", "retry") else 60) 

Four invariants carry the design. The primary key makes replays idempotent. The usage row makes spend visible per day. The STOP file is a kill switch a human can touch. The attempt counter turns infinite retries into a dead-letter state.

Test it without spending anything

Swap the model call for a fake and the whole queue becomes testable offline. This snippet defines the behavior you want to keep. Run it before the first real call.

import os, worker as w w.call_model = lambda prompt, timeout=60: "ok:" + prompt w.DAILY_CALL_CAP = 2 conn = w.db() conn.execute("INSERT OR REPLACE INTO jobs(key, payload) VALUES ('sku-1', 'rewrite sku-1')") assert w.run_once(conn, "2030-01-01") == "done" assert w.run_once(conn, "2030-01-01") == "idle" # dedupe holds assert w.charge(conn, "2030-01-01") is True # second call fits assert w.charge(conn, "2030-01-01") is False # cap blocks the third  open("STOP", "w").close() assert w.run_once(conn, "2030-01-01") == "halted" os.remove("STOP") 

A restart test follows the same shape. Close the connection, reopen the database, and confirm run_once returns idle. If it returns done, the state is in memory instead of the ledger.

The cap deserves suspicion. Watch what happens at 02:00 when the queue still has work. The worker returns capped and sleeps. Tomorrow it continues. That is the correct indie behavior: degrade, never overspend.

Running it on a free server

A free server is a shared resource. Assume it sleeps, restarts, or gets recycled. Write state to disk on every transition, and confirm that disk persists across restarts. If it does not, point WORKER_DB at a small external store.

Start the loop under a supervisor the platform restarts automatically. Keep the process crash-only. Any SIGKILL should cost at most one retried job, never a duplicate result.

Rate limits belong in the client, not in the loop. When the endpoint answers 429, sleep with a growing delay and let the attempt counter decide when to stop. A queue that respects refusal is a queue you can leave running overnight.

Where the free path fits

Workload Fits? Reason
Nightly content or metadata batch Yes Latency is irrelevant, retries are cheap
Internal summarization for one operator Yes Low volume, easy to inspect
Anything a customer waits on No No latency or availability promise
Regulated or secret data No Verify data handling first

Limits worth stating plainly

Free access and a free server are availability claims, not service levels. Quotas, model choices, and hardware can change. Nothing here guarantees throughput, uptime, or permanence.

Teams with paid customers on the critical path should not use this. Neither should anyone who needs an audit trail or a vendor agreement. The design is for one person testing an idea before spending money.

If that describes the week, the loop above is worth an evening. Free model access and the free server option make the first run cheap enough to start today.