# Security Considerations for Agents with Subagent Delegation (callable_agents)

> Secure your agents with subagent delegation limits, strict allow-listing, and JSON Schema validation. Learn how callable agents prevent malicious handoff chains in Claude Financial Services.

- Repository: [Anthropic/financial-services](https://github.com/anthropics/financial-services)
- Tags: best-practices
- Published: 2026-05-07

---

**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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/agent.yaml) manifests to determine the exact execution graph without parsing runtime dynamics.

## Runtime Validation Controls

The reference orchestration script [`scripts/orchestrate.py`](https://github.com/anthropics/financial-services/blob/main/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.

```python
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:

```python
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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/pitch-agent/agent.yaml) demonstrates how to declare authorized subagents:

```yaml
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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/scripts/orchestrate.py) implementation demonstrates secure payload handling:

```python
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:

```json
{
  "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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/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`](https://github.com/anthropics/financial-services/blob/main/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.