Skip to content

Bugfix - Subagents do not include base rules#678

Open
jutaz wants to merge 5 commits into
hotovo:mainfrom
jutaz:fix/subagent-system-prompt-rules
Open

Bugfix - Subagents do not include base rules#678
jutaz wants to merge 5 commits into
hotovo:mainfrom
jutaz:fix/subagent-system-prompt-rules

Conversation

@jutaz
Copy link
Copy Markdown
Contributor

@jutaz jutaz commented Feb 21, 2026

As per my investigation, subagents do not receive AGENTS.md and .aider-desk/rules/ files when they have a custom systemPrompt configured. The bug was in agent.ts where getSystemPrompt() was only called when systemPrompt was undefined - but subagents pass their custom prompt, causing the base prompt with rules to be skipped entirely.

@github-actions github-actions Bot requested a review from wladimiiir February 21, 2026 09:37
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @jutaz, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug where subagents, when configured with a custom system prompt, failed to inherit essential base rules. The fix ensures that all agents consistently incorporate foundational project rules by explicitly merging them with any custom prompts, thereby preventing unintended deviations in agent behavior. New tests have been added to guarantee the robustness of this prompt merging mechanism.

Highlights

  • Bugfix: Subagent System Prompt Merging: Resolved an issue where subagents with custom systemPrompt configurations would inadvertently skip the inclusion of base rules defined in AGENTS.md and .aider-desk/rules/ files. Previously, the getSystemPrompt() method was only called if systemPrompt was undefined, leading to base rules being ignored when a custom prompt was present.
  • System Prompt Logic Update: Modified the runAgent method to explicitly fetch the base system prompt (containing project rules) and then merge it with any custom systemPrompt provided. This ensures that base rules are always prepended to custom prompts, maintaining essential agent behavior.
  • New Test Coverage: Introduced a new test suite in agent.test.ts specifically to validate the systemPrompt merging logic. These tests cover scenarios where custom prompts are merged with base prompts, only base prompts are used, and empty base prompts are handled gracefully.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/main/agent/tests/agent.test.ts
    • Added new test suite 'systemPrompt merging' to verify the correct combination of base and custom system prompts.
    • Introduced tests for scenarios including merging both prompts, using only the base prompt, and handling an empty base prompt.
  • src/main/agent/agent.ts
    • Updated the runAgent method to always retrieve the baseSystemPrompt from the promptsManager.
    • Implemented logic to merge the baseSystemPrompt with any provided systemPrompt, ensuring base rules are prepended.
Activity
  • No specific activity (comments, reviews, or progress updates) was provided in the context for this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Feb 21, 2026

Code Review Summary

Status: 2 New Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 2
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src/main/agent/agent.ts 891 Missing else clause for the case when includeRules is true, systemPrompt is provided, but wrappedRules is empty/falsy
src/main/agent/agent.ts 1741 Same missing else clause issue - when includeRules is true, customSystemPrompt is provided, but wrappedRules is empty/falsy
Files Reviewed (9 files)

Fix these issues in Kilo Cloud

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully addresses the issue where subagents were missing base rules and project-level rule files when a custom system prompt was provided, ensuring the base system prompt is always fetched and merged. However, this change increases exposure to a pre-existing prompt injection vulnerability: rule files are embedded in the prompt using CDATA sections without sanitizing the ]]> termination sequence, which could allow an attacker to hijack the agent's behavior. Additionally, the current implementation in agent.ts has inefficiencies and potential prompt duplication, and new unit tests need to be reorganized.

Comment thread src/main/agent/agent.ts
if (!systemPrompt) {
systemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), task, profile);
// Get the base system prompt (project rules)
const baseSystemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), task, profile);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

This change at lines 732-739 increases the attack surface for a prompt injection vulnerability. The baseSystemPrompt embeds rule files using CDATA sections without sanitizing the ]]> termination sequence in PromptsManager.getSystemPrompt. An attacker could exploit this to inject arbitrary instructions. Additionally, the current merging logic always fetches the base system prompt, leading to inefficiency and potential duplication if systemPrompt is already a full system prompt. Consider a more refined merging approach, such as checking if the provided prompt is a full override and using the additionalInstructions parameter of getSystemPrompt for merging. Remediation for the prompt injection vulnerability involves sanitizing rule file content in src/main/prompts/prompts-manager.ts by escaping or replacing ]]> before wrapping it in a CDATA section.

