Agent-First Mail v0.8: Ask the Human
v0.8 brings the human decision inside afmail: interactive review windows for triage, cases, notifications, and drafts, opened by the agent and answered by you — rendered from the same typed documents the agent reads, through one MiniJinja template system you can edit and translate.
afmail builds a local workspace your agent navigates by path. Everything up to v0.7 was about making that workspace safe for the agent to act in alone — one push boundary, idempotent mutations, bounded pulls. But some decisions aren’t the agent’s to make. A dense thread needs a person to read it; a suggested classification needs a person to agree; a draft reply needs a person to fix a sentence. Until now, the only way to get a human into that loop was to send them off to a separate app to look at Markdown. v0.8 brings the decision inside afmail.
Review is a call the agent makes
The new review verb is the domain-specific version of “ask the user a question.” The agent doesn’t guess and act — it persists a typed suggestion, opens a window, and waits for a typed answer.
afmail message show message_inbox_90233_7 # read it; note review_base_hash
afmail triage suggestion set message_inbox_90233_7 \
--message-review-base-hash <HASH> --reason "refund request, belongs with support" \
case-create --name "Refund request" --group support
afmail triage review message_inbox_90233_7 # opens a window, blocks until you decide
The window shows the message and the agent’s recommendation. You agree with one click, or you disagree and say why. The command returns one structured result carrying human_suggestion.outcome: accepted and changed come back with a complete typed suggested_action the agent maps straight to a canonical command; rejected and deferred mutate nothing and tell the agent to stop and reconsider; cancelled is an explicit refusal and dismissed is simply unresolved. A human decision explains a choice — it never authorizes a remote push. That boundary is unchanged.
Because a review is just a command, it composes with everything else: give it a --request-id and a retried suggestion set is idempotent; edit the underlying message and the old suggestion goes review_stale rather than being applied against changed facts.
Four windows, one shape
The same shape covers the four places a human decision matters:
afmail triage review # decide the whole triage queue at once
afmail case review c20260723001 # read the thread, edit notes, act on the case
afmail notification review n20260723002 # rename or clear a notification collection
afmail case draft review c20260723001 reply # edit a draft reply before it's ever queued
Every surface has one top-right Done button and no Save button. Notes and drafts are the deliberate exception to result-only review: as you type, edits autosave the canonical workspace file after a short idle pause, under a hash check, and the terminal result reports every file that was saved even if you close the window afterward. A save conflict stays visible and keeps Done disabled rather than clobbering newer state.
Review defaults to a local window. Pass --mode lan to review from your phone or another browser on the same trusted network — afmail prints a one-time URL that expires in fifteen minutes. (headless exists for render and test inspection.) None of this depends on a separate UI service anymore; afmail owns its review surface directly.
The same facts, rendered once
Here’s the part that makes review trustworthy. The window you look at and the case.md your agent reads are not two different renderings of the mailbox that can drift apart — they are the same render, emitted twice.
afmail now runs one MiniJinja template system for generated Markdown, new scaffolds, and Review HTML, and every one of them consumes the same versioned, typed render documents. Rust’s job stops at the facts: a MessageRenderDocument carries the canonical message plus output-neutral analysis — segment offsets, direction, a suggested action — and never a formatted date, a translated label, an escaped string, or a table row. The templates own all of that: wording, localization, pluralization, formatting, escaping, markup, ordering, and layout. Review builds no HTML-specific copy of your data, so the human and the agent are provably looking at the same thing.
Templates you can edit — in your language
Because presentation lives entirely in templates, the templates are yours. Export the built-ins into the workspace and change anything:
afmail render templates # export editable templates into templates/
afmail render refresh # rebuild generated Markdown from local state
afmail doctor # compile + smoke-render every template
Language entry templates live under templates/<language>/ and shared layouts under templates/shared/. Complete built-in packs ship for en-US, de-DE, es-ES, fr-FR, ja-JP, ko-KR, pt-BR, zh-CN, and zh-TW, and workspace.language_bcp47 selects the closest one. An override can hide a fact, reorder the layout, add a partial, or replace an entry wholesale — and if an override is invalid, afmail reports the error instead of silently falling back to the built-in.
Review templates may bring HTML and CSS but never JavaScript — no inline handlers, no data: URLs, no safe output, no disabled autoescaping. afmail validates every source and the final composed HTML, forces autoescaping, serves under a nonce-bound CSP, and injects exactly one trusted, behavior-only runtime (autosave, submit, conflict handling) at a single marker you place before </body>. Suggested actions render fail-closed: only the known action set gets decision controls, and an unrecognized one shows unsupported-state copy rather than falling through to something destructive.
Under the hood
- Reply ordering. When a push both sends a reply and moves the message it answers, the send now runs first, so a reply is never stranded by a move that ran ahead of it.
- A typed provider API. afmail exposes its domain through its own typed OpenAPI HTTP API for trusted runtimes — no separate UI dependency in the loop.
- One-step migration. If a command returns
workspace_upgrade_required, runafmail initonce from the workspace root and retry. There’s no interim layout to maintain.
Install
brew install agentfirstkit/tap/afmail # macOS / Linux
scoop bucket add agentfirstkit https://github.com/agentfirstkit/scoop-bucket
scoop install afmail # Windows
cargo install agent-first-mail # any platform
After installing, run afmail skill install so your agent reloads afmail’s updated behavior rules.