---
title: Waiting detector
---

# Waiting detector

## What it is

When a Claude Code agent finishes a turn, Omniscio has to decide: _did the agent actually stop and need you, or did it just describe waiting on something it set in motion?_ If the agent's last text is `"Waiting on lint to finish"`, `"I'll continue once tests pass"`, or `"Standing by for the deploy notification"` — and the agent didn't invoke any tool to schedule its return — older Omniscio builds would flip the session to **Needs You** and drop it in your inbox, even though the agent isn't really done. That's a spurious interruption: you go check the session, find nothing for you to do, and the agent eventually keeps working anyway.

The waiting detector closes that gap. When the agent ends its turn with prose that matches a high-confidence "I am waiting" pattern, Omniscio keeps the session in **Running** state for the **waiting period you set** (default **30 minutes**, adjustable in Settings → Sessions → "Waiting period") instead of flipping it to your inbox. A compact orange **⏱ Auto-waited** tag appears on its own small line directly above the body of the agent reply that tripped the rule (the same idiom as the folded-activity expansion) so you can still see at a glance that Omniscio chose to keep running; hovering the tag reveals which rule fired and the exact phrase it matched. Any fresh agent activity (a new tool call, more output) resets that window. If the whole window passes with **no new activity at all**, Omniscio surfaces the session to your inbox — it flips to **Needs You** (reason `wait_timeout`) so a genuinely stuck wait always reaches you within one window, rather than being held any longer.

**Prose is the fallback — the agent's TOOL choice is the first and strongest signal.** When the agent actually *starts* background work (a backgrounded command, a subagent, a `Monitor` watch, or a **research run** via the `Workflow` tool), Omniscio does not have to guess from the wording at all: it recognizes the dispatch and holds the session quietly. Since 2026-09-01 that recognition is one shared rule covering any tool that declares itself background, so a new background-capable tool works automatically. This is what stopped **research chats landing in your inbox just to say "the research is running"** — the `Workflow` tool was missing from the old per-tool list, so every research turn fell through to the prose guess.

**A backgrounded command wakes the agent by itself — no Omniscio timer is involved (2026-09-16).** When an agent runs a command with `run_in_background: true` (or the harness moves a slow foreground command to the background), Claude Code itself re-invokes the agent the moment that command exits. Omniscio's ETA check-in never fires early in that case — it is disarmed because a genuinely new turn started — but every such re-invocation is a full turn. A session that backgrounds short commands (a `git rev-parse`, a status probe) and closes each turn on the waiting sentence therefore shows a long run of **⏱ Auto-waited** rows seconds apart: that is the CLI waking it, not the detector misfiring. The instruction agents receive now says so and tells them to keep short commands in the foreground. Measurements and the log trail: [`2026-09-16-cli-self-wake-loop-backgrounded-short-commands`](/.claude/memory/waiting-detector-ledger/2026-09-16-cli-self-wake-loop-backgrounded-short-commands.md).

**The detector is ON by default** since 2026-05-27. It was opt-in from 2026-05-23 through 2026-05-26 while the rejection-guard + pattern tuning work (length cap, trailing-wait shape carve-out, six-regex user-directed veto, completion-clause broadening) closed the false-positive classes seen in the live-DB back-tests; with that work done the default flipped on. Turn it off in Settings if you want to revert to the legacy flip-to-inbox behaviour — every Gate 6 firing carries an inline **⏱ Auto-waited** tag on the agent's reply naming the rule that matched, and Settings → Diagnostics → Waiting Detector activity is the full audit log.

## Where to find it

Waiting detection is not a screen of its own — it runs wherever sessions run, so you meet it in the session chat and in your inbox. In the **chat**, the orange **⏱ Auto-waited** pill appears on its own line directly above the agent reply that tripped the rule (hover it for the rule that fired and the phrase it matched), and the **Keep waiting** and **Check in** buttons sit right above the message box while a session is waiting or parked. In the **inbox**, a wait that times out arrives as a **Needs You** row whose right-click menu carries **Keep waiting** and **Check in** — desktop only; on a phone, open the session to reach the same actions.

