Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0406ad0
feat(agent-core): support profileOverride for dynamic-role subagents
RealKai42 May 29, 2026
7591e67
feat(agent-core): add swarm types and pure plan-parse/concurrency hel…
RealKai42 May 29, 2026
985fd5c
feat(agent-core): add SwarmCoordinator (plan, parallel workers, synth…
RealKai42 May 29, 2026
9c309b1
feat(agent-core): add Swarm tool wired to SwarmCoordinator with recur…
RealKai42 May 29, 2026
b0b61c2
feat(tui): add /swarm command that triggers the Swarm tool
RealKai42 May 29, 2026
d6a3d91
fix(agent-core): enforce swarm worker tool allowlist and propagate abort
RealKai42 May 29, 2026
fc5e4bf
Merge remote-tracking branch 'origin/main' into kaiyi/karachi
RealKai42 May 29, 2026
8021cec
fix(agent-core): clarify swarm planner tool guidance, add profileOver…
RealKai42 May 29, 2026
adc18ad
feat(tui): add swarm dashboard model and reducer
RealKai42 May 29, 2026
3475837
feat(tui): add SwarmDashboardComponent
RealKai42 May 29, 2026
7ed20f3
feat(agent-core): emit structured swarm progress (planned/synthesizin…
RealKai42 May 29, 2026
03e49e5
feat(tui): render swarm runs as a live dashboard instead of nested to…
RealKai42 May 29, 2026
81749b9
fix(tui): count only workers in swarm dashboard, finalize on cancel, …
RealKai42 May 29, 2026
e873370
fix(tui): render swarm via the managed tool-call lifecycle to stop du…
RealKai42 May 29, 2026
0d11fbc
fix(tui): match swarm card styling to AgentGroup conventions and fix …
RealKai42 May 29, 2026
c03ba22
fix(tui): collapse multi-line swarm task to one line in header and to…
RealKai42 May 29, 2026
649596b
Merge remote-tracking branch 'origin/main' into kaiyi/karachi
RealKai42 May 29, 2026
adb6827
feat(tui): show live token counts for running swarm workers
RealKai42 May 29, 2026
e88003f
feat(agent-core): stall-detection hard-stop for swarm workers (repeat…
RealKai42 May 29, 2026
60bc6be
fix(agent-core): remove NUL byte from swarm stall-hook repeat key
RealKai42 May 29, 2026
f2cc148
feat(agent-core): swarm coordinator failure-recovery loop (retry/rege…
RealKai42 May 29, 2026
5375300
feat(tui): surface swarm recovery (retrying/dropped) in the dashboard
RealKai42 May 29, 2026
df04b8d
fix(swarm): resolve reassign orphan row, enrich stall context, decisi…
RealKai42 May 29, 2026
d6942ec
fix(agent-core): drop subagent summary-continuation re-prompt
RealKai42 May 29, 2026
cc9176b
Merge branch 'main' into kaiyi/karachi
RealKai42 May 29, 2026
38ba4b8
fix(kimi-code): route /swarm through the session-request lifecycle
RealKai42 May 29, 2026
a17cfee
fix(kimi-code): show swarm failures distinctly from cancellation
RealKai42 May 29, 2026
9d7c9a6
fix(agent-core): reject empty planner subtask fields
RealKai42 May 30, 2026
e288ffa
fix(kimi-code): show the /swarm request in the live transcript
RealKai42 May 30, 2026
f31bf2b
refactor(kimi-code): render the swarm dashboard in a dedicated SwarmCard
RealKai42 May 30, 2026
1461143
fix(kimi-code): show the swarm report when replaying a completed run
RealKai42 May 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/swarm-agent-orchestration.md
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.
5 changes: 5 additions & 0 deletions apps/kimi-code/src/tui/commands/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
handleInitCommand,
handleTitleCommand,
} from './session';
import { handleSwarmCommand } from './swarm';

// ---------------------------------------------------------------------------
// Re-exports — keep existing consumers working
Expand Down Expand Up @@ -104,6 +105,7 @@ export interface SlashCommandHost {
switchToSession(session: Session, message: string): Promise<void>;
beginSessionRequest(): void;
failSessionRequest(message: string): void;
appendUserTranscriptEntry(content: string): void;
sendQueuedMessage(session: Session, item: QueuedMessage): void;

// UI
Expand Down Expand Up @@ -255,6 +257,9 @@ async function handleBuiltInSlashCommand(
case 'plan':
await handlePlanCommand(host, args);
return;
case 'swarm':
await handleSwarmCommand(host, args);
return;
case 'compact':
await handleCompactCommand(host, args);
return;
Expand Down
7 changes: 7 additions & 0 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ export const BUILTIN_SLASH_COMMANDS = [
priority: 100,
availability: (args) => (args.trim().toLowerCase() === 'clear' ? 'idle-only' : 'always'),
},
{
name: 'swarm',
aliases: [],
description: 'Run a task as a parallel agent swarm',
priority: 100,
availability: 'idle-only',
},
{
name: 'model',
aliases: [],
Expand Down
44 changes: 44 additions & 0 deletions apps/kimi-code/src/tui/commands/swarm.ts
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));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle sessions whose active tools lack Swarm

This directly prompts the current session to call Swarm, but resumed sessions created before this commit replay their old tools.set_active_tools record from the wire, so their active tool list does not include the newly added Swarm entry from agent.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 👍 / 👎.

Comment on lines +39 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route /swarm through the normal send lifecycle

This calls session.prompt directly, so the TUI never runs the normal sendMessageInternal setup (beginSessionRequest, streaming state, transcript entry, and queue handling). During the initial model latency before any SDK event arrives, the app still considers itself idle, so another user input or idle-only slash command can be accepted and race with the swarm turn instead of being blocked/queued like a normal prompt.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show the swarm request in the transcript

This starts a real model turn but, unlike the normal send path, never appends the user's /swarm task to the live transcript before calling session.prompt. In a live session the user sees a Swarm tool card with no preceding user request, and after resume the replayed user message comes from the internal buildSwarmPrompt(...) wrapper instead of the command/task the user actually entered; add an explicit transcript entry for the swarm request before dispatching the prompt.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist the readable /swarm request for replay

Fresh evidence after the live-transcript fix: this still records the verbose buildSwarmPrompt(task) as a normal user prompt, and replay renders user-origin messages directly (apps/kimi-code/src/tui/controllers/session-replay.ts:254-256). The manual appendUserTranscriptEntry('/swarm ...') only affects the current in-memory transcript, so after resuming/exporting history the turn shows the internal “Use the Swarm tool…” wrapper instead of the command/task the user actually entered; send the framed prompt with a non-user/internal origin or persist a replayable readable request alongside it.

Useful? React with 👍 / 👎.

} catch (error) {
host.failSessionRequest(`Failed to start swarm: ${formatErrorMessage(error)}`);
}
}
50 changes: 50 additions & 0 deletions apps/kimi-code/src/tui/components/messages/managed-tool-card.ts
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>;
}
Loading
Loading