feat(swarm): /swarm mode — a standing instruction to fan work out
The `agent_swarm` tool works with the mode off; what the mode adds is the doctrine — decompose finely, give every member a disjoint scope, do not do the work yourself. `/swarm`, `/swarm on|off`, `/swarm <task>`, gated on the tool actually being in the toolset. Two triggers, not upstream's three. Upstream's `tool` trigger exists so invoking the tool makes the doctrine appear; kigi's tool carries its own description and works with the mode off, so that trigger would add a state only the tool can reach. `Manual` persists until switched off, `Task` expires at turn end — both are real user intents. Load-bearing decisions, each from a defect found in review: - Expiry is a DROP guard, not a post-loop call. A user interrupt aborts the turn future rather than cancelling it, so post-loop code never runs; three `?` paths skip it too. Either way a `/swarm <task>` mode would leak into the next, unrelated prompt. - `enter` is a total no-op while armed, so the `/swarm <task>` shorthand cannot downgrade a standing `/swarm on` into a per-turn mode that then disarms itself. This matches upstream; the first cut diverged, and the test asserting the divergence was inverted with the fix. - An explicit `/swarm off` retracts UNCONDITIONALLY. The doctrine rides the conversation and so survives a resume, a fork and a compaction that an in-memory flag does not; trusting the flag left a session reading "off" with the instruction still steering and no way to clear it. That also retires the `reminder_live` field, whose doc comment claimed to know something the code cannot. - The mode is deliberately NOT persisted, unlike /goal and /graph: those strand real work when lost, this is a prompt hint whose recovery is retyping one command. Recorded in AGENTS.md so the omission reads as a decision, not a gap. - The pre-session gate is `subagents_enabled`, not a hardcoded `true`: the builder strips `agent_swarm` wherever it strips `task`, and advertising it then offers a menu entry that resolves to literal text. - The exit reminder is as emphatic as the doctrine it revokes; a single weak clause is the likelier of the two to be summarised away. The doctrine rides the existing `push_system_reminder` channel rather than a second injection path of its own.
This commit is contained in:
1 parent
9edb8729ef
commit
16328e55f9
20 files changed
+474
-4
No files matched your search
@@ -121,6 +121,8 @@ mod model_switch;
|
||||
mod prompt_queue;
|
||||
#[path = "acp_session_impl/slash_exec.rs"]
|
||||
mod slash_exec;
|
||||
#[path = "acp_session_impl/swarm.rs"]
|
||||
mod swarm;
|
||||
use super::PromptOrigin;
|
||||
use super::acp_types;
|
||||
use super::chat_persistence;
|
||||
@@ -616,6 +618,9 @@ pub(crate) struct SessionActor {
|
||||
/// layered over the goal engine. Modeled after `goal_tracker` above;
|
||||
/// all graph state logic lives in `graph_tracker.rs`.
|
||||
pub(crate) graph_tracker: Arc<parking_lot::Mutex<crate::session::graph_tracker::GraphTracker>>,
|
||||
/// Swarm mode: a standing instruction to fan work out, independent of the
|
||||
/// goal/graph engines — it steers tool choice, it does not drive turns.
|
||||
pub(crate) swarm_mode: std::cell::Cell<crate::session::swarm_mode::SwarmMode>,
|
||||
/// Max graph nodes running concurrently (1 = serial G0 behavior).
|
||||
/// Cached at actor construction from `resolve_graph_concurrency`.
|
||||
pub(crate) graph_concurrency: u32,
|
||||
@@ -1033,6 +1038,9 @@ impl SessionActor {
|
||||
// Graph rides the goal harness: nodes execute as goals, so
|
||||
// `/graph` is only real when `/goal` is.
|
||||
graph: self.graph_enabled && goal,
|
||||
swarm: tool_names
|
||||
.iter()
|
||||
.any(|n| n == kigi_tools::implementations::kigi::AGENT_SWARM_TOOL_NAME),
|
||||
}
|
||||
}
|
||||
/// Names of every tool registered with the session's tool bridge.
|
||||
|
||||
Reference in new issue
Block a user