{"slug": "my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with", "title": "My Hermes agent's stop condition was a 40-line if/elif chain. I replaced it with 3 lines.", "summary": "A developer replaced a 40-line if/elif chain for stopping a Hermes research agent with a three-line solution using the new `agent-loop-stop` library. The library provides composable stop conditions like `after_n_turns()`, `cost_exceeds()`, and `response_contains()`, which can be combined with operators or functions. The package, which requires only the Python standard library, allows different agents to use named, reusable stop configurations.", "body_md": "*This is a submission for the Hermes Agent Challenge.*\n\nMy Hermes research agent's stop logic had grown into a 40-line if/elif block. Stop after 20 turns. Stop if cost exceeds $2. Stop if the response contains \"FINAL ANSWER\". Stop if the last tool called was \"write_summary\". Each condition was written out longhand, tested independently, and hard to reuse across different agents.\n\nI extracted the pattern into `agent-loop-stop`\n\n.\n\n``` python\nfrom agent_loop_stop import any_of, after_n_turns, cost_exceeds, response_contains\n\nstopper = any_of(\n    after_n_turns(20),\n    cost_exceeds(2.00),\n    response_contains(\"FINAL ANSWER\"),\n)\n\nfor turn in range(1, 100):\n    response = call_llm(messages)\n    state = {\"turn\": turn, \"cost_usd\": running_cost, \"response\": response.text}\n    if stopper.check(state):\n        break\n```\n\nThat's it. `stopper.check(state)`\n\nreturns True when any condition fires. The state dict can have whatever you want in it — built-in conditions read well-known keys.\n\n```\nafter_n_turns(20)                           # state[\"turn\"] >= 20\ncost_exceeds(2.00)                          # state[\"cost_usd\"] > 2.00\nresponse_contains(\"FINAL ANSWER\")          # case-insensitive substring\nlast_tool_was(\"write_summary\")             # state[\"last_tool\"] == name\ncustom(lambda s: s.get(\"retries\") > 3)    # any callable\nalways()                                   # always True (testing)\nnever()                                    # always False (placeholder)\n# Stop when either fires\nc = after_n_turns(20) | cost_exceeds(1.00)\n\n# Stop only when both fire\nc = after_n_turns(10) & cost_exceeds(0.50)\n\n# Invert\nc = ~response_contains(\"continue\")\n```\n\nOr use the function form:\n\n```\nany_of(after_n_turns(20), cost_exceeds(2.00), response_contains(\"done\"))\nall_of(after_n_turns(5), cost_exceeds(0.25))\nnegate(response_contains(\"error\"))\n```\n\nBoth styles work identically. The operator form is more concise; the function form is more explicit about what's happening.\n\n``` python\nfrom agent_loop_stop import check_all\n\nresult = check_all(\n    state,\n    {\n        \"turn_limit\": after_n_turns(20),\n        \"cost_limit\": cost_exceeds(2.00),\n        \"done_signal\": response_contains(\"FINAL ANSWER\"),\n    },\n)\n\nif result.stopped:\n    log.info(f\"Agent stopped. Reason(s): {result.triggered}\")\n    # [\"turn_limit\"] or [\"done_signal\"] or [\"cost_limit\", \"done_signal\"]\n```\n\n`check_all`\n\nchecks every named condition individually and returns a `StopResult`\n\nwith which ones triggered. This is what I log in my Hermes agent — if I see \"turn_limit\" fired instead of \"done_signal\", that means the agent ran out of turns without finishing.\n\n```\nc = custom(lambda s: len(s.get(\"tool_calls_this_turn\", [])) > 5)\n```\n\nOr subclass for reusable predicates:\n\n``` python\nfrom agent_loop_stop import StopCondition\n\nclass TokenBudgetStop(StopCondition):\n    def __init__(self, limit: int):\n        self._limit = limit\n\n    def check(self, state):\n        return state.get(\"tokens_used\", 0) > self._limit\n\nstopper = any_of(after_n_turns(20), TokenBudgetStop(4000))\n```\n\nDifferent Hermes agents have different stop requirements:\n\n```\nSUPERVISOR_STOP = any_of(\n    after_n_turns(50),\n    cost_exceeds(5.00),\n    response_contains(\"SYNTHESIS COMPLETE\"),\n)\n\nWORKER_STOP = any_of(\n    after_n_turns(15),\n    cost_exceeds(0.50),\n    last_tool_was(\"submit_findings\"),\n)\n```\n\nNamed, reusable, composable. Each agent gets its own stop config that says exactly what it means.\n\nStandard library only: `dataclasses`\n\n, `typing`\n\n. No third-party packages.\n\n```\npip install agent-loop-stop\n```\n\n", "url": "https://wpnews.pro/news/my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with", "canonical_source": "https://dev.to/mukundakatta/my-hermes-agents-stop-condition-was-a-40-line-ifelif-chain-i-replaced-it-with-3-lines-3fej", "published_at": "2026-05-25 21:21:12+00:00", "updated_at": "2026-05-25 21:33:48.402929+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-products", "artificial-intelligence", "large-language-models"], "entities": ["Hermes"], "alternates": {"html": "https://wpnews.pro/news/my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with", "markdown": "https://wpnews.pro/news/my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with.md", "text": "https://wpnews.pro/news/my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with.txt", "jsonld": "https://wpnews.pro/news/my-hermes-agent-s-stop-condition-was-a-40-line-if-elif-chain-i-replaced-it-with.jsonld"}}