-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Added: Ability to hide subagents from primary agents. #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.
|
This actually works on me, am onboard. Regarding Option A vs Option B, I got no particular preference. That said Option A (if chosen) should probably be a follow up PR, since it fundamentally changes how the subagents feature works. I imagine you'd want to ask a broader set of people before changing a feature this ingrained. |
This feature is not going to be merged upstream in its current form. Removes: - subagents field from Agent schema and built-in agents - subagents config option - filterSubagents function and runtime validation - Subagent filtering from prompt tool resolution - Subagent filtering from TUI autocomplete - subagents-filter.test.ts test file - Subagents documentation section from agents.mdx The SDK types will be regenerated automatically on the next build.
* revert: remove PR sst#4773 subagent restrictions feature This feature is not going to be merged upstream in its current form. Removes: - subagents field from Agent schema and built-in agents - subagents config option - filterSubagents function and runtime validation - Subagent filtering from prompt tool resolution - Subagent filtering from TUI autocomplete - subagents-filter.test.ts test file - Subagents documentation section from agents.mdx The SDK types will be regenerated automatically on the next build. * chore: format code * docs: update fork README to remove PR sst#4773 and refresh date * feat: add ghostty-opentui dependency for terminal ANSI rendering * feat: force color output in bash tool for ANSI rendering * feat: add live token tracking during streaming responses * fix: use correct subagent session ID for click navigation * feat: add search, token display, and bash ANSI viewer to TUI - Add Ctrl+F search with match highlighting and navigation - Add toggle tokens command with IN/OUT display - Add full-screen bash output viewer with ANSI color support - Integrate ghostty-terminal component for terminal rendering * docs: add implementation plans for restored PR features * ci: retry tests * [WIP] Update subagent restrictions based on PR feedback (#106) * Initial plan * fix: replace strikethrough with ANSI highlighting for search results
|
@rekram1-node thoughts? I'm onboard with the proposed approach. |
Summary
Add
subagentsconfig option to control which subagents an agent can invoke via the Task tool.Changes
subagentsfield to agent schema and configurationThis uses same rules as tools do for filtering.
Modified Files
packages/opencode/src/agent/agent.ts- Add subagents field to schema and built-in agentspackages/opencode/src/config/config.ts- Add subagents config optionpackages/opencode/src/session/prompt.ts- Filter Task tool descriptionpackages/opencode/src/tool/task.ts- Add filtering logic and runtime validationpackages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx- Filter autocompletepackages/opencode/test/subagents-filter.test.ts- Tests for filtering logicpackages/web/src/content/docs/agents.mdx- Documentation for subagents config