perf(agent): stabilkan async & parallel — satu runtime, bounded concurrency, isolasi error
Seperti Claude Code: satu runtime shared, concurrency dibatasi, error subagent terisolasi (satu node gagal tidak menggagalkan cycle). - feat(runtime): global tokio runtime via OnceLock — ganti 9+ titik Runtime::new() per tool call (spawn, parallel_delegate, workflow, explore, dir_cache, daemon handler). Hemat resource, hilangkan panic path Runtime::new().expect() di daemon compaction. - fix(workflow): execute_cycle ganti try_join_all (fail-fast) → buffer_unordered(8) + isolasi error per node; node gagal di-log dan diganti [ERROR], hasil node lain tetap dipakai (Claude Code-style). - fix(parallel_delegate): spawn subagent dibatasi per batch max_parallel (tidak unbounded threads). - perf(subagent): run_agent adaptif max_tokens (800/1600/4096), temp 0.2, truncate tool output 12k, error-recovery note utk tool error berulang. - test: runtime singleton + block_on (2 test).
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
//! 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<NodeOutput>.
|
||||
//! cycle concurrently via a BOUNDED buffer (`buffer_unordered(MAX)`) → collect
|
||||
//! `Vec<NodeOutput>`. Unlike `try_join_all`, a single failing node does NOT
|
||||
//! fail the whole cycle — failed nodes are logged and replaced with an
|
||||
//! `[ERROR]` output so the remaining results are preserved (like Claude
|
||||
//! Code's isolated subagents).
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::future::try_join_all;
|
||||
use tracing::info;
|
||||
use futures_util::stream::StreamExt;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use zesdex_domain::cms::{AppConfigRepository, SettingsRepository};
|
||||
use zesdex_domain::core::Store;
|
||||
@@ -15,16 +19,21 @@ use crate::subagent::context::SubagentContext;
|
||||
use crate::subagent::division::AccessTier;
|
||||
use crate::subagent::engine::run_agent;
|
||||
use crate::tools::ToolCtx;
|
||||
use zesdex_domain::workflow::{CognitiveCycle, NodeOutput};
|
||||
use zesdex_domain::workflow::{CognitiveCycle, NodeDirective, NodeOutput};
|
||||
|
||||
/// Maximum number of hive-mind nodes running concurrently per cycle.
|
||||
/// Keeps thread/runtime pressure bounded (Claude Code-style).
|
||||
const MAX_CONCURRENT_NODES: usize = 8;
|
||||
|
||||
/// 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.
|
||||
/// 3. Spawn directives with bounded concurrency — each builds a
|
||||
/// `SubagentContext` and calls `run_agent`.
|
||||
/// 4. Collect `NodeOutput`s; failed nodes are logged and replaced with an
|
||||
/// `[ERROR]` placeholder so the cycle still completes.
|
||||
pub async fn execute_cycle(cycle: &CognitiveCycle, tool_ctx: &ToolCtx) -> Result<Vec<NodeOutput>> {
|
||||
info!(
|
||||
"Executing cycle {} with {} directives",
|
||||
@@ -53,10 +62,9 @@ pub async fn execute_cycle(cycle: &CognitiveCycle, tool_ctx: &ToolCtx) -> Result
|
||||
|
||||
let cycle_index = cycle.index;
|
||||
|
||||
use zesdex_domain::workflow::NodeDirective;
|
||||
|
||||
// Run all directives in this cycle concurrently.
|
||||
let handles: Vec<_> = cycle
|
||||
// Run all directives with bounded concurrency. Each node is its own
|
||||
// future; failures are collected, not propagated (isolated errors).
|
||||
let tasks: Vec<_> = cycle
|
||||
.directives
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -79,18 +87,33 @@ pub async fn execute_cycle(cycle: &CognitiveCycle, tool_ctx: &ToolCtx) -> Result
|
||||
_ => AccessTier::Read,
|
||||
};
|
||||
|
||||
let node_id = format!("Node-{}-{}", cycle_index, i);
|
||||
async move {
|
||||
let result = run_agent(ctx, &dir, access, tc).await?;
|
||||
Ok::<NodeOutput, anyhow::Error>(NodeOutput {
|
||||
id: format!("Node-{}-{}", cycle_index, i),
|
||||
directive: dir,
|
||||
output: result,
|
||||
})
|
||||
match run_agent(ctx, &dir, access, tc).await {
|
||||
Ok(output) => Ok::<NodeOutput, anyhow::Error>(NodeOutput {
|
||||
id: node_id.clone(),
|
||||
directive: dir,
|
||||
output,
|
||||
}),
|
||||
Err(e) => {
|
||||
warn!(node = %node_id, error = %e, "hive-mind node failed (isolated)");
|
||||
Ok::<NodeOutput, anyhow::Error>(NodeOutput {
|
||||
id: node_id,
|
||||
directive: dir,
|
||||
output: format!("[ERROR] {e}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let results = try_join_all(handles).await?;
|
||||
// Bounded concurrency: run at most MAX_CONCURRENT_NODES futures at once.
|
||||
let mut stream = futures_util::stream::iter(tasks).buffer_unordered(MAX_CONCURRENT_NODES);
|
||||
let mut results = Vec::with_capacity(cycle.directives.len());
|
||||
while let Some(node) = stream.next().await {
|
||||
results.push(node?);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user