Hermes 调度中心:spawn 多 Agent 与 terminal + delegate_task 编排实战
简介
前文我们搞懂了架构拓扑,上一篇我们确定了任务-工具选型矩阵。现在到了最关键的一步:如何用 Hermes Agent 作为调度中心,把 Claude Code、Codex、OpenCode 这些工具编排成一支协同作战的队伍?
Hermes Agent 拥有两个核心能力来实现这一目标:
terminal工具:spawn 子进程、执行命令、捕获输出、管理生命周期delegate_task能力:将子任务委派给专门的 subagent 处理
本篇将通过完整的实战代码,演示如何用这两个能力构建一个生产级的多 Agent 调度系统。
2. 进程管理基础:terminal 工具全解
2.1 terminal 工具能力矩阵
| 能力 | 参数 | 说明 |
|---|---|---|
| 前台执行 | command |
默认模式,命令完成后返回 |
| 后台执行 | background=true |
启动长驻进程,返回 session_id |
| 工作目录 | workdir |
指定命令执行的工作目录 |
| 超时控制 | timeout |
最大等待时间 |
| PTY 模式 | pty=true |
交互式终端,支持 REPL |
| 进程管理 | process(action=...) |
poll/wait/log/kill/submit |
| 输出捕获 | process(action="log") |
获取进程完整输出 |
| 输入交互 | process(action="submit") |
向进程发送输入 |
2.2 前台执行:快速命令
# 场景:快速检查工具可用性
terminal(command="claude --version")
# → claude-code 1.0.x
terminal(command="codex --version")
# → codex-cli 2.0.x
terminal(command="opencode --version")
# → opencode 0.3.x
# 场景:检查项目结构
terminal(command="find src -name '*.py' | head -20", workdir="/opt/data/my-project")2.3 后台执行:长驻进程管理
# 场景:启动 Claude Code 作为后台 Agent
result = terminal(
command='claude "Review all Python files in src/ for security issues"',
background=True,
workdir="/opt/data/my-project",
timeout=600, # 最多 10 分钟
)
# → session_id: sess_abc123
# 等待完成
process(action="wait", session_id="sess_abc123", timeout=600)
# 获取输出
output = process(action="log", session_id="sess_abc123")3. Spawn Claude Code:深度编码任务
3.1 基础 Spawn
# 方式一:直接命令行调用
terminal(
command='claude "Generate a REST API endpoint for user registration '
'with email validation, password hashing, and JWT token creation. '
'Use FastAPI and follow the existing project patterns."',
workdir="/opt/data/my-project",
timeout=300,
)
# 方式二:带系统提示词
terminal(
command='claude -p "You are an expert Python developer. '
'Always write PEP8-compliant code with type hints. '
'Include docstrings and error handling. '
'Generate a REST API endpoint for user registration..."',
workdir="/opt/data/my-project",
timeout=300,
)3.2 交互式 Claude Code 会话
# 使用 PTY 模式进行交互式会话
pty_result = terminal(
command="claude",
pty=True,
background=True,
workdir="/opt/data/my-project",
)
session_id = pty_result["session_id"]
# 发送任务指令
process(action="submit", session_id=session_id,
data="Create a data validation module using Pydantic")
# 等待响应并获取输出
process(action="wait", session_id=session_id, timeout=120)
output = process(action="log", session_id=session_id, limit=100)
# 可以继续对话
process(action="submit", session_id=session_id,
data="Now add unit tests for all validators")
process(action="wait", session_id=session_id, timeout=120)
final_output = process(action="log", session_id=session_id)
# 完成后关闭
process(action="close", session_id=session_id)3.3 Claude Code 带权限控制
# 在生产环境中,建议限制 Claude Code 的权限
terminal(
command='claude --allowedTools="Edit,Read,Bash(git*,pytest*,python*)" '
'"Implement the user authentication module"',
workdir="/opt/data/my-project",
timeout=300,
)4. Spawn Codex:批量处理任务
4.1 Codex CLI 批量操作
# 场景:批量修复代码风格问题
terminal(
command='codex "Fix all PEP8 violations in the src/ directory. '
'Apply black formatting and isort imports. '
'Do not modify test files."',
workdir="/opt/data/my-project",
timeout=180,
)
# 场景:批量生成类型注解
terminal(
command='codex "Add type hints to all functions in src/services/. '
'Use typing module for complex types. '
'Run mypy after to verify."',
workdir="/opt/data/my-project",
timeout=240,
)4.2 Codex API 批量处理(通过 Python SDK)
# 先安装 OpenAI SDK
terminal(command="pip install openai", timeout=60)
# 然后执行批量处理脚本
write_file(
path="/opt/data/my-project/batch_fix.py",
content="""
import asyncio
from openai import AsyncOpenAI
import os
import glob
async def batch_deprecation_fix():
client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
# 找到所有需要修复的文件
files = glob.glob("src/**/*.py", recursive=True)
deprecated_pattern = "asyncio.get_event_loop()"
fixed_count = 0
for filepath in files:
with open(filepath) as f:
content = f.read()
if deprecated_pattern not in content:
continue
# 调用 Codex 修复
response = await client.responses.create(
model="codex",
input=f"Fix deprecated asyncio usage in:
{content}
Replace asyncio.get_event_loop() with asyncio.get_running_loop().
Return the full fixed file content.",
)
fixed_content = response.output_text
if fixed_content != content:
with open(filepath, 'w') as f:
f.write(fixed_content)
fixed_count += 1
print(f"Fixed: {filepath}")
print(f"Total files fixed: {fixed_count}")
asyncio.run(batch_deprecation_fix())
"""
)
# 执行批量处理脚本
terminal(
command="python batch_fix.py",
workdir="/opt/data/my-project",
timeout=600,
)5. Spawn OpenCode:轻量任务处理
5.1 OpenCode 文档生成
# 场景:批量生成模块文档
terminal(
command='opencode "Generate comprehensive docstrings for all '
'classes and functions in src/models/. '
'Use Google docstring format."',
workdir="/opt/data/my-project",
timeout=120,
)
# 场景:生成 README
terminal(
command='opencode "Generate a README.md for this project. '
'Include: project description, installation instructions, '
'usage examples, API reference, and contribution guidelines. '
"Analyze the project structure first."',
workdir="/opt/data/my-project",
timeout=180,
)5.2 OpenCode 代码审查
# 场景:PR 审查
terminal(
command='opencode "Review the changes in the latest git commit. '
'Focus on: security issues, performance concerns, and '
'code quality. Provide specific line-by-line feedback."',
workdir="/opt/data/my-project",
timeout=120,
)
# 场景:代码复杂度分析
terminal(
command='opencode "Analyze the cyclomatic complexity of all '
'functions in src/. Flag any function with complexity > 10. '
'Suggest refactoring for the most complex ones."',
workdir="/opt/data/my-project",
timeout=120,
)6. delegate_task:子任务委派的艺术
6.1 delegate_task 基础用法
# delegate_task 允许 Hermes Agent 将子任务委派给专门的 subagent
# 适用于需要不同上下文或专长的子任务
# 场景 1:委派代码生成任务
result = delegate_task(
prompt="Generate a FastAPI CRUD API for the User model. "
"Include: create, read, update, delete endpoints. "
"Use SQLAlchemy async. Add request validation with Pydantic. "
"Follow REST best practices.",
# subagent 会自动获得独立的上下文和工具访问权限
)
# → 返回生成的代码和元数据
# 场景 2:委派测试编写
test_result = delegate_task(
prompt="Write comprehensive unit tests for the User API. "
"Cover: happy paths, edge cases, error handling, "
"authentication failures. Use pytest and pytest-asyncio. "
"Aim for > 90% code coverage.",
)
# 场景 3:委派文档编写
doc_result = delegate_task(
prompt="Write API documentation for the User endpoints. "
"Use OpenAPI/Swagger format. Include request/response "
"examples, error codes, and authentication requirements.",
)6.2 delegate_task 与 terminal 的组合编排
# 组合策略:delegate_task 负责智能决策,terminal 负责执行
class TaskOrchestrator:
"""Hermes 调度中心的任务编排器"""
def __init__(self, project_path: str):
self.project_path = project_path
self.task_results = {}
async def execute_feature_pipeline(self, feature_spec: str):
"""端到端的功能开发流水线"""
# Step 1: delegate_task — 架构设计
design = delegate_task(
prompt=f"Design the architecture for this feature: {feature_spec}. "
f"Output: module structure, class diagrams (text), "
f"API contracts, data models.",
)
self.task_results["design"] = design
# Step 2: terminal — 创建目录结构
terminal(
command=f"mkdir -p {self.project_path}/src/features/{{api,services,models}}",
workdir=self.project_path,
)
# Step 3: delegate_task — Claude Code 实现核心逻辑
implementation = delegate_task(
prompt=f"Implement the feature based on this design: {design}. "
f"Use Claude Code. Generate all source files. "
f"Follow existing project patterns.",
)
self.task_results["implementation"] = implementation
# Step 4: terminal — 执行 Claude Code 实现
terminal(
command=f'claude "{implementation.instructions}"',
workdir=self.project_path,
timeout=300,
)
# Step 5: delegate_task — Codex 批量测试生成
test_plan = delegate_task(
prompt="Generate a test plan for the implemented feature. "
"List all test cases needed.",
)
# Step 6: terminal — Codex 执行测试生成
terminal(
command=f'codex "{test_plan.test_generation_prompt}"',
workdir=self.project_path,
timeout=180,
)
# Step 7: terminal — 运行测试验证
test_result = terminal(
command="pytest tests/ -v --cov=src/features "
"--cov-report=term-missing",
workdir=self.project_path,
timeout=120,
)
self.task_results["test_result"] = test_result
# Step 8: delegate_task — OpenCode 文档生成
delegate_task(
prompt="Generate documentation for the new feature module. "
"Include API reference, usage examples, and "
"integration guide.",
)
return self.task_results7. 实战:端到端自动化流水线
7.1 完整编排示例:Bug 修复流水线
# 完整场景:从 Bug 报告到修复提交的全自动化
class BugFixPipeline:
"""自动化 Bug 修复流水线"""
def __init__(self, repo_path: str):
self.repo = repo_path
async def fix_bug(self, bug_report: dict) -> PipelineResult:
"""执行完整的 Bug 修复流程"""
# === Phase 1: 根因分析 (Claude Code) ===
analysis = terminal(
command=f'claude "Analyze this bug report and find the root cause '
f'in the codebase: {bug_report["description"]}. '
f"Relevant module: {bug_report['module']}. "
f'Output: root cause analysis, affected files, fix strategy."',
workdir=self.repo,
timeout=300,
)
affected_files = self.parse_affected_files(analysis.output)
# === Phase 2: 生成修复补丁 (delegate_task → Claude Code) ===
fix_plan = delegate_task(
prompt=f"Generate fix patches for these files: {affected_files}. "
f"Bug root cause: {analysis.output}. "
f"Use Claude Code to implement the fix.",
)
# === Phase 3: 应用修复 (terminal → Claude Code) ===
terminal(
command=f'claude "{fix_plan.fix_instructions}"',
workdir=self.repo,
timeout=300,
)
# === Phase 4: 批量测试验证 (terminal → pytest) ===
test_result = terminal(
command=f"pytest {bug_report['test_path']} -v "
f"--tb=short",
workdir=self.repo,
timeout=120,
)
if not test_result.success:
# === Phase 4b: 修复测试 (delegate_task → Codex) ===
delegate_task(
prompt=f"Fix the failing tests. "
f"Test output: {test_result.output}. "
f"Use Codex for batch test fixes.",
)
# 重新运行测试
test_result = terminal(
command=f"pytest {bug_report['test_path']} -v --tb=short",
workdir=self.repo,
timeout=120,
)
# === Phase 5: 代码审查 (terminal → OpenCode) ===
review = terminal(
command='opencode "Review the latest changes. '
'Focus on: correctness, edge cases, performance. '
'Approve or request changes."',
workdir=self.repo,
timeout=120,
)
# === Phase 6: 提交 (terminal → git) ===
if review.approved:
terminal(
command=f'git add -A && '
f'git commit -m "fix: {bug_report["title"]}\n\n'
f'{bug_report["description"]}"',
workdir=self.repo,
)
return PipelineResult(
status="fixed" if review.approved else "review_failed",
analysis=analysis,
fix_plan=fix_plan,
test_result=test_result,
review=review,
)7.2 并发编排:多任务并行处理
# 场景:同时处理多个独立的子任务
async def parallel_code_review(pr_numbers: list[int]):
"""并发审查多个 Pull Request"""
sessions = {}
# 为每个 PR 启动独立的审查任务
for pr_num in pr_numbers:
session = terminal(
command=f'opencode "Review PR #{pr_num}. '
f"Focus on security and correctness. "
f'Output a review summary."',
workdir="/opt/data/my-project",
background=True,
timeout=180,
)
sessions[pr_num] = session["session_id"]
# 等待所有审查完成
results = {}
for pr_num, session_id in sessions.items():
process(action="wait", session_id=session_id, timeout=180)
output = process(action="log", session_id=session_id)
results[pr_num] = self.parse_review(output)
# 汇总报告
return self.generate_review_report(results)7.3 错误处理与恢复策略
class ResilientOrchestrator:
"""带容错能力的编排器"""
MAX_RETRIES = 3
RETRY_DELAY = 5 # seconds
async def execute_with_retry(self, command: str, **kwargs) -> TerminalResult:
"""带重试的命令执行"""
last_error = None
for attempt in range(self.MAX_RETRIES):
try:
result = terminal(command=command, **kwargs)
if result.exit_code == 0:
return result
last_error = Exception(
f"Exit code {result.exit_code}: {result.output}"
)
except Exception as e:
last_error = e
if attempt < self.MAX_RETRIES - 1:
await asyncio.sleep(self.RETRY_DELAY * (attempt + 1))
# 可选:在重试前调整策略
command = self.adjust_command(command, attempt)
raise last_error
def adjust_command(self, command: str, attempt: int) -> str:
"""根据失败次数调整命令策略"""
if attempt == 1:
# 第一次重试:增加超时
return command + " --timeout=600"
if attempt == 2:
# 第二次重试:缩小范围
return command.replace("src/", "src/core/")
return command
async def handle_process_failure(self, session_id: str) -> str:
"""处理后台进程失败"""
# 获取失败日志
logs = process(action="log", session_id=session_id)
# 分析失败原因
error_type = self.analyze_error(logs)
if error_type == "timeout":
# 超时:重新启动,增加 timeout
return "restart_with_longer_timeout"
elif error_type == "oom":
# OOM:缩小任务范围
return "reduce_scope"
elif error_type == "permission":
# 权限问题:调整权限后重试
return "fix_permissions_and_retry"
else:
# 未知错误:委派给专家 Agent 分析
return delegate_task(
prompt=f"Analyze this process failure and suggest recovery: {logs}"
)8. 高级编排模式
8.1 链式编排:Pipeline 模式
# 链式编排:每个步骤的输出是下一步的输入
class ChainedOrchestrator:
"""链式任务编排"""
def __init__(self):
self.chain_state = {}
async def run_chain(self, steps: list[ChainStep]):
"""执行编排链"""
current_context = {}
for step in steps:
# 可选:条件跳过
if step.condition and not step.condition(current_context):
continue
# 构建命令,注入上一步的输出
command = self.render_command(step.command, current_context)
# 选择执行方式
if step.tool == "claude_code":
result = terminal(command=f'claude "{command}"', **step.kwargs)
elif step.tool == "codex":
result = terminal(command=f'codex "{command}"', **step.kwargs)
elif step.tool == "opencode":
result = terminal(command=f'opencode "{command}"', **step.kwargs)
elif step.tool == "delegate":
result = delegate_task(prompt=command)
elif step.tool == "shell":
result = terminal(command=command, **step.kwargs)
# 更新上下文
current_context[step.output_key] = result
self.chain_state[step.name] = result
# 失败处理
if step.fail_fast and result.get("exit_code", 0) != 0:
raise ChainException(
f"Chain failed at step: {step.name}",
state=self.chain_state,
)
return self.chain_state
# 使用示例
chain = ChainedOrchestrator()
result = await chain.run_chain([
ChainStep(
name="analyze",
tool="claude_code",
command="Analyze the bug and identify root cause",
output_key="analysis",
fail_fast=True,
),
ChainStep(
name="fix",
tool="codex",
command="Generate fix based on: {analysis}",
output_key="fix",
fail_fast=True,
),
ChainStep(
name="test",
tool="shell",
command="pytest tests/ -v",
output_key="test_result",
fail_fast=True,
),
ChainStep(
name="review",
tool="opencode",
command="Review the changes",
output_key="review",
fail_fast=False, # 审查失败不阻断
),
ChainStep(
name="commit",
tool="shell",
command="git add -A && git commit -m 'auto-fix'",
output_key="commit_result",
fail_fast=True,
condition=lambda ctx: ctx.get("review", {}).get("approved", True),
),
])8.2 监控与可观测性
class ObservableOrchestrator:
"""带监控的可观测编排器"""
def __init__(self):
self.metrics = {
"total_tasks": 0,
"successful_tasks": 0,
"failed_tasks": 0,
"total_duration": 0,
"tool_usage": defaultdict(int),
}
self.event_log = []
def record_event(self, event: dict):
"""记录编排事件"""
self.event_log.append({
"timestamp": datetime.now().isoformat(),
**event,
})
def execute_monitored(self, tool: str, command: str, **kwargs):
"""带监控的执行"""
start_time = time.time()
self.metrics["total_tasks"] += 1
self.metrics["tool_usage"][tool] += 1
self.record_event({"type": "start", "tool": tool, "command": command})
try:
if tool == "delegate":
result = delegate_task(prompt=command, **kwargs)
else:
result = terminal(command=command, **kwargs)
duration = time.time() - start_time
self.metrics["successful_tasks"] += 1
self.metrics["total_duration"] += duration
self.record_event({
"type": "success",
"tool": tool,
"duration": duration,
})
return result
except Exception as e:
duration = time.time() - start_time
self.metrics["failed_tasks"] += 1
self.record_event({
"type": "failure",
"tool": tool,
"error": str(e),
"duration": duration,
})
raise
def get_dashboard_data(self) -> dict:
"""生成仪表盘数据"""
return {
"metrics": self.metrics,
"recent_events": self.event_log[-20:],
"avg_duration": (
self.metrics["total_duration"] /
max(self.metrics["successful_tasks"], 1)
),
"success_rate": (
self.metrics["successful_tasks"] /
max(self.metrics["total_tasks"], 1) * 100
),
}9. 最佳实践清单
✅ DO
- 明确分工:Claude Code 做深度推理,Codex 做批量处理,OpenCode 做轻量任务
- 超时设置:每个 terminal 调用都设置合理的 timeout
- 输出捕获:始终捕获并检查进程输出,不要盲目假设成功
- 错误恢复:为每个步骤设计 fallback 策略
- 工作目录隔离:不同任务使用独立的 workdir 避免污染
- 权限最小化:用
--allowedTools限制 Agent 的权限范围 - 日志记录:记录每个步骤的输入输出,便于调试和审计
- 并发控制:使用
background=true并行处理无依赖任务
❌ DON'T
- 不要盲等:后台进程一定要配合
process(action="wait")或poll - 不要硬编码路径:使用变量和配置管理路径
- 不要忽略退出码:始终检查
exit_code - 不要混用上下文:不同任务的上下文要保持隔离
- 不要一次性做太多:拆分大任务为可管理的子任务
- 不要忘记清理:后台进程完成后调用
process(action="close")
10. 总结
Hermes Agent 作为调度中心,通过 terminal 和 delegate_task 两大核心能力,可以高效编排 Claude Code、Codex、OpenCode 等多种 Agent 工具:
| 能力 | 工具 | 适用场景 |
|---|---|---|
| 前台执行 | terminal(command=...) |
快速命令、单步操作 |
| 后台执行 | terminal(background=true) |
长驻进程、并行任务 |
| 交互会话 | terminal(pty=true) |
REPL、多轮对话 |
| 子任务委派 | delegate_task(prompt=...) |
需要独立上下文的子任务 |
| 进程管理 | process(action=...) |
监控、控制、获取输出 |
编排的精髓在于:
- 用
delegate_task做智能决策和任务规划 - 用
terminal执行具体的工具调用 - 两者组合,实现从需求分析到代码交付的端到端自动化
下篇预告
多 Agent 系统的可观测性与调试:如何追踪、监控和优化 Agent 编排?
当多个 Agent 协同工作时,出了问题怎么排查?下一篇将深入讲解:
- 如何为每个 Agent 调用添加追踪 ID 和上下文标签
- 实时监控 Dashboard 的构建方案
- Agent 行为的异常检测与告警
- 调试技巧:回放、快照、对比分析
- 性能优化:识别瓶颈、减少冗余调用、缓存策略
让多 Agent 系统从"能工作"到"可信赖"。
本文属于「AI Agent 工具集成实战」系列。系列完整目录见系列索引。