> ## Documentation Index
> Fetch the complete documentation index at: https://memproof.kyberon.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval Flow

> Understand how Memproof handles operations that require human or automated approval before execution.

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.

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
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`).

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
mp = Memproof(
    policy="memproof.yaml",
    attesta_enabled=True,
    attesta_url="https://attesta.example.com",
    attesta_token="at-xxx",
)
```

<Note>
  If Attesta is unreachable, the broker returns `approved=False` with a note explaining the failure. The operation is denied rather than silently approved.
</Note>

## Resolving Pending Operations

<Steps>
  <Step title="Check operation status">
    Use the operation ID from the original response to check current status.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    status = mp.get_operation_status(result.operation_id)
    print(status.status)  # "pending_approval"
    ```
  </Step>

  <Step title="Approve the operation">
    Call `approve()` with an actor ID identifying who approved it.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    approved = await mp.approve(
        operation_id=result.operation_id,
        actor_id="reviewer@example.com",
        notes="Content reviewed and cleared.",
    )
    print(approved.status)  # "committed"
    ```
  </Step>

  <Step title="Or deny the operation">
    Call `deny()` to permanently block the operation.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    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"
    ```
  </Step>
</Steps>

## Action Context

The broker builds an Attesta-compatible action context for each pending operation:

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
  "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"]
  }
}
```

<Warning>
  Content is truncated to 200 characters in the action context preview. The full content is never sent to external approval services.
</Warning>

## End-to-End Example

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
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())
```

<Tip>
  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.
</Tip>
