Hermes Agent Integration Practice: From Protocol to Production
Sharing our complete experience integrating Hermes Agent into HagiCode, including core insights on ACP protocol adaptation, session pool management, and frontend-backend contract synchronization.
Background
While building the AI-assisted coding platform HagiCode, the team needed to integrate an Agent framework capable of running both locally and scaling to the cloud. After research, Nous Research's Hermes Agent was selected as the underlying engine for our comprehensive Agent.
Technology selection is neither particularly hard nor simple. After all, there are quite a few competitive Agent frameworks on the market, but Hermes's ACP protocol and tool system really stand out, perfectly aligning with HagiCode's "have it all" scenario—local development, team collaboration, and cloud scalability. But truly integrating Hermes into a production system requires solving a series of engineering challenges—this is no trivial matter.
HagiCode's tech stack is built on Orleans for distributed systems, with React + TypeScript on the frontend. Integrating Hermes requires maintaining existing architectural consistency while making Hermes a "first-class citizen" executor alongside ClaudeCode, OpenCode, and others. Easy to say, but in practice... well, you know.
This article shares our practical experience integrating Hermes Agent in the HagiCode project, hoping to provide reference for teams with similar needs. After all, there's no need for others to step into the same potholes we've already fallen into.
About HagiCode
The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an AI-driven coding assistance platform supporting unified integration and management of multiple AI Providers. During the Hermes Agent integration, we designed a universal Provider abstraction layer enabling seamless integration of new Agent types into our existing system.
If you're interested in HagiCode, welcome to visit GitHub to learn more. More eyes, more strength—that's all.
Architecture Design
Layered Design Approach
HagiCode's Hermes integration adopts a clear layered architecture with each layer having distinct responsibilities:
Backend Core Layer
-
HermesCliProvider: ImplementsIAIProviderinterface as the unified AI Provider entry point -
HermesPlatformConfiguration: Manages Hermes executable path, parameters, authentication and other configurations -
ICliProvider<HermesOptions>: Low-level CLI abstraction provided by HagiCode.Libs, handling subprocess lifecycle
Transport Layer
-
StdioAcpTransport: Communicates with Hermes ACP subprocess via standard input/output - ACP protocol methods:
initialize,authenticate,session/new,session/prompt
Runtime Layer
-
HermesGrain: Orleans Grain implementation handling distributed session execution -
CliAcpSessionPool: Session pool reusing ACP subprocesses to avoid frequent startup overhead
Frontend Layer
-
ExecutorAvatar: Hermes visual identity and icon -
executorTypeAdapter: Provider type mapping logic - SignalR real-time messaging: Maintains Hermes identity consistency in message streams
This layered design enables independent evolution of each layer—for example, adding a new transport method (like WebSocket) in the future would only require modifying the transport layer. After all, who wants to overhaul the entire system just to change a transport method? Too exhausting.
Unified Interface Abstraction
All AI Providers implement the IAIProvider interface, the core design of HagiCode's architecture:
public interface IAIProvider
{
string Name { get; }
ProviderCapabilities Capabilities { get; }
IAsyncEnumerable<AIStreamingChunk> StreamAsync(
AIRequest request,
CancellationToken cancellationToken = default);
Task<AIResponse> ExecuteAsync(
AIRequest request,
CancellationToken cancellationToken = default);
}
HermesCliProvider implements this interface, standing on equal footing with ClaudeCodeProvider, OpenCodeProvider, and others. Benefits of this design:
- Replaceability: Switching Providers doesn't affect upper-layer business logic
- Testability: Easy to Mock Providers for unit testing
- Extensibility: New Providers only need to implement the interface
In the end, interfaces are like rules—rules that let everyone coexist harmoniously, each playing to their strengths without interference. Isn't that a kind of beauty?
Core Implementation
Provider Layer Implementation
HermesCliProvider is the heart of the entire integration, coordinating various components to complete an AI call:
public sealed class HermesCliProvider : IAIProvider, IVersionedAIProvider
{
private readonly ICliProvider<LibsHermesOptions> _provider;
private readonly ConcurrentDictionary<string, string> _sessionBindings;
public ProviderCapabilities Capabilities { get; } = new()
{
SupportsStreaming = true,
SupportsTools = true,
SupportsSystemMessages = true,
SupportsArtifacts = false
};
public async IAsyncEnumerable<AIStreamingChunk> StreamAsync(
AIRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// 1. Resolve session binding key
var bindingKey = ResolveBindingKey(request.CessionId);
// 2. Get or create Hermes session through session pool
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. Execute and collect streaming response
await foreach (var message in _provider.ExecuteAsync(options, request.Prompt, cancellationToken))
{
// 4. Map ACP message to AIStreamingChunk
if (_responseMapper.TryConvertToStreamingChunk(message, out var chunk))
{
yield return chunk;
}
}
}
}
Key design points here:
-
Session binding: Binding multiple requests to the same Hermes subprocess through
CessionIdfor context continuity in multi-turn conversations -
Response mapping: Converting Hermes ACP message format to unified
AIStreamingChunkformat -
Streaming processing: Using
IAsyncEnumerablefor true streaming response support
Session binding is like human relationships—once a connection is established, subsequent communication has context without starting from scratch each time. Just need to maintain the relationship well, or it'll break.
ACP Protocol Adaptation
Hermes uses ACP (Agent Communication Protocol), different from traditional HTTP APIs. ACP is a standard input/output based protocol with several characteristics:
-
Startup marker: Hermes process outputs
//readymarker after startup - Dynamic authentication: Authentication methods are not fixed, requiring protocol negotiation
-
Session reuse: Reusing established sessions through
SessionId -
Response fragmentation: Complete responses may be scattered across multiple
session/updatenotifications
HagiCode handles these characteristics through StdioAcpTransport:
public class StdioAcpTransport
{
public async Task InitializeAsync(CancellationToken cancellationToken)
{
// Wait for //ready marker
var readyLine = await _outputReader.ReadLineAsync(cancellationToken);
if (readyLine != "//ready")
{
throw new InvalidOperationException("Hermes did not send ready signal");
}
// Send initialize request
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);
}
}
Protocols are like tacit understanding between people—with it, communication flows smoothly. But building that understanding takes time—running in is unavoidable for anyone.
Session Pool Management
Frequent Hermes subprocess startup has significant overhead, so we implemented a session pool mechanism:
services.AddSingleton(static _ =>
{
var registry = new CliProviderPoolConfigurationRegistry();
registry.Register("hermes", new CliPoolSettings
{
MaxActiveSessions = 50,
IdleTimeout = TimeSpan.FromMinutes(10)
});
return registry;
});
Key session pool parameters:
-
MaxActiveSessions: Controls concurrency ceiling to avoid resource exhaustion -
IdleTimeout: Idle timeout balancing startup cost and memory usage
In practice we found:
- Idle timeout set too short causes frequent restarts, too long occupies memory
- Concurrency ceiling needs adjustment based on actual load; too large may cause system lag
- Need to monitor session pool usage for timely parameter adjustment
It's like many life choices—too aggressive leads to problems, too conservative misses opportunities. Just finding a balance.
Frontend Integration
Type Mapping
The frontend needs to correctly identify Hermes Provider and display corresponding visual elements:
// 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';
}
};
Visual Presentation
Hermes has dedicated icon and color identity:
// ExecutorAvatar.tsx
const renderExecutorGlyph = (executorType: ExecutorVisualType, iconSize: number) => {
switch (executorType) {
case 'Hermes':
return (
<svg viewBox="0 0 24 24" fill="none" className="h-4 w-4">
<rect x="4" y="4" width="16" height="16" rx="4" fill="currentColor" opacity="0.16" />
<path d="M8 7v10M16 7v10M8 12h8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
default:
return <DefaultAvatar />;
}
};
After all, beautiful things deserve beautiful presentation. But for that beauty to be seen, it relies on our frontend developers' efforts.
Contract Synchronization
Frontend and backend maintain contract consistency through OpenAPI generation. The backend defines the AIProviderType enum:
public enum AIProviderType
{
Unknown,
ClaudeCode,
OpenCode,
HermesCli // New addition
}
The frontend generates corresponding TypeScript types through OpenAPI, ensuring enum value consistency. This is key to avoiding "Unknown" displays on the frontend.
Contracts are like promises—once agreed upon, they must be kept, or you'll face awkward situations like "Unknown".
Configuration Management
Hermes configuration is managed through 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"
}
}
}
}
This configuration-driven design brings flexibility:
- Can override executable paths for development and testing
- Can customize startup parameters to adapt to different Hermes versions
- Can configure authentication information supporting multiple authentication methods
Configuration is like multiple choice questions in life—with enough options, you can always find what fits. But sometimes too many choices cause decision paralysis.
Practical Experience
Health Check
Implementing a reliable Provider requires comprehensive health checks:
public async Task<ProviderTestResult> PingAsync(CancellationToken cancellationToken = default)
{
var response = await ExecuteAsync(new AIRequest
{
Prompt = "Reply with exactly PONG.",
CessionId = null,
AllowedTools = Array.Empty<string>(),
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}'."
};
}
Health checks require attention:
- Use simple test cases avoiding complex scenarios
- Set reasonable timeout values
- Log response times for performance analysis
Like people need physical exams, systems need health checks—early detection and treatment prevents major issues later.
Validation Tools
HagiCode provides a dedicated console for validating Hermes integration:
# Basic validation
HagiCode.Libs.Hermes.Console --test-provider
# Complete suite (including repository analysis)
HagiCode.Libs.Hermes.Console --test-provider-full --repo .
# Custom executable
HagiCode.Libs.Hermes.Console --test-provider-full --executable /path/to/hermes
This tool is very useful during development for quickly validating integration correctness. After all, who wants to think about testing only when problems arise?
Common Issue Handling
Authentication Failure
- Check if
Authentication.PreferredMethodIdmatches authentication methods actually supported by Hermes - Confirm authentication information format is correct (API Key, Bearer Token, etc.)
Session Timeout
- Increase
StartupTimeoutMsvalue - Check MCP server reachability
- Review system resource usage
Incomplete Response
- Ensure proper aggregation of
session/updatenotifications and final results - Check streaming cancellation logic
- Verify error handling completeness
Frontend Shows Unknown
- Confirm OpenAPI generation includes
HermesClienum value - Check if type mapping is correct
- Clear browser cache and regenerate types
Problems are inevitable. When they arise, don't panic—investigate the cause slowly and you'll find a solution. After all, there are always more solutions than difficulties.
Performance Optimization Recommendations
- Use session pool: Reuse ACP subprocesses to reduce startup overhead
- Set timeouts reasonably: Balance memory and startup costs
-
Reuse session IDs: Use same
CessionIdfor batch tasks - Configure MCP on-demand: Avoid unnecessary tool calls
Performance is like efficiency in life—doing it right halves the effort, doing it wrong doubles the effort. But finding that "right" point requires experience and luck.
Summary
Integrating Hermes Agent into production systems requires consideration across multiple levels:
- Architecture level: Design unified Provider interface implementing replaceable component architecture
- Protocol level: Properly handle ACP protocol peculiarities like startup markers, dynamic authentication
- Performance level: Reuse resources through session pools balancing startup cost and memory usage
- Frontend level: Ensure contract synchronization providing consistent visual experience
HagiCode's practice shows that through good layered design and configuration driving, complex Agent systems can be seamlessly integrated into existing architecture.
These principles sound simple, but in practice you'll encounter various problems. No matter—solved problems become experience, unsolved ones become lessons, both valuable.
Beautiful things or people don't need to be possessed—as long as they remain beautiful, simply appreciating their beauty is enough. Technology is similar: as long as it makes the system better, which framework or protocol is used doesn't really matter...
References
- HagiCode Project
- HagiCode Official Site
- Hermes Agent Documentation
- ACP Protocol Specification
- HagiCode Installation Guide
- HagiCode Desktop
Original Article & License
Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.
This article was created with AI assistance and reviewed by the author before publication.
- Author: newbe36524
- Original URL: https://docs.hagicode.com/go?platform=devto&target=%2Fblog%2F2026-04-14-hermes-agent-integration-practice%2F
- License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.
Top comments (0)