opencode icon indicating copy to clipboard operation
opencode copied to clipboard

Added: Ability to hide subagents from primary agents system prompt.

Open Sewer56 opened this issue 4 weeks ago β€’ 37 comments

Summary

Add hidden and permission.task options to control subagent visibility and invocation permissions.

  • fixes #4764
  • fixes #4267

Changes

  • Add hidden: true option to hide subagents from the @ autocomplete menu
    • πŸ““ This is already in dev branch
  • Add permission.task config to control which subagents an agent can invoke via the Task tool (supports allow/deny/ask with glob patterns)
  • Filter denied subagents from the Task tool description so the model doesn't attempt to invoke them
  • Bypass permission checks when users explicitly invoke subagents via @ autocomplete

Purpose

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 - Add hidden property and permission.task merging logic
  • src/config/config.ts - Add schema for new config options
  • src/session/prompt.ts - Track user-invoked agents, regenerate Task tool description with filtered subagents
  • src/tool/task.ts - Enforce task permissions with user override bypass
  • src/cli/cmd/tui/component/prompt/autocomplete.tsx - Filter hidden agents from autocomplete
  • test/permission-task.test.ts - Tests for subagent filtering
  • packages/sdk/js/src/v2/gen/types.gen.ts - Updated SDK types
  • packages/web/src/content/docs/agents.mdx - Documentation for new features

Sewer56 avatar Nov 26 '25 08:11 Sewer56

I really hope this one gets merged, asked for this to be implemented a while back, thanks for making it happen

malhashemi avatar Nov 29 '25 14:11 malhashemi

@rekram1-node

Sewer56 avatar Nov 29 '25 18:11 Sewer56

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 avatar Dec 02 '25 17:12 rekram1-node

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?

malhashemi avatar Dec 03 '25 09:12 malhashemi

@rekram1-node

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 permission field semantically speaking declares how the LLM controlling an agent is allowed to execute a tool by default, however the task tool is a bit different than the rest of the tools, semantically speaking.

The place where it differs is that unlike others, the task tool can be invoked by humans using the @ notation, with the human being presented a list of subagents to invoke.

What this means is a human could try @ calling a subagent, and then receive an error saying this is not permitted. You can think of this as typing ! to execute a command and receiving 'you are not allowed because this command has not been whitelisted in the bash tool'. That would probably be quite weird.

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 coder and coder-high, etc.)

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 avatar Dec 03 '25 10:12 Sewer56

@Sewer56 I do agree with @rekram1-node here in terms of how to disable/enable a subagent for the primary one. Having said that I do agree with you in terms of subagents should not even be mentioned in the primary agent system prompt if they are disabled. I think this goes back to my comment about how does deny vs disable works in opencode. I believe this enable/disable pattern is pretty redundant to permissions. The default behaviour for deny should be to exclude it from the system prompt. This should also not interfere with the human ability to call any subagent through @ as that should be a seperate invocation pattern. I have not reviewed the parts of the codebase that needs to be touched to achieve this, but from functionality perspective I believe that what would make most sense

malhashemi avatar Dec 03 '25 12:12 malhashemi

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

rekram1-node avatar Dec 03 '25 16:12 rekram1-node

Also if it is set to "deny" then the agent should see the tool as not having that subagent as an option

rekram1-node avatar Dec 03 '25 16:12 rekram1-node

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.

shuv1337 avatar Dec 03 '25 20:12 shuv1337

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:

orchestrator-coder
orchestrator-coder-high
orchestrator-quality-gate-gpt5
orchestrator-quality-gate-opus
orchestrator-commit
orchestrator-planner
orchestrator-searcher-fast
orchestrator-searcher
mcp-search
github

every time I type @. With no local directories on display, because everything is taken up by the subagents that aren't meant to be invoked by humans.

Sewer56 avatar Dec 04 '25 01:12 Sewer56

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

rekram1-node avatar Dec 06 '25 17:12 rekram1-node

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. And the real question is, where do I place this override?

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 subagents setting introduced here to hide it from the user, or something of the like.

You could even make subagents a global thing, but then you need local and global, in case a user wants it in some contexts but not in others. That's where the real ambiguity comes in. Imaginr trying to explain all that in docs too.

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.

Sewer56 avatar Dec 07 '25 02:12 Sewer56

Disclaimer: I designed this proposal but used Claude Opus 4.5 to help format and present it clearly.


@rekram1-node / @Sewer56

After thinking through the UX, I'd like to propose an alternative design that separates the two distinct concerns here:

  1. Human visibility: whether a subagent appears in menus/autocomplete
  2. LLM invocation control: which subagents a primary agent can spawn

These are independent problems and conflating them creates the semantic confusion discussed above.


Proposed Design

1. Human Visibility: visible property on subagents

Add a visible property (default true) that controls whether a subagent appears in the agent selection menu.

JSON config:

{
  "$schema": "https://opencode.ai/config.json",
  "agent": {
    "orchestrator-coder": {
      "description": "Internal coding subagent for orchestration loops",
      "mode": "subagent",
      "visible": false
    },
    "orchestrator-planner": {
      "description": "Internal planning subagent",
      "mode": "subagent",
      "visible": false
    },
    "code-reviewer": {
      "description": "Reviews code for best practices",
      "mode": "subagent",
      "visible": true
    }
  }
}

Markdown frontmatter:

---
description: Internal coding subagent for orchestration loops
mode: subagent
visible: false
---

You are a coding subagent. Focus on implementation tasks assigned by the orchestrator.

Rules:

  • visible: false hides the subagent from the agent menu.
  • Does NOT apply to mode: primary or mode: all agents (these are always visible and in Tab rotation)
  • Does NOT affect whether LLM can invoke the subagent (that's controlled by permissions)

2. LLM Invocation Control: permission.task

Use the existing permissions pattern to control which subagents a primary agent can spawn. This is consistent with how permission.bash works.

JSON config:

{
  "$schema": "https://opencode.ai/config.json",
  "agent": {
    "build": {
      "mode": "primary",
      "permission": {
        "task": {
          "*": "deny",
          "orchestrator-*": "allow",
          "code-reviewer": "allow"
        }
      }
    },
    "plan": {
      "mode": "primary",
      "permission": {
        "task": {
          "*": "deny"
        }
      }
    },
    "orchestrator": {
      "description": "Main orchestration agent",
      "mode": "primary",
      "permission": {
        "task": {
          "*": "deny",
          "orchestrator-coder": "allow",
          "orchestrator-planner": "allow",
          "orchestrator-quality-gate": "ask"
        }
      }
    }
  }
}

Markdown frontmatter:

---
description: Main orchestration agent
mode: primary
permission:
  task:
    "*": deny
    orchestrator-coder: allow
    orchestrator-planner: allow
    orchestrator-quality-gate: ask
---

You are an orchestrator. Delegate tasks to specialized subagents.

Permission values:

  • "allow": LLM can invoke this subagent freely
  • "ask": Prompt user for approval before spawning the subagent
  • "deny": LLM cannot invoke this subagent AND it's removed from the Task tool description in the system prompt (preventing confusion/hallucination)

Wildcard support:

  • "*": "deny" β€” Deny all by default (place first)
  • "orchestrator-*": "allow" β€” Allow all subagents starting with orchestrator-
  • Rules are evaluated sequentially; specific rules override earlier wildcards

3. Interaction with tools.task

For clarity on the hierarchy:

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] or explore) rather than the primary agent name
  • Pressing Tab returns to the primary agent
  • To select a subagent again, user goes to /agents menu or uses keybind

Agent menu:

  • Shows all primary agents
  • Shows all mode: all agents
  • Shows only subagents where visible: true (or not set, since default is true)

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 /agents menu or keybind
  • visible: false hides 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 subagents
  • visible: false hides from agent menu.
  • Users who know the name can still type @hidden-subagent manually

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, general but NOT any orchestrator-* subagents
  • Orchestrator agent can only invoke its own orchestrator-* subagents, with orchestrator-quality-gate requiring user approval
  • Human sees only code-reviewer in the agent menu (plus built-in explore and general)
  • Human does NOT see orchestrator-coder, orchestrator-planner, or orchestrator-quality-gate
  • code-reviewer does not see the task tool.

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.bash patterns
  • Follows standard config merging (local overrides global)
  • Addresses both issues #4764 and #4267

Happy to discuss further or adjust based on feedback.

malhashemi avatar Dec 09 '25 22:12 malhashemi

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.

Sewer56 avatar Dec 10 '25 01:12 Sewer56

@rekram1-node thoughts? I'm onboard with the proposed approach.

Sewer56 avatar Dec 11 '25 06:12 Sewer56

