I needed a way to stop agents from burning tokens in silence. A self-auditing loop with a JSONL ledger, a hard token budget, and a verification command after every stage is enough to make every decision explainable—and it runs on a free Linux server.
An agent makes a bad call. It burns 40,000 tokens before anyone notices. The worst part is that nobody can explain why. A log file is not a ledger. I wanted an append-only paper trail that counts every token, records every decision, and fails safely when the budget is gone. This is the loop I actually run: no tools, no retries, no vector store. Just a model, a task, and an audit record.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free tier includes 10 million tokens for experiments. That covers hundreds of small agent runs.
What I am building and why a ledger beats a log
I split the agent into three parts:
- A reasoning ledger stored as JSON Lines (JSONL).
- A token budget guard that stops the loop before it overshoots.
- A verification command for every stage, so I never assume a step worked.
A typical log file mixes stdout, stack traces, and model chatter. JSONL is append-only, crash-safe, and greppable. Each line is one decision. I can stream it, count it with jq, and reconstruct the prompt context without a database. For a single-process experiment, a database is overkill.
Compared with a full agent framework, this loop is intentionally dumb. Frameworks hide retries, tool calls, and parallel branches. That is useful in production and opaque when I am trying to learn where tokens leak. I want the opposite: one call, one record, one budget check.
What I need:
- A small Linux server. Any free tier works.
- Python 3.10 or newer.
- The
openaiPython package, which talks to any OpenAI-compatible Chat Completions endpoint. - A MonkeyCode account for free model access and a free server option.
Provision the server and configure model access
I log in and install only the basics.
ssh user@your-server
sudo apt update && sudo apt install -y python3 python3-venv git
mkdir -p ~/agent && cd ~/agent
python3 -m venv venv
source venv/bin/activate
pip install openai
Then I verify the stage instead of hoping it worked.
python3 --version
pip show openai | head -2
I expect Python 3.10+ and a printed package version. If either command fails, I stop. Skipping verification is how silent failures start.
MonkeyCode gives me three values: a base URL, an API key, and a model name. I export them as environment variables so the code never hard-codes secrets.
export LLM_BASE_URL="$MONKEYCODE_BASE_URL"
export LLM_API_KEY="$MONKEYCODE_API_KEY"
export LLM_MODEL="$MONKEYCODE_MODEL"
Verification for this stage:
env | grep LLM_
All three variables must appear. I keep the key out of shell history. A .env file or the server's secret store is safer than pasting the key into a profile.
Write the loop, the JSONL ledger, and the token budget guard
I create agent.py. The loop calls the model, logs the reply and usage, and repeats until the model writes [DONE] or I hit a step cap.
import json
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("LLM_BASE_URL"),
api_key=os.getenv("LLM_API_KEY"),
)
LEDGER = "ledger.jsonl"
def log_decision(entry):
with open(LEDGER, "a") as f:
f.write(json.dumps(entry) + "\n")
def call_model(messages, max_tokens=512):
return client.chat.completions.create(
model=os.getenv("LLM_MODEL"),
messages=messages,
max_tokens=max_tokens,
)
def run_task(task, steps=5):
messages = [{
"role": "system",
"content": "Before every action, state your reasoning in one line. End with [DONE] when finished."
}]
messages.append({"role": "user", "content": task})
for i in range(steps):
resp = call_model(messages)
reply = resp.choices[0].message.content
usage = resp.usage
log_decision({
"step": i,
"prompt_tokens": usage.prompt_tokens,
"completion_tokens": usage.completion_tokens,
"total_tokens": usage.total_tokens,
"decision": reply[:200],
})
messages.append({"role": "assistant", "content": reply})
if "[DONE]" in reply:
break
return messages
if __name__ == "__main__":
import sys
task = sys.argv[1] if len(sys.argv) > 1 else "Say hello and stop."
run_task(task)
Why JSONL? It is append-only. It survives crashes. I can grep it. I can stream it. Each record stores prompt_tokens, completion_tokens, total_tokens, and a truncated decision so I can audit without dumping the entire transcript into one giant file. A line looks like this:
{"step": 0, "prompt_tokens": 48, "completion_tokens": 91, "total_tokens": 139, "decision": "I will list three review risks in one pass, then stop."}
I cap max_tokens at 512 per call so a single rambling reply cannot recreate that 40,000-token burn. If [DONE] never appears, the steps=5 cap still ends the loop.
Add the token budget guard
Agents overshoot. A guard stops them. I add a budget check before every call.
BUDGET = int(os.getenv("TOKEN_BUDGET", "10000"))
def spent_tokens():
total = 0
if os.path.exists(LEDGER):
for line in open(LEDGER):
total += json.loads(line)["total_tokens"]
return total
def within_budget():
return spent_tokens() < BUDGET
I call within_budget() at the top of the loop.
for i in range(steps):
if not within_budget():
log_decision({"step": i, "error": "budget_exceeded", "total_tokens": 0})
break
resp = call_model(messages)
Now the agent fails safely. It records the reason. It stops spending. That is the difference between a log that says "process died" and a ledger that says budget_exceeded at step 3. The default budget of 10,000 tokens is well under the silent 40,000-token failure that started this experiment.
Run the loop and verify token accounting
I run a real task, not a toy hello.
python agent.py "List three risks in this code review and stop with [DONE]"
Then I verify the ledger exists.
cat ledger.jsonl | wc -l
I expect at least two lines—one per model call. If I see zero, the process never wrote a decision and I debug before I raise the budget.
Token accounting is a separate stage. I check totals, the most expensive step, and the reasoning trail.
cat ledger.jsonl | jq -r '.total_tokens' | awk '{s+=$1} END {print "total tokens:", s}'
cat ledger.jsonl | jq -s 'max_by(.total_tokens) | {step, total_tokens}'
cat ledger.jsonl | jq -r '.decision'
Each line shows what the agent decided. Each line shows what it cost. If one step dominates the total, that is where I tighten max_tokens or the system prompt.
Verification checklist I actually use:
| Stage | Command | Expected result |
|---|---|---|
| Server | python3 --version |
3.10+ |
| Config | `env \ | grep LLM_` |
| Loop | python agent.py "..." |
ledger.jsonl created |
| Tokens | `cat ledger.jsonl \ | wc -l` |
The ledger then answers four questions a log file usually cannot:
- Which step consumed the most tokens.
- Whether the agent looped or stopped with
[DONE]. - Whether the budget guard fired.
- The exact decision snippet at each step.
That turns a black box into an auditable record.
Limitations, who should skip this, and what I do next
The loop is single-process. No retries. No tool calls. No parallel branches. The ledger is local and does not sync across machines. Free tiers change, so I verify current quotas before I depend on them. I do not run this with sensitive data: the ledger stores raw model replies.
I would not use this if I needed multi-agent orchestration, if the project handled PII or regulated data, or if I already had a full agent framework. Those teams should use a real framework. I use this loop for learning and small experiments.
Start with one task. Watch the ledger. Raise the budget and see where tokens leak. MonkeyCode's free tier covers the first 10 million tokens. The free server runs this loop all day. That is enough to learn agent economics without a credit card.
Clone these files onto a free server tonight, run one task, and read ledger.jsonl before you add tools or retries. If you cannot explain a step from the ledger, the loop is not self-auditing yet—fix the record, not the model.
Top comments (0)