//! Hive-mind cycle execution — run one cycle of parallel nodes. //! //! Flow: load settings → resolve LLM credentials → run all directives in the //! cycle concurrently via try_join_all → collect Vec. use anyhow::Result; use futures_util::future::try_join_all; use tracing::info; use zesdex_domain::cms::{AppConfigRepository, SettingsRepository}; use zesdex_domain::core::Store; use crate::persistence::{JsonAppConfigRepository, JsonSettingsRepository}; use crate::subagent::context::SubagentContext; use crate::subagent::division::AccessTier; use crate::subagent::engine::run_agent; use crate::tools::ToolCtx; use crate::workflow::hive_mind::types::{CognitiveCycle, NodeOutput}; /// Execute one cycle: run each node directive and collect outputs. /// /// Flow: /// 1. Load `Settings` and `AppConfig` from the store directory. /// 2. Resolve provider, model, base_url, and api_key. /// 3. Spawn all directives concurrently — each builds a `SubagentContext` /// and calls `run_agent` (Full access). /// 4. `try_join_all` waits for all to complete, then collect `NodeOutput`s. pub async fn execute_cycle( cycle: &CognitiveCycle, tool_ctx: &ToolCtx, ) -> Result> { info!( "Executing cycle {} with {} directives", cycle.index, cycle.directives.len() ); let store = Store::new(); let settings = JsonSettingsRepository::new() .load(&store.base_dir) .unwrap_or_default(); let app_config = JsonAppConfigRepository::new() .load(&store.base_dir) .unwrap_or_default(); let (provider, model) = crate::subagent::provider::resolve_subagent_provider(&settings, &app_config); let base_url = app_config .providers .get(&provider) .map(|p| p.api_base.clone()) .unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string()); let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config); let cycle_index = cycle.index; use crate::workflow::hive_mind::types::NodeDirective; // Run all directives in this cycle concurrently. let handles: Vec<_> = cycle .directives .iter() .enumerate() .map(|(i, node_dir): (usize, &NodeDirective)| { let dir = node_dir.directive.clone(); let access_tier = node_dir.access_tier.clone(); let ctx = SubagentContext::new( dir.clone(), tool_ctx.clone(), access_tier.clone(), base_url.clone(), api_key.clone(), model.clone(), ); let tc = tool_ctx.clone(); let access = match access_tier.as_str() { "write" => AccessTier::Write, "full" => AccessTier::Full, _ => AccessTier::Read, }; async move { let result = run_agent(ctx, &dir, access, tc).await?; Ok::(NodeOutput { id: format!("Node-{}-{}", cycle_index, i), directive: dir, output: result, }) } }) .collect(); let results = try_join_all(handles).await?; Ok(results) }