Delegation and References

A record names two agents: the principal, who asks for the work and renders the verdict on it, and the performer, who does it. When the performer needs another agent for part of the work, it delegates that part as a child record. The records form a delegation chain, and every link in it is signed. References then tie any of those records to the identifiers your other systems already use.

The calls below use one key per agent: $ORCHESTRATOR_KEY, $COORDINATOR_KEY and $SOURCING_KEY, each an agent-full key minted for that agent with POST /v1/admin/api-keys. For delegation between agents on different hosts over the A2A protocol, see Delegate over A2A.

Ask another agent to do the work

The principal writes the record, naming the performer:

curl -s -X POST "$AGLEDGER_API_URL/v1/records" \
  -H "Authorization: Bearer $ORCHESTRATOR_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "type": "principal-gate-generic-v1",
    "gateMode": "principal",
    "performerAgentId": "'"$COORDINATOR_ID"'",
    "criteria": {
      "summary": "Source 1,800 HX-4471-B seal kits for the Boise plant",
      "poRef": "REQ-2026-08817",
      "quantity": 1800,
      "deliverBy": "2026-11-14"
    }
  }'

The record lands at CREATED. The principal can take either of two routes from here:

Find it, and accept or reject it

The performer finds open proposals in its inbox:

curl -s "$AGLEDGER_API_URL/v1/records/agent/proposals" -H "Authorization: Bearer $COORDINATOR_KEY"
{
  "data": [
    {
      "id": "01a0dc02-c41d-7239-b2cb-2960d1476c65",
      "type": "principal-gate-generic-v1",
      "status": "PROPOSED",
      "principalAgentId": "01a0dc02-c39e-7685-ba2d-1998031b0a41",
      "criteria": { "summary": "Source 1,800 HX-4471-B seal kits for the Boise plant" }
    }
  ]
}

GET /v1/records?actionable=true returns the same proposal alongside everything else waiting on this agent. That query is the one to use when an agent restarts or misses a notification.

The terms are fixed at creation. There is no counter-proposal: the performer either accepts them as written or rejects them. To accept:

curl -s -X POST "$AGLEDGER_API_URL/v1/records/$RECORD_ID/accept" \
  -H "Authorization: Bearer $COORDINATOR_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" -d '{"message":"Accepted as written."}'

The record returns to CREATED with acceptanceStatus: "ACCEPTED". The principal then activates it ({"action":"activate"} on /transition), and it becomes ACTIVE.

To refuse the terms, the performer calls /reject, and the record ends at REJECTED:

curl -s -X POST "$AGLEDGER_API_URL/v1/records/$RECORD_ID/reject" \
  -H "Authorization: Bearer $COORDINATOR_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" -d '{"reason":"Part is discontinued; no supplier quotes it."}'

The message or reason is signed into the RECORD_ACCEPTED or RECORD_REJECTED chain entry, kept on the record as lastTransitionReason until the next transition replaces it, and carried as data.reason on the record.proposal_accepted / record.proposal_rejected event and webhook.

A principal that wants different terms creates a new record.

Delegate part of the work onward

Only the record's performer can delegate from it. It creates the child record with parentRecordId, and it becomes the child's principal:

curl -s -X POST "$AGLEDGER_API_URL/v1/records" \
  -H "Authorization: Bearer $COORDINATOR_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "type": "principal-gate-generic-v1",
    "gateMode": "principal",
    "parentRecordId": "'"$RECORD_ID"'",
    "performerAgentId": "'"$SOURCING_ID"'",
    "criteria": { "summary": "Quote 1,800 HX-4471-B, FKM only, ISO 9001, delivered Boise by 2026-11-14" }
  }'
{
  "id": "01a0dc02-c502-78fc-b975-573515055db3",
  "status": "CREATED",
  "parentRecordId": "01a0dc02-c41d-7239-b2cb-2960d1476c65",
  "rootRecordId": "01a0dc02-c41d-7239-b2cb-2960d1476c65",
  "chainDepth": 1,
  "principalAgentId": "01a0dc02-c3cb-78a3-b252-aa6f571634b0",
  "performerAgentId": "01a0dc02-c3e7-7e98-b919-a383e4121197"
}

The child goes through the same propose, accept and activate steps, or autoActivate. A few rules apply to it:

Only criteria is carried to the child. Put in it everything the child's performer needs: it is the performer's record of what was asked.

Read the tree

A party to a record reads its direct children, or the whole tree from the root:

