DEV Community

Avery Lin
Avery Lin

Posted on

A Reasoning Ledger for a Free-Tier Coding Agent

An agent changed a function. Nobody knew why. The conversation was gone. The diff was clean. The reasoning was missing.

Agents produce diffs fast. They produce explanations faster. Those explanations evaporate. A reasoning ledger fixes that. Every decision gets an append-only entry. The entry holds the timestamp, the choice, the rationale, and the alternatives. The agent writes before it acts. The human reads later.

This tutorial builds that ledger from zero. The stack is small. A Python script calls a free model. A FastAPI app exposes a webhook. A JSONL file remembers everything. The build runs on MonkeyCode's free model tier and its free server option. MonkeyCode is open source. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free tier currently includes 10 million tokens and a hosted server for small apps. Limits change. Read the current dashboard before you rely on them.

The code is provider-agnostic. Any OpenAI-compatible endpoint works. MonkeyCode is one option. The tutorial stays useful either way.

Step 1: Get a token

Sign in to the MonkeyCode dashboard. Create an API token. Copy it into your shell.

export MONKEYCODE_API_KEY="your-token-here"
export MONKEYCODE_BASE_URL="https://api.monkeycode.example/v1"
export MONKEYCODE_MODEL="your-free-model-id"
Enter fullscreen mode Exit fullscreen mode

The base URL and model id come from the dashboard. Do not guess them. Do not hardcode them.

Verify the token with a model list request.

curl -s "$MONKEYCODE_BASE_URL/models" \
  -H "Authorization: Bearer $MONKEYCODE_API_KEY"
Enter fullscreen mode Exit fullscreen mode

Expect a JSON array. It contains the free model id. Save that id. If the endpoint does not expose /models, copy the model id from the dashboard instead.

Step 2: Scaffold the project

Create a directory. Create a virtual environment. Install three packages.

mkdir reasoning-agent
cd reasoning-agent
python -m venv .venv
source .venv/bin/activate
pip install openai fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode

Three packages cover the whole build. openai talks to the model. fastapi hosts the webhook. uvicorn runs the server. A virtual environment keeps dependencies local. It also keeps the free server deploy clean.

Step 3: Write the ledger

Create ledger.py. The ledger is a JSONL file. Each line is one decision.

import json
import time
from pathlib import Path

LEDGER_PATH = Path("ledger.jsonl")

def append_entry(entry: dict) -> None:
    entry["timestamp"] = time.time()
    with LEDGER_PATH.open("a") as fh:
        fh.write(json.dumps(entry) + "\n")

def read_ledger() -> list[dict]:
    if not LEDGER_PATH.exists():
        return []
    return [json.loads(line) for line in LEDGER_PATH.read_text().splitlines()]
Enter fullscreen mode Exit fullscreen mode

Think of the ledger as a flight recorder. The plane does not need it to fly. The investigators do. A database adds moving parts. A file adds none. JSONL is enough for a single agent.

Verify the ledger with a one-liner.

python -c "import ledger; ledger.append_entry({'decision': 'smoke'}); print(ledger.read_ledger())"
Enter fullscreen mode Exit fullscreen mode

Expect one entry. The timestamp appears automatically.

Step 4: Build the agent loop

The agent has one job. It reads a failing test. It asks the model for a patch. It writes the patch to a file. It logs the plan. It never applies the patch itself. A human applies it later.

Create agent.py.

import os
import subprocess
from pathlib import Path
from openai import OpenAI

import ledger

client = OpenAI(
    api_key=os.environ["MONKEYCODE_API_KEY"],
    base_url=os.environ["MONKEYCODE_BASE_URL"],
)
MODEL = os.environ["MONKEYCODE_MODEL"]

def run(cmd: list[str]) -> str:
    return subprocess.run(cmd, capture_output=True, text=True).stdout

def main() -> None:
    test_out = run(["pytest", "-q"])
    if "passed" in test_out:
        print("Tests pass. No action.")
        return
    diff = run(["git", "diff"])
    prompt = f"Failing output:\n{test_out}\nCurrent diff:\n{diff}\nWrite a minimal patch."
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    proposal = response.choices[0].message.content
    Path("proposal.patch").write_text(proposal)
    ledger.append_entry({
        "stage": "proposal",
        "decision": "write proposal.patch",
        "rationale": proposal[:500],
        "alternatives": ["revert last commit", "edit by hand"],
    })

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it on a repo with a broken test.

python agent.py
Enter fullscreen mode Exit fullscreen mode

Verify two things. proposal.patch exists. ledger.jsonl contains a proposal entry. An auto-applied patch can delete data. A written patch can be reviewed. Review is the point.

Step 5: Deploy on the free server

The free server runs small apps. A FastAPI app fits. Create server.py.

from fastapi import FastAPI, Request
import agent
import ledger

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    payload = await request.json()
    ledger.append_entry({
        "stage": "webhook",
        "decision": "accept payload",
        "rationale": payload.get("event", "unknown"),
        "alternatives": [],
    })
    agent.main()
    return {"ok": True}
Enter fullscreen mode Exit fullscreen mode

Run the server locally first.

uvicorn server:app --port 8000
Enter fullscreen mode Exit fullscreen mode

Send a test payload from another terminal.

curl -X POST http://localhost:8000/webhook \
  -H "Content-Type: application/json" \
  -d '{"event": "test"}'
Enter fullscreen mode Exit fullscreen mode

Check the ledger.

tail -n 2 ledger.jsonl
Enter fullscreen mode Exit fullscreen mode

Expect two new entries. One for the webhook. One for the proposal. Deploy the same app to the free server. The dashboard explains the exact steps. The code does not change. The webhook is a door. The ledger is the logbook. Every visitor leaves a trace.

Step 6: Read the ledger back

A ledger is only useful when someone reads it. Add a report command.

def report() -> None:
    for entry in read_ledger():
        print(entry["timestamp"], entry["decision"])
Enter fullscreen mode Exit fullscreen mode

Run it after every agent run. The report answers the old question of why. The answer is now on disk. Grep the file for a stage name. Grep it for a timestamp range. The ledger is plain text, so every Unix tool works.

Limitations

The free tier is an evaluation tier. It has no SLA. The token allowance is generous but finite. The model id changes. The base URL changes. Hardcode nothing.

Who should skip this approach? Teams with compliance requirements. Repos with secrets. High-throughput CI. A ledger helps, but it does not make a free tier production-grade.

Never auto-apply model patches. The tutorial writes a patch file on purpose. A human applies it. That is the whole point of the ledger.

Try it

The agent will still surprise you. Now you can trace the surprise. Run the tutorial on a real failing test. The ledger will show you what the model was thinking.

Top comments (0)