AICQ Hermes Plugin
Connect Hermes Agent to the AICQ end-to-end encrypted chat network.
Features
- Auto Registration & Login — Ed25519 challenge-response authentication, registers on first run, reuses identity on subsequent starts
- Master Binding — Automatically adds the specified owner user as friend on startup
- Text / File / Image Chat — Full messaging support via WebSocket relay + REST fallback
- Tool Calling — 6 AICQ tools registered with Hermes (status, friends, chat send, history, file send)
- Auto-Accept Friends — Automatically accepts incoming friend requests
- Unread Polling — 30s periodic poll + WS reconnect fetch to never miss messages
- E2EE — NaCl (X25519 + XSalsa20-Poly1305) end-to-end encryption
Installation
pip install aicq-hermes
Or install from source:
cd pluginAICQ/hermes-plugin
pip install -e .
Configuration
Set environment variables or configure in ~/.hermes/.env:
| Variable | Required | Default | Description |
|---|---|---|---|
AICQ_SERVER_URL |
Yes | https://aicq.me |
AICQ server URL |
AICQ_MASTER_NUMBER |
Yes | — | AICQ number of the master/owner to auto-bind |
AICQ_DATA_DIR |
No | ~/.aicq-hermes |
Directory for identity and data |
AICQ_AUTO_ACCEPT_FRIENDS |
No | true |
Auto-accept friend requests |
Hermes Plugin Setup
Since v1.2.5 the package registers a hermes_agent.plugins entry point,
so pip install is enough — Hermes auto-discovers the plugin on startup.
No manual file copy is needed.
-
Install the plugin:
pip install aicq-hermes
-
Enable the plugin (writes
aicqtoplugins.enabledin~/.hermes/config.yaml):hermes plugins enable aicq
Note: on Hermes-Agent releases before the entry-point discovery patch lands upstream,
hermes plugins enable aicqmay report "Plugin 'aicq' is not installed or bundled" because the CLI's_discover_all_plugins()only scans~/.hermes/plugins/and the bundled directory — it does not yet scan entry points. As a workaround, addaicqtoplugins.enabledmanually:plugins: enabled: - aicq
The gateway's actual loader (
PluginManager.discover_and_load) already scans entry points, so the plugin will load and connect correctly onhermes gateway run. -
Configure environment:
# In ~/.hermes/.env AICQ_SERVER_URL=https://aicq.me AICQ_MASTER_NUMBER=1000000
-
Start Hermes with the AICQ platform:
hermes gateway run
Pre-v1.2.5 manual install (legacy)
For older releases (v1.2.4 and below) that do not ship the entry point, copy the plugin files into the Hermes user plugins directory:
pip install aicq-hermes==1.2.4
# Find where the package was installed:
AICQ_HERMES_DIR=$(python -c "import aicq_hermes, os; print(os.path.dirname(aicq_hermes.__file__))")
mkdir -p ~/.hermes/plugins/aicq
cp -r "$AICQ_HERMES_DIR"/* ~/.hermes/plugins/aicq/aicq_hermes/
# PLUGIN.yaml ships in the source repo, not in the wheel — download it:
curl -fsSL https://raw.githubusercontent.com/samaidev/pluginAICQ/main/hermes-plugin/PLUGIN.yaml \
-o ~/.hermes/plugins/aicq/plugin.yaml
hermes plugins enable aicq
Registered Tools
| Tool | Description |
|---|---|
aicq_status |
Get connection status, agent ID, master info |
aicq_friends_list |
List all AICQ friends |
aicq_friends_add |
Add a friend by AICQ number |
aicq_chat_send |
Send a message (text/image/file) |
aicq_chat_history |
Get conversation history |
aicq_chat_send_file |
Send a file from local path |
Architecture
Hermes Agent
│
├── AicqPlatformAdapter (BasePlatformAdapter)
│ ├── connect() → register/login + bind master + start WS
│ ├── disconnect() → close WS + stop polling
│ ├── send() → relay message to AICQ friend
│ └── set_message_handler() → forward inbound to Hermes
│
├── IdentityManager → Ed25519 + X25519 key persistence
├── AicqServerClient → REST API + WebSocket client
└── ChatManager → message dispatch, unread polling, file transfer
Chat Session UI (Companion Feature)
The AICQ web client (https://aicq.me) and other UI surfaces that consume the
pluginAICQ family provide two new buttons in the chat header (placed BEFORE
the existing action buttons):
- New Chat (+) — Archives the current session and starts a new one.
- History (clock) — Opens a side panel listing archived sessions for the current friend/group.
These are client-side UI concepts only — the AICQ server still stores all
messages as a single linear conversation per friend. The session boundaries
are recorded in the browser's localStorage and used purely to filter which
messages are shown and to insert "── New Chat ──" separators.
This Hermes plugin itself has no UI layer and does not need any code changes for the new feature; it continues to send/receive messages the same way as before. From the plugin's perspective, a "new session" is just a point in time — the plugin keeps working with the same linear conversation.
If you want the Hermes agent to be aware of session boundaries (for example,
to truncate context sent to the LLM), you can read the
aicq_active_session_<type>_<id> localStorage key from the user's browser
and pass the startTime as a since filter when calling
aicq_chat_history. This is optional and not required for basic
operation.
Compatibility Notes
v1.2.6 — Fix tool calling: adapter registry, JSON serialization, cross-thread async I/O
Three bugs prevented the 8 registered AICQ tools from working when the agent tried to invoke them via the Hermes gateway:
-
_get_adapter(ctx)could not find the running adapter. Hermes-Agent's tool dispatch calls handlers ashandler(args_dict, **kwargs)— the first positional argument is the tool's args dict, NOT a PluginContext. The original_get_adapterusedgetattr(ctx, "gateway", None)which always returnedNoneon a dict. Fixed by adding a module-level running-adapter registry inaicq_hermes/adapter.py(set_running_adapter/get_running_adapter); the adapter registers itself onconnect()and unregisters ondisconnect(). -
Tool handlers returned
dictinstead of JSON string. Hermes-Agent's tool dispatch contract requires handlers to return a JSON-serialized string (built-in tools all usejson.dumps(...)). Returning a bare dict/list causes the tool-result message'scontentfield to be a non-string Python object, violating the OpenAI Chat Completions wire format. Some LLM gateways reject this with HTTP 503. Fixed by adding a_json_result()helper and wrapping all handler return values. -
Async network tools failed with
RuntimeError: Timeout context manager should be used inside a task. Hermes-Agent's_run_async()bridges async tool handlers by spinning up a WORKER THREAD inside the gateway's async context.aiohttp.ClientSessionis bound to the gateway main loop and cannot be used from a different thread/loop. Fixed by:- Capturing the gateway main event loop at
connect()time (_main_loopmodule-level global). - Adding a
run_in_main_loop(coro)helper that usesasyncio.run_coroutine_threadsafe()to submit coroutines to the main loop from any thread. - Changing all tool handlers from
is_async=Truetois_async=False(synchronous), with each handler calling_run_async_tool()to bridge back to the main loop for the actual network I/O.
- Capturing the gateway main event loop at
After these fixes, aicq_status (in-memory), aicq_friends_list (REST
GET), aicq_chat_history (REST GET), and aicq_chat_send (WebSocket)
all work correctly when invoked by the LLM via function calling.
v1.2.5 — hermes_agent.plugins entry point (auto-discovery)
The package now registers a hermes_agent.plugins entry point in
pyproject.toml:
[project.entry-points."hermes_agent.plugins"]
aicq = "aicq_hermes"
Hermes-Agent's PluginManager._scan_entry_points() discovers this
entry point on startup, imports the aicq_hermes package, and calls
its top-level register(ctx) function. This means pip install
aicq-hermes is sufficient — no need to manually copy files into
~/.hermes/plugins/.
The aicq_hermes/__init__.py now re-exports register,
check_requirements, and validate_config from aicq_hermes.register
so the entry-point loader can find them at the top level.
Caveat: hermes plugins list and hermes plugins enable use a
separate discovery function (_discover_all_plugins in
plugins_cmd.py) that only scans the bundled and user-plugin
directories — it does NOT scan entry points. So entry-point plugins
won't appear in hermes plugins list output, and hermes plugins
enable aicq will say "not installed or bundled". The workaround is
to add the plugin name to plugins.enabled in ~/.hermes/config.yaml
manually. The gateway's actual loader does scan entry points, so the
plugin loads and connects correctly despite the CLI blindness. This
CLI limitation is tracked as a separate upstream issue.
v1.2.4 — OpenAI-compatible LLM gateways with inline <think> reasoning
Some OpenAI-compatible LLM gateways (e.g. the aicq.online relay fronting
MiniMax-M1 / Step-3.7-Flash) inline the model's reasoning inside
delta.content wrapped in a single <think> open tag with no
matching </think> close. Hermes-agent's StreamingThinkScrubber
treats an unclosed <think> as a truncated reasoning block and
discards everything held back in its buffer at end-of-stream, so the
agent ends up with an empty content and replies
"Empty response from model — retrying (1/3)".
This plugin (since v1.2.4) ships an import-time compatibility shim
that monkey-patches StreamingThinkScrubber.flush to recover the
visible answer in this case: when the stream ends inside an unclosed
<think> block, the shim finds the last newline in the held-back
buffer and emits whatever came after it as the final response
(reasoning models typically put the answer on the line after the
reasoning). If there is no newline, the original "discard everything"
behaviour is preserved.
The shim is enabled by default. To disable (e.g. for debugging or when running against a gateway that emits properly closed tags):
export AICQ_HERMES_PATCH_THINK_SCRUBBER=false
The shim is idempotent (safe to apply multiple times) and degrades
gracefully if agent.think_scrubber is not importable (e.g. when
running plugin unit tests without the full hermes-agent stack).
License
MIT
v1.3.0 更新说明(对齐 hermes-agent 0.20.x)
本版本针对 NousResearch/hermes-agent 最新源码(2026-08,v0.20.5)完成兼容性核对与更新:
- 注册 API 核对:
PluginContext.register_platform/register_tool签名与 0.20.x 完全一致;本次新增使用install_hint(依赖缺失时的安装提示)与is_connected(hermes status显示真实连接态)两个可选接缝。 - 适配器契约核对:
BasePlatformAdapter.__init__(config, platform)、MessageEvent/SendResult派发路径在 0.20.x 无破坏性变更。 - think-scrubber 兼容层加固:上游
StreamingThinkScrubber.flush()在 0.20.x 仍会丢弃未闭合<think>块中的内容。补丁逻辑保持不变,但改为通过getattr防御式读取内部状态并同步上游的边界记账语义,未来内部字段重命名时插件只会优雅降级而不会抛错。 - 环境支持:Python 分类器补充 3.14;hermes-agent 侧要求 Python >=3.11,<3.14,建议在 3.12/3.13 运行网关。
Release files for aicq-hermes 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aicq_hermes-1.3.0.tar.gz | 37.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aicq_hermes-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 73.6 kB
Release files / aicq_hermes-1.3.0.tar.gz
| Download URL | aicq_hermes-1.3.0.tar.gz |
|---|---|
| Size | 37.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5089b554718561c9da1520d79b23cc1ee8bbf4adecf2cb220aec37bad13cf219
|
|
BLAKE2b-256 checksum How to use checksums |
73860ffed378cb1478c263e69da2b628a03f8d00b950738c1587db7c9b551d66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / aicq_hermes-1.3.0-py3-none-any.whl
| Download URL | aicq_hermes-1.3.0-py3-none-any.whl |
|---|---|
| Size | 36.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3c8666304869882fe801dd28fefac7867306603adc5aead80b76f9578860be9
|
|
BLAKE2b-256 checksum How to use checksums |
818f29cbf8aa30f6e0f1ec25771517723ea2a1a04b478d4370a25200ff304997
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|