惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
D
DataBreaches.Net
M
MIT News - Artificial intelligence
量子位
N
Netflix TechBlog - Medium
The Cloudflare Blog
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
B
Blog
博客园_首页
博客园 - Franky
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
MongoDB | Blog
MongoDB | Blog
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
H
Help Net Security
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
My Hermes agent's stop condition was a 40-line if/elif ch...
Mukunda Rao · 2026-05-26 · via DEV Community

Mukunda Rao Katta

Hermes Agent Challenge Submission: Write About Hermes Agent

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.

Three lines replace forty

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

Enter fullscreen mode Exit fullscreen mode

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.

All built-in conditions

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)

Enter fullscreen mode Exit fullscreen mode

Compose with operators

# 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")

Enter fullscreen mode Exit fullscreen mode

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"))

Enter fullscreen mode Exit fullscreen mode

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

Diagnostic: which condition fired?

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"]

Enter fullscreen mode Exit fullscreen mode

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.

Custom conditions

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

Enter fullscreen mode Exit fullscreen mode

Or subclass for reusable predicates:

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))

Enter fullscreen mode Exit fullscreen mode

Per-agent stop configs

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"),
)

Enter fullscreen mode Exit fullscreen mode

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

Zero dependencies

Standard library only: dataclasses, typing. No third-party packages.

pip install agent-loop-stop

Enter fullscreen mode Exit fullscreen mode

Repo: https://github.com/MukundaKatta/agent-loop-stop