Interrupt

Hermes Agent Loop 中止机制全解析 #

一、核心数据结构:中断信号是线程安全的 #

整个中断机制的核心在 tools/interrupt.py,它实现了一个线程级别的中断信号表

# tools/interrupt.py
_interrupted_threads: set[int] = set()   # 被标记中断的线程 ID 集合
_lock = threading.Lock()

def set_interrupt(active: bool, thread_id: int | None = None):
    """将某个线程标记为中断/清除中断"""
    tid = thread_id or threading.current_thread().ident
    with _lock:
        if active:
            _interrupted_threads.add(tid)
        else:
            _interrupted_threads.discard(tid)

def is_interrupted() -> bool:
    """当前线程是否被标记中断"""
    return threading.current_thread().ident in _interrupted_threads

关键设计:按线程隔离。Gateway 中多个 agent 会话并发运行在同一进程中,中断一个会话不会影响其他会话。

二、Agent 侧的中断接口:interrupt() 方法 #

AIAgent 类(run_agent.py)暴露了两个关键方法:

# run_agent.py:2237
def interrupt(self, message: str = None):
    """请求中断当前工具调用循环"""
    self._interrupt_requested = True     # 标志位
    self._interrupt_message = message    # 附带的新消息
    # 向当前执行线程发送中断信号
    _set_interrupt(True, self._execution_thread_id)
    # 传播给所有子 agent(delegate_task 创建的子任务)
    for child in self._running_children:
        child.interrupt(message)

clear_interrupt() 做相反操作:清除标志、清除线程信号、清理子 agent。

三、主循环中的中断检查点 #

主循环在 agent/conversation_loop.py:run_conversation() 中,有多层中断检测:

# 1. 循环入口检测
while (api_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0) \
        or agent._budget_grace_call:
    
    # ★ 每次循环迭代开始都检查
    if agent._interrupt_requested:
        interrupted = True
        _turn_exit_reason = "interrupted_by_user"
        break
    
    api_call_count += 1
    
    # ... 调用 LLM、执行工具调用、处理结果 ...
    
    # 2. 工具调用结果处理中也检查(约 1569 行)
    if agent._interrupt_requested:
        break
    
    # 3. 资源清理前检查(约 2706 行)
    if agent._interrupt_requested:
        break

四、API 调用级别的中断(最精妙的部分) #

API 调用在 agent/chat_completion_helpers.py 中实现了可中断的后台线程模式

def interruptible_api_call(agent, api_kwargs):
    """在后台线程中执行 API 调用,允许主循环检测中断"""
    result = {"response": None, "error": None}
    
    def _call():
        """实际 API 调用在子线程中运行"""
        try:
            client = _set_request_client(create_client(...))
            result["response"] = client.chat.completions.create(**api_kwargs)
        except Exception as e:
            result["error"] = e
    
    # ★ 启动后台线程
    worker = threading.Thread(target=_call)
    worker.start()
    
    # ★ 主线程轮询中断信号
    while worker.is_alive():
        if agent._interrupt_requested:
            # 关闭 HTTP 连接,让后台线程的 recv() 收到 EPIPE
            _close_request_client_once("interrupt_abort")
            break
        worker.join(timeout=0.5)  # 每 0.5 秒检查一次
    
    return result

设计要点

  • API 调用在独立线程中执行
  • 主线程每 0.5 秒检查一次 _interrupt_requested
  • 中断时通过关闭 HTTP socket 让后台线程的 recv() 阻塞提前返回
  • 避免了直接 Thread._stop() 导致的资源泄漏(FD 回收、SSL BIO 写坏等)

流式调用(interruptible_streaming_api_call)同理,在 SSE 事件循环中也会检查中断信号。

五、工具执行中的中断 #

工具在执行长操作时(如 terminal 后台进程、网络请求),通过 is_interrupted() 主动检测:

# 任何工具内部都可以这样检测
from tools.interrupt import is_interrupted

if is_interrupted():
    return json.dumps({"output": "[interrupted]", "returncode": 130})

六、触发中断的用户入口 #

触发方式 代码路径 说明
CLI 中 Ctrl+C cli.py 信号处理器 → agent.interrupt() 键盘中断
/new/reset cli.py / gateway/run.py 新会话会先 interrupt 当前 agent
/stop gateway 的 Level-2 handler 中止后台进程 + interrupt agent
Gateway 收到新消息 gateway/run.py:18617 用户发来新消息 → interrupt 当前 turn → 处理新消息
超时自动中断 gateway/run.py:18858 超时后自动 interrupt + 清理
delegate_task 子任务 run_agent.py:2299 父 agent 中断时传播给所有子 agent

七、中断后的处理流程 #

中断触发
  ↓
agent._interrupt_requested = True
  ↓
_set_interrupt(True, execution_thread_id)
  ↓
传播给所有子 agent
  ↓
主循环在下一个检查点 break
  ↓
记录 _turn_exit_reason = "interrupted_by_user"
  ↓
执行资源清理(VM、浏览器、终端进程)
  ↓
clear_interrupt() 清理状态
  ↓
如果附带新消息 → 作为下一条 user message 继续
如果 /new → 开启全新会话

八、与普通 /steer 的区别 #

Hermes 还提供了 /steer 命令(注入消息但不中断):

def steer(self, text: str):
    """注入消息到下一个 tool result 中,不打断当前执行"""
    self._steer_message = text
    # 注意:这里不调用 interrupt()

/steer 适用于“想给建议但不打断当前工具调用”的场景,而 /new 和新消息触发的是软中断(soft interrupt),会优雅地中止当前循环。

总结 #

Hermes 的中止机制是一个线程安全的标志位 + 多检查点轮询 + HTTP 连接优雅关闭的组合方案,保证了即使 API 调用卡住或工具执行耗时,也能在数秒内安全退出,且不影响其他并发会话。