Everything you configure lives in **Settings → Sessions**: the waiting period, the **Keep-waiting button duration**, and the toggles for **Detect waiting agents**, **Hide repeated auto-waits**, **Count repeated auto-waits**, **Auto check-in on waiting sessions**, **Also check in after it's in your inbox**, **Confirm ambiguous waits before interrupting** and **AI second-opinion on waiting detection**. The firing audit log is **Settings → Diagnostics → Waiting Detector activity**, which ships hidden behind a performance flag.

## How it behaves

### Works on every engine — not just Claude (2026-08-11)

Everything else on this page describes the detector on the **Claude** turn path. As of 2026-08-11 the same core idea — detect the keep-alive sentinel at turn-end, HOLD the session **Running** (out of your inbox), auto-NUDGE it when the window elapses, and SURFACE it only if genuinely stuck — also runs for **every non-Claude engine** (Codex, Gemini, Cursor, OpenCode, GLM, DeepSeek, and the rest). Before this, only Claude sessions auto-waited; a Codex/etc. turn that ended on the sentinel flipped straight to **Needs You**, so you had to nudge it by hand and it would just re-park — that's the loop this closes.

It's a self-contained sibling ([external-auto-wait.ts](/src/main/services/waiting/external-auto-wait.ts)) wired once into the shared turn-end chain every non-Claude engine concludes a turn through ([turn-end-gates.ts](/src/main/services/turn-end/turn-end-gates.ts)), and it REUSES the same detection, nudge prompt, caps, and **⏱ Auto-waited** banner as the Claude path — so a held external session reads identically in chat. Key points:

- **Same controls.** It honors the master **Detect waiting agents** setting; a question always surfaces (never held); and the nudge + surface are bounded by the same max-consecutive-nudge cap plus an absolute ceiling, so a stuck external session always reaches your inbox (reason `wait_timeout`) and can never loop forever. Only a genuine reply from **you** clears a hold — an automatic nudge cannot.
- **v1 scope = the explicit sentinel.** The external path covers the canonical keep-alive sentence (and the high-precision corpus). The Claude-only refinements below — the AI second-opinion (Stage 2), the Tier-3 confirmation probe, and false-done lost-thread recovery — are **not** ported yet; the explicit sentence every engine is instructed to emit is fully covered.
- **A message from another agent can't cost it the wait (2026-09-22).** When a held session receives a message from one of your OTHER agents, it takes a fresh turn — and if that turn folds the exchange away (the agent decides the exchange is machine traffic you have no reason to read, so chat shows one quiet **▸ Agent exchange** line), the wait **resumes** where it left off instead of landing in your inbox early. A turn that puts something readable in front of you still surfaces, and a turn that asks you something always does. Only the remaining part of the original window is resumed, so it can never stretch past what the agent declared.
- **Off-switch.** Env kill switch `AMC_DISABLE_EXTERNAL_AUTO_WAIT=1` disables the external path only (the master setting + `AMC_DISABLE_WAITING_DETECTOR` still govern Claude). Mechanics + invariants: [external-auto-wait-contract.md](/.claude/memory/contracts/external-auto-wait-contract.md) (EAW-1..9).
- **The guarantee above was silently broken, and is restored (2026-09-29).** When the rules were merged onto the one shared turn-end chain, the chain's engine filter was handed each engine's *display label* (`'Codex'`, `'[Antigravity]'`) where it expected a provider id (`'codex'`, `'antigravity'`). The id lookup is an exact match, so a label came back "not a known engine" and the chain read that as Claude — which skipped every external rule on every non-Claude engine. External auto-wait therefore never once ran: a Codex session that emitted the keep-alive sentence went straight to **Needs You**. Claude, DeepSeek, GLM and the other Claude-binary engines were unaffected, which is why it went unnoticed. The chain's engine field is now a typed provider id, so a label is a compile error, and a guard test refuses any conclude site that passes one. If you saw an external session ignore the sentinel before this date, that is why.

