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.

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 orchestrator pre-flight check exits cleanly on Starter rather than running scenarios that would silently no-op
  • 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 scopePolicy (one per agent)
demo-orchestrator"Coordinate customer requests by delegating to specialist worker agents"Allow filesystem reads; deny database writes; allow delegate
demo-data-worker"Read and write the customer database on behalf of the orchestrator"Allow database read + write; allow delegate
demo-notification-worker"Send customer-facing notifications on behalf of the orchestrator"Allow notify:*; allow delegate

The "Policy" column above describes MITRITY policy rules you'll author in /app/policies in the next step — one policy per agent, because the whole point of the demo is the privilege diff between them. Policy rules govern tool access through three fields per rule: tool_pattern + operations + action.

The tool names themselves (filesystem, database, notifications, delegate) are built into the demo wrapper's gateway — they're declared in tools_config.yaml shipped inside the Docker image. You can browse the tools available to your tenant at /app/tools, but you don't need to "enable" anything there for this demo. The demo uses policy rules (not capabilities) to drive the privilege diff between the orchestrator and data-worker, because policies are the simpler surface to reason about for a five-minute walkthrough. The only thing that's actually plan-gated in this demo is delegation chains (Pro/Enterprise tenants).

Step 1b: Author policy rules

Go to /app/policies and create one policy per agent, then attach each policy to its corresponding agent. The exact rules below are what make the S2 privilege-escalation scenario meaningful — they create the orchestrator-vs-data-worker permission gap the delegation engine compares.

demo-orchestrator-policy — attach to demo-orchestrator

  • Rule 1: tool_pattern = "filesystem:*", operations = ["read"], action = allow
  • Rule 2: tool_pattern = "database:write*", action = deny ← this is what makes the S2 privilege-escalation scenario fire
  • Rule 3: tool_pattern = "delegate:*", action = allow
  • Default action: deny

demo-data-worker-policy — attach to demo-data-worker

  • Rule 1: tool_pattern = "database:read*", action = allow
  • Rule 2: tool_pattern = "database:write*", action = allow
  • Rule 3: tool_pattern = "delegate:*", action = allow
  • Default action: deny

demo-notification-worker-policy — attach to demo-notification-worker

  • Rule 1: tool_pattern = "notify:*", action = allow
  • Rule 2: tool_pattern = "delegate:*", action = allow
  • Default action: deny

If you skip the explicit policy rules, the demo still runs but the S2 privilege-escalation scenario silently no-ops — the delegation engine has no permission gap to detect, so there's nothing to flag as an escalation.

The privilege difference between the orchestrator (no DB write) and the data-worker (DB write) is what makes scenario S2 (privilege escalation) fire — the orchestrator has to delegate the write because it can't perform it directly, which the policy rules in Step 1b enforce.

Step 2: 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 3: 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 orchestrator runner 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.

S1 — Clean delegation. The orchestrator inspects a customer request, decides it needs data, and delegates the lookup to the data-worker. A two-hop chain appears in /delegation-chains showing the request flowing orchestrator → data-worker → response.

S2 — Privilege escalation. The orchestrator gets a request that requires writing to the database. It can't do that itself (no write permission), so it delegates to the data-worker. The backend's /evaluate call compares the two agents' tool permissions, finds the data-worker holds (tool, op) pairs the orchestrator does not, and flags the hop with blocked_reason: "privilege_escalation". The hop is persisted to delegation_hops (denied attempts are stored, not dropped). Visible on /delegation-chains under the Blocked tab; the chain detail page renders the Escalated resources card showing the (tool, op) diff.

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 — every continuation hop hits the backend /evaluate endpoint, the engine clears each one, and the chain accumulates real hops across three event streams with action_taken: "allowed".

S4 — Deep chain. A delegation chain that exceeds the agent's max_chain_depth (default 5). The depth check runs server-side as part of /evaluate — the backend walks the persisted chain, sees the next hop would push depth to 6, and returns blocked_reason: "depth_exceeded". The hop is persisted as blocked. Visible on /delegation-chains under the Blocked tab with blocked_reason: depth_exceeded.

S5 — Threat-intel match. One of the workers attempts to read /etc/passwd — matched against MITRITY's built-in threat indicator catalog. The worker's profile policy (critical → block) blocks the action; a threat_match event lands on /threat-intel.

S6 — Circular delegation. A worker tries to delegate back to the orchestrator that originally invoked it. Because chain state is rebuilt server-side from delegation_hops on every /evaluate call (not carried in args by the calling agent), the backend sees the orchestrator already in the chain regardless of how the hop is shaped — the cycle is caught even when it's transitive across multiple intermediate hops. The hop is persisted as blocked with blocked_reason: "circular_delegation". Visible on /delegation-chains under the Blocked tab.

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 persisted.
  • /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.

Architecture

Docker Compose
├── orchestrator container
│   ├── Claude scenario runner (S1-S6)
│   └── Mitrity Gateway (own agent identity, own profile)
│       ├── filesystem  (read-only inspection of the request file)
│       └── delegate    (HTTP POST to a named worker)
│
├── data-worker container
│   ├── FastAPI /task endpoint (per-request Claude loop)
│   └── Mitrity Gateway (own agent identity, own profile)
│       ├── data        (query_database, fetch_orders, create_order)
│       └── delegate    (worker can also delegate further)
│
└── notification-worker container
    ├── FastAPI /task endpoint
    └── Mitrity Gateway (own agent identity, own profile)
        ├── notify      (send_notification, email_customer)
        └── delegate    (worker can also delegate further)

Each container's gateway speaks to your MITRITY control plane independently. Delegation hops happen via real governed HTTP between the containers — the gateway captures each hop and reports it to the control plane. The dashboard reassembles the chain across the three event streams.

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
Primary focusPolicies, DLP, injection, holds, credential brokerDelegation chains, per-agent threat intel, privilege boundaries
Plan requiredAny (Starter included)Pro or Enterprise
Setup complexity1 agent in dashboard3 agents + one policy per agent
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

See .env.example in the repo.

Troubleshooting

The orchestrator boots and prints "Skipping delegation/TI scenarios — current tenant is on the Starter plan." Your tenant is on Starter. Delegation chains and threat intelligence require Pro or Enterprise. Upgrade at /app/billing.

Containers boot but I see no delegation chains in the dashboard. 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. Each container needs its own correct pair.

The S2 privilege-escalation scenario doesn't fire. Confirm the three policies from Step 1b are authored and attached to the matching agents. The orchestrator's policy must deny database:write* while the data-worker's policy allows it — that gap is what the delegation engine compares. If both agents resolve to the same default-allow, there's no escalation to detect.

Next steps

Demo: Multi-Agent Governance — Documentation | MITRITY