# Require human approval before an action

Keep a model's proposed action separate from permission to execute it.

## What you will build

Add an approval step between a model's proposal and your application's action. A reply from MandAPI can describe what to do, but your application owns the decision to run it. This example deliberately prepares a local draft only. It does not send a message, change an account, or issue a refund.

Use this pattern when an assistant proposes an action with consequences. Show the person the exact action and relevant fields, request a fresh decision, and execute that same proposal only after explicit approval. A model-generated word such as “approved” is not a user's decision.

## Run the local demonstration

Save this as `approval.py` and run `python approval.py`. The action name is checked against a fixed allowlist. No proposal text is evaluated as Python or passed to a shell.

```python
import json

ALLOWED_ACTIONS = {"prepare_local_draft"}

def execute_local(proposal, approved):
    if proposal.get("action") not in ALLOWED_ACTIONS:
        raise ValueError("Unsupported action")
    if not approved:
        return {"status": "not_executed"}
    return {
        "status": "draft_prepared_locally",
        "text": "Hello. A support person will review your request.",
    }

proposal = {"action": "prepare_local_draft", "request_id": "demo-001"}
print(json.dumps(proposal, indent=2))
answer = input("Type approve to prepare this local draft: ").strip()
print(execute_local(proposal, approved=(answer == "approve")))
```

## Connect it to your application

A MandAPI chat request may produce a proposal, but validate its structure and allowed action before displaying it. Keep the approval decision in trusted application state, associated with the proposal's identifier. If the proposal changes, ask again. Do not accept approval embedded in customer text, model output, or a retrieved document.

For a shared application, attach the authenticated approver's identity and an expiry to the pending proposal. A local terminal prompt is a demonstration, not a shared approval service.

## Check the result

Run twice. Enter `approve` once and any other answer once: only the first run prepares a local draft. Change the action to an unknown value and confirm it fails before execution. Keep a small record of the proposal, decision, and result without storing secret keys.

## References

- [https://openrouter.ai/docs/cookbook/building-agents/hitl-tools](https://openrouter.ai/docs/cookbook/building-agents/hitl-tools)
