Recipe: A2A delegation
When the triage agent can’t finish a task alone it calls a sub-agent — an agent-to-agent
(A2A) hop. That hop is not a trusted
internal call — it is gated by the same /authz decision, and it carries the original
on-behalf-of human subject — here the owner from the
scenario of the
temporary-elevation walkthrough. The sub-agent acts
for the owner, on the owner’s task, with the owner’s delegation — it never gains standing authority of
its own.
This mirrors the platform’s end-to-end governed flow: while triaging a sample
incident, incident-triage reads a runbook (regulated → needs
approval → the approver approves) and then A2A-calls a remediation
sub-agent, the hop carrying subject=ethan.park.
from palonexus import PaloNexus
AGENT = "northstar-devops-incident-agent"OWNER, APPROVER = "ethan.park@northstar.example", "maya.chen@northstar.example"
pn = PaloNexus.offline()agent = pn.agents.register(name=AGENT, owner=OWNER, sponsor=APPROVER, scenario="devops-incident")agent.provision()
with pn.task(subject=OWNER, task_id="INC-4821", scenario="devops-incident", actor=AGENT) as task: # Hop 1 — the triage agent reads a regulated runbook: deny-by-default -> needs approval. first = task.check(action="runbooks:read", resource="runbooks-api:/runbooks/db-failover") assert first.needs_approval
deleg = task.request_delegation(action="runbooks:read", resource="runbooks-api:/runbooks/db-failover", reason="INC-4821 db failover", ttl=300) pn._fake.approve_delegation(deleg.id, approver=APPROVER) # the approver approves (portal, live) task.authorize(action="runbooks:read", resource="runbooks-api:/runbooks/db-failover")
# Hop 2 — A2A to the remediation sub-agent. The hop is itself gated; the decision # carries the SAME on-behalf-of subject (the owner), not the agent's own identity. a2a = task.check(action="agent:invoke", resource="northstar-remediation-agent", target_kind="agent") assert a2a.needs_approval and a2a.subject == OWNER # gated, on-behalf-of the owner
pn._fake.grant(subject=OWNER, action="agent:invoke", resource="northstar-remediation-agent", scenario="devops-incident") allowed = task.check(action="agent:invoke", resource="northstar-remediation-agent", target_kind="agent") assert allowed.allow and allowed.subject == OWNER print("A2A hop authorized, on-behalf-of:", allowed.subject)
assert pn.audit.verify_chain() # both hops are on the chainpn.close()A2A hop authorized, on-behalf-of: ethan.park@northstar.exampleWhat this proves
Section titled “What this proves”- The hop is gated.
agent:invoketo the sub-agent goes through/authzexactly like a tool or model call — there is no privileged “internal” path. - Identity propagates, it doesn’t escalate. Both decisions record
subject=ethan.park. The sub-agent inherits the owner’s task-scoped authority for this task only; it cannot act as itself. - Each edge is independently authorized. Hop 1 (runbook) and hop 2 (sub-agent) each require their own grant — approving one does not silently widen the other.
- Auditable end to end.
pn.audit.tail(task_id="INC-4821")reconstructs the whole chain, trace-correlated in Tempo (the Grafana trace backend) on a live deployment.
In production, agent.present(audience="northstar-remediation-agent") attaches a fresh holder-
signed VP (verifiable presentation) to the A2A hop and the
egress proxy enforces it at the network layer.
Related
Section titled “Related”- Revocation race — revoke mid-A2A-run.
- Security model — identity propagation.