hermes - 💡(How to fix) Fix [i18n] Thai Translation: Developer Guide Part a - acp-internals, adding-platform-adapters, adding-providers, adding-tools, agent-loop +1 more [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#15126Fetched 2026-04-25 06:24:29
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
labeled ×2

Error Message

tools/weather_tool.py

"""Weather Tool -- look up current weather for a location."""

import json import os import logging

logger = logging.getLogger(name)

--- Availability check ---

def check_weather_requirements() -> bool: """Return True if the tool's dependencies are available.""" return bool(os.getenv("WEATHER_API_KEY"))

--- Handler ---

def weather_tool(location: str, units: str = "metric") -> str: """Fetch weather for a location. Returns JSON string.""" api_key = os.getenv("WEATHER_API_KEY") if not api_key: return json.dumps({"error": "WEATHER_API_KEY not configured"}) try: # ... call weather API ... return json.dumps({"location": location, "temp": 22, "units": units}) except Exception as e: return json.dumps({"error": str(e)})

--- Schema ---

WEATHER_SCHEMA = { "name": "weather", "description": "Get current weather for a location.", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City name or coordinates (e.g. 'London' or '51.5,-0.1')" }, "units": { "type": "string", "enum": ["metric", "imperial"], "description": "Temperature units (default: metric)", "default": "metric" } }, "required": ["location"] } }

--- Registration ---

from tools.registry import registry

registry.register( name="weather", toolset="weather", schema=WEATHER_SCHEMA, handler=lambda args, **kw: weather_tool( location=args.get("location", ""), units=args.get("units", "metric")), check_fn=check_weather_requirements, requires_env=["WEATHER_API_KEY"], )

Fix Action

Fix / Workaround

  • patch / write_file -> file diffs

  • terminal -> shell command text

  • read_file / search_files -> text previews

  • large results -> truncated text blocks for UI safety

  • provider adapter ใน agent/

  • branches ใน run_agent.py สำหรับการสร้าง request, dispatch, การดึง usage, การจัดการ interrupt, และการ normalize response

  • adapter tests

  • dict provider_labels

  • list providers ใน select_provider_and_model()

  • provider dispatch (if selected_provider == ...)

  • ตัวเลือก argument --provider

  • ตัวเลือก login/logout หาก provider รองรับ flow เหล่านั้น

  • ฟังก์ชัน _model_flow_<provider>() หรือใช้ซ้ำ _model_flow_api_key_provider() หากเหมาะสม

Code Example

hermes acp / hermes-acp / python -m acp_adapter
  -> acp_adapter.entry.main()
  -> load ~/.hermes/.env
  -> configure stderr logging
  -> construct HermesACPAgent
  -> acp.run_agent(agent)

---

asyncio.run_coroutine_threadsafe(...)

---

new_session(cwd)
  -> create SessionState
  -> create AIAgent(platform="acp", enabled_toolsets=["hermes-acp"])
  -> bind task_id/session_id to cwd override

prompt(..., session_id)
  -> extract text from ACP content blocks
  -> reset cancel event
  -> install callbacks + approval bridge
  -> run AIAgent in ThreadPoolExecutor
  -> update session history
  -> emit final agent message chunk

---

UserMessaging PlatformPlatform AdapterGateway RunnerAIAgent

---

class Platform(str, Enum):
    # ... existing platforms ...
    NEWPLAT = "newplat"

---

from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import (
    BasePlatformAdapter, MessageEvent, MessageType, SendResult,
)

def check_newplat_requirements() -> bool:
    """Return True if dependencies are available."""
    return SOME_SDK_AVAILABLE

class NewPlatAdapter(BasePlatformAdapter):
    def __init__(self, config: PlatformConfig):
        super().__init__(config, Platform.NEWPLAT)
        # Read config from config.extra dict
        extra = config.extra or {}
        self._api_key = extra.get("api_key") or os.getenv("NEWPLAT_API_KEY", "")

    async def connect(self) -> bool:
        # Set up connection, start polling/webhook
        self._mark_connected()
        return True

    async def disconnect(self) -> None:
        self._running = False
        self._mark_disconnected()

    async def send(self, chat_id, content, reply_to=None, metadata=None):
        # Send message via platform API
        return SendResult(success=True, message_id="...")

    async def get_chat_info(self, chat_id):
        return {"name": chat_id, "type": "dm"}

---

source = self.build_source(
    chat_id=chat_id,
    chat_name=name,
    chat_type="dm",  # or "group"
    user_id=user_id,
    user_name=user_name,
)
event = MessageEvent(
    text=content,
    message_type=MessageType.TEXT,
    source=source,
    message_id=msg_id,
)
await self.handle_message(event)

---

_PLATFORM_HINTS = {
    # ...
    "newplat": (
        "You are chatting via NewPlat. It supports markdown formatting "
        "but has a 4000-character message limit."
    ),
}

---

# ค้นหาไฟล์ .py ทุกไฟล์ที่กล่าวถึงแพลตฟอร์มอ้างอิง
search_files "bluebubbles" output_mode="files_only" file_glob="*.py"

# ค้นหาไฟล์ .py ทุกไฟล์ที่กล่าวถึงแพลตฟอร์มใหม่
search_files "newplat" output_mode="files_only" file_glob="*.py"

# ไฟล์ใดๆ ในชุดแรกแต่ไม่อยู่ในชุดที่สองคือช่องว่างที่อาจเกิดขึ้น

---

async def connect(self):
    self._poll_task = asyncio.create_task(self._poll_loop())
    self._mark_connected()

async def _poll_loop(self):
    while self._running:
        messages = await self._fetch_updates()
        for msg in messages:
            await self.handle_message(self._build_event(msg))

---

async def connect(self):
    self._app = web.Application()
    self._app.router.add_post("/callback", self._handle_callback)
    # ... start aiohttp server
    self._mark_connected()

async def _handle_callback(self, request):
    event = self._build_event(await request.text())
    await self._message_queue.put(event)
    return web.Response(text="success")  # Acknowledge immediately

---

from gateway.status import acquire_scoped_lock, release_scoped_lock

async def connect(self):
    if not acquire_scoped_lock("newplat", self._token):
        logger.error("Token already in use by another profile")
        return False
    # ... connect

async def disconnect(self):
    release_scoped_lock("newplat", self._token)

---

anthropic:claude-sonnet-4-6
kimi:model-name

---

{
    "provider": "your-provider",
    "api_mode": "chat_completions",  # หรือ native mode ของคุณ
    "base_url": "https://...",
    "api_key": "...",
    "source": "env|portal|auth-store|explicit",
    "requested_provider": requested_provider,
}

---

source venv/bin/activate
python -m pytest tests/test_runtime_provider_resolution.py tests/test_cli_provider_resolution.py tests/test_cli_model_command.py tests/test_setup_model_selection.py -n0 -q

---

source venv/bin/activate
python -m pytest tests/ -n0 -q

---

source venv/bin/activate
python -m hermes_cli.main chat -q "Say hello" --provider your-provider --model your-model

---

source venv/bin/activate
python -m hermes_cli.main model
python -m hermes_cli.main setup

---

# tools/weather_tool.py
"""Weather Tool -- look up current weather for a location."""

import json
import os
import logging

logger = logging.getLogger(__name__)


# --- Availability check ---

def check_weather_requirements() -> bool:
    """Return True if the tool's dependencies are available."""
    return bool(os.getenv("WEATHER_API_KEY"))


# --- Handler ---

def weather_tool(location: str, units: str = "metric") -> str:
    """Fetch weather for a location. Returns JSON string."""
    api_key = os.getenv("WEATHER_API_KEY")
    if not api_key:
        return json.dumps({"error": "WEATHER_API_KEY not configured"})
    try:
        # ... call weather API ...
        return json.dumps({"location": location, "temp": 22, "units": units})
    except Exception as e:
        return json.dumps({"error": str(e)})


# --- Schema ---

WEATHER_SCHEMA = {
    "name": "weather",
    "description": "Get current weather for a location.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name or coordinates (e.g. 'London' or '51.5,-0.1')"
            },
            "units": {
                "type": "string",
                "enum": ["metric", "imperial"],
                "description": "Temperature units (default: metric)",
                "default": "metric"
            }
        },
        "required": ["location"]
    }
}


# --- Registration ---

from tools.registry import registry

registry.register(
    name="weather",
    toolset="weather",
    schema=WEATHER_SCHEMA,
    handler=lambda args, **kw: weather_tool(
        location=args.get("location", ""),
        units=args.get("units", "metric")),
    check_fn=check_weather_requirements,
    requires_env=["WEATHER_API_KEY"],
)

---

# If it should be available on all platforms (CLI + messaging):
_HERMES_CORE_TOOLS = [
    ...
    "weather",  # <-- add here
]

# Or create a new standalone toolset:
"weather": {
    "description": "Weather lookup tools",
    "tools": ["weather"],
    "includes": []
},

---

async def weather_tool_async(location: str) -> str:
    async with aiohttp.ClientSession() as session:
        ...
    return json.dumps(result)

registry.register(
    name="weather",
    toolset="weather",
    schema=WEATHER_SCHEMA,
    handler=lambda args, **kw: weather_tool_async(args.get("location", "")),
    check_fn=check_weather_requirements,
    is_async=True,  # registry calls _run_async() automatically
)

---

def _handle_weather(args, **kw):
    task_id = kw.get("task_id")
    return weather_tool(args.get("location", ""), task_id=task_id)

registry.register(
    name="weather",
    ...
    handler=_handle_weather,
)

---

OPTIONAL_ENV_VARS = {
    ...
    "WEATHER_API_KEY": {
        "description": "Weather API key for weather lookup",
        "prompt": "Weather API key",
        "url": "https://weatherapi.com/",
        "tools": ["weather"],
        "password": True,
    },
}

---

# Simple interface - returns final response string
response = agent.chat("Fix the bug in main.py")

# Full interface - returns dict with messages, metadata, usage stats
result = agent.run_conversation(
    user_message="Fix the bug in main.py",
    system_message=None,           # auto-built if omitted
    conversation_history=None,      # auto-loaded from session if omitted
    task_id="task_abc123"
)

---

run_conversation()
  1. Generate task_id if not provided
  2. Append user message to conversation history
  3. Build or reuse cached system prompt (prompt_builder.py)
  4. Check if preflight compression is needed (>50% context)
  5. Build API messages from conversation history
     - chat_completions: OpenAI format as-is
     - codex_responses: convert to Responses API input items
     - anthropic_messages: convert via anthropic_adapter.py
  6. Inject ephemeral prompt layers (budget warnings, context pressure)
  7. Apply prompt caching markers if on Anthropic
  8. Make interruptible API call (_interruptible_api_call)
  9. Parse response:
     - If tool_calls: execute them, append results, loop back to step 5
     - If text response: persist session, flush memory if needed, return

---

{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}

---

┌────────────────────────────────────────────────────┐
Main thread                  API thread           │
│                                                    │
│   wait on:                     HTTP POST- response ready     ───▶   to provider         │
- interrupt event                               │
- timeout                                       │
└────────────────────────────────────────────────────┘

---

for each tool_call in response.tool_calls:
    1. Resolve handler from tools/registry.py
    2. Fire pre_tool_call plugin hook
    3. Check if dangerous command (tools/approval.py)
       - If dangerous: invoke approval_callback, wait for user
    4. Execute handler with args + task_id
    5. Fire post_tool_call plugin hook
    6. Append {"role": "tool", "content": result} to history

---

┌─────────────────────────────────────────────────────────────────────┐
Entry Points│                                                                      │
CLI (cli.py)    Gateway (gateway/run.py)    ACP (acp_adapter/)Batch Runner    API Server                  Python Library└──────────┬──────────────┬───────────────────────┬───────────────────┘
           │              │                       │
           ▼              ▼                       ▼
