Gateway Approval Attribution

Record who approved a gateway request and bind an approval to a specific operator

Every resolved gateway approval records which operator approved it, and a request can optionally be bound to a specific reviewer so only they can resolve it.

Diagram

Approval Attribution

🤖 Request

🚪 Resolve

🧑 Operator identity

✅ Audit trail

Quick Start

  1. Approve — attribution is automatic

    The gateway captures the resolving operator on the audit trail. No extra code is needed on the agent side.

    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Deployer",
        instructions="Deploy safely",
        approval=True,
    )
    agent.start("Deploy the release candidate to prod")
  2. Bind a request to specific reviewers

    Pass authorized_reviewers so only listed operators can resolve the request.

    python
    from praisonaiagents.approval import ApprovalRequest
    
    request = ApprovalRequest(
        tool_name="deploy",
        arguments={"target": "prod"},
        risk_level="high",
        agent_name="deployer",
        authorized_reviewers=["operator:alice", "operator:oncall"],
    )

How Resolver Identity Is Captured

The POST /approval/resolve endpoint derives a non-secret operator identity from the authenticated request, in this precedence order.

Diagram

Yes

No

Yes

No

Yes

No

X-Operator-Id
header?

operator:<handle>

Bearer token?

operator:<12-hex>

Client IP?

operator:<ip>

gateway

OrderSourceIdentity string
1X-Operator-Id header (trimmed to 64 chars)operator:<handle>
2Short SHA-256 digest of the bearer tokenoperator:<12-hex>
3Client IPoperator:<ip>
4Fallbackgateway

Set X-Operator-Id from a proxy or SSO layer so operators sharing one gateway token still get distinct audit attribution. Holders of the same token collapse to one token-digest identity by design.

bash
curl -X POST -H "Authorization: Bearer $OPS_TOKEN" \
  -H "X-Operator-Id: alice" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "apr-1a2b3c4d", "approved": true}' \
  http://localhost:8080/approval/resolve

The identity flows into Resolution.resolver and is recorded on the audit trail and the allow-always grant. When no resolver is supplied, attribution falls back to the legacy "gateway" / "gateway:human" constant.


Per-request Reviewer Custody

Bind an approval to specific reviewers so only they can resolve it.

Diagram
💾 SQLite🛡️ ExecApprovalManager🚪 Gateway HTTP📱 Operator UI💾 SQLite🛡️ ExecApprovalManager🚪 Gateway HTTP📱 Operator UIalt[resolver not in authorized_reviewers][authorised (or unbound)]POST /approval/resolve (X-Operator-Id: alice)derive → “operator:alice”resolve(rid, Resolution(approved=True, resolver=”operator:alice”))False (re-insert pending)404 resolver not authorisedrecord approver = “operator:alice”True200 OK
BehaviourDetail
Unauthorised resolverReceives 404 with {"error": "Request not found, already resolved, or resolver not authorised"} and the request stays pending
First-answer-winsPreserved between authorised reviewers via the atomic pop
DurabilityThe binding survives a gateway restart (persisted and rehydrated by the SQLite store)

Defence in depth with the core SDK. Since PraisonAI #4950, the core ApprovalRegistry also honours ApprovalRequest.liveness at the resolution boundary. A gateway request that binds liveness is re-checked twice: once by the gateway resolve() boundary and once by the core registry before persisting a grant. Backends that bypass the gateway still get the core fail-closed guarantee automatically. See Approval Protocol → Live-Authority Binding.


Best Practices

Stamp X-Operator-Id at your proxy or SSO layer

Operators sharing one gateway token collapse to a single token-digest identity. Stamp X-Operator-Id per user upstream so each decision is attributed to an individual.

Use reviewer custody for high-risk requests

The APPROVALS scope is coarse — any holder may resolve any request. Bind high-risk requests to named reviewers with authorized_reviewers.

Read the approver field in audit reports

For gateway-resolved requests, approver now carries the real principal, so reports can answer “who approved this exec?”.


Related