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:
- A policy rule with
action: require_approval matches the operation.
- 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:
| Mode | Config Flags | Behavior |
|---|
| Manual queue | Default (no Attesta config) | Operations queue in-memory for approve()/deny() calls |
| Attesta bridge | attesta_enabled=True, attesta_url, attesta_token | HTTP 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
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"
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"
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.