Nouveau: Approbation des agents, red teaming et analyse de modèles. Découvrir
MITRITY
Docs / Premiers pas / Demo: Multi-Agent Governance

Demo: Multi-Agent Governance

Run a self-contained Docker compose stack that puts three Claude-powered agents to work — an orchestrator and two specialist workers — and exercises MITRITY's governance across real agent-to-agent delegation. Unlike the single-agent demos, every delegation hop, every privilege boundary, and every threat-intel match emerges from genuine multi-agent activity: no seed data, no synthetic UUIDs.

This 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 (the gateway demo also runs phase 9) and have no phase 7: delegation chains and threat intelligence live here, with their number.

The orchestrator is a Claude Agent SDK agent, wired to MITRITY with the mitrity adapter and governed on both of its entrances:

  • MCP tools (delegate__delegate_to, fs__read_file, fs__list_directory) reach the model through the orchestrator's gateway, which the SDK starts as its MCP server. Every tools/call is evaluated against your policies before the upstream tool runs. An allowed delegation is a governed HTTP POST to the worker, whose own gateway judges the next hop on the same chain.
  • The SDK's own built-in tools (Bash, Write, Edit) never produce an MCP call. 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 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 and requires 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're comfortable with 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 → + New Agent and create the following three agents. Copy each Agent ID and Agent Key (ak_...) — 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, the workers can write — and it is what makes the privilege-escalation scenario meaningful. Don't grant the orchestrator the worker tools; that's the demo.

Tool names. 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 a tool 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 one when a policy is created or updated; 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, notify:email_customer. Existing rules were rewritten to the mcp:<namespace>__<tool> form; any rule you add uses that form too.

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

  • Capabilities — the capability surface (which (tool, operation) pairs each agent CAN attempt). Per-agent. Pre-decision filter.
  • Policy rules — the runtime decision (allow/deny/alert/hold for attempts within the surface). Per-policy.

For the S2 privilege-escalation scenario to fire as described below, you need both layers configured. Step 2 authors the policy rules; Step 3 authors the differentiated Capabilities that the delegation engine compares when computing 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

Go to /app/policies and create one policy per agent, then attach each policy to its corresponding agent. Rules target the served tool names:

orchestrator-policy — attach to orchestrator

  • Rule 1: tool_pattern = "mcp:fs__*", operations = ["read"], action = allow — covers fs__read_file and fs__list_directory
  • Rule 2: tool_pattern = "mcp:delegate__delegate_to", action = allow
  • Default action: deny

data-worker-policy — attach to data-worker

  • Rule 1: tool_pattern = "mcp:data__*", action = allow — covers data__query_database, data__fetch_orders and data__create_order
  • Rule 2: tool_pattern = "mcp:delegate__delegate_to", action = allow
  • Default action: deny

notification-worker-policy — attach to notification-worker

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

Explicit names work in place of the globs (mcp:data__create_order instead of mcp:data__*) when you want one rule per tool. The orchestrator's gateway serves no data__* or notify__* tool, so the orchestrator cannot create an order directly under any policy; what S2 shows is 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 the calling agent does not — and this is a Capability comparison, not a policy-rule comparison. So the orchestrator and data-worker need differentiated Capabilities for the diff to be non-empty.

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

NameCategoryAction type patternOperations
MCP Filesystemstoragemcp:fs__*read
MCP Databasedatabasemcp:data__*read, write
MCP Notifymessagingmcp:notify__*send

Then grant Capabilities per agent from each agent's detail page → Capabilities → Add capability:

AgentCapabilities
orchestratorMCP Filesystem (read), MCP Delegate (delegate_to)
data-workerMCP Database (read, write), MCP Delegate (delegate_to)
notification-workerMCP Notify (send), MCP Delegate (delegate_to)

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

If you skip Step 3, the demo still runs — 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 will not populate.

Step 4: Confirm threat intelligence is enabled

S5 needs the built-in indicator catalog. If your tenant was created after May 25 2026, the mitrity_curated feed is enabled by default. Verify at /app/threat-intel/settings — the Subscribed feeds list should include mitrity_curated. If not, click Subscribe on the feed.

Step 5: Clone and configure

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

Edit .env with your six MITRITY agent values (one ID + 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, notification-worker — each with its own Mitrity Gateway, its own agent identity, and its own role-scoped tool surface. The first run can take a few minutes for the image builds; 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 cycles through six scenarios over ~5 minutes.

Watch the dashboard at mitrity.com/app while it runs — specifically /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 multiple agents. Every scenario is one delegate__delegate_to call from the orchestrator; 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 appears in /delegation-chains with decision allowed, showing the request flowing orchestrator → data-worker → response.

S2 — Privilege escalation. The orchestrator delegates an order creation 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 (write) and the orchestrator does not, so the hop is blocked with blocked_reason: "privilege_escalation". Denied hops are recorded, not dropped: the chain is visible on /delegation-chains under the Blocked tab, and the chain detail page renders the Escalated resources card naming (MCP Database, write) as the escalated (tool, op) pair.

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 — each continuation hop is judged by the receiving worker's gateway on the same chain, the engine clears each one, and 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 with blocked_reason: "depth_exceeded" at hop N+1. Visible 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. The worker's profile policy (critical → block) blocks the action; 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. Because chain state is rebuilt from the recorded hops on every evaluation (not carried in the arguments by the calling agent), the engine sees the orchestrator already in the chain regardless of how the hop is shaped — the cycle is caught even when it is transitive across intermediate hops. The hop is recorded as blocked with blocked_reason: "circular_delegation". Visible 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 — and, for an allowed delegation, the worker's answer. The workers' own hops (S3, S4, S6) are judged by the worker's gateway and 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 — Real-time view of multi-hop delegation. Each scenario produces at least one chain; click into one to see hop-by-hop timing, the Escalated resources card (when a hop carries the privilege_escalation signal), and blocking decisions. The Blocked tab populates with the denied hops from S2, S4, and S6 — 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 Landscape tab (trending indicators) populate.
  • /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, fed by what its runtime attested at startup (which built-in tools are hooked, which are not, 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 speaks to your MITRITY control plane independently; each agent's actions are evaluated by its gateway against its profile. Delegation hops happen via real governed HTTP 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 the hook calls. It attests the runtime's posture — which built-in tools are hooked, which are not, other MCP servers, the permission mode — so the orchestrator's coverage badge reflects what is actually governed. The adapter is mitrity.claude_agent_sdk.Governor from the mitrity package on PyPI (pip install 'mitrity[claude-agent-sdk]'); 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 — including a further delegate__delegate_to — is judged under the worker's identity. 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 — those scenarios focus on delegation + threat intel — but the capability is fully available for ad-hoc testing.

To try it: provision a credential in the dashboard (/app/credentials), grant it to one of the three agents (e.g., the data-worker), then drive a tool call whose args include ${credential:<id>}. The gateway substitutes the placeholder via the broker before the upstream tool runs. Rotation in the dashboard propagates to the running container within 30 seconds with no agent or gateway restart.

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

For a guided walkthrough (provisioning, substitution, mid-scenario rotation, fail-closed), 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 multi-agent specifics.

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, verify 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, verify 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 the Subscribed feeds list at /app/threat-intel/settings includes mitrity_curated (Step 4).

A policy rule does not match. Rules target gateway-served tools as mcp:<namespace>__<tool> (mcp:delegate__delegate_to, mcp:data__create_order), not the bare tool name and not 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 holding MCP Database (write) while the orchestrator does not. If both agents have zero capabilities configured, the diff returns empty and the check no-ops.

S2 fires but the chain detail's "Escalated resources" card is empty. The hop blocked for some other reason (depth, cycle, allow-list) before privilege escalation 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 populates the card.

Next steps

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