### How to use it

#### The default behavior

The detector is **on** by default. Every turn-end whose final assistant prose matches a high-confidence waiting pattern (and clears the question check + three rejection guards) keeps the session in **Running** state for the configured waiting period (default 30 minutes) instead of flipping to **Needs You** — the inline **⏱ Auto-waited** tag appears on its own line directly above the body of the agent reply that fired.

#### Turn it off (if you want the legacy behaviour back)

**Settings → Sessions → Detect waiting agents** (toggle, on by default since 2026-05-27). Flip it off to revert to the pre-2026-05-23 behaviour where every turn-end flips the session to **Needs You** regardless of waiting prose. When on (the default), the agent's final text is scanned on every turn-complete. If a high-confidence waiting pattern matches AND the agent did NOT ask you a question AND the prose clears the three rejection guards (see "Three rejection guards" below), the session stays running and the **⏱ Auto-waited** tag appears on the agent's reply naming the rule that fired (on hover).

Behind that tag, Omniscio still writes one reliable persistent record — a real `notable-system` system message stored on the session's `conversation_messages` row, queryable for analysis (every entry's text starts with `"Agent appears to be waiting"`, and the rule id + matched phrase are stored in the row's metadata). The chat just renders that row as the compact tag instead of as a full prose line; nothing about the stored data changed. A parallel diagnostic line `Gate 6: prose match pattern=<id> matchedText="..."` is also written to `main.log` (the standard electron-log file) at `verbose` level — only present when you have verbose logging on, which is off by default. To audit firings across sessions, use **Settings → Diagnostics → Waiting Detector activity** (below) or the SQLite DB directly.

If the agent asks you a question (text ends in `?`, or a multiple-choice widget shape is detected), the question always wins — the session flips to **Needs You** as it would have without the detector. You will never be silently held back from a real question.

**A question the agent answers itself is not a question for you.** Agents often head a section of a report with a question and then answer it — "## Is it affecting other agents?" followed by the answer. That is a section title, not something waiting on you, so it does not cancel the wait. Two rules keep the two apart: a markdown heading ending in `?` over a list is never read as an ask, and a message that *closes* on the agent's waiting sentence keeps waiting even if a question appeared earlier in it. A message that actually **ends** on a question — however it is formatted — still flips to **Needs You**, and a real multiple-choice widget always does.

#### Emergency env override

For when Omniscio won't start or the setting is somehow stuck on, launch with:

```
AMC_DISABLE_WAITING_DETECTOR=1 npm run dev
```

This is read once at startup and short-circuits Gate 6 unconditionally regardless of the setting state.

#### What you see in chat

When the detector fires, a small orange pill (the waiting orange — the same shade as a timed-out wait, so it reads as the same "waiting" signal as the session status) is attached **to the specific agent row whose prose actually tripped the rule** — not to the merged-turn header by default. The pill reads:

```
⏱ Auto-waited
```

Hover it and a tooltip spells out why Omniscio kept the session running:

```
Auto-kept running — agent appears to be waiting
Pattern: waiting-for-on-any · explicit-hold
Matched: "waiting for completion notifications"
```

Three pieces of information so you can judge whether the suppression was correct:

- **Pattern `<patternId>`** — the entry in [`waiting-patterns.ts`](/src/main/process/waiting-patterns.ts) that matched (e.g. `waiting-for-on-any`, `i-am-waiting`, `standing-by`).
- **`· <category>`** — the broad bucket the pattern belongs to (`explicit-hold`, `future-resume`, `state-marker`, `temporal-anchor`, `proxy-notification`).
- **Matched: `"..."`** — the literal substring the regex captured.

**Where the pill lives depends on which row fired the rule** (per-message anchoring, 2026-05-27; tag moved body-adjacent 2026-05-28):

