Hermes Agent集成实战:全流程协议到生产部署
Hermes Agent 生产集成实战:ACP协议适配与会话池管理
详述 HagiCode 将 Hermes Agent 投入生产环境的完整流程,涵盖 ACP 协议适配、会话池复用、前后端契约对齐等核心经验,帮助团队快速落地 Agent 集成。
项目背景与选型考量
构建 HagiCode 这个 AI 编码辅助平台时,团队需要一个既能本地运行又能平滑扩展到云端的 Agent 框架。经过调研,Nous Research 的 Hermes Agent 凭借其 ACP 协议和工具系统,最终被选为基础引擎。
市面上的 Agent 框架各有特色,但 Hermes 的 ACP 协议与工具编排能力恰好满足 HagiCode 的多场景需求:本地开发、团队协作、云端扩展缺一不可。将 Hermes 真正嵌入生产系统,需要逐一攻克跨进程通信、会话生命周期、认证协商等工程难题。
HagiCode 基于 Orleans 构建分布式后端,前端采用 React + TypeScript。集成的关键在于保持现有架构统一的前提下,让 Hermes 成为与 ClaudeCode、OpenCode 并列的一等执行器。以下是我们在 HagiCode 中集成 Hermes Agent 的实践经验,希望为类似场景的团队提供可复用的参考路径。
关于 HagiCode
本文方案源自 HagiCode 项目的实际集成。HagiCode 是一个 AI 驱动的编码辅助平台,支持统一接入和管理多种 AI Provider。在集成 Hermes Agent 的过程中,我们设计了一套通用的 Provider 抽象层,使新型 Agent 能无缝融入现有体系。
架构设计
分层架构解耦
HagiCode 的 Hermes 集成采用清晰的分层设计,每层职责明确:
后端核心层
HermesCliProvider: 实现IAIProvider接口,作为统一的 AI Provider 入口HermesPlatformConfiguration: 管理 Hermes 可执行文件路径、参数、认证等配置ICliProvider: HagiCode.Libs 提供的底层 CLI 抽象,负责子进程生命周期管理
传输层
StdioAcpTransport: 通过标准输入输出与 Hermes ACP 子进程通信- ACP 协议方法:
initialize、authenticate、session/new、session/prompt
运行时层
HermesGrain: Orleans Grain 实现,处理分布式会话执行CliAcpSessionPool: 会话池,复用 ACP 子进程,避免频繁启动开销
前端层
ExecutorAvatar: Hermes 视觉标识与图标executorTypeAdapter: Provider 类型映射逻辑- SignalR 实时消息传递:保持 Hermes 身份在消息流中的一致性
这种分层使各层可独立演进。例如未来需要增加 WebSocket 传输方式,仅修改传输层即可,无需牵动全局。
统一接口抽象
所有 AI Provider 都实现 IAIProvider 接口,这是 HagiCode 架构的核心:
public interface IAIProvider
{
string Name { get; }
ProviderCapabilities Capabilities { get; }
IAsyncEnumerable StreamAsync(
AIRequest request,
CancellationToken cancellationToken = default);
Task ExecuteAsync(
AIRequest request,
CancellationToken cancellationToken = default);
}
HermesCliProvider 实现该接口,与 ClaudeCodeProvider、OpenCodeProvider 处于平等地位。这种设计带来的优势:
- 可替换性: 切换 Provider 不影响上层业务逻辑
- 可测试性: 可轻松 Mock Provider 进行单元测试
- 可扩展性: 新增 Provider 只需实现接口即可
接口约定如同技术契约,各方遵循统一规范,才能互不干扰地协同工作。
核心实现
Provider 层实践
HermesCliProvider 是整个集成的枢纽,负责协调各组件完成一次 AI 调用:
public sealed class HermesCliProvider : IAIProvider, IVersionedAIProvider
{
private readonly ICliProvider _provider;
private readonly ConcurrentDictionary _sessionBindings;
public ProviderCapabilities Capabilities { get; } = new()
{
SupportsStreaming = true,
SupportsTools = true,
SupportsSystemMessages = true,
SupportsArtifacts = false
};
public async IAsyncEnumerable StreamAsync(
AIRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// 1. 解析会话绑定 key
var bindingKey = ResolveBindingKey(request.CessionId);
// 2. 通过会话池获取或创建 Hermes 会话
var options = new HermesOptions
{
ExecutablePath = _platformConfiguration.ExecutablePath,
Arguments = _platformConfiguration.Arguments,
SessionId = _sessionBindings.TryGetValue(bindingKey, out var sessionId) ? sessionId : null,
WorkingDirectory = request.WorkingDirectory,
Model = request.Model
};
// 3. 执行并收集流式响应
await foreach (var message in _provider.ExecuteAsync(options, request.Prompt, cancellationToken))
{
// 4. 映射 ACP 消息到 AIStreamingChunk
if (_responseMapper.TryConvertToStreamingChunk(message, out var chunk))
{
yield return chunk;
}
}
}
}
关键设计要点:
- 会话绑定: 通过
CessionId将多个请求绑定到同一个 Hermes 子进程,保持多轮对话上下文连续 - 响应映射: 将 Hermes ACP 消息格式转换为统一的
AIStreamingChunk - 流式处理: 使用
IAsyncEnumerable支持真正的流式响应
会话绑定好比建立一次对话关系,后续交流有了上下文基础,但需要精心维护才能避免连接中断。
ACP 协议适配
Hermes 采用 ACP(Agent Communication Protocol)协议,与常规 HTTP API 不同。ACP 基于标准输入输出,具有以下特点:
- 启动标记: Hermes 进程启动后输出
//ready标记 - 动态认证: 认证方式通过协议协商确定,非固定配置
- 会话复用: 通过
SessionId重用已有会话 - 响应分散: 完整响应可能分布在多个
session/update通知中
HagiCode 通过 StdioAcpTransport 处理这些特性:
public class StdioAcpTransport
{
public async Task InitializeAsync(CancellationToken cancellationToken)
{
// 等待 //ready 标记
var readyLine = await _outputReader.ReadLineAsync(cancellationToken);
if (readyLine != "//ready")
{
throw new InvalidOperationException("Hermes did not send ready signal");
}
// 发送 initialize 请求
await SendRequestAsync(new
{
jsonrpc = "2.0",
id = 1,
method = "initialize",
@params = new
{
protocolVersion = "2024-11-05",
capabilities = new { },
clientInfo = new { name = "HagiCode", version = "1.0.0" }
}
}, cancellationToken);
}
}
协议层面的配合如同团队协作,双方达成一致后才能高效沟通。初期需投入精力建立默契,后期则顺畅自然。
会话池管理
频繁启动 Hermes 子进程代价高昂,因此我们引入会话池机制:
services.AddSingleton(static _ =>
{
var registry = new CliProviderPoolConfigurationRegistry();
registry.Register("hermes", new CliPoolSettings
{
MaxActiveSessions = 50,
IdleTimeout = TimeSpan.FromMinutes(10)
});
return registry;
});
会话池的关键参数:
MaxActiveSessions: 控制并发上限,防止资源耗尽IdleTimeout: 空闲超时,平衡启动开销与内存占用
实践总结:
- 空闲超时设置过短会导致频繁重启,过长则浪费内存
- 并发上限需根据实际负载调整,过大可能引发系统卡顿
- 持续监控会话池使用情况,便于动态调整参数
参数调优如同钢丝上行走,过于激进易出故障,过于保守则错失效率。寻找平衡点是关键。
前端集成
类型映射
前端需要正确识别 Hermes Provider 并展示对应的视觉元素:
// executorTypeAdapter.ts
export const resolveExecutorVisualTypeFromProviderType = (
providerType: PCode_Models_AIProviderType | null | undefined
): ExecutorVisualType => {
switch (providerType) {
case PCode_Models_AIProviderType.HERMES_CLI:
return 'Hermes';
default:
return 'Unknown';
}
};
视觉呈现
Hermes 拥有专属图标和配色标识:
// ExecutorAvatar.tsx
const renderExecutorGlyph = (executorType: ExecutorVisualType, iconSize: number) => {
switch (executorType) {
case 'Hermes':
return (
);
default:
return ;
}
};
良好的视觉呈现需要前端同学精细打磨,才能让用户感知到不同 Agent 的差异性。
契约同步
前后端通过 OpenAPI 生成来保持契约一致。后端定义 AIProviderType 枚举:
public enum AIProviderType
{
Unknown,
ClaudeCode,
OpenCode,
HermesCli // 新增
}
前端通过 OpenAPI 生成对应的 TypeScript 类型,确保枚举值同步。这是避免界面显示 "Unknown" 的核心措施。
契约同步如同技术承诺,一旦约定就必须严格执行,否则就会出现类型不匹配的尴尬局面。
配置管理
Hermes 的配置通过 appsettings.json 管理:
{
"Providers": {
"HermesCli": {
"ExecutablePath": "hermes",
"Arguments": "acp",
"StartupTimeoutMs": 10000,
"ClientName": "HagiCode",
"Authentication": {
"PreferredMethodId": "api-key",
"MethodInfo": {
"api-key": "your-api-key-here"
}
},
"SessionDefaults": {
"Model": "claude-sonnet-4-20250514",
"ModeId": "default"
}
}
}
}
配置驱动的设计带来极大灵活性:
- 可覆盖可执行文件路径,方便开发测试
- 可自定义启动参数,适配不同 Hermes 版本
- 可配置认证信息,支持多种认证方式
配置项如同时局中的选择题,提供足够选项后,总能找到适合当前场景的组合。但选项过多也可能引发选择困难,需权衡。
实践经验
健康检查
一个可靠的 Provider 需要完善的健康检查机制:
public async Task PingAsync(CancellationToken cancellationToken = default)
{
var response = await ExecuteAsync(new AIRequest
{
Prompt = "Reply with exactly PONG.",
CessionId = null,
AllowedTools = Array.Empty(),
WorkingDirectory = ResolveWorkingDirectory(null)
}, cancellationToken);
var success = string.Equals(response.Content.Trim(), "PONG", StringComparison.OrdinalIgnoreCase);
return new ProviderTestResult
{
ProviderName = Name,
Success = success,
ResponseTimeMs = stopwatch.ElapsedMilliseconds,
ErrorMessage = success ? null : $"Unexpected Hermes ping response: '{response.Content}'."
};
}
健康检查注意事项:
- 采用简单测试用例,避免复杂场景干扰
- 设置合理的超时时间
- 记录响应时间,用于性能分析
系统的健康检查如同人体定期体检,提前发现隐患才能避免生产事故。
验证工具
HagiCode 提供专用控制台用于验证 Hermes 集成:
# 基础验证
HagiCode.Libs.Hermes.Console --test-provider
# 完整套件(含仓库分析)
HagiCode.Libs.Hermes.Console --test-provider-full --repo .
# 自定义可执行文件
HagiCode.Libs.Hermes.Console --test-provider-full --executable /path/to/hermes
该工具在开发过程中价值显著,可快速验证集成是否正确。没有人愿意在问题爆发后才想起测试。
常见问题处理
认证失败
- 核对
Authentication.PreferredMethodId与 Hermes 实际支持的认证方法是否匹配 - 确认认证信息格式正确(API Key、Bearer Token 等)
会话超时
- 增大
StartupTimeoutMs值 - 检查 MCP 服务器可达性
- 查看系统资源使用情况
响应不完整
- 确保正确聚合
session/update通知与最终结果 - 检查流式处理的取消逻辑
- 验证错误处理是否完备
前端显示 Unknown
- 确认 OpenAPI 生成已包含
HermesCli枚举值 - 检查类型映射是否正确
- 清除浏览器缓存后重新生成类型
问题总会出现,保持冷静、逐步排查即可解决。办法总比困难多。
性能优化建议
- 使用会话池: 复用 ACP 子进程,减少启动开销
- 合理设置超时: 平衡内存占用与启动成本
- 复用会话 ID: 批量任务使用同一个
CessionId - 按需配置 MCP: 避免不必要的工具调用
性能优化如同提升工作效率,选对方法事半功倍,误入歧途则事倍功半。找到那个平衡点需要经验积累与持续调优。
总结
将 Hermes Agent 集成到生产系统需要从多个维度考量:
- 架构层面: 设计统一的 Provider 接口,实现可替换的组件架构
- 协议层面: 正确处理 ACP 协议的特殊性,如启动标记、动态认证等
- 性能层面: 通过会话池复用资源,平衡启动成本与内存占用
- 前端层面: 确保契约同步,提供一致的视觉体验
HagiCode 的实践表明,良好的分层设计与配置驱动能够将复杂的 Agent 系统无缝融入现有架构。
这些道理说起来简单,但真正落地时总会遇到各种挑战。遇到问题解决掉就是经验,解决不了就是教训——两者都是宝贵的积累。
技术之美在于让系统更健壮、更高效,至于采用哪种框架或协议,终究是手段而非目的。
参考资料
- HagiCode 项目地址
- HagiCode 官网
- Hermes Agent 文档
- ACP 协议规范
- HagiCode 安装指南
- HagiCode Desktop
