Drawing an approval button is easy. Building a workflow that respects an approval requires a clear contract between the proposal, the reviewer and the executor. Start with an action you can describe completely, such as drafting a support reply or proposing how to route an incoming request.
1. Show the exact proposed action
The review screen should state the destination, the fields or message to be sent, the reason and the source information used. Give the proposal a unique identifier and a state: proposed, approved, rejected, expired or executed. Changes to its important fields should invalidate the previous approval.
Define who may approve, how long an approval is valid, and what happens if the underlying source changes. A reviewer should not approve a vague instruction such as “do the appropriate thing” when the executor is capable of making several consequential changes.
2. Enforce permission outside the model
The execution layer should check the approved proposal, permissions, destination and current limits itself. Text generated by a model is not proof that approval exists. Give the model only the access required to prepare the proposal.
The OWASP prompt-injection guidance describes how external content can influence a model and recommends measures such as least privilege and validation. Retrieved text and incoming messages should be treated as data, rather than as authorization to expand the workflow.
Worked example: routing a support request
In this illustrative workflow, a message asks for a billing correction. The assistant proposes routing it to Billing with the message reference and a short summary. A reviewer can change the destination or reject the proposal. Approval applies to that routing operation only; it does not authorize a refund, an account edit or a response containing confidential information.
3. Decide what a retry means
Use a stable operation identifier so retrying the same accepted action does not create a second ticket or send a duplicate reply. After a network timeout, check whether the destination already accepted the operation before trying again. A timeout is an unknown outcome, not proof that nothing happened.
Keep rejection and expiry visible. An expired proposal should return to review if it is still useful, rather than executing automatically. Provide an operator with a way to inspect uncertain outcomes and pause new proposals while resolving them.
4. Test the awkward paths first
- A proposal is edited after approval.
- The approval expires before execution.
- The destination changes or permission is revoked.
- A request times out after the destination accepted it.
- The same action is submitted twice.
- The reviewer rejects the proposal.
Record who approved, what was approved, the execution identifier and the observed outcome. Avoid placing secrets or unnecessary personal data in the audit record. Measure rejected and uncertain cases alongside successful ones.
A small next step
Build the state transitions using a synthetic request and a fake destination before connecting a model. If that version cannot handle rejection and retries predictably, adding a stronger model will not repair the action contract.