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