Skip to content

feat(policy): support subagent-specific policies in TOML#21431

Merged
akh64bit merged 3 commits intomainfrom
akkr/subagents-policy-mar6
Mar 9, 2026
Merged

feat(policy): support subagent-specific policies in TOML#21431
akh64bit merged 3 commits intomainfrom
akkr/subagents-policy-mar6

Conversation

@akh64bit
Copy link
Copy Markdown
Contributor

@akh64bit akh64bit commented Mar 6, 2026

Summary

This PR introduces the ability to specify a subagent directly in policy rules via the .toml configuration file. By adding a subagent property to PolicyRule, we allow users to restrict rules to specific subagents, preventing these rules from applying universally across all agents unless desired.

Details

  • Schema Update: Added subagent to PolicyRuleSchema in the toml-loader.
  • Event Modification: Expanded ToolConfirmationRequest in message-bus to take an optional subagent field.
  • Context Injection: Updated LocalAgentExecutor to inject the executing subagent's name via a Proxy applied to its MessageBus (which was safely implemented directly via method override instead of an unsafe Proxy object), automatically populating subagent for any subagent-originated TOOL_CONFIRMATION_REQUEST.
  • Policy Enforcement: PolicyEngine now includes an explicit check for subagent within ruleMatches. If a rule defines a subagent, it will only trigger if the subagent executing the tool matches.
  • Fallbacks: If subagent is not specified in the .toml file, the rule applies universally (to both main and subagents).

Related Issues

Fixes #21134

How to Validate

  1. Run the test suite using npm run preflight.
  2. Review the updated policy/toml-loader.test.ts and agents/local-executor.test.ts.
  3. Try defining a rule in a workspace policy TOML with a subagent flag (e.g. subagent = "docs-writer") and verify it's only triggered by that subagent.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

@akh64bit akh64bit requested a review from a team as a code owner March 6, 2026 18:22
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 enhances the policy enforcement system by introducing granular control over tool usage for subagents. It allows developers to define policies that are specific to individual subagents, preventing universal application of rules unless explicitly desired. This change provides greater flexibility and precision in managing agent behavior and permissions within the system.

Highlights

  • Subagent-Specific Policies: Introduced the ability to define policy rules that apply only to specific subagents via TOML configuration files.
  • Schema and Type Updates: Extended the PolicyRuleSchema in toml-loader and the ToolConfirmationRequest interface in message-bus with an optional subagent field.
  • Subagent Context Injection: LocalAgentExecutor was updated to inject the executing subagent's name into TOOL_CONFIRMATION_REQUEST messages by using a proxied MessageBus.
  • Policy Enforcement Logic: The PolicyEngine now explicitly checks for the subagent field when evaluating policy rules, ensuring rules only trigger if the executing subagent matches the rule's definition.
  • Universal Fallback: Policy rules that do not specify a subagent will continue to apply universally to both main and subagents.
Activity
  • No human activity (comments, reviews, progress updates) has been recorded for this pull request yet.
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.

@akh64bit akh64bit requested review from a team, Abhijit-2592 and gundermanc March 6, 2026 18:25
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 6, 2026

Size Change: +941 B (0%)

Total Size: 26 MB

ℹ️ View Unchanged
Filename Size Change
./bundle/gemini.js 25.6 MB +941 B (0%)
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 221 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 227 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 11.5 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B 0 B
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B

compressed-size-action

@akh64bit akh64bit requested a review from abhipatel12 March 6, 2026 18:25
@akh64bit akh64bit added area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Mar 6, 2026
@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 6, 2026

This PR is a followup from this PR #21133
As per discussion we decided that we want to have capability to change the policy of subagents in policy engine (.toml file) instead of settings.json.
This PR is clean implementation of the parent issue from the main branch.

Copy link
Copy Markdown
Contributor

@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 introduces support for subagent-specific policies in TOML configuration files, enhancing control over agent behavior. However, the current implementation contains critical flaws that allow subagents to bypass these policies. Specifically, the Proxy used to inject subagent names into the MessageBus is bypassed when tools use the request method due to incorrect this binding, and the PolicyEngine fails to propagate the subagent identifier for shell commands. These issues compromise the integrity of the subagent isolation model. Additionally, while the Proxy solution is clever, it introduces potential fragility and maintainability concerns.

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = Reflect.get(target, prop, receiver);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return typeof value === 'function' ? value.bind(target) : value;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

The Proxy implementation used to inject the subagent name into MessageBus messages incorrectly binds all methods to the target (the original MessageBus instance). When a tool calls messageBus.request(...), the request method is executed with this set to the original MessageBus. Since MessageBus.request internally calls this.publish(...), it calls the original publish method instead of the proxied one, bypassing the subagent name injection. This allows subagents to bypass any policies that rely on the subagent field. This Proxy approach, while clever, introduces fragility and makes the code harder to reason about and maintain, as it relies on assumptions about internal MessageBus implementation and could break with future changes.

Suggested change
return typeof value === 'function' ? value.bind(target) : value;
return typeof value === 'function' ? value.bind(receiver) : value;

@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 6, 2026

I've applied the suggested fix to bind the proxy functions to receiver instead of target to ensure internal calls like MessageBus.request correctly invoke the proxied methods. Additionally, I updated PolicyEngine.checkShellCommand to explicitly accept and propagate the subagent identifier so that it applies during shell command validations. The unit tests and preflight checks have been successfully run.

@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 6, 2026

@abhipatel12 This links to the parent issue #20195

@akh64bit akh64bit enabled auto-merge March 6, 2026 19:05
@scidomino
Copy link
Copy Markdown
Collaborator

  1. Proxy Usage and eslint-disable Comments:
    The PR description explicitly states that the injecting of subagent on the MessageBus "was safely implemented directly via method override instead of an unsafe Proxy object". However, the code implementation contradicts this and explicitly uses a new Proxy().
    Within that proxy code, two ESLint rules are disabled (@typescript-eslint/no-unsafe-assignment and @typescript-eslint/no-unsafe-return). Disabling type-checking rules and having any values goes directly against the Gemini CLI Strict Development Rules.
    Solution: We can recommend the actual method override they mentioned in their description. Using Object.create(parentMessageBus) cleanly resolves the typing issues, removes the need for eslint-disable lines, and automatically propagates internal class calls (like MessageBus.request calling publish) correctly to the overridden method.

  2. Missing Documentation:
    The subagent property should be documented in the TOML rule schema section of docs/reference/policy-engine.md.

@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 9, 2026

@scidomino Thanks for the review! I've addressed the feedback:

  1. Proxy Usage: Replaced the Proxy implementation with Object.create(parentMessageBus) to ensure proper method bindings for internal calls like MessageBus.request and removed the unsafe eslint-disable directives.
  2. Missing Documentation: Added the subagent property documentation to the TOML rule schema section in docs/reference/policy-engine.md.

I've also resolved the merge conflicts with the main branch. Let me know if there is anything else!

@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 9, 2026

@gundermanc Thanks for the suggestion! As per @scidomino's recommendation above, I've actually just replaced the Proxy implementation entirely with Object.create(parentMessageBus). This natively preserves the internal class call bindings (like MessageBus.request) and eliminates the need for the Proxy overhead altogether.


// Create an override object to inject the subagent name into tool confirmation requests
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const subagentMessageBus = Object.create(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: can we type the local const instead of casting:

    const subagentMessageBus: typeof parentMessageBus = Object.create(
      parentMessageBus,
    );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think doing so lets us delete the suppression above.

runtimeContext: Config,
onActivity?: ActivityCallback,
): Promise<LocalAgentExecutor<TOutput>> {
const parentMessageBus = runtimeContext.getMessageBus();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to add any tests for this path?

}

// Check subagent if specified (only for PolicyRule, SafetyCheckerRule doesn't have it)
if ('subagent' in rule && rule.subagent) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need tests for this case?


# (Optional) The name of a subagent. If provided, the rule only applies to tool calls
# made by this specific subagent.
subagent = "generalist"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we configure different values of this rule for multiple subagents by having the same rule muliple times, each with a different subagent?

* The name of the subagent this rule applies to.
* If undefined, the rule applies regardless of whether it's the main agent or a subagent.
*/
subagent?: string;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you considered instead structuring the schema to be a map from subagent name => rules, instead of having a flat set of rules with the subagent name?

I wonder if that might prevent some potential invalid states, like having conflicting configs for the same subagent.

Copy link
Copy Markdown
Member

@gundermanc gundermanc left a comment

Choose a reason for hiding this comment

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

Approved with some suggestions.

@akh64bit akh64bit added this pull request to the merge queue Mar 9, 2026
@akh64bit
Copy link
Copy Markdown
Contributor Author

akh64bit commented Mar 9, 2026

@gundermanc Good catch! I've updated the implementation to strongly type the local subagentMessageBus const instead of casting the return type, making the assignment cleaner. Since Object.create() still inherently returns any under TS 5+, an ESLint @typescript-eslint/no-unsafe-assignment suppression is still required, but it does eliminate the explicit type cast overhead. The fix has been applied.

@gundermanc
Copy link
Copy Markdown
Member

Since Object.create() still inherently returns any under TS 5+, an ESLint @typescript-eslint/no-unsafe-assignment suppression is still required

I think you can typically assign an any typed thing to an unknown typed local without generating a warning.

Merged via the queue into main with commit 527074b Mar 9, 2026
26 of 27 checks passed
@akh64bit akh64bit deleted the akkr/subagents-policy-mar6 branch March 9, 2026 20:18
kunal-10-cloud pushed a commit to kunal-10-cloud/gemini-cli that referenced this pull request Mar 12, 2026
liamhelmer pushed a commit to badal-io/gemini-cli that referenced this pull request Mar 12, 2026
yashodipmore pushed a commit to yashodipmore/geemi-cli that referenced this pull request Mar 21, 2026
SUNDRAM07 pushed a commit to SUNDRAM07/gemini-cli that referenced this pull request Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a mechanism to add the policy for subagents.

3 participants