feat(hive-mind): implement multi-agent orchestration with cognitive cycles

- Introduced a new hive-mind architecture that allows the Core Intelligence to issue directives to anonymous processing nodes.
- Each node executes its directive and merges output into a collective state, visible to all nodes in real-time.
- Added support for dynamic cognitive cycles, enabling flexible task management.
- Implemented documentation generation for hive-mind runs, ensuring a durable record of decisions and actions.
- Refactored existing company pipeline tools to align with the new hive-mind structure, replacing division-specific prompts with a more generalized approach.
- Updated workflow rendering to accommodate hive-mind nodes and their system-assigned designations.
- Enhanced error handling and validation for cognitive cycle plans.
This commit is contained in:
asepharyana
2026-07-14 08:12:43 +07:00
parent b1e0dcae14
commit 25f084f9db
23 changed files with 895 additions and 1225 deletions
+46 -77
View File
@@ -143,6 +143,46 @@ pub fn spawn_quick_review(
/// ─── Background Subagent Spawners (async, report via `SystemNote`) ───
///
/// Run a subagent built from `def`, retrying once if the first attempt
/// fails. Background subagents call this instead of running once and
/// silently swallowing the error into a note string, so a single transient
/// LLM/tool failure doesn't just disappear.
///
/// Return: `Ok(output)` if either attempt succeeded, `Err(message)`
/// describing the final failure if both attempts failed.
fn run_subagent_with_retry(
def: &AgentDefinition,
session_dir: &Path,
workspaces: &[std::path::PathBuf],
label: &str,
) -> Result<String, String> {
let mut last_err = String::new();
for attempt in 1..=2 {
let mut ctx = build_subagent_context(def);
ctx.session_dir = session_dir.to_path_buf();
ctx.workspaces = workspaces.to_vec();
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let drain_label = label.to_string();
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
if let SubagentEvent::StepFailed { step, error } = &event {
tracing::warn!("[{drain_label}] step {step} failed: {error}");
}
}
});
match run_subagent(&ctx, &tx) {
Ok(output) => return Ok(output),
Err(e) => {
tracing::warn!("[{label}] attempt {attempt}/2 failed: {e}");
last_err = e.to_string();
}
}
}
Err(format!("failed after 2 attempts: {last_err}"))
}
/// Spawn a background subagent that generates tests for modified files.
///
/// Uses the test-generator prompt and has read-write access so it can
@@ -184,40 +224,13 @@ pub fn spawn_background_test_gen(
.with_system_prompt(prompt)
;
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-test-gen] tool: {}", tool);
}
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-test-gen] result: {}", tool);
}
SubagentEvent::StepCompleted { .. } => {
tracing::trace!("[bg-test-gen] step done");
}
SubagentEvent::StepFailed { step, error } => {
tracing::warn!("[bg-test-gen] step {} failed: {}", step, error);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-test-gen] completed");
}
}
}
});
let result = run_subagent(&ctx, &tx);
let result = run_subagent_with_retry(&def, &sd, &ws, "bg-test-gen");
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Auto test-gen: {first}")
}
Err(e) => format!("Auto test-gen failed: {e}"),
Err(e) => format!("ESCALATED: Auto test-gen {e}"),
};
if let Ok(mut q) = events.lock() {
@@ -264,35 +277,13 @@ pub fn spawn_background_arch_review(
.with_system_prompt(prompt)
;
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-arch] tool: {}", tool);
}
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-arch] result: {}", tool);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-arch] completed");
}
_ => {}
}
}
});
let result = run_subagent(&ctx, &tx);
let result = run_subagent_with_retry(&def, &sd, &ws, "bg-arch-review");
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Architecture review: {first}")
}
Err(e) => format!("Architecture review failed: {e}"),
Err(e) => format!("ESCALATED: Architecture review {e}"),
};
if let Ok(mut q) = events.lock() {
@@ -350,35 +341,13 @@ pub fn spawn_background_security_review(
.with_system_prompt(prompt)
;
let mut ctx = build_subagent_context(&def);
ctx.session_dir = sd;
ctx.workspaces = ws;
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let _drain = std::thread::spawn(move || {
while let Some(event) = rx.blocking_recv() {
match &event {
SubagentEvent::ToolCall { tool, .. } => {
tracing::debug!("[bg-security] tool: {}", tool);
}
SubagentEvent::ToolResult { tool, .. } => {
tracing::debug!("[bg-security] result: {}", tool);
}
SubagentEvent::Completed { .. } => {
tracing::debug!("[bg-security] completed");
}
_ => {}
}
}
});
let result = run_subagent(&ctx, &tx);
let result = run_subagent_with_retry(&def, &sd, &ws, "bg-security-review");
let message = match &result {
Ok(output) => {
let first = output.lines().next().unwrap_or(output);
format!("Security review: {first}")
}
Err(e) => format!("Security review failed: {e}"),
Err(e) => format!("ESCALATED: Security review {e}"),
};
if let Ok(mut q) = events.lock() {
+81 -227
View File
@@ -1,237 +1,91 @@
//! Company-style agent divisions: specialized subagent roles that form an
//! organizational hierarchy like a company.
//! Access tiers for the anonymous processing nodes spawned by the
//! hive-mind orchestrator (`app::workflow::hive_mind`).
//!
//! ```text
//! CEO (Main Agent)
//! ├── Strategy Division (planner) — architecture, diagrams, plan
//! ├── Engineering Division (coder) — implementation
//! ├── Quality Division (tester) — review, test
//! ├── Security Division (auditor) — security audit
//! └── Documentation Division (doc) — documentation
//! ```
//!
//! Each division has a specific role, tools, and system prompt tailored to
//! its function. The main agent (CEO) delegates work to divisions via
//! the company pipeline workflow.
//! Nodes have no persistent identity of their own — the Core Intelligence
//! addresses each one only by directive and access tier. Since node
//! designations are system-assigned coordinates rather than named roles,
//! tool access can't be a lookup table keyed by role name. Instead the
//! Core Intelligence picks one of these three tiers per node, matched to
//! what that node's specific directive needs — this keeps the Harness
//! gate meaningful while the node roster itself stays fully dynamic.
use crate::app::subagent::spawn::AgentDefinition;
/// The three tool-access tiers a hive-mind node can be granted.
pub mod tool_scope {
/// Read-only investigation: no file mutation, no shell, no VCS.
pub const READ: &str = "read";
/// Read-tier plus file mutation and non-destructive shell (tests/builds).
pub const WRITE: &str = "write";
/// Write-tier plus delete, git, and the remaining LSP actions.
pub const FULL: &str = "full";
/// Division roles — used as both the `role` field in `AgentDefinition`
/// and as the key for pipeline routing.
pub mod roles {
/// Strategy Division: plans architecture, creates diagrams, breaks down work.
pub const STRATEGY: &str = "planner";
/// Engineering Division: implements code per the plan.
pub const ENGINEERING: &str = "coder";
/// Quality Division: reviews implementation, writes tests.
pub const QUALITY: &str = "tester";
/// Security Division: audits for vulnerabilities.
pub const SECURITY: &str = "auditor";
/// Documentation Division: updates docs, README, inline documentation.
pub const DOCUMENTATION: &str = "documenter";
}
const READ_TOOLS: &[&str] = &[
"read", "grep", "glob", "search", "seqthink", "recall",
"lsp_connect", "lsp_diagnostics", "lsp_hover", "lsp_definition",
"lsp_references", "read_findings",
];
/// ─── Division Agent Definitions ───
///
/// Build the Strategy Division agent — chief architect and planner.
///
/// Tools: read-only (read, grep, glob, search, lsp, plan, seqthink, recall)
/// Role: never writes code; produces detailed plans with mermaid diagrams.
pub fn strategy_division() -> AgentDefinition {
AgentDefinition::new(
"strategy-division".to_string(),
roles::STRATEGY.to_string(),
)
.with_system_prompt(crate::resources::DIVISION_PLANNER_PROMPT.to_string())
.with_allowed_tools(vec![
"read".to_string(),
"grep".to_string(),
"glob".to_string(),
"search".to_string(),
"seqthink".to_string(),
"plan".to_string(),
"recall".to_string(),
"lsp_connect".to_string(),
"lsp_diagnostics".to_string(),
"lsp_hover".to_string(),
"lsp_definition".to_string(),
"lsp_references".to_string(),
"read_findings".to_string(),
])
}
const WRITE_TOOLS: &[&str] = &[
"read", "grep", "glob", "search", "seqthink", "recall",
"lsp_connect", "lsp_diagnostics", "lsp_hover", "lsp_definition",
"lsp_references", "read_findings",
"write", "edit", "bash", "todowrite", "todofinish", "remember",
];
/// Build the Engineering Division agent — implements code per the plan.
///
/// Tools: full access (all write/edit/bash/git/LSP tools)
/// Role: executes the strategy plan, one file at a time.
pub fn engineering_division() -> AgentDefinition {
AgentDefinition::new(
"engineering-division".to_string(),
roles::ENGINEERING.to_string(),
)
.with_system_prompt(crate::resources::DIVISION_IMPLEMENTER_PROMPT.to_string())
.with_allowed_tools(vec![
"read".to_string(),
"write".to_string(),
"edit".to_string(),
"delete".to_string(),
"bash".to_string(),
"grep".to_string(),
"glob".to_string(),
"git_operator".to_string(),
"seqthink".to_string(),
"lsp_connect".to_string(),
"lsp_diagnostics".to_string(),
"lsp_hover".to_string(),
"lsp_definition".to_string(),
"lsp_references".to_string(),
"lsp_completion".to_string(),
"lsp_disconnect".to_string(),
"todowrite".to_string(),
"todofinish".to_string(),
"read_findings".to_string(),
])
}
const FULL_TOOLS: &[&str] = &[
"read", "grep", "glob", "search", "seqthink", "recall",
"lsp_connect", "lsp_diagnostics", "lsp_hover", "lsp_definition",
"lsp_references", "read_findings",
"write", "edit", "bash", "todowrite", "todofinish", "remember",
"delete", "git_operator", "lsp_completion", "lsp_disconnect",
];
/// Build the Quality Division agent — reviews code and writes tests.
///
/// Tools: read, write, grep, glob, bash (for running tests), LSP, memory
/// Role: verifies correctness and creates/runs tests.
pub fn quality_division() -> AgentDefinition {
AgentDefinition::new(
"quality-division".to_string(),
roles::QUALITY.to_string(),
)
.with_system_prompt(crate::resources::DIVISION_TESTER_PROMPT.to_string())
.with_allowed_tools(vec![
"read".to_string(),
"write".to_string(),
"edit".to_string(),
"grep".to_string(),
"glob".to_string(),
"bash".to_string(),
"seqthink".to_string(),
"recall".to_string(),
"remember".to_string(),
"lsp_connect".to_string(),
"lsp_diagnostics".to_string(),
"lsp_hover".to_string(),
"lsp_definition".to_string(),
"lsp_references".to_string(),
"read_findings".to_string(),
])
}
/// Build the Security Division agent — security auditor.
///
/// Tools: read-only + search + memory
/// Role: audits implementation for vulnerabilities.
pub fn security_division() -> AgentDefinition {
AgentDefinition::new(
"security-division".to_string(),
roles::SECURITY.to_string(),
)
.with_system_prompt(crate::resources::SECURITY_REVIEWER_PROMPT.to_string())
.with_allowed_tools(vec![
"read".to_string(),
"grep".to_string(),
"glob".to_string(),
"search".to_string(),
"seqthink".to_string(),
"recall".to_string(),
"remember".to_string(),
"lsp_connect".to_string(),
"lsp_diagnostics".to_string(),
"lsp_hover".to_string(),
"lsp_definition".to_string(),
"lsp_references".to_string(),
"read_findings".to_string(),
])
}
/// Build the Documentation Division agent — documentation maintainer.
///
/// Tools: read, grep, glob, write, edit, memory
/// Role: updates README, inline docs, architecture docs.
pub fn documentation_division() -> AgentDefinition {
AgentDefinition::new(
"documentation-division".to_string(),
roles::DOCUMENTATION.to_string(),
)
.with_system_prompt(crate::resources::DIVISION_DOCUMENTER_PROMPT.to_string())
.with_allowed_tools(vec![
"read".to_string(),
"write".to_string(),
"edit".to_string(),
"grep".to_string(),
"glob".to_string(),
"recall".to_string(),
"remember".to_string(),
"read_findings".to_string(),
])
}
/// ─── Division Registry ───
///
/// A named division with its agent definition and display metadata.
#[derive(Debug, Clone)]
pub struct Division {
/// Display name for the division (e.g. "Strategy", "Engineering").
pub name: &'static str,
/// Role tag used for pipeline routing (matches `roles::*` constants).
#[allow(dead_code)]
pub role: &'static str,
/// One-line description of what this division does.
#[allow(dead_code)]
pub description: &'static str,
/// Agent definition with tools, prompt, and step budget.
pub agent_def: AgentDefinition,
}
impl Division {
pub fn new(
name: &'static str,
role: &'static str,
description: &'static str,
agent_def: AgentDefinition,
) -> Self {
Division { name, role, description, agent_def }
/// Resolve a tier name to its concrete tool allowlist.
///
/// Unrecognized scope strings fall back to `READ` — the least-privileged
/// tier — rather than silently granting broader access.
///
/// Return: an owned `Vec<String>` suitable for `AgentDefinition::with_allowed_tools`.
pub fn tools_for(scope: &str) -> Vec<String> {
let tools: &[&str] = match scope {
FULL => FULL_TOOLS,
WRITE => WRITE_TOOLS,
_ => READ_TOOLS,
};
tools.iter().map(|s| (*s).to_string()).collect()
}
}
/// Return all company divisions as an ordered list matching the pipeline flow:
/// Strategy → Engineering → Quality → Security → Documentation.
pub fn all_divisions() -> Vec<Division> {
vec![
Division::new(
"Strategy",
roles::STRATEGY,
"Architecture planning with diagrams and step-by-step breakdown",
strategy_division(),
),
Division::new(
"Engineering",
roles::ENGINEERING,
"Code implementation following the plan",
engineering_division(),
),
Division::new(
"Quality",
roles::QUALITY,
"Code review and comprehensive testing",
quality_division(),
),
Division::new(
"Security",
roles::SECURITY,
"Security vulnerability audit",
security_division(),
),
Division::new(
"Documentation",
roles::DOCUMENTATION,
"Documentation updates and maintenance",
documentation_division(),
),
]
#[cfg(test)]
mod tests {
use super::tool_scope::{tools_for, FULL, READ, WRITE};
#[test]
fn read_tier_excludes_write_tools() {
let tools = tools_for(READ);
assert!(!tools.contains(&"write".to_string()));
assert!(!tools.contains(&"bash".to_string()));
}
#[test]
fn write_tier_includes_bash_but_not_delete_or_git() {
let tools = tools_for(WRITE);
assert!(tools.contains(&"bash".to_string()));
assert!(tools.contains(&"write".to_string()));
assert!(!tools.contains(&"delete".to_string()));
assert!(!tools.contains(&"git_operator".to_string()));
}
#[test]
fn full_tier_includes_delete_and_git() {
let tools = tools_for(FULL);
assert!(tools.contains(&"delete".to_string()));
assert!(tools.contains(&"git_operator".to_string()));
}
#[test]
fn unknown_scope_falls_back_to_read() {
let tools = tools_for("bogus");
assert!(!tools.contains(&"write".to_string()));
assert!(!tools.contains(&"delete".to_string()));
}
}
+2 -2
View File
@@ -29,13 +29,13 @@ fn build_subagent_tools(allowed_tools: &[String]) -> (Vec<Box<dyn crate::tool::T
let all = all_tools();
let filtered: Vec<Box<dyn crate::tool::Tool>> = if allowed_tools.is_empty() {
all.into_iter()
.filter(|t| t.name() != "company_pipeline" && t.name() != "workflow_run")
.filter(|t| t.name() != "hive_mind" && t.name() != "workflow_run")
.collect()
} else {
all.into_iter()
.filter(|t| {
allowed_tools.contains(&t.name().to_string())
&& t.name() != "company_pipeline"
&& t.name() != "hive_mind"
&& t.name() != "workflow_run"
})
.collect()