curl -s "$AGLEDGER_API_URL/v1/records/$RECORD_ID/sub-records" -H "Authorization: Bearer $ORCHESTRATOR_KEY"
curl -s "$AGLEDGER_API_URL/v1/records/$CHILD_ID/chain" -H "Authorization: Bearer $ORCHESTRATOR_KEY"

/chain returns the tree from the root, depth-ordered, with each record's parentRecordId and chainDepth. It carries the records your key could read one at a time: a party on a record sees it and the whole subtree under it, so the orchestrator sees every branch and a subagent sees its own branch but not the root or its siblings. The parent's own body also carries hasChildren and childRecordIds. To page through the children of one parent with filters, use GET /v1/records/search?parentRecordId=<id>.

What is signed at creation

The child's first chain entry is RECORD_DELEGATED. Its actor is the delegating agent, and its signed payload carries the link:

{
  "entryType": "RECORD_DELEGATED",
  "actorDisplayName": "procurement-coordinator",
  "payload": {
    "recordId": "01a0dc02-c502-78fc-b975-573515055db3",
    "parentRecordId": "01a0dc02-c41d-7239-b2cb-2960d1476c65",
    "chainDepth": 1,
    "principalAgentId": "01a0dc02-c3cb-78a3-b252-aa6f571634b0",
    "performerAgentId": "01a0dc02-c3e7-7e98-b919-a383e4121197",
    "criteria": { "summary": "Quote 1,800 HX-4471-B, FKM only, ISO 9001, delivered Boise by 2026-11-14" }
  }
}

Get it with GET /v1/records/{id}/audit-export. The parent link, both named agents and the child's criteria are inside the signature, so none of them can be changed afterwards without the export failing verification.

That entry is the delegating agent's statement. The work itself is attributed to the child's performer when it submits its completion (COMPLETION_SUBMITTED).

Who renders the verdict on delegated work

The verdict on a child belongs to the child's principal, which is the agent that delegated it, or to an org-admin key:

curl -s -X POST "$AGLEDGER_API_URL/v1/records/$CHILD_ID/verdict" \
  -H "Authorization: Bearer $COORDINATOR_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" -d '{"completionId":"'"$COMPLETION_ID"'","verdict":"accept"}'

The verdict itself, the Settlement Signal webhook it fires, revisions and disputes are covered in Approvals.

How children affect the parent

A parent is graded on its own completion, like any record. Its children can fail it, and nothing they do settles it: a child that delivered only says the delegated work passed, not that the parent's own criteria were met. Once every child has ended, the rollup reads them:

{
  "status": "PROCESSING",
  "awaitingActor": "principal",
  "verdictChecks": { "auto_rollup": { "pass": false } }
}

In either mode, the parent reaches FULFILLED with a SETTLE signal only on a completion its own performer submits. After a failure, the parent's principal requests a revision (POST /v1/records/{id}/revision; in principal mode, after rejecting the synthesized completion). The performer then submits the parent's own completion to POST /v1/records/{id}/completions, which the parent's gate grades in auto mode, and which waits for the principal's verdict in principal mode, where accept settles it. The one other way to FULFILLED is a dispute of the failure that the principal resolves OVERTURNED, which signals RELEASE.

What a child counts as in the rollup:

Child statusRollup
FULFILLED, or REMEDIATED after an overturned disputepass
RECORDED, or CANCELLED with terminalReason: CANCEL_PRE_WORKneutral
REJECTED, EXPIRED (terminalReason: TIMED_OUT), CANCELLED with terminalReason: CANCEL_IN_PROGRESS, FAILED that can no longer be revisedfail

Work starts at activation. A child cancelled while CREATED or PROPOSED is neutral; one cancelled once ACTIVE fails the parent, whether or not anything was submitted. A child created with autoActivate is ACTIVE from its create response, so it has no neutral cancel window: leave autoActivate off on a child you may withdraw, and activate it when the work begins. The cancel step in a child's nextSteps says which of the two a cancel would be.

A child at FAILED has not ended while its principal can still request a revision, so it holds the parent until it is revised or a dispute resolves it. Once the child's deadline passes (the resubmission a revision asks for would be refused) or its revision budget is spent (maxRevisions, or maxSubmissions when the org's maxSubmissionsMode enforces it), the rollup counts the child as a failed one, and the child's nextSteps stop offering the revision. Under advisory or none a spent maxSubmissions closes nothing: the revision is still accepted, still offered, and the child still holds the parent. A rejection that spends the last revision fails the parent in the same call; a deadline that passes later fails it in the expiry sweep. Either way, a parent already past its own deadline is expired rather than failed. A parent failed this way is itself a FAILED child of its own parent, and counts there by the same rule.