Hey @rekram1-node! I noticed @Sewer56 is already working on the proposed design, that's awesome! Could you share your thoughts while there's still work to be done? This feature would really make a big difference in the UX of subagents, so your input is really valued.

malhashemi avatar Dec 12 '25 17:12 malhashemi

@malhashemi You beat me to it; haha. Yeah, got first prototype of it here. I like to review on GitHub so I've pushed it before I've fully reviewed it, not ready yet, but basics seem to be working.

Sewer56 avatar Dec 12 '25 17:12 Sewer56

Okay just to recap I talked w/ dax about this and he said "@" should not hide denied subagents so if the user @s them they should still work, and that the permissions model is what we should use to hide/show etc.

Now the only hiccup here is that we are prolly going to change the name of the task tool to something more "agent-y" so we any change we do here related to it should prolly be experimental for now

rekram1-node avatar Dec 12 '25 17:12 rekram1-node

Idk if i like adding this config to hide from user, is that really a pain point for people?

rekram1-node avatar Dec 12 '25 17:12 rekram1-node

Idk if i like adding this config to hide from user, is that really a pain point for people?

A massive one for me honestly, imagine having a swarm of subagents not meant to be called by me (I have such in one of the projects) and all they do is clutter file tagging for no reason

malhashemi avatar Dec 12 '25 17:12 malhashemi

But is it something you are wanting to hide on an agent basis or is it something like hey I never wanna see this appear in my @ results.

rekram1-node avatar Dec 12 '25 17:12 rekram1-node

Both.

I'm mostly concerned about hiding from the system prompt personally.

image

Though, admittedly, I wouldn't say this isn't a bit of a nuisance, ahaha.

Sewer56 avatar Dec 12 '25 17:12 Sewer56

But is it something you are wanting to hide on an agent basis or is it something like hey I never wanna see this appear in my @ results.

Agent bases, thats why the agent permission and user tagging is of a different concerns. Plus the 'visible' property does not prevent it from being called, just simply hides it from cluttering file tagging. At least that how I imagine it should work. If a user really wants to he can still call the agent with the @ but then again this entire thing doesn't make much sense why is agents mixed with files? Agents belong in agents menu.

Probably too ingrained to change now

malhashemi avatar Dec 12 '25 17:12 malhashemi

In the current state of branch:

  • visible hides whether it shows up in the autocomplete.
  • If an agent is present in user's last message, e.g. @hidden-agent, it is permitted to run even if denied.
  • If denied by the task tool permissions, a subagent is excluded from system prompt; and not allowed to run (unless invoked by user).

There is a caveat. Some models (think Anthropic models with thinking) may be reluctant to try running a subagent that's not in system prompt- thinking it doesn't exist.

We may need to modify the hidden injected text; which currently says Use the above message and context to generate a prompt and call the task tool with subagent:

Edit: I added an extra message . Invoked by user; guaranteed to exist., if it's a subagent that's not in system prompt. Seems to work well with Sonnet so far.

Sewer56 avatar Dec 12 '25 18:12 Sewer56

Updated opening post accordingly.

Sewer56 avatar Dec 12 '25 18:12 Sewer56

Okay so dax shipped a refactor that added a new "hidden" field to agents so you can hide from @ completions in your configurations

rekram1-node avatar Dec 15 '25 20:12 rekram1-node

(Did it actually ship? I added hidden: true to agents on latest dev branch but they still appear) (Maybe it's in one of the branches like llm-centralization)

Edit: https://github.com/sst/opencode/pull/5462 Which means shipped in 1.0.154

Sewer56 avatar Dec 16 '25 10:12 Sewer56

image

But it still appears in .162 , not sure why; code appears fine at glance; it shouldn't show up.

In any case, the code only hides it from autocomplete- I need it removed from the system prompt too FWIW- otherwise the LLM winds up calling the agents when I don't want that.

Sewer56 avatar Dec 16 '25 10:12 Sewer56

Updated PR to latest dev. Now with visible -> hidden renamed to keep track with changes in dev- so this extends what dev has got in llm-centralization branch.

So now, this PR allows you to hide it from system prompt separately from autocomplete; while still allowing explicit invoke.

Sewer56 avatar Dec 16 '25 12:12 Sewer56

CC @rekram1-node

Sewer56 avatar Dec 16 '25 12:12 Sewer56