The key point is that a loop is not an error. Every call succeeds, the output is valid, and the framework is happy, so max_retries never fires. Monitoring HTTP codes will show a healthy system while the bill climbs.
The two failure cases for a naive counter are the ones that bite in practice: the A to B to A cycle that a total count does not recognise, and the counter resetting to zero when the graph resumes after a crash. Hashing the payload at the handoff and keeping a short window catches both, as long as that window lives outside the process.
At Ascendra Ventures we treat a spend ceiling and a loop check as part of the scope of any agent build, with a test that forces a loop and confirms it stops at a known cost. It is a small piece of work next to a $3k surprise.
How do you handle loops where the payload changes slightly each turn, like a timestamp or a retry count, so the hashes never match?