mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
fix(agent-core): drop subagent summary-continuation re-prompt
The summary-continuation pass re-prompted any subagent whose first
summary was under 200 chars to "expand" it, then read back the
follow-up turn — replacing the original output rather than appending.
For swarm's structured-output subagents this was harmful: a reviser's
compact decision JSON (e.g. {"kind":"retry"}) is always under the
threshold, so the expand turn always fired and could replace the JSON
with prose, silently degrading the recovery loop into conservative
drops. It also taxed every short-but-complete handoff with an extra
turn.
Remove the heuristic entirely so a subagent's first summary is returned
as-is. The max-tokens truncation guard is unaffected.
This commit is contained in:
parent
df04b8d2fb
commit
d6942ec5d5
3 changed files with 9 additions and 36 deletions
|
|
@ -12,15 +12,7 @@ import {
|
|||
import { linkAbortSignal } from '../utils/abort';
|
||||
import { collectGitContext } from './git-context';
|
||||
import type { Session } from './index';
|
||||
import SUMMARY_CONTINUATION_PROMPT from './summary-continuation.md';
|
||||
|
||||
/**
|
||||
* A subagent summary shorter than this many characters triggers one
|
||||
* follow-up turn that asks the subagent to expand it, so the parent
|
||||
* agent receives a technically complete handoff.
|
||||
*/
|
||||
const SUMMARY_MIN_LENGTH = 200;
|
||||
const SUMMARY_CONTINUATION_ATTEMPTS = 1;
|
||||
const HOOK_TEXT_PREVIEW_LENGTH = 500;
|
||||
const SUBAGENT_MAX_TOKENS_ERROR =
|
||||
'Subagent turn failed before completing its final summary: reason=max_tokens';
|
||||
|
|
@ -258,19 +250,7 @@ export class SessionSubagentHost {
|
|||
child.turn.prompt([{ type: 'text', text: childPrompt }], origin);
|
||||
await runChildTurnToCompletion(child, options.signal);
|
||||
|
||||
// A subagent that returns an overly terse summary leaves the parent
|
||||
// agent under-informed. Give it a bounded number of chances to expand
|
||||
// the handoff; if it is still short after that, accept it as-is rather
|
||||
// than retrying indefinitely.
|
||||
let result = lastAssistantText(child);
|
||||
let remainingContinuations = SUMMARY_CONTINUATION_ATTEMPTS;
|
||||
while (remainingContinuations > 0 && result.length < SUMMARY_MIN_LENGTH) {
|
||||
remainingContinuations -= 1;
|
||||
options.signal.throwIfAborted();
|
||||
child.turn.prompt([{ type: 'text', text: SUMMARY_CONTINUATION_PROMPT }], origin);
|
||||
await runChildTurnToCompletion(child, options.signal);
|
||||
result = lastAssistantText(child);
|
||||
}
|
||||
const result = lastAssistantText(child);
|
||||
const usage = child.usage.data().total;
|
||||
parent.emitEvent({
|
||||
type: 'subagent.completed',
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
Your previous response was too brief. Please provide a more comprehensive summary that includes:
|
||||
|
||||
1. Specific technical details and implementations
|
||||
2. Detailed findings and analysis
|
||||
3. All important information that the parent agent should know
|
||||
|
|
@ -435,15 +435,17 @@ describe('SessionSubagentHost', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('re-prompts the child when the first summary is too short', async () => {
|
||||
it('returns a short summary as-is without re-prompting the child', async () => {
|
||||
const parent = testAgent();
|
||||
parent.configure();
|
||||
parent.newEvents();
|
||||
|
||||
const longSummary = 'Detailed findings: '.repeat(20);
|
||||
const shortSummary = 'done';
|
||||
const child = testAgent();
|
||||
child.mockNextResponse({ type: 'text', text: 'done' });
|
||||
child.mockNextResponse({ type: 'text', text: longSummary });
|
||||
child.mockNextResponse({ type: 'text', text: shortSummary });
|
||||
// A second response is queued to prove it is never consumed: a short
|
||||
// summary must NOT trigger a follow-up "expand" turn.
|
||||
child.mockNextResponse({ type: 'text', text: 'Detailed findings: '.repeat(20) });
|
||||
const session = fakeSession(parent.agent, child.agent);
|
||||
const host = new SessionSubagentHost(session, 'main');
|
||||
|
||||
|
|
@ -455,12 +457,8 @@ describe('SessionSubagentHost', () => {
|
|||
signal,
|
||||
});
|
||||
|
||||
await expect(handle.completion).resolves.toMatchObject({ result: longSummary.trim() });
|
||||
expect(child.llmCalls).toHaveLength(2);
|
||||
expect(child.llmCalls[1]?.history.at(-1)).toMatchObject({
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: expect.stringContaining('too brief') }],
|
||||
});
|
||||
await expect(handle.completion).resolves.toMatchObject({ result: shortSummary });
|
||||
expect(child.llmCalls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('fails the child instead of re-prompting when the response is truncated', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue