-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix(agent-core-v2): cap default subagent delegation at one level #3012
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
Changes from all commits
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,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Fix unbounded recursive subagent spawning by capping default agent delegation at a single level; custom agent profiles can still declare their own delegation allowlists (including `*`). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| import { renderPrompt } from '#/_base/utils/render-prompt'; | ||
|
|
||
| import { | ||
| DEFAULT_AGENT_PROFILE_NAME, | ||
| type AgentProfile, | ||
| type AgentProfileContext, | ||
| type EnvironmentDisclosureSnapshot, | ||
| type SystemPromptRenderResult, | ||
| } from './agentProfileCatalog'; | ||
| import { BUILTIN_AGENT_PROFILE_SOURCE_ID } from './builtinAgentProfileLoader'; | ||
|
|
||
| import SYSTEM_PROMPT_TEMPLATE from './system.md?raw'; | ||
|
|
||
|
|
@@ -27,8 +29,68 @@ export function subagentAllowlistFor( | |
| readonly profileName?: string; | ||
| readonly subagents?: readonly string[]; | ||
| }, | ||
| extras?: readonly string[], | ||
| ): readonly string[] | undefined { | ||
| return caller.profileName === undefined ? catalog.getDefault().subagents : caller.subagents; | ||
| const declared = caller.subagents ?? catalog.getDefault().subagents; | ||
|
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.
When a user or project 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.
When a custom agent overrides an allowlisted built-in such as Useful? React with 👍 / 👎. |
||
| if (declared?.length === 1 && declared[0] === '*') return undefined; | ||
| if (extras === undefined || extras.length === 0) return declared; | ||
| return [...new Set([...(declared ?? []), ...extras])]; | ||
| } | ||
|
|
||
| export function isDiscoveredAgentProfileSource(sourceId: string | undefined): boolean { | ||
| return ( | ||
| sourceId !== undefined && | ||
| sourceId !== BUILTIN_AGENT_PROFILE_SOURCE_ID && | ||
| !sourceId.startsWith('feature:') | ||
| ); | ||
| } | ||
|
|
||
| export function rootDelegationExtras( | ||
| catalog: { | ||
| inspect(name: string): { readonly sourceId: string } | undefined; | ||
| }, | ||
| caller: { | ||
| readonly profileName?: string; | ||
| readonly subagents?: readonly string[]; | ||
| }, | ||
| profiles: readonly { readonly name: string }[], | ||
| ): readonly string[] | undefined { | ||
| if ( | ||
| caller.profileName !== undefined && | ||
| caller.profileName !== DEFAULT_AGENT_PROFILE_NAME && | ||
| caller.subagents !== undefined | ||
| ) { | ||
| return undefined; | ||
| } | ||
| const discovered = profiles | ||
| .filter( | ||
| (profile) => | ||
| profile.name !== DEFAULT_AGENT_PROFILE_NAME && | ||
| isDiscoveredAgentProfileSource(catalog.inspect(profile.name)?.sourceId), | ||
| ) | ||
| .map((profile) => profile.name); | ||
| return discovered.length === 0 ? undefined : discovered; | ||
| } | ||
|
|
||
| export function profileCanDelegate( | ||
| profile: Pick<AgentProfile, 'tools' | 'disallowedTools'>, | ||
| ): boolean { | ||
| const possesses = (name: string) => | ||
| (profile.tools === undefined || profile.tools.includes(name)) && | ||
| !(profile.disallowedTools ?? []).includes(name); | ||
| return possesses('Agent') || possesses('AgentSwarm'); | ||
| } | ||
|
|
||
| export function withoutDelegatingTargets( | ||
| catalog: { | ||
| get(name: string): Pick<AgentProfile, 'tools' | 'disallowedTools'> | undefined; | ||
| }, | ||
| allowlist: readonly string[], | ||
| ): readonly string[] { | ||
| return allowlist.filter((name) => { | ||
| const target = catalog.get(name); | ||
| return target === undefined || !profileCanDelegate(target); | ||
| }); | ||
| } | ||
|
|
||
| export function subagentTypeNotAllowedMessage( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,7 @@ registerAgentProfile({ | |
| name: 'agent', | ||
| description: 'Default agent', | ||
| tools: AGENT_TOOLS, | ||
| subagents: ['coder', 'explore', 'plan'], | ||
|
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.
When the built-in default profile is active, this hard-coded allowlist filters the Useful? React with 👍 / 👎. |
||
| renderSystemPrompt: (context) => | ||
| renderSystemPromptResult('', context, { skillActive: skillActiveFor(AGENT_TOOLS) }), | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,8 +79,7 @@ export function parseAgentFileText(options: ParseAgentFileOptions): AgentFileDef | |
| options.path, | ||
| ); | ||
| const rawSubagents = parseStringList(frontmatter['subagents'], 'subagents', options.path); | ||
| const subagents = | ||
| rawSubagents?.length === 1 && rawSubagents[0] === '*' ? undefined : rawSubagents; | ||
| const subagents = rawSubagents; | ||
|
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.
Preserving AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L36-L40 Useful? React with 👍 / 👎. |
||
|
|
||
| const prompt = parsed.body.trim(); | ||
| if (prompt.length === 0) { | ||
|
|
||
Large diffs are not rendered by default.
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.
When a discovered custom profile omits both
toolsandsubagents, it receives theAgenttool, inheritscoder/explore/plan, and this filter retains terminal targets such asexplore; consequently the default main agent can launch that custom agent, which can then launchexplore, contradicting the documented main → subagent cap and the requirement for an explicit allowlist to enable deeper chains. Unlike the earlier self-recursion report, the fresh evidence is this retained terminal-target path; inherited delegation should be disabled for subagents unless they explicitly declaresubagents.Useful? React with 👍 / 👎.