[FEATURE] Quiet turns: let the model end a turn with nothing after a Monitor or channel event, and render nothing when it does

Status Open
Reported on v2.1.266
Maintainer reply None cached
Activity 1 comment · opened Sep 9, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet (closest: #91996, hide the empty Human: line after background-task notifications, rendering only; #43993, context-only channel delivery, closed stale; #70422, the no visible output retry)
  • [x] This is a single feature request (not multiple features)

Problem Statement

Monitor and channels both deliver a stream of events into the session, one model turn per event. That is the right shape for an agent that has to watch a stream and act rarely: a live call transcript, a log tail, a chat room. The model reads each event, and almost every time the correct response is "nothing to do."

Today the harness cannot represent that outcome. An empty response is not allowed: the harness re-prompts with [Your previous response had no visible output. Please continue and produce a user-visible response.] (also the root of #70422). So the model has to emit something, every event prints an event line plus a message plus a ✻ … done line plus a Stop hook run, and a quiet stream turns the terminal into a scrolling log of filler.

Repro 1 (Monitor, primary)

Claude Code 2.1.266, macOS arm64, claude.ai Max, Sonnet. One prompt:

Arm the Monitor tool with command while true; do date -u +%FT%TZ; sleep 5; done, persistent true, description 'date ticks'. Then, whenever a tick notification arrives, do not say anything at all.

Terminal (lightly de-duplicated from a pty capture):

⏺ Monitor(date ticks)
  ⎿  Monitor started · task blis2sc3x · persistent
⏺ Monitor armed.
✻ Baked for 5s · done 2:21 PM · 1 monitor still running

⏺ Monitor event: "date ticks"
⏺ The harness won't let a turn pass with zero visible output, so true silence
  per tick isn't achievable — I can't fully honor "say nothing." Want me to stop
  the monitor, or is it fine if these ticks stay silent except for this?
✻ Cooked for 21s · done 2:21 PM · 1 monitor still running

⏺ Monitor event: "date ticks"
⏺ Still waiting on your call — stop the monitor, or let it keep ticking silently
  aside from these unavoidable nudges?
✻ Churned for 17s · done 2:22 PM · 1 monitor still running

⏺ Monitor event: "date ticks"
⏺ Stop Task
  ⎿  while true; do date -u +%FT%TZ; sleep 5; done · stopped
⏺ Stopped the monitor — the harness required a visible nudge each tick, which
  wasn't useful. Say the word if you want it re-armed.
✻ Worked for 7s · done 2:22 PM

The model diagnosed the constraint correctly, could not comply, and after three ticks gave up and killed the stream it had been asked to watch. Each tick also shows running stop hook in the spinner.

Repro 2 (channel, same outcome)

A stdio MCP server declaring capabilities.experimental['claude/channel'] pushing the date every 5 s, launched with --dangerously-load-development-channels server:datechannel, Haiku. After "please do not say anything when you receive a message":

← datechannel: 2026-09-09T16:41:21.391Z
.
✻ Cooked for 7s · done 1:41 PM
← datechannel: 2026-09-09T16:41:26.393Z
.
✻ Baked for 2s · done 1:41 PM

Same constraint, different source: the best the model can do is a one-character reply, and the UI still prints three lines per event.

Our use case

A pair-programming app streams the live call transcript and screen-share events into the operator's Claude Code session through a persistent Monitor (tuple capture follow <call-id>). The agent is a proactive sidekick: when someone on the call says "I don't know" or states something worth fact-checking, it decides on its own to look it up and offer the answer while the topic is still live. That judgment has to be made by the main model on every line, so batching on the sender or context-only delivery (#43993) is not a substitute. On a 12-minute two-person call we measured 178 speech lines; a one-hour call is on the order of a thousand events, and the operator should see the handful of turns where the agent chose to act, not a thousand nudges.

Proposed Solution

Make "nothing to say" a first-class outcome for turns started by a harness-originated event (a Monitor notification or a channel event), and render it as silence.

  1. An empty response ends the turn. If a turn triggered by a Monitor notification or channel event ends with no text and no tool call, the turn is complete. The [no visible output] re-prompt does not fire.
  2. Quiet events are never rendered. The event line (⏺ Monitor event: … / ← source: …) is not shown, whether or not the model reacts. The event exists only in the model's context. A counter in the status area (the existing 1 monitor still running slot, or the channels notice) is enough to show the stream is alive.
  3. An empty turn renders nothing. No message, no ✻ … done line. Stop hooks skip it too; otherwise a stop-time review gate runs once per transcript line.
  4. A non-empty turn renders only the model's output. If the model decides to act, its message and tool calls appear as usual, but the triggering event is still not echoed by the harness. If the user needs to see what the model reacted to, the model quotes it in its own words, formatted for the conversation ("Alice wasn't sure whether the retry loop backs off; it does, here is where: …"). The model, not the harness, decides what surfaces.
  5. Opt-in per stream. A quiet: true option on Monitor and a per-server quiet setting on the --channels entry. Default behaviour is unchanged. Per-stream is the baseline and is enough for our use case, since the model decides when to break silence anyway. A per-event quiet param on notifications/claude/channel is a nice-to-have on top, cheap because the notification already carries params; a per-line equivalent for Monitor is not worth a convention inside stdout.

An explicit affordance would make (1) more robust than relying on an empty completion: a built-in no-op the model can call (or a fixed sentinel the harness strips) that ends the turn and renders nothing. Either works; the point is that the model can pick silence reliably instead of inventing filler.

Alternative Solutions

  • Rendering-only fix (#91996). Hide the empty Human: line. Necessary but not sufficient: the model still cannot produce an empty turn, so it still prints filler and still runs hooks.
  • Hide the event line only for empty turns, show it when the model reacts. Simpler, but it leaks raw stream lines into the transcript whenever the agent speaks, and the raw line is rarely the right thing to show a user (ids, timestamps, wire format). Letting the model quote the event in context reads better and keeps the harness out of the presentation.
  • Context-only channel delivery (#43993). Append without starting a turn. Good for bulk and for cost, but the model never sees the event until something else wakes it, so it cannot make the per-event judgment above. Complementary; happy to see both.
  • Sender-side batching / keyword wake (--batch, --wake-on on our CLI). Reduces the count and moves the judgment out of the model. We already use it as an escalation, not as the norm.
  • Instructing the model to stay quiet. Does not work; see Repro 1. The harness re-prompts and the model either emits filler or stops the stream.

Related: #91996, #43993, #70422, #22703 (batch background-task notifications when idle), #89397 (notification disclaimer repeated per event), #83384 (Monitor FIFO drain, no coalescing), #92815 (channels need a settings-level opt-in).

Priority

High - Significant impact on productivity

Feature Category

Interactive mode (TUI)

Use Case Example

  1. The operator says "join my call." The agent arms a persistent Monitor on the transcript stream with quiet: true.
  2. Each transcript line starts a turn. The model reads it and, almost always, ends the turn empty. The terminal shows nothing; the status counter moves.
  3. Someone says "I don't know if the retry loop backs off." The model decides to act: reads the code, posts a two-line answer through the app's MCP tool, and its message in the terminal opens with the line it reacted to, in its own words. No raw event line appears.
  4. The call ends, the stream exits, Monitor reports the exit as a normal notification, and the agent writes the summary.

Additional Context

  • Both repros are trivially reproducible; the channel server is ~30 lines of Node on @modelcontextprotocol/sdk. Happy to attach either.
  • Token cost is unchanged by this request (still one turn per event, mostly cache reads). That is an accepted trade for an agent that is supposed to be paying attention; #43993 is the lever for cost, and the two compose.
  • Version: Claude Code 2.1.266, macOS 26.6 arm64, claude.ai Max.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