Repository navigation
fix(toolsets): restore fork-custom messaging + moa toolsets dropped by parity merge - #121
Conversation
…y upstream parity merge The 2026-06-29 upstream parity merge (PR #119) took upstream's toolsets.py structure, which silently dropped two fork-only toolsets: * messaging -> send_message (agent-callable; the fork's origin-routing work #71/#83 is built on it). Upstream deliberately omits an agent-callable send_message; this fork re-enables it. * moa -> mixture_of_agents (tools/mixture_of_agents_tool.py, fully deleted). Post-merge resolve_toolset('messaging')/('moa') returned [] with no error, so on deploy Apollo would silently lose both tools across cli/telegram/discord. Caught by the config-migration dry-run's post-migration toolset validation. Restores: - toolsets.py: messaging + moa entries (marked FORK-CUSTOM so a future merge preserves them) - tools/mixture_of_agents_tool.py (self-registers moa via registry.register) - agent/display.py: mixture_of_agents formatter + arg-key - model_tools.py: moa_tools legacy alias - acp_adapter/tools.py: mixture_of_agents in polished ACP tools - hermes_cli/tools_config.py: messaging+moa UI rows, moa default-off + env req - hermes_cli/config.py: OPENROUTER_API_KEY tools list (+ mixture_of_agents) - hermes_cli/tips.py: mixture_of_agents tip Adds tests/agent/test_fork_custom_toolsets.py — a HARD regression gate that fails CI if either fork toolset is dropped again (proven: 6/6 pass restored, 6/6 fail on the broken pre-restore tree). Hardened against the editable-install live-tree leak so a deleted module can't false-pass via a sibling install.
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
invalid-return-type |
1 |
First entries
tests/agent/test_fork_custom_toolsets.py:23: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tools/mixture_of_agents_tool.py:111: [invalid-return-type] invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `tuple[str, str, bool]`
✅ Fixed issues: none
Unchanged: 6393 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
|
| Filename | Overview |
|---|---|
| toolsets.py | Restores messaging and moa fork-custom toolset entries with clear FORK-CUSTOM comment block to prevent future merge accidents. |
| tools/mixture_of_agents_tool.py | Full restore of the MoA tool module; all four issues from prior review threads are properly addressed. Docstring documents processing_time in the return shape but the implementation intentionally omits it (only in debug logs). |
| tests/agent/test_fork_custom_toolsets.py | New hard CI gate for both fork-custom toolsets; editable-install leak trap is well-designed, though tests access the private registry._tools attribute directly. |
| hermes_cli/tools_config.py | Adds messaging/moa UI rows, marks moa default-off, and declares OPENROUTER_API_KEY requirement — all consistent with the rest of the file. |
| hermes_cli/config.py | Adds mixture_of_agents to the OPENROUTER_API_KEY tools list — straightforward one-line addition. |
| hermes_cli/tips.py | Adds a tool-level tip for mixture_of_agents alongside the existing /moa slash-command tip. |
| model_tools.py | Adds moa_tools legacy alias mapping to mixture_of_agents — simple one-liner consistent with other legacy aliases. |
| agent/display.py | Wires mixture_of_agents into build_tool_preview arg-key map and get_cute_tool_message — clean addition matching the existing pattern. |
| acp_adapter/tools.py | Adds mixture_of_agents to the _POLISHED_TOOLS set — one-word addition matching the format of every other entry. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Agent
participant MoA as mixture_of_agents_tool
participant OR as OpenRouter API
participant R1 as Ref Model 1 (claude-opus-4.6)
participant R2 as Ref Model 2 (gemini-2.5-pro)
participant R3 as Ref Model 3 (gpt-5.4-pro)
participant R4 as Ref Model 4 (deepseek-v3.2)
participant Agg as Aggregator (claude-opus-4.6)
Agent->>MoA: mixture_of_agents(user_prompt)
MoA->>MoA: check OPENROUTER_API_KEY
par Layer 1 — parallel reference calls
MoA->>OR: "chat.completions R1 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
MoA->>OR: "chat.completions R2 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
MoA->>OR: chat.completions R3 no temp
OR-->>MoA: response / empty → retry x6
MoA->>OR: "chat.completions R4 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
end
MoA->>MoA: "filter successes >= MIN_SUCCESSFUL_REFERENCES"
Note over MoA: Layer 2 — aggregation
MoA->>OR: "chat.completions Agg system=all_responses"
OR-->>MoA: aggregated content / empty → retry x1
alt still empty after retry
MoA-->>Agent: RuntimeError not silent success
else content present
MoA-->>Agent: JSON success response models_used
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Agent
participant MoA as mixture_of_agents_tool
participant OR as OpenRouter API
participant R1 as Ref Model 1 (claude-opus-4.6)
participant R2 as Ref Model 2 (gemini-2.5-pro)
participant R3 as Ref Model 3 (gpt-5.4-pro)
participant R4 as Ref Model 4 (deepseek-v3.2)
participant Agg as Aggregator (claude-opus-4.6)
Agent->>MoA: mixture_of_agents(user_prompt)
MoA->>MoA: check OPENROUTER_API_KEY
par Layer 1 — parallel reference calls
MoA->>OR: "chat.completions R1 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
MoA->>OR: "chat.completions R2 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
MoA->>OR: chat.completions R3 no temp
OR-->>MoA: response / empty → retry x6
MoA->>OR: "chat.completions R4 reasoning=xhigh"
OR-->>MoA: response / empty → retry x6
end
MoA->>MoA: "filter successes >= MIN_SUCCESSFUL_REFERENCES"
Note over MoA: Layer 2 — aggregation
MoA->>OR: "chat.completions Agg system=all_responses"
OR-->>MoA: aggregated content / empty → retry x1
alt still empty after retry
MoA-->>Agent: RuntimeError not silent success
else content present
MoA-->>Agent: JSON success response models_used
end
Reviews (3): Last reviewed commit: "fix(moa): aggregator empty-content raise..." | Re-trigger Greptile
…ter gpt- temperature guard Addresses Greptile P1/P1/P2 on the restored mixture_of_agents_tool.py (pre-existing fork bugs, fixed while restoring): - empty final-retry content now returns success=False instead of forwarding an empty string to the aggregator - _run_aggregator_model now honors the computed agg_model (aggregator_model param was silently ignored) - temperature guard matches OpenRouter-namespaced 'openai/gpt-*' IDs, not just 'gpt-*'
|
Addressed all three Greptile findings in 091b3f9 (these were pre-existing bugs in the fork's mixture_of_agents_tool.py, fixed while restoring it):
Resolving the threads. |
…success
Greptile P1 follow-up: _run_aggregator_model returned "" after both retries
failed, which the caller wrapped as {success: True, response: ""}. Now raises
RuntimeError so the caller's failure path reports it — symmetric with the
reference-model final-attempt guard.
Problem
The 2026-06-29 upstream parity merge (PR #119) took upstream's
toolsets.pystructure, which silently dropped two fork-only toolsets:messaging→send_message(agent-callable — the fork's origin-routing work fix(send_message): route bare in-turn target to current channel, not home #71/fix(send_message): route subagent bare sends to parent origin, not home (v2) #83 depends on it; upstream deliberately omits an agent-callable send_message)moa→mixture_of_agents(tools/mixture_of_agents_tool.pywas fully deleted)Post-merge,
resolve_toolset('messaging')and('moa')returned[]with no error, so on deploy Apollo would silently lose both tools across cli/telegram/discord. Caught by the config-migration dry-run's post-migration toolset validation (not by the test suite — hence the new guard below).Fix — restore the fork features
toolsets.py: re-addmessaging+moa(marked FORK-CUSTOM with a comment so a future merge preserves them)tools/mixture_of_agents_tool.py: restored (self-registersmoaviaregistry.register)agent/display.py:mixture_of_agentsformatter + arg-keymodel_tools.py:moa_toolslegacy aliasacp_adapter/tools.py:mixture_of_agentsin polished ACP toolshermes_cli/tools_config.py: messaging+moa UI rows,moadefault-off + OPENROUTER_API_KEY requirementhermes_cli/config.py: OPENROUTER_API_KEY tools list (+mixture_of_agents)hermes_cli/tips.py:mixture_of_agentstipE2E regression guard (the "catch it next time" ask)
tests/agent/test_fork_custom_toolsets.py— a hard CI gate that fails if either fork toolset is dropped again. Proven: 6/6 pass on this branch, 6/6 fail on the broken pre-restore tree. Hardened against the editable-install live-tree leak (TRAP 1 fromdocs/sync/review/ci-gate-traps.md) so a deleted module can't false-pass via a sibling install.Verification
resolve_toolsetparity restored:messaging→1,moa→1 (matches pre-merge live tree exactly)py_compilecleanUnblocks the staged fleet deploy of the parity merge.