Skip to content

Conversation

@Sewer56
Copy link

@Sewer56 Sewer56 commented Nov 26, 2025

Summary

Add subagents config option to control which subagents an agent can invoke via the Task tool.

Changes

  • Add subagents field to agent schema and configuration
  • Filter subagent autocomplete suggestions in TUI based on agent's allowed subagents
  • Regenerate Task tool description to only show available subagents
  • Validate subagent access at runtime, blocking disallowed invocations
    • This is not strictly needed. But just in case.
  • Support wildcard patterns with longer patterns taking precedence
  • Documentation included.

This uses same rules as tools do for filtering.

Modified Files

  • packages/opencode/src/agent/agent.ts - Add subagents field to schema and built-in agents
  • packages/opencode/src/config/config.ts - Add subagents config option
  • packages/opencode/src/session/prompt.ts - Filter Task tool description
  • packages/opencode/src/tool/task.ts - Add filtering logic and runtime validation
  • packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx - Filter autocomplete
  • packages/opencode/test/subagents-filter.test.ts - Tests for filtering logic
  • packages/web/src/content/docs/agents.mdx - Documentation for subagents config

- 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
@malhashemi
Copy link
Contributor

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

@Sewer56
Copy link
Author

Sewer56 commented Nov 29, 2025

@rekram1-node

@rekram1-node
Copy link
Collaborator

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

@malhashemi
Copy link
Contributor

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?

@Sewer56
Copy link
Author

Sewer56 commented Dec 3, 2025

@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)

@malhashemi
Copy link
Contributor

malhashemi commented Dec 3, 2025

@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

@rekram1-node
Copy link
Collaborator

rekram1-node commented Dec 3, 2025

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
Copy link
Collaborator

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

@kcrommett
Copy link
Contributor

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.

@Sewer56
Copy link
Author

Sewer56 commented Dec 4, 2025

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.

@rekram1-node
Copy link
Collaborator

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

@Sewer56
Copy link
Author

Sewer56 commented Dec 7, 2025

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.

@malhashemi
Copy link
Contributor

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:

Happy to discuss further or adjust based on feedback.

@Sewer56
Copy link
Author

Sewer56 commented Dec 10, 2025

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.

kcrommett added a commit to Latitudes-Dev/shuvcode that referenced this pull request Dec 10, 2025
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.
kcrommett added a commit to Latitudes-Dev/shuvcode that referenced this pull request Dec 10, 2025
kcrommett added a commit to Latitudes-Dev/shuvcode that referenced this pull request Dec 10, 2025
* 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
@Sewer56
Copy link
Author

Sewer56 commented Dec 11, 2025

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

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

Labels

None yet

Projects

None yet

5 participants