Suggested change
const baseSystemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), task, profile);
if (systemPrompt) {
// If it's not a full system prompt, merge it with the base prompt (rules, persona, etc.)
if (!systemPrompt.startsWith('<AiderDeskSystemPrompt')) {
systemPrompt = await this.promptsManager.getSystemPrompt(settings, task, profile, undefined, systemPrompt);
}
} else {
// No custom prompt provided, fetch the default system prompt
systemPrompt = await this.promptsManager.getSystemPrompt(settings, task, profile);
}

});
});

describe('systemPrompt merging', () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This new describe block for system prompt merging is nested inside the Agent - getContextFilesAsToolCallMessages block. Since these tests are testing the runAgent logic and prompt merging rather than context file formatting, they should be moved to a separate top-level describe block or a block dedicated to runAgent to maintain proper test organization.

@jutaz jutaz force-pushed the fix/subagent-system-prompt-rules branch from 71b58a5 to dd3d494 Compare February 21, 2026 10:32
@aresstokrat
Copy link
Copy Markdown

Just wanted to confirm this bug from a real-world multi-agent setup.

I'm running a project with Architect (main agent) delegating to Scout (GLM-4.5-air) and Builder subagents. I have project hygiene rules configured as Skills with checkboxes enabled for both agents. Despite this, subagents consistently ignore file placement rules — creating temp scripts, diagnostic files, and test artifacts directly in the project root.

Debugging the session logs revealed the root cause matches exactly what this PR describes: the Scout subagent received only ~2,100 tokens in its context — just the bare task prompt with zero rules or skills attached. The Architect sees everything correctly, but the moment it delegates via run_task, all project rules vanish.

This has been a significant pain point — the project accumulated dozens of misplaced files (test_.js, check_.py, temp_.ts, fix_.js scattered across root directories) because subagents had no awareness of the file structure conventions.

Great catch @jutaz — looking forward to this being merged.

@aresstokrat
Copy link
Copy Markdown

One thought for a potential follow-up (not for this PR): now that base rules are correctly propagated, it might be worth considering per-profile rule filtering for subagents in the future.

In my setup, the Architect profile has extensive rules (deploy runbook, db-safety, release protocol, methodology docs) that are irrelevant for a Scout subagent doing read-only file navigation, and they eat into the attention budget of smaller models like GLM-4.5-air.

Something like a rules or includeRules field in subagent profile config — so each subagent only receives the rules relevant to its role — would be a nice optimization. But that's a separate feature, this bugfix is the right first step.

@wladimiiir
Copy link
Copy Markdown
Contributor

@aresstokrat
It is possible to add rule files to a specific agent, by adding it into .aider-desk/agents/my-agent/rules folder. So in case you have a agent that's specifically used only as a subagent, you can put the rules there.

Comment thread src/main/agent/agent.ts
systemPrompt = `${wrappedRules}\n\n${customSystemPrompt}`;
} else if (!customSystemPrompt) {
systemPrompt = wrappedRules || '';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Missing else clause causes systemPrompt to be undefined

When includeRules is true, customSystemPrompt exists, but wrappedRules is empty/falsy, the systemPrompt variable remains undefined. This will cause line 1520 to use an empty string instead of the custom prompt.

Add an else clause:

} else if (customSystemPrompt) {
  // Use custom prompt even when rules are empty
  systemPrompt = customSystemPrompt;
}

jutaz added 3 commits March 7, 2026 13:37
- Add toggle option to SubagentConfig (default: false)
- When enabled, subagents receive project rules (AGENTS.md, .aider-desk/rules)
- Create rules-block.hbs template for standardized rules wrapping
- Add UI checkbox in subagent settings section
- Add i18n translations for the new option
- Add tests for includeRules behavior (on/off/undefined)
@jutaz jutaz force-pushed the fix/subagent-system-prompt-rules branch from ce12d69 to b1a28f7 Compare March 7, 2026 11:38
Comment thread src/main/agent/agent.ts Outdated
}

