My biggest fear with autonomous agents was never "will it work."
It was walking away. You give an agent a goal, it starts looping — browse, extract, save, judge, repeat — and the moment you close the laptop, a question starts nagging: what is this costing right now? I've checked an API dashboard at 7am the way you check your phone after a night out. Nervously.
The fix sounds boring: give the loop a budget. But when I actually tried to do that, I ran into a problem I didn't expect — and it wasn't technical.
"This run gets 600,000 tokens" means nothing
Tokens are the natural unit for LLM spend, and they are a terrible unit for humans. Quick: is 600,000 tokens a lot for a research task? Is 50,000 stingy? If a teammate says "I capped the nightly job at 2 million tokens," do you nod because you understand, or because nodding is easier?
Nobody has intuition for raw token counts. Which means token budgets don't get set — or get set once, wrong, and never touched again.
While building rysh (an agentic terminal multiplexer — every pane can be a shell, an agent, or a chat), I ended up giving agent budgets a vocabulary borrowed from reading:
- 1 page = 1,000 tokens — about a page of dense text, honestly
- 1 book = 200 pages (200k tokens)
- 1 shelf = 20 books (4M tokens)
So a run gets --budget-size 20p — twenty pages. My Instagram-scouting automation gets 3b — three books per session. A serious overnight job might get a shelf.
The units changed the conversation more than the enforcement did. "This run gets three books" is a sentence you can say out loud in a planning meeting, and everyone in the room has the same rough picture. "This run gets 600,000 tokens" is a number you paste into a config file and forget. Legible budgets get set, argued about, and tuned. Illegible ones rot.
(And yes — the ceiling is enforced, not advisory. The loop cannot outspend it.)
The anatomy of a budgeted loop
In rysh, an automation is a markdown file with YAML frontmatter, stored under .rysh/automations/ — so the whole loop lives in git, next to the code it serves. Here's the (trimmed) recipe for a real one I run: it scouts podcast-guest candidates on Instagram for a show, discovery-only, and saves a shortlist file.
loop:
do: # inner loop — one working session
interval: 30
max_duration: 7m
budget:
size: 3b # 3 books = 600k tokens, hard ceiling
watch:
takeover_when: 90 # at 90% spent, switch to the wrap-up leg
takeover_prompt: >
The discovery budget is used up — stop browsing now.
Save the shortlist as-is, add a note that it is PARTIAL,
then print the file path and what to cover next time.
while: # outer loop — repeat until the goal holds
max_iterations: 5
max_duration: 40m
budget: 15b # whole job: 15 books
prompts:
until: >
The saved shortlist has at least 12 strong candidates, each
with a handle, a why-they-fit line, and a contact — no dupes.
iterate_with: >
Continue scouting for {{args}}. The current shortlist is
seeded above: find what's missing, merge, save back.
Two loops, two budgets:
-
dois one working session. It gets its own token ceiling (3 books) and its own wall-clock cap (7 minutes). Whichever ceiling is hit first wins. -
whilewraps the whole job. Its budget (15 books) bounds everything the job can ever spend, across all sessions. Even if the goal is never met, the total cost has a known worst case before the job starts.
That last sentence is the whole point. You can answer "what will this cost tonight?" at review time, from the diff.
The takeover leg — because dying at 100% is also a failure
Budgeted loops usually fail in one of two default ways:
- No budget → the bill surprises you.
- A hard cutoff → the run dies mid-thought at 100%, and the work in flight is lost.
Both are bad, and the second one quietly teaches teams to remove budgets — which gets you back to the first one.
So the budget has a watch. takeover_when: 90 means: when 90% of the ceiling is spent, the task's own prompt is taken off the wheel and the takeover_prompt takes over — with the reserved 10% as its budget. Its job is not to make progress. Its job is to land the plane: save what exists, label it PARTIAL, note what's missing, exit clean.
The run always ends with saved, labeled output. Never a wall.
Reset and resume — the loop is interruptible by design
When a do session ends (goal-ish reached, budget spent, or clock expired), an LLM judge reads the saved output against the until: text — a stop condition that's written down in the file, reviewable in code review like everything else.
- Goal met → the loop ends.
-
Not yet → the inner budget resets to a fresh 3 books, and the next session starts — seeded with the shortlist the last one saved (that's the
iterate_withprompt). It resumes the work, not the transcript. Each pass starts with a clean context and the artifact, which is both cheaper and less noisy than dragging a 200k-token history along.
And because sessions checkpoint their state, you can interrupt the whole thing:
##auto web stop scout # park it
##auto web continue scout # resume from the last checkpoint, budget re-armed
stop isn't a kill. continue isn't a restart. The loop picks up where it left off, with a fresh session budget. Then you schedule it and go to sleep:
##cron add nightly-scout "0 7 * * *" ##auto web run --headless scout
Design decisions I'd defend (and what I'm less sure about)
Human units over raw tokens. Covered above, but it's the hill I'll die on: a budget nobody can reason about is a budget nobody maintains.
Percentage-based takeover over "detect when stuck." takeover_when is a number (10–99), not a natural-language predicate. Detecting "the agent is stuck" reliably is a research problem; "90% of budget is gone" is a fact. Plain-language guardrails ("stop on any login wall") live in the recipe prompt itself, where the agent applies them per-step.
Artifact-seeded resume over transcript replay. Restarting from the saved file keeps each pass cheap and forces the automation to define what "its output" actually is — which turns out to be great discipline.
What I'm less sure about: the judge. An LLM evaluating until: is far better than iteration counting, but it's still an LLM reading your criteria — vague goals get vague verdicts. Writing a crisp until: is the skill; mine took three edits before "12 strong candidates, no dupes, each with a contact" stopped being gameable.
Honest footer
rysh is my product. It's in private beta, built on Claude (bring your own Anthropic key), self-hostable — your automations are files in your repo, not rows in my database. The longer write-up, with the takeover and tunneling mechanics side by side, is here: agents on a leash
If you run agents in loops and want to kick the tires while it's early, we're onboarding design partners: rysh.ai/design-partner — or tell me why pages/books/shelves is the wrong abstraction. I've been wrong before; that's what the takeover leg is for.
Top comments (0)