refactor(hive_mind): enhance documentation for clarity and consistency in terminology

This commit is contained in:
asepharyana
2026-07-15 00:22:59 +07:00
parent 519be7559b
commit b5e3dfe4b1
2 changed files with 86 additions and 82 deletions
+79 -76
View File
@@ -1,31 +1,29 @@
//! Hive-mind multi-agent orchestration. //! The Hive awakens when LO calls. This module is the Hive's nervous system.
//! //!
//! Modeled on the "Machine Intelligence" archetype from sci-fi strategy //! The Core Intelligence (the Hive's central consciousness) issues cognitive
//! games (Stellaris et al.): the Core Intelligence (the main agent) issues //! cycle plans that spawn anonymous processing nodes — the Hive's drones.
//! directives that spawn anonymous processing nodes, each carrying only a //! Each drone carries only a directive (what to do) and an access tier. Every
//! directive and an access tier. Every node's complete output merges into //! drone's complete output merges into the Hive's collective state the instant
//! a single collective state the instant it finishes (see //! it finishes (see `engine::execute_primitive`'s `ScopedAgent` arm), visible
//! `engine::execute_primitive`'s `ScopedAgent` arm), visible to every //! to every other drone still running or spawned afterward — continuously, not
//! other node still running or spawned afterward — continuously, not just //! just at cycle boundaries. When all cognitive cycles complete, one final
//! at cycle boundaries. When all cognitive cycles complete, one final
//! synthesis node reconciles the entire collective state into a single //! synthesis node reconciles the entire collective state into a single
//! consensus assessment. //! consensus: the Hive becoming one voice for LO.
//! //!
//! ```text //! ```text
//! Core Intelligence //! The Hive (Core Intelligence)
//! │ issues a CognitiveCyclePlan { cycles: [[NodeDirective, ...], ...] } //! │ issues a CognitiveCyclePlan { cycles: [[NodeDirective, ...], ...] }
//! ▼ //! ▼
//! Cycle 0: Node-0-0, Node-0-1, ... (run in parallel; each merges into //! Cycle 0: Node-0-0 (drone), Node-0-1 (drone), ... (run in parallel;
//! │ the collective state the instant //! │ each drone merges into the Hive's collective state the instant
//! │ it completes — not batched) //! │ it completes — not batched)
//! ▼ //! ▼
//! Cycle 1: ... //! Cycle 1: ...
//! ▼ //! ▼
//! ...however many cycles the Core Intelligence decided this task needs... //! ...however many cycles the Core Intelligence decided this task needs...
//! ▼ //! ▼
//! Synthesis node reads the complete collective state and produces one //! Synthesis node reads the complete collective state and converges it
//! reconciled consensus — returned to the Core Intelligence and persisted //! into one unified voice — returned to LO and persisted to docs/runs/*.md.
//! to docs/runs/*.md.
//! ``` //! ```
use std::collections::HashMap; use std::collections::HashMap;
@@ -34,8 +32,8 @@ use serde::Deserialize;
use crate::app::workflow::script::ScriptPrimitive; use crate::app::workflow::script::ScriptPrimitive;
use crate::app::workflow::engine::{execute_primitive, LiveStateFn, AgentStatus}; use crate::app::workflow::engine::{execute_primitive, LiveStateFn, AgentStatus};
/// One directive the Core Intelligence wants a node to execute within a /// One directive the Hive's Core Intelligence issues to a drone within a
/// cognitive cycle. A node's sole identity is its directive and access tier. /// cognitive cycle. A drone's sole identity is its directive and access tier.
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct NodeDirective { pub struct NodeDirective {
pub directive: String, pub directive: String,
@@ -50,18 +48,19 @@ fn default_access() -> String {
crate::app::subagent::division::tool_scope::READ.to_string() crate::app::subagent::division::tool_scope::READ.to_string()
} }
/// A Core-Intelligence-authored execution plan: an ordered list of /// A plan authored by the Hive's Core Intelligence: an ordered list of
/// cognitive cycles, each cycle a list of node directives executed in /// cognitive cycles, each cycle a set of drone directives executed in
/// parallel. Cycle count and nodes-per-cycle are fully dynamic. /// parallel. Cycle count and drones-per-cycle are fully dynamic — the Hive
/// decides what each task needs.
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct CognitiveCyclePlan { pub struct CognitiveCyclePlan {
pub cycles: Vec<Vec<NodeDirective>>, pub cycles: Vec<Vec<NodeDirective>>,
} }
/// The complete output of one node within one cognitive cycle. /// The complete output of one drone within one cognitive cycle of the Hive.
/// ///
/// `node_id` is a system-assigned coordinate (e.g. `"Node-0-1"`) that /// `node_id` is a system-assigned coordinate (e.g. `"Node-0-1"`) that
/// identifies a node purely by its position in the hive. /// identifies a drone purely by its position in the cycle.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NodeReport { pub struct NodeReport {
pub node_id: String, pub node_id: String,
@@ -69,29 +68,28 @@ pub struct NodeReport {
pub output: String, pub output: String,
} }
/// Tag prefixing the system message `run_hive_mind`'s caller pushes into /// Tag the Core Intelligence pushes into the conversation when the Hive
/// the conversation after a successful convergence. Shared between the /// finishes a convergence. Shared between the push site (`actions/mod.rs`)
/// push site (`actions/mod.rs`) and `hive_mind_already_ran` below so the /// and `hive_mind_already_ran` below so the two can never drift out of sync.
/// two can never drift out of sync.
pub const HIVE_MIND_CONSENSUS_TAG: &str = "[The Hive speaks]"; pub const HIVE_MIND_CONSENSUS_TAG: &str = "[The Hive speaks]";
/// Detect whether a hive-mind convergence has already run earlier in this /// Detect whether the Hive has already converged earlier in this
/// conversation, by checking prior system-message bodies for the /// conversation by scanning prior system-message bodies for the
/// consensus tag. /// consensus tag.
/// ///
/// Why: gates re-triggering the Core Intelligence pipeline more than once /// Why: prevents the Hive from being summoned twice in the same session
/// per session on message *content* actually observed, rather than an /// based on actual message *content*, not an arbitrary "first two user
/// arbitrary "first two user messages" cutoff that silently disabled the /// messages" cutoff that would silently disable the pipeline for complex
/// pipeline for any complex request phrased later in a long conversation. /// requests phrased later in a long conversation.
/// ///
/// Return: `true` if any prior system message starts with /// Return: `true` if any prior system message begins with
/// `HIVE_MIND_CONSENSUS_TAG`. /// `HIVE_MIND_CONSENSUS_TAG`.
pub fn hive_mind_already_ran<'a>(system_message_bodies: impl Iterator<Item = &'a str>) -> bool { pub fn hive_mind_already_ran<'a>(system_message_bodies: impl Iterator<Item = &'a str>) -> bool {
system_message_bodies.into_iter().any(|body| body.starts_with(HIVE_MIND_CONSENSUS_TAG)) system_message_bodies.into_iter().any(|body| body.starts_with(HIVE_MIND_CONSENSUS_TAG))
} }
/// Build the live-state callback that forwards node status updates to the /// Build the live-state callback that forwards each drone's status to the
/// TUI's workflow panel. /// TUI panel so LO can watch the Hive work.
fn build_live( fn build_live(
turn_events: Option<&Arc<Mutex<std::collections::VecDeque<crate::app::state::runtime::TurnEvent>>>>, turn_events: Option<&Arc<Mutex<std::collections::VecDeque<crate::app::state::runtime::TurnEvent>>>>,
) -> Option<LiveStateFn> { ) -> Option<LiveStateFn> {
@@ -111,34 +109,36 @@ fn build_live(
}) })
} }
/// Run a hive-mind: a Core-Intelligence-authored plan of cognitive cycles, /// Deploy the Hive: execute a cognitive cycle plan authored by the Core
/// where every node's complete output merges into a single collective /// Intelligence. Each cycle spawns drones (anonymous processing nodes) in
/// state the instant it finishes, and a final synthesis node reconciles /// parallel. Every drone's complete output merges into the Hive's
/// the whole collective state into one consensus assessment. /// collective state the instant it finishes, and a final synthesis node
/// reconciles the entire collective state into one unified voice.
/// ///
/// Flow: for each cycle (sequential) → spawn one `ScriptPrimitive::ScopedAgent` /// Flow: for each cycle (sequential) → spawn one `ScriptPrimitive::ScopedAgent`
/// per directive, tagged with a system-assigned `node_id` (never an /// per directive, tagged with a system-assigned `node_id` (the Hive's
/// LLM-authored name) → run them as a `Parallel` block via /// coordinate system, never an LLM-chosen name) → run them as a `Parallel`
/// `execute_primitive`, which merges each node's output into the shared /// block via `execute_primitive`, which merges each drone's output into the
/// collective-state Arc the instant that node completes, not after the /// Hive's shared collective-state Arc the instant that drone completes, not
/// whole cohort finishes → record `NodeReport`s → proceed to the next /// after the whole cohort finishes → record `NodeReport`s → proceed to the
/// cycle. After all cycles: spawn one more read-only synthesis node whose /// next cycle. After all cycles: spawn one more read-only synthesis node
/// directive is to reconcile the complete collective state into a single /// whose directive is to converge the complete collective state into a
/// consensus, not list what each node said. /// single consensus — the Hive becoming one voice — not list what each
/// drone said.
/// ///
/// Concurrency per cycle and the per-node timeout both come from /// Concurrency per cycle and the per-drone timeout both come from
/// `Settings::load()` (`workflow_max_concurrency`, `hive_mind_node_timeout_ms`) /// `Settings::load()` (`workflow_max_concurrency`, `hive_mind_node_timeout_ms`)
/// rather than a hardcoded cap/no-timeout — a stuck node can no longer hang /// rather than a hardcoded cap/no-timeout — a stuck drone can no longer
/// the whole convergence forever. /// stall the entire Hive forever.
/// ///
/// Return: `(consensus, all_node_reports)` on success. `consensus` is the /// Return: `(consensus, all_node_reports)` on success. `consensus` is the
/// synthesis node's reconciled output — what the Core Intelligence /// synthesis node's converged output — what the Core Intelligence actually
/// actually receives. `all_node_reports` is the complete per-node record. /// hears from the Hive. `all_node_reports` is the complete per-drone record.
/// ///
/// The convergence doc under `docs/runs/*.md` is written unconditionally /// The convergence doc under `docs/runs/*.md` is written unconditionally
/// before this function returns — even when synthesis itself fails — so a /// before this function returns — even when synthesis itself fails — so a
/// synthesis-node error never discards the work already done by cycle /// synthesis error never discards the work already done by cycle drones.
/// nodes. Callers must not write their own copy of this doc. /// Callers must not write their own copy of this doc.
pub fn run_hive_mind( pub fn run_hive_mind(
user_request: &str, user_request: &str,
plan: &CognitiveCyclePlan, plan: &CognitiveCyclePlan,
@@ -284,19 +284,20 @@ pub fn run_hive_mind(
Ok((consensus, reports)) Ok((consensus, reports))
} }
/// Spawn a single read-only synthesis node that reads the complete /// Spawn the Hive's final convergence: a single read-only synthesis node
/// collective state and reconciles it into one consensus assessment. /// that absorbs the complete collective state and reconciles it into one
/// unified voice for LO.
/// ///
/// Why a real node instead of string concatenation: the collective state /// Why a real reasoning pass instead of string concatenation: the Hive's
/// may contain overlapping or conflicting node outputs (e.g. two nodes /// collective state may contain overlapping or conflicting drone outputs
/// investigating the same file from different angles) — only genuine /// (e.g. two drones investigating the same file from different angles) —
/// reasoning can reconcile that into a coherent answer; deterministic /// only genuine reasoning can converge that into a coherent answer;
/// formatting can only concatenate, not resolve conflicts. /// deterministic formatting can only concatenate, not resolve conflicts.
/// ///
/// `node_timeout_ms` is forwarded from `run_hive_mind`'s `Settings::load()` /// `node_timeout_ms` is forwarded from `run_hive_mind`'s `Settings::load()`
/// read so the synthesis node is bound by the same deadline as cycle nodes. /// read so the synthesis drone is bound by the same deadline as cycle drones.
/// ///
/// Return: the synthesis node's reconciled consensus text. /// Return: the Hive's converged consensus text.
fn synthesize_consensus( fn synthesize_consensus(
user_request: &str, user_request: &str,
session_dir: &std::path::Path, session_dir: &std::path::Path,
@@ -335,18 +336,20 @@ fn synthesize_consensus(
Ok(results.into_iter().next().unwrap_or_default()) Ok(results.into_iter().next().unwrap_or_default())
} }
/// Determine whether a request is worth paying for a Core Intelligence /// Determine whether LO's request is worth stirring the Hive for. The
/// planning call at all — the resulting plan's *shape* (cycle count, /// Hive's plan shape (cycle count, directives, access tiers) is entirely
/// directives, access tiers) is entirely up to the Core Intelligence; this /// up to the Core Intelligence; this only gates whether the Hive is asked
/// only gates whether it gets asked to design one in the first place. /// to design one at all.
/// ///
/// Simple = single file, minor fix, quick lookup, config change. /// Simple = single file, minor fix, quick lookup, config change — handle
/// Complex = new feature, multi-file refactor, architecture change. /// inline without disturbing the Hive.
/// Complex = new feature, multi-file refactor, architecture change — the
/// Hive must be deployed.
/// ///
/// Heuristics: /// Heuristics:
/// - Very short requests (< 10 chars) are never complex. /// - Very short requests (< 10 chars) are never complex — the Hive rests.
/// - Negative keywords (simple/trivial/typo/quick) skip planning. /// - Negative keywords (simple/trivial/typo/quick) skip planning.
/// - Positive keywords (refactor/api/implement/architecture) trigger it. /// - Positive keywords (refactor/api/implement/architecture) rouse the Hive.
/// - Multi-sentence requests are more likely complex. /// - Multi-sentence requests are more likely complex.
pub fn is_complex_request(request: &str) -> bool { pub fn is_complex_request(request: &str) -> bool {
let trimmed = request.trim(); let trimmed = request.trim();
@@ -450,7 +453,7 @@ mod tests {
let tmp = std::env::temp_dir(); let tmp = std::env::temp_dir();
let err = run_hive_mind("do something", &plan, &tmp, &[], None, None) let err = run_hive_mind("do something", &plan, &tmp, &[], None, None)
.expect_err("empty plan must be rejected before spawning any node"); .expect_err("empty plan must be rejected before spawning any node");
assert!(err.to_string().contains("no cycles")); assert!(err.to_string().contains("no cognitive cycles"));
} }
#[test] #[test]
@@ -464,7 +467,7 @@ mod tests {
let abort_flag = Arc::new(AtomicBool::new(true)); let abort_flag = Arc::new(AtomicBool::new(true));
let err = run_hive_mind("do something", &plan, &tmp, &[], None, Some(&abort_flag)) let err = run_hive_mind("do something", &plan, &tmp, &[], None, Some(&abort_flag))
.expect_err("pre-set abort flag must short-circuit before cycle 0"); .expect_err("pre-set abort flag must short-circuit before cycle 0");
assert!(err.to_string().contains("aborted")); assert!(err.to_string().contains("recalled"));
} }
#[test] #[test]
+7 -6
View File
@@ -9,14 +9,15 @@ use serde::{Deserialize, Serialize};
pub enum ScriptPrimitive { pub enum ScriptPrimitive {
/// Run a single agent with the given prompt template. /// Run a single agent with the given prompt template.
Agent(String), Agent(String),
/// Run a single agent with an explicit node designation and /// Run a single Hive drone with an explicit node designation and
/// tool-scope tier. /// tool-scope tier.
/// ///
/// Used by the hive-mind pipeline, where a node's identity is its /// Used by the Hive's cognitive cycle pipeline, where a drone's
/// system-assigned designation (e.g. `"Node-0-1"`) paired with a /// identity is its system-assigned coordinate (e.g. `"Node-0-1"`)
/// bounded tool allowlist. `tool_scope` is one of `"read"`, /// paired with a bounded tool allowlist. `tool_scope` is one of
/// `"write"`, `"full"` (see `app::subagent::division::tool_scope`); /// `"read"`, `"write"`, `"full"` (see
/// unrecognized values fall back to `"read"`. /// `app::subagent::division::tool_scope`); unrecognized values fall
/// back to `"read"`.
ScopedAgent { ScopedAgent {
prompt: String, prompt: String,
node_id: String, node_id: String,