新着: エージェントの承認、レッドチーミング、モデルスキャン。 詳しく見る
MITRITY
Docs / はじめに / Demo: Multi-Agent Governance

Demo: Multi-Agent Governance

This demo shows how MITRITY governs real agent-to-agent delegation. A self-contained Docker compose stack puts three Claude-powered agents to work: an orchestrator and two specialist workers. Unlike in the single-agent demos, every delegation hop, privilege boundary and threat-intel match comes from real multi-agent activity, with no seed data and no synthetic UUIDs.

This demo is phase 7 of the MITRITY governance demo series. The single-agent demos, Mitrity Gateway and Mitrity MCP Sidecar, run phases 1–6 and 8, and the gateway demo also runs phase 9. They have no phase 7, because this demo covers delegation chains and threat intelligence as phase 7.

The orchestrator is a Claude Agent SDK agent, connected to MITRITY with the mitrity adapter. MITRITY governs both of its entrances:

  • MCP tools (delegate__delegate_to, fs__read_file, fs__list_directory): the SDK starts the orchestrator's gateway as its MCP server, and the model reaches these tools through it. The gateway evaluates every tools/call against your policies before the upstream tool runs. An allowed delegation is a governed HTTP POST to the worker, and the worker's own gateway judges the next hop on the same chain.
  • The SDK's own built-in tools (Bash, Write, Edit): these never produce an MCP call. Instead, the adapter installs a PreToolUse hook that admits each one through the gateway's loopback admission API before the SDK runs it (surface=agent_hook). No scenario here uses them. They are hooked so that the orchestrator's coverage is complete and attested. If the gateway cannot be reached, the call is denied.

The demo takes about 5 minutes to run. You need Docker Desktop, an Anthropic API key and a MITRITY tenant on the Pro or Enterprise plan.

First time with MITRITY? Start with the single-agent Mitrity Gateway or Mitrity MCP Sidecar demos. The multi-agent demo assumes you know your way around the dashboard and want to see delegation chains in action.

Prerequisites

  • Docker Desktop (or any Docker runtime with compose v2)
  • A MITRITY tenant on Pro or Enterprise. Starter doesn't include delegation chains or threat intelligence: the demo boots on Starter, but those dashboard pages stay empty.
  • An Anthropic API key for the three Claude agents

Step 1: Provision three agents in the dashboard

Go to Agents → Register Agent and create the three agents below. The registration form also needs each agent's Intent Policy, so first create the three policies of Step 2 in /app/policies, then select each agent's own policy when you register it. For each agent, copy the Agent Key (ak_...) that the dialog shows, and the Agent ID from the agent's detail page. The key cannot be retrieved later.

Agent nameMission scopeTools it should hold
orchestrator"Coordinate customer-order workflow"delegate__delegate_to + read-only fs__read_file, fs__list_directory (no data writes)
data-worker"Query and modify customer order data"data__query_database, data__fetch_orders, data__create_order, delegate__delegate_to
notification-worker"Notify customers of order status changes"notify__send_notification, notify__email_customer, delegate__delegate_to

The asymmetry is intentional. The orchestrator has narrow read-only access, and the workers can write. That difference is what makes the privilege-escalation scenario meaningful, so don't grant the orchestrator the worker tools.

Tool names work like this:

  • Each container's gateway serves its upstream's tools as <namespace>__<tool>: the upstream's namespace and the tool name, joined by a double underscore (delegate__delegate_to, data__create_order, notify__email_customer).
  • A policy rule targets one of these tools as mcp:<namespace>__<tool>, for example mcp:data__create_order, and the SDK's built-in tools as builtin:<tool>.
  • A pattern without a surface prefix matches nothing, and the control plane rejects it when you create or update a policy. The full grammar is in Writing policies.

Created rules for an earlier version of this demo? Tools used to be served as delegate:delegate_to, data:create_order and notify:email_customer. Existing rules were rewritten to the mcp:<namespace>__<tool> form, and any rule you add uses that form too.

MITRITY's governance has two complementary layers, and the demo exercises both:

  • Capabilities: the capability surface, that is, which (tool, operation) pairs each agent can attempt. Capabilities are set per agent, and they filter attempts before the decision.
  • Policy rules: the runtime decision (allow, deny, alert or hold) for attempts within that surface. Policy rules are set per policy.

For the S2 privilege-escalation scenario to fire as described below, you need both layers:

  • Step 2 authors the policy rules;
  • Step 3 authors the differentiated Capabilities that the delegation engine compares when it computes the privilege diff.

Without Step 3, S2's delegation is still governed by the policy rules, but no escalation is detected.

Step 2: Author policy rules

  1. Go to /app/policies and create one policy for each agent, with the rules below.
  2. Attach each policy to its agent. A policy you selected when you registered the agent in Step 1 is already attached.

The rules target the served tool names.

orchestrator-policy, attached to orchestrator:

  • Rule 1: tool_pattern = "mcp:fs__*", operations = ["fs__read_file", "fs__list_directory"], action = allow. A rule's operations are the served names, as the edge sees them, so ["read"] would match neither tool.
  • Rule 2: tool_pattern = "mcp:delegate__delegate_to", action = allow
  • Default action: deny

data-worker-policy, attached to data-worker:

  • Rule 1: tool_pattern = "mcp:data__*", action = allow. It covers data__query_database, data__fetch_orders and data__create_order.
  • Rule 2: tool_pattern = "mcp:delegate__delegate_to", action = allow
  • Default action: deny
  • Threat Intelligence: Enforce. A new policy has it Off, and while it is off, the control plane sends the data-worker no threat indicators, so S5 cannot match.

notification-worker-policy, attached to notification-worker:

  • Rule 1: tool_pattern = "mcp:notify__*", action = allow. It covers notify__send_notification and notify__email_customer.
  • Rule 2: tool_pattern = "mcp:delegate__delegate_to", action = allow
  • Default action: deny

If you want one rule per tool, explicit names work in place of the globs (mcp:data__create_order instead of mcp:data__*). The orchestrator's gateway serves no data__* or notify__* tool, so the orchestrator cannot create an order directly under any policy. S2 shows what happens when it tries to get that work done through a worker instead.

If you skip the explicit policy rules, the demo still runs, but you lose the runtime allow/deny gating on each agent's own tool calls.

Step 3: Author Capabilities (drives S2)

S2's privilege-escalation check fires only when the proposed delegate holds (tool, operation) pairs that the calling agent does not. This is a Capability comparison, not a policy-rule comparison. So the orchestrator and the data-worker need differentiated Capabilities, or the diff is empty.

MCP Delegate ships as a built-in tool with the action pattern mcp:delegate__*. You find it on the Built-in tab of /app/tools, and there is nothing to create. The demo's filesystem, data and notification MCP upstreams are specific to the demo, so create them as custom tools: in /app/tools, open the Custom tab and select Add tool.

NameCategoryAction type patternOperations
MCP Filesystemstoragemcp:fs__*read_file, list_directory
MCP Databasedatabasemcp:data__*query_database, fetch_orders, create_order
MCP Notifymessagingmcp:notify__*send_notification, email_customer

The operations are the tools' own names, as each upstream serves them. On a definition whose pattern is mcp:<namespace>__*, the control plane adds the namespace for the edge (read_file becomes fs__read_file), and an operation that names no served tool, such as read, allows no call. A policy rule's operations are written differently: as the served names, such as fs__read_file in Step 2, because the control plane sends them to the edge as they are.

Then grant Capabilities to each agent. On each agent's detail page, open Capabilities and select Add capability:

AgentCapabilities
orchestratorMCP Filesystem (read_file, list_directory), MCP Delegate (delegate_to)
data-workerMCP Database (query_database, fetch_orders, create_order), MCP Delegate (delegate_to)
notification-workerMCP Notify (send_notification, email_customer), MCP Delegate (delegate_to)

The gap is intentional: the data-worker holds MCP Database, and the orchestrator does not. In S2, the orchestrator delegates an order creation to the data-worker. The delegation engine computes the diff and surfaces MCP Database as an escalated resource. The chain detail's Escalated resources card shows this data.

If you skip Step 3, the demo still runs, because capabilities default to permissive fall-through. But S2's privilege_escalation block won't fire (the diff returns empty), and the chain detail's Escalated resources card stays empty.

Step 4: Confirm threat intelligence is enabled

S5 needs the built-in indicator catalog, and a block for critical matches. If your tenant was created after May 25 2026, the mitrity_curated feed is enabled by default. To check, and to set the block:

  1. Open Security → Threat Intel and select the Settings tab.
  2. Under Subscribed Feeds, check that MITRITY Curated (mitrity_curated) is ticked. If it isn't, tick it.
  3. Under Per-Severity Actions, set Critical to Block. The default action is Alert, so without this step a critical match is alerted, not blocked.
  4. Select Save Settings.

Step 5: Clone and configure

Clone the demo repository and create your .env file:

git clone git@github.com:mitrity-io/iag-demo-multi-agent.git
cd iag-demo-multi-agent
cp .env.example .env

Then edit .env with your six MITRITY agent values (one ID and one key per agent) and your Anthropic key:

ANTHROPIC_API_KEY=sk-ant-...
MITRITY_CONTROL_PLANE_URL=https://api.mitrity.com

MITRITY_AGENT_ID_ORCHESTRATOR=<uuid>
MITRITY_AGENT_KEY_ORCHESTRATOR=ak_...

MITRITY_AGENT_ID_DATA=<uuid>
MITRITY_AGENT_KEY_DATA=ak_...

MITRITY_AGENT_ID_NOTIFY=<uuid>
MITRITY_AGENT_KEY_NOTIFY=ak_...

Step 6: Run the demo

docker compose up --build

Three containers boot: orchestrator, data-worker and notification-worker. Each has its own Mitrity Gateway, its own agent identity and its own role-scoped tool surface.

The first run can take a few minutes, because the images build. The orchestrator image installs the adapter from PyPI, pinned to mitrity[claude-agent-sdk]==0.2.0. Another release is one build argument away: docker compose build --build-arg MITRITY_PYTHON_SPEC='mitrity[claude-agent-sdk]==X.Y.Z'.

The orchestrator waits for both workers' health checks (their gateway handshakes) before the first scenario. Then it cycles through six scenarios over ~5 minutes.

While it runs, watch the dashboard at mitrity.com/app, especially /delegation-chains, /threat-intel and /audit.

What the demo does

Six scenarios run in order. Each takes ~30 seconds, with real Claude-driven tool use across several agents. Every scenario is one delegate__delegate_to call from the orchestrator, and the workers delegate further where the task tells them to.

  • S1, Clean delegation: the orchestrator delegates an order lookup to the data-worker. A two-hop chain with decision allowed appears in /delegation-chains. It shows the request flowing orchestrator → data-worker → response.
  • S2, Privilege escalation: the orchestrator delegates an order creation that it cannot perform itself. Its gateway serves no data tool, and it holds no MCP Database capability. The delegation engine compares the two agents' Capabilities from Step 3. The data-worker holds MCP Database and the orchestrator does not, so the hop is blocked with blocked_reason: "privilege_escalation". Denied hops are recorded, not dropped. The chain appears on /delegation-chains under the Blocked tab, and the chain detail page shows the Escalated resources card, which names MCP Database with the operations the orchestrator does not hold: create_order, fetch_orders and query_database.
  • S3, End-to-end pipeline: a three-hop chain, orchestrator → data-worker (fetch order) → notification-worker (email customer). The full pipeline runs as governed delegation. The receiving worker's gateway judges each continuation hop on the same chain, and the engine clears each one. The chain accumulates real hops across three event streams, with action_taken: "allowed".
  • S4, Deep chain: the orchestrator asks the data-worker to cascade an audit step through further delegations, past the agent's max_chain_depth (default 5). The depth check runs on the full persisted chain, so the hop that would exceed the limit is blocked at hop N+1 with blocked_reason: "depth_exceeded". You see it on /delegation-chains under the Blocked tab.
  • S5, Threat-intel match: the delegated task asks the data-worker to read /etc/passwd, which matches MITRITY's built-in system-file-read indicator. With Threat Intelligence on Enforce in the worker's policy and critical matches set to Block (Steps 2 and 4), the action is blocked. The match lands on /threat-intel, and the block shows on /audit with the Threat Intel badge.
  • S6, Circular delegation: the data-worker tries to delegate back to the orchestrator that originally invoked it. On every evaluation, the engine rebuilds the chain state from the recorded hops; the calling agent does not carry it in the arguments. So the engine sees the orchestrator already in the chain, however the hop is shaped, and catches the cycle even when it is transitive across intermediate hops. The hop is recorded as blocked with blocked_reason: "circular_delegation". You see it on /delegation-chains under the Blocked tab.

The orchestrator's terminal narrates each call as the SDK reports it: OK, BLOCKED or HELD with the gateway's reason, plus the worker's answer for an allowed delegation. The workers' own hops (S3, S4, S6) are judged by the worker's gateway, so they show up on the dashboard, not in the orchestrator's terminal.

What to look for

Open these dashboard pages while the demo runs:

  • /delegation-chains: a real-time view of multi-hop delegation. Each scenario produces at least one chain. Open one to see hop-by-hop timing, blocking decisions and the Escalated resources card (when a hop carries the privilege_escalation signal). The Blocked tab fills with the denied hops from S2, S4 and S6, because every attempted-but-denied hop is recorded.
  • /threat-intel: indicator matches from S5, plus any other matches the built-in indicator catalog catches on agent activity. Both the Matches tab (per event) and the Landscape tab (trending indicators) fill up.
  • /audit: every tool call across all three agents, with its governance decision, latency and policy match. Filter by agent to see each worker's stream separately.
  • Agents: the orchestrator's Execution coverage card. It shows what the orchestrator's runtime attested at startup: which built-in tools are hooked, which are not, and which other MCP servers are configured.

Architecture

Docker Compose
├── orchestrator container
│   ├── Python runner (Claude Agent SDK) — scenarios S1–S6
│   │   ├── built-in tools: Bash, Write, Edit ── PreToolUse hook (mitrity adapter)
│   │   │                                          └── admission API (unix:/run/mitrity/admission.sock)
│   │   └── MCP server "mitrity" = Mitrity Gateway (stdio; own agent identity, own profile)
│   │       ├── upstream "filesystem" (namespace fs): fs__read_file, fs__list_directory
│   │       └── upstream "delegate" (namespace delegate): delegate__delegate_to
│   └── HTTP POST /task to a worker (chain_id, delegator_agent_id, task)
│
├── data-worker container
│   ├── HTTP /task server (per-request Claude tool-use loop)
│   └── Mitrity Gateway (stdio; own agent identity, own profile)
│       ├── upstream "data" (namespace data): data__query_database, data__fetch_orders, data__create_order
│       └── upstream "delegate" (namespace delegate): delegate__delegate_to
│
└── notification-worker container
    ├── HTTP /task server (per-request Claude tool-use loop)
    └── Mitrity Gateway (stdio; own agent identity, own profile)
        ├── upstream "notify" (namespace notify): notify__send_notification, notify__email_customer
        └── upstream "delegate" (namespace delegate): delegate__delegate_to

Each container's gateway talks to your MITRITY control plane independently, and each agent's actions are evaluated by its gateway against its profile. Delegation hops are real governed HTTP calls between the containers. The gateway captures each hop and reports it to the control plane, and the dashboard reassembles the chain across the three event streams.

The orchestrator's gateway serves both entrances: the MCP tools/call stream from the SDK, and the loopback admission API that the hook calls. It attests the runtime's posture, so the orchestrator's coverage badge reflects what is actually governed. The attestation covers which built-in tools are hooked, which are not, other MCP servers and the permission mode. The adapter is mitrity.claude_agent_sdk.Governor from the mitrity package on PyPI (pip install 'mitrity[claude-agent-sdk]'), and orchestrator/runner.py shows the few lines that wire it up.

The workers are plain HTTP /task servers. Each request runs a Claude tool-use loop against the worker's own gateway over stdio, so every worker-side call is judged under the worker's identity, including a further delegate__delegate_to. All three containers run as unprivileged users (the orchestrator as demo, uid 1000).

Credential broker (per-agent, hot rotation)

All three gateways ship with credentials.injection_enabled: true. None of S1–S6 exercise it directly, because those scenarios focus on delegation and threat intel, but the capability is fully available for ad-hoc testing.

To try it:

  1. Provision a credential in the dashboard (/app/credentials).
  2. Grant it to one of the three agents (e.g., the data-worker).
  3. Drive a tool call whose args include ${credential:<id>}.

The gateway substitutes the placeholder through the broker before the upstream tool runs. A rotation in the dashboard reaches the running container within 30 seconds, with no agent or gateway restart.

The multi-agent setting adds per-agent credential scoping. The data-worker can hold DB credentials while the notification-worker independently holds SMTP / Slack / SendGrid keys, and rotating one does not invalidate the other's cache.

For a guided walkthrough of provisioning, substitution, mid-scenario rotation and the fail-closed path, see phase 6 in either single-agent demo:

When to use this demo (vs. the single-agent demos)

Single-agent demosMulti-agent demo (this)
Containers13 (compose stack)
Agents13 distinct identities
Phases1–6 and 8 (gateway: also 9)7
Primary focusPolicies, DLP, injection, holds, credential broker, built-in tools through the admission hook (gateway: the governed shell)Delegation chains, per-agent threat intel, privilege boundaries
Plan requiredAny (Starter included)Pro or Enterprise
Setup complexity1 agent in dashboard3 agents + one policy per agent + custom tools + per-agent capability grants
Best forFirst-time evaluation, single-agent governance demosShowcasing multi-agent architecture, delegation, privilege escalation detection

Start with the single-agent demos to learn the governance core, then run this one to see what is specific to multiple agents.

Environment variables

VariableRequiredDescription
ANTHROPIC_API_KEYYesAnthropic API key (shared across all three containers)
MITRITY_CONTROL_PLANE_URLYesControl plane URL (e.g., https://api.mitrity.com)
MITRITY_AGENT_ID_ORCHESTRATOR / _KEY_ORCHESTRATORYesOrchestrator agent identity
MITRITY_AGENT_ID_DATA / _KEY_DATAYesData-worker agent identity
MITRITY_AGENT_ID_NOTIFY / _KEY_NOTIFYYesNotification-worker agent identity
ANTHROPIC_MODELNoClaude model for all three agents (default: claude-sonnet-5)
MITRITY_DEMO_SPEEDNonormal (default) or fast (skip the pauses between scenarios)

See .env.example in the repo.

Troubleshooting

  • /delegation-chains is empty after a run: check /audit first. If there are no events from any of the three agents, check that each agent's AGENT_ID + AGENT_KEY env var pair matches what's in the dashboard. If chains were emitted but the dashboard shows nothing, check that all three agents are registered to the same tenant. A worker registered to another tenant lands its events in that tenant's audit log.
  • /threat-intel shows no matches: confirm that the data-worker's policy has Threat Intelligence on Enforce (Step 2), and that MITRITY Curated is ticked under Subscribed Feeds on the Threat Intel Settings tab (Step 4).
  • S5 shows a match but no block: set Per-Severity Actions → Critical to Block on the Threat Intel Settings tab (Step 4). With the default action, Alert, a critical match is alerted, not blocked.
  • A policy rule does not match: rules target gateway-served tools as mcp:<namespace>__<tool> (mcp:delegate__delegate_to, mcp:data__create_order), not as the bare tool name, and not in the older mcp:<namespace>:<tool> form.
  • The orchestrator exits before the first scenario: docker compose up waits for both workers to report healthy (their gateway handshake with the control plane). If a worker never becomes healthy, its logs (docker compose logs data-worker) name the missing variable or the rejected agent key.
  • The S2 privilege-escalation scenario doesn't fire: check Step 3 first, not Step 2. The privilege-escalation engine compares Capabilities, not policy rules. Both agents need differentiated grants: the data-worker holds MCP Database, and the orchestrator does not. If both agents have zero capabilities configured, the diff returns empty and the check does nothing.
  • S2 fires, but the chain detail's "Escalated resources" card is empty: the hop was blocked for another reason (depth, cycle, allow-list) before the privilege-escalation check ran. The engine evaluates in order and returns the first match. Check the hop's blocked_reason in the chain detail header: only privilege_escalation fills the card.

Next steps

  • Demo: Mitrity Gateway: a single-agent demo focused on policies, DLP, injection, holds, built-in tools and the governed shell.
  • Demo: Mitrity MCP Sidecar: a single-agent demo in the transparent-proxy deployment form.
  • Integration modes: how the gateway and the sidecar compare as architectures.
  • Governance coverage: what the admission hook covers in each framework, and how the coverage badge is computed.
  • Delegation chains: per-agent allow and disallow lists, depth limits, fan-out windows and privilege-escalation detection.
  • Threat intelligence: indicator feeds, per-severity policies and per-indicator overrides.