OpenHands / OpenHands/software-agent-sdk

[Feature]: Per-conversation sub-agent deny-list (disabled_agents), enforced at spawn time

Open
#4,556 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement ready-for-dev
Dominant language
Python
Stars
1.1k
Forks
542
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Is there an existing feature request for this?
  • I have searched existing issues and feature requests, and this is not a duplicate.
Problem or Use Case

The sub-agent side of the SDK has no per-conversation deny-list. Skills have one (AgentContext.disabled_skills); sub-agents have nothing equivalent.

Concrete case: the GUI Sub-Agents page (OpenHands/OpenHands#16662) lets a user toggle agents off and persists that preference, but best I can tell nothing enforces it yet; the wiring is parked as probably-in-the-SDK. Sent to the server today, that key is silently dropped during validation: the agent gets built without it, no error. Agent-server embedders wanting per-user or per-conversation agent restrictions hit the same wall from the other side: the sub-agent registry is global to the process, so an agent cannot be unregistered for one conversation without disappearing for all of them.

Desired Behavior

Mirror the disabled_skills model:

  • A new optional field, AgentContext.disabled_agents: list[str]. A deny-list of sub-agent names. Default [] disables nothing. A listed name that is not registered is a harmless no-op.
  • Enforcement at spawn time. When the task tool (TaskManager._create_task / _resume_task) or the delegate tool's spawn path is asked for a disabled type, it refuses with an error naming the agent. The refusal comes back as an ordinary tool error, so the calling LLM can pick another type or do the work itself.

The field rides the existing AgentContext plumbing (StartConversationRequest, the settings schema), so a server or frontend sets it per conversation with no new transport.

Acceptance Criteria
  • AgentContext(disabled_agents=[...]) round-trips through serialization (it must survive StartConversationRequest)
  • Starting a task with a disabled sub-agent type fails with an error naming the type
  • Resuming a task with a since-disabled type fails the same way
  • A type not on the list spawns normally
  • A delegate spawn naming a disabled type returns an error observation and creates nothing
Alternatives Considered

Registration-time filtering. Ruled out: the registry is process-global and shared across conversations on agent-server, and built-ins register at tool-router import. Filtering there disables the agent for every conversation in the process, not the one that asked.

Prompt-side hiding (filtering the advertised agent list in the task tool description). Complementary, not a substitute: it enforces nothing on its own, and the description is built without conversation context today. Out of scope here; a reasonable follow-up.

Priority / Severity

Medium - Would improve experience

Estimated Scope

Small - API addition, config option, or minor change

Feature Area
  • Agent API / Core functionality
  • Tools / Tool system
  • Skills / Plugins
  • Agent Server
  • Workspace management
  • Configuration / Settings
  • Examples / Templates
  • Documentation
  • Testing / Development tools
  • Performance / Optimization
  • Integrations (GitHub, APIs, etc.)
  • Other
Technical Implementation Ideas (Optional)

The field declaration mirrors disabled_skills in openhands-sdk/openhands/sdk/context/agent_context.py. The guard reads the parent conversation's agent_context.disabled_agents in TaskManager before the factory lookup and raises ValueError naming the type; the task executor already converts exceptions into error observations, so no transport change is needed. The delegate executor gets the same check before factory resolution, returning an error DelegateObservation, matching how it reports its other spawn-validation failures.

Roughly 40 lines of implementation plus tests. A working version sits on a branch with tests covering each acceptance criterion above; happy to open the PR once this is triaged.

Additional Context

Refs: #3911 (sub-agent discovery API) and OpenHands/OpenHands#16662 (the GUI page whose toggle this makes enforceable).

The GUI side still needs one line passing the persisted preference into the agent_context payload (agent-server-adapter.ts); that can ride with #16662 or a small sibling PR in OpenHands/OpenHands.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with openhands-sdk/openhands/sdk/context/agent_context.py and trace AgentContext through StartConversationRequest and the settings schema. Then inspect TaskManager._create_task, _resume_task, and the delegate spawn path; run the relevant SDK tests and verify serialization plus disabled and allowed spawn behavior match every acceptance criterion.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design, tooling
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.