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:
@@ -164,6 +164,56 @@ edges stay deterministic Rust. The harness appends a terminal
|
||||
the replan cap; `{"ops": []}` is a respected free no-op; failures
|
||||
degrade.
|
||||
|
||||
## Swarm (`agent_swarm` tool + `/swarm` mode)
|
||||
|
||||
Cheap, unstructured fan-out — the one thing `/graph` does not offer. One
|
||||
`prompt_template` containing `{{item}}` is expanded over an `items` list
|
||||
into up to `MAX_AGENT_SWARM_MEMBERS` (128) ordinary subagents, run to
|
||||
completion, and returned as ONE aggregate. Ported from kimi-code; entirely
|
||||
client-side, no backend surface.
|
||||
|
||||
- Engine (`kigi-tools/.../kigi/agent_swarm/`): `plan.rs` validates and
|
||||
expands (every fault is reported BEFORE a member starts; expanded
|
||||
prompts must be pairwise distinct), `schedule.rs` is the launch ramp as
|
||||
pure arithmetic (5 immediate, then 1 per 700ms; capacity shrinks on a
|
||||
provider rate limit and recovers after a quiet window;
|
||||
`KIGI_AGENT_SWARM_MAX_CONCURRENCY` caps it and a malformed value is a
|
||||
hard error), `run.rs` drives it against the EXISTING single-spawn
|
||||
`SubagentBackend`, `tool.rs` is the tool.
|
||||
- `SwarmMemberOutcome::Backgrounded` is load-bearing: a member that
|
||||
outlives the 600s foreground budget is detached by the coordinator and
|
||||
KEEPS RUNNING. It is never reported `Failed` and never offered for
|
||||
resume — relaunching its item would put a second agent on the same
|
||||
files. Members share the caller's tree with NO isolation; the
|
||||
distinct-prompt rule is the only thing keeping them apart.
|
||||
- `InFlightGuard` (run.rs) cancels live members when the runner's future
|
||||
is dropped. Send-now cancels the turn WITHOUT cancelling subagents and
|
||||
aborts the task; the dropped receivers read as "parent gone" and each
|
||||
child re-attaches itself. There is no cooperative path to use instead —
|
||||
`kigi_tool_runtime::Cancellation` is constructed nowhere.
|
||||
- Both retries (`MAX_RATE_LIMIT_RETRIES`) and wall clock
|
||||
(`MAX_SWARM_RUNTIME`) are bounded: the swarm blocks the caller's turn,
|
||||
so every wait needs a ceiling. Stragglers at the deadline are reported
|
||||
as still-running WITH their ids.
|
||||
- `ToolKind::AgentSwarm` is its own variant because `TemplateRenderer`'s
|
||||
`by_kind` map holds ONE tool name per kind — sharing `Task` would
|
||||
silently redirect `${{ tools.by_kind.task }}` in other tools' prompts.
|
||||
It gates exactly as `Task` does (`capability.rs`), and `builder.rs`
|
||||
strips it wherever `task` is stripped. `MAX_SUBAGENT_DEPTH` stays 1:
|
||||
upstream's unlimited nesting is a hazard, not a feature.
|
||||
- `/swarm | /swarm on|off | /swarm <task>` (`BuiltinGate::Swarm` = the
|
||||
tool is in the toolset) arms a doctrine reminder via the existing
|
||||
`push_system_reminder`. Two triggers only: `Manual` persists until
|
||||
switched off, `Task` auto-expires at turn end via `SwarmTurnGuard` — a
|
||||
DROP guard, because a user interrupt aborts the turn future and never
|
||||
reaches post-loop code. `enter` is a total no-op while armed so the
|
||||
`/swarm <task>` shorthand cannot downgrade a standing `/swarm on`.
|
||||
- The mode is deliberately NOT persisted (unlike `/goal` and `/graph`,
|
||||
which strand real work if lost): it is a prompt hint whose recovery is
|
||||
retyping one command. The injected doctrine IS durable, so an explicit
|
||||
`/swarm off` retracts UNCONDITIONALLY — a resumed or compacted session
|
||||
can read as "off" with the instruction still in context.
|
||||
|
||||
## Provider registry & API-key auth (post-0.1.3 expansion)
|
||||
|
||||
- The platform registry is compiled-in spec rows in `kigi-models`
|
||||
|
||||
Reference in New Issue
Block a user