-
Notifications
You must be signed in to change notification settings - Fork 132
feat: add /swarm parallel agent-swarm orchestration #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0406ad0
7591e67
985fd5c
9c309b1
b0b61c2
d6a3d91
fc5e4bf
8021cec
adc18ad
3475837
7ed20f3
03e49e5
81749b9
e873370
0d11fbc
c03ba22
649596b
adb6827
e88003f
60bc6be
f2cc148
5375300
df04b8d
d6942ec
cc9176b
38ba4b8
a17cfee
9d7c9a6
e288ffa
f31bf2b
1461143
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@moonshot-ai/agent-core": minor | ||
| "@moonshot-ai/kimi-code": minor | ||
| --- | ||
|
|
||
| Add `/swarm` command and Swarm tool: decompose a task into parallel role-specialized subagents and synthesize their results. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { NO_ACTIVE_SESSION_MESSAGE } from '../constant/kimi-tui'; | ||
| import { formatErrorMessage } from '../utils/event-payload'; | ||
| import type { SlashCommandHost } from './dispatch'; | ||
|
|
||
| export function buildSwarmPrompt(task: string): string { | ||
| return [ | ||
| 'Use the Swarm tool to accomplish the following task.', | ||
| 'Call the Swarm tool exactly once with this task as its `task` argument; do not do the work yourself.', | ||
| '', | ||
| 'Task:', | ||
| task, | ||
| ].join('\n'); | ||
| } | ||
|
|
||
| export async function handleSwarmCommand(host: SlashCommandHost, args: string): Promise<void> { | ||
| const session = host.session; | ||
| if (session === undefined) { | ||
| host.showError(NO_ACTIVE_SESSION_MESSAGE); | ||
| return; | ||
| } | ||
| const task = args.trim(); | ||
| if (task.length === 0) { | ||
| host.showError('Usage: /swarm <task>'); | ||
| return; | ||
| } | ||
| // Show the readable command in the transcript before the turn starts. The | ||
| // prompt actually sent to the model is the verbose buildSwarmPrompt wrapper, | ||
| // so without this the live transcript would show a Swarm tool card with no | ||
| // preceding user request. | ||
| host.appendUserTranscriptEntry(`/swarm ${task}`); | ||
| // Route through the same session-request lifecycle as a normal send / | ||
| // skill activation rather than calling session.prompt raw. beginSessionRequest | ||
| // flips streamingPhase out of 'idle' synchronously, so the input gate closes | ||
| // immediately and shows the waiting pane; otherwise, during the window before | ||
| // turn.started arrives the UI still thinks it is idle and a fast follow-up | ||
| // message could be dispatched as a second concurrent prompt and be silently | ||
| // dropped as agent_busy. | ||
| host.beginSessionRequest(); | ||
| try { | ||
| await session.prompt(buildSwarmPrompt(task)); | ||
|
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This calls Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This starts a real model turn but, unlike the normal send path, never appends the user's Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence after the live-transcript fix: this still records the verbose Useful? React with 👍 / 👎. |
||
| } catch (error) { | ||
| host.failSessionRequest(`Failed to start swarm: ${formatErrorMessage(error)}`); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import type { Component } from '@earendil-works/pi-tui'; | ||
|
|
||
| import type { SwarmEvent } from './swarm-dashboard-model'; | ||
| import type { ToolCallBlockData, ToolResultBlockData } from '#/tui/types'; | ||
|
|
||
| /** | ||
| * Narrow shared contract for a card that the streaming-UI managed tool-call | ||
| * lifecycle owns in `_pendingToolComponents` (keyed by tool call id). Both | ||
| * `ToolCallComponent` and `SwarmCard` implement it, so callers can hold a | ||
| * registry value and dispatch the lifecycle methods below polymorphically. | ||
| * | ||
| * The interface is deliberately minimal: it only carries members the callers | ||
| * invoke on the union without first knowing which concrete card they hold. | ||
| * Anything reached only after `isSwarm()` returns false (the whole subagent | ||
| * API, progress lines, plan info, background-task terminal status, …) is left | ||
| * off and accessed via `instanceof ToolCallComponent` narrowing at the call | ||
| * site instead. | ||
| */ | ||
| export interface ManagedToolCard extends Component { | ||
| /** True iff this card drives the `Swarm` coordinator dashboard. */ | ||
| isSwarm(): boolean; | ||
|
|
||
| /** | ||
| * Fold a swarm dashboard event into the card and re-render in place. A safe | ||
| * no-op on a non-swarm card so callers can route blindly after an | ||
| * `isSwarm()` guard without re-narrowing. | ||
| */ | ||
| applySwarm(event: SwarmEvent): void; | ||
|
|
||
| /** Deliver the tool result and drive the card to its terminal state. */ | ||
| setResult(result: ToolResultBlockData): void; | ||
|
|
||
| /** Re-sync the live tool-call metadata (args, truncation, …). */ | ||
| updateToolCall(toolCall: ToolCallBlockData): void; | ||
|
|
||
| /** Tool-output expansion toggle (Ctrl+O). No-op on cards without a body. */ | ||
| setExpanded(expanded: boolean): void; | ||
|
|
||
| /** | ||
| * Plan-box expansion toggle. Returns true iff the card actually owns a plan | ||
| * preview (so the caller can decide whether to consume the keystroke). | ||
| */ | ||
| setPlanExpanded(expanded: boolean): boolean; | ||
|
|
||
| /** Release any timers/resources. Must be safe to call more than once. */ | ||
| dispose(): void; | ||
|
|
||
| /** Readonly view of the backing tool call (id, name, description). */ | ||
| readonly toolCallView: Readonly<ToolCallBlockData>; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This directly prompts the current session to call
Swarm, but resumed sessions created before this commit replay their oldtools.set_active_toolsrecord from the wire, so their active tool list does not include the newly addedSwarmentry fromagent.yaml. In that context/swarm <task>is accepted by the TUI but the model is asked to use a tool that is not exposed, so the command fails or devolves into normal chat; migrate old agent tool lists or check tool availability before sending this framed prompt.Useful? React with 👍 / 👎.