-
-
Notifications
You must be signed in to change notification settings - Fork 92
feat: Introduce HiveMind Ensemble Subsystem for Swarm/Fusion Orchestration #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Mirrowel
wants to merge
33
commits into
main
Choose a base branch
from
Ensemble-HiveMind
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 3 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
9779303
docs(hivemind): add comprehensive HiveMind orchestration plan and tas…
Mirrowel 20e0cb1
feat(ensemble): add HiveMind ensemble manager, config loader, and def…
Mirrowel e80c3e0
feat(ensemble): delegate HiveMind ensemble requests to ensemble manager
Mirrowel 2c99326
feat(ensemble): ✨ add _prepare_drones to prepare drone configs for pa…
Mirrowel d13eb95
feat(ensemble): add parallel drone execution and response formatter
Mirrowel eccbea4
feat(ensemble): add arbiter prompt builder, arbiter caller, and swarm…
Mirrowel 0ab51aa
fix(ensemble): use litellm.acompletion for drone API calls
Mirrowel eb5d7a1
feat(ensemble): add streaming arbiter and swarm handlers
Mirrowel 8af4919
fix(client): prefetch model mapping to avoid repeated lookups during …
Mirrowel 4d83427
feat(ensemble): route swarm requests to streaming handler when stream…
Mirrowel b343bd4
feat(ensemble): ✨ add temperature jitter and adversarial drone mode
Mirrowel aa8a609
feat(ensemble): ✨ add blind-mode response anonymization and hoist imp…
Mirrowel bc2672a
feat(ensemble): prepare specialist model configurations for fusion
Mirrowel e86457a
feat(ensemble): add fusion phase 5 with specialist roles, arbiter rou…
Mirrowel 08edb05
docs(hivemind): 📚 update HiveMind task checklist progress
Mirrowel d03d34d
feat(ensemble): ✨ add streaming fusion handler and consolidate fusion…
Mirrowel e41cfd2
feat(ensemble): add recursive arbiter mode and filter internal reasoning
Mirrowel 0856dc0
fix(ensemble): 🐛 use deepcopy, load provider models, and robustly han…
Mirrowel 5da1db4
feat(ensemble): dynamically aggregate usage and add cost/latency trac…
Mirrowel 865f7cf
feat(ensemble): add specialist weight descriptions and embed expertis…
Mirrowel 60243f5
feat(ensemble): extract specialist metadata for arbiter and return al…
Mirrowel 55a94f8
fix(rotator): 🐛 include HiveMind fusion models in available models li…
Mirrowel 4b0a0bf
docs(hivemind): 📚 add HiveMind API and user guide, update task checklist
Mirrowel 6c9f278
feat(ensemble): add preset-based hivemind swarm model discovery and h…
Mirrowel d093b26
docs(hivemind): 📚 mark fusion features and documentation items comple…
Mirrowel 2323dbc
feat(ensemble): support multi-fusion config format and fusion id suffix
Mirrowel d8c90b2
docs(hivemind): 📚 standardize "HiveMind Ensemble" naming across docum…
Mirrowel 105d10a
feat(ensemble): switch swarm loader to preset-based format and add sa…
Mirrowel d8ed4a2
feat(ensemble): add role template support and sample role configs
Mirrowel 6794096
fix(config): 🐛 report correct swarm preset count in loader log
Mirrowel f8de42b
refactor(ensemble): 🔨 standardize HiveMind ensemble initialization logs
Mirrowel e03d42c
feat(ensemble): ✨ enable implicit preset lookup for compact swarm IDs…
Mirrowel 9e6cbc0
docs(ensemble): 📚 add HiveMind Ensemble documentation, presets, roles…
Mirrowel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # HiveMind (Swarm/Fusion) Implementation | ||
|
|
||
| ## Phase 1: Core Infrastructure | ||
| - [/] Design and Plan | ||
| - [x] Explore codebase | ||
| - [x] Create comprehensive implementation plan | ||
| - [ ] Create `src/rotator_library/ensemble_manager.py` | ||
| - [ ] Define `EnsembleManager` class skeleton | ||
| - [ ] Implement config loading and validation | ||
| - [ ] Implement `is_ensemble()` detection | ||
| - [ ] Implement conflict resolution for naming | ||
| - [ ] Modify `src/rotator_library/client.py` | ||
| - [ ] Initialize `EnsembleManager` in `__init__` | ||
| - [ ] Integrate into `acompletion()` dispatcher | ||
| - [ ] Add logging for HiveMind operations | ||
| - [ ] Create `ensemble_config.json` | ||
| - [ ] Define schema for Fusions | ||
| - [ ] Define schema for Swarm defaults | ||
| - [ ] Define arbitration strategies | ||
|
|
||
| ## Phase 2: Basic Swarm Mode | ||
| - [ ] Implement Swarm Features | ||
| - [ ] `_prepare_drones()` - basic cloning | ||
| - [ ] `_execute_parallel()` - asyncio.gather | ||
| - [ ] `_format_for_arbiter()` - response aggregation | ||
| - [ ] `_build_arbiter_prompt()` - synthesis strategy | ||
| - [ ] `_call_arbiter()` - judge execution | ||
| - [ ] Testing | ||
| - [ ] Test basic 3-drone swarm | ||
| - [ ] Test arbiter synthesis | ||
| - [ ] Test partial failures | ||
|
|
||
| ## Phase 3: Advanced Swarm Features | ||
| - [ ] Temperature Jitter | ||
| - [ ] Implement jitter logic | ||
| - [ ] Test randomness and clamping | ||
| - [ ] Adversarial Mode | ||
| - [ ] Implement adversarial prompt injection | ||
| - [ ] Test with configurable count | ||
| - [ ] Blind Switch | ||
| - [ ] Implement response anonymization | ||
| - [ ] Test with blind=true/false | ||
| - [ ] Confidence Scoring | ||
| - [ ] Implement score extraction | ||
| - [ ] Add logging for scores | ||
|
|
||
| ## Phase 4: Fusion Mode | ||
| - [ ] Implement Fusion Features | ||
| - [ ] `_prepare_models()` - multi-model setup | ||
| - [ ] Role assignment and prompts | ||
| - [ ] Role context for Arbiter | ||
| - [ ] Weight system (future) | ||
| - [ ] Testing | ||
| - [ ] Test 2-model fusion | ||
| - [ ] Test role context injection | ||
| - [ ] Test specialist descriptions | ||
|
|
||
| ## Phase 5: Recursive/Reflective Mode | ||
| - [ ] Implement Recursion | ||
| - [ ] Consensus check logic | ||
| - [ ] Conflict extraction | ||
| - [ ] `_trigger_round_2()` implementation | ||
| - [ ] Max rounds enforcement | ||
| - [ ] Testing | ||
| - [ ] Test low-confidence trigger | ||
| - [ ] Test Round 2 critique | ||
| - [ ] Test final re-synthesis | ||
|
|
||
| ## Phase 6: Polish & Edge Cases | ||
| - [ ] Error Handling | ||
| - [ ] Partial failure handling | ||
| - [ ] Arbiter failure fallback | ||
| - [ ] Infinite recursion prevention | ||
| - [ ] Performance | ||
| - [ ] Latency logging | ||
| - [ ] Token usage tracking | ||
| - [ ] Rate limit mitigation | ||
| - [ ] Documentation | ||
| - [ ] User guide | ||
| - [ ] Example configs | ||
| - [ ] API reference | ||
|
|
||
| ## Verification | ||
| - [ ] Automated Tests | ||
| - [ ] test_ensemble_manager.py (all 8 test cases) | ||
| - [ ] test_swarm_logic.py | ||
| - [ ] test_fusion_logic.py | ||
| - [ ] test_recursion.py | ||
| - [ ] Manual Tests | ||
| - [ ] Scenario 1: Simple Swarm | ||
| - [ ] Scenario 2: Adversarial Swarm | ||
| - [ ] Scenario 3: Fusion with Roles | ||
| - [ ] Scenario 4: Recursive Refinement |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """ | ||
| HiveMind Ensemble Module | ||
|
|
||
| This module provides parallel model execution (Swarm/Fusion) with intelligent arbitration. | ||
| """ | ||
|
|
||
| from .manager import EnsembleManager | ||
|
|
||
| __all__ = ['EnsembleManager'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| """ | ||
| Configuration loader for HiveMind ensemble configs. | ||
|
|
||
| Loads and validates configurations from the ensemble_configs directory structure. | ||
| """ | ||
|
|
||
| import os | ||
| import json | ||
| import logging | ||
| from pathlib import Path | ||
| from typing import Dict, List, Any, Optional | ||
|
|
||
| lib_logger = logging.getLogger("rotator_library.ensemble") | ||
|
|
||
|
|
||
| class ConfigLoader: | ||
| """Loads and manages ensemble configurations from folder structure.""" | ||
|
|
||
| def __init__(self, config_dir: str): | ||
| """ | ||
| Initialize the config loader. | ||
|
|
||
| Args: | ||
| config_dir: Path to ensemble_configs directory (relative to rotator_library) | ||
| """ | ||
| self.config_dir = Path(config_dir) | ||
| self.swarms_dir = self.config_dir / "swarms" | ||
| self.fusions_dir = self.config_dir / "fusions" | ||
| self.strategies_dir = self.config_dir / "strategies" | ||
|
|
||
| # Loaded configurations | ||
| self.swarm_default: Optional[Dict[str, Any]] = None | ||
| self.swarm_configs: Dict[str, Dict[str, Any]] = {} | ||
| self.fusion_configs: Dict[str, Dict[str, Any]] = {} | ||
| self.strategies: Dict[str, str] = {} | ||
|
|
||
| def load_all(self) -> None: | ||
| """Load all configurations from the directory structure.""" | ||
| lib_logger.info("[HiveMind] Loading ensemble configurations...") | ||
|
|
||
| # Create directories if they don't exist | ||
| self._ensure_directories() | ||
|
|
||
| # Load swarm configurations | ||
| self._load_swarm_configs() | ||
|
|
||
| # Load fusion configurations | ||
| self._load_fusion_configs() | ||
|
|
||
| # Load strategy templates | ||
| self._load_strategies() | ||
|
|
||
| lib_logger.info( | ||
| f"[HiveMind] Loaded {len(self.swarm_configs)} swarm configs, " | ||
| f"{len(self.fusion_configs)} fusion configs, " | ||
| f"{len(self.strategies)} strategies" | ||
| ) | ||
|
|
||
| def _ensure_directories(self) -> None: | ||
| """Create config directories if they don't exist.""" | ||
| for directory in [self.swarms_dir, self.fusions_dir, self.strategies_dir]: | ||
| directory.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| def _load_swarm_configs(self) -> None: | ||
| """Load swarm configurations from swarms/ directory.""" | ||
| if not self.swarms_dir.exists(): | ||
| lib_logger.warning(f"[HiveMind] Swarms directory not found: {self.swarms_dir}") | ||
| return | ||
|
|
||
| # Load default.json first | ||
| default_path = self.swarms_dir / "default.json" | ||
| if default_path.exists(): | ||
| try: | ||
| with open(default_path, 'r', encoding='utf-8') as f: | ||
| self.swarm_default = json.load(f) | ||
| lib_logger.debug("[HiveMind] Loaded default swarm config") | ||
| except Exception as e: | ||
| lib_logger.error(f"[HiveMind] Failed to load default swarm config: {e}") | ||
| else: | ||
| lib_logger.warning("[HiveMind] No default swarm config found") | ||
|
|
||
| # Load model-specific configs | ||
| for config_file in self.swarms_dir.glob("*.json"): | ||
| if config_file.name == "default.json": | ||
| continue | ||
|
|
||
| try: | ||
| with open(config_file, 'r', encoding='utf-8') as f: | ||
| config = json.load(f) | ||
|
|
||
| # Extract model name from config | ||
| model_name = config.get("model") | ||
| if model_name: | ||
| self.swarm_configs[model_name] = config | ||
| lib_logger.debug(f"[HiveMind] Loaded swarm config for '{model_name}'") | ||
| else: | ||
| lib_logger.warning( | ||
| f"[HiveMind] Swarm config '{config_file.name}' missing 'model' field" | ||
| ) | ||
| except Exception as e: | ||
| lib_logger.error(f"[HiveMind] Failed to load swarm config '{config_file.name}': {e}") | ||
|
|
||
| def _load_fusion_configs(self) -> None: | ||
| """Load fusion configurations from fusions/ directory.""" | ||
| if not self.fusions_dir.exists(): | ||
| lib_logger.warning(f"[HiveMind] Fusions directory not found: {self.fusions_dir}") | ||
| return | ||
|
|
||
| for config_file in self.fusions_dir.glob("*.json"): | ||
| try: | ||
| with open(config_file, 'r', encoding='utf-8') as f: | ||
| config = json.load(f) | ||
|
|
||
| fusion_id = config.get("id") | ||
| if not fusion_id: | ||
| lib_logger.warning( | ||
| f"[HiveMind] Fusion config '{config_file.name}' missing 'id' field" | ||
| ) | ||
| continue | ||
|
|
||
| # Check for duplicate IDs | ||
| if fusion_id in self.fusion_configs: | ||
| lib_logger.warning( | ||
| f"[HiveMind] Duplicate fusion ID '{fusion_id}'. " | ||
| f"Config from '{config_file.name}' will override previous." | ||
| ) | ||
|
|
||
| self.fusion_configs[fusion_id] = config | ||
| lib_logger.debug(f"[HiveMind] Loaded fusion config '{fusion_id}'") | ||
|
|
||
| except Exception as e: | ||
| lib_logger.error(f"[HiveMind] Failed to load fusion config '{config_file.name}': {e}") | ||
|
|
||
| def _load_strategies(self) -> None: | ||
| """Load strategy templates from strategies/ directory.""" | ||
| if not self.strategies_dir.exists(): | ||
| lib_logger.warning(f"[HiveMind] Strategies directory not found: {self.strategies_dir}") | ||
| return | ||
|
|
||
| for strategy_file in self.strategies_dir.glob("*.txt"): | ||
| try: | ||
| with open(strategy_file, 'r', encoding='utf-8') as f: | ||
| content = f.read() | ||
|
|
||
| strategy_name = strategy_file.stem | ||
| self.strategies[strategy_name] = content | ||
| lib_logger.debug(f"[HiveMind] Loaded strategy '{strategy_name}'") | ||
|
|
||
| except Exception as e: | ||
| lib_logger.error( | ||
| f"[HiveMind] Failed to load strategy '{strategy_file.name}': {e}" | ||
| ) | ||
|
|
||
| def get_swarm_config(self, model: str) -> Dict[str, Any]: | ||
| """ | ||
| Get swarm configuration for a specific model. | ||
|
|
||
| Merges default config with model-specific overrides. | ||
|
|
||
| Args: | ||
| model: Base model name (without [swarm] suffix) | ||
|
|
||
| Returns: | ||
| Merged configuration dictionary | ||
| """ | ||
| # Start with default | ||
| config = self.swarm_default.copy() if self.swarm_default else {} | ||
|
Mirrowel marked this conversation as resolved.
Outdated
Mirrowel marked this conversation as resolved.
Outdated
Mirrowel marked this conversation as resolved.
Outdated
Mirrowel marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Apply model-specific overrides | ||
| if model in self.swarm_configs: | ||
| model_config = self.swarm_configs[model] | ||
| # Deep merge | ||
| for key, value in model_config.items(): | ||
| if key == "model": | ||
| continue # Don't copy the model name | ||
| if isinstance(value, dict) and key in config: | ||
| config[key] = {**config[key], **value} | ||
| else: | ||
| config[key] = value | ||
|
|
||
| return config | ||
|
|
||
| def get_fusion_config(self, fusion_id: str) -> Optional[Dict[str, Any]]: | ||
| """ | ||
| Get fusion configuration by ID. | ||
|
|
||
| Args: | ||
| fusion_id: Fusion identifier | ||
|
|
||
| Returns: | ||
| Fusion configuration or None if not found | ||
| """ | ||
| return self.fusion_configs.get(fusion_id) | ||
|
|
||
| def get_strategy(self, strategy_name: str) -> Optional[str]: | ||
| """ | ||
| Get strategy template by name. | ||
|
|
||
| Args: | ||
| strategy_name: Strategy identifier | ||
|
|
||
| Returns: | ||
| Strategy template string or None if not found | ||
| """ | ||
| return self.strategies.get(strategy_name) | ||
|
|
||
| def get_all_fusion_ids(self) -> List[str]: | ||
| """Get list of all fusion IDs.""" | ||
| return list(self.fusion_configs.keys()) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.swarm_default.copy()creates a shallow copy. Sinceswarm_defaultcontains nested dictionaries (e.g.,temperature_jitter), modifying nested fields in the returned configuration could inadvertently mutate the global default configuration for subsequent requests.Consider using
copy.deepcopy()to ensure full isolation: