摘要
Hermes Agent 近期版本更新的核心,不只是新增 Provider 和插件,而是向"持久化 Agent 工作流"演进。本文结合 Kanban 教程,解析任务编排、运行历史、失败恢复与多角色协作,并用 Python 实现一个可运行的轻量级 Agent Kanban 示例。
背景介绍
Hermes Agent 最近连续发布了 v0.1.1 与 v0.1.12 两个重要版本。表面上看,这两次更新包含大量功能:新的 CLI、Provider 扩展、Dashboard 增强、后台 Agent、更多插件与多模态能力。但从工程视角看,真正值得关注的是 Hermes 正在从"聊天式 Agent"转向"持久化 Agent 协作系统"。
v0.1.1:Interface Release
v0.1.1 被称为 Interface Release,重点是交互层和 Provider 架构升级:
- 基于 React + Ink 重构交互式 CLI;
- 支持 sticky composer、实时流式输出、状态栏、浅色主题;
- 子 Agent 重启时具备更高可见性;
- Provider 通信拆分为更清晰的可插拔 Transport Layer;
- 原生支持 AWS Bedrock Converse API;
- 扩展 NVIDIA、Google Gemini CLI OAuth、Vercel Gateway 等推理路径;
- 增加 QQ Bot Adapter、Shell Hooks、Direct Delivery、Slash Steer 等能力。
这说明 Hermes 开始把 Agent 系统拆解为更标准的接口层、传输层和插件层。
v0.1.12:Curator Release
v0.1.12 被称为 Curator Release,重点是后台自治能力:
- Autonomous Curator:后台 Agent 可定期评分、裁剪、合并技能库;
- Self-improvement Loop 增强,支持基于 Rubric 的技能更新;
- 子任务可继承父级运行时的 Provider、Model 与 Credential;
- 新增 Azure AI Foundry、MiniMax、Tencent Token Hub、LM Studio 等 Provider;
- Dashboard 增加 Models Tab;
- 改进多模态图片路由、Gateway 媒体处理;
- 集成 Spotify、Google Meet、ComfyUI、TouchDesigner MCP;
- 冷启动性能提升约 57%。
这些变化共同指向一个方向:Hermes 不再只是"问一句、答一句"的 Agent,而是在构建一个可持续运行、可观测、可恢复的 Agent 工作流系统。
核心原理
Kanban:Agent 工作流的状态载体
Hermes 文档中新加入的 Kanban 教程,是理解其演进方向的关键。传统 Agent 系统通常依赖 Chat Log 保存上下文,但聊天记录并不适合作为工程任务的状态管理系统。
Kanban 的价值在于将任务状态显式结构化:
ready:任务可执行;claimed:任务已被某个 Worker 获取;blocked:任务执行失败,需要人工或上游修正;completed:任务完成;crashed:Worker 中途崩溃;gave_up:超过失败阈值后熔断。
这与真实研发流程非常接近。例如:
- PM Agent 编写需求文档;
- Engineer Agent 根据需求实现功能;
- Reviewer Agent 审查实现;
- 如果缺少密码强度校验或重置链接不是一次性 Token,则任务进入
blocked; - Engineer 根据反馈重试;
- 第二次执行成功,并记录变更文件、测试结果与元数据。
重点是:重试历史不再隐藏在聊天记录中,而是作为结构化数据保存。
运行历史与元数据
Hermes Kanban 的一个关键设计是 Run History。每一次执行都应记录:
- 执行角色;
- 开始与结束时间;
- 输出摘要;
- 失败原因;
- 修改文件;
- 测试结果;
- 上一次失败反馈。
这样 Reviewer 在审查时,可以先查看实现摘要和变更文件,再进入 Diff。对于长周期 Agent 工作流,这比单纯依赖上下文窗口更加可靠。
熔断与崩溃恢复
真实 Worker 会失败,常见原因包括:
- API Key 缺失;
- 网络异常;
- 模型限流;
- 内存不足;
- 子进程崩溃。
Kanban 调度器需要两类保护:
- Circuit Breaker :连续启动失败超过阈值后,将任务标记为
gave_up,避免无限重试; - Crash Recovery :Worker 中途崩溃时,释放任务 Claim,将任务重新放回
ready,交给新 Worker 执行。
这正是 Hermes Kanban 教程强调的工程化价值:任务状态、失败记录、重试历史和人工干预点必须可见。
技术资源与工具选型
在多 Agent 工作流中,模型能力与 API 稳定性会直接影响执行质量。我个人常用的 AI 开发平台是薛定猫 AI(xuedingmao.com),它采用 OpenAI 兼容接口,适合快速接入多模型实验环境。
其技术价值主要体现在:
- 聚合 500+ 主流大模型,包括 GPT-5.4、Claude 4.6、Gemini 3.1 Pro 等;
- 新模型实时首发,开发者可以第一时间验证前沿模型 API;
- 使用统一 OpenAI Compatible 接口,降低多模型切换与集成成本;
- 对 Agent 系统开发来说,统一
base_url + api_key + model的接入方式非常适合做 Provider 抽象。
下面示例默认使用 claude-opus-4-6。该模型在长上下文理解、复杂任务规划、代码审查、多步骤推理方面表现很强,适合承担 PM、Engineer、Reviewer 这类角色化 Agent。
实战演示:用 Python 实现轻量级 Agent Kanban
下面代码实现一个简化版 Kanban 调度器:
- 使用 SQLite 保存任务与运行历史;
- 使用 OpenAI 兼容接口调用模型;
- 模拟 PM → Engineer → Reviewer 流程;
- 支持失败记录、阻塞状态与重试;
- 包含基础 Circuit Breaker。
安装依赖
bash
pip install openai
完整代码
python
import os
import json
import sqlite3
import time
from datetime import datetime
from typing import Optional
from openai import OpenAI
DB_PATH = "agent_kanban.db"
MODEL_NAME = "claude-opus-4-6"
MAX_FAILURES = 3
def now() -> str:
return datetime.utcnow().isoformat()
def get_client() -> OpenAI:
"""
薛定猫 AI 采用 OpenAI 兼容模式:
base_url + api_key + model 即可完成接入。
"""
api_key = os.getenv("XUEDINGMAO_API_KEY")
if not api_key:
raise RuntimeError("Missing XUEDINGMAO_API_KEY")
return OpenAI(
api_key=api_key,
base_url="https://xuedingmao.com/v1"
)
def init_db() -> None:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
role TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'ready',
input TEXT NOT NULL,
output TEXT,
failure_count INTEGER NOT NULL DEFAULT 0,
parent_id INTEGER,
metadata TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
role TEXT NOT NULL,
status TEXT NOT NULL,
summary TEXT,
error TEXT,
metadata TEXT,
started_at TEXT NOT NULL,
ended_at TEXT,
FOREIGN KEY(task_id) REFERENCES tasks(id)
)
""")
conn.commit()
conn.close()
def create_task(title: str, role: str, input_text: str, parent_id: Optional[int] = None) -> int:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO tasks(title, role, status, input, parent_id, metadata, created_at, updated_at)
VALUES (?, ?, 'ready', ?, ?, '{}', ?, ?)
""", (title, role, input_text, parent_id, now(), now()))
task_id = cur.lastrowid
conn.commit()
conn.close()
return task_id
def claim_task() -> Optional[dict]:
"""
获取一个 ready 任务,并将其状态改为 claimed。
单机 SQLite 场景下可满足本地 Agent 编排。
"""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("""
SELECT * FROM tasks
WHERE status = 'ready'
ORDER BY id ASC
LIMIT 1
""")
row = cur.fetchone()
if not row:
conn.close()
return None
cur.execute("""
UPDATE tasks
SET status = 'claimed', updated_at = ?
WHERE id = ?
""", (now(), row["id"]))
conn.commit()
conn.close()
return dict(row)
def call_agent(role: str, task_input: str) -> str:
client = get_client()
system_prompt = f"""
你是一个专业的软件研发 Agent,当前角色是:{role}。
请输出结构化结果,包含:
1. summary:执行摘要
2. details:详细内容
3. metadata:JSON 格式元数据,例如 changed_files、tests、risk
"""
response = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": system_prompt.strip()},
{"role": "user", "content": task_input}
],
temperature=0.2
)
return response.choices[0].message.content
def record_run(task_id: int, role: str, status: str, summary: str = "", error: str = "", metadata: Optional[dict] = None) -> None:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO runs(task_id, role, status, summary, error, metadata, started_at, ended_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
task_id,
role,
status,
summary,
error,
json.dumps(metadata or {}, ensure_ascii=False),
now(),
now()
))
conn.commit()
conn.close()
def update_task(task_id: int, status: str, output: str = "", metadata: Optional[dict] = None) -> None:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
UPDATE tasks
SET status = ?, output = ?, metadata = ?, updated_at = ?
WHERE id = ?
""", (
status,
output,
json.dumps(metadata or {}, ensure_ascii=False),
now(),
task_id
))
conn.commit()
conn.close()
def increase_failure(task_id: int, error: str) -> None:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT failure_count FROM tasks WHERE id = ?", (task_id,))
failure_count = cur.fetchone()[0] + 1
if failure_count >= MAX_FAILURES:
status = "gave_up"
else:
status = "ready"
cur.execute("""
UPDATE tasks
SET failure_count = ?, status = ?, updated_at = ?
WHERE id = ?
""", (failure_count, status, now(), task_id))
conn.commit()
conn.close()
record_run(
task_id=task_id,
role="dispatcher",
status=status,
summary="任务执行失败,已触发失败计数",
error=error,
metadata={"failure_count": failure_count}
)
def worker_loop_once() -> bool:
task = claim_task()
if not task:
return False
task_id = task["id"]
role = task["role"]
try:
result = call_agent(role, task["input"])
# 示例:Reviewer 发现缺陷时,将任务阻塞
if role == "reviewer" and "缺少" in result:
update_task(
task_id,
"blocked",
result,
{"reason": "review_failed"}
)
record_run(task_id, role, "blocked", result)
else:
update_task(
task_id,
"completed",
result,
{"model": MODEL_NAME}
)
record_run(task_id, role, "completed", result)
except Exception as exc:
increase_failure(task_id, str(exc))
return True
def print_runs() -> None:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("""
SELECT r.id, r.task_id, r.role, r.status, r.summary, r.error, r.metadata
FROM runs r
ORDER BY r.id ASC
""")
for row in cur.fetchall():
print("=" * 80)
print(f"Run #{row['id']} | Task {row['task_id']} | {row['role']} | {row['status']}")
if row["summary"]:
print(row["summary"][:500])
if row["error"]:
print("ERROR:", row["error"])
print("META:", row["metadata"])
conn.close()
if __name__ == "__main__":
init_db()
pm_task = create_task(
title="编写密码重置功能需求文档",
role="pm",
input_text="请为一个 SaaS 系统设计密码重置功能需求,包含安全约束、验收标准和边界场景。"
)
engineer_task = create_task(
title="实现密码重置功能",
role="engineer",
input_text="根据 PM 需求实现密码重置功能,要求包含密码强度校验、一次性重置链接和测试说明。",
parent_id=pm_task
)
reviewer_task = create_task(
title="审查密码重置实现",
role="reviewer",
input_text="请审查工程实现是否满足安全要求,重点检查密码强度、Token 单次使用、过期时间和测试覆盖。",
parent_id=engineer_task
)
while worker_loop_once():
time.sleep(1)
print_runs()
运行前设置 API Key:
bash
export XUEDINGMAO_API_KEY="你的 API Key"
python kanban_agent.py
这个示例虽然简化,但已经体现 Hermes Kanban 的核心思想:任务不是聊天上下文的一部分,而是可查询、可恢复、可审计的数据对象。
注意事项
1. Kanban 不适合所有 Agent 任务
如果只是让另一个 Agent 快速回答一个问题,直接 Delegate Task 更轻量。Kanban 更适合:
- 长周期任务;
- 多角色协作;
- 需要审查与重试;
- 需要保留历史;
- 需要人工介入;
- 周期性运营或监控任务。
2. 单机边界需要明确
Hermes 文档中提到,Kanban 设计上更像单主机本地协调层,通常基于本地 SQLite 文件和同机 Worker。它不是多服务器共享的企业级工作流引擎。
如果要扩展到分布式场景,需要额外引入:
- PostgreSQL / MySQL;
- 分布式锁;
- 消息队列;
- Worker 心跳;
- 任务租约;
- 权限模型。
3. Dashboard 不应随意暴露
如果 Dashboard 绑定到 0.0.0.0,插件路由可能从网络访问。对于普通开发环境,保持 localhost 是更安全的默认选择。
4. 失败历史必须结构化
长任务 Agent 系统最怕"失败但不可见"。建议至少记录:
- 输入;
- 输出;
- 错误堆栈;
- 模型名称;
- 运行时间;
- 重试次数;
- 阻塞原因;
- 人工处理记录。
总结
Hermes Agent v0.1.1 与 v0.1.12 的更新,本质上是在构建一个更工程化的 Agent Runtime。Kanban 教程尤其重要,因为它把 Agent 的执行过程从"聊天记录"提升为"持久化状态机"。
对于真实开发场景,Agent 系统不能只关注模型回答质量,还必须处理任务状态、角色协作、失败恢复、审查反馈和运行审计。Hermes Kanban 的设计方向,正是 AI Agent 从 Demo 走向工程落地的关键一步。
#AI #大模型 #Python #机器学习 #技术实战