Skip to main content
When a policy rule evaluates to require_approval, Memproof pauses the operation and waits for an explicit approve or deny decision before persisting the memory. This guide covers the full lifecycle.

When Approval is Triggered

An operation enters the approval flow when:
  1. A policy rule with action: require_approval matches the operation.
  2. The operation is a mutation (remember, update, or forget).
The response will have status: "pending_approval" and the operation will not be written to the backend adapter until resolved.
result = await mp.remember(
    content="User SSN is 123-45-6789",
    scope={"tenant_id": "t1", "project_id": "p1", "agent_id": "a1"},
    context={"actor_type": "agent", "actor_id": "a1",
             "source": "untrusted", "timestamp": "2026-01-01T00:00:00Z"},
)
print(result.status)  # "pending_approval"
print(result.operation_id)  # "op-abc123..."

The ApprovalBroker

The ApprovalBroker is the component that manages pending approvals. It supports two modes:
ModeConfig FlagsBehavior
Manual queueDefault (no Attesta config)Operations queue in-memory for approve()/deny() calls
Attesta bridgeattesta_enabled=True, attesta_url, attesta_tokenHTTP call to Attesta for automated verdicts

Manual Approval (Default)

When Attesta is not configured, the broker queues the operation and returns None from request_approval(), signaling the orchestrator to set status to pending_approval.

Attesta Integration

When enabled, the broker sends an action context to the Attesta /v1/actions/evaluate endpoint. Attesta returns a synchronous verdict (approved, modified, or denied).
mp = Memproof(
    policy="memproof.yaml",
    attesta_enabled=True,
    attesta_url="https://attesta.example.com",
    attesta_token="at-xxx",
)
If Attesta is unreachable, the broker returns approved=False with a note explaining the failure. The operation is denied rather than silently approved.

Resolving Pending Operations

1

Check operation status

Use the operation ID from the original response to check current status.
status = mp.get_operation_status(result.operation_id)
print(status.status)  # "pending_approval"
2

Approve the operation

Call approve() with an actor ID identifying who approved it.
approved = await mp.approve(
    operation_id=result.operation_id,
    actor_id="reviewer@example.com",
    notes="Content reviewed and cleared.",
)
print(approved.status)  # "committed"
3

Or deny the operation

Call deny() to permanently block the operation.
denied = await mp.deny(
    operation_id=result.operation_id,
    actor_id="reviewer@example.com",
    notes="Contains sensitive data that should not be stored.",
)
print(denied.status)  # "blocked"

Action Context

The broker builds an Attesta-compatible action context for each pending operation:
{
  "function_name": "memory.remember",
  "kwargs": {
    "operation_id": "op-abc123",
    "content_preview": "User SSN is 123-45-...",
    "scope": {"tenant_id": "t1", "project_id": "p1", "agent_id": "a1"}
  },
  "agent_id": "a1",
  "session_id": null,
  "metadata": {
    "source": "untrusted",
    "actor_type": "agent",
    "actor_id": "a1"
  },
  "hints": {
    "risk_score": 0.85,
    "risk_level": "high",
    "risk_factors": ["PII_DETECTED"]
  }
}
Content is truncated to 200 characters in the action context preview. The full content is never sent to external approval services.

End-to-End Example

import asyncio
from memproof import Memproof

async def main():
    mp = Memproof(policy="memproof.yaml", adapter="in_memory")

    # 1. Create a memory that triggers approval
    result = await mp.remember(
        content="Patient diagnosis: Type 2 diabetes",
        scope={"tenant_id": "hospital", "project_id": "ehr", "agent_id": "intake-bot"},
        context={"actor_type": "agent", "actor_id": "intake-bot",
                 "source": "api", "timestamp": "2026-01-15T10:00:00Z"},
    )

    if result.status.value == "pending_approval":
        print(f"Operation {result.operation_id} awaiting approval")

        # 2. A human reviewer approves
        final = await mp.approve(
            operation_id=result.operation_id,
            actor_id="dr.smith@hospital.org",
            notes="Verified with patient consent form.",
        )
        print(f"Final status: {final.status}")

asyncio.run(main())
In a web application, expose the approve() and deny() methods behind authenticated admin endpoints. The operation_id serves as the stable reference between the request path and the review UI.