refactor(hive_mind): enhance documentation for clarity and consistency in terminology
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user