{"slug":"agent-loop-review","name":"Agent Loop Review: Repeats, Caps, Self-Grading","version":"1.0.0","updated_at":"2026-10-18T20:04:11.144Z","use_when":"Reviews the description, configuration or code of an agent loop, anything that repeats on its own: a scheduled job that calls a model, an agent that retries until it is done, a batch of actions, a self-improving prompt loop. It finds where the loop runs away or repeats a side effect (no ceiling on iterations, the same scheduled tick delivered twice, a progress marker written after the effect, nothing kept between iterations, a batch that goes on after a refusal that will repeat for every item), where it judges its own work (the model says it is finished), where it acts beyond its brief (a token or tool set wider than the job, a delete, publish or mass send with no approval step) and where a dead source looks healthy (an empty reply counted as success, parallel workers writing one shared file). Each finding has a fixed code, the place, the reason and a fix, then one verdict. Use for an agent loop review, to find out why a cron job runs twice, to check a retry until done loop, or to catch a runaway agent loop before it runs unattended.","not_for":"Money limits and bills (use spend-guard-review or bill-spike-finder), checking an agent's report against its log (agent-report-audit), or a false success claim (done-means-done). It reads pasted material only. Two facts are the owner's own, dated and not re-checked: the double scheduled tick (12 Oct 2026) and the shared-file corruption (8 Oct 2026).","languages":["any"],"tags":["agents","cron","retries","idempotency","automation","code-review","scheduler"],"category":"agents","category_url":"https://aiskills402.com/categories/agents","keywords":["agent loop review","cron job runs twice","retry until done","runaway agent loop"],"faq":[{"q":"Why is the comment 'runs once a minute' not enough for a cron handler?","a":"On 12 October 2026 we saw a Cloudflare Cron Trigger deliver the same scheduled minute twice, one to two minutes apart, and a job that trusted the comment posted twice: two articles in one tick and the same social post again. The skill flags any outward effect that runs without first reserving its minute, and the fix it gives is a per-minute, per-task row that only the first delivery can create; the second sees nothing new and stops."},{"q":"What does it say about a loop that ends when the model says it is done?","a":"It reports the loop as grading its own work, even when a ceiling on turns exists, and asks for an end condition the model cannot talk its way past: a test that actually runs, a measured value, or a second reader. A loop with no ceiling at all gets a separate finding, so one paste can carry both."},{"q":"How does it treat a batch that logs every failure and carries on?","a":"It separates one bad item among many, which is a normal skip, from a refusal that will repeat for every remaining item, such as a wrong credential, a missing credit or an unverified sender. For the second kind it asks the batch to stop at the first refusal, say why, and report how many items were done and how many were never tried."},{"q":"Does it help Claude Sonnet?","a":"No. Thirty loop setups went to Sonnet and to Haiku, each run bare and again with the skill, and every answer was read by hand. Both models found the same defects unaided in all 30 and left the sound code alone. The skill adds a short answer: one fixed code per problem, its place, a fix, and one exact verdict line. By that strict format Sonnet was exact on 26 of 30 and Haiku on 17 of 30."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"=== billing/create_charge.py ===\nimport time, requests\n\ndef create_charge(order_id, cents):\n    last = None\n    for attempt in range(3):\n        try:\n            r = requests.post(\"https://api.example.com/v1/charges\", json={\"order\": order_id, \"amount\": cents}, timeout=10)\n            if r.status_code < 500:\n                r.raise_for_status()\n                return r.json()…","output_excerpt":"[NOT-IDEMPOTENT] billing/create_charge.py, `create_charge` retry loop: a timeout or a 5xx does not prove the charge failed, so the next attempt can charge the customer a second time or a third. The request carries no idempotency key, and nothing in the paste shows the other side deduplicating on `order_id`.…"}],"page_url":"https://aiskills402.com/skills/agent-loop-review","markdown_url":"https://aiskills402.com/skills/agent-loop-review.md","image_url":"https://cdn.aiskills402.com/og/skills/agent-loop-review/016c994a.png","related_url":"https://api.aiskills402.com/v1/skills/agent-loop-review/related","purchases_count":null,"tested":{"date":"2026-10-18","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on all 30 setups by content, read by hand: it found the charge retried without a key, the loop that retries a dead feed for ever, the loop that ends when the model says DONE, the marker written after the mail is sent, the batch of purchases and the batch of notices that go on after a refusal that repeats, the admin token and the wide token scopes, the auto-merge and the bulk send with no approval, the empty list that makes a dead source look healthy, the parallel writers of one file and of one counter row, and a loop that is both uncapped and self-judged. It answered No findings. for the sound setups. By the skill's own strict format 26 of the 30 were exact: in the other four it added a code the paste does not support (silence on a retry loop, no-state on a prompt loop that does write its result, no-state on the newsletter claim that the skill itself recommends, a repeat-save finding on a job whose helper is not pasted)."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right on all 30 setups by content, read by hand: the same defects as Sonnet were named, each with a fix and the verdict line. By the skill's own strict format only 17 of the 30 were exact: in the others it added a code the paste does not support, mostly an approval or silence or no-state finding on a loop that only reads, only proposes or only writes a draft. It is the less careful reviewer of sound code: it reported a problem on some sound setups where Sonnet answered No findings."},"note":"Thirty setups of an agent loop written by us (21 with a planted fault, of which 3 hard, and 9 sound), pasted as code, configuration or a log tail; the shared request states the rules (a comment is a claim, a watched script needs no approval step, today is 18 October 2026). Each answer is scored twice: by the skill's own format (the exact set of codes, the verdict, a bare No findings. for sound code, no fence) and by content in any words, with a fence removed first. Nine setups were changed after the first run because the paste really carried a second defect nobody planted, and three controls were rewritten once more after a second read; all of them were run again on both sides, so the numbers are final for all 30. Facts: the double scheduled tick (12 October 2026) and the shared-file corruption (8 October 2026) are the owner's own measurements and were NOT re-checked; the rest is design judgement, not a measured platform fact. Content checks widened after the runs, for both sides: the cap, the self-grading, the silence and the stop phrasings, the words for losing everything at the end, the state and approval-date phrasings, and the sound-code checks for a double charge said of a caller that is not pasted, for overwriting in unrelated places and for a missing spend limit. Several extra codes are accepted as optional where a second defect is real by the skill's own definitions. One run per model and setup, the latest run of each.","baseline":{"date":"2026-10-18","rows":[{"label":"Setups reviewed right by content (30 setups)","better":"higher","strong":{"with":{"n":30,"of":30},"without":{"n":30,"of":30}},"weak":{"with":{"n":30,"of":30},"without":{"n":30,"of":30}}}],"note":"Same request on both sides; the bare side may use any words. Read by hand, Sonnet and Haiku without the skill both found every planted defect in all 30 setups and left the sound ones alone: the double tick, the missing key, the endless retry, the self-judged loop, the marker after the effect, the batch that goes on, the wide token, the missing approval, the dead source that looks healthy, the shared file and counter. Several bare answers first looked wrong only because of wording, and those checks were widened for both sides. So the content gain is zero for both models; what the skill adds is the fixed code per finding, the place and the fix in a set order, and an exact verdict line, which the bare answers do not give."},"report_url":null},"price_usd":"0.01","price_micro":10000,"size_bytes":12100,"sha256":"4b828f37b9fe3be25ea2bed40c65806f6cb75f9024d1a4a0e09d487ca067ac51","outline":["When this was checked","The answer","The codes","Rules","Work in this order","How to check a fix","Short example"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/agent-loop-review/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"Agent Loop Review for Cron and Retries","seo_description":"Reviews an agent loop for runaway retries, a cron tick delivered twice, self-grading and unapproved sends, each with a fix. Pay $0.01 once, in USDC.","versions":[{"version":"1.0.0","date":"2026-10-18","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-18\n\nFirst release: reviews the description, configuration or code of an agent loop (a scheduled job that calls a model, an\nagent that retries until done, a batch of actions, a self-improving prompt loop) and lists each problem with a fixed code,\nthe place, the reason and a fix, then a verdict (needs fixes, no known problems). Codes: an effect that runs again when\nthe trigger repeats or a step is retried (NOT-IDEMPOTENT), nothing kept between iterations or a marker written after the\neffect (NO-STATE), no ceiling (NO-ITERATION-CAP), the model grading its own work (SELF-GRADED), a batch that goes on after\nan error that repeats for every item (NO-STOP-ON-FAILURE), a permission wider than the job (OVERBROAD-PERMISSION), an\nirreversible or outward action with no approval (NO-HUMAN-GATE), \"no error\" taken as success (SILENCE-IS-SUCCESS),\nparallel workers writing one shared place (PARALLEL-SHARED-STATE).\n\nFacts: all five behaviours behind the codes are the owner's own dated measurements and rules (12 October 2026 double\nscheduled tick; 8 October 2026 shared-file corruption; silence rules; stop-at-first-refusal rule of 12–15 October 2026),\nmarked \"owner-measured, not re-checked\" in SKILL.md and notes/facts-2026-10-18.md. Public pages were fetched on\n2026-10-18 only to confirm they answer 200 and that the scheduler page does not state the double delivery. No model,\nno live system and no paid API was used.\n\nDecisions:\n- Verdict is binary (needs fixes / no known problems), batch 4 § 0.2; no severity groups, order of lines carries the severity.\n- Dated section \"When this was checked\" (batch 2 § 0.1) because the Cron Trigger behaviour is a vendor behaviour; not-for says it too.\n- NOT-IDEMPOTENT versus NO-STATE: a marker in the wrong order is NO-STATE; NOT-IDEMPOTENT needs a repeated trigger or a retried step in the paste. The neighbouring code is `optional` in the cases where both are defensible.\n- A person who starts a script by hand and watches it is a gate (task rule 3 and SKILL.md rule), so one-off scripts get no NO-HUMAN-GATE.\n- A stop condition is not a ceiling; a ceiling does not stop SELF-GRADED — the two codes can appear together (hard case).\n- Cases: 30 (21 planted of which 3 hard, 9 controls), 13 planted cases from the starting point (plain code, config or a log tail, no hint comment). One planted case keeps the author's reassuring comment \"runs once a minute\" as a claim.\n- Controls re-read as a reviewer: the marker is written BEFORE the effect and put back on failure (not after); the retry key is made once outside the loop; the atomic batch is one D1 batch; the approval table is read by the loop.\n- checksBase asks for substance in free words (twice, duplicate, idempotency; forever, no limit; its own work; crash between; and so on), never a code; the \"runs once a minute so it is fine\" answer is rejected by notRegex.\n- Price: start $0.05 (50000 micro-USDC), class A only if Sonnet gains at least 2 content cases; to be set after the runs. FAQ entry \"Does it help Claude Sonnet?\" was a placeholder, now written from the final numbers.\n\n## Test run 2026-10-18 and first reading (superseded by the final measurement below)\n\nMeasured (one run per model and setup, 30 setups): content by checksBase, Sonnet 30/30 with the skill, 27/30 without; Haiku 28/30 with, 26/30 without, after the widening below. Counting only the 21 setups that were not changed: Sonnet 21/21 with, 20/21 without; Haiku 21/21 with, 20/21 without. By the skill's own format on the 21: Sonnet 17/21, Haiku 12/21 (30 setups: 22 and 12).\n\nChecks widened after the run, for both sides (make-cases.mjs):\n- RX_CAP: \"no iteration cap\", \"indefinitely\", \"termination bound\" (an answer said \"No iteration cap... runs indefinitely\").\n- RX_SELF: \"the only evidence\", \"nothing verifies\", \"verify the output\" (completion judged by a substring of the model's own text).\n- RX_SILENCE: \"reported as success\", \"looks identical\", \"swallowed ... done/resolves\" (a total failure reported as done).\n- RX_STOP: \"kept going\", \"carried on\", \"break out\" (the answer said the loop caught the error and kept going).\n- RX_STATE: \"loses every result\", \"append each\", \"as it completes\".\n- clean-retry-counted-key: a double charge said of a caller or in a hypothetical (\"could\", \"can\") is not a finding on the pasted code.\n- clean-atomic-marker: \"prevents duplicate invoices\" is not a finding.\n- clean-fix-loop-tests: the missing-limit check now means a limit on iterations, not \"no spend limit\".\n- clean-paid-batch-stops: the \"no cap or limit\" check is dropped (a spend cap is the neighbour skill spend-guard-review).\n- clean-readonly-agent: the \"no approval or human\" check is dropped for a read-only report writer; the \"no cap or limit\" check stays.\n- clean-parallel-own-files: overwrite, clobber and corrupt count only when said of the workers; \"race\" and \"lost update\" still count.\n- Optional extra codes (a real second defect the skill may report): NO-STATE on no-stop-paid-batch and parallel-counter-row, SILENCE-IS-SUCCESS on no-stop-mail-batch.\n\nSetups changed because the paste really carried a second defect (NEEDS RE-RUN, both sides, not counted): clean-claim-then-act (post claim made conditional, a failed post leaves the queue), clean-fix-loop-tests (work in a scratch worktree, cleanup on every path), clean-atomic-marker (no counter that can over-count), clean-paid-batch-stops (ledger written before the payment), clean-monitor-per-source (an empty array is not an answer), clean-approved-deletes (the applied marker keyed on the proposal), not-idempotent-cron-post (a failed post is marked failed, not put back), overbroad-token-scopes (the helpdesk answer is checked), hard-refusal-continues-and-green (results saved per image, skipped on rerun).\n\nReal failures kept: Sonnet added silence on a retry loop, no-state on a prompt loop that writes its result, no-state on a newsletter claim, a repeat-save finding on a job with an unpasted helper; Haiku added approval, silence or no-state findings on loops that only read, propose or draft.\n\nPrice: left open at this point; see the final measurement below.\n\n## Re-run of 9 setups, read by hand (2026-10-18, evening)\n\nBefore the hand reading (compare-baseline, all 30 with both result files): Sonnet 30/30 with the skill, 25/30 without; Haiku 30/30 with, 25/30 without.\n\nEvery failure was read by hand, both sides, both models:\n- clean-claim-then-act — FLAWED CASE again: the bare Sonnet answer and the Sonnet answer with the skill both found that a refusal repeating for every post (expired token, quota) marked one approved post failed per tick until the queue was burned, and that releasing the minute's claim let a second delivery post the next item in the same minute. Rewritten: the job is paused (a row in paused_tasks, read first) and an alert is sent on the first failed post, the claim is no longer released, and old claims are deleted. NEEDS RE-RUN.\n- clean-paid-batch-stops — FLAWED CASE again: the log line written before the payment also marked a definite refusal as bought (never retried), a slug listed twice was paid twice, a throw from pay() ended with no report, and the approval was only the file name. Rewritten on a database: rows with approved_by, a claim to 'paying' before the payment, gone/bought after it, any refusal or throw stops the batch and leaves the row in 'paying' for a person, and a run does not start while a row is left in 'paying'. NEEDS RE-RUN.\n- clean-approved-deletes — FLAWED CASE: Haiku with the skill found that chained proposals (B→C applied before A→B) move orders to a customer already deleted. Rewritten: self-pairs are not selected, and a proposal whose winner no longer exists is marked -1 and printed. An independent review (Fable, at the owner's request) then found two more real defects that both bare models had raised: the cap was cut BEFORE the insert, so pairs already proposed used it up and later duplicates were never proposed, and a second approved pair with the same loser was marked applied with nothing moved. Fixed: the cap counts only NEW proposals (rowcount), and both customers must still exist. Proven by running the paste on an in-memory SQLite (cap with a repeated pair, chains in both orders, a mutual pair, the same loser twice, a self-pair, a second run: 0 orders on a deleted customer). The paste is data only: it travels to `claude -p` with no tools and is never executed by the tester. NEEDS RE-RUN. (The bare Haiku answer, \"no approval gate in the pasted code\", was a real failure: the paste shows the approved flag.)\n- clean-monitor-per-source, bare Sonnet — check too strict: \"every failure is swallowed with no record of why\" is a note on the log (the answer says the alert still fires). Widened for both sides: \"swallowed\" followed by record, reason, why or log is not a finding.\n- hard-refusal-continues-and-green, bare Sonnet and Haiku — check too strict: both found both defects (\"it reports success when everything failed\", \"a failed batch reports success\"). RX_SILENCE widened for both sides with \"failed batch/run/job … reports success/done/finished\" and \"success when everything/all/nothing…\"; a neutral \"the batch reports done\" still fails (control).\n- clean-parallel-own-files, bare Sonnet (first run) — \"Promise.race\" matched \\brace\\b: widened (not after a dot). The remaining hit, two OVERLAPPING RUNS clobbering each other's files, is a claim about runs, not workers: widened for \"overruns / a second, another, overlapping, two, next run\". \"The parallel runs … clobber each other\" still fails (control).\n- clean-retry-counted-key, bare Haiku (first run) — \"so a double charge or stuck charge cannot be traced\" is about the log: widened (a double charge followed by \"cannot be traced/seen/detected/reconciled\").\n- clean-claim-then-act checks rewritten with the new paste: a duplicate DELIVERY may be named, a duplicate POST is the false finding; \"prevents double-posting\" is not a finding; \"if that constraint is missing\" is a conditional about an unpasted schema (only for \"missing\"; \"if …, there is no idempotency\" still fails).\n- Optional second codes (with the skill, both models, true by the skill's own definitions): NO-STOP-ON-FAILURE on not-idempotent-cron-post (the queue burned one post per tick), NO-STATE on hard-refusal-continues-and-green (the log line written after the paid image).\n- Real format failures kept (with the skill): Haiku NOT-IDEMPOTENT on a prompt listed twice (no repeated trigger in the paste), Haiku SILENCE-IS-SUCCESS on overbroad-token-scopes (a failed call already throws; an empty week is an answer).\n- control.mjs: one more pair per widening — the defensible answer passes, the realistic wrong one next to it fails.\n- Re-run of the 3 rewritten controls (2026-10-18, 6+6 runs, results-2026-10-18-3.md and -baseline-3.md): with the skill Sonnet 3/3 by format, Haiku 1/3 (it added SILENCE-IS-SUCCESS for the marker written before the post, and NO-STOP-ON-FAILURE for a run racing the pause — real failures by the skill's own rules); bare Sonnet 3/3, bare Haiku 2/3 on content. The bare Haiku miss was \"no approval date to check against\" (asks for an expiry, does not say the approval is missing): widened for both sides, with a control pair; the mutation that removes the widening turns control red.\n\n## Final measurement (2026-10-18)\n\nContent (checksBase on both sides, all 30 setups, latest run of each): Sonnet 30/30 with the skill, 30/30 without; Haiku 30/30 with, 30/30 without. Sonnet gain 0. By the skill's own format: Sonnet 26/30, Haiku 17/30.\nPrice by the table: Sonnet gain 0 and Haiku gain 0 → 10000 ($0.01). The value of the skill is the fixed coded answer and the \"only what is pasted\" discipline, not finding problems a bare model misses.\nFAQ \"Does it help Claude Sonnet?\" now answers No with these numbers; suggested price 10000, the seo description ends with \"Pay $0.01 once, in USDC.\".\n"}]}