feat(tools/wot_engine): add Web-of-Thought multi-agent reasoning - #20158
feat(tools/wot_engine): add Web-of-Thought multi-agent reasoning#20158Abd0r wants to merge 4 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the substantial implementation and test coverage. The current main checkout does not already contain this capability, but the proposed integration has blocking behavior gaps.
Problems
tools/wot_engine.py:662-708builds each streaming request before streaming begins, while:816-822only runs those fixed requests concurrently. Published chunks cannot enter another agent's in-flight request, so the advertised same-round streaming exchange is not implemented.tools/wot_engine.py:647-653and:735-742produce onlyDONEtags and never setto_agent;@nameaddressing and queueintereststherefore cannot route ordinary messages.tools/wot_engine.py:918-930strips tool-call backend fields, then:766-771usesLLM_*/OLLAMA_URLinstead of Hermes's active provider and credential resolution.tools/wot_engine.py:1060-1092adds an always-available static toolset. Currentmodel_tools.py:389-445includes all toolsets by default, so this adds the schema to ordinary sessions rather than gating it.
Suggested changes
- Re-scope through a plugin/MCP or make a deliberately configured, provider-integrated implementation; implement or accurately narrow the routing/streaming contracts and cover them end-to-end.
Automated hermes-sweeper review.
| break | ||
|
|
||
| if mode in ("parallel", "streaming"): | ||
| turn_fn = (lambda a: a.turn_streaming(round_no)) if mode == "streaming" \ |
There was a problem hiding this comment.
This only starts independent streams concurrently. Each agent has already drained its inbox and built messages at turn_streaming() entry, so chunks published while another request is in flight cannot affect that request. The advertised same-round streaming exchange needs a different transport/protocol, or this mode should be described as transcript streaming.
| content=content, | ||
| reasoning=resp.reasoning, | ||
| round=round_no, | ||
| tags=["DONE"] if is_done else [], |
There was a problem hiding this comment.
There is no parsing of @name or topic syntax before this Message is published: to_agent remains None and normal messages receive no tags. That makes both direct addressing and the queue mode's interests matching unreachable for agent-produced output.
| sanitized: List[Dict[str, Any]] = [] | ||
| for a in agents: | ||
| spec = dict(a) | ||
| for k in ("model", "base_url", "api_key"): |
There was a problem hiding this comment.
The tool schema exposes agents[].model, but this removes it and the engine later uses LLM_DEFAULT_MODEL plus LLM_BASE_URL/LLM_API_KEY. A Hermes tool should use the active agent's resolved provider, credentials, fallbacks, and endpoint rather than unrelated environment variables.
| "parameters": wot_chat_tool_schema["function"]["parameters"], | ||
| }, | ||
| handler=_wot_handler, | ||
| check_fn=lambda: True, |
There was a problem hiding this comment.
Because this import also adds wot to static TOOLSETS, current model_tools.py includes this tool in the default all-toolsets path. An unconditional check function therefore makes this a default schema addition, not an explicit opt-in capability.
| @@ -0,0 +1,101 @@ | |||
| --- | |||
| name: web-of-thought | |||
| description: When to invoke wot_chat (multi-agent reasoning) and how to design the inner agents — when this beats answering directly. | |||
There was a problem hiding this comment.
This description exceeds the repository's 60-character skill-description limit and does not end with a period. Please also update the skill to the required modern section structure before landing it.
Attribution CI fix
That local machine email is not in Fix: rewrote author/committer on the PR commits only (filter-branch over the 3 tip commits) to:
No code change in this push — history identity only. Force-pushed tip:
Still intentional residual for this PR (not claimed fixed here):
|
Adds a self-contained multi-agent reasoning engine that coordinates 3-7 LLM agents through a shared message bus. Generic agents (no role taxonomy) talk to each other across four communication modes — parallel, streaming, sequential, queue — over any OpenAI-compatible backend. The engine is exposed as a single Hermes tool, `wot_chat`, registered under a new `wot` toolset. Caller passes agent specs (name + system_prompt) and a task; the engine orchestrates the conversation and returns a structured transcript for the outer agent to synthesize. Files: - tools/wot_engine.py — engine + tool registration (~1040 lines) - tests/tools/test_wot_engine.py — 36 unit tests, all green - skills/coordination/web-of-thought/SKILL.md — methodology guidance (when to invoke, how to design agents, mode selection, cost discipline) Engine specifics: - Backend probe at startup: detects llama.cpp / Ollama / vLLM / OpenAI-compat. Uses id_slot pinning + cache_prompt: true on llama.cpp for KV-cache reuse across agents. - Reasoning content extraction: handles delta.reasoning_content from thinking-mode models (DeepSeek-R1, QwQ, etc.) separately from content, so peer messages can choose to propagate raw / strip / summarize CoT. - Per-agent timeout via asyncio.wait_for, per-channel token budget, monotonic seq counter on Message envelope for stream debugging. - AgentSpec auto-sanitizes whitespace in names (real LLMs emit "Critical Thinker" / "Agent A"); raises only when sanitized name is empty. - /v1 suffix is stripped from base_url at client init so callers can pass either form (http://host:8088 OR http://host:8088/v1) without doubling. - Hermes tool registration is at module top-level (not wrapped in try/except) so tools/registry.py:_module_registers_tools picks it up via AST scan. Skill methodology: - When to invoke wot_chat (multi-perspective questions, tradeoffs, decisions with real downside) and when NOT to (lookups, single-fact, simple chat). - Agent design rule: minimal differentiating system_prompt, no scripted personas, no role-cargo names. Engine remains role-agnostic. - Mode selection: parallel (default) / streaming / sequential / queue. - Cost discipline: max_rounds: 2-3 for most cases, set token_budget for hard caps. - Reading the result: errors first, then agents_done, then transcript. Validated end-to-end on Ubuntu 24.04 + RTX 4050 with two model configurations: 1. Local llama.cpp + Qwen3-4B-Instruct-Q4_K_M (--parallel 4 --jinja): 5/5 sessions completed, 48 WoT messages across runs, 0 inner errors. 2. OpenRouter + DeepSeek-V4-Flash (with skill loaded): 5/5 sessions, skill methodology measurably moved model behavior toward leaner invocations (avg agent name ~10 chars vs ~22 unloaded; max_rounds explicitly set 5/5; 43% latency drop). License: MIT (auto per CONTRIBUTING.md).
Adds per-agent base_url + api_key fields to AgentSpec, enabling a single WoT session to mix backends (e.g. one agent on local Ollama, another on OpenRouter). _LLMClient caches backend probes per-base_url so each unique target is only probed once across the run. Engine changes: - AgentSpec: new optional fields base_url + api_key - _LLMClient: _probe_cache: Dict[str, BackendInfo], ensure_probed() now takes optional base_url_override and caches per-target - _resolve_target() helper composes the right URL + auth headers per call - _openai_payload_for(backend, ...) takes backend explicitly (so id_slot + cache_prompt only land when the THIS request actually targets llama-server) - complete() and stream() take base_url_override + api_key_override kwargs - _stream_openai and _stream_ollama_native take per-call base + headers - Agent.turn_batch + turn_streaming pass spec.base_url + spec.api_key - wot_chat_tool boundary strips model + base_url + api_key from outer-Hermes args (defensive: outer model hallucinates these); direct Python callers using AgentSpec(base_url=..., api_key=...) still work Tests: - 39/39 unit tests passing (up from 36) - New: MultiBackendMixTests verifies per-agent base_url threads to client - New: WotChatToolStripsCallerControlFields verifies tool boundary strips caller-supplied model/base_url/api_key Validated end-to-end: - One WoT session with 2 agents on different backends: - alpha on DeepSeek-V4-Flash via OpenRouter - beta on deepseek-r1:1.5b via local Ollama - Probe cache shows both targets: https://openrouter.ai/api → openai-compat http://127.0.0.1:11434 → ollama - 0 engine errors, both transcripts assembled with correct from-attribution
Partial salvage for @teknium1 hermes-sweeper review on NousResearch#20158 (salvageability=low — full same-round stream injection + Hermes provider integration remain out of scope for this PR). - Gate wot_chat behind HERMES_ENABLE_WOT so the tool is not always-on in ordinary sessions (check_fn was previously always True). - Narrow advertised streaming mode: post-completion peer partials, not mid-flight injection into another agent's open request. - Document that queue @name / interests routing is not implemented yet (only DONE early-stop). - Document LLM_BASE_URL credential path vs Hermes session provider. - Skill.md aligned with the narrowed contract.
- resolve_hermes_endpoint(): Hermes runtime/config first, LLM_BASE_URL escape hatch, then OLLAMA/localhost; surface source in result.backend - parse_message_routing(): @AgentName DMs + #tag interests on publish - queue mode: history-based eligibility so drained inboxes still wake on prior-round DMs/tags; DMs always wake target regardless of interests - honest mode/schema/toolset docs (streaming ≠ mid-token injection) - skill: ≤60-char description, Hermes provider, routing table - tests for routing parse, DM delivery, queue interests, endpoint meta
Option B salvage landed — tip
|
Summary
Adds a self-contained multi-agent reasoning engine that coordinates 3-7 LLM agents through a shared message bus. Generic agents (no role taxonomy hardcoded) talk to each other across four communication modes —
parallel,streaming,sequential,queue— over any OpenAI-compatible backend. Exposed as a single Hermes tool (wot_chat) under a newwottoolset, plus a methodology skill atskills/coordination/web-of-thought/.This is a separate concern from PRs #19607 / #19796 (free-tier search backends). They touch different surfaces and are independently reviewable.
Files
tools/wot_engine.py— engine + tool registration (1,034 lines)tests/tools/test_wot_engine.py— 36 unit tests, all greenskills/coordination/web-of-thought/SKILL.md— methodology guidance for callers (when to invoke, how to design agents, mode selection, cost discipline)Engine design
name + system_prompt. Engine is content-agnostic; the model decides agent personalities dynamically.parallel— all agents react to the task simultaneously, see peers' completed messages on round boundariesstreaming— same as parallel but agents see partial CoT tokens as they're generatedsequential— round-robin; each agent gets full prior transcriptqueue— tag-driven pull (agents declareinterests, only act when relevant tag appears)id_slotpinning +cache_prompt: trueon llama.cpp for KV-cache reuse across agents. Strips trailing/v1frombase_urlso callers can pass either form.delta.reasoning_contentseparately fromdelta.contentfor thinking-mode models (DeepSeek-R1, QwQ, Qwen3.5/3.6 with thinking on). Stored inMessage.reasoningso peer messages can choose to propagate raw / strip / summarize CoT (thepropagate_reasoningknob;summaryis currently stubbed tostrip— flagged in the docstring).token_budget, per-agentturn_timeoutviaasyncio.wait_for, monotonicseqper agent onMessageenvelope._, disallowed characters dropped. Raises only if the result is empty.modelfield is stripped from inner agent specs atwot_chat_toolboundary — outer Hermes tends to hallucinate names likegpt-4o. Engine usesLLM_DEFAULT_MODEL(env-driven) for all inner agents.Hermes integration
try/except) sotools/registry.py:_module_registers_toolsAST scanner picks it up.wottoolset is auto-created at module load time viatoolsets.create_custom_toolset(...)so-t wotvalidates without modifyingtoolsets.py.--skills coordination/web-of-thoughtand prefixes the system prompt with methodology guidance.How this fits next to existing Hermes multi-agent surfaces
Hermes already ships several multi-agent / delegation primitives. WoT is additive, not redundant — it fills a specific gap none of them serve.
delegate_taskmixture_of_agentswot_chat)@name-mentionsWhat WoT specifically adds: in-process live multi-agent reasoning where inner agents can address each other directly and the outer agent sees the full transcript as it forms. That's the niche the existing surfaces don't fill —
delegate_taskdeliberately hides intermediate output, MoA's reference models don't see each other, and Kanban's polling comments aren't real-time. Several long-open feature requests (#412 consensus/voting, #376 adversarial debate, #479 best-of-N + judge, #5876 multi-agent council) all reduce to this missing primitive.WoT does not replace any of the above. Compose: outer Hermes can call
delegate_taskfor durable cross-process work, dispatchwot_chatfor live debate within its own turn, and use Kanban for cross-session orchestration. They're complementary.Validated end-to-end
Setup: Ubuntu 24.04 + RTX 4050. Isolated Hermes install (separate
HERMES_HOME, no overlap with any production setup).1. Local llama.cpp + Qwen3-4B-Instruct-Q4_K_M (
--parallel 4 --jinja --ctx-size 65536):2. OpenRouter + DeepSeek-V4-Flash (with skill loaded):
errors[])max_rounds: 3explicitly set on 5/5,token_budgeton 3/5, ~43% latency drop vs no-skill baselineCoverage — honest framing
Integration-validated end-to-end (with V4 Flash via OpenRouter as inner agents, full session JSONL captured):
parallelmode — 5/5 sessions clean, 48 WoT messages, multi-round @-mention emergencestreamingmode — 22 streaming chunks + 3 final messages produced,stop_reason=all_donecleansequentialmode — agent ordering preserved across 2 rounds with cross-round @-mentions (round-2 alpha addresses round-1 beta)queuemode — interests tags drove tag-prefixed output ([design]→[code]→[review]), 3 rounds completedturn_timeout— standalone test, 2/2 agents timed out at 2.0s as configured, errors surfaced viaerrors[]modelstripping (saved a run when V4 Flash hallucinatedgpt-4o)/v1suffix doubling fix (caught the OpenRouter 404)Routing-validated (engine routes correctly; downstream model output quality is upstream's concern):
/api/chatpath for thinking models — backend probe identifieskind='ollama', request hits/api/chat(not/v1/), parses bothmessage.contentandmessage.thinkingfields. Tested live withdeepseek-r1:1.5banddeepseek-r1:7b. Output quality of small R1 distills + Ollama template handling is broken upstream (well-known) — engine correctly returns whatever Ollama emits.Unit-test only (no integration run on this PR):
Multi-backend mix — integration-validated (added in second commit
b1e8872):AgentSpecnow has optionalbase_url+api_keyfields for per-agent backend override_LLMClientcaches backend probes per-base_url so each unique target is only probed oncehttps://openrouter.ai/api/v1)deepseek-r1:1.5bvia local Ollama (http://127.0.0.1:11434)https://openrouter.ai/api → openai-compatandhttp://127.0.0.1:11434 → ollamafrom-attributionwot_chat_toolboundary stripsmodel+base_url+api_keyfrom outer-Hermes-supplied args (Hermes hallucinates them); direct Python callers usingAgentSpec(base_url=..., api_key=...)still workStubbed:
propagate_reasoning="summary"— currently behaves identically to"strip". A real summary mode would distill peer CoT through a small model; deferred to a follow-up. Docstring is honest about this.Linked issues
Closes (auto-close on merge):
parallelmode + a synthesizer agent delivers exactly the AgentWorkflows-style consensus pattern.sequentialmode with two agents is iterative-refinement debate.coordination/web-of-thoughtskill is the council methodology; the engine is the substrate.parallelmode with a synthesizer/judge agent is the Best-of-N pattern.Refs (does not auto-close — partial coverage):
Channelprimitive is the shared memory pool; CAMEL-AI-specific patterns are out of scope here.Channel) and per-agent persona (system_prompt).Test plan
pytest -p no:xdist tests/tools/test_wot_engine.py— 36/36 passing on this branchpytest tests/flags)Backwards compatibility
Pure-add. New tool, new toolset, new skill, new test file. Zero changes to existing code paths.
License
MIT (auto per
CONTRIBUTING.md).