fix(delegate): strip cronjob toolset from delegated children (#43466)

_strip_blocked_tools used a hardcoded set missing 'cronjob'. Children
on gateway platforms could inherit the cronjob toolset, scheduling
persistent jobs that outlive the delegation despite DELEGATE_BLOCKED_TOOLS.

Fix: derive the strip set from DELEGATE_BLOCKED_TOOLS at runtime so the
two lists can never drift. Add 'cronjob' to DELEGATE_BLOCKED_TOOLS for
documentation consistency. Two regression tests lock the invariant.

Salvaged from #43687 by @riyas22. Adapted test to current main (no
'messaging' toolset exists -- send_message is intentionally not
registered as an agent tool).

Closes #43466
This commit is contained in:
Riyasudeen Farook 2026-06-25 01:37:25 +05:30 committed by kshitijk4poor
parent ed1fdb5b61
commit 1e4df599ec
2 changed files with 46 additions and 5 deletions

View file

@ -49,6 +49,7 @@ DELEGATE_BLOCKED_TOOLS = frozenset(
"memory", # no writes to shared MEMORY.md
"send_message", # no cross-platform side effects
"execute_code", # children should reason step-by-step, not write scripts
"cronjob", # no scheduling more work in the parent's name
]
)
@ -766,12 +767,21 @@ def _resolve_workspace_hint(parent_agent) -> Optional[str]:
def _strip_blocked_tools(toolsets: List[str]) -> List[str]:
"""Remove toolsets that contain only blocked tools."""
"""Remove toolsets that contain only blocked tools.
The strip set is derived from DELEGATE_BLOCKED_TOOLS plus the explicit
composite/scenario toolsets (delegation, code_execution) that have no
one-to-one tool. This keeps the blocklist and the strip set in lockstep
so new blocked tools can't silently leak through as toolset names.
"""
# Composite toolsets that should never pass through to children, even
# though their individual tools aren't all in DELEGATE_BLOCKED_TOOLS.
_COMPOSITE_BLOCKED_TOOLSETS = frozenset({"delegation", "code_execution"})
blocked_toolset_names = {
"delegation",
"clarify",
"memory",
"code_execution",
name
for name, defn in TOOLSETS.items()
if name in _COMPOSITE_BLOCKED_TOOLSETS
or all(t in DELEGATE_BLOCKED_TOOLS for t in defn.get("tools", []))
}
return [t for t in toolsets if t not in blocked_toolset_names]