- **Firing on the visible final reply** — the pill sits on its own small line directly above the agent reply's body prose (the same idiom as the folded-activity case below), **not** in the merged-turn header. This is the most common case: the agent's last paragraph contained the wait declaration, the merged-turn shape shows that paragraph as the visible body, and the pill sits right above it so you read the tag and the prose that tripped it together.
- **Firing on a folded activity row** — when Omniscio's per-turn merge folds the agent's tool calls and intermediate notes into the bubble's collapsible activity expansion, and the firing row is inside that fold, the pill is **hidden until you expand the activity**. Click the bubble's activity header to open the panel and the pill appears adjacent to the specific row that fired. The visible final reply stays clean — anchoring the tag there would mis-attribute the wait to a row whose visible prose contained no waiting language. This case is most common when the agent prefaces a long tool sequence with a wait declaration (_"Waiting on the deploy notification — pulling the build log in the meantime…"_) and then writes a clean status close in the final reply.
- **Older firings (before 2026-05-27)** — older cache rows that lack the per-message anchor field fall back to the visible final reply on first read (the tag renders above that reply's body prose, the same as a visible firing), then rebuild with the correct anchor on the next conversation reopen.

The pill replaces the old standalone "Agent appears to be waiting…" banner bubble that earlier builds dropped into the conversation as its own grey message — same information, far less visual noise. **Older firings from before the inline-tag conversion still render as that standalone banner line** (they predate the structured metadata the tag reads from), so a long-lived session may show both shapes; that's expected. The underlying `notable-system` row and the Diagnostics audit panel (below) are identical for both shapes.

If you see a pill where the matched phrase isn't actually a wait declaration, that's a pattern-library bug — file it with the session id, the rule id, and the matched substring. (Hits are pattern-based by default; an **opt-in** AI second-opinion layer — off by default — can additionally veto a false pattern hit and catch waits the patterns miss. See the **AI second opinion (Stage 2)** section below.)

#### Hide repeated auto-waits (Settings → Sessions, opt-in)

A session that waits over and over — polling a deploy, running a `/loop`, re-checking a long build — stacks up one **⏱ Auto-waited** message per wait cycle. Turn on **Settings → Sessions → "Hide repeated auto-waits"** (`hideAutoWaitedUnlessFinal`, **default off**) to collapse all but the latest of them into a one-line `⏱ Auto-waited — click to expand` marker, so you only see the current wait state.

**One message folds on its own, no toggle needed (A9).** The canonical **"Waiting on a background process to finish — I'll continue once it completes."** keep-alive message — the exact phrase Omniscio instructs agents to emit when they're blocked on something they started — always collapses once a newer reply follows it, even with this setting off. It's an unambiguous "still busy" marker, so you never have to turn anything on to declutter it; turning the setting **off** does not bring it back as a full bubble (it always expands on click). The toggle adds the rest of the family — scheduled wake-ups, background tasks, and other waiting phrasings. **And it folds no matter how long the message is (A10, 2026-07-13):** because that sentinel is Omniscio's own keep-alive marker — not a real deliverable — even a long "Waiting on a background process…" update, or several that Omniscio grouped into one block, collapses once a newer reply follows it. Only the fuzzy family below is spared when its reply is a genuine answer.

The most recent wait always stays fully visible — the live "waiting right now" state, or the final wait of a session that has since stopped or timed out. So does any wait fused into a turn **you** opened: the first wait in a run rides the turn that carries your prompt, and a turn containing a message you typed is **never** collapsed. A bare "still waiting" notice collapses once the agent has moved on. For the **fuzzy** waiting family (scheduled wake-ups, background tasks, other phrasings), a wait turn whose reply is **itself a real answer** (or asks you a question) is never collapsed, even mid-run, so an answer can never hide behind an Auto-waited marker. The canonical "Waiting on a background process…" sentinel folds once superseded regardless of length (A10 above) — because it's a status marker, not an answer. **But when the agent posts a real answer right AFTER that wait — the wait line, then a system nudge or recovery, then a substantive summary — Omniscio splits them into two turns so the answer stands as its own visible message, never dragged into the wait's fold (A11, 2026-07-14).** So a genuine answer is never hidden behind an Auto-waited marker, canonical or fuzzy. **And a message that ENDS by promising to continue — "Waiting on the lint and guard runs to finish — I'll continue once **they** complete" — folds too, no matter how it's worded or how long it ran (A13, 2026-08-05):** agents paraphrase the keep-alive line with specific job names ("they" for "it", naming the jobs), and a status update that ends by parking on background work is not an answer — so it collapses once a newer reply follows it, even when it ran a little over length. A genuine deliverable or a question that happens to end that way still stays visible, and "…I'll continue once **you** approve" (waiting on _you_) never folds.

Nothing is deleted — each marker is a button that expands the full turn and folds it back. It is a pure chat-render preference read live at display time: toggling it re-renders instantly, changes no message rows, and is independent of the detector itself (you can hide the repeats while leaving detection on). It covers both wait shapes — prose waits ("Waiting on…") and agent `ScheduleWakeup` parks — and mirrors the sibling **"Hide background check-ins"** toggle. When a wait is _also_ a background check-in (a self-resume that reported progress AND declared a wait), the two collapses don't stack: the turn folds into a single **⏱ Auto-waited** marker — this fold wins over "Background check-in" — so it stays one click to the message. Mechanics + the safety invariants (A1–A13 — A8 keeps a substantive reply from ever folding for the fuzzy family; A9 is the default-on canonical-sentinel carve-out above; A10 makes that canonical sentinel fold regardless of length; A13 folds a paraphrased "…I'll continue once X completes" park, length-exempt but still sparing a real deliverable / question) are in [waiting-detector-contract.md](/.claude/memory/contracts/waiting-detector-contract.md) ("Collapsing repeated auto-waits").

#### Count repeated auto-waits (Settings → Sessions, opt-in)

A single wait often cycles several times — it times out, you click **Keep waiting**, it waits again, times out again. Each of those posts its own grey row to the chat: _"Auto-wait timed out — … Surfacing to you."_ and _"Wait timer reset — staying running…"_. Turn on **Settings → Sessions → "Count repeated auto-waits"** (`foldAutoWaitNoticesEnabled`, **default off**) to fold those repeat rows out of the conversation and show the count on the existing **⏱ Auto-waited** badge instead: **⏱ Auto-waited 2×**, **3×**, and so on. The number is how many times the session auto-waited in that stretch — the first wait, plus each "Keep waiting".

It only ever folds a wait that already carries the **⏱ Auto-waited** badge (a prose wait); a background-task wait has no badge, so its rows stay put — nothing is hidden with no badge to carry the count. The number appears only at 2 or more (a single wait stays a plain **⏱ Auto-waited**). Like its sibling above, it is a pure chat-render preference read live at display time — toggling it re-renders instantly, deletes nothing, and leaves the underlying rows in the database (Search, Share, and export are unaffected). It composes with **"Hide repeated auto-waits"**: when a folded episode collapses to a one-line marker, that marker carries the same count. Mechanics + invariants (B1–B5) are in [waiting-detector-contract.md](/.claude/memory/contracts/waiting-detector-contract.md) ("Folding repeated auto-wait NOTICES into a count").

#### Auditing firings (Settings → Diagnostics → Waiting Detector activity)

> **Panel hidden by default since 2026-06-04 (perf).** The render is gated behind `SHOW_WAITING_DETECTOR_ACTIVITY` (set `false`) in [DiagnosticsSettings.tsx](/src/renderer/src/features/settings/sections/diagnostics/DiagnosticsSettings.tsx) — rendering the firing audit log was a drag on the Diagnostics screen. The card component, its IPC handlers, the verdict store, and the search-index entry are all preserved; flip the flag to `true` to restore it. The backend keeps persisting firings regardless of the panel's visibility. Everything below describes the panel's behavior when re-enabled.

Once the detector is on, the firings are persisted as kinded `notable-system` rows on each session's history. **Settings → Diagnostics → Waiting Detector activity** surfaces those rows as a single chronological audit log so you can spot a bad pattern without grepping `interaction.log` or scrolling through dozens of sessions, and the panel is also where you triage firings into a verdict bundle for AI-assisted pattern tuning.

Each row shows:

- **When it fired** — relative timestamp (`3m ago`, `2h ago`); hover for the exact ISO.
- **Which session + project** — click the session name to deep-link straight to that session's chat, where the firing is in context (the **⏱ Auto-waited** tag on the agent's reply, or a standalone banner line for older pre-metadata rows).
- **Which rule + category** — the `patternId` + `patternCategory` from `waiting-patterns.ts`, color-coded by category. Older rows from before the 2026-05-23 enrichment land may show an amber `legacy` badge instead — the matched text is still parsed out of the banner via a fallback regex, but the rule id was never persisted on those rows.
- **The matched substring** — the exact text the regex captured, capped at 200 characters.
- **The triggering prose** — the first ~500 characters of the agent message that triggered the suppression, italicized and clamped to 2 lines. Use this to judge whether the suppression was correct _in context_, not just based on the matched substring.
- **A tri-state verdict segmented control** — `👍 Good` (the suppression was correct, the agent really was waiting) or `👎 Bad` (false positive — the agent should have flipped to **Needs You**). Re-clicking the active tag clears it. When `👎 Bad` is active, a one-line "Why?" input appears underneath; what you type commits on blur (or Enter) and is capped at 200 characters silently.

