-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Added: Ability to hide subagents from primary agents system prompt. #4773
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
base: dev
Are you sure you want to change the base?
Conversation
- Add subagents config field to agent schema for wildcard-based filtering - Add filterSubagents helper and runtime validation in Task tool - Add per-agent subagent filtering in prompt tool resolution - Add comprehensive tests for subagent filtering patterns
- Document subagents config option for filtering agent invocations - Add examples for exclusion patterns, wildcards, and precedence rules - Include Markdown agent configuration example
- Filter @ autocomplete suggestions based on current agent's subagents configuration - Fixes bug where all subagents were shown regardless of visibility settings
fa1816b to
3c7b3e5
Compare
f8ee907 to
6a9856d
Compare
|
I really hope this one gets merged, asked for this to be implemented a while back, thanks for making it happen |
|
I think the correct approach here is to allow "task" to have granular permissions like bash does, and then you can whitelist, blacklist, wildcard match, enable/disable subagents |
@rekram1-node I actually have a question here, how does deny vs disable differ for tools/custom tools For example with Deny would the agent still see the tool description? The main advantage of restricting subagents to primary is to reduce choice overload and reduce context. So does deny work similarly to disable in that regard? |
|
I believe what you're thinking of is this: "agent": {
"build": {
"permission": {
"task": {
"subagent_name": "deny"
}
}
}
}etc. However, I chose not to go with that approach; and there is a rather good reason. The The place where it differs is that unlike others, the What this means is a human could try In any case, the original assignment and problem statement in the 2 issues was to show/hide items. Although preventing execution of a given subagent is the desired outcome, the other outcome is to avoid discoverability of the subagent by both the LLM by hiding from the system prompt (and the user). If not removed from system prompt, an LLM could still try calling it; be denied, and then get stuck or go off the rails if there are multiple subagents with similar naming. (think The current idiom for showing/hiding items within opencode configs is to expose a true/false at the top level; as we have with tools themselves and other parts of the config. There is no precedent for limiting user input/visibility from tool permissions, but there is for top level config items. (Had to rewrite this, turns out my reply 10 hours ago didn't send) |
|
@Sewer56 |
|
it doesnt have to be that way, if the user is @ it themselves then it could bypass the permission blocks, we already do similar stuff for cwd bypasses But im not sure what makes the most sense there tbh |
|
Also if it is set to "deny" then the agent should see the tool as not having that subagent as an option |
|
For what it's worth, the primary use case of this PR for me (which I have been using in my fork for the last week), is hiding the subagents from the user @ autocomplete list. I have a bunch of small subagents and i restrict them all to a single dedicated primary agent. That way my @ mention list isn't polluted in day to day use with built-in agents, but i can just tab to my 'lazy agent' and then my @ list has all of the subagents there. |
|
My only question is if you're okay with the semantic inconsistency here. Technically the permissions field controls (or should control) what an LLM is allowed to invoke. But in this case, we wouldn't be doing that, the LLM is given free range to call any subagent as it wishes. When you do an @ call, you ask the LLM to invoke on your behalf, but the LLM is really the one doing the invoking. In that vein, it could re-invoke whenever it wants, or even start guessing names of 'hidden' subagents if they are predictable. Sure you could do a 'hack', see if the previous user message mentioned a specific subagent and disallow if that was not the case, but even that has some caveats. For instance, the LLM is given free reign to call it 0-* times. Should we only give it one call? Should we give it multiple? There is ambiguity. In any case. For me, part of the desired functionality is also to hide it from the humans too. I have subagents that are meant to be purely used by LLMs as part of orchestrator loops. These subagents aren't used anywhere else but in these orchestrators, and I got multiple variants that use different models for speed/cost tradeoff. Without hiding I would have something like: every time I type |
|
I get ur point about no precedent for changing the @ completions, Ig to me whatd make the most sense: task permission says what agents the agent can access when you have that agent selected, the @ would respect it… But ig if you wanted the user to still @ it, it woudlnt be super messy you wouldnt have to read previous user message etc etc, whenever you parse the parts sent by user you would just pass a bypass flag like we do for other part types I dont know which ux makes more sense tho |
|
You can send the bypass flag, but as noted above, there is ambiguity of whether you should allow it once, or multiple times, and the LLM could really go with either. Easy solution is just multiple times, so that flag is essentially sticky. I don't mind doing that, but then yeah, you still would want the override to hide it to the user. There might be users who will still want manual invoke. This wasn't expressed in the issues above, but it's not impossible to imagine. Technically speaking you could use the perms on the tool to control visibility of subagents to the LLM primary agent. And then use the You could even make I'm open to suggestions based on common consensus. I just originally figured the easiest way forward was to have a simple toggle that flips it for everyone, with no room for confusion. For people who want an escape hatch (e.g. force call a subagent), there would still be option of just swapping primary agent to one that has the subagents available. |
After thinking through the UX, I'd like to propose an alternative design that separates the two distinct concerns here:
These are independent problems and conflating them creates the semantic confusion discussed above. Proposed Design1. Human Visibility:
|
| Config | Effect |
|---|---|
tools: { task: false } on primary |
Primary cannot spawn ANY subagents (no Task tool) |
permission.task.X: "deny" |
Primary cannot spawn subagent X specifically |
visible: false on subagent |
Human cannot see in agent menu |
Example - a primary that cannot spawn subagents at all:
{
"agent": {
"simple-build": {
"mode": "primary",
"tools": {
"task": false
}
}
}
}4. TUI Behavior Changes
When a subagent is active:
- TUI should display the subagent name (e.g.,
[explore]orexplore) rather than the primary agent name - Pressing Tab returns to the primary agent
- To select a subagent again, user goes to
/agentsmenu or uses keybind
Agent menu:
- Shows all primary agents
- Shows all
mode: allagents - Shows only subagents where
visible: true(or not set, since default istrue)
5. The @ Question: Two Options
There are two valid approaches for how humans invoke subagents. I recommend Option A but presenting both for discussion:
Option A (Recommended): Move subagent invocation entirely to agent menu
- Remove subagents from
@autocomplete entirely @is used only for file/context tagging- Subagents are invoked via
/agentsmenu or keybind visible: falsehides from the agent menu
Rationale: Subagents are designed to be used by primary agents. If a user needs frequent direct access to a subagent, they should set mode: all which makes it a primary agent that's also available as a subagent. This creates a clean separation: @ = context, agent menu = agents.
Tradeoff: More friction for users who want quick inline subagent invocation.
Option B: Keep @ for subagents but control autocomplete
@still invokes subagentsvisible: falsehides from agent menu.- Users who know the name can still type
@hidden-subagentmanually
Rationale: Preserves quick inline invocation for power users.
Tradeoff: Maintains the current inconsistency where @ is overloaded for both files and agents.
6. Complete Example
Here's a complete config showing all features:
{
"$schema": "https://opencode.ai/config.json",
"agent": {
"build": {
"mode": "primary",
"permission": {
"task": {
"*": "deny",
"code-reviewer": "allow",
"explore": "allow",
"general": "allow"
}
}
},
"orchestrator": {
"description": "Automated orchestration agent",
"mode": "primary",
"permission": {
"task": {
"*": "deny",
"orchestrator-coder": "allow",
"orchestrator-planner": "allow",
"orchestrator-quality-gate": "ask"
}
}
},
"orchestrator-coder": {
"description": "Internal: Handles coding tasks for orchestrator",
"mode": "subagent",
"visible": false
},
"orchestrator-planner": {
"description": "Internal: Handles planning for orchestrator",
"mode": "subagent",
"visible": false
},
"orchestrator-quality-gate": {
"description": "Internal: Quality verification",
"mode": "subagent",
"visible": false
},
"code-reviewer": {
"description": "Reviews code for best practices and issues",
"mode": "subagent",
"visible": true,
"tools": {
"write": false,
"edit": false,
"task": false
}
}
}
}With this config:
- Build agent can invoke
code-reviewer,explore,generalbut NOT anyorchestrator-*subagents - Orchestrator agent can only invoke its own
orchestrator-*subagents, withorchestrator-quality-gaterequiring user approval - Human sees only
code-reviewerin the agent menu (plus built-inexploreandgeneral) - Human does NOT see
orchestrator-coder,orchestrator-planner, ororchestrator-quality-gate code-reviewerdoes not see thetasktool.
Summary
| Concern | Config | Location |
|---|---|---|
| Hide subagent from human | visible: false |
On the subagent |
| Control which subagents LLM can invoke | permission.task |
On the primary agent |
| Disable Task tool entirely | tools: { task: false } |
On the primary agent |
| Make subagent also a primary | mode: all |
On the agent |
This design:
- Separates human UX from LLM behavior
- Is consistent with existing
permission.bashpatterns - Follows standard config merging (local overrides global)
- Addresses both issues [FEATURE]: Allow showing/hiding subagents from primary agents #4764 and [bug] Restrict which subagents a custom agent can spawn (built-in Plan mode can write files via sub agent) #4267
Happy to discuss further or adjust based on feedback.
|
I didn't know contributing to |
Force merge to adopt content from 3eb358f while preserving branch history.
|
Recreated the PR to work over the new permissions system. This is a bit frustrating at this point though. I've been keeping this branch very up to date on a daily basis, including resolving merges; and doing the basic testing at each step. I've replied to every comment within a matter of hours (usually less than 4), and I've adapted to every related change made in main, such as introduction of a I don't know what else I need to do. In an ideal world, this would have been merged before work on replacing the permissions system would have started; saving everyone some effort. There was more than a week's time to do this. However, instead I had to recreate the PR yet again, because the changes were simply too destructive. I'm at a loss- unfortunately. |
|
@Sewer56 appreciate ur patience and apologies for not being in contact, I will take this across the finish line today/tmr for u so u dont need to slave any longer. I try my best to keep up w/ github notifications but across github/x/discord I get hundreds of messages a day and it's really hard to read them all. |
|
I think this is good to go now that v1.1 is released? |
|
Seems good. I thought I'd need to update docs, but turns out I already did it last time. Interesting change when you swap out to new task:
orchestrator-*: allow
"*": denyBut once you swap out to the new task: {
"orchestrator-*": "allow",
"*": "deny"
}That caught me offguard for a sec. Seems to be case for other tools too. |
The task tool was incorrectly disabled when using permission rules like:
task:
"orchestrator-*": "allow"
"*": "deny"
The disabled() function was checking evaluate("task", "*", ruleset) which
matched the global deny rule, causing the tool to be removed even though
specific subagent patterns were allowed.
Changes:
- PermissionNext.disabled() now special-cases task tool to check if ANY
rule is non-deny before disabling
- prompt.ts removes task tool only if no subagents pass the filter
- Fixed pre-existing bugs where evaluate() result (Rule object) was
compared to string instead of checking .action property
|
Hm, caught a subtle bug. When merging latest. Not sure when it happened, but at some point, this didn't auto activate the So the subagents would be injected, but the It will remove tool if no subagents are runnable. And it'll allow running if at least 1 is runnable. Provided at least 1 rule allows any subagent to run. Also there was subtle change to In addition, I noticed in the new permissions system, the rules are applied in declaration order. Meaning that putting All good for merging again. |
|
this is high on my list, im just out of town for a wedding but ill try to get this in Edit: just got back from wedding too late tho gotta sleep |

Summary
Add
hiddenandpermission.taskoptions to control subagent visibility and invocation permissions.Changes
hidden: trueoption to hide subagents from the@autocomplete menudevbranchpermission.taskconfig to control which subagents an agent can invoke via the Task tool (supports allow/deny/ask with glob patterns)@autocompletePurpose
Enables orchestrator-style agent architectures where internal helper subagents should only be invoked programmatically by other agents, not directly by users, while also providing fine-grained control over which agents can call which subagents.
Modified Files
src/agent/agent.ts- Addhiddenproperty andpermission.taskmerging logicsrc/config/config.ts- Add schema for new config optionssrc/session/prompt.ts- Track user-invoked agents, regenerate Task tool description with filtered subagentssrc/tool/task.ts- Enforce task permissions with user override bypasssrc/cli/cmd/tui/component/prompt/autocomplete.tsx- Filter hidden agents from autocompletetest/permission-task.test.ts- Tests for subagent filteringpackages/sdk/js/src/v2/gen/types.gen.ts- Updated SDK typespackages/web/src/content/docs/agents.mdx- Documentation for new features