Files
zesdex/apps/infrastructure/src/tools/workflow.rs
T

240 lines
7.1 KiB
Rust
Raw Normal View History

//! Workflow tools — orchestrate multi-step agent workflows and hive-mind convergence.
use anyhow::Result;
use serde_json::{json, Value};
use tracing::info;
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 crate::workflow::hive_mind::types::{CognitiveCycle, NodeOutput};
use crate::workflow::script::WorkflowScript;
/// 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"]
})
}
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
let yaml = arg_str(args, "workflow_yaml")?;
let script = WorkflowScript::parse(&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(),
"deepseek-v4-flash-free".to_string(),
None,
);
let rt = tokio::runtime::Runtime::new()?;
let result: Vec<String> =
rt.block_on(async { execute_workflow(&script, ctx, &llm_client).await })?;
Ok(format!(
"Workflow '{}' completed.\n\n{}",
script.name,
result.join("\n---\n")
))
}
}
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"]
})
}
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
let finding = crate::tools::arg_str(args, "finding")?;
if let Some(ref findings) = ctx.workflow_findings {
if let Ok(mut guard) = findings.lock() {
guard.push(finding.clone());
}
}
Ok(format!("Finding recorded: {finding}"))
}
}
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": {}
})
}
fn run(&self, ctx: &ToolCtx, _args: &Value) -> Result<String> {
let findings = ctx
.workflow_findings
.as_ref()
.and_then(|f| f.lock().ok())
.map(|guard| 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"]
})
}
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
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<String> = cycle_val
.get("directives")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|d| d.get("directive").and_then(|v| v.as_str()))
.map(String::from)
.collect()
})
.unwrap_or_default();
let cycle = CognitiveCycle {
index: cycle_idx as u32,
directives,
};
let nodes: Vec<NodeOutput> =
rt.block_on(async { execute_cycle(&cycle, ctx).await })?;
all_node_outputs.extend(nodes);
}
let node_count = all_node_outputs.len();
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)
}
}