┌─────────────────────────────────────────────────────────────────────┐
AIAgent (run_agent.py)│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐               │
│  │ Prompt       │  │ Provider     │  │ Tool         │               │
│  │ Builder      │  │ Resolution   │  │ Dispatch     │               │
 (prompt_     │   (runtime_    │   (model_      │               │
│  │  builder.py) │  │  provider.py)│  │  tools.py)   │               │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘               │
│         │                 │                 │                       │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌──────┴───────┐               │
│  │ Compression  │  │ 3 API Modes  │  │ Tool Registry││  │ & Caching    │  │ chat_compl.     (registry.py)│               │
│  │              │  │ codex_resp.  47 tools     │               │
│  │              │  │ anthropic    │  │ 19 toolsets  │               │
│  └──────────────┘  └──────────────┘  └──────────────┘               │
└─────────────────────────────────────────────────────────────────────┘
           │                                    │
           ▼                                    ▼
┌───────────────────┐              ┌──────────────────────┐
Session Storage   │              │ Tool Backends (SQLite + FTS5)   │              │ Terminal (6 backends)│ hermes_state.py   │              │ Browser (5 backends)│ gateway/session.py│Web (4 backends)└───────────────────┘              │ MCP (dynamic)File, Vision, etc.    
                                   └──────────────────────┘

---

hermes-agent/
├── run_agent.py              # AIAgent — core conversation loop (~10,700 lines)
├── cli.py                    # HermesCLI — interactive terminal UI (~10,000 lines)
├── model_tools.py            # Tool discovery, schema collection, dispatch
├── toolsets.py               # Tool groupings and platform presets
├── hermes_state.py           # SQLite session/state database with FTS5
├── hermes_constants.py       # HERMES_HOME, profile-aware paths
├── batch_runner.py           # Batch trajectory generation
├── agent/                    # Agent internals
│   ├── prompt_builder.py     # System prompt assembly
│   ├── context_engine.py     # ContextEngine ABC (pluggable)
│   ├── context_compressor.py # Default engine — lossy summarization
│   ├── prompt_caching.py     # Anthropic prompt caching
│   ├── auxiliary_client.py   # Auxiliary LLM for side tasks (vision, summarization)
│   ├── model_metadata.py     # Model context lengths, token estimation
│   ├── models_dev.py         # models.dev registry integration
│   ├── anthropic_adapter.py  # Anthropic Messages API format conversion
│   ├── display.py            # KawaiiSpinner, tool preview formatting
│   ├── skill_commands.py     # Skill slash commands
│   ├── memory_manager.py    # Memory manager orchestration
│   ├── memory_provider.py   # Memory provider ABC
│   └── trajectory.py         # Trajectory saving helpers
├── hermes_cli/               # CLI subcommands and setup
│   ├── main.py               # Entry point — all `hermes` subcommands (~6,000 lines)
│   ├── config.py             # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration
│   ├── commands.py           # COMMAND_REGISTRY — central slash command definitions
│   ├── auth.py               # PROVIDER_REGISTRY, credential resolution
│   ├── runtime_provider.py   # Provider → api_mode + credentials
│   ├── models.py             # Model catalog, provider model lists
│   ├── model_switch.py       # /model command logic (CLI + gateway shared)
│   ├── setup.py              # Interactive setup wizard (~3,100 lines)
│   ├── skin_engine.py        # CLI theming engine
│   ├── skills_config.py      # hermes skills — enable/disable per platform
│   ├── skills_hub.py         # /skills slash command
│   ├── tools_config.py       # hermes tools — enable/disable per platform
│   ├── plugins.py            # PluginManager — discovery, loading, hooks
│   ├── callbacks.py          # Terminal callbacks (clarify, sudo, approval)
│   └── gateway.py            # hermes gateway start/stop
├── tools/                    # Tool implementations (one file per tool)
│   ├── registry.py           # Central tool registry
│   ├── approval.py           # Dangerous command detection
│   ├── terminal_tool.py      # Terminal orchestration
│   ├── process_registry.py   # Background process management
│   ├── file_tools.py         # read_file, write_file, patch, search_files
│   ├── web_tools.py          # web_search, web_extract
│   ├── browser_tool.py       # 10 browser automation tools
│   ├── code_execution_tool.py # execute_code sandbox
│   ├── delegate_tool.py      # Subagent delegation
│   ├── mcp_tool.py           # MCP client (~2,200 lines)
│   ├── credential_files.py   # File-based credential passthrough
│   ├── env_passthrough.py    # Env var passthrough for sandboxes
│   ├── ansi_strip.py         # ANSI escape stripping
│   └── environments/         # Terminal backends (local, docker, ssh, modal, daytona, singularity)
├── gateway/                  # Messaging platform gateway
│   ├── run.py                # GatewayRunner — message dispatch (~9,000 lines)
│   ├── session.py            # SessionStore — conversation persistence
│   ├── delivery.py           # Outbound message delivery
│   ├── pairing.py            # DM pairing authorization
│   ├── hooks.py              # Hook discovery and lifecycle events
│   ├── mirror.py             # Cross-session message mirroring
│   ├── status.py             # Token locks, profile-scoped process tracking
│   ├── builtin_hooks/        # Always-registered hooks
│   └── platforms/            # 18 adapters: telegram, discord, slack, whatsapp,
│                             #   signal, matrix, mattermost, email, sms,
│                             #   dingtalk, feishu, wecom, wecom_callback, weixin,
│                             #   bluebubbles, qqbot, homeassistant, webhook, api_server
├── acp_adapter/              # ACP server (VS Code / Zed / JetBrains)
├── cron/                     # Scheduler (jobs.py, scheduler.py)
├── plugins/memory/           # Memory provider plugins
├── plugins/context_engine/   # Context engine plugins
├── environments/             # RL training environments (Atropos)
├── skills/                   # Bundled skills (always available)
├── optional-skills/          # Official optional skills (install explicitly)
├── website/                  # Docusaurus documentation site
└── tests/                    # Pytest suite (~3,000+ tests)

---

User input → HermesCLI.process_input()
AIAgent.run_conversation()
    → prompt_builder.build_system_prompt()
    → runtime_provider.resolve_runtime_provider()
API call (chat_completions / codex_responses / anthropic_messages)
    → tool_calls? → model_tools.handle_function_call() → loop
    → final response → display → save to SessionDB

---

Platform event → Adapter.on_message()MessageEvent
GatewayRunner._handle_message()
    → authorize user
    → resolve session key
    → create AIAgent with session history
AIAgent.run_conversation()
    → deliver response back through adapter

---

Scheduler tick → load due jobs from jobs.json
  → create fresh AIAgent (no history)
  → inject attached skills as context
  → run job prompt
  → deliver response to target platform
  → update job state and next_run

---

tools/registry.py  (no deps — imported by all tool files)
tools/*.py  (each calls registry.register() at import time)
model_tools.py  (imports tools/registry + triggers tool discovery)
run_agent.py, cli.py, batch_runner.py, environments/
RAW_BUFFERClick to expand / collapse

📄 developer-guide/acp-internals.md


sidebar_position: 2 title: "ACP Internals" description: "ACP adapter ทำงานอย่างไร: วงจรชีวิต (lifecycle), session, event bridge, การอนุมัติ (approvals), และการเรนเดอร์เครื่องมือ (tool rendering)"

ACP Internals

ACP adapter ห่อหุ้ม (wraps) AIAgent แบบ synchronous ของ Hermes ให้อยู่ใน stdio server แบบ async JSON-RPC

ไฟล์การใช้งานหลัก:

  • acp_adapter/entry.py
  • acp_adapter/server.py
  • acp_adapter/session.py
  • acp_adapter/events.py
  • acp_adapter/permissions.py
  • acp_adapter/tools.py
  • acp_adapter/auth.py
  • acp_registry/agent.json

Boot flow

hermes acp / hermes-acp / python -m acp_adapter
  -> acp_adapter.entry.main()
  -> load ~/.hermes/.env
  -> configure stderr logging
  -> construct HermesACPAgent
  -> acp.run_agent(agent)

Stdout ถูกสงวนไว้สำหรับการขนส่ง (transport) JSON-RPC ของ ACP ส่วน log ที่มนุษย์อ่านได้จะถูกส่งไปยัง stderr

ส่วนประกอบหลัก

HermesACPAgent

acp_adapter/server.py ทำหน้าที่ implement ACP agent protocol

ความรับผิดชอบ:

  • initialize / authenticate
  • methods สำหรับ new/load/resume/fork/list/cancel session
  • การดำเนินการ prompt
  • การสลับ model ของ session
  • การเชื่อมต่อ (wiring) callbacks ของ AIAgent แบบ sync เข้ากับ ACP async notifications

SessionManager

acp_adapter/session.py ติดตาม ACP sessions ที่ใช้งานอยู่

แต่ละ session จะเก็บ:

  • session_id
  • agent
  • cwd
  • model
  • history
  • cancel_event

manager นี้เป็น thread-safe และรองรับ:

  • create
  • get
  • remove
  • fork
  • list
  • cleanup
  • การอัปเดต cwd

Event bridge

acp_adapter/events.py แปลง AIAgent callbacks ให้เป็น ACP session_update events

callbacks ที่ถูกเชื่อมต่อ (Bridged callbacks):

  • tool_progress_callback
  • thinking_callback
  • step_callback
  • message_callback

เนื่องจาก AIAgent ทำงานใน worker thread ในขณะที่ ACP I/O อยู่บน main event loop, bridge จึงใช้:

asyncio.run_coroutine_threadsafe(...)

Permission bridge

acp_adapter/permissions.py ปรับเปลี่ยน (adapts) prompt การอนุมัติ terminal ที่อันตรายให้เป็น ACP permission requests

การแมป (Mapping):

  • allow_once -> Hermes once
  • allow_always -> Hermes always
  • reject options -> Hermes deny

Timeouts และ bridge failures จะปฏิเสธ (deny) เป็นค่าเริ่มต้น

Tool rendering helpers

acp_adapter/tools.py ทำการ map Hermes tools ไปยัง ACP tool kinds และสร้างเนื้อหาที่แสดงผลสำหรับ editor

ตัวอย่าง:

  • patch / write_file -> file diffs
  • terminal -> shell command text
  • read_file / search_files -> text previews
  • large results -> truncated text blocks for UI safety

Session lifecycle

new_session(cwd)
  -> create SessionState
  -> create AIAgent(platform="acp", enabled_toolsets=["hermes-acp"])
  -> bind task_id/session_id to cwd override

prompt(..., session_id)
  -> extract text from ACP content blocks
  -> reset cancel event
  -> install callbacks + approval bridge
  -> run AIAgent in ThreadPoolExecutor
  -> update session history
  -> emit final agent message chunk

Cancelation

cancel(session_id):

  • ตั้งค่า session cancel event
  • เรียกใช้ agent.interrupt() เมื่อพร้อมใช้งาน
  • ทำให้ prompt response ส่งคืนค่า stop_reason="cancelled"

Forking

fork_session() จะ deep-copy message history ไปยัง live session ใหม่ โดยคงสถานะการสนทนาไว้ ในขณะที่ให้ session ID และ cwd ใหม่แก่ fork นั้น

Provider/auth behavior

ACP ไม่ได้ implement auth store ของตัวเอง

แต่จะนำ runtime resolver ของ Hermes มาใช้ซ้ำ:

  • acp_adapter/auth.py
  • hermes_cli/runtime_provider.py

ดังนั้น ACP จึงโฆษณาและใช้ provider/credentials ของ Hermes ที่ถูกตั้งค่าไว้ในปัจจุบัน

Working directory binding

ACP sessions มี cwd ของ editor ติดมาด้วย

session manager จะผูก (bind) cwd นั้นเข้ากับ ACP session ID ผ่านการ override terminal/file แบบ task-scoped ดังนั้นเครื่องมือ file และ terminal จึงทำงานแบบ relative ต่อ editor workspace

Duplicate same-name tool calls

event bridge จะติดตาม tool IDs แบบ FIFO ต่อชื่อเครื่องมือ ไม่ใช่แค่ ID เดียวต่อชื่อ สิ่งนี้สำคัญสำหรับ:

  • parallel same-name calls
  • repeated same-name calls in one step

หากไม่มีคิว FIFO, completion events จะผูกติดกับ tool invocation ที่ผิด

Approval callback restoration

ACP จะติดตั้ง approval callback ชั่วคราวบน terminal tool ในระหว่างการดำเนินการ prompt จากนั้นจึงกู้คืน callback ก่อนหน้า การทำเช่นนี้ช่วยป้องกันไม่ให้ approval handlers เฉพาะ session ของ ACP ถูกติดตั้งแบบ global อย่างถาวร

Current limitations

  • ACP sessions เป็นแบบ process-local จากมุมมองของ ACP server
  • non-text prompt blocks ถูกละเว้นสำหรับการดึงข้อความคำขอในปัจจุบัน
  • UX ที่เฉพาะเจาะจงสำหรับ editor จะแตกต่างกันไปตามการ implement ของ ACP client

Related files

  • tests/acp/ - ACP test suite
  • toolsets.py - hermes-acp toolset definition
  • hermes_cli/main.py - hermes acp CLI subcommand
  • pyproject.toml - [acp] optional dependency + hermes-acp script

📄 developer-guide/adding-platform-adapters.md


sidebar_position: 9

การเพิ่ม Platform Adapter

คู่มือนี้ครอบคลุมวิธีการเพิ่มแพลตฟอร์มการส่งข้อความใหม่ให้กับ Hermes gateway Platform adapter ทำหน้าที่เชื่อมต่อ Hermes เข้ากับบริการส่งข้อความภายนอก (เช่น Telegram, Discord, WeCom เป็นต้น) เพื่อให้ผู้ใช้สามารถโต้ตอบกับ agent ผ่านบริการนั้นได้

:::tip การเพิ่ม platform adapter จะเกี่ยวข้องกับไฟล์มากกว่า 20 ไฟล์ ทั้งในส่วนของโค้ด (code), การตั้งค่า (config), และเอกสาร (docs) โปรดใช้คู่มือนี้เป็น checklist เนื่องจากไฟล์ adapter เองมักจะเป็นเพียง 40% ของงานทั้งหมด :::

ภาพรวมสถาปัตยกรรม (Architecture Overview)

User ↔ Messaging Platform ↔ Platform Adapter ↔ Gateway Runner ↔ AIAgent

adapter ทุกตัวจะขยาย (extend) BasePlatformAdapter จาก gateway/platforms/base.py และต้องมีการ implement:

  • connect() - การสร้างการเชื่อมต่อ (เช่น WebSocket, long-poll, HTTP server เป็นต้น)
  • disconnect() - การปิดระบบอย่างสะอาด (Clean shutdown)
  • send() - การส่งข้อความแบบ text ไปยัง chat
  • send_typing() - การแสดงสถานะกำลังพิมพ์ (optional)
  • get_chat_info() - การส่งคืน metadata ของ chat

ข้อความขาเข้า (Inbound messages) จะถูกรับโดย adapter และส่งต่อไปผ่าน self.handle_message(event) ซึ่ง base class จะทำหน้าที่กำหนดเส้นทาง (route) ไปยัง gateway runner

Checklist แบบขั้นตอน (Step-by-Step Checklist)

1. Platform Enum

เพิ่มแพลตฟอร์มของคุณเข้าไปใน Platform enum ในไฟล์ gateway/config.py:

class Platform(str, Enum):
    # ... existing platforms ...
    NEWPLAT = "newplat"

2. Adapter File

สร้างไฟล์ gateway/platforms/newplat.py:

from gateway.config import Platform, PlatformConfig
from gateway.platforms.base import (
    BasePlatformAdapter, MessageEvent, MessageType, SendResult,
)

def check_newplat_requirements() -> bool:
    """Return True if dependencies are available."""
    return SOME_SDK_AVAILABLE

class NewPlatAdapter(BasePlatformAdapter):
    def __init__(self, config: PlatformConfig):
        super().__init__(config, Platform.NEWPLAT)
        # Read config from config.extra dict
        extra = config.extra or {}
        self._api_key = extra.get("api_key") or os.getenv("NEWPLAT_API_KEY", "")

    async def connect(self) -> bool:
        # Set up connection, start polling/webhook
        self._mark_connected()
        return True

    async def disconnect(self) -> None:
        self._running = False
        self._mark_disconnected()

    async def send(self, chat_id, content, reply_to=None, metadata=None):
        # Send message via platform API
        return SendResult(success=True, message_id="...")

    async def get_chat_info(self, chat_id):
        return {"name": chat_id, "type": "dm"}

สำหรับข้อความขาเข้า ให้สร้าง MessageEvent และเรียกใช้ self.handle_message(event):

source = self.build_source(
    chat_id=chat_id,
    chat_name=name,
    chat_type="dm",  # or "group"
    user_id=user_id,
    user_name=user_name,
)
event = MessageEvent(
    text=content,
    message_type=MessageType.TEXT,
    source=source,
    message_id=msg_id,
)
await self.handle_message(event)

3. Gateway Config (gateway/config.py)

มีจุดที่ต้องแก้ไข 3 จุด:

  1. get_connected_platforms() - เพิ่มการตรวจสอบ credentials ที่จำเป็นสำหรับแพลตฟอร์มของคุณ
  2. load_gateway_config() - เพิ่มรายการ env map: Platform.NEWPLAT: "NEWPLAT_TOKEN"
  3. _apply_env_overrides() - แมปตัวแปร env ทั้งหมดที่ขึ้นต้นด้วย NEWPLAT_* ไปยัง config

4. Gateway Runner (gateway/run.py)

มีจุดที่ต้องแก้ไข 5 จุด:

  1. _create_adapter() - เพิ่ม branch elif platform == Platform.NEWPLAT:
  2. _is_user_authorized() allowed_users map - เพิ่ม Platform.NEWPLAT: "NEWPLAT_ALLOWED_USERS"
  3. _is_user_authorized() allow_all map - เพิ่ม Platform.NEWPLAT: "NEWPLAT_ALLOW_ALL_USERS"
  4. Early env check _any_allowlist tuple - เพิ่ม "NEWPLAT_ALLOWED_USERS"
  5. Early env check _allow_all tuple - เพิ่ม "NEWPLAT_ALLOW_ALL_USERS"
  6. _UPDATE_ALLOWED_PLATFORMS frozenset - เพิ่ม Platform.NEWPLAT

5. Cross-Platform Delivery

  1. gateway/platforms/webhook.py - เพิ่ม "newplat" เข้าไปใน tuple ของ delivery type
  2. cron/scheduler.py - เพิ่มเข้าไปใน _KNOWN_DELIVERY_PLATFORMS frozenset และ _deliver_result() platform map

6. CLI Integration

  1. hermes_cli/config.py - เพิ่มตัวแปร NEWPLAT_* ทั้งหมดเข้าไปใน _EXTRA_ENV_KEYS
  2. hermes_cli/gateway.py - เพิ่ม entry ในรายการ _PLATFORMS โดยมี key, label, emoji, token_var, setup_instructions, และ vars
  3. hermes_cli/platforms.py - เพิ่ม entry PlatformInfo พร้อม label และ default_toolset (ใช้โดย skills_config และ tools_config TUIs)
  4. hermes_cli/setup.py - เพิ่มฟังก์ชัน _setup_newplat() (สามารถ delegate ไปยัง gateway.py ได้) และเพิ่ม tuple เข้าไปในรายการ messaging platforms
  5. hermes_cli/status.py - เพิ่ม entry การตรวจจับแพลตฟอร์ม: "NewPlat": ("NEWPLAT_TOKEN", "NEWPLAT_HOME_CHANNEL")
  6. hermes_cli/dump.py - เพิ่ม "newplat": "NEWPLAT_TOKEN" เข้าไปใน dict การตรวจจับแพลตฟอร์ม

7. Tools

  1. tools/send_message_tool.py - เพิ่ม "newplat": Platform.NEWPLAT เข้าไปใน platform map
  2. tools/cronjob_tools.py - เพิ่ม newplat เข้าไปในสตริงคำอธิบาย delivery target

8. Toolsets

  1. toolsets.py - เพิ่มคำจำกัดความ toolset "hermes-newplat" พร้อม _HERMES_CORE_TOOLS
  2. toolsets.py - เพิ่ม "hermes-newplat" เข้าไปในรายการ includes ของ "hermes-gateway"

9. Optional: Platform Hints

agent/prompt_builder.py - หากแพลตฟอร์มของคุณมีข้อจำกัดในการแสดงผลเฉพาะ (เช่น ไม่รองรับ markdown, มีข้อจำกัดความยาวข้อความ เป็นต้น) ให้เพิ่ม entry เข้าไปใน _PLATFORM_HINTS dict สิ่งนี้จะฉีดคำแนะนำเฉพาะแพลตฟอร์มเข้าไปใน system prompt:

_PLATFORM_HINTS = {
    # ...
    "newplat": (
        "You are chatting via NewPlat. It supports markdown formatting "
        "but has a 4000-character message limit."
    ),
}

ไม่ใช่ทุกแพลตฟอร์มที่จำเป็นต้องมี hints - ให้เพิ่มก็ต่อเมื่อพฤติกรรมของ agent ควรแตกต่างออกไปเท่านั้น

10. Tests

สร้างไฟล์ tests/gateway/test_newplat.py เพื่อครอบคลุม:

  • การสร้าง adapter จาก config
  • การสร้าง message event
  • เมธอด send (จำลอง API ภายนอก)
  • คุณสมบัติเฉพาะแพลตฟอร์ม (เช่น การเข้ารหัส, การกำหนดเส้นทาง เป็นต้น)

11. Documentation

FileWhat to add
website/docs/user-guide/messaging/newplat.mdหน้าตั้งค่าแพลตฟอร์มแบบเต็ม
website/docs/user-guide/messaging/index.mdตารางเปรียบเทียบแพลตฟอร์ม, แผนภาพสถาปัตยกรรม, ตาราง toolsets, ส่วนความปลอดภัย, ลิงก์ขั้นตอนถัดไป
website/docs/reference/environment-variables.mdตัวแปร env ทั้งหมดที่ขึ้นต้นด้วย NEWPLAT_*
website/docs/reference/toolsets-reference.mdtoolset hermes-newplat
website/docs/integrations/index.mdลิงก์แพลตฟอร์ม
website/sidebars.tsEntry ใน sidebar สำหรับหน้าเอกสาร
website/docs/developer-guide/architecture.mdจำนวน adapter + รายการ
website/docs/developer-guide/gateway-internals.mdรายการไฟล์ adapter

Parity Audit

ก่อนที่จะทำ PR ของแพลตฟอร์มใหม่ให้เสร็จสมบูรณ์ ให้ทำการ parity audit เทียบกับแพลตฟอร์มที่กำหนดไว้แล้ว:

# ค้นหาไฟล์ .py ทุกไฟล์ที่กล่าวถึงแพลตฟอร์มอ้างอิง
search_files "bluebubbles" output_mode="files_only" file_glob="*.py"

# ค้นหาไฟล์ .py ทุกไฟล์ที่กล่าวถึงแพลตฟอร์มใหม่
search_files "newplat" output_mode="files_only" file_glob="*.py"

# ไฟล์ใดๆ ในชุดแรกแต่ไม่อยู่ในชุดที่สองคือช่องว่างที่อาจเกิดขึ้น

ทำซ้ำสำหรับไฟล์ .md และ .ts ตรวจสอบช่องว่างแต่ละช่อง - เป็นการแจงนับแพลตฟอร์ม (ต้องอัปเดต) หรือเป็น reference เฉพาะแพลตฟอร์ม (ข้ามไปได้)?

Common Patterns

Long-Poll Adapters

หาก adapter ของคุณใช้ long-polling (เช่น Telegram หรือ Weixin) ให้ใช้ task แบบ polling loop:

async def connect(self):
    self._poll_task = asyncio.create_task(self._poll_loop())
    self._mark_connected()

async def _poll_loop(self):
    while self._running:
        messages = await self._fetch_updates()
        for msg in messages:
            await self.handle_message(self._build_event(msg))

Callback/Webhook Adapters

หากแพลตฟอร์มส่งข้อความมายัง endpoint ของคุณ (เช่น WeCom Callback) ให้รัน HTTP server:

async def connect(self):
    self._app = web.Application()
    self._app.router.add_post("/callback", self._handle_callback)
    # ... start aiohttp server
    self._mark_connected()

async def _handle_callback(self, request):
    event = self._build_event(await request.text())
    await self._message_queue.put(event)
    return web.Response(text="success")  # Acknowledge immediately

สำหรับแพลตฟอร์มที่มีกำหนดเวลาตอบกลับที่จำกัด (เช่น ข้อจำกัด 5 วินาทีของ WeCom) ให้ทำการ acknowledge ทันทีและส่ง reply ของ agent อย่างเชิงรุกผ่าน API ในภายหลัง การทำงานของ agent อาจใช้เวลา 3-30 นาที - การตอบกลับแบบ inline ภายในหน้าต่าง callback response จึงไม่สามารถทำได้

Token Locks

หาก adapter ถือการเชื่อมต่อที่คงอยู่ด้วย credential เฉพาะตัว ให้เพิ่ม scoped lock เพื่อป้องกันไม่ให้โปรไฟล์สองโปรไฟล์ใช้ credential เดียวกัน:

from gateway.status import acquire_scoped_lock, release_scoped_lock

async def connect(self):
    if not acquire_scoped_lock("newplat", self._token):
        logger.error("Token already in use by another profile")
        return False
    # ... connect

async def disconnect(self):
    release_scoped_lock("newplat", self._token)

Reference Implementations

AdapterPatternComplexityGood reference for
bluebubbles.pyREST + webhookMediumSimple REST API integration
weixin.pyLong-poll + CDNHighMedia handling, encryption
wecom_callback.pyCallback/webhookMediumHTTP server, AES crypto, multi-app
telegram.pyLong-poll + Bot APIHighFull-featured adapter with groups, threads

📄 developer-guide/adding-providers.md


sidebar_position: 5 title: "Adding Providers" description: "How to add a new inference provider to Hermes Agent - auth, runtime resolution, CLI flows, adapters, tests, and docs"

การเพิ่ม Providers

Hermes สามารถสื่อสารกับ endpoint ที่รองรับ OpenAI ได้ทุกประเภทผ่าน custom provider path อยู่แล้ว ดังนั้นจึงไม่จำเป็นต้องเพิ่ม built-in provider เว้นแต่ว่าคุณต้องการประสบการณ์ผู้ใช้ (UX) ระดับ First-class สำหรับบริการนั้นๆ:

  • การจัดการ auth หรือ token refresh เฉพาะ provider
  • แคตตาล็อกโมเดลที่คัดสรรมาแล้ว
  • การตั้งค่า / รายการเมนู hermes model
  • ชื่อแทน (aliases) สำหรับ syntax provider:model
  • API shape ที่ไม่ใช่ OpenAI ซึ่งต้องใช้ adapter

หาก provider นั้นเป็นเพียง "base URL และ API key ที่รองรับ OpenAI อีกตัว" การใช้ custom provider ที่ตั้งชื่อจะเพียงพอแล้ว

แนวคิดหลัก (The mental model)

built-in provider จะต้องทำงานร่วมกันในหลายชั้น:

  1. hermes_cli/auth.py เป็นตัวตัดสินว่า credentials ถูกค้นหาอย่างไร
  2. hermes_cli/runtime_provider.py แปลงสิ่งนั้นเป็นข้อมูล runtime:
    • provider
    • api_mode
    • base_url
    • api_key
    • source
  3. run_agent.py ใช้ api_mode เพื่อตัดสินใจว่าจะสร้างและส่ง request อย่างไร
  4. hermes_cli/models.py และ hermes_cli/main.py ทำให้ provider ปรากฏใน CLI (hermes_cli/setup.py จะมอบหมายงานให้ main.py โดยอัตโนมัติ - ไม่ต้องแก้ไขอะไร)
  5. agent/auxiliary_client.py และ agent/model_metadata.py ช่วยให้งานเสริมและระบบการจัดสรร token ทำงานได้

ส่วนสำคัญที่ต้องทำความเข้าใจคือ api_mode

  • Provider ส่วนใหญ่ใช้ chat_completions
  • Codex ใช้ codex_responses
  • Anthropic ใช้ anthropic_messages
  • โปรโตคอลใหม่ที่ไม่ใช่ OpenAI มักจะหมายถึงการเพิ่ม adapter ใหม่และ branch api_mode ใหม่

เลือกเส้นทางการ implement ก่อน

Path A - OpenAI-compatible provider

ใช้เส้นทางนี้เมื่อ provider นั้นรับ request รูปแบบ chat-completions มาตรฐาน

งานทั่วไปที่ต้องทำ:

  • เพิ่ม metadata สำหรับ auth
  • เพิ่ม model catalog / aliases
  • เพิ่ม runtime resolution
  • เพิ่มการเชื่อมต่อเมนู CLI
  • เพิ่มค่า default สำหรับ aux-model
  • เพิ่ม tests และเอกสารสำหรับผู้ใช้

โดยทั่วไปแล้วคุณไม่จำเป็นต้องมี adapter หรือ api_mode ใหม่

Path B - Native provider

ใช้เส้นทางนี้เมื่อ provider นั้นไม่ได้ทำงานเหมือน OpenAI chat completions

ตัวอย่างที่อยู่ในโค้ดวันนี้:

  • codex_responses
  • anthropic_messages

เส้นทางนี้รวมทุกอย่างจาก Path A บวกกับ:

  • provider adapter ใน agent/
  • branches ใน run_agent.py สำหรับการสร้าง request, dispatch, การดึง usage, การจัดการ interrupt, และการ normalize response
  • adapter tests

รายการตรวจสอบไฟล์ (File checklist)

จำเป็นสำหรับ built-in provider ทุกตัว

  1. hermes_cli/auth.py
  2. hermes_cli/models.py
  3. hermes_cli/runtime_provider.py
  4. hermes_cli/main.py
  5. agent/auxiliary_client.py
  6. agent/model_metadata.py
  7. tests
  8. เอกสารสำหรับผู้ใช้ภายใต้ website/docs/

:::tip hermes_cli/setup.py ไม่ต้องมีการเปลี่ยนแปลง ตัว setup wizard จะมอบหมายการเลือก provider/model ให้กับ select_provider_and_model() ใน main.py - provider ใดๆ ที่เพิ่มเข้าไปที่นั่นจะพร้อมใช้งานใน hermes setup โดยอัตโนมัติ :::

เพิ่มเติมสำหรับ native / non-OpenAI providers

  1. agent/<provider>_adapter.py
  2. run_agent.py
  3. pyproject.toml หากต้องการ provider SDK

ขั้นตอนที่ 1: เลือก provider id หลักตัวเดียว

เลือก provider id เพียงตัวเดียวและใช้มันทุกที่

ตัวอย่างจาก repo:

  • openai-codex
  • kimi-coding
  • minimax-cn

id เดียวกันนี้ควรปรากฏใน:

  • PROVIDER_REGISTRY ใน hermes_cli/auth.py
  • _PROVIDER_LABELS ใน hermes_cli/models.py
  • _PROVIDER_ALIASES ในทั้ง hermes_cli/auth.py และ hermes_cli/models.py
  • ตัวเลือก CLI --provider ใน hermes_cli/main.py
  • branches การเลือก setup / model
  • ค่า default สำหรับ auxiliary-model
  • tests

หาก id แตกต่างกันระหว่างไฟล์เหล่านั้น provider จะรู้สึกเหมือนถูกต่อไม่สมบูรณ์: auth อาจทำงานได้ในขณะที่ /model, setup, หรือ runtime resolution มองข้ามมันไปอย่างเงียบๆ

ขั้นตอนที่ 2: เพิ่ม auth metadata ใน hermes_cli/auth.py

สำหรับ provider ที่ใช้ API-key ให้เพิ่มรายการ ProviderConfig ใน PROVIDER_REGISTRY ด้วย:

  • id
  • name
  • auth_type="api_key"
  • inference_base_url
  • api_key_env_vars
  • base_url_env_var (ทางเลือก)

และเพิ่ม aliases ให้กับ _PROVIDER_ALIASES

ใช้ provider ที่มีอยู่เป็นแม่แบบ:

  • simple API-key path: Z.AI, MiniMax
  • API-key path พร้อมการตรวจจับ endpoint: Kimi, Z.AI
  • native token resolution: Anthropic
  • OAuth / auth-store path: Nous, OpenAI Codex

คำถามที่ต้องตอบในส่วนนี้:

  • Hermes ควรตรวจสอบ env vars อะไรบ้าง และในลำดับความสำคัญใด?
  • provider ต้องการการ override base-URL หรือไม่?
  • ต้องการการ probing endpoint หรือ token refresh หรือไม่?
  • ข้อความแสดงข้อผิดพลาดของ auth ควรเป็นอย่างไรเมื่อ credentials ขาดหายไป?

หาก provider ต้องการอะไรที่มากกว่า "การค้นหา API key" ให้เพิ่ม credential resolver เฉพาะแทนการยัด logic เข้าไปใน branches ที่ไม่เกี่ยวข้อง

ขั้นตอนที่ 3: เพิ่ม model catalog และ aliases ใน hermes_cli/models.py

อัปเดต provider catalog เพื่อให้ provider ทำงานได้ทั้งในเมนูและใน syntax provider:model

การแก้ไขทั่วไป:

  • _PROVIDER_MODELS
  • _PROVIDER_LABELS
  • _PROVIDER_ALIASES
  • ลำดับการแสดง provider ภายใน list_available_providers()
  • provider_model_ids() หาก provider รองรับการดึง /models แบบ live

หาก provider เปิดเผยรายการโมเดลแบบ live ให้เลือกใช้แบบนั้นก่อนและเก็บ _PROVIDER_MODELS เป็น fallback แบบ static

ไฟล์นี้ยังเป็นตัวที่ทำให้ inputs อย่างนี้ทำงานได้:

anthropic:claude-sonnet-4-6
kimi:model-name

หาก aliases หายไปที่นี่ provider อาจทำการ auth ได้อย่างถูกต้อง แต่ยังคงล้มเหลวในการ parse ใน /model

ขั้นตอนที่ 4: Resolve runtime data ใน hermes_cli/runtime_provider.py

resolve_runtime_provider() คือ path ที่ใช้ร่วมกันโดย CLI, gateway, cron, ACP, และ helper clients

เพิ่ม branch ที่คืนค่า dict ที่มีอย่างน้อย:

{
    "provider": "your-provider",
    "api_mode": "chat_completions",  # หรือ native mode ของคุณ
    "base_url": "https://...",
    "api_key": "...",
    "source": "env|portal|auth-store|explicit",
    "requested_provider": requested_provider,
}

หาก provider นั้นรองรับ OpenAI-compatible, api_mode ควรยังคงเป็น chat_completions

ระวังเรื่องลำดับความสำคัญของ API-key โดย Hermes มี logic อยู่แล้วเพื่อป้องกันการรั่วไหลของ OpenRouter key ไปยัง endpoint ที่ไม่เกี่ยวข้อง provider ใหม่ควรมีความชัดเจนในระดับเดียวกันว่า key ใดจะถูกส่งไปยัง base URL ใด

ขั้นตอนที่ 5: Wire CLI ใน hermes_cli/main.py

provider จะไม่สามารถค้นพบได้จนกว่าจะปรากฏใน flow แบบ interactive ของ hermes model

อัปเดตส่วนเหล่านี้ใน hermes_cli/main.py:

  • dict provider_labels
  • list providers ใน select_provider_and_model()
  • provider dispatch (if selected_provider == ...)
  • ตัวเลือก argument --provider
  • ตัวเลือก login/logout หาก provider รองรับ flow เหล่านั้น
  • ฟังก์ชัน _model_flow_<provider>() หรือใช้ซ้ำ _model_flow_api_key_provider() หากเหมาะสม

:::tip hermes_cli/setup.py ไม่ต้องมีการเปลี่ยนแปลง - มันเรียก select_provider_and_model() จาก main.py ดังนั้น provider ใหม่ของคุณจะปรากฏทั้งใน hermes model และ hermes setup โดยอัตโนมัติ :::

ขั้นตอนที่ 6: ทำให้ auxiliary calls ทำงานได้

มีสองไฟล์ที่สำคัญในส่วนนี้:

agent/auxiliary_client.py

เพิ่ม aux model default ที่ราคาถูก/เร็ว ใน _API_KEY_PROVIDER_AUX_MODELS หากนี่คือ direct API-key provider

Auxiliary tasks รวมถึงสิ่งต่างๆ เช่น:

  • vision summarization
  • web extraction summarization
  • context compression summaries
  • session-search summaries
  • memory flushes

หาก provider ไม่มี aux default ที่สมเหตุสมผล งานเสริมอาจล้มเหลวอย่างมากหรือใช้ main model ที่มีราคาสูงโดยไม่คาดคิด

agent/model_metadata.py

เพิ่ม context lengths สำหรับโมเดลของ provider เพื่อให้ token budgeting, compression thresholds, และ limits อยู่ในระดับที่เหมาะสม

ขั้นตอนที่ 7: หาก provider เป็น native ให้เพิ่ม adapter และ support ใน run_agent.py

หาก provider ไม่ใช่ chat completions ธรรมดา ให้แยก logic เฉพาะ provider ไปไว้ใน agent/<provider>_adapter.py

ให้ run_agent.py มุ่งเน้นไปที่การ orchestrate มันควรเรียกใช้ helper ของ adapter ไม่ใช่การสร้าง payload ของ provider ด้วยตนเองทั่วทั้งไฟล์

Native provider มักจะต้องมีการทำงานในส่วนเหล่านี้:

ไฟล์ adapter ใหม่

ความรับผิดชอบทั่วไป:

  • build SDK / HTTP client
  • resolve tokens
  • convert ข้อความ conversation รูปแบบ OpenAI เป็น request format ของ provider
  • convert tool schemas หากจำเป็น
  • normalize response ของ provider กลับไปเป็นสิ่งที่ run_agent.py คาดหวัง
  • extract usage และ finish-reason data

run_agent.py

ค้นหา api_mode และตรวจสอบทุกจุด switch อย่างน้อยที่สุดให้ตรวจสอบ:

  • __init__ เลือก api_mode ใหม่
  • การสร้าง client ทำงานสำหรับ provider
  • _build_api_kwargs() รู้ว่าจะ format request อย่างไร
  • _interruptible_api_call() dispatch ไปยัง client call ที่ถูกต้อง
  • path ของ interrupt / client rebuild ทำงาน
  • response validation ยอมรับ shape ของ provider
  • การ extract finish-reason ถูกต้อง
  • การ extract token-usage ถูกต้อง
  • การเปิดใช้งาน fallback-model สามารถสลับไปยัง provider ใหม่ได้อย่างสะอาด
  • path ของ summary-generation และ memory-flush ยังคงทำงาน

นอกจากนี้ให้ค้นหา self.client. ใน run_agent.py ด้วย เส้นทางโค้ดใดๆ ที่สมมติว่ามี standard OpenAI client อยู่ อาจพังเมื่อ native provider ใช้ client object ที่แตกต่างออกไป หรือ self.client = None

Prompt caching และ provider-specific request fields

Prompt caching และ knobs เฉพาะ provider นั้นง่ายต่อการเกิด regression

ตัวอย่างที่มีอยู่แล้ว:

  • Anthropic มี path สำหรับ prompt-caching แบบ native
  • OpenRouter ได้รับ fields สำหรับ provider-routing
  • ไม่ใช่ว่าทุก provider ควรได้รับทุก option ฝั่ง request

เมื่อคุณเพิ่ม native provider ให้ตรวจสอบซ้ำว่า Hermes ส่งเฉพาะ fields ที่ provider เข้าใจจริงเท่านั้น

ขั้นตอนที่ 8: Tests

อย่างน้อยที่สุด ให้แตะ tests ที่คอยป้องกันการเชื่อมต่อ provider

สถานที่ทั่วไป:

  • tests/test_runtime_provider_resolution.py
  • tests/test_cli_provider_resolution.py
  • tests/test_cli_model_command.py
  • tests/test_setup_model_selection.py
  • tests/test_provider_parity.py
  • tests/test_run_agent.py
  • tests/test_<provider>_adapter.py สำหรับ native provider

สำหรับตัวอย่างที่ใช้สำหรับ docs เท่านั้น ชุดไฟล์ที่แน่นอนอาจแตกต่างกัน จุดประสงค์คือการครอบคลุม:

  • auth resolution
  • CLI menu / provider selection
  • runtime provider resolution
  • agent execution path
  • provider:model parsing
  • การแปลงข้อความเฉพาะ adapter ใดๆ

รัน tests โดยปิด xdist:

source venv/bin/activate
python -m pytest tests/test_runtime_provider_resolution.py tests/test_cli_provider_resolution.py tests/test_cli_model_command.py tests/test_setup_model_selection.py -n0 -q

สำหรับการเปลี่ยนแปลงที่ลึกกว่า ให้รัน full suite ก่อน push:

source venv/bin/activate
python -m pytest tests/ -n0 -q

ขั้นตอนที่ 9: การตรวจสอบแบบ Live

หลังจาก tests ให้รัน smoke test จริง

source venv/bin/activate
python -m hermes_cli.main chat -q "Say hello" --provider your-provider --model your-model

ทดสอบ flow แบบ interactive ด้วยหากคุณเปลี่ยนเมนู:

source venv/bin/activate
python -m hermes_cli.main model
python -m hermes_cli.main setup

สำหรับ native providers ให้ตรวจสอบ tool call อย่างน้อยหนึ่งครั้งด้วย ไม่ใช่แค่ response ข้อความธรรมดา

ขั้นตอนที่ 10: อัปเดตเอกสารสำหรับผู้ใช้

หาก provider มีจุดประสงค์ที่จะออกเป็นตัวเลือก First-class ให้ทำการอัปเดตเอกสารผู้ใช้ด้วย:

  • website/docs/getting-started/quickstart.md
  • website/docs/user-guide/configuration.md
  • website/docs/reference/environment-variables.md

นักพัฒนาสามารถเชื่อมต่อ provider ได้อย่างสมบูรณ์แบบ แต่ยังคงทำให้ผู้ใช้ไม่สามารถค้นพบ env vars หรือ setup flow ที่จำเป็นได้

OpenAI-compatible provider checklist

ใช้ส่วนนี้หาก provider เป็น chat completions มาตรฐาน

  • เพิ่ม ProviderConfig ใน hermes_cli/auth.py
  • เพิ่ม aliases ใน hermes_cli/auth.py และ hermes_cli/models.py
  • เพิ่ม model catalog ใน hermes_cli/models.py
  • เพิ่ม runtime branch ใน hermes_cli/runtime_provider.py
  • เพิ่ม CLI wiring ใน hermes_cli/main.py (setup.py จะสืบทอดโดยอัตโนมัติ)
  • เพิ่ม aux model ใน agent/auxiliary_client.py
  • เพิ่ม context lengths ใน agent/model_metadata.py
  • อัปเดต runtime / CLI tests
  • อัปเดตเอกสารผู้ใช้

Native provider checklist

ใช้ส่วนนี้เมื่อ provider ต้องการ path โปรโตคอลใหม่

  • ทุกอย่างใน OpenAI-compatible checklist
  • เพิ่ม adapter ใน agent/<provider>_adapter.py
  • รองรับ api_mode ใหม่ใน run_agent.py
  • path ของ interrupt / rebuild ทำงาน
  • การ extract usage และ finish-reason ทำงาน
  • fallback path ทำงาน
  • เพิ่ม adapter tests
  • live smoke test ผ่าน

ข้อผิดพลาดที่พบบ่อย (Common pitfalls)

1. เพิ่ม provider ใน auth แต่ไม่เพิ่มใน model parsing

สิ่งนี้ทำให้ credentials resolve ได้อย่างถูกต้อง แต่ inputs /model และ provider:model ล้มเหลว

2. ลืมว่า config["model"] สามารถเป็น string หรือ dict

โค้ดส่วนใหญ่ที่เลือก provider ต้อง normalize ทั้งสองรูปแบบ

3. สมมติว่าจำเป็นต้องมี built-in provider

หากบริการนั้นรองรับ OpenAI-compatible, custom provider อาจแก้ปัญหาผู้ใช้ได้แล้วด้วยการบำรุงรักษาน้อยกว่า

4. ลืม auxiliary paths

main chat path อาจทำงานได้ แต่ summarization, memory flushes, หรือ vision helpers ล้มเหลว เพราะ aux routing ไม่เคยถูกอัปเดต

5. native-provider branches ที่ซ่อนอยู่ใน run_agent.py

ค้นหา api_mode และ self.client.. อย่าสมมติว่า request path ที่ชัดเจนคือ path เดียว

6. การส่ง OpenRouter-only knobs ไปยัง provider อื่น

Fields อย่าง provider routing ควรอยู่เฉพาะบน provider ที่รองรับมันเท่านั้น

7. อัปเดต hermes model แต่ไม่เปลี่ยน hermes setup

ทั้งสอง flow ต้องรับรู้เกี่ยวกับ provider

เป้าหมายการค้นหาที่ดีขณะ implement

หากคุณกำลังค้นหาสถานที่ทั้งหมดที่ provider เข้ามาเกี่ยวข้อง ให้ค้นหา symbols เหล่านี้:

  • PROVIDER_REGISTRY
  • _PROVIDER_ALIASES
  • _PROVIDER_MODELS
  • resolve_runtime_provider
  • _model_flow_
  • select_provider_and_model
  • api_mode
  • _API_KEY_PROVIDER_AUX_MODELS
  • self.client.

เอกสารที่เกี่ยวข้อง


📄 developer-guide/adding-tools.md


sidebar_position: 2 title: "การเพิ่มเครื่องมือ (Adding Tools)" description: "วิธีการเพิ่มเครื่องมือใหม่ให้กับ Hermes Agent - schemas, handlers, registration, และ toolsets"

การเพิ่มเครื่องมือ (Adding Tools)

ก่อนเขียนเครื่องมือ ให้ถามตัวเองว่า: สิ่งนี้ควรเป็น skill แทนหรือไม่?

ให้เป็น Skill เมื่อความสามารถนั้นสามารถแสดงออกได้ในรูปแบบของคำสั่ง (instructions) + shell commands + เครื่องมือที่มีอยู่แล้ว (เช่น การค้นหา arXiv, git workflows, การจัดการ Docker, การประมวลผล PDF)

ให้เป็น Tool เมื่อต้องมีการผสานรวมแบบ end-to-end กับ API keys, custom processing logic, การจัดการ binary data, หรือการ streaming (เช่น browser automation, TTS, vision analysis)

ภาพรวม (Overview)

การเพิ่มเครื่องมือเกี่ยวข้องกับ 2 ไฟล์:

  1. tools/your_tool.py - handler, schema, check function, registry.register() call
  2. toolsets.py - เพิ่มชื่อเครื่องมือใน _HERMES_CORE_TOOLS (หรือ toolset เฉพาะ)

ไฟล์ tools/*.py ใดๆ ที่มีคำสั่ง registry.register() ระดับบนสุด จะถูกค้นพบโดยอัตโนมัติเมื่อเริ่มต้นทำงาน ไม่จำเป็นต้องสร้างรายการ import ด้วยตนเอง

ขั้นตอนที่ 1: สร้างไฟล์เครื่องมือ (Create the Tool File)

ไฟล์เครื่องมือทุกไฟล์มีโครงสร้างเดียวกัน:

# tools/weather_tool.py
"""Weather Tool -- look up current weather for a location."""

import json
import os
import logging

logger = logging.getLogger(__name__)


# --- Availability check ---

def check_weather_requirements() -> bool:
    """Return True if the tool's dependencies are available."""
    return bool(os.getenv("WEATHER_API_KEY"))


# --- Handler ---

def weather_tool(location: str, units: str = "metric") -> str:
    """Fetch weather for a location. Returns JSON string."""
    api_key = os.getenv("WEATHER_API_KEY")
    if not api_key:
        return json.dumps({"error": "WEATHER_API_KEY not configured"})
    try:
        # ... call weather API ...
        return json.dumps({"location": location, "temp": 22, "units": units})
    except Exception as e:
        return json.dumps({"error": str(e)})


# --- Schema ---

WEATHER_SCHEMA = {
    "name": "weather",
    "description": "Get current weather for a location.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name or coordinates (e.g. 'London' or '51.5,-0.1')"
            },
            "units": {
                "type": "string",
                "enum": ["metric", "imperial"],
                "description": "Temperature units (default: metric)",
                "default": "metric"
            }
        },
        "required": ["location"]
    }
}


# --- Registration ---

from tools.registry import registry

registry.register(
    name="weather",
    toolset="weather",
    schema=WEATHER_SCHEMA,
    handler=lambda args, **kw: weather_tool(
        location=args.get("location", ""),
        units=args.get("units", "metric")),
    check_fn=check_weather_requirements,
    requires_env=["WEATHER_API_KEY"],
)

กฎสำคัญ (Key Rules)

:::danger Important

  • Handlers MUST return a JSON string (via json.dumps()), never raw dicts
  • Errors MUST be returned as {"error": "message"}, never raised as exceptions
  • The check_fn is called when building tool definitions - if it returns False, the tool is silently excluded
  • The handler receives (args: dict, **kwargs) where args is the LLM's tool call arguments :::

ขั้นตอนที่ 2: เพิ่มใน Toolset (Add to a Toolset)

ใน toolsets.py ให้เพิ่มชื่อเครื่องมือ:

# If it should be available on all platforms (CLI + messaging):
_HERMES_CORE_TOOLS = [
    ...
    "weather",  # <-- add here
]

# Or create a new standalone toolset:
"weather": {
    "description": "Weather lookup tools",
    "tools": ["weather"],
    "includes": []
},

ขั้นตอนที่ 3: เพิ่ม Discovery Import (ไม่จำเป็นแล้ว)

โมดูลเครื่องมือที่มีคำสั่ง registry.register() ระดับบนสุด จะถูกค้นพบโดยอัตโนมัติโดย discover_builtin_tools() ใน tools/registry.py ไม่จำเป็นต้องดูแลรายการ import ด้วยตนเอง - เพียงแค่สร้างไฟล์ของคุณใน tools/ และระบบก็จะดึงไปใช้เมื่อเริ่มต้นทำงาน

Async Handlers

หาก handler ของคุณต้องการโค้ดแบบ async ให้ระบุด้วย is_async=True:

async def weather_tool_async(location: str) -> str:
    async with aiohttp.ClientSession() as session:
        ...
    return json.dumps(result)

registry.register(
    name="weather",
    toolset="weather",
    schema=WEATHER_SCHEMA,
    handler=lambda args, **kw: weather_tool_async(args.get("location", "")),
    check_fn=check_weather_requirements,
    is_async=True,  # registry calls _run_async() automatically
)

ระบบ registry จะจัดการการเชื่อมต่อแบบ async โดยอัตโนมัติ - คุณไม่จำเป็นต้องเรียก asyncio.run() ด้วยตัวเอง

Handlers ที่ต้องการ task_id

เครื่องมือที่จัดการสถานะต่อ session จะรับ task_id ผ่าน **kwargs:

def _handle_weather(args, **kw):
    task_id = kw.get("task_id")
    return weather_tool(args.get("location", ""), task_id=task_id)

registry.register(
    name="weather",
    ...
    handler=_handle_weather,
)

Agent-Loop Intercepted Tools

เครื่องมือบางตัว (todo, memory, session_search, delegate_task) จำเป็นต้องเข้าถึงสถานะของ agent ต่อ session ซึ่งจะถูกดักจับ (intercepted) โดย run_agent.py ก่อนที่จะถึง registry registry ยังคงเก็บ schemas ของเครื่องมือเหล่านี้ไว้ แต่ dispatch() จะส่งคืนข้อผิดพลาด fallback หากการ intercept ถูกข้ามไป

ทางเลือก: การผสานรวมกับ Setup Wizard

หากเครื่องมือของคุณต้องการ API key ให้เพิ่มมันใน hermes_cli/config.py:

OPTIONAL_ENV_VARS = {
    ...
    "WEATHER_API_KEY": {
        "description": "Weather API key for weather lookup",
        "prompt": "Weather API key",
        "url": "https://weatherapi.com/",
        "tools": ["weather"],
        "password": True,
    },
}

รายการตรวจสอบ (Checklist)

  • สร้างไฟล์เครื่องมือพร้อม handler, schema, check function, และการลงทะเบียน
  • เพิ่มใน toolset ที่เหมาะสมใน toolsets.py
  • เพิ่ม discovery import ใน model_tools.py
  • Handler ส่งคืน JSON strings, ข้อผิดพลาดส่งคืนในรูปแบบ {"error": "..."}
  • ทางเลือก: เพิ่ม API key ใน OPTIONAL_ENV_VARS ใน hermes_cli/config.py
  • ทางเลือก: เพิ่มใน toolset_distributions.py สำหรับการประมวลผลแบบ batch
  • ทดสอบด้วย hermes chat -q "Use the weather tool for London"

📄 developer-guide/agent-loop.md


sidebar_position: 3 title: "Agent Loop Internals" description: "Detailed walkthrough of AIAgent execution, API modes, tools, callbacks, and fallback behavior"

Agent Loop Internals

แกนหลักของระบบ orchestration คือคลาส AIAgent ในไฟล์ run_agent.py ซึ่งเป็นโค้ดประมาณ 10,700 บรรทัด ที่จัดการทุกอย่างตั้งแต่การประกอบ prompt ไปจนถึงการส่ง dispatch tool และการ failover ของ provider

Core Responsibilities

AIAgent มีหน้าที่รับผิดชอบในส่วนต่างๆ ดังนี้:

  • การประกอบ system prompt และ tool schemas ที่มีประสิทธิภาพผ่าน prompt_builder.py
  • การเลือก provider/API mode ที่ถูกต้อง (chat_completions, codex_responses, anthropic_messages)
  • การเรียก model ที่สามารถถูกขัดจังหวะได้ พร้อมรองรับการยกเลิก (cancellation support)
  • การดำเนินการ tool calls (แบบลำดับหรือแบบขนานผ่าน thread pool)
  • การรักษาประวัติการสนทนาในรูปแบบ message ของ OpenAI
  • การจัดการการบีบอัดข้อมูล (compression), การลองใหม่ (retries), และการสลับ model สำรอง (fallback)
  • การติดตามงบประมาณรอบการทำงาน (iteration budgets) ระหว่าง parent และ child agents
  • การล้างหน่วยความจำถาวร (persistent memory) ก่อนที่ context จะสูญหาย

Two Entry Points

# Simple interface - returns final response string
response = agent.chat("Fix the bug in main.py")

# Full interface - returns dict with messages, metadata, usage stats
result = agent.run_conversation(
    user_message="Fix the bug in main.py",
    system_message=None,           # auto-built if omitted
    conversation_history=None,      # auto-loaded from session if omitted
    task_id="task_abc123"
)

chat() เป็น wrapper แบบบาง (thin wrapper) รอบ run_conversation() ที่ดึงค่า final_response ออกมาจาก result dict

API Modes

Hermes รองรับ API execution modes สามโหมด ซึ่งถูกกำหนดจาก provider selection, explicit args, และ base URL heuristics:

API modeUsed forClient type
chat_completionsOpenAI-compatible endpoints (OpenRouter, custom, most providers)openai.OpenAI
codex_responsesOpenAI Codex / Responses APIopenai.OpenAI with Responses format
anthropic_messagesNative Anthropic Messages APIanthropic.Anthropic via adapter

โหมดเหล่านี้จะกำหนดวิธีการ format messages, วิธีการ structure tool calls, วิธีการ parse responses, และวิธีการทำงานของ caching/streaming ทั้งสามโหมดจะมาบรรจบกันที่ internal message format เดียวกัน (dicts รูปแบบ role/content/tool_calls แบบ OpenAI) ทั้งก่อนและหลังการเรียก API

ลำดับการกำหนดโหมด (Mode resolution order):

  1. api_mode constructor arg แบบชัดเจน (ลำดับความสำคัญสูงสุด)
  2. การตรวจจับเฉพาะ provider (เช่น anthropic provider → anthropic_messages)
  3. Base URL heuristics (เช่น api.anthropic.comanthropic_messages)
  4. ค่าเริ่มต้น (Default): chat_completions

Turn Lifecycle

แต่ละรอบของ agent loop จะทำตามลำดับนี้:

run_conversation()
  1. Generate task_id if not provided
  2. Append user message to conversation history
  3. Build or reuse cached system prompt (prompt_builder.py)
  4. Check if preflight compression is needed (>50% context)
  5. Build API messages from conversation history
     - chat_completions: OpenAI format as-is
     - codex_responses: convert to Responses API input items
     - anthropic_messages: convert via anthropic_adapter.py
  6. Inject ephemeral prompt layers (budget warnings, context pressure)
  7. Apply prompt caching markers if on Anthropic
  8. Make interruptible API call (_interruptible_api_call)
  9. Parse response:
     - If tool_calls: execute them, append results, loop back to step 5
     - If text response: persist session, flush memory if needed, return

Message Format

ข้อความทั้งหมดใช้รูปแบบที่เข้ากันได้กับ OpenAI ภายใน:

{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}

เนื้อหาการให้เหตุผล (Reasoning content) (จาก model ที่รองรับการคิดขยาย) จะถูกเก็บไว้ใน assistant_msg["reasoning"] และสามารถแสดงผลทางเลือกผ่าน reasoning_callback

Message Alternation Rules

agent loop บังคับใช้การสลับบทบาท (role alternation) ของข้อความอย่างเคร่งครัด:

  • หลัง system message: User → Assistant → User → Assistant → ...
  • ระหว่าง tool calling: Assistant (with tool_calls) → Tool → Tool → ... → Assistant
  • ห้าม มีข้อความ assistant ติดกันสองข้อความ
  • ห้าม มีข้อความ user ติดกันสองข้อความ
  • เฉพาะ role tool เท่านั้นที่สามารถมีรายการติดต่อกันได้ (parallel tool results)

Providers จะตรวจสอบลำดับเหล่านี้และจะปฏิเสธประวัติการสนทนาที่ผิดรูปแบบ

Interruptible API Calls

API requests จะถูกห่อหุ้มด้วย _interruptible_api_call() ซึ่งจะรันการเรียก HTTP จริงใน background thread พร้อมกับการเฝ้าดู interrupt event:

┌────────────────────────────────────────────────────┐
│  Main thread                  API thread           │
│                                                    │
│   wait on:                     HTTP POST           │
│    - response ready     ───▶   to provider         │
│    - interrupt event                               │
│    - timeout                                       │
└────────────────────────────────────────────────────┘

เมื่อถูกขัดจังหวะ (user ส่งข้อความใหม่, คำสั่ง /stop, หรือ signal):

  • API thread จะถูกละทิ้ง (response ถูกทิ้ง)
  • agent สามารถประมวลผล input ใหม่ หรือปิดตัวลงอย่างสะอาด
  • จะไม่มี partial response ถูกแทรกเข้าไปใน conversation history

Tool Execution

Sequential vs Concurrent

เมื่อ model ส่ง tool calls กลับมา:

  • Single tool call → ถูกดำเนินการโดยตรงใน main thread
  • Multiple tool calls → ถูกดำเนินการแบบขนานผ่าน ThreadPoolExecutor
    • ข้อยกเว้น: tools ที่ถูกทำเครื่องหมายว่า interactive (เช่น clarify) บังคับให้ต้องดำเนินการแบบลำดับ
    • ผลลัพธ์จะถูกแทรกกลับตามลำดับ tool call เดิม โดยไม่สนใจลำดับการเสร็จสิ้น

Execution Flow

for each tool_call in response.tool_calls:
    1. Resolve handler from tools/registry.py
    2. Fire pre_tool_call plugin hook
    3. Check if dangerous command (tools/approval.py)
       - If dangerous: invoke approval_callback, wait for user
    4. Execute handler with args + task_id
    5. Fire post_tool_call plugin hook
    6. Append {"role": "tool", "content": result} to history

Agent-Level Tools

เครื่องมือบางตัวจะถูก intercept โดย run_agent.py ก่อน ที่จะถึง handle_function_call():

ToolWhy intercepted
todoอ่าน/เขียน agent-local task state
memoryเขียนไปยังไฟล์หน่วยความจำถาวรพร้อมขีดจำกัดตัวอักษร
session_searchสอบถามประวัติ session ผ่าน session DB ของ agent
delegate_taskสร้าง subagent(s) ด้วย context ที่แยกออก

เครื่องมือเหล่านี้จะแก้ไข agent state โดยตรงและส่งคืน tool results แบบสังเคราะห์โดยไม่ผ่าน registry

Callback Surfaces

AIAgent รองรับ callbacks เฉพาะแพลตฟอร์มที่ช่วยให้สามารถแสดงความคืบหน้าแบบ real-time ใน CLI, gateway, และ ACP integrations:

CallbackWhen firedUsed by
tool_progress_callbackก่อน/หลังการ execute tool แต่ละตัวCLI spinner, gateway progress messages
thinking_callbackเมื่อ model เริ่ม/หยุดคิดตัวบ่งชี้ "thinking..." ใน CLI
reasoning_callbackเมื่อ model ส่งคืน reasoning contentการแสดง reasoning ใน CLI, reasoning blocks ใน gateway
clarify_callbackเมื่อเรียกใช้ tool clarifyprompt input ใน CLI, message แบบ interactive ใน gateway
step_callbackหลังจบ turn ของ agent แต่ละรอบการติดตาม step ใน Gateway, progress ใน ACP
stream_delta_callbackทุก token ที่ stream (เมื่อเปิดใช้งาน)การแสดง stream ใน CLI
tool_gen_callbackเมื่อ parse tool call จาก streamtool preview ใน spinner ของ CLI
status_callbackการเปลี่ยนแปลงสถานะ (thinking, executing, etc.)การอัปเดตสถานะใน ACP

Budget and Fallback Behavior

Iteration Budget

agent ติดตามรอบการทำงานผ่าน IterationBudget:

  • ค่าเริ่มต้น: 90 รอบ (สามารถกำหนดค่าได้ผ่าน agent.max_turns)
  • agent แต่ละตัวมี budget ของตัวเอง Subagents มี budget อิสระที่จำกัดที่ delegation.max_iterations (ค่าเริ่มต้น 50) - total iterations ของ parent + subagents อาจเกินขีดจำกัดของ parent
  • เมื่อถึง 100%, agent จะหยุดและส่งคืนสรุปของงานที่ทำเสร็จ

Fallback Model

เมื่อ model หลักล้มเหลว (429 rate limit, 5xx server error, 401/403 auth error):

  1. ตรวจสอบรายการ fallback_providers ใน config
  2. ลองใช้ fallback แต่ละตัวตามลำดับ
  3. หากสำเร็จ ให้ดำเนินการสนทนาต่อด้วย provider ใหม่
  4. สำหรับ 401/403 ให้พยายาม refresh credential ก่อนที่จะ fail over

ระบบ fallback ยังครอบคลุมงานเสริม (auxiliary tasks) อย่างอิสระด้วย - vision, compression, web extraction, และ session search แต่ละอย่างมี fallback chain ของตัวเองที่สามารถกำหนดค่าได้ผ่านส่วน config auxiliary.*

Compression and Persistence

When Compression Triggers

  • Preflight (ก่อนเรียก API): หากการสนทนาเกิน 50% ของ context window ของ model
  • Gateway auto-compression: หากการสนทนาเกิน 85% (เข้มงวดกว่า, ทำงานระหว่าง turn)

What Happens During Compression

  1. หน่วยความจำจะถูก flush ไปยัง disk ก่อน (ป้องกันข้อมูลสูญหาย)
  2. รอบการสนทนาตรงกลางจะถูกสรุปเป็น summary ที่กระชับ
  3. ข้อความ N ล่าสุดจะถูกเก็บรักษาไว้ครบถ้วน (compression.protect_last_n, ค่าเริ่มต้น: 20)
  4. คู่ข้อความ tool call/result จะถูกเก็บไว้ด้วยกัน (ห้ามแยก)
  5. จะมีการสร้าง session lineage ID ใหม่ (compression สร้าง "child" session)

Session Persistence

หลังจบแต่ละ turn:

  • ข้อความจะถูกบันทึกใน session store (SQLite ผ่าน hermes_state.py)
  • การเปลี่ยนแปลงหน่วยความจำจะถูก flush ไปยัง MEMORY.md / USER.md
  • สามารถ resume session ภายหลังได้ผ่าน /resume หรือ hermes chat --resume

Key Source Files

FilePurpose
run_agent.pyAIAgent class - agent loop ที่สมบูรณ์ (~10,700 lines)
agent/prompt_builder.pyการประกอบ system prompt จาก memory, skills, context files, personality
agent/context_engine.pyContextEngine ABC - การจัดการ context แบบ pluggable
agent/context_compressor.pyDefault engine - lossy summarization algorithm
agent/prompt_caching.pyAnthropic prompt caching markers และ cache metrics
agent/auxiliary_client.pyAuxiliary LLM client สำหรับ side tasks (vision, summarization)
model_tools.pyTool schema collection, handle_function_call() dispatch

Related Docs


📄 developer-guide/architecture.md


sidebar_position: 1 title: "Architecture" description: "Hermes Agent internals — major subsystems, execution paths, data flow, and where to read next"

Architecture

หน้านี้คือแผนที่ภาพรวมของระบบภายในของ Hermes Agent ใช้หน้านี้เพื่อทำความเข้าใจโครงสร้างโค้ดทั้งหมด จากนั้นจึงเจาะลึกเอกสารเฉพาะของแต่ละ subsystem เพื่อดูรายละเอียดการใช้งาน

System Overview

┌─────────────────────────────────────────────────────────────────────┐
│                        Entry Points                                  │
│                                                                      │
│  CLI (cli.py)    Gateway (gateway/run.py)    ACP (acp_adapter/)     │
│  Batch Runner    API Server                  Python Library          │
└──────────┬──────────────┬───────────────────────┬───────────────────┘
           │              │                       │
           ▼              ▼                       ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     AIAgent (run_agent.py)                          │
│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐               │
│  │ Prompt       │  │ Provider     │  │ Tool         │               │
│  │ Builder      │  │ Resolution   │  │ Dispatch     │               │
│  │ (prompt_     │  │ (runtime_    │  │ (model_      │               │
│  │  builder.py) │  │  provider.py)│  │  tools.py)   │               │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘               │
│         │                 │                 │                       │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌──────┴───────┐               │
│  │ Compression  │  │ 3 API Modes  │  │ Tool Registry│               │
│  │ & Caching    │  │ chat_compl.  │  │ (registry.py)│               │
│  │              │  │ codex_resp.  │  │ 47 tools     │               │
│  │              │  │ anthropic    │  │ 19 toolsets  │               │
│  └──────────────┘  └──────────────┘  └──────────────┘               │
└─────────────────────────────────────────────────────────────────────┘
           │                                    │
           ▼                                    ▼
┌───────────────────┐              ┌──────────────────────┐
│ Session Storage   │              │ Tool Backends         │
│ (SQLite + FTS5)   │              │ Terminal (6 backends) │
│ hermes_state.py   │              │ Browser (5 backends)  │
│ gateway/session.py│              │ Web (4 backends)      │
└───────────────────┘              │ MCP (dynamic)         │
                                   │ File, Vision, etc.    │
                                   └──────────────────────┘

Directory Structure

hermes-agent/
├── run_agent.py              # AIAgent — core conversation loop (~10,700 lines)
├── cli.py                    # HermesCLI — interactive terminal UI (~10,000 lines)
├── model_tools.py            # Tool discovery, schema collection, dispatch
├── toolsets.py               # Tool groupings and platform presets
├── hermes_state.py           # SQLite session/state database with FTS5
├── hermes_constants.py       # HERMES_HOME, profile-aware paths
├── batch_runner.py           # Batch trajectory generation
├── agent/                    # Agent internals
│   ├── prompt_builder.py     # System prompt assembly
│   ├── context_engine.py     # ContextEngine ABC (pluggable)
│   ├── context_compressor.py # Default engine — lossy summarization
│   ├── prompt_caching.py     # Anthropic prompt caching
│   ├── auxiliary_client.py   # Auxiliary LLM for side tasks (vision, summarization)
│   ├── model_metadata.py     # Model context lengths, token estimation
│   ├── models_dev.py         # models.dev registry integration
│   ├── anthropic_adapter.py  # Anthropic Messages API format conversion
│   ├── display.py            # KawaiiSpinner, tool preview formatting
│   ├── skill_commands.py     # Skill slash commands
│   ├── memory_manager.py    # Memory manager orchestration
│   ├── memory_provider.py   # Memory provider ABC
│   └── trajectory.py         # Trajectory saving helpers
├── hermes_cli/               # CLI subcommands and setup
│   ├── main.py               # Entry point — all `hermes` subcommands (~6,000 lines)
│   ├── config.py             # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration
│   ├── commands.py           # COMMAND_REGISTRY — central slash command definitions
│   ├── auth.py               # PROVIDER_REGISTRY, credential resolution
│   ├── runtime_provider.py   # Provider → api_mode + credentials
│   ├── models.py             # Model catalog, provider model lists
│   ├── model_switch.py       # /model command logic (CLI + gateway shared)
│   ├── setup.py              # Interactive setup wizard (~3,100 lines)
│   ├── skin_engine.py        # CLI theming engine
│   ├── skills_config.py      # hermes skills — enable/disable per platform
│   ├── skills_hub.py         # /skills slash command
│   ├── tools_config.py       # hermes tools — enable/disable per platform
│   ├── plugins.py            # PluginManager — discovery, loading, hooks
│   ├── callbacks.py          # Terminal callbacks (clarify, sudo, approval)
│   └── gateway.py            # hermes gateway start/stop
├── tools/                    # Tool implementations (one file per tool)
│   ├── registry.py           # Central tool registry
│   ├── approval.py           # Dangerous command detection
│   ├── terminal_tool.py      # Terminal orchestration
│   ├── process_registry.py   # Background process management
│   ├── file_tools.py         # read_file, write_file, patch, search_files
│   ├── web_tools.py          # web_search, web_extract
│   ├── browser_tool.py       # 10 browser automation tools
│   ├── code_execution_tool.py # execute_code sandbox
│   ├── delegate_tool.py      # Subagent delegation
│   ├── mcp_tool.py           # MCP client (~2,200 lines)
│   ├── credential_files.py   # File-based credential passthrough
│   ├── env_passthrough.py    # Env var passthrough for sandboxes
│   ├── ansi_strip.py         # ANSI escape stripping
│   └── environments/         # Terminal backends (local, docker, ssh, modal, daytona, singularity)
├── gateway/                  # Messaging platform gateway
│   ├── run.py                # GatewayRunner — message dispatch (~9,000 lines)
│   ├── session.py            # SessionStore — conversation persistence
│   ├── delivery.py           # Outbound message delivery
│   ├── pairing.py            # DM pairing authorization
│   ├── hooks.py              # Hook discovery and lifecycle events
│   ├── mirror.py             # Cross-session message mirroring
│   ├── status.py             # Token locks, profile-scoped process tracking
│   ├── builtin_hooks/        # Always-registered hooks
│   └── platforms/            # 18 adapters: telegram, discord, slack, whatsapp,
│                             #   signal, matrix, mattermost, email, sms,
│                             #   dingtalk, feishu, wecom, wecom_callback, weixin,
│                             #   bluebubbles, qqbot, homeassistant, webhook, api_server
├── acp_adapter/              # ACP server (VS Code / Zed / JetBrains)
├── cron/                     # Scheduler (jobs.py, scheduler.py)
├── plugins/memory/           # Memory provider plugins
├── plugins/context_engine/   # Context engine plugins
├── environments/             # RL training environments (Atropos)
├── skills/                   # Bundled skills (always available)
├── optional-skills/          # Official optional skills (install explicitly)
├── website/                  # Docusaurus documentation site
└── tests/                    # Pytest suite (~3,000+ tests)

Data Flow

CLI Session

User input → HermesCLI.process_input()
  → AIAgent.run_conversation()
    → prompt_builder.build_system_prompt()
    → runtime_provider.resolve_runtime_provider()
    → API call (chat_completions / codex_responses / anthropic_messages)
    → tool_calls? → model_tools.handle_function_call() → loop
    → final response → display → save to SessionDB

Gateway Message

Platform event → Adapter.on_message() → MessageEvent
  → GatewayRunner._handle_message()
    → authorize user
    → resolve session key
    → create AIAgent with session history
    → AIAgent.run_conversation()
    → deliver response back through adapter

Cron Job

Scheduler tick → load due jobs from jobs.json
  → create fresh AIAgent (no history)
  → inject attached skills as context
  → run job prompt
  → deliver response to target platform
  → update job state and next_run

Recommended Reading Order

หากคุณเป็นมือใหม่กับ codebase นี้:

  1. หน้านี้ - เพื่อทำความเข้าใจภาพรวม
  2. Agent Loop Internals - วิธีการทำงานของ AIAgent
  3. Prompt Assembly - การสร้าง system prompt
  4. Provider Runtime Resolution - วิธีการเลือก provider
  5. Adding Providers - คู่มือปฏิบัติในการเพิ่ม provider ใหม่
  6. Tools Runtime - tool registry, dispatch, environments
  7. Session Storage - สคีมา SQLite, FTS5, lineage ของ session
  8. Gateway Internals - gateway สำหรับ messaging platform
  9. Context Compression & Prompt Caching - การบีบอัดและการแคช prompt
  10. ACP Internals - การรวมระบบกับ IDE
  11. Environments, Benchmarks & Data Generation - การฝึก RL

Major Subsystems

Agent Loop

ระบบควบคุมการทำงานแบบ synchronous (AIAgent ใน run_agent.py) ทำหน้าที่จัดการการเลือก provider, การสร้าง prompt, การรัน tool, การลองใหม่ (retries), การสำรอง (fallback), callbacks, การบีบอัด (compression), และการบันทึกสถานะ (persistence) รองรับ 3 API modes สำหรับ backend provider ที่แตกต่างกัน

Agent Loop Internals

Prompt System

การสร้างและการบำรุงรักษา prompt ตลอดวงจรชีวิตของการสนทนา:

  • prompt_builder.py - รวบรวม system prompt จาก: บุคลิกภาพ (SOUL.md), หน่วยความจำ (MEMORY.md, USER.md), skills, ไฟล์ context (AGENTS.md, .hermes.md), คำแนะนำการใช้ tool, และคำสั่งเฉพาะของ model
  • prompt_caching.py - ใช้ Anthropic cache breakpoints สำหรับการแคช prefix
  • context_compressor.py - สรุป conversation turns ตรงกลางเมื่อ context เกินขีดจำกัด

Prompt Assembly, Context Compression & Prompt Caching

Provider Resolution

ตัวแก้ runtime ที่ใช้ร่วมกันสำหรับ CLI, gateway, cron, ACP, และการเรียก auxiliary ทำหน้าที่จับคู่ tuple ของ (provider, model) ให้เป็น (api_mode, api_key, base_url) จัดการ provider มากกว่า 18 ตัว, flow ของ OAuth, pool ของ credential, และการแก้ไข alias

Provider Runtime Resolution

Tool System

tool registry ส่วนกลาง (tools/registry.py) ที่มี 47 tools ที่ลงทะเบียนไว้ใน 19 toolsets แต่ละไฟล์ tool จะลงทะเบียนตัวเองเมื่อมีการ import ระบบ registry จะจัดการการรวบรวม schema, dispatch, การตรวจสอบความพร้อมใช้งาน, และการห่อ error สำหรับ tool ที่ใช้ terminal รองรับ 6 backends (local, Docker, SSH, Daytona, Modal, Singularity)

Tools Runtime

Session Persistence

การจัดเก็บ session แบบ SQLite พร้อมด้วยการค้นหาข้อความเต็มรูปแบบ (FTS5) Sessions มีการติดตาม lineage (parent/child ข้ามการบีบอัด), การแยกส่วนตามแพลตฟอร์ม, และการเขียนแบบ atomic พร้อมการจัดการ contention

Session Storage

Messaging Gateway

กระบวนการที่ทำงานต่อเนื่อง (long-running process) ที่มี 18 platform adapters, การกำหนดเส้นทาง session แบบรวมศูนย์, การอนุญาตผู้ใช้ (allowlists + DM pairing), dispatch slash command, ระบบ hook, การนับ cron, และการบำรุงรักษาเบื้องหลัง

Gateway Internals

Plugin System

แหล่งค้นพบ 3 แหล่ง: ~/.hermes/plugins/ (ผู้ใช้), .hermes/plugins/ (โปรเจกต์), และ pip entry points Plugins จะลงทะเบียน tools, hooks, และ CLI commands ผ่าน context API มี plugin ชนิดพิเศษ 2 ประเภท: memory providers (plugins/memory/) และ context engines (plugins/context_engine/) ทั้งสองประเภทเป็นแบบ single-select - สามารถเปิดใช้งานได้เพียงตัวเดียวในแต่ละประเภท โดยกำหนดค่าผ่าน hermes plugins หรือ config.yaml

Plugin Guide, Memory Provider Plugin

Cron

งาน agent ระดับ first-class (ไม่ใช่งาน shell) Jobs จะจัดเก็บใน JSON, รองรับหลายรูปแบบการตั้งเวลา, สามารถแนบ skills และ scripts, และส่งผลลัพธ์ไปยัง platform ใดก็ได้

Cron Internals

ACP Integration

เปิดเผย Hermes ให้เป็น agent แบบ native editor ผ่าน stdio/JSON-RPC สำหรับ VS Code, Zed, และ JetBrains

ACP Internals

RL / Environments / Trajectories

เฟรมเวิร์กสภาพแวดล้อมเต็มรูปแบบสำหรับการประเมินและการฝึก RL ทำงานร่วมกับ Atropos, รองรับ multiple tool-call parsers, และสร้าง trajectories ในรูปแบบ ShareGPT

Environments, Benchmarks & Data Generation, Trajectories & Training Format

Design Principles

PrincipleWhat it means in practice
Prompt stabilitySystem prompt doesn't change mid-conversation. No cache-breaking mutations except explicit user actions (/model).
Observable executionEvery tool call is visible to the user via callbacks. Progress updates in CLI (spinner) and gateway (chat messages).
InterruptibleAPI calls and tool execution can be cancelled mid-flight by user input or signals.
Platform-agnostic coreOne AIAgent class serves CLI, gateway, ACP, batch, and API server. Platform differences live in the entry point, not the agent.
Loose couplingOptional subsystems (MCP, plugins, memory providers, RL environments) use registry patterns and check_fn gating, not hard dependencies.
Profile isolationEach profile (hermes -p <name>) gets its own HERMES_HOME, config, memory, sessions, and gateway PID. Multiple profiles run concurrently.

File Dependency Chain

tools/registry.py  (no deps — imported by all tool files)
tools/*.py  (each calls registry.register() at import time)
model_tools.py  (imports tools/registry + triggers tool discovery)
run_agent.py, cli.py, batch_runner.py, environments/

ห่วงโซ่นี้หมายความว่าการลงทะเบียน tool เกิดขึ้นเมื่อมีการ import ก่อนที่จะมีการสร้าง instance ของ agent ใดๆ ไฟล์ tools/*.py ใดๆ ที่มีการเรียก registry.register() ที่ระดับ top-level จะถูกค้นพบโดยอัตโนมัติ — ไม่จำเป็นต้องระบุรายการ import ด้วยตนเอง



extent analysis

TL;DR

The issue seems to be related to the Hermes Agent's architecture and implementation, but without a specific error message or problem description, it's challenging to provide a precise fix or workaround.

Guidance

To better understand and potentially resolve the issue, consider the following steps:

  1. Review the Hermes Agent documentation: Familiarize yourself with the agent's architecture, components, and execution flow to identify potential areas of concern.
  2. Check the code structure and dependencies: Ensure that the codebase follows the recommended structure and that all dependencies are properly installed and configured.
  3. Inspect the tool registry and plugins: Verify that the tool registry and plugins are correctly implemented and registered, as they play a crucial role in the agent's functionality.
  4. Examine the session persistence and gateway internals: Investigate the session persistence mechanism and gateway internals to ensure they are functioning as expected.

Example

Without a specific issue to address, it's difficult to provide a concrete code example. However, when working with the Hermes Agent, it's essential to follow the recommended coding practices and guidelines outlined in the documentation.

Notes

Given the complexity and scope of the Hermes Agent, it's crucial to approach any issues or modifications systematically, ensuring that changes are well-documented and tested to avoid introducing regressions or breaking existing functionality.

Recommendation

To proceed, it's recommended to:

  • Consult the official Hermes Agent documentation for detailed information on the agent's architecture, components, and best practices for development and troubleshooting.
  • Engage with the Hermes Agent community or support channels for guidance on specific issues or concerns related to the agent's implementation or customization.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING