Your agent loops on the same tool call. It rephrases. It retries. Tokens vanish.
No crash. No error. Just a silent budget leak.
The current agent conversation keeps circling one idea: remember decisions, not just data. Raw logs tell you what happened. A decision ledger tells you why. This tutorial turns that idea into a runnable pattern.
You will build a self-auditing agent on a free server. Every step ends with a verification check. The final artifact is a SQLite ledger with a hard token budget.
What You Need
- MonkeyCode is an open-source project. Its current free tier includes a 10M token allowance and a free server option. Quotas change, so verify the numbers in your dashboard before you build.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
- Python 3.10+ on the server.
-
requestsand the standard librarysqlite3.
The design is endpoint-agnostic. You will adapt one function to the API contract in the project docs.
Step 1: Provision the Free Server
MonkeyCode's free server option gives you a Linux box. The dashboard shows the SSH command.
ssh root@<server-ip>
python3 --version
apt update && apt install -y python3-venv python3-pip
pip install requests
Verify: python3 --version prints 3.10 or newer. python3 -c "import requests, sqlite3" exits without errors.
Step 2: Set API Credentials
The dashboard exposes three values: an API key, a base URL, and a model name. Export them as environment variables.
export MC_API_KEY="<from-dashboard>"
export MC_BASE_URL="<from-dashboard>"
export MC_MODEL="<from-dashboard>"
export MC_BUDGET_TOKENS="100000"
Verify: echo ${MC_API_KEY:?} ${MC_BASE_URL:?} ${MC_MODEL:?} prints all three values. No empty strings.
Step 3: Write the Ledger Agent
Create agent.py. The loop is simple: ask the model for an action, run the tool, record the decision, check the budget.
import json
import os
import sqlite3
import time
import requests
API_KEY = os.environ["MC_API_KEY"]
BASE_URL = os.environ["MC_BASE_URL"]
MODEL = os.environ["MC_MODEL"]
BUDGET = int(os.environ.get("MC_BUDGET_TOKENS", "100000"))
DB = "ledger.db"
def init_db():
con = sqlite3.connect(DB)
con.execute("""
CREATE TABLE IF NOT EXISTS decisions (
id INTEGER PRIMARY KEY,
ts REAL,
step INTEGER,
action TEXT,
tool_input TEXT,
tokens_in INTEGER,
tokens_out INTEGER,
total_used INTEGER
)
""")
con.commit()
return con
def call_llm(messages):
# Adapt auth and payload to the endpoint contract in the project docs.
resp = requests.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"model": MODEL, "messages": messages},
timeout=60,
)
resp.raise_for_status()
data = resp.json()
usage = data.get("usage", {})
tokens_in = usage.get("prompt_tokens", 0)
tokens_out = usage.get("completion_tokens", 0)
return data["choices"][0]["message"], tokens_in, tokens_out
def search_tool(query):
# Replace with a real tool. Returns a canned result.
return f"results for {query}: 3 items, none critical"
def main():
con = init_db()
used = 0
step = 0
messages = [
{"role": "system", "content": (
"You are a cautious operator. Return JSON only: "
'{"action": "search" | "stop", "query": "..."}'
)}
]
while used < BUDGET:
step += 1
msg, tokens_in, tokens_out = call_llm(messages)
used += tokens_in + tokens_out
try:
action = json.loads(msg["content"])
except json.JSONDecodeError:
break
query = action.get("query", "")
if action.get("action") == "stop":
con.execute(
"INSERT INTO decisions VALUES (?,?,?,?,?,?,?,?)",
(None, time.time(), step, "stop", query,
tokens_in, tokens_out, used),
)
con.commit()
break
result = search_tool(query)
messages.append({"role": "assistant", "content": msg["content"]})
messages.append({"role": "tool", "content": result})
con.execute(
"INSERT INTO decisions VALUES (?,?,?,?,?,?,?,?)",
(None, time.time(), step, action.get("action"), query,
tokens_in, tokens_out, used),
)
con.commit()
print(f"step={step} used={used} action={action.get('action')}")
con.close()
print(f"final_used={used}")
if __name__ == "__main__":
main()
Verify: python3 agent.py prints increasing used values. The script stops when the model returns "action": "stop".
Step 4: Enforce the Hard Budget
The while used < BUDGET condition is the enforcement point. When the budget is exhausted, the loop exits. No tool call runs after that.
Test it with a tiny budget:
MC_BUDGET_TOKENS=500 python3 agent.py
Verify: final_used is less than or equal to 500. The script never exceeds the cap.
Step 5: Read the Decision Ledger
The ledger answers questions raw logs cannot. Query it with sqlite3:
sqlite3 ledger.db \
"SELECT step, action, tool_input, tokens_in, tokens_out, total_used \
FROM decisions ORDER BY step;"
Verify: every row has a monotonic total_used. The final row matches final_used from the script output.
Why This Pattern Works
A hard budget is a constraint, not a punishment. It forces the agent to stop and reconsider. The ledger makes the reasoning visible after the fact.
The ledger is written before the next step runs. That makes it enforcement, not forensics. Two failure modes disappear:
-
Looping: repeated tool calls with the same
tool_inputare obvious in the query output. -
Silent overruns:
total_usedis checked before every step, not after the damage.
Limitations
- The 10M token figure is current as of this writing. Free quotas change. Re-check before you rely on them.
- The ledger trusts the endpoint's
usagefield. If the endpoint omits it, both values default to zero. Add a character-based fallback if your API does not report usage. - This is a single-process design. No concurrency, no retries, no queue.
- The tool is a stub. Replace
search_toolwith a real integration and add error handling.
Who Should Not Use This
- Teams that need a latency SLA or guaranteed uptime. A free server has no contract.
- High-throughput production workloads. The SQLite write per step becomes a bottleneck.
- Sensitive data pipelines without a review of where the server and API sit.
Try It Against a Real Allowance
The fastest way to validate the budget logic is to compare it with a real allowance. The MonkeyCode dashboard shows your remaining tokens. Run the script, then compare the ledger total with the dashboard number. If they disagree, your endpoint's usage reporting is the first suspect.
MonkeyCode provides free models that can run this workflow.
Top comments (0)