# My Hermes agent's stop condition was a 40-line if/elif chain. I replaced it with 3 lines.

> Source: <https://dev.to/mukundakatta/my-hermes-agents-stop-condition-was-a-40-line-ifelif-chain-i-replaced-it-with-3-lines-3fej>
> Published: 2026-05-25 21:21:12+00:00

*This is a submission for the Hermes Agent Challenge.*

My 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.

I extracted the pattern into `agent-loop-stop`

.

``` python
from agent_loop_stop import any_of, after_n_turns, cost_exceeds, response_contains

stopper = any_of(
    after_n_turns(20),
    cost_exceeds(2.00),
    response_contains("FINAL ANSWER"),
)

for turn in range(1, 100):
    response = call_llm(messages)
    state = {"turn": turn, "cost_usd": running_cost, "response": response.text}
    if stopper.check(state):
        break
```

That's it. `stopper.check(state)`

returns True when any condition fires. The state dict can have whatever you want in it — built-in conditions read well-known keys.

```
after_n_turns(20)                           # state["turn"] >= 20
cost_exceeds(2.00)                          # state["cost_usd"] > 2.00
response_contains("FINAL ANSWER")          # case-insensitive substring
last_tool_was("write_summary")             # state["last_tool"] == name
custom(lambda s: s.get("retries") > 3)    # any callable
always()                                   # always True (testing)
never()                                    # always False (placeholder)
# Stop when either fires
c = after_n_turns(20) | cost_exceeds(1.00)

# Stop only when both fire
c = after_n_turns(10) & cost_exceeds(0.50)

# Invert
c = ~response_contains("continue")
```

Or use the function form:

```
any_of(after_n_turns(20), cost_exceeds(2.00), response_contains("done"))
all_of(after_n_turns(5), cost_exceeds(0.25))
negate(response_contains("error"))
```

Both styles work identically. The operator form is more concise; the function form is more explicit about what's happening.

``` python
from agent_loop_stop import check_all

result = check_all(
    state,
    {
        "turn_limit": after_n_turns(20),
        "cost_limit": cost_exceeds(2.00),
        "done_signal": response_contains("FINAL ANSWER"),
    },
)

if result.stopped:
    log.info(f"Agent stopped. Reason(s): {result.triggered}")
    # ["turn_limit"] or ["done_signal"] or ["cost_limit", "done_signal"]
```

`check_all`

checks every named condition individually and returns a `StopResult`

with 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.

```
c = custom(lambda s: len(s.get("tool_calls_this_turn", [])) > 5)
```

Or subclass for reusable predicates:

``` python
from agent_loop_stop import StopCondition

class TokenBudgetStop(StopCondition):
    def __init__(self, limit: int):
        self._limit = limit

    def check(self, state):
        return state.get("tokens_used", 0) > self._limit

stopper = any_of(after_n_turns(20), TokenBudgetStop(4000))
```

Different Hermes agents have different stop requirements:

```
SUPERVISOR_STOP = any_of(
    after_n_turns(50),
    cost_exceeds(5.00),
    response_contains("SYNTHESIS COMPLETE"),
)

WORKER_STOP = any_of(
    after_n_turns(15),
    cost_exceeds(0.50),
    last_tool_was("submit_findings"),
)
```

Named, reusable, composable. Each agent gets its own stop config that says exactly what it means.

Standard library only: `dataclasses`

, `typing`

. No third-party packages.

```
pip install agent-loop-stop
```


