Security Considerations for Agents with Subagent Delegation (callable_agents)

Claude Financial Services agents limit subagent delegation to a single level with strict allow-listing and JSON Schema validation to prevent malicious handoff chains.

The anthropics/financial-services repository implements a managed-agent framework where parent agents delegate tasks to specialized subagents via the callable_agents field. This architecture enables powerful multi-agent workflows while enforcing defense-in-depth security controls to prevent arbitrary code execution or unauthorized access. Understanding these security considerations for agents with subagent delegation is critical when deploying autonomous systems that handle sensitive financial data.

Single-Level Delegation Architecture

The framework enforces a strict single-level delegation model that prevents recursive agent spawning. According to managed-agent-cookbooks/README.md (lines 34-35), only the orchestrating parent agent may declare callable_agents, while leaf subagents are explicitly prohibited from declaring their own delegation chains.

This containment strategy serves three security purposes:

  • Prevention of arbitrary execution chains – Untrusted user input (such as uploaded documents) cannot trick the parent into spawning a chain of subagents that eventually reaches unauthorized write-capable services.
  • Controlled blast radius – The orchestrator maintains complete visibility over which leaf agents execute, preventing a compromised subagent from silently delegating to additional malicious agents.
  • Simplified audit trails – Security teams can statically analyze agent.yaml manifests to determine the exact execution graph without parsing runtime dynamics.

Runtime Validation Controls

The reference orchestration script scripts/orchestrate.py implements dual validation layers for every handoff request extracted from model output.

Allow-List Enforcement

Before routing any delegation, the orchestrator validates the target against a hardcoded ALLOWED_TARGETS list (lines 23-27). This guarantees that even if an attacker manipulates the model into generating a malicious handoff_request, the request cannot reach agents outside the predefined set.

ALLOWED_TARGETS = {
    "researcher",
    "modeler", 
    "deck-writer",
    # Additional authorized leaf agents

}

If target_agent is not present in this set, the orchestrator discards the request entirely.

JSON Schema Validation

After allow-list verification, the payload undergoes strict schema validation against HANDOFF_PAYLOAD_SCHEMA (lines 29-38). This constraint prevents injection attacks through oversized payloads, unexpected data types, or malicious character sequences:

HANDOFF_PAYLOAD_SCHEMA = {
    "type": "object",
    "properties": {
        "event": {"type": "string", "maxLength": 1000},
        "context_ref": {"type": "string", "pattern": r"^[a-zA-Z0-9_/\-]+$"}
    },
    "required": ["event"],
    "additionalProperties": False
}

The extract_handoff function uses regex HANDOFF_RE to isolate JSON blobs from model output, then applies both validation checks before returning the structured handoff object.

Configuration-Time Safeguards

Security validation begins before runtime through manifest verification and capability separation.

Manifest Reference Verification

The linting tool scripts/check.py (lines 104-108) validates that every callable_agents.manifest path points to an existing file. This catches misconfigurations early, preventing runtime failures where a parent agent attempts to delegate to a non-existent subagent.

Capability Separation

The architecture strictly separates read and write capabilities across the agent hierarchy. Only leaf subagents declared in callable_agents may possess Write capabilities, while parent agents and intermediate workers remain read-only. For example, in pitch-agent/agent.yaml, the deck-writer subagent holds write access to generate output files, whereas the researcher and modeler subagents only consume and transform data.

Implementation Examples

Defining a Parent Agent with callable_agents

The following manifest from pitch-agent/agent.yaml demonstrates how to declare authorized subagents:

name: pitch-agent
model: claude-opus-4-7
system:
  file: ../../plugins/agent-plugins/pitch-agent/agents/pitch-agent.md
  append: "You are running headless. Produce files in ./out/; do not assume an open Office document."
skills:
  - { from_plugin: ../../plugins/agent-plugins/pitch-agent }
callable_agents:
  - { manifest: ./subagents/researcher.yaml }
  - { manifest: ./subagents/modeler.yaml }
  - { manifest: ./subagents/deck-writer.yaml }

The scripts/check.py utility resolves these relative paths during the CI/CD pipeline to ensure all referenced manifests exist before deployment.

Orchestrator Handoff Extraction

The scripts/orchestrate.py implementation demonstrates secure payload handling:

HANDOFF_RE = re.compile(r'\{"type":\s*"handoff_request".*?\}', re.DOTALL)

def extract_handoff(text: str) -> dict | None:
    m = HANDOFF_RE.search(text)
    if not m:
        return None
    obj = json.loads(m.group(0))
    target = obj.get("target_agent")
    payload = obj.get("payload")
    if target not in ALLOWED_TARGETS:
        return None
    jsonschema.validate(instance=payload, schema=HANDOFF_PAYLOAD_SCHEMA)
    return {"target_agent": target, "payload": payload}

This function returns None for any handoff failing security checks, effectively sandboxing the delegation process.

Sample Handoff Request

When the parent agent generates a delegation request, it produces JSON matching the strict schema:

{
  "type": "handoff_request",
  "target_agent": "deck-writer",
  "payload": {
    "event": "Produce final pitch deck for Acme Corp",
    "context_ref": "session/12345"
  }
}

The orchestrator only forwards this request after verifying that deck-writer appears in ALLOWED_TARGETS and that the payload conforms to HANDOFF_PAYLOAD_SCHEMA.

Summary

  • Single-level delegation prevents recursive agent chains that could amplify the impact of prompt injection attacks.
  • Allow-list enforcement in scripts/orchestrate.py blocks delegation attempts to unauthorized agents.
  • JSON Schema validation filters malicious payloads through HANDOFF_PAYLOAD_SCHEMA constraints.
  • Configuration validation via scripts/check.py ensures manifest references resolve to valid files before runtime.
  • Capability separation restricts write access to leaf subagents only, minimizing the attack surface for data modification.

Frequently Asked Questions

Can subagents in the financial-services framework delegate to other subagents?

No. The architecture explicitly restricts callable_agents to a single level of indirection. Leaf subagents listed in a parent manifest cannot themselves declare callable_agents, preventing arbitrary delegation chains that could bypass security controls (as documented in managed-agent-cookbooks/README.md).

How does the orchestrator prevent malicious handoff requests?

The orchestrator implements defense-in-depth through ALLOWED_TARGETS (lines 23-27) and HANDOFF_PAYLOAD_SCHEMA (lines 29-38) in scripts/orchestrate.py. Every extracted handoff undergoes target verification against the allow-list and structural validation against the JSON Schema; failures result in immediate request discarding.

What happens if a manifest references a non-existent subagent?

The scripts/check.py linting tool validates all callable_agents.manifest paths during the build process (lines 104-108). It fails the CI pipeline if any referenced subagent YAML file is missing, preventing runtime delegation errors.

Which subagents have permission to modify files or external systems?

Only leaf subagents explicitly granted Write capabilities in their individual manifests (such as deck-writer in the Pitch Agent example) can modify external resources. Parent orchestrators and intermediate subagents operate in read-only mode, ensuring data modification requires explicit authorization through the allow-list mechanism.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →