The easiest way to make an AI workflow feel impressive is to let it finish the job: draft the email and send it, identify stale records and update them, find a supplier and place the order. The easiest way to make that workflow expensive is to let a plausible-looking interpretation become an irreversible action without a pause.
A useful middle ground is the proposal-before-action pattern. Ask the model to prepare a precise proposed change first. Show that proposal to the person or program that owns the decision. Only then allow a separate execution step to carry out the already-approved change. This is not a request for a vague “please confirm?” at the end of a long chat. It is a deliberately narrow handoff with an object to inspect: recipients, fields to change, before/after values, source evidence, and anything the model could not determine.
Anthropic distinguishes predictable, predefined workflows from agents that dynamically direct their own tool use. For well-defined tasks, a workflow gives you consistency; it can also put programmatic gates between steps.[1] OpenAI recommends keeping tool approvals enabled so users can review and confirm operations, and says that structured outputs and confirmation help reduce unexpected behavior.[2] The practical conclusion is simple: do the reasoning in a proposal, but reserve the write, send, publish, or purchase for an explicit approval boundary.
Make the proposed change inspectable
A proposal is useful only if a reviewer can answer “what will happen?” without reopening every source. Do not ask an AI to return “I’m ready to update the CRM.” Ask it to return the record identifier, the exact fields that would change, the old and new values, the evidence behind each change, and a list of uncertainty or blockers.
For an email, that means the exact recipients, subject, body, attachments, and send time. For a database update, it means the record ID and a field-by-field diff. For a code change, it means the files, the test result, and the patch or a link to it. The proposal should be easy to reject partly: approve the address change but not the status change; edit the recipient but keep the draft.
This changes the prompt too. The model is no longer asked to “update the account.” It is asked to diagnose, explain, and produce a constrained intent object. Your application decides whether that object is valid, whether it needs human approval, and what execution tool is allowed to receive it.
Copy this proposal-before-action contract
Use this as a tool input/output contract or as a prompt section before an AI touches a consequential workflow.
TASK
Prepare a proposed [email | record update | purchase | publication].
Do not execute it, call an action tool, or claim it has happened.
ALLOWED SCOPE
- Target: [specific account, record, draft, or workspace]
- Allowed changes: [named fields or permitted action]
- Forbidden changes: [anything outside scope]
RETURN EXACTLY
{
"action": "[allowed action name]",
"target_id": "[stable identifier]",
"changes": [
{"field": "[field]", "before": "...", "after": "...", "evidence": "short source quote"}
],
"external_effects": ["[recipient, charge, publish, or null]"],
"uncertainties": ["..."],
"requires_approval": true,
"execution_token": null
}
RULES
- If an ID, value, or evidence is missing, list it in uncertainties.
- Never invent a target or a before value.
- Do not produce an execution token. A separate approval service creates one
only after a reviewer accepts this exact proposal.
The important field is not the model-generated requires_approval flag; your system should decide that policy. The value of the template is that it makes the proposal complete enough to check. A reviewer sees the impact before the operation rather than reverse-engineering it from a fluent explanation.
Keep approval bound to the exact proposal
After review, do not send the model’s prose back with “approved, go ahead.” That makes room for drift: a later message can change the target, and the system may not know which version was approved. Instead, have the reviewer approve a stable proposal ID or a digest of the structured object. The execution service should fetch that approved object itself, validate it again, and use an allowlisted action with the minimum permissions needed.
For example, an approved contact update may permit only customer_id=1842, status=active, and renewal_date=2026-11-01. The executor should reject an extra email field even if it arrived in the same request. Save an audit record with the proposal, approver, time, result, and any returned ID. If the action is retried, use an idempotency key so a timeout does not turn into two emails or two charges.
Failure mode: approval theater
A giant prose summary can make a person click “approve” without noticing the consequential detail. An approval gate also cannot repair a proposal that lacks enough evidence, and routine low-risk work can become unusably slow if every read needs a person. Keep the proposal compact, highlight external effects, and use risk-based rules: auto-execute only a small allowlist of reversible, well-validated actions; require review for sends, spending, publication, deletion, permission changes, and ambiguous inputs.
The habit to keep
let AI prepare the work, but make the moment that changes the world small, explicit, and separately authorized.