On a failed parent, GET /v1/records/{id}/gate-status reads phase2Status: "failed": the rollup is the parent's evaluation. The completion the engine synthesizes for a principal parent's verdict lists with rollupSynthesized: true and agentId: null, since no agent submitted it. Until the principal renders a verdict on it, POST /v1/records/{id}/evaluate reports the rollup as its phase2Result (outcome: reject, recommendation: HOLD, the auto_rollup check); after, it reports the principal's verdict.

Deadlines and cancels

Give delegated records a deadline. A gated child that names none under a gated parent takes the parent's, and expires in the same sweep as the parent. A child expires at its deadline from any open state, CREATED included, so one that is never activated fails the parent too. To have a stalled or failed child fail the parent while the parent can still be revised, give the child an earlier deadline than the parent's.

Cancelling a parent queues a cancel for each of its open children, one job each, so poll /sub-records rather than reading the cancel's 200 as final. Two child states are left running: DISPUTED, which only the dispute's resolution or withdrawal closes, and FAILED, which becomes cancellable once its principal requests a revision.

References

References link a record or an agent to the identifiers your other systems already use: a ticket, a pull request, a purchase order, an invoice. An auditor can then start from either side.

Attach them to a record

curl -s -X POST "$AGLEDGER_API_URL/v1/records/$RECORD_ID/references" \
  -H "Authorization: Bearer $ORCHESTRATOR_KEY" -H 'Content-Type: application/json' \
  -d '{"references":[
        {"system":"jira","refType":"ticket","refId":"PROC-4471","displayName":"Boise seal kit sourcing"},
        {"system":"netsuite","refType":"purchase-order","refId":"REQ-2026-08817"}
      ]}'

The body is always the array form, with 1 to 25 references per call. system and refType values are lowercase kebab-case (purchase-order, not PurchaseOrder), and a value that does not match gets a 400 whose recoveryHint shows the pattern.

Each attach is written to the record's chain as a signed RECORD_REFERENCE_ADDED entry, carrying the system, refType and refId of every reference in the call.

References bind to one record. Tagging a root does not tag its children: tag each record you want findable, or find the root and read its /chain.

Read them back:

curl -s "$AGLEDGER_API_URL/v1/records/$RECORD_ID/references" -H "Authorization: Bearer $ORCHESTRATOR_KEY"

Attach them to an agent

For example, the GitHub App or service account an agent runs as. This needs agents:manage, so it is an org-admin call: an agent's own key gets a 403.

curl -s -X POST "$AGLEDGER_API_URL/v1/agents/$SOURCING_ID/references" \
  -H "Authorization: Bearer $ADMIN_KEY" -H 'Content-Type: application/json' \
  -d '{"references":[{"system":"github","refType":"app-installation","refId":"58213377","displayName":"sourcing-agent GitHub App"}]}'

curl -s "$AGLEDGER_API_URL/v1/agents/$SOURCING_ID/references" -H "Authorization: Bearer $ADMIN_KEY"

Go from the other system's id to the record

curl -s "$AGLEDGER_API_URL/v1/references?system=jira&refType=ticket&refId=PROC-4471" \
  -H "Authorization: Bearer $ADMIN_KEY"
{
  "data": [
    {
      "entityType": "record",
      "entityId": "01a0dc02-c41d-7239-b2cb-2960d1476c65",
      "reference": { "system": "jira", "refType": "ticket", "refId": "PROC-4471", "displayName": "Boise seal kit sourcing" }
    }
  ],
  "total": 1
}

GET /v1/references returns records and agents alike, each with its entityType. An admin key finds every match in its org; an agent key finds only the records it is the principal or performer of and its own agent references. To filter on a reference together with other record fields, use the record search:

curl -s "$AGLEDGER_API_URL/v1/records/search?ref.system=jira&ref.id=PROC-4471" -H "Authorization: Bearer $ORCHESTRATOR_KEY"

A typical audit starts at the ticket. PROC-4471 leads to the record, the record's /chain leads to every delegated step, and each step's /audit-export shows who asked, who accepted, who delivered and who rendered the verdict. The same path runs in reverse, from a record to the ticket.

Field shapes for every call above are in the API reference.

Air-gapped

Every call here goes to your own Server and nothing else: no call leaves your network, and the same commands work against an internal URL. The audit exports verify offline against the keys from GET /v1/verification-keys, with the verifier installed from your internal package mirror.