Verdicts persist to `<userData>/waiting-detector-verdicts.json` (atomic write, single-flight queue so rapid retag clicks can't race-replace each other on disk). The legacy `waiting-detector-fp-reports.jsonl` file from earlier builds is opportunistically migrated into the new store the first time the panel loads — every legacy row becomes a `👎 Bad` verdict carrying its old note as the reason; the JSONL file itself is left untouched on disk so nothing destructive happens.

The header pill shows `N firings · X 👍 · Y 👎 · Z untagged`. All four numbers are GLOBAL across every firing in the DB — paging doesn't skew them, so you can use the `untagged` count to track how much triage is left even when you only see the latest 100 rows in view.

The **Export bundle (JSON)** button next to **Refresh** copies the full triage JSON to your clipboard:

```jsonc
{
  "version": 1,
  "exportedAt": "...",
  "totalVerdicts": 42,
  "good": 31,
  "bad": 11,
  "droppedOrphans": 0,
  "verdicts": [
    {
      "messageId": "...",
      "sessionId": "...",
      "sessionName": "...",
      "projectName": "...",
      "timestamp": "...",
      "patternId": "waiting-for-on-any",
      "patternCategory": "explicit-hold",
      "matchedText": "waiting for completion",
      "bannerText": "Agent appears to be waiting — rule: ...",
      "triggerProsePreview": "...",
      "isLegacy": false,
      "verdict": "bad",
      "reason": "Agent was actually summarizing what waiting means.",
      "taggedAt": "..."
    }
  ]
}
```

Paste it into a chat with a tuning-capable model so it can suggest pattern-library refinements (`detectSuppressibleWait` guards, new `noMatch` examples in `waiting-patterns.ts`, or a regex narrowing). Only tagged firings are exported; untagged rows are skipped because they carry no triage signal. Orphan verdicts (a verdict for a `messageId` whose banner row was deleted from the DB) are silently dropped and counted under `droppedOrphans`.

The panel loads the latest 100 firings on first open and on each **Refresh** click. If the detector is currently off, the empty state spells that out so you don't sit looking at an empty list wondering whether anything fired.

The audit log respects soft-deleted sessions: a row whose session was deleted still surfaces (so you can review a deleted false positive) but the session/project name columns are blank to signal that the underlying session is gone.

### When the agent declares there is nothing to say — the quiet holds (2026-09-19)

Three holds are **not prose matches at all** — the agent declared, in its own words or by marker, that this turn carries nothing for anyone. They ride the same hold machinery and show the same **⏱ Auto-waited** pill:

- **Hibernation** — the agent parked itself until its next scheduled wake-up.
- **Empty final** — the agent emitted its final message and left it with no visible prose in it.
- **Overseer release-acknowledgement** — the agent answered its Overseer's release with nothing new.

As of 2026-09-19 the pill and the armed banner for these say what actually happened ("Held quiet — the agent declared a final message with nothing in it to show you…") instead of the prose-wait wording, which read *"agent appears to be waiting — matched: 'agent-empty-final'"* — false twice over, since nothing was waiting and no pattern matched.

**A Plain Speak card is content.** The empty-final hold used to claim a turn whose whole reply was a card, because the card is stripped from the visible reply text. But the card is exactly what you read (it is your default view), and it can carry the only thing that mattered: measured on 2026-09-19, 42 card-only turns were held in one day, and two of them carried a card whose **Recommended Action** was **Unblock the agent** — a person-only guard holding a branch from landing — while twelve carried a **Wait** variant that never got its check-in probe. The hold now reads the card's Recommended Action and **yields** when it asks you for anything; only a card that asks for nothing (`None`, or a closing rung — `Archive` / `Understand then archive`) keeps the quiet hold. So a card that asks you to act reaches you; a card that tells you nothing is needed stays quiet.

**A turn the app itself is waiting on is never held (2026-09-19).** When Omniscio notices a reply went out with no glance card, it quietly asks that session for one. The answer is always a bare card, so the hold used to claim it — and holding it did not delay the card, it **broke** it: the step that attaches the card to the message it describes runs on the same turn-end signal a hold suppresses, so the answer was never attached, never hidden, and you saw the same reply twice. That turn now always concludes normally. Measured before the fix: card answers resolved ~91% of the time through 09-16, ~34% the day the hold landed.

## Related

- [session-stuck-in-needs-you.md](session-stuck-in-needs-you.md) — the user-facing **Needs You** sub-state diagnostic page; the waiting detector reduces how often you'll see the generic amber dot for sessions that aren't really waiting on you.
- [scroll-position-memory.md](scroll-position-memory.md) — when the detector fires, scroll position memory respects the suppressed inbox state (no scroll-to-bottom forced).
- [focus-mode.md](focus-mode.md) — Focus Mode is a separate notification-batching gate that runs _after_ `needs_you` is set. The waiting detector reduces the upstream rate at which Focus Mode sees attention items.
- [main-heartbeat.md](main-heartbeat.md) — `main.log` (the standard electron-log file) records every Gate 6 hit as `[Process:<sessionId>] turn-complete: Gate 6: prose match pattern=<id> matchedText="..."` at `verbose` level (only when verbose logging is enabled). The persistent, always-on record is the `notable-system` row stored on the session's `conversation_messages` (rendered in chat as the **⏱ Auto-waited** tag).
- Contract source of truth: [.claude/memory/contracts/waiting-detector-contract.md](/.claude/memory/contracts/waiting-detector-contract.md) — read this before any code change to the detector.

This page is split across three parts: [part 2](waiting-detector-part-2.md) covers what happens when a silent wait reaches its cap, the agent-stated ETA window, the Keep waiting and Check in buttons, the automatic check-ins on both sides of your inbox, the inbox right-click menu, the confirmation probe for ambiguous waits and the Plain Speak Wait card; [part 3](waiting-detector-part-3.md) covers everything the detector deliberately never does, the optional AI second opinion and the first half of the technical reference; and [part 4](waiting-detector-part-4.md) covers the ScheduleWakeup check-in mode and the rest of the technical reference.