//! Workflow tools — orchestrate multi-step agent workflows and hive-mind convergence. //! //! `WorkflowRun` executes a YAML-defined multi-step workflow. `NoteFinding` and //! `ReadFindings` record and retrieve findings during execution. `HiveMind` //! orchestrates a convergence — multiple parallel agent cycles followed by //! consensus synthesis. use anyhow::Result; use serde_json::{json, Value}; use tracing::{debug, info, instrument, warn}; use crate::llm::provider::LlmClient; use crate::tools::{arg_str, Tool, ToolCtx}; use crate::workflow::engine::execution::execute_workflow; use crate::workflow::hive_mind::cycle::execute_cycle; use crate::workflow::hive_mind::synthesis::synthesize_consensus; use zesdex_domain::workflow::{CognitiveCycle, NodeDirective, NodeOutput}; /// Execute a multi-step workflow defined in YAML. /// /// Flow: parse YAML → build plan from script phases → execute via workflow engine. pub struct WorkflowRun; impl Tool for WorkflowRun { fn name(&self) -> &'static str { "workflow_run" } fn description(&self) -> &'static str { "Execute a multi-step workflow defined in YAML" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "workflow_yaml": { "type": "string", "description": "YAML workflow definition" } }, "required": ["workflow_yaml"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let yaml = arg_str(args, "workflow_yaml")?; let script = crate::workflow::script::parse_workflow_script(&yaml)?; info!( "Workflow started: {} ({} phases)", script.name, script.phases.len() ); let phase_names: Vec<&str> = script.phases.iter().map(|p| p.name.as_str()).collect(); info!( "Workflow '{}' phases: {}", script.name, phase_names.join(", ") ); let llm_client = LlmClient::new( crate::llm::provider::DEFAULT_API_KEY.to_string(), zesdex_domain::agent::defaults::DEFAULT_MODEL.to_string(), None, ); let rt = tokio::runtime::Runtime::new()?; let result: Vec = rt.block_on(async { execute_workflow(&script, ctx, &llm_client).await })?; info!(phase_count = result.len(), "Workflow completed"); Ok(format!( "Workflow '{}' completed.\n\n{}", script.name, result.join("\n---\n") )) } } /// Record a finding during workflow or hive-mind execution. /// /// Flow: extract finding text and optional category → prepend `[category]` tag /// → push onto `ctx.workflow_findings` shared list. pub struct NoteFinding; impl Tool for NoteFinding { fn name(&self) -> &'static str { "note_finding" } fn description(&self) -> &'static str { "Record a finding during workflow or hive-mind execution" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "finding": { "type": "string", "description": "The finding text" }, "category": { "type": "string", "description": "Category for the finding" } }, "required": ["finding"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let finding = crate::tools::arg_str(args, "finding")?; let category = args .get("category") .and_then(|v| v.as_str()) .unwrap_or("general"); let tagged = format!("[{category}] {finding}"); if let Some(ref findings) = ctx.workflow_findings { if let Ok(mut guard) = findings.lock() { guard.push(tagged); info!(finding_count = guard.len(), category = %category, "finding recorded"); } } else { debug!("no workflow_findings channel available — finding not persisted"); } Ok(format!("Finding recorded: {finding}")) } } /// Read all findings recorded so far in the current workflow. /// /// Flow: lock `ctx.workflow_findings` → clone the list → format as numbered output. pub struct ReadFindings; impl Tool for ReadFindings { fn name(&self) -> &'static str { "read_findings" } fn description(&self) -> &'static str { "Read all findings recorded so far in the current workflow" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": {} }) } #[instrument(skip(self, ctx, _args))] fn run(&self, ctx: &ToolCtx, _args: &Value) -> Result { let findings = ctx .workflow_findings .as_ref() .and_then(|f| f.lock().ok()) .map(|guard| { debug!(finding_count = guard.len(), "reading findings"); guard.clone() }) .unwrap_or_default(); if findings.is_empty() { return Ok("No findings recorded yet.".to_string()); } Ok(format!( "Findings ({}):\n{}", findings.len(), findings.join("\n") )) } } /// Orchestrate a hive-mind convergence — multiple agents across parallel cycles. /// /// Flow: parse cycles from args → execute each cycle via `execute_cycle` → /// collect all node outputs → synthesize consensus → return report. pub struct HiveMind; impl Tool for HiveMind { fn name(&self) -> &'static str { "hive_mind" } fn description(&self) -> &'static str { "Run a hive-mind convergence with multiple nodes across sequential cycles" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "cycles": { "type": "array", "items": { "type": "object", "properties": { "directives": { "type": "array", "items": { "type": "object", "properties": { "directive": {"type": "string"}, "access": {"type": "string", "enum": ["read", "write", "full"]} } } } } }, "description": "Array of cycles, each with an array of node directives" } }, "required": ["cycles"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let cycles_val = args .get("cycles") .and_then(|v| v.as_array()) .ok_or_else(|| anyhow::anyhow!("missing 'cycles' array"))?; info!("Hive mind starting with {} cycles", cycles_val.len()); let rt = tokio::runtime::Runtime::new()?; let mut all_node_outputs = Vec::new(); for (cycle_idx, cycle_val) in cycles_val.iter().enumerate() { let directives: Vec = cycle_val .get("directives") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|d| { let directive = d .get("directive") .and_then(|v| v.as_str())?; let access = d .get("access") .and_then(|v| v.as_str()) .unwrap_or("read"); Some(NodeDirective { directive: directive.to_string(), access_tier: access.to_string(), }) }) .collect() }) .unwrap_or_default(); info!(cycle_index = cycle_idx, node_count = directives.len(), "executing hive-mind cycle"); let cycle = CognitiveCycle { index: cycle_idx as u32, directives, }; let nodes: Vec = rt.block_on(async { execute_cycle(&cycle, ctx).await })?; all_node_outputs.extend(nodes); } let node_count = all_node_outputs.len(); info!(node_count, "all cycles completed, synthesizing consensus"); let consensus = rt.block_on(async { synthesize_consensus(&all_node_outputs, ctx).await })?; let report = format!( "Hive mind convergence completed.\nNodes executed: {}\n\nConsensus:\n{}", node_count, consensus ); Ok(report) } }