|
| 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. |
0 commit comments