DEV Community

Dakota Wu
Dakota Wu

Posted on

Audit Context Re-Send Before You Trust a Free Token Allowance

Core conclusion: in a multi-turn agent loop, the transcript you resend — not the model's reply — decides whether a free token allowance survives the day. Measure the re-send multiplier on one real run before building a workflow on top of it.

Small teams tend to budget by output length. That is the wrong variable. Every turn ships the whole prior conversation back as input, so consumption grows quadratically while the visible artifact (the answer) grows linearly.

The multiplier, in one worked example

Assume a loop that produces roughly 400 tokens per reply and resends full history each turn. These numbers are illustrative arithmetic, not a benchmark.

  • Turn 1 input: 0 prior tokens.
  • Turn 5 input: ~1,600 prior tokens.
  • Turn 20 input: ~7,600 prior tokens.
  • Cumulative input across 20 turns: ~76,000 tokens. Cumulative output: ~8,000.

A 9.5:1 input-to-output ratio. Retries make it worse, because a failed turn usually gets retried with the failure text appended — history grows and the loop immediately pays for the growth again.

An operator-stated free allowance is a real constraint under that arithmetic. At the time of writing, the operator of the open-source MonkeyCode project states a free token allowance and a free server option; both are promotional availability claims that can change, so read the current project page before planning around specific numbers. The method below works regardless of where the tokens come from.

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

Step 1 — Record one honest loop

The audit needs a transcript, not a mental model of the loop. Log every turn as JSONL with the prompt actually sent and the reply actually received. If the provider returns usage numbers, store them too — they calibrate the estimate later.

{"turn": 1, "prompt": "summarize issue 41", "reply": "...", "usage_input": 812}
{"turn": 2, "prompt": "now write the patch", "reply": "...", "usage_input": 1930}
Enter fullscreen mode Exit fullscreen mode

One constraint: record a run that failed at least once. Clean runs understate the multiplier by a wide margin, and the failure path is usually where indie budgets die.

Step 2 — Estimate re-send with bytes, then calibrate

A tokenizer is optional for this. Counting UTF-8 bytes and dividing by four gives a usable proxy in the same order of magnitude, and the script below prints the calibration ratio when real usage numbers exist.

#!/usr/bin/env python3
"""audit_loop.py - estimate context re-send from a JSONL transcript.

Proxy: 1 token ~= 4 UTF-8 bytes. Estimate only, not a tokenizer.
Usage: python3 audit_loop.py transcript.jsonl [--ceiling 200000]
"""
import argparse
import json
import sys
from pathlib import Path

BYTES_PER_TOKEN = 4


def estimate(text: str) -> int:
    return max(1, len(text.encode("utf-8")) // BYTES_PER_TOKEN)


def load(path: Path) -> list[dict]:
    rows = []
    with path.open(encoding="utf-8") as fh:
        for line in fh:
            line = line.strip()
            if line:
                rows.append(json.loads(line))
    return rows


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("transcript", type=Path)
    ap.add_argument("--ceiling", type=int, default=0)
    args = ap.parse_args()

    rows = load(args.transcript)
    history = 0
    cumulative_in = 0
    cumulative_out = 0
    measured_in = 0

    print(f"{'turn':>4} {'in_proxy':>9} {'out_proxy':>9} {'cum_in':>9}")
    for i, row in enumerate(rows, start=1):
        out_tok = estimate(row.get("reply", ""))
        in_tok = history + estimate(row.get("prompt", ""))
        history += out_tok
        cumulative_in += in_tok
        cumulative_out += out_tok
        measured_in += int(row.get("usage_input") or 0)
        print(f"{i:>4} {in_tok:>9} {out_tok:>9} {cumulative_in:>9}")

    if cumulative_out and cumulative_in:
        ratio = cumulative_in / cumulative_out
        print(f"\ninput/output ratio: {ratio:.1f}:1")
    if measured_in:
        ratio = measured_in / cumulative_in
        print(f"calibration vs reported usage: {ratio:.2f}x")
        print("  (>1.5x or <0.67x means retune BYTES_PER_TOKEN)")

    print(f"cumulative input proxy: {cumulative_in} tokens")
    if args.ceiling and cumulative_in > args.ceiling:
        print("verdict: run exceeded ceiling", file=sys.stderr)
        return 2
    return 0


if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode
python3 audit_loop.py transcript.jsonl --ceiling 200000
echo "exit=$?"
Enter fullscreen mode Exit fullscreen mode

Exit code 2 is deliberate. It lets the audit slot into a pre-commit hook or a CI step without anyone parsing prose.

Step 3 — Gate the loop with a daily ledger

An audit is a measurement; a gate is a policy. The gate is a small append-only file with one row per call, fsynced so a crash cannot erase the evidence.

#!/usr/bin/env python3
"""gate.py - daily token ceiling for a single-process agent loop."""
import json
import os
import sys
import time
from pathlib import Path

LEDGER = Path(os.environ.get("LOOP_LEDGER", "budget.jsonl"))
CEILING = int(os.environ.get("LOOP_DAILY_TOKENS", "0"))


def spent_today() -> int:
    day = time.strftime("%Y-%m-%d")
    if not LEDGER.exists():
        return 0
    total = 0
    for line in LEDGER.read_text(encoding="utf-8").splitlines():
        if not line.strip():
            continue
        row = json.loads(line)
        if row.get("day") == day:
            total += int(row.get("tokens", 0))
    return total


def charge(tokens: int, tag: str) -> None:
    row = {
        "day": time.strftime("%Y-%m-%d"),
        "ts": int(time.time()),
        "tokens": tokens,
        "tag": tag,
    }
    with LEDGER.open("a", encoding="utf-8") as fh:
        fh.write(json.dumps(row) + "\n")
        fh.flush()
        os.fsync(fh.fileno())


def preflight(estimated: int, tag: str = "run") -> bool:
    if CEILING <= 0:
        return True
    used = spent_today()
    if used + estimated > CEILING:
        print(f"refuse tag={tag} used={used} est={estimated} ceiling={CEILING}",
              file=sys.stderr)
        return False
    return True
Enter fullscreen mode Exit fullscreen mode
export LOOP_DAILY_TOKENS=250000
python3 -c "import gate; print(gate.preflight(40000, 'nightly'))"
Enter fullscreen mode Exit fullscreen mode

Two rules keep this honest. Charge before the call with the estimate, then charge again after with the real number and a settle tag; and set the ceiling below the allowance, not at it, because the proxy drifts on code-heavy prompts.

Step 4 — Shrink the transcript instead of the model

The cheapest fix is not a smaller model. It is a smaller history. Replace full-history resend with a rolling summary capped at a fixed byte budget.

import os
from pathlib import Path


def roll(summary_path: str, new_reply: str, cap: int = 2000) -> None:
    p = Path(summary_path)
    prev = p.read_text(encoding="utf-8") if p.exists() else ""
    merged = (prev + "\n" + new_reply.strip()).strip()
    raw = merged.encode("utf-8")
    if len(raw) > cap:
        merged = raw[-cap:].decode("utf-8", errors="ignore")
        merged = merged.split("\n", 1)[-1]
    tmp = p.with_suffix(".tmp")
    tmp.write_text(merged, encoding="utf-8")
    os.replace(tmp, p)
Enter fullscreen mode Exit fullscreen mode

Run the audit again after the swap. A loop that drops from 9:1 to 3:1 input-to-output typically fits a free allowance for a full day of iteration, which is the whole point for a solo build.

Where each option fits

Situation Full history resend Rolling summary Fresh single call
First prototype, one file fine overkill fine
Multi-step refactor, 20+ turns costs spiral recommended loses context
Nightly scripted job avoid recommended recommended
Debugging a single failure fine hides the trace fine

The free server option is the natural host for the gate and the ledger, since both are tiny and stateful. Whether it fits a given workload depends on current limits, so check the project page rather than a blog post — including this one.

Limits and who should skip this

  • The byte proxy misjudges CJK text, minified code, and base64 blobs. Calibrate against reported usage before trusting the ceiling.
  • The ledger assumes one writer. Two concurrent processes will double-count or race; use a lock or one ledger per process.
  • Free allowances and free server availability are promotional and can change without notice. Nothing here guarantees capacity.
  • Skip this if you need an SLA, deterministic latency, or auditable per-request billing. A paid provider with metered invoices is the correct tool there.

For an indie route shipped today, the sequence is short: record one loop, audit the multiplier, gate the day, shrink the transcript. If the open-source MonkeyCode project is the host you want to try, the free model access and free server option are the two things to verify on its current page before you commit a build to them.

Top comments (0)