fix(agents): filter assembly descriptor subagent policy by allowed_subagents (#5205) (#5262)

* fix(agents): filter assembly descriptor subagent policy by allowed_subagents (#5205)

* test(agents): ensure non-vacuous subagent catalog in assembly descriptor test

---------

Co-authored-by: 1747687484-collab <229902011+1747687484-collab@users.noreply.github.com>
This commit is contained in:
kbsama 2026-09-10 14:51:42 +08:00 committed by GitHub
parent 37b03a3811
commit 3e1349576e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 1 deletions

View File

@ -128,6 +128,7 @@ def _subagent_release_policy(
enabled: bool,
max_concurrent: int,
max_total: int,
allowed_subagents: list[str] | None = None,
) -> dict[str, object]:
"""Delegation limits as the run will actually enforce them.
@ -147,7 +148,7 @@ def _subagent_release_policy(
from deerflow.subagents import get_available_subagent_names, get_subagent_config
type_allowlist = sorted(set(get_available_subagent_names(app_config=app_config)))
type_allowlist = sorted(set(get_available_subagent_names(app_config=app_config, allowed_subagents=allowed_subagents)))
runtime_limits: dict[str, object] = {}
for name in type_allowlist:
subagent_config = get_subagent_config(name, app_config=app_config)
@ -1092,6 +1093,7 @@ def _assemble_lead_agent(config: RunnableConfig, *, app_config: AppConfig) -> Le
enabled=subagent_enabled,
max_concurrent=max_concurrent_subagents,
max_total=max_total_subagents,
allowed_subagents=allowed_subagents,
),
"deferred_tools": {
"enabled": resolved_app_config.tool_search.enabled,
@ -1211,6 +1213,7 @@ def _assemble_lead_agent(config: RunnableConfig, *, app_config: AppConfig) -> Le
enabled=subagent_enabled,
max_concurrent=max_concurrent_subagents,
max_total=max_total_subagents,
allowed_subagents=allowed_subagents,
),
"deferred_tools": {
"enabled": resolved_app_config.tool_search.enabled,

View File

@ -126,6 +126,7 @@ class TestLeadAgentAssembly:
from deerflow.config.app_config import AppConfig
from deerflow.config.model_config import ModelConfig
from deerflow.config.sandbox_config import SandboxConfig
from deerflow.config.subagents_config import CustomSubagentConfig, SubagentsAppConfig
app_config = AppConfig(
models=[
@ -139,6 +140,7 @@ class TestLeadAgentAssembly:
supports_vision=False,
)
],
subagents=SubagentsAppConfig(custom_agents={"researcher": CustomSubagentConfig(description="research", system_prompt="research")}),
sandbox=SandboxConfig(use="deerflow.sandbox.local:LocalSandboxProvider"),
)
monkeypatch.setattr(lead_agent_module, "get_app_config", lambda: app_config)
@ -216,6 +218,34 @@ class TestLeadAgentAssembly:
assert assembly.graph["system_prompt"] == "allowed_subagents=['general-purpose']"
assert assembly.descriptor.base_prompt_hash == canonical_hash(assembly.graph["system_prompt"])
def test_descriptor_subagent_policy_respects_custom_agent_allowed_subagents(self, monkeypatch):
"""Fixes #5205: custom agent assembly descriptor must restrict its
subagents policy allowlist and runtime limits to allowed_subagents."""
from deerflow.agents.lead_agent import agent as lead_agent_module
from deerflow.agents.lead_agent.agent import assemble_lead_agent
from deerflow.config.agents_config import AgentConfig
from deerflow.extensions import bind_agent_build_extensions
self._isolate_from_the_ambient_config(monkeypatch)
agent_config = AgentConfig(name="custom", allowed_subagents=["general-purpose"])
monkeypatch.setattr(lead_agent_module, "load_agent_config", lambda name, *, user_id=None: agent_config)
with bind_agent_build_extensions(self._extensions_with_an_agent_assembly_observer()):
assembly = assemble_lead_agent(
{
"configurable": {
"thread_id": "t-scoped-subagents",
"agent_name": "custom",
"subagent_enabled": True,
}
}
)
subagent_policy = assembly.descriptor.effective_policies["subagents"]
assert subagent_policy["enabled"] is True
assert subagent_policy["type_allowlist"] == ["general-purpose"]
assert list(subagent_policy["runtime_limits"].keys()) == ["general-purpose"]
def test_observers_receive_the_descriptor(self, monkeypatch):
from deerflow.agents.lead_agent.agent import assemble_lead_agent
from deerflow.extensions import bind_agent_build_extensions