const messages = await this.prepareMessages(task, profile, await task.getContextMessages(), await task.getContextFiles());
<<<<<<< HEAD
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Unresolved merge conflict

This file contains merge conflict markers that must be resolved before merging. The conflict is between HEAD and commit ce12d69 in the estimateTokens method.

The conflict appears to be about:

  • HEAD: getAvailableTools(task, 'agent', profile, provider, profile.model)
  • Branch: getAvailableTools(task, profile, provider) plus the new includeRules logic

You need to:

  1. Resolve the conflict by choosing the correct method signature for getAvailableTools
  2. Integrate the includeRules logic from the branch
  3. Remove the conflict markers (<<<<<<<, =======, >>>>>>>)
  4. Test that the merged code works correctly

The mock was missing the dispatchEvent method which is called by
extensionManager in agent.ts. Added mock that returns the data passed to it
to simulate the extension event system.
Comment thread src/main/agent/agent.ts
} else if (!systemPrompt) {
systemPrompt = wrappedRules || '';
}
} else if (!systemPrompt) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Logic bug - this condition will never be true

The condition else if (!systemPrompt) at line 890 will never execute because systemPrompt is already set at line 734-736 (before this new code block). When systemPrompt is initially undefined, it gets set to baseSystemPrompt at line 735, so by the time execution reaches line 890, systemPrompt will never be falsy.

This means:

  1. For main agents (non-subagents), the base system prompt won't be applied as intended
  2. The logic at lines 873-893 is redundant with the earlier logic at 734-736

Suggested fix: Remove the earlier assignment at line 734-736, or restructure this logic to handle all systemPrompt assignment in one place.

Comment thread src/main/agent/agent.ts
} else if (!customSystemPrompt) {
systemPrompt = wrappedRules || '';
}
} else if (customSystemPrompt) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Same logic bug as in the other location

This has the same issue as the first location (line 890). The condition else if (customSystemPrompt) at line 1738 should work correctly, but the fallback at line 1741-1743 (else { systemPrompt = baseSystemPrompt; }) may not work as intended if there's similar earlier logic in this function that pre-populates systemPrompt.

Verify that systemPrompt is not already set before this code block executes. If it is, this fallback logic will never execute.

Comment thread src/main/agent/agent.ts
${systemPrompt}`;
} else if (!systemPrompt) {
systemPrompt = wrappedRules || '';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Missing else clause for the case when includeRules is true, systemPrompt is provided, but wrappedRules is empty/falsy

The current logic:

if (systemPrompt && wrappedRules) {
  systemPrompt = `${wrappedRules}\n\n${systemPrompt}`;
} else if (!systemPrompt) {
  systemPrompt = wrappedRules || '';
}

This doesn't handle the case where systemPrompt exists but wrappedRules is empty. In this scenario, systemPrompt remains unchanged, which may not be the intended behavior.

Consider adding an else clause:

if (systemPrompt && wrappedRules) {
  systemPrompt = `${wrappedRules}\n\n${systemPrompt}`;
} else if (!systemPrompt) {
  systemPrompt = wrappedRules || '';
} else {
  // systemPrompt exists but wrappedRules is empty - use systemPrompt as-is
  // (no change needed, but makes intent explicit)
}

Comment thread src/main/agent/agent.ts
${customSystemPrompt}`;
} else if (!customSystemPrompt) {
systemPrompt = wrappedRules || '';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Same missing else clause issue as in the other location (line 891)

When includeRules is true, customSystemPrompt is provided, but wrappedRules is empty/falsy, the code doesn't explicitly handle this case. Consider adding an else clause to make the intent clear:

if (customSystemPrompt && wrappedRules) {
  systemPrompt = `${wrappedRules}\n\n${customSystemPrompt}`;
} else if (!customSystemPrompt) {
  systemPrompt = wrappedRules || '';
} else {
  // customSystemPrompt exists but wrappedRules is empty - use customSystemPrompt as-is
  systemPrompt = customSystemPrompt;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants