perf(agent): eksekusi tool read-only paralel seperti Claude Code

Sebelumnya loop utama mengeksekusi semua tool call satu-per-satu
(sequential for loop). Seperti Claude Code, tool read-only yang
independen dalam satu pesan assistant (read/grep/glob/semantic_search
dsb.) kini dijalankan konkuren dengan bounded parallelism (max 8),
mengurangi latensi per turn secara signifikan untuk beban coding.

- feat(registry): tool_is_parallel_safe() — whitelist tool read-only
  yang aman dijalankan paralel; tool mutating/shell tetap sequential
- feat(executor): is_parallel_safe() delegasi ke registry; ToolExecutor
  trait Default=false (konservatif)
- fix(application): execute_tool_calls_in_parallel() — join_all +
  semaphore bounded 8, hasil dikumpulkan dalam URUTAN panggilan asli
  (kontrak OpenAI/Anthropic tool-result ordering)
- loop utama: batch paralel hanya jika SEMUA tool parallel-safe; jika
  ada satu tool mutating, jatuh balik ke jalur sequential aman
- test: +2 registry test, +2 application test (konkurensi & urutan,
  fallback batch mutating)
This commit is contained in:
asepharyana
2026-08-28 01:17:26 +07:00
parent 9ad04cf819
commit 74b1ad4302
7 changed files with 271 additions and 6 deletions
+11
View File
@@ -15,6 +15,13 @@ impl InfrastructureToolExecutor {
tools: all_tools(),
}
}
/// Whether a tool is read-only and safe to execute concurrently with
/// other parallel-safe tools. Delegates to the registry so the main
/// turn loop and subagent engine share one source of truth.
pub fn is_parallel_safe(tool_name: &str) -> bool {
crate::tools::tool_is_parallel_safe(tool_name)
}
}
impl ToolExecutor for InfrastructureToolExecutor {
@@ -37,4 +44,8 @@ impl ToolExecutor for InfrastructureToolExecutor {
}
}
}
fn is_parallel_safe(&self, tool_name: &str) -> bool {
Self::is_parallel_safe(tool_name)
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ pub mod workflow;
// like `crate::tools::{Tool, ToolCtx}` continue to work.
pub use context::{ToolCtx, ToolCtxBuilder};
pub use graduated::{check_graduated_checks, GraduatedCheck};
pub use registry::{all_tools, tool_defs, tool_is_risky};
pub use registry::{all_tools, tool_defs, tool_is_parallel_safe, tool_is_risky};
pub use util::{arg_str, execute_cmd, log_write_edit_tool, resolve_path};
/// Common interface every agent-invocable tool implements.
+84
View File
@@ -52,6 +52,34 @@ pub fn tool_is_risky(name: &str) -> bool {
matches!(name, "write" | "delete" | "edit" | "bash" | "git_operator")
}
/// Whether a tool by name is read-only and therefore safe to run *in
/// parallel* with other tool calls within the same assistant message.
///
/// Read-only tools only inspect the workspace (read files, grep, glob,
/// semantic search, list symbols, recall memory, web search, directory
/// listing). They have no side effects, so concurrent execution cannot
/// create data races or conflicting writes.
///
/// Everything else (edits, writes, deletes, shell, git, planning, memory
/// writes, agent/spawn orchestration) stays sequential to preserve
/// correctness.
pub fn tool_is_parallel_safe(name: &str) -> bool {
matches!(
name,
"read"
| "grep"
| "glob"
| "semantic_search"
| "list_symbols"
| "web_search"
| "recall"
| "dir_list"
| "pong"
| "seq_think"
| "dir_cache_update"
)
}
/// Convert a list of tools into provider-facing `ToolDef` request schema.
pub fn tool_defs(tools: &[Box<dyn super::Tool>]) -> Vec<zesdex_domain::core::ToolDef> {
tools
@@ -66,3 +94,59 @@ pub fn tool_defs(tools: &[Box<dyn super::Tool>]) -> Vec<zesdex_domain::core::Too
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_only_tools_are_parallel_safe() {
for name in [
"read",
"grep",
"glob",
"semantic_search",
"list_symbols",
"web_search",
"recall",
"dir_list",
"pong",
"seq_think",
"dir_cache_update",
] {
assert!(
tool_is_parallel_safe(name),
"{name} should be parallel-safe"
);
}
}
#[test]
fn mutating_and_shell_tools_are_not_parallel_safe() {
for name in [
"edit",
"write",
"delete",
"bash",
"git_operator",
"git_worktree",
"remember",
"forget",
"todowrite",
"todofinish",
"plan_enter",
"plan_ready",
"workflow_run",
"hive_mind",
"spawn_agents",
"spawn_pipeline",
"parallel_delegate",
"explore_codebase",
] {
assert!(
!tool_is_parallel_safe(name),
"{name} should NOT be parallel-safe"
);
}
}
}