Bug Description
After commit 4d36349 ("feat(plugins): bundled platform plugins auto-load by default", 2026-04-29), sessions that use a context engine plugin (e.g. hermes-lcm) can produce duplicate tool names in self.tools. Providers that enforce tool name uniqueness — Anthropic, Google Vertex AI, Azure, and Amazon Bedrock — reject the request with HTTP 400. When these providers are part of OpenRouter's auto-routing fallback chain, the failure is silent: the agent logs the error, exhausts its fallback list, and either returns a degraded response or fails the turn entirely with no user-visible explanation.
Affected Component
comp/agent, comp/plugins, comp/tools
Root Cause
run_agent.py injects context engine tool schemas unconditionally:
# run_agent.py ~L2007 (upstream, before fix)
for _schema in self.context_compressor.get_tool_schemas():
_wrapped = {"type": "function", "function": _schema}
self.tools.append(_wrapped) # ← no dedup check
The memory manager injection path (added earlier) already has a dedup guard. The context engine path does not. When a context engine plugin like hermes-lcm is active, its 6 tool schemas (lcm_grep, lcm_describe, lcm_expand, lcm_expand_query, lcm_status, lcm_doctor) get appended on every AIAgent.__init__ call. In gateway deployments where agents are re-initialized per session, or in any path that re-enters the injection block, duplicates accumulate.
Additionally, agent/auxiliary_client.py's _build_call_kwargs() passes tools directly to the API with no dedup:
# agent/auxiliary_client.py ~L3167 (upstream, before fix)
if tools:
kwargs["tools"] = tools # ← passed as-is, no dedup
This means any upstream duplication reaches every provider call.
Evidence
37 HTTP 400 errors logged across 2026-04-29 23:47 → 2026-05-01 09:48, all with identical message:
tools: Tool names must be unique.
Provider breakdown (via OpenRouter auto-routing):
- Azure: 20 occurrences
- Google Vertex AI: 14 occurrences
- Amazon Bedrock: 1 occurrence (separate error format:
{"message":"tools: Tool names must be unique."})
Errors appear in both interactive gateway sessions and cron job sessions. First occurrence correlates with commit 4d36349 landing on 2026-04-29.
Steps to Reproduce
- Enable the
hermes-lcm context engine plugin (or any context engine plugin that returns tools from get_tool_schemas())
- Configure OpenRouter as provider with
model: anthropic/claude-sonnet-4.x (or any model whose auto-routing includes Google/Azure/Bedrock as fallbacks)
- Start the gateway on the Telegram (or any) platform
- Send a message — observe HTTP 400 in
errors.log when OpenRouter routes to a strict provider
Expected Behavior
Tool schemas injected by context engine plugins are deduplicated against the existing tool list, matching the behaviour already implemented for memory manager tool injection. Providers never receive duplicate tool names.
Actual Behavior
Duplicate tool names reach the API. Strict providers (Google Vertex, Azure, Bedrock) reject with HTTP 400. The error is non-retryable and the agent silently degrades or fails.
Proposed Fix
Three defensive layers (already applied in our fork, diff below):
1. run_agent.py — dedup at context engine injection point:
_ce_existing_names = {
t.get("function", {}).get("name")
for t in self.tools
if isinstance(t, dict)
}
for _schema in self.context_compressor.get_tool_schemas():
_tname = _schema.get("name", "")
if _tname and _tname in _ce_existing_names:
logger.warning(
"Context engine tool '%s' already in tool list — skipping duplicate injection",
_tname,
)
continue
_wrapped = {"type": "function", "function": _schema}
self.tools.append(_wrapped)
if _tname:
self.valid_tool_names.add(_tname)
self._context_engine_tool_names.add(_tname)
_ce_existing_names.add(_tname)
2. agent/auxiliary_client.py — dedup in _build_call_kwargs() (all non-Anthropic providers):
if tools:
_seen: set = set()
_deduped: list = []
for _t in tools:
_tname = (_t.get("function") or {}).get("name", "")
if _tname and _tname in _seen:
logger.warning(
"_build_call_kwargs: duplicate tool name '%s' removed before API call " "(provider=%s model=%s)",
_tname, provider, model,
)
continue
if _tname:
_seen.add(_tname)
_deduped.append(_t)
kwargs["tools"] = _deduped
3. agent/anthropic_adapter.py — dedup in convert_tools_to_anthropic():
seen_names: set = set()
for t in tools:
fn = t.get("function", {})
name = fn.get("name", "")
if name and name in seen_names:
logger.warning(
"convert_tools_to_anthropic: duplicate tool name '%s' — dropping second occurrence",
name,
)
continue
if name:
seen_names.add(name)
result.append({...})
The fix is intentionally conservative — layers 2 and 3 are last-resort guards at the API call boundary so that any future injection path that forgets dedup doesn't cause provider failures.
Related Issues
Environment
- Hermes Agent commit:
828d3a320 (main as of 2026-04-29)
- Plugin: hermes-lcm v0.7.1
- Provider: OpenRouter (auto-routing to Google Vertex / Azure / Bedrock)
- Platform: Telegram gateway + CLI sessions + cron jobs (all affected)
Bug Description
After commit 4d36349 ("feat(plugins): bundled platform plugins auto-load by default", 2026-04-29), sessions that use a context engine plugin (e.g. hermes-lcm) can produce duplicate tool names in
self.tools. Providers that enforce tool name uniqueness — Anthropic, Google Vertex AI, Azure, and Amazon Bedrock — reject the request with HTTP 400. When these providers are part of OpenRouter's auto-routing fallback chain, the failure is silent: the agent logs the error, exhausts its fallback list, and either returns a degraded response or fails the turn entirely with no user-visible explanation.Affected Component
comp/agent,comp/plugins,comp/toolsRoot Cause
run_agent.pyinjects context engine tool schemas unconditionally:The memory manager injection path (added earlier) already has a dedup guard. The context engine path does not. When a context engine plugin like hermes-lcm is active, its 6 tool schemas (
lcm_grep,lcm_describe,lcm_expand,lcm_expand_query,lcm_status,lcm_doctor) get appended on everyAIAgent.__init__call. In gateway deployments where agents are re-initialized per session, or in any path that re-enters the injection block, duplicates accumulate.Additionally,
agent/auxiliary_client.py's_build_call_kwargs()passestoolsdirectly to the API with no dedup:This means any upstream duplication reaches every provider call.
Evidence
37 HTTP 400 errors logged across 2026-04-29 23:47 → 2026-05-01 09:48, all with identical message:
Provider breakdown (via OpenRouter auto-routing):
{"message":"tools: Tool names must be unique."})Errors appear in both interactive gateway sessions and cron job sessions. First occurrence correlates with commit 4d36349 landing on 2026-04-29.
Steps to Reproduce
hermes-lcmcontext engine plugin (or any context engine plugin that returns tools fromget_tool_schemas())model: anthropic/claude-sonnet-4.x(or any model whose auto-routing includes Google/Azure/Bedrock as fallbacks)errors.logwhen OpenRouter routes to a strict providerExpected Behavior
Tool schemas injected by context engine plugins are deduplicated against the existing tool list, matching the behaviour already implemented for memory manager tool injection. Providers never receive duplicate tool names.
Actual Behavior
Duplicate tool names reach the API. Strict providers (Google Vertex, Azure, Bedrock) reject with HTTP 400. The error is non-retryable and the agent silently degrades or fails.
Proposed Fix
Three defensive layers (already applied in our fork, diff below):
1.
run_agent.py— dedup at context engine injection point:2.
agent/auxiliary_client.py— dedup in_build_call_kwargs()(all non-Anthropic providers):3.
agent/anthropic_adapter.py— dedup inconvert_tools_to_anthropic():The fix is intentionally conservative — layers 2 and 3 are last-resort guards at the API call boundary so that any future injection path that forgets dedup doesn't cause provider failures.
Related Issues
Environment
828d3a320(main as of 2026-04-29)