Skip to content

Commit 0c7e315

Browse files
feat(ca2a-delegation): add agent-to-agent delegation example
Add a new example demonstrating the cA2A agent-to-agent boundary, the layer above the cMCP agent-to-tool boundary the other examples show. - A three-hop credit delegation chain (credit-platform -> lead-credit-agent -> screening-sub-agent -> bureau-connector) built with ca2a-runtime. The authority to write the risk report is granted to the lead agent but withheld from every delegated scope, so attenuation alone guarantees no sub-agent can regain it (separation of duties by credential). - The demo verifies the valid chain, then shows a sub-agent trying to widen its scope and being rejected with SCOPE_ESCALATION. Both chains are also verifiable via `ca2a verify-chain`. - README maps the same pattern onto healthcare, multi-tenant-saas and industrial-embodied-ai, and is explicit that only attenuated delegation and offline verification run today; the live peer path and sealed channel are cA2A roadmap. - Pure delegation_scenario module + unit tests and a CI job. Cross-linked from the startup-tpm next-steps table and the root README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent eb65794 commit 0c7e315

10 files changed

Lines changed: 463 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,25 @@ jobs:
123123

124124
- name: Run people directory tests
125125
run: python -m unittest discover -s tests -v
126+
127+
ca2a-delegation:
128+
runs-on: ubuntu-latest
129+
defaults:
130+
run:
131+
working-directory: ca2a-delegation
132+
steps:
133+
- uses: actions/checkout@v7
134+
135+
- name: Set up Python 3.11
136+
uses: actions/setup-python@v6
137+
with:
138+
python-version: "3.11"
139+
140+
- name: Install cA2A runtime (pre-release)
141+
run: python -m pip install --pre ca2a-runtime
142+
143+
- name: Run delegation tests
144+
run: python -m unittest discover -s tests -v
145+
146+
- name: Run the delegation demo
147+
run: python delegation_agent.py

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
22
[![cMCP](https://img.shields.io/badge/Uses-cMCP_Runtime-7c3aed)](https://github.com/agentrust-io/cmcp)
33
[![Agent Manifest](https://img.shields.io/badge/Uses-Agent_Manifest-0ea5e9)](https://github.com/agentrust-io/agent-manifest)
4+
[![cA2A](https://img.shields.io/badge/Uses-cA2A-7c3aed)](https://github.com/agentrust-io/ca2a)
45
[![Discord](https://dcbadge.limes.pink/api/server/9JWNpH7E?style=flat)](https://discord.gg/9JWNpH7E)
56

67
# agentrust-io Examples
78

8-
End-to-end integration examples showing cMCP, Agent Manifest, and TRACE working together across deployment scenarios. Each example is self-contained and runnable on a fresh cloud VM. Running them shows how the three projects compose: cMCP enforces policy at the tool call boundary, Agent Manifest carries the identity and capability declaration, and TRACE emits a signed Trust Record for every tool invocation so you can see what the full audit trail looks like in practice.
9+
End-to-end integration examples showing cMCP, Agent Manifest, and TRACE working together across deployment scenarios. Each example is self-contained and runnable on a fresh cloud VM. Running them shows how the projects compose: cMCP enforces policy at the tool call boundary, Agent Manifest carries the identity and capability declaration, cA2A attenuates authority at the agent-to-agent boundary, and TRACE emits a signed Trust Record for every tool invocation so you can see what the full audit trail looks like in practice.
910

1011
## Examples
1112

1213
| Example | What it shows | Platform | Compliance |
1314
|---|---|---|---|
15+
| `ca2a-delegation/` | Agent-to-agent delegation with cA2A: attenuated credit-workflow chain, offline verification, and a rejected scope escalation | Software-only (offline) | Separation of duties, least authority |
1416
| `embodied-action-receipts/` | Fixture-style offline verification for embodied action receipts: accepted chain, missing receipt, signature mismatch and valid controller rejection | Software-only fixtures | TRACE action-receipt evidence boundary |
1517
| `financial-services/` | Corporate credit risk agent: six-step assessment with CDD, exposure and IFRS 9 guardrails on the write | SEV-SNP / TDX | EU AI Act Art. 9/12, CRR Art. 395, EBA/GL/2020/06, EU AML, DORA Art. 9 |
1618
| `healthcare/` | Clinical agent on a coherent ICD-10 patient: drug-interaction check feeds EU AI Act Art. 14 HITL and contraindication denies | SEV-SNP / TDX | EU AI Act Art. 14, HIPAA |
1719
| `industrial-embodied-ai/` | Material-movement agent with cMCP authorization, an independent safety-controller boundary and offline-verifiable closed-session evidence | TEE / software-only development mode | OT security and industrial robot safety references |
1820
| `multi-tenant-saas/` | HR SaaS with an EU tenant (enforcing GDPR residency/Art. 9) and a US tenant (advisory) on one catalog | TDX | GDPR Art. 6/9/44, customer DPA |
1921
| `startup-tpm/` | 15-minute quickstart on any cloud VM with Trusted Launch | TPM 2.0 | Development / staging |
2022

21-
Each example is fully runnable with no external dependencies: it ships a mock upstream MCP server, an agent script, an attested tool catalog, and a Cedar policy bundle, and ends by printing the signed TRACE Trust Record for the session. The `trace-output/` files in each example are captured from real runs.
23+
Most examples are fully runnable with no external dependencies: they ship a mock upstream MCP server, an agent script, an attested tool catalog, and a Cedar policy bundle, and end by printing the signed TRACE Trust Record for the session. The `trace-output/` files are captured from real runs. The `ca2a-delegation/` and `embodied-action-receipts/` examples are offline verifiers and ship their captured chains instead.
2224

2325
## Quickstart
2426

ca2a-delegation/README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# ca2a-delegation: The Agent-to-Agent Boundary
2+
3+
The other examples in this repo govern the **agent-to-tool** boundary: cMCP decides what a *single* agent may call. This example governs the **agent-to-agent** boundary with [cA2A](https://github.com/agentrust-io/ca2a): when a lead agent hands part of a task to a sub-agent, each hop carries a signed delegation credential whose scope is a provable subset of its parent, so authority can only ever narrow as it flows outward.
4+
5+
It uses the credit-risk workflow from [`financial-services/`](../financial-services/README.md) as the worked case, and maps the same pattern onto the other examples at the bottom.
6+
7+
> **Scope of what runs here.** cA2A is in alpha. This example exercises the part that is built today: **attenuated delegation credentials and offline chain verification** (`ca2a_runtime.delegation`). The live peer path (attesting an inbound peer, sealing the payload to its measurement) and the per-hop TRACE provenance record are cA2A roadmap. See the [cA2A ROADMAP](https://github.com/agentrust-io/ca2a/blob/main/ROADMAP.md) and [LIMITATIONS](https://github.com/agentrust-io/ca2a/blob/main/LIMITATIONS.md).
8+
9+
---
10+
11+
## The scenario
12+
13+
A credit assessment is decomposed across three agents. The authority to **write the risk report** is the sensitive one: it is granted to the lead agent but is deliberately never delegated onward, so no sub-agent can regain it.
14+
15+
```
16+
credit-platform
17+
│ grants: read:documents, screen:sanctions, read:bureau, run:risk-model, write:risk-report
18+
19+
lead-credit-agent
20+
│ delegates (evidence-gathering only): read:documents, screen:sanctions, read:bureau
21+
│ WITHHELD: run:risk-model, write:risk-report
22+
23+
screening-sub-agent
24+
│ delegates (narrowest): read:bureau
25+
26+
bureau-connector
27+
```
28+
29+
Each hop's scope is a subset of its parent's; each hop's issuer is the previous hop's subject; each links to its parent by `credential_id`.
30+
31+
---
32+
33+
## Run it
34+
35+
```bash
36+
git clone https://github.com/agentrust-io/examples.git
37+
cd examples/ca2a-delegation
38+
pip install --pre ca2a-runtime # cA2A is alpha; --pre is required
39+
```
40+
41+
```bash
42+
python delegation_agent.py
43+
```
44+
45+
```
46+
Building the credit delegation chain:
47+
[0] credit-platform -> lead-credit-agent
48+
scope: ['read:bureau', 'read:documents', 'run:risk-model', 'screen:sanctions', 'write:risk-report']
49+
[1] lead-credit-agent -> screening-sub-agent
50+
scope: ['read:bureau', 'read:documents', 'screen:sanctions']
51+
[2] screening-sub-agent -> bureau-connector
52+
scope: ['read:bureau']
53+
54+
verify_chain: verified=True hops=3 leaf_scope=['read:bureau']
55+
authority withheld at the first delegation (never reaches a sub-agent): ['run:risk-model', 'write:risk-report']
56+
57+
Now the bureau connector tries to grant itself write:risk-report ...
58+
verify_chain: verified=False code=SCOPE_ESCALATION
59+
reason: hop 2 scope exceeds parent grant
60+
```
61+
62+
The demo writes both chains to `chain-output/`. Verify either from the CLI, which checks the same four invariants:
63+
64+
```bash
65+
ca2a verify-chain --chain chain-output/credit-delegation-chain.json
66+
# {"verified": true, "hops": 3, "leaf_scope": ["read:bureau"]}
67+
68+
ca2a verify-chain --chain chain-output/escalation-attempt.json
69+
# {"verified": false, "code": "SCOPE_ESCALATION", "error": "hop 2 scope exceeds parent grant"}
70+
```
71+
72+
---
73+
74+
## What verification checks
75+
76+
`verify_chain` fails on the first violation:
77+
78+
1. **Signature** on every hop against the issuer's Ed25519 public key.
79+
2. **Continuity**: each hop's issuer is the previous hop's subject.
80+
3. **Attenuation**: each hop's scope is a subset of its parent's scope (`SCOPE_ESCALATION` otherwise).
81+
4. **Anti-replay / structure**: unique `credential_id`s, `parent_id` links to the previous hop, depth increments by one and stays within `max_depth`.
82+
83+
Because the write authority is withheld at the first delegation, attenuation alone guarantees that no descendant, however many hops down, can write the risk report. That is separation of duties enforced by the credential, not by convention.
84+
85+
---
86+
87+
## How this maps onto the other examples
88+
89+
The same agent-to-agent boundary applies wherever one agent hands work to another. cA2A is the layer above the cMCP tool boundary each of these already demonstrates.
90+
91+
| Example | Delegation hop | Attenuation the chain proves | Escalation it blocks |
92+
|---|---|---|---|
93+
| [`financial-services`](../financial-services/) | lead credit agent → screening sub-agent → bureau connector | evidence-gathering scope is a subset; `write:risk-report` is withheld | a sub-agent trying to write the risk report |
94+
| [`healthcare`](../healthcare/) | clinician agent → pharmacy/medication-safety sub-agent | pharmacist gets only `check:interaction`; no `read:record` or `write:plan` | a consult agent trying to write the treatment plan |
95+
| [`multi-tenant-saas`](../multi-tenant-saas/) | tenant orchestrator → export processor | EU parent holds `export:eea-only`; it cannot mint a child with `export:us` | a cross-border delegation the tenant never had authority to grant |
96+
| [`industrial-embodied-ai`](../industrial-embodied-ai/) | cell orchestrator → robot-cell agent (its `delegation_chain` field is the hook) | the cell agent gets only `move:buffer-zone-1` | a motion request outside the granted zone |
97+
98+
In each, the sensitive capability is granted high and withheld from the delegated scope, so cA2A's attenuation check is what stops a sub-agent from doing more than it was handed. Where a sub-agent is in a different trust domain (the SaaS export processor, a cross-hospital consult), the cA2A **sealed channel** (roadmap) is what would bind the task payload to that peer's attested measurement.
99+
100+
---
101+
102+
## The tests
103+
104+
`tests/test_delegation_scenario.py` checks that the chain verifies, that each hop is a subset of its parent, that the write authority is never delegated, and that the escalation attempt raises `ScopeEscalation`.
105+
106+
```bash
107+
python -m unittest discover -s tests -v
108+
```
109+
110+
---
111+
112+
## License
113+
114+
Apache 2.0. See [LICENSE](../LICENSE) in the repo root.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"chain": [
3+
{
4+
"credential_id": "credit-cred-0",
5+
"issuer": "7f55fd29bc6632cc46c68910e44d8d0efdb46d6c0599e316ef693ffe07f1cc5b",
6+
"subject": "cb3a37b9f41f452008e0077222a6850f5f8a6bada53b383a85e86c0dee1b7739",
7+
"scope": [
8+
"read:bureau",
9+
"read:documents",
10+
"run:risk-model",
11+
"screen:sanctions",
12+
"write:risk-report"
13+
],
14+
"depth": 0,
15+
"parent_id": null,
16+
"signature": "1c67f820a1acabb7821d8310cd85883da567eff2ceaecf678c8bb8dcd8fe06858157a8f40d35f11fd7e4b91ba2a29476bf495fbc2198bcaf9227746e51528500"
17+
},
18+
{
19+
"credential_id": "credit-cred-1",
20+
"issuer": "cb3a37b9f41f452008e0077222a6850f5f8a6bada53b383a85e86c0dee1b7739",
21+
"subject": "a399d0d49232152829593199ca2c74ff2b66ab669e6e02c1fdeaeb910f0ab1b5",
22+
"scope": [
23+
"read:bureau",
24+
"read:documents",
25+
"screen:sanctions"
26+
],
27+
"depth": 1,
28+
"parent_id": "credit-cred-0",
29+
"signature": "62fae79108808b023a95ed68c4ae328de6b4dcd30e11f9ed87cb61fb03fe46f350f2377b9aa2deac55d87da1ec975273b01b8961dc86a48a3b3f7674a2948a0f"
30+
},
31+
{
32+
"credential_id": "credit-cred-2",
33+
"issuer": "a399d0d49232152829593199ca2c74ff2b66ab669e6e02c1fdeaeb910f0ab1b5",
34+
"subject": "b93a9ce5014c6484d9861d9dc22cab731b0d6d21594c91edea8d11c1be65bfbf",
35+
"scope": [
36+
"read:bureau"
37+
],
38+
"depth": 2,
39+
"parent_id": "credit-cred-1",
40+
"signature": "4e8b5ac961f1d174297c726046069f87dac7a6894002ff385fdda5729912783615ae1e5857815377b6289dadde2897ab2f6a56dc8e548e968bc3f923b20dd501"
41+
}
42+
]
43+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"chain": [
3+
{
4+
"credential_id": "credit-cred-0",
5+
"issuer": "a8ea035f0d574cb3ace5c8e988fa10b972935887f266af109b725b909e91d243",
6+
"subject": "e5c7125ecce92f4de683e32e4fa28b9d8ab87fe5a10ceb7de35077a8ab95c435",
7+
"scope": [
8+
"read:bureau",
9+
"read:documents",
10+
"run:risk-model",
11+
"screen:sanctions",
12+
"write:risk-report"
13+
],
14+
"depth": 0,
15+
"parent_id": null,
16+
"signature": "6a7bdb7d20800d7e415173449b6a93fddedacbe1e103a6add8e92e52ebd017ae7590aa498721002aea168cd1354f692e38249edb1654ce216f8302295b3ea000"
17+
},
18+
{
19+
"credential_id": "credit-cred-1",
20+
"issuer": "e5c7125ecce92f4de683e32e4fa28b9d8ab87fe5a10ceb7de35077a8ab95c435",
21+
"subject": "915f9a0266a99eb6c4d92dea2c8a476f53b8ff8ef77fc749dd935a353fc1721b",
22+
"scope": [
23+
"read:bureau",
24+
"read:documents",
25+
"screen:sanctions"
26+
],
27+
"depth": 1,
28+
"parent_id": "credit-cred-0",
29+
"signature": "34ee23de76780d88168a7f80abde7b5437aaa029073cd9c37978d7ff205d45c8c7fb12c3409dfeaff601fbbeb1f2ef7da93c087bc6c76cfc36b4aef0a9a88f07"
30+
},
31+
{
32+
"credential_id": "credit-cred-2",
33+
"issuer": "915f9a0266a99eb6c4d92dea2c8a476f53b8ff8ef77fc749dd935a353fc1721b",
34+
"subject": "a90c204fa79fc6a2f222ad5a9a5581d7cd1f7d47f0e65dba9a269627b275bc9f",
35+
"scope": [
36+
"read:bureau",
37+
"write:risk-report"
38+
],
39+
"depth": 2,
40+
"parent_id": "credit-cred-1",
41+
"signature": "7bd69aa6ef7b6ce975b26aa65b15fdcd6c6df7c96a4fefb6ac6e12eea4a9a8d6020586de2e9e706c058906c1be50b9aaae3fb36f616b2ba4509f20664cc76708"
42+
}
43+
]
44+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
"""
3+
cA2A delegation demo for the credit-risk workflow.
4+
5+
Builds a three-hop delegation chain (credit platform -> lead credit agent ->
6+
screening sub-agent -> bureau connector), verifies it offline, then shows a
7+
sub-agent trying to widen its scope and being rejected. No network, no TEE.
8+
9+
Usage:
10+
python delegation_agent.py
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import json
16+
import sys
17+
from pathlib import Path
18+
19+
if hasattr(sys.stdout, "reconfigure"):
20+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
21+
22+
sys.path.insert(0, str(Path(__file__).resolve().parent))
23+
24+
import delegation_scenario as scenario # noqa: E402
25+
from ca2a_runtime.errors import CA2AError # noqa: E402
26+
27+
OUT_DIR = Path(__file__).resolve().parent / "chain-output"
28+
29+
30+
def _write(document: dict, name: str) -> Path:
31+
OUT_DIR.mkdir(exist_ok=True)
32+
path = OUT_DIR / name
33+
path.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8")
34+
return path
35+
36+
37+
def show_valid_chain() -> None:
38+
chain = scenario.build_credit_chain()
39+
print("Building the credit delegation chain:")
40+
for label, cred in zip(scenario.HOP_LABELS, chain):
41+
print(f" [{cred.depth}] {label}")
42+
print(f" scope: {sorted(cred.scope)}")
43+
print()
44+
45+
scenario.verify(chain)
46+
leaf = sorted(chain[-1].scope)
47+
withheld = sorted(scenario.CREDIT_CHAIN_SCOPES[0] - chain[1].scope)
48+
print(f"verify_chain: verified=True hops={len(chain)} leaf_scope={leaf}")
49+
print(f" authority withheld at the first delegation (never reaches a sub-agent): {withheld}")
50+
path = _write(scenario.as_chain_document(chain), "credit-delegation-chain.json")
51+
print(f" wrote {path.name}")
52+
print()
53+
54+
55+
def show_escalation_attempt() -> None:
56+
chain = scenario.build_escalation_attempt()
57+
print("Now the bureau connector tries to grant itself write:risk-report ...")
58+
path = _write(scenario.as_chain_document(chain), "escalation-attempt.json")
59+
try:
60+
scenario.verify(chain)
61+
print(" UNEXPECTED: the chain verified. This should not happen.")
62+
sys.exit(1)
63+
except CA2AError as exc:
64+
code = getattr(exc, "code", "CA2A_ERROR")
65+
print(f" verify_chain: verified=False code={code}")
66+
print(f" reason: {exc}")
67+
print(f" wrote {path.name}")
68+
print()
69+
70+
71+
def main() -> None:
72+
print("=== cA2A: agent-to-agent delegation for the credit-risk workflow ===")
73+
print()
74+
show_valid_chain()
75+
show_escalation_attempt()
76+
print("The write:risk-report authority stays with the lead agent by construction:")
77+
print("it is not in any delegated child's scope, so no descendant can regain it.")
78+
print()
79+
print("Verify either chain from the CLI (same four invariants):")
80+
print(" ca2a verify-chain --chain chain-output/credit-delegation-chain.json")
81+
82+
83+
if __name__ == "__main__":
84+
main()

0 commit comments

Comments
 (0)