| name | hermes-agent-architecture |
| description | Deep expertise in Hermes Agent architecture, implementation patterns, and extension development |
| triggers | ["how does hermes agent work internally","explain hermes agent architecture","how to extend hermes agent with custom tools","implement a hermes plugin","how does hermes memory system work","create custom toolset for hermes","integrate hermes with messaging platform","optimize hermes agent performance"] |
Hermes Agent Architecture
Skill by ara.so — Hermes Skills collection.
Hermes Agent is a production-grade LLM agent framework by Nous Research featuring advanced memory management, multi-agent orchestration, 18+ messaging platform integrations, and a sophisticated tool execution system. This skill covers internal architecture, extension patterns, and implementation strategies verified against source code.
Installation
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
pip install -e .
poetry install
cp config.example.yaml config.yaml
Core Architecture Components
Agent Loop and Execution
The main agent loop is in hermes/agent.py:
from hermes.agent import Agent
from hermes.config import Config
config = Config.load("config.yaml")
agent = Agent(config)
await agent.run()
response = await agent.process_message(
"Analyze the repository structure",
context={"cwd": "/path/to/repo"}
)
Key execution flow:
process_message() → Prompt assembly
- Model inference → Tool calls extraction
- Tool dispatch via
ToolRegistry
- Result aggregation → Memory storage
- Response generation
Tool System Architecture
Tools are registered centrally via decorators:
from hermes.tools.registry import tool_registry
from hermes.tools.base import ToolResult
@tool_registry.register(
name="custom_analyzer",
description="Analyze code patterns",
category="analysis",
parameters={
"file_path": {
"type": "string",
"description": "Path to file to analyze"
},
"pattern": {
"type": "string",
"description": "Pattern to search for"
}
}
)
async def custom_analyzer(file_path: str, pattern: str, **kwargs) -> ToolResult:
"""Custom code analysis tool."""
try:
with open(file_path, 'r') as f:
content = f.read()
matches = re.findall(pattern, content)
return ToolResult(
success=True,
data={"matches": matches, "count": len(matches)},
message=f"Found {len(matches)} matches"
)
except Exception as e:
return ToolResult(
success=False,
error=str(e)
)
Toolset grouping (from hermes/tools/toolsets.py):
from hermes.tools.toolsets import Toolset, toolset_registry
@toolset_registry.register("code_analysis")
class CodeAnalysisToolset(Toolset):
"""Custom toolset for code analysis."""
def get_tools(self):
return [
"custom_analyzer",
"list_functions",
"complexity_check"
]
def get_description(self):
return "Tools for analyzing code structure and patterns"
Memory System
Three-layer architecture (hermes/memory/):
from hermes.memory.manager import MemoryManager
from hermes.memory.store import MemoryStore
from hermes.memory.provider import MemoryProvider
store = MemoryStore(db_path="~/.hermes/memory.db")
manager = MemoryManager(store)
await manager.add_message(
role="user",
content="Remember that I prefer functional programming",
session_id="current_session"
)
memories = await manager.search_memories(
query="programming preferences",
limit=5
)
snapshot = manager.freeze_snapshot()
Session search with FTS5:
from hermes.memory.session_db import SessionDB
session_db = SessionDB(db_path="~/.hermes/sessions.db")
results = await session_db.search(
query="docker configuration",
limit=10
)
summary = await session_db.get_session_summary(
query="docker issues",
llm_client=auxiliary_client
)
Context Compression v3
Automatic context management (hermes/compression/compressor.py):
from hermes.compression.compressor import ContextCompressor
compressor = ContextCompressor(
model_client=client,
max_tokens=128000,
preserve_recent=5
)
compressed = await compressor.compress(
messages=conversation_history,
strategies=[
"md5_dedup",
"smart_collapse",
"param_truncation"
]
)
summary = await compressor.summarize_structured(
messages=old_messages,
format="bullet_points"
)
Skills System
Progressive disclosure with conditional activation (hermes/skills/):
from hermes.skills.manager import SkillsManager
skills_manager = SkillsManager(
skills_dir="~/.hermes/skills",
config=config
)
"""
---
name: docker-expert
triggers:
- docker
- container
- dockerfile
conditions:
- file_exists: Dockerfile
- OR:
- file_exists: docker-compose.yml
- env_var: DOCKER_HOST
credentials:
- DOCKER_API_KEY
---
"""
await skills_manager.load_plugin_skills(
plugin_name="custom_plugin",
skills_manifest=plugin.get_skills()
)
Multi-Agent Architecture
Four runtime mechanisms:
from hermes.tools.delegate import delegate_task
result = await delegate_task(
task="Research Python async patterns",
specialist_config={
"model": "claude-3-7-sonnet",
"toolsets": ["web_search", "code_analysis"]
}
)
from hermes.multi_agent.moa import MixtureOfAgents
moa = MixtureOfAgents(
agents=[
{"name": "researcher", "model": "gpt-4"},
{"name": "critic", "model": "claude-3-opus"},
{"name": "synthesizer", "model": "claude-3-7-sonnet"}
]
)
consensus = await moa.deliberate(
question="What's the best architecture for this service?"
)
from hermes.multi_agent.reviewer import BackgroundReviewer
reviewer = BackgroundReviewer(model="gpt-4o")
review = await reviewer.review_conversation(
messages=conversation_history,
focus="security concerns"
)
await agent.send_message(
to_agent="code_reviewer",
content="Please review the changes in PR #123"
)
Browser Automation
Multi-backend architecture (hermes/tools/browser/):
from hermes.tools.browser import browser_navigate, browser_interact
result = await browser_navigate(
url="https://github.com/trending",
extract_content=True,
backend="playwright"
)
await browser_interact(
action="click",
selector="button[aria-label='Star']",
wait_for="networkidle"
)
Code Execution Sandbox
Secure Python execution (hermes/tools/code_exec/):
from hermes.tools.code_exec import execute_code
result = await execute_code(
code="""
import numpy as np
data = np.random.rand(100)
print(f"Mean: {data.mean()}")
""",
language="python",
timeout=30,
allowed_imports=["numpy", "pandas", "matplotlib"]
)
Communication modes:
sandbox_config = {
"mode": "uds",
"socket_path": "/tmp/hermes_sandbox.sock"
}
sandbox_config = {
"mode": "file_rpc",
"rpc_dir": "/tmp/hermes_rpc"
}
Messaging Gateway Integration
Platform adapter plugin system (hermes/gateway/):
from hermes.gateway.platform_registry import platform_registry
from hermes.gateway.base import PlatformAdapter, PlatformMessage
@platform_registry.register("custom_chat")
class CustomChatAdapter(PlatformAdapter):
"""Custom messaging platform integration."""
platform_name = "custom_chat"
async def initialize(self):
"""Connect to platform API."""
self.client = CustomChatClient(
api_key=self.config.get("api_key")
)
await self.client.connect()
async def receive_messages(self):
"""Poll for new messages."""
async for raw_msg in self.client.stream_messages():
yield PlatformMessage(
platform="custom_chat",
channel_id=raw_msg.channel,
user_id=raw_msg.author_id,
username=raw_msg.author_name,
content=raw_msg.text,
message_id=raw_msg.id,
timestamp=raw_msg.created_at
)
async def send_message(self, channel_id: str, content: str, **kwargs):
"""Send response to platform."""
.client.send(
channel=channel_id,
text=content
)
() -> :
gateway = MessagingGateway(config)
gateway.register_platform(CustomChatAdapter(config.platforms.custom_chat))
gateway.run()
Built-in platform adapters:
- Discord, Slack, Telegram, IRC
- WeChat, QQ, DingTalk, WeCom (企业微信)
- WhatsApp, Signal, Matrix
- BlueBubbles (iMessage), SMS
- 腾讯元宝 (Tencent Yuanbao)
Plugin System
Dual hook architecture (hermes/plugins/):
from hermes.plugins.base import Plugin, plugin_registry
@plugin_registry.register
class DashboardPlugin(Plugin):
"""Web dashboard for monitoring agent activity."""
name = "dashboard"
version = "1.0.0"
async def initialize(self, agent):
"""Setup plugin."""
self.agent = agent
self.app = create_dashboard_app()
agent.register_command(
name="/dashboard",
handler=self.open_dashboard,
description="Open web dashboard"
)
agent.register_hook(
"before_tool_call",
self.log_tool_call
)
async def log_tool_call(self, tool_name, parameters):
"""Log tool executions to dashboard."""
await self.app.broadcast_event({
"type": "tool_call",
"tool": tool_name,
"params": parameters,
"timestamp": time.time()
})
async def open_dashboard(self, args):
"""Handle /dashboard command."""
url = await .app.get_url()
agent.load_plugins(plugins_dir=)
MCP (Model Context Protocol) Integration
from hermes.mcp.client import MCPClient
mcp = MCPClient(server_url="http://localhost:8000")
await mcp.connect()
mcp_tools = await mcp.list_tools()
Smart Model Routing
from hermes.routing.smart_router import SmartRouter
router = SmartRouter(
default_model="claude-3-7-sonnet",
short_message_model="claude-3-5-haiku",
short_message_threshold=100
)
model = router.select_model(
messages=conversation,
task_type="code_generation"
)
Prompt Caching Optimization
from hermes.optimization.cache import CacheStrategy
cache_strategy = CacheStrategy(
enabled=True,
min_cache_size=2000,
freeze_system_prompt=True
)
messages = prompt_builder.build_messages(
system_prompt=frozen_system,
memory_snapshot=frozen_memory,
new_messages=recent_messages
)
Security & Safety
from hermes.security.approval import ApprovalSystem
approval = ApprovalSystem(
mode="smart",
dangerous_patterns=[
r"rm -rf",
r"DROP TABLE",
r"chmod 777"
]
)
if await approval.requires_approval(command):
user_confirmed = await approval.request_approval(
command=command,
risk_level="high",
explanation="This will delete system files"
)
if not user_confirmed:
return ToolResult(success=False, error="User rejected")
Error Handling & Fault Tolerance
from hermes.errors import HermesError, ToolExecutionError
from hermes.errors.classifier import ErrorClassifier
classifier = ErrorClassifier()
try:
result = await tool_function(**params)
except Exception as e:
error_info = classifier.classify(e)
if error_info.category == "rate_limit":
await asyncio.sleep(error_info.retry_after)
result = await tool_function(**params)
elif error_info.category == "auth_failure":
alt_creds = credential_pool.get_next()
result = await tool_function(**params, creds=alt_creds)
elif error_info.recoverable:
fallback_model = config.get_fallback_model()
result = await fallback_model.complete(...)
else:
raise HermesError(
message=f"Unrecoverable error in {tool_name}",
original_error=e,
context=error_info.context
)
Configuration Patterns
Multi-Profile Setup
profiles:
default:
model: claude-3-7-sonnet-20250219
provider: anthropic
toolsets:
- filesystem
- web_search
- code_execution
memory:
enabled: true
compress_threshold: 50
code_assistant:
model: claude-3-7-sonnet-20250219
toolsets:
- filesystem
- git
- code_execution
- browser
skills:
- python-expert
- rust-expert
memory:
enabled: true
session_isolation: true
researcher:
model: gpt-4o
toolsets:
- web_search
- browser
- pdf_tools
auxiliary_model: gpt-4o-mini
memory:
Credential Pool Management
credentials:
anthropic:
pool:
- api_key: ${ANTHROPIC_KEY_1}
rate_limit: 1000
- api_key: ${ANTHROPIC_KEY_2}
rate_limit: 500
selection_strategy: round_robin
openai:
pool:
- api_key: ${OPENAI_KEY_MAIN}
organization: ${OPENAI_ORG}
- api_key: ${OPENAI_KEY_BACKUP}
Gateway Configuration
gateway:
enabled: true
platforms:
discord:
enabled: true
token: ${DISCORD_TOKEN}
allowed_channels:
- "1234567890"
admin_users:
- "user#1234"
channel_prompts:
"1234567890": "You are a helpful coding assistant."
slack:
enabled: true
token: ${SLACK_TOKEN}
signing_secret: ${SLACK_SIGNING_SECRET}
socket_mode: true
wechat:
enabled: true
auto_login: true
contact_whitelist:
- "friend_name"
session_management:
timeout: 3600
max_per_user: 5
pii_redaction: true
CLI Commands
hermes
hermes "Analyze the codebase structure"
hermes --profile researcher "Find recent papers on RAG"
hermes dump --format json --output state.json
hermes skills list
hermes skills reload
hermes --reload-skills
hermes sessions list
hermes sessions search "docker configuration"
hermes sessions delete <session_id>
hermes gateway --platforms discord,slack
hermes trajectory --output dataset/ --runs 100
Slash Commands (in interactive mode)
/exit or /quit - Exit session
/reset - Clear conversation
/dump - Export state
/models - List available models
/switch <model> - Switch model
/profile <name> - Switch profile
/tools - List active tools
/skills - List loaded skills
/reload-skills - Reload skill library
/memory search <query> - Search memories
/help - Show commands
Development Patterns
Custom Provider Transport
from hermes.providers.base import ProviderTransport
from hermes.providers.registry import provider_registry
@provider_registry.register("custom_llm")
class CustomLLMTransport(ProviderTransport):
"""Custom LLM provider integration."""
async def create_completion(self, messages, model, **kwargs):
"""Send completion request."""
response = await self.http_client.post(
f"{self.base_url}/v1/chat/completions",
json={
"model": model,
"messages": self._format_messages(messages),
"tools": self._format_tools(kwargs.get("tools", []))
},
headers={"Authorization": f"Bearer {self.api_key}"}
)
return self._parse_response(response)
async def stream_completion(self, messages, model, **kwargs):
"""Stream completion chunks."""
async with self.http_client.stream(
"POST",
f"{self.base_url}/v1/chat/completions",
json={"model": model, : messages, : }
) stream:
line stream.aiter_lines():
line.startswith():
._parse_chunk(line)
():
[
{
: tool[],
: tool[],
: tool[]
}
tool tools
]
Context Reference System
from hermes.context.references import ContextReferenceParser
parser = ContextReferenceParser(
sandbox_root="/workspace",
max_file_size=100000
)
content, references = await parser.parse(
"@file:src/main.py @url:https://docs.python.org/3/library/asyncio.html"
)
context_additions = await parser.resolve_references(references)
Parallel Tool Execution
from hermes.tools.parallel import ParallelExecutor
executor = ParallelExecutor(max_workers=5)
tool_calls = [
{"name": "search_web", "params": {"query": "Python async"}},
{"name": "search_web", "params": {"query": "Rust async"}},
{"name": "write_file", "params": {"path": "test.txt", "content": "x"}},
]
results = await executor.execute_batch(
tool_calls,
conflict_detection=True
)
Voice Mode Integration
from hermes.voice.stt import STTProvider
from hermes.voice.tts import TTSProvider
stt = STTProvider(
provider="deepgram",
api_key="${DEEPGRAM_API_KEY}",
language="en"
)
transcript = await stt.transcribe_audio(
audio_file="recording.wav"
)
tts = TTSProvider(
provider="gemini",
voice="alloy"
)
audio_data = await tts.synthesize(
text="Analysis complete. Found 3 issues.",
output_format="mp3"
)
from hermes.voice.ptt import PushToTalkSession
async with PushToTalkSession(stt, tts, agent) as session:
await session.run()
Troubleshooting
Memory Issues
Problem: Context window exceeded despite compression.
config.memory.compress_threshold = 30
config.memory.max_memories_per_query = 3
compressor.summarize_structured(
messages=old_messages[:-10],
format="bullet_points"
)
Problem: Memories not being recalled.
from hermes.memory.session_db import SessionDB
db = SessionDB()
await db.rebuild_fts_index()
config.memory.similarity_threshold = 0.7
Tool Execution
Problem: Tool results too large.
config.tools.max_result_size = 50000
config.tools.round_token_budget = 100000
Problem: Parallel execution conflicts.
from hermes.tools.parallel import PathConflictDetector
detector = PathConflictDetector()
conflicts = detector.find_conflicts([
("write_file", {"path": "src/main.py"}),
("read_file", {"path": "src/main.py"})
])
@tool_registry.register(safety_class="stateful")
async def my_stateful_tool(...):
...
Gateway Issues
Problem: Platform authentication failing.
hermes gateway --test-auth --platform discord
config.gateway.platforms.wechat.auto_login = true
config.gateway.platforms.dingtalk.use_qr = true
config.gateway.platforms.slack.verify_signature = true
Problem: PII leaking in logs.
config.gateway.pii_redaction = true
config.gateway.redact_patterns:
- r'\b\d{3}-\d{2}-\d{4}\b'
- r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
Performance
Problem: Slow response times.
config.optimization.cache.enabled = true
config.optimization.cache.min_size = 2000
config.routing.short_message_model = "claude-3-5-haiku"
config.routing.threshold = 100
config.tools.parallel_execution = true
config.tools.max_parallel_workers = 5
Problem: High API costs.
config.memory.compress_threshold = 20
config.auxiliary_model = "gpt-4o-mini"
config.credentials.anthropic.selection_strategy = "round_robin"
Debugging
import logging
logging.basicConfig(level=logging.DEBUG)
hermes dump --include-memory --include-tools --output debug.json
config.debug.trace_tools = true
await agent.load_plugin("dashboard")
Best Practices
- Memory management: Use
freeze_snapshot() before each model call to maximize cache hits
- Tool development: Always return
ToolResult with structured data, mark safety class correctly
- Multi-agent: Prefer
delegate_task for focused sub-tasks, MoA for complex decisions
- Gateway mode: Use
channel_prompts for platform-specific behavior, enable PII redaction
- Skills: Write skills with clear triggers, use conditional activation to reduce noise
- Security: Enable danger command approval in production, use sandbox for code execution
- Performance: Enable prompt caching, use auxiliary models for simple tasks, parallelize read-only tools
- Extensions: Register via decorators, use hook system for cross-cutting concerns, follow plugin structure for complex additions
Resources