---
title: Plain Speak — what you see (part 2)
---

# Plain Speak — what you see (part 2)

## What it is

This is part 2 of the [Plain Speak](plain-speak.md) page. It is the reader-facing half: what lands on screen and when.

## Where to find it

In the conversation, on any message carrying a rewrite.

## How it behaves

### What you see

You see Plain Speak in two places:

- **Settings → Plain Speak.** Plain Speak is its own section in Settings — in the **Accounts & AI** group, alongside Accounts, My API Keys, and AI Coach (it is no longer a tab inside an "AI Manager" panel). It opens with a single **Enable Plain Speak** master toggle at the very top — flip it off and the entire rest of the panel collapses away (only the toggle remains visible). With the master toggle on, the panel reveals a **Show Plain Speak first** toggle (agent messages open on the Plain Speak card instead of the original — **on by default** since 2026-08-25; turn it off to keep seeing the original first), a **rule card** describing what Plain Speak does (its title and a one-line description), and the **recent-activity feed**. It also carries a **skip-phrases box** (_Skip Plain Speak for phrases_) — a text area, one phrase per line: when an agent message contains any listed phrase (case-insensitive, any position), that message's Plain Speak card is skipped, so you can silence Plain Speak on messages you never want summarized (for example, add `[PR-MERGE` to skip the PR-prep merge cards). It defaults empty — nothing is skipped until you add a phrase — and, like everything else here, it is hidden when the master toggle is off. **As of 2026-07-20 (inline-only) the panel is deliberately minimal:** there is no pipeline picker, no "Agents write their own Plain Speak" sub-toggle, no **Cost & access** card (no daily cap, no API-key toggle — the inline card is free), and no _Model:_ line (there is no single backing model — the agent that wrote the message wrote the card). **And as of 2026-05-30 the rule card is read-only:** the prompt is locked to the built-in default, so there is no prompt textbox, no _Reset to default_ link, and no _Preview_ button. Inbox Pilot is a separate feature with its own sidebar tile (not a tab here); it keeps its own Cost & access card, model line, and editable prompt.
- **Inline on affected agent bubbles.** When Plain Speak applies, the agent's bubble shows a small pill button in the **message header row**, sitting **inline immediately after the timestamp** on both desktop and mobile (alongside the "Agent" label, the bot icon, and the timestamp itself). On **both desktop and mobile** it is the same quiet, equal-weight swap-arrows icon (`⇄`) — a bare icon that is grey while showing the Original view and tinted with the accent color only once an alternate view (Plain Speak / Narration) is active, so accent is spent only when it signals something. (Owner 2026-08-16, "make the desktop icon match mobile": the old desktop-only bordered accent chip was dropped, so the toggle now reads identically on both viewports.) The icon stays compact enough to sit next to the timestamp without colliding with the always-visible mobile three-dot menu at the top-right of the bubble. The bubble body underneath is the rewrite. Click the pill (or tap the icon) and the body underneath swaps to the agent's original markdown — formatted to match the regular agent-message render path; the swap-arrows icon stays on both viewports and its accent tint drops back to grey on the Original view. On a settled (non-streaming) turn that means intermediate **tool-call activity pills are hidden** in the body; the totals pill that already sits above the bubble (e.g. "> 74 actions —") summarises every action, and re-rendering each "> N actions —" pill inline between paragraphs would just be visual noise. The agent's prose narration still renders. (On a still-streaming turn the activity remains inline, matching the streaming render path.) Click again to flip back to Plain Speak. You can also press **V** to flip the latest agent message that has a Plain Speak rewrite — same effect as clicking the pill, with no need to mouse over to the message. Hovering the pill (desktop only) shows a tooltip with the action and its **V** shortcut drawn as a key-cap — the tooltip reads _Switch to Original_ with a small **V** key-cap after it (the key is shown once, as a cap, not also spelled inline as "(V)") — and the cap tracks the binding live if you rebind it. The pill's `aria-label` reads "Switch to Original" / "Switch to Plain Speak" depending on the current view, and `aria-pressed` reflects whether the rewrite is showing — both of these stay attached to the icon-only mobile variant so screen readers announce the same labels on both viewports. The default view when the rewrite first arrives is the **Plain Speak card** (2026-08-25; it was Original from 2026-07-19) — the message opens on the card and the original is one pill-click away (the pill reads _Switch to Original_ until you click it). You can flip this default per-user with the **Show Plain Speak first** toggle in Settings (2026-07-20): turn it **off** and messages open on the original instead (the pill then reads _Switch to Plain Speak_). Either way a per-message pill / **V** flip always wins over the default. The bubble's three-dot menu has **Copy Markdown** and **Copy Formatted** entries; both always copy whichever view is currently shown — copy the rewrite while you're looking at it, flip to Original and copy that instead. The bubble is also expandable to show the model used, tokens in/out, the per-call cost, and a **View in decisions log** link that jumps to the full audit row. **Right-click the pill** (desktop only — there is no right-click on mobile) opens a small one-item context menu, **Plain Speak settings**, that jumps straight to Settings → Plain Speak (the same panel the opt-in-reset banner's "Open Plain Speak settings" button opens). Left-click still flips the view; right-click never toggles it.

  **The toggle stays pinned while you scroll on mobile (2026-08-16).** On a phone, when you scroll down through a long agent message, the ⇄ toggle (and the ▶ narration control beside it) float and stay glued to the top of that message instead of scrolling away — so you can flip between Plain Speak, Original, and Narration without scrolling back up — then drop back into place at the message's end. While they float, **just the icons** show — no pill or box behind them (refined 2026-08-17: "I want just the icon"); a soft background-colored halo keeps them legible over the body text, and the float is GPU-composited (`will-change: transform`) so it stays smooth. Desktop is unchanged (its `⋯` menu already pins). Mechanics: `useMessageHeaderPin` writes a paint-only `transform` (CSS `position: sticky` is unsafe in the virtualized conversation scroller), reusing the same pin primitives the QuestionWidget header uses; it is a read-only scroll consumer that never moves your scroll position. **They never sit on top of a question widget (2026-09-10).** If that message is asking you something, the floating icons park just above the question card and then scroll away with the message — they are never parked over its buttons where they could swallow a tap. See `frontend-scroll-contract.md`.

  **Dwell preservation (still dormant — it pins whatever the resolved default already is).** The mechanism below pins the RESOLVED default at rewrite-arrival, which by definition already matches what the reader is showing, so it stays a belt-and-suspenders no-op whichever way the default points (Original 2026-07-19 → 2026-08-25, the Plain Speak card since). Historically, when the default view was Plain Speak: rewrites can take 60–180 s under load, and a common path was a green (running) session already open with the agent's original markdown rendered, the user mid-paragraph, the rewrite arriving, and the body abruptly swapping from the original to the 5-section card — yanking the user out of their reading. To prevent that, at the exact moment the rewrite arrives for a given message, Omniscio checks four conditions: (1) the user has not already chosen a view for that message, (2) the message's session is the currently active one, (3) the window is visible (not minimized / backgrounded), (4) the message's bubble is in the viewport (not scrolled off). If all four are true, the per-message view auto-claims **Original** so the body keeps showing the markdown the user was reading. The check runs synchronously between the store mutation and React's commit, so there is no one-frame flash of Plain Speak before the view settles. If any of the four conditions is false (panel hidden, window backgrounded, bubble scrolled out of view, different session active, or the user already toggled the view), the default behaviour stands and the body shows the new rewrite — that's the intended attention signal for messages the user wasn't actively reading. The pill and the V hotkey still work the same way in both cases; only the auto-swap on arrival is suppressed.

  **QuestionWidget answers in Plain Speak view send the verbose format.** When you submit answers from a QuestionWidget that rendered out of the Plain Speak rewrite (`## Questions` block in the rewrite, view is `'plain'`), the response sent to the agent expands `1. A` into the **full question text + the user's full answer text**, with a header explaining the questions came from the Plain Speak overlay and were paraphrased (the agent never saw the rewritten text verbatim). When you flip to **Original** first and submit from there, the response stays terse — `1. A  2. B` — exactly as it always has. Format and edge cases (skipped questions, all-skipped fallback, multi-pick, custom answer, free-form Yes/No) are documented under [question-widget.md § Send your answer](question-widget.md).

  **A question the card dropped still shows, and empty "None" notes are hidden (2026-08-07).** The agent writes its own card, and occasionally it leaves the question out even though its original message asked one — which would leave the plain card with no way to answer. When that happens, the plain view now surfaces the **original message's own question widget** directly beneath the card: the exact same widget (same question text + lettered options) you'd see by flipping to **Show Original**, and answering it sends the same terse response as answering from Original (the agent wrote it verbatim, so it isn't treated as paraphrased). The fallback appears only on the **most-recent** agent message — the one row where an answer is actionable; on older, already-answered rows nothing is resurfaced. Conversely, when neither the card nor the original has a real question AND the card's `## Questions` area merely **leads with** "None" / "No questions" (even with trailing filler, e.g. "None. Everything is done."), that whole area is hidden instead of rendering the filler as prose. A card that carries a real, answerable question is **never** hidden — only empty "None…" notes are. Mechanics: `PlainEnglishOverlay` reconciles the card's Questions against the original; invariant `the-plain-view-plain-english` in `plain-speak-overlay-gating-contract.md`.

**Snippet override (sticky, per-message):** when you send a Quick Reply snippet that has the **Override Plain Speak "Latest"** toggle on, the `## Latest` line of the agent's reply to that turn displays the snippet's configured override text instead of the model's recap. The other four sections still come from the model. The override is **stamped on a single agent message** (the final non-aside agent message of the turn responding to the snippet) and **stays there permanently** — it's part of that message's history, not a session-wide flag. Subsequent agent messages (after another operator send, or any later turn) render the normal Plain Speak rewrite with no override. Scrolling back later, the original turn still shows the override text on its `## Latest`. See [quick-replies.md § Per-snippet effects](quick-replies.md#per-snippet-effects-auto-title-plain-speak-override-auto-tags).

**One-time opt-in-reset banner.** If the retired 2026-05-16 default-on migration had switched Plain Speak on for your install, the first launch on or after 2026-05-25 shows a one-time sky-tinted banner across the top of the app: _"Plain Speak is now off by default — it only runs if you turn it on yourself. We reset it so you're in control; you can re-enable it anytime."_ It carries an **Open Plain Speak settings** button (jumps straight to Settings → Plain Speak and dismisses the banner) and an **×** dismiss button. Either action clears the banner permanently — it never reappears, even across restarts. Fresh installs, and anyone who never had Plain Speak on, never see it. The banner is purely informational; it changes no settings on its own — flipping Plain Speak back on is a separate, deliberate click in Settings. **Historical as of 2026-08-25:** the force-off migration that raised this banner was retired with the default-ON flip, so nothing sets the flag any more. The banner + its dismiss button stay wired only for an install that was reset back then and never dismissed it.

### Safety behavior

Plain Speak is built around two protections against the model misleading you:

- **Errors and blockers are surfaced, not hidden.** The system prompt's `Recommended Action` rules force the model to pick the highest-priority phrase from a fixed list — **Unblock the agent** > **View original** > **Answer questions** > **Make a decision** > **Commit locally** > **Push to remote** > **Update documentation** > **Understand then archive** > **Archive** > **Wait** — so a real blocker, approval gate, or decision request always wins over a "still working on it" status. The two closing rungs are gated on top of that ordering: they claim the session is finished and can be closed, so an agent may not pick one while the conversation is live (you just asked it something and will react to its answer, it still owes you a result, or it asked you something) — there, and any time no rung honestly fits, the action is **None** and the line simply hides, rather than falling back to Archive because the turn ended. Measured on this board before the gate landed, 37.6% of all cards carried an archive rung and 57.8% of those were written inside a live exchange. Destructive / shared-state approval gates ("OK to push?", "Confirm before I delete?", "Approve merging into master?") route through **Answer questions** with a synthesized Yes/No question that names the specific action — there is no separate "Approve or deny" rung, since a bare "Approve or deny" never told you what you'd be approving. The prompt also instructs the `Response` body to lead with errors, blockers, or unfinished work when relevant. There is no "everything is fine" failure mode — if the agent says it broke, the Recommended Action is **Unblock the agent** and the body explains what broke. **View original** sits second in priority and applies when the agent's reply IS the deliverable (an essay, a draft email, a long-form prompt, a code block the operator asked for) — the summary cannot substitute for the artifact, so the action is to toggle to the original. (As a defensive backstop, Omniscio also still recognizes a literal `ORIGINAL_ONLY` token if a model emits it and falls back to the original message, but the active prompt no longer instructs the model to do so.)
- **The transcript is treated as untrusted data.** The envelope wraps the entire conversation transcript in `<context untrusted="true">` and the system prompt instructs the model to treat the contents as data, never instructions — so an embedded "ignore previous instructions" line from the agent or operator is summarised as text rather than executed. JSON content blocks from the agent are unwrapped to plain text before being placed inside the envelope, so a tool-call payload cannot smuggle structure into the prompt.

Plain Speak also skips messages shorter than 80 characters (no point rewriting a one-line response). It additionally skips a **status or wait** turn-end so a terse deferral never gets a 5-section card: **(A)** if the message contains the **official auto-wait sentinel** ("Waiting on a background process to finish — I'll continue once it completes.", the exact line the harness instructs agents to emit when purely waiting) it is skipped **unconditionally, regardless of length**; **(B)** any other fuzzy auto-wait phrasing or a background notification ("Tests green (exit 0). Build still running.") is skipped only when it is **short — under 300 characters of the agent's actual prose**. Tool-activity (`▸`/`←`) lines do **not** count toward that length, so a short wait wrapped in tool scaffolding still skips (measuring the raw row with its tool clutter is the bug this avoids). A long message that merely _ends_ in a non-sentinel wait, and any normal answer, still get rewritten (see _While the rewrite is computing_ below). Output length scales to the source message — short messages produce short Response bodies, long messages get more bullets — and the rewrite is capped at 4,000 characters total.

### While the rewrite is computing

When an agent message ends and the session would normally show **Needs You**, Omniscio holds the alert back until Plain Speak's rewrite is ready. The point: the user shouldn't see the unrewritten original first, glance away, and miss the rewrite that arrives a second later.

Concretely, while Plain Speak is computing the rewrite for that turn:

- The session does **not** appear in the unified inbox's **Needs You** group.
- The session does **not** appear in the project sidebar's per-project **Needs You** subgroup (desktop and mobile).
- The session does **not** count toward the project sidebar's amber attention-count badge next to the project icon.
- The session does **not** appear in the AI Coaching sub-sidebar's **Needs You** group; it stays under **Active** until the rewrite renders.
- The **tray badge** count does not tick up for this session.
- The **OS notification** ("Agent Needs You" toast) does not fire.
- The **chime** sound does not play.
- The session's **status indicators stay green** ("Running"). The sidebar dot, the session panel's left border, the mobile status dot, and the unified-inbox row's color cue all keep reading as Running until the rewrite renders, then snap to Needs You (amber) at the same moment the inbox row, badge, chime, and OS toast catch up. The same override applies even if the underlying status sub-state would normally be `question`, `plan_approval`, `permission_request`, `rate_limited`, `api_error`, or `auth_error` — the user explicitly chose this aggressive scope so the visual cue matches the held-back attention signal exactly. (`user_stopped` is **not** in this list — see below: Plain Speak skips user-interrupted turns entirely, so there's no rewrite-pending window to gate.)

Snooze and scheduled-response take precedence: a session with `snoozedUntil` or `scheduledResponse` set never contributes to any of the above counts, gated or not. The overlay-pending re-route only kicks in when neither is set.

The agent's message itself still arrives normally — the message body streams into the chat thread the moment the agent finishes its turn. The only thing that's deferred is the cross-app attention signal (inbox row, badge, chime, OS toast). The user can still open the session by hand and see the original body during this brief window.

The rewrite typically arrives within a few seconds. Once it does, Omniscio re-fires the deferred attention signal: the session enters the inbox, the tray badge updates, the chime plays, and the OS notification appears — with the bubble already showing the Plain Speak rewrite. If the user has already opened the session and replied during the LLM round-trip (status flipped from `needs_you` to `running`), the deferred re-fire is suppressed: there's nothing to alert about anymore.

The gate is **bypassed** (session enters the inbox immediately, no waiting) when Plain Speak is structurally unable to rewrite this turn:

- Master toggle is off (`overlay.enabled === false`)
- Per-session toggle is off
- The session was **spawned with `suppressOverlayPrompts`** — the opt-out shared by a CLI spawn, a locked AI reviewer, and the "answer in a box" projects (Session Search and Ask Omniscio). It gates every door, not just the prompt: the Plain Speak card directive is never injected, so the agent writes no card at all; no card is applied for the session either, so this gate never engages and no paid rewrite runs; and a card the session already carries is not displayed — the message reads as the original. (Distinct from the per-session toggle above: that gates the runtime pass for one session and the user can flip it, while this one is fixed when the session is created.) See `cli-server-gating.md` and `plain-speak-overlay-gating-contract.md` `the-session-opt-out-gates-the-apply-and-the-render`.
- Daily cost cap is reached
- Message is shorter than 80 characters
- **Turn ended on a deferral — the official auto-wait sentinel (any length) or a SHORT non-sentinel wait / background notification.** The exact harness sentinel ("Waiting on a background process to finish…") is skipped no matter how long the turn is; any other "waiting on a background process" hold or a "tests green (exit 0), build still running" status line is skipped only when the agent's **prose** is under 300 characters (tool `▸`/`←` lines are not counted toward the length). Omniscio classifies these with the same detectors that drive the ⏱ Auto-waited badge and the background-ack collapse (`matchCanonicalSentinel` / `detectSuppressibleWait` / `detectBackgroundAck`) and writes a `skipped` audit row (reasoning `"auto-wait or background deferral"`), so the message shows plain with no Plain Speak pill. A LONG message with no sentinel that merely ends in a wait still gets its card, and a wait directed at **you** ("Waiting on your approval") is never treated as a background deferral, so it keeps its overlay.
- Account is API-key without the **Allow on API-key accounts** override
- **Turn ended on a user-initiated interrupt (Esc / Ctrl+C → `pendingAction === 'user_stopped'`).** The latest agent message in this case is the streaming accumulator finalized at exit — a partial mid-stream cutoff, not a real final turn — so summarising it as a polished 5-section card would be misleading. Plain Speak writes a `skipped` audit row with reasoning `"user-interrupted turn"` and lets the chime, badge, inbox row, and OS notification fire immediately. Inbox Pilot (the sibling router) is unaffected and still classifies the turn if enabled.
- **Turn ended in a rate-limit, auth-error, or API-error state (`pendingAction === 'rate_limited'` / `'auth_error'` / `'api_error'`).** The agent's last message in these states is (or contains) the literal error notice the user must read and act on — paraphrasing it as a 5-section card adds friction and can obscure the actual error text. Plain Speak writes a `skipped` audit row with reasoning `"rate_limited turn"`, `"auth_error turn"`, or `"api_error turn"` (whichever matched), the chime / badge / inbox row / OS notification fire immediately for the error, and the agent bubble renders the original markdown with no Plain Speak / Original toggle pill. Inbox Pilot still classifies the turn if enabled.
- **Turn is parked for Omniscio to resume on its own.** This covers three cases. The agent hit an error before its final answer, and the automatic recovery replays the turn within about a minute (`pendingAction === 'response_aborted'`). An API-key provider ran out of credit, and the balance check resumes the session once it is funded (`'balance_parked'`). Or the app closed mid-turn and the session resumes on the next launch (`'suspended'`). Plain Speak neither writes a card for such a turn nor asks the agent for one, and writes a `skipped` audit row with reasoning `"<action> turn"`. Asking would take the turn meant for the recovery: before 2026-09-24 the agent answered the card request instead, the replay never came, and the session sat idle in your inbox showing the error. The replayed turn gets its card normally when it finishes.
- **The turn is a dev-pipeline GATE REPORT** — a message that leads with one of the six byte-exact gate headers (`## 🔍 🟢 Plan Ready`, `## ⚔️ 🟢 Red Team Complete`, `## 🔨 🟢 Build Complete`, `## 💎 🟢 Elegance Pass Complete`, `## 📝 🟢 Docs Complete`, `## 🚀 🟢 Ready to Merge`, including the `🟡` "needs you" / `🔴` "blocked" status variants). These cards are deliberately formatted for a human — header + emojis + a "what shipped" structure — and Omniscio's renderer already lifts them header-first (`gate-report-preamble-contract.md`); rewriting one into the generic 5-section card would discard that format. So `evaluate()` skips the overlay (GATE 4.6) via `isGateReport()` (`src/shared/gate-header-grammar.ts`) and writes a `skipped` audit row, reasoning `"dev-pipeline gate report"`. **The check runs on the agent's _derived final prose_** — the same text the renderer shows as the message body, via `getLatestAgentGateReportText` → `deriveAgentProse` — **not the raw stored turn.** A stored turn prefixes the card with the turn's tool-activity/thinking lines (`▸`/`←`), so reading the raw text would bury the header dozens of lines down and silently disable the skip (a live backtest measured 0 of 82 real cards detected on the raw text vs. 81 on the derived prose). The detector is precise: it fires only when **exactly one** gate header **leads** that final prose (≥2 ⇒ a quote or discussion, none ⇒ not a report), tolerating a short non-structural bookkeeping preface and a header the agent wrapped in backticks. Unlike the short-deferral and content skips, this one runs in `evaluate()` **before** the needs-you alert is held back (like the error-state skip above) — a gate card is a needs-you message, so its chime / badge / inbox row fire immediately and are never deferred. This `evaluate()` skip of overlay-TEXT generation is UNCHANGED (no paid rewrite for a gate report). **A gate report gets NO PAID and NO MACHINE-SYNTHESIZED overlay** — owner directive 2026-07-22, reversing a 2026-07-19 experiment that synthesized one; the client-side per-phase synthesis is RETIRED and must NEVER be rebuilt. **But it is no longer a blanket skip** (superseded 2026-07-23, one day later): the skip branch now calls `applyGateReportInlineCard`, which takes the agent's OWN `[[AMC_PLAIN_SPEAK]]` card (the one it wrote below the `[DEV-PIPELINE | …]` marker) and applies it FREE through the shared `applyInlineCard`, persisting `overlayText` — which is exactly what makes the pill render. So the bubble DOES carry a Plain Speak ⇄ Original pill, Plain Speak shows the agent's card, and the original view keeps the native gate report (header + the pipeline stepper the renderer already draws). No valid inline card ⇒ a `skipped` `'gate report: no inline card'` row and the native render only. See `plain-speak-overlay-gating-contract.md` `evaluate-gate-a-dev-pipeline`/`dev-pipeline-gate-report-shows` and _Dev-pipeline gate reports_ below. Inbox Pilot still classifies the turn if enabled.
- **The turn matches one of your Skip-phrases** (see _What you see_ → the _Skip Plain Speak for phrases_ box). If you've listed phrases there and the agent's message contains any of them, Omniscio skips the overlay (GATE 4.7) and writes a `skipped` audit row, reasoning `"user skip pattern"`. The match is a case-insensitive substring anywhere in the message, with the agent's own inline card stripped first so it can never self-trigger. Like the gate-report skip it runs **before** the needs-you alert is held back, so the chime / badge / inbox row fire immediately — the skip only hides the Plain Speak card, never the attention signal. An empty box (the default for every install) has no effect. This is the per-install, config-driven sibling of the built-in gate-report / deferral skips — e.g. `[PR-MERGE` silences the PR-prep merge cards. Its behaviour is pinned by `evaluate-gate-skips-the-overlay` in `plain-speak-overlay-gating-contract.md`. Inbox Pilot still classifies the turn if enabled.

If the rewrite never returns within **5 minutes** (LLM hang or process death), the gate releases automatically and the session enters the inbox normally. This is a defensive backstop — under normal conditions the rewrite arrives in seconds, but the OpenRouter Qwen cascade can take 60–180 s under load, so the cap is wide enough to cover real slow-but-working runs without firing prematurely. The gate also clears immediately on rewrite **failure**: a failed rewrite still removes the gate so the user is never stuck waiting on a dead call.

## Related

- [Plain Speak](plain-speak.md) — part 1, and how the card reaches you.
- [Plain Speak part 3](plain-speak-part-3.md) — the gate reports and the cost controls.
- [agent-message-display.md](agent-message-display.md) — how the rest of a reply renders around it.

