{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"budgets-and-stopping-rules","title":"Budgets belong in the harness, not the prompt","url":"https://addaly.com/learn/agents-and-automation/budgets-and-stopping-rules","position":32,"of":72,"minutes":7,"answer":"Anything you need guaranteed goes in the harness; a prompt can only ask.","keyIdea":"Anything you need guaranteed goes in the harness; a prompt can only ask."},"body":{"format":"markdown","text":"## An agent with no definition of done will find more to do\n\nGive a capable model a goal, tools and a loop, and left alone it will keep going. It will re-verify a fact it already verified. It will check one more source. It will politely offer to also update the spreadsheet. This is not a defect in the model. There is nothing in the loop that says stop.\n\nSo you supply the stop. Four budgets, and every serious agent has all four:\n\n```python\nBUDGET = {\n    \"steps\":   12,     # loop iterations\n    \"usd\":     0.50,   # hard spend cap per run\n    \"seconds\": 120,    # wall clock\n    \"writes\":  3,      # actions that change the world\n}\n```\n\nThe fourth is the one people leave out and the one that saves you. **Blast radius** is a count of irreversible actions: refunds issued, emails sent, rows deleted, files published. Cap it separately, because the run that costs ₹4 and takes nine seconds can still send the same message to 900 people.\n\n## The prompt is a request; the counter is a rule\n\nWriting \"you have a maximum of 8 tool calls, do not exceed this\" in a system prompt feels like setting a limit. It is not one. The model has no reliable count of actions it has taken (it is reading a transcript, not consulting a register) and even a model that counts perfectly is still the party deciding whether to comply. You have written a suggestion.\n\nA counter in your loop is different in kind. It does not persuade; it returns.\n\nThis distinction generalises. Anything you actually need guaranteed (spend caps, allowed domains, which tables are writable, whether this account can be charged) lives in the code that executes tool calls. The prompt is for guidance and taste. The harness is for rules.\n\n## Stopping rules beyond \"budget exhausted\"\n\nBudgets are the backstop. You also want the agent to stop for good reasons:\n\n- **Success criterion.** State it concretely enough to check. Not \"reconcile the accounts\" but \"every row in `pending` has either a matched payment ID or a reason code.\"\n- **Give-up criterion.** \"If the supplier portal returns 403 twice, stop and escalate.\" Without one, the agent grinds against a wall that is not going to move.\n- **No-progress detector.** If two consecutive steps produce the same tool name with the same arguments, halt. It is five lines and it catches most runaway loops:\n\n```python\nif (call.name, call.args) == last_call:\n    return stop(\"no progress: repeated identical call\", messages)\nlast_call = (call.name, call.args)\n```\n\n## What to do when you hit the limit\n\nThis is where most implementations quietly do damage. Two failure modes, both common:\n\n- Returning a partial result formatted exactly like a complete one. Now the caller believes 14 invoices were processed when 8 were.\n- Throwing an exception that discards everything the run learned in ninety seconds.\n\nDo neither. Hand back a structured account of the stop:\n\n> Stopped after 12 steps (step budget). Reconciled 8 of 14 invoices. The remaining 6 all failed at the same point: no GST number on the supplier record. Next action: add GST numbers for Rakesh Traders, Coastal Supply, and four others listed below.\n\nThat is useful to a human, and it is useful as input to the next run. A partial result that is honest about being partial is a good outcome. A partial result pretending to be complete is worse than a crash, because a crash gets investigated.\n\n## Set them low first\n\nStart every new agent tighter than feels reasonable (5 steps, one write, thirty seconds) and watch what hits the ceiling. The runs that die at the limit tell you more about the task than the runs that succeed. Raise limits with evidence, one at a time."},"check":{"question":"A team writes \"You may make at most 8 tool calls. Do not exceed this.\" in the system prompt. Logs show runs with 15 and 22 tool calls. What is the core problem?","options":[{"text":"The instruction is advice with nothing behind it: the loop never checks a counter, so exceeding the limit has no consequence.","correct":true},{"text":"The instruction appears only in the system prompt and gets less salient as the transcript grows; repeating it each turn would fix it.","correct":false,"tempting":"Diagnoses an attention problem, but even a perfectly attended instruction has no enforcement behind it."},{"text":"Eight calls is too few for the work, so the model overruns; sizing the limit correctly would produce compliance.","correct":false,"tempting":"Reads an overrun as a sizing problem, when a correctly sized unenforced limit still would not bind on the hard runs."},{"text":"The model cannot see how many calls it has made, so it needs a tool that reports the current call count.","correct":false,"tempting":"Fixes visibility without fixing authority: an accurately informed agent can still choose to make the ninth call."}]},"prev":{"slug":"reading-a-trace","title":"Reading a run, step by step","url":"https://addaly.com/learn/agents-and-automation/reading-a-trace"},"next":{"slug":"state-machines-over-loops","title":"When the loop should be a graph","url":"https://addaly.com/learn/agents-and-automation/state-machines-over-loops"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Budgets belong in the harness, not the prompt', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/budgets-and-stopping-rules (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Budgets belong in the harness, not the prompt","courseTitle":"Agents and Automation","module":{"index":4,"slug":"making-it-reliable","title":"Surviving real data"},"answer":null,"keyIdea":"Anything you need guaranteed goes in the harness; a prompt can only ask.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"an-agent-with-no-definition-of-done-will-find-more-to-do","runs":[{"text":"An agent with no definition of done will find more to do"}]},{"t":"p","runs":[{"text":"Give a capable model a goal, tools and a loop, and left alone it will keep going. It will re-verify a fact it already verified. It will check one more source. It will politely offer to also update the spreadsheet. This is not a defect in the model. There is nothing in the loop that says stop."}]},{"t":"p","runs":[{"text":"So you supply the stop. Four budgets, and every serious agent has all four:"}]},{"t":"code","lang":"python","label":"Python","text":"BUDGET = {\n    \"steps\":   12,     # loop iterations\n    \"usd\":     0.50,   # hard spend cap per run\n    \"seconds\": 120,    # wall clock\n    \"writes\":  3,      # actions that change the world\n}"},{"t":"p","runs":[{"text":"The fourth is the one people leave out and the one that saves you. "},{"text":"Blast radius","b":true},{"text":" is a count of irreversible actions: refunds issued, emails sent, rows deleted, files published. Cap it separately, because the run that costs ₹4 and takes nine seconds can still send the same message to 900 people."}]},{"t":"h2","id":"the-prompt-is-a-request-the-counter-is-a-rule","runs":[{"text":"The prompt is a request; the counter is a rule"}]},{"t":"p","runs":[{"text":"Writing “you have a maximum of 8 tool calls, do not exceed this” in a system prompt feels like setting a limit. It is not one. The model has no reliable count of actions it has taken (it is reading a transcript, not consulting a register) and even a model that counts perfectly is still the party deciding whether to comply. You have written a suggestion."}]},{"t":"p","runs":[{"text":"A counter in your loop is different in kind. It does not persuade; it returns."}]},{"t":"p","runs":[{"text":"This distinction generalises. Anything you actually need guaranteed (spend caps, allowed domains, which tables are writable, whether this account can be charged) lives in the code that executes tool calls. The prompt is for guidance and taste. The harness is for rules."}]},{"t":"h2","id":"stopping-rules-beyond-budget-exhausted","runs":[{"text":"Stopping rules beyond “budget exhausted”"}]},{"t":"p","runs":[{"text":"Budgets are the backstop. You also want the agent to stop for good reasons:"}]},{"t":"ul","items":[[{"text":"Success criterion.","b":true},{"text":" State it concretely enough to check. Not “reconcile the accounts” but “every row in "},{"text":"pending","code":true},{"text":" has either a matched payment ID or a reason code.”"}],[{"text":"Give-up criterion.","b":true},{"text":" “If the supplier portal returns 403 twice, stop and escalate.” Without one, the agent grinds against a wall that is not going to move."}],[{"text":"No-progress detector.","b":true},{"text":" If two consecutive steps produce the same tool name with the same arguments, halt. It is five lines and it catches most runaway loops:"}]]},{"t":"code","lang":"python","label":"Python","text":"if (call.name, call.args) == last_call:\n    return stop(\"no progress: repeated identical call\", messages)\nlast_call = (call.name, call.args)"},{"t":"h2","id":"what-to-do-when-you-hit-the-limit","runs":[{"text":"What to do when you hit the limit"}]},{"t":"p","runs":[{"text":"This is where most implementations quietly do damage. Two failure modes, both common:"}]},{"t":"ul","items":[[{"text":"Returning a partial result formatted exactly like a complete one. Now the caller believes 14 invoices were processed when 8 were."}],[{"text":"Throwing an exception that discards everything the run learned in ninety seconds."}]]},{"t":"p","runs":[{"text":"Do neither. Hand back a structured account of the stop:"}]},{"t":"quote","runs":[{"text":"Stopped after 12 steps (step budget). Reconciled 8 of 14 invoices. The remaining 6 all failed at the same point: no GST number on the supplier record. Next action: add GST numbers for Rakesh Traders, Coastal Supply, and four others listed below."}]},{"t":"p","runs":[{"text":"That is useful to a human, and it is useful as input to the next run. A partial result that is honest about being partial is a good outcome. A partial result pretending to be complete is worse than a crash, because a crash gets investigated."}]},{"t":"h2","id":"set-them-low-first","runs":[{"text":"Set them low first"}]},{"t":"p","runs":[{"text":"Start every new agent tighter than feels reasonable (5 steps, one write, thirty seconds) and watch what hits the ceiling. The runs that die at the limit tell you more about the task than the runs that succeed. Raise limits with evidence, one at a time."}]}],"check":{"question":[{"text":"A team writes “You may make at most 8 tool calls. Do not exceed this.” in the system prompt. Logs show runs with 15 and 22 tool calls. What is the core problem?"}],"options":[{"text":[{"text":"The instruction is advice with nothing behind it: the loop never checks a counter, so exceeding the limit has no consequence."}],"correct":true,"tempting":null},{"text":[{"text":"The instruction appears only in the system prompt and gets less salient as the transcript grows; repeating it each turn would fix it."}],"correct":false,"tempting":[{"text":"Diagnoses an attention problem, but even a perfectly attended instruction has no enforcement behind it."}]},{"text":[{"text":"Eight calls is too few for the work, so the model overruns; sizing the limit correctly would produce compliance."}],"correct":false,"tempting":[{"text":"Reads an overrun as a sizing problem, when a correctly sized unenforced limit still would not bind on the hard runs."}]},{"text":[{"text":"The model cannot see how many calls it has made, so it needs a tool that reports the current call count."}],"correct":false,"tempting":[{"text":"Fixes visibility without fixing authority: an accurately informed agent can still choose to make the ninth call."}]}],"order":[1,2,3,0],"why":null},"prev":{"slug":"reading-a-trace","title":"Reading a run, step by step","minutes":9},"next":{"slug":"state-machines-over-loops","title":"When the loop should be a graph","minutes":9}}}