#Conversational Approvals
Most approvals are yes/no. But when an AI wants to send something — an email, a reply, a summary, a support response — a plain Approve/Reject is the wrong shape. The reviewer doesn't want to bounce it back; they want to tweak it and send it.
Conversational Approvals let you attach the AI's proposed content to a decision as an editable draft. The reviewer refines it in conversation — in the dashboard or by replying in the Slack thread — and your workflow resumes with their final version, not the AI's first draft.
It builds on the same human-in-the-loop decision — a proposed draft is just a decision that carries an editable artifact.
#1. Propose a draft
Pass proposedOutput when you open the decision. Its presence makes the decision refinable: the reviewer can rewrite it before approving.
const decision = await flowplane.awaitApproval(ctx, { id: 'send-welcome-email', assigneeEmail: 'cx@acme.com', prompt: 'Review the welcome email before we send it.', proposedOutput: { title: 'Welcome email', // a short label the reviewer sees content: draftEmailBody, // the editable text },}) if (decision.approved) { await sendEmail(decision.output.content) // the reviewer's final version}
decision.output.content is the effective text — the reviewer's edit if they made one, otherwise your original draft. Always act on output, never on the draft you sent.
proposedOutput is distinct from context. context.blocks is read-only — the prompt, the diff, the cost the reviewer sees. proposedOutput is the one thing the reviewer can change.
#2. The reviewer refines it
The reviewer shapes the draft through a short back-and-forth. Each turn is applied by a model and updates the draft in place; they can iterate until it's right, then approve.
- In the dashboard — edit the draft by hand, or click Refine with AI and give an instruction ("make it warmer", "add the invoice number").
- In Slack — the decision posts the draft in a channel; reply in the thread with your instruction and the revised draft is posted straight back. Keep replying to keep refining, then click Approve. See Approvals in Slack.
Every turn is kept as a refinement log, so continuity instructions work ("go back to the warmer version").
#3. The model
Refinement runs on a model you control:
- Bring your own — configure your org's provider + key in Settings → Refine with AI (Anthropic, OpenAI, or any OpenAI-compatible endpoint — self-hosted, VPC, Azure). Your content only ever reaches the model you chose.
- Platform default — if your org hasn't configured one, refinement uses the platform's default model out of the box. A configured BYO model always takes precedence.
#4. Resume with the final text
On approve, the engine resumes with refinedOutput ?? proposedOutput — the reviewer's version if they touched it, else your draft. For the SDK, that's decision.output. An agent polling over the MCP or the worker API reads the same value from the decision's output field.
A reject needs no draft — it's a decline, and output is null.
#Via the MCP
Agents using the Ratifia MCP request a refinement with proposed_output:
request_decision( title: "Send welcome email", question: "Review this before I send it.", proposed_output: { title: "Welcome email", content: "<draft>" })
When it resolves, await_decision / check_decision return the final text in output.content (with a refined flag telling you whether the human changed it). Act on output.content, not the draft you sent.
#Requirements
- The decision must carry a
proposedOutputto be refinable — plain approve/reject decisions are unaffected. - Refining in Slack requires the workspace's Slack app to have Event Subscriptions enabled (thread replies are delivered over Slack's Events API, not the button-click channel). See Approvals in Slack.
- A model must be available — your BYO model or the platform default.