Limit what subagents can do

A subagent works on its parent's authority. Prismor already holds it to the parent's rules, so it can never be looser than the agent that launched it. This guide goes further and sets three limits: how many subagents an agent may launch, how long one may run, and what data it may be handed. Each step below was recorded in a real Claude Code session.

You'll need

  • A Prismor build with subagent limits, hooked into Claude Code (prismor setup), or an SDK agent guarded by a Prismor adapter. They ship in the release after 1.65.4. Until then, install from main: pipx install --force git+https://github.com/PrismorSec/prismor.
  • For step 5, an admin role in the console.

1. Cap how many subagents an agent launches

Add a rule to .prismor/policy.yaml:

version: "1.0"
rules:
  - id: one-subagent-per-session
    title: Launch at most one subagent per session
    severity: HIGH
    category: custom-authz
    event_types: [subagent_spawn]
    when: "subagent.number > 1"
    action: block
    mode: enforce

subagent.number says which of an agent's subagents a call belongs to. On a launch, it's the number the new subagent would get. One is a small cap, chosen so the demo hits it quickly. In practice you'd pick something like five.

Then ask Claude Code for two subagents at once:

The first subagent runs and reports back. The second launch is refused before the subagent starts. Claude Code tells you why and how to approve that one launch if you want it. Launches that run in parallel each reserve their own number, so starting several at once doesn't slip past the cap. A launch that some other rule refuses doesn't use up a number.

2. See why it was blocked

Open the dashboard (prismor dashboard) and click the session. The blocked launch shows the rule, and Condition that held shows the expression that decided it:

Dashboard session view: one subagent ran, the second Agent launch is blocked, with Condition that held: when: subagent.number > 1

3. Let a delegation expire

A long-running subagent keeps its parent's authority for as long as it runs. To put a time limit on it, use subagent.age, the seconds since the subagent's first call:

  - id: subagent-expiry
    title: A subagent may run for at most 15 minutes
    severity: HIGH
    category: custom-authz
    event_types: [shell, network, file_read, file_write]
    when: "subagent.age > 900"
    action: block
    mode: enforce

When the time is up, the subagent's calls are blocked and it has to report back. The parent can launch a fresh subagent, which starts again at zero. To give one kind of work more time, add its type: subagent.age > 900 and subagent.type != 'migration-runner'.

4. Screen what a subagent is handed

A subagent works from what its parent puts in its prompt. If that includes a customer's email address or an API key, the subagent can pass it on with tools the parent never used. The data boundary sets what may be handed over, data class by data class:

settings:
  data_boundary:
    enabled: true
    mode: enforce
    handoffs:
      secret: block
      email: block
      per_subagent:
        billing: {email: allow}

Now ask for a subagent and tell Claude Code to put a customer's email in its prompt:

The launch is blocked with pii-to-subagent. The message offers three ways forward: approve this one launch, allow email for that subagent type under per_subagent, or hand over a reference such as a ticket id instead of the address. The dashboard shows the finding with the value masked:

Dashboard: the blocked handoff, rule pii-to-subagent, Handoff gives subagent 'general-purpose' email, evidence d***@northwind-trading.co

Test values never count: [email protected] or a test card number passes through. Without a handoffs block, nothing is screened.

5. Ship the cap to every device from the console

In the console, open Policies, pick a policy, and under Detection rules click Edit, then New rule under Your own rules. A console rule needs a pattern, so match the launch tool by name and put the cap under Only when…:

  • Which events to check: subagent_spawn
  • Patterns: ^(Agent|Task)$, with Match patterns against set to the tool name
  • Only when…: subagent.number > 5

Type Agent into Try it against a sample to check that the pattern matches:

The console rule form: max-five-subagents, subagent_spawn selected, pattern ^(Agent|Task)$ against the tool name, Only when subagent.number > 5, and the sample Agent reporting that the rule would fire

Publish the policy and every enrolled device picks it up.

SDK agents

The same rules apply to agents built with the Python and JS adapters. A framework subagent gets its number when it first acts, so with a cap of five, every call from a sixth subagent is blocked while the first five keep working. Handoffs are screened where the adapter can see them: an OpenAI Agents SDK agent used as a tool, a Google ADK AgentTool, a BeeAI HandoffTool, and Vercel AI SDK tools you list in the adapter's handoffs option:

const tools = prismorTools({ ask_researcher }, {
  agentName: 'support-bot',
  handoffs: { ask_researcher: 'researcher' },
});

Troubleshooting

SymptomFix
Every launch gets throughCheck that the rule has mode: enforce, or that the device is in enforce. Under observe, the dashboard shows what would have been blocked.
A console rule won't save without a patternMatch the tool name: ^(Agent|Task)$ for Claude Code launches. For SDK subagents, use . against the tool name to match every call.
A handoff with an email isn't blockedhandoffs needs data_boundary.enabled: true, and the address must be real. example.com and other test domains never count.
Codex subagents aren't blocked at launchCodex has no launch event. Its subagents get a number when they first act, so the cap blocks their calls instead.

Next

  • Limiting subagents: delegation records, the full list of handoff surfaces, and the limits of each check
  • Subagents: how a subagent inherits its parent's pause, mode, denied tools and IAM profile
  • Attribute conditions: subagent.depth, subagent.chain and every other attribute a rule can read
  • All guides