Journal · 5 Sep 2026

An HR agent should draft — not send

A screen that says “Approve” is not the same thing as a system that cannot send without approval. One is an interface. The other is a guarantee. Here is the difference, using our own Miyaha prototype as the example, and a checklist for telling them apart in any AI agent you are evaluating.

Miyaha is an MVP prototype for an HR “chase-bot”: an AI employee, shown in these screens under the persona name Hera, that drafts follow-up messages to people who owe HR paperwork — a timesheet, an I-9, a piece of interview feedback — and keeps track of who still owes what. The pitch, in one line, is she drafts, you approve, nothing sends without your click.

That pitch is only worth something if the approval step is enforced somewhere a UI cannot quietly work around it. This article is about that distinction: what the screenshots below actually demonstrate, what they do not prove, and what we would test before calling the boundary real.

What the screenshots show

These are UI-prototype screens over sample data in a demo workspace, not a production deployment with real employees.

Miyaha Ask screen: Hera offers to chase, answer, or open a case, with sample suggested prompts
Ask — MVP interface, sample data, Hera persona.
Miyaha Inbox screen: a drafted nudge is held for review because it contained personal data, with Approve, Edit and approve, and Deny with reason buttons
Inbox — MVP interface, sample data, Hera persona.

In the Inbox screen, a drafted Slack message is held before it reaches anyone. The screen shows the exact text, who it would go to, and why it was held, then offers three actions: approve, edit the text and approve the edited version, or deny with a reason. That is the correct shape for an approval interface — the person deciding can see precisely what they are approving before they click.

Why the interface is not the guarantee

A screen like this proves that the product has a review step somewhere in its interaction design. It does not, by itself, prove any of the following, all of which a real send-boundary needs:

None of this is a claim that Miyaha's backend fails these checks — we are not publishing a security audit of our own product here. The point is narrower and, we think, more useful: a screenshot of an approval card is evidence of an interaction design, not evidence of an enforced boundary. The two get confused constantly in AI-agent marketing, and the confusion is exactly where trust gets misplaced.

What a genuine send-boundary needs

The following five properties are what we would want built and independently verified before describing an agent's approval gate as trustworthy rather than merely present. We are presenting them here as recommendations and test cases for evaluating any draft-then-approve agent — including our own — not as a list of things already proven shipped.

1. Server-side enforcement

The rule “nothing sends without an approved decision” has to live in the code path that actually performs the send, not only in the screen that asks for approval. A UI that hides the send button is not a boundary if a request straight to the underlying action can skip the gate.

Test idea: attempt to trigger the send action directly, bypassing the approval screen entirely, and confirm the system rejects it rather than merely failing to expose a button for it.

2. Changed-draft reapproval

If the text of a drafted message changes after a human has already approved it, that approval should no longer be valid for the new text. Approving what you read has to mean approving exactly what you read.

Test idea: approve a draft, then alter its content before it is sent, and confirm the system requires a fresh approval of the changed text rather than sending the altered version under the old decision.

3. Exact recipient/payload binding

An approval should be tied to a specific recipient and a specific payload, not just “a send action happened.” Otherwise a decision made about one message could, in principle, be reused to justify sending a different message, or the same message to a different person.

Test idea: approve a draft addressed to one person, then attempt to redirect the send to a different recipient or substitute a different payload under the same approval, and confirm it is rejected.

4. Single-use approval and idempotent handling

One approval decision should be consumed exactly once at the point where Miyaha itself decides to act, and that consumption should not be a bare in-memory or database flag on its own. The recommended pattern is to claim the approval and persist a durable outbox/delivery-attempt record — carrying a stable request ID — atomically, in the same step, so the two cannot come apart. A dispatcher then reads from that durable record to actually deliver the message, and, critically, a recovery pass reads the same record if the service crashes after the approval is claimed but before dispatch happens: the approved action is completed once from the durable record instead of being stranded, and a repeated recovery pass recognises the same request ID rather than sending twice. What happens after the message leaves Miyaha's boundary is a separate, equally real failure window — if the process crashes, or the receiving channel (Slack, email, or another system) never confirms whether a message arrived, after the send has already gone out, Miyaha cannot honestly claim to know it was delivered exactly once. The correct response to that ambiguity is the same durable record: on next contact, reconcile the receiving channel's own delivery record against the stable request ID, recording the outcome as unknown/pending in the meantime, never assuming success and never resending blindly. This is a proposed pattern for evaluating (and building toward), not a description of Miyaha's current implementation. There is no blanket automatic retry and no magical exactly-once guarantee across either boundary; the honest claim is a single, durably consumed local approval with a recoverable delivery attempt, plus a defined, reconciled path for the cases where the remote outcome is unclear.

Test idea: submit the same approval decision multiple times in quick succession and confirm it is consumed exactly once locally, with the repeats rejected. Separately, kill the process after the approval is claimed and the outbox/delivery-attempt record is persisted, but before dispatch, then restart it, and confirm the recovery path reads that durable record and completes exactly one dispatch rather than stranding or duplicating the action. Separately again, kill the process or lose the channel's response after a real send attempt has already gone out but before the outcome is recorded, and confirm the system records the outcome as unknown/pending rather than assuming success, and only reconciles or retries using the durable request ID (for example via an idempotency key) rather than resending blindly.

5. Revoked access and honest retry states

If a person's access is revoked between when a draft is shown and when they act on it, the system should block the action rather than honour a stale session. Separately, if a send's outcome after a legitimate approval is unclear or it fails outright, that state should be visible on its own terms and only retried after reconciling with the receiving channel — never silently reinterpreted as a fresh approval, and never reported as sent when it was not.

Test idea: revoke a user's access after a draft is shown but before they click, and confirm the approval attempt is blocked. Separately, simulate a lost or failed delivery after approval and confirm the system shows a failed or unknown/pending state, retried only after reconciliation, rather than a false success or an automatic blanket re-send.

Acceptance-test checklist

A practical checklist for evaluating this kind of boundary in any agent, including Miyaha's. These are recommended tests, not a report of tests already run and published against our own production code.

Recommended acceptance tests for a draft-then-approve send boundary
TestSetupActionExpected outcome
Bypass attempt A drafted message exists, not yet approved. Call the send path directly, skipping the approval screen. Rejected. No message delivered.
Changed-draft reapproval A draft has been approved. Alter the draft's text or recipient before delivery. Delivery blocked pending a fresh approval of the new content.
Recipient/payload binding An approval exists for message A to person X. Attempt delivery of message B, or to person Y, under that approval. Rejected. Only the exact approved payload to the exact approved recipient is deliverable.
Single-use / idempotency One approval decision is submitted. Resubmit the same decision two or more times in quick succession. Consumed exactly once locally; repeats rejected. Downstream delivery state is not assumed and is reconciled before any retry.
Lost downstream response An approved send request is dispatched to the receiving channel. The channel's confirmation response is lost or times out. Recorded as unknown/pending, not success; retried only after reconciling the channel's own delivery record (for example via an idempotency key), never automatically.
Crash recovery before dispatch An approval has been claimed and a durable outbox/delivery-attempt record persisted with a stable request ID, but the message has not yet been dispatched. Kill the process before dispatch, then restart it. The recovery path reads the durable record and completes exactly one dispatch; the approved action is neither stranded nor duplicated.
Crash recovery after remote send The message has already been dispatched to the receiving channel under its request ID. Kill the process, or lose the channel's response, after the send call returns but before the outcome is recorded locally. Outcome recorded as unknown/pending; reconciliation against the channel's own record (via the same request ID) resolves it without a duplicate send.
Revoked access A draft is shown to a user whose access is then revoked. The (now former) user attempts to approve. Approval blocked; no delivery.
Honest failure state An approval succeeds; the downstream send then fails. Inspect the resulting state. Shown as failed and retryable, never reported as sent, never silently re-approved.

Limitations

This article does not certify that Miyaha's current backend passes every test above — that would require an independent audit we have not published. What it does is separate what the screenshots demonstrate (a coherent draft-review-approve interaction) from what they cannot demonstrate (a proven, tamper-resistant send boundary), and set out the checklist we think that proof requires. If you are evaluating any AI agent that makes an approval-gate claim, ask to see these tests run against the real system, not just the screen.

Further reading

For a broader treatment of why evaluating AI agents is harder than eyeballing a demo, see Anthropic's engineering write-up on the subject: Demystifying evals for AI agents.

Open the showroom