[FEATURE] Advisory notes channel for background watchers — system-attributed context, not chat messages
Preflight Checklist
- [ ] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Use case: a background "watchdog" process (started per event with async: true) verifies the session's work against project directives while the main agent works. Today its only voice is asyncRewake: one wake per spawned process, delivered as a system reminder — workable for a single alert, unusable for a watcher that needs to deliver several small factual notes over a long turn ("directive X applies to the file you just edited", "criterion Y now has evidence"). Spawning one process per prospective note, and the rendering issues around rewake output (#44881), make it fragile. Reference implementation of the surrounding system (all public hooks): https://gist.github.com/brunox64/ffbdfb4f78fe928fce31c866c9d83aad
Channels don't cover this. #27441 and #24947 (both closed — the latter as completed by channels) asked for external processes to push messages into a running session, and channels does that well for chat/webhook sources: an event arrives as a <channel source="..."> turn the model is expected to read and reply to. That's the right shape for "a human or external system wants to talk to this session." It's the wrong shape for "an internal watcher wants to leave the model three small facts without asking it to do anything or reply to anyone" — every channel message is chat-shaped and two-way by design. The related primitive send_message has its own open reliability gap (#87653, #87323: content lands in the transcript but doesn't reliably reach the receiving agent's context), which is the same class of problem from a different angle.
Proposed Solution
A minimal primitive for one-way, non-conversational, throttleable notes — either:
- Option A — notes file: the harness watches
$CLAUDE_SESSION_NOTES(path exposed in hook input); each appended line is delivered asadditionalContextat the next tool boundary, capped per turn. - Option B — repeatable rewake: allow an
asynchook process to emit multipleexit 2-style notes over its lifetime (e.g., a length-prefixed stream on fd 3), same delivery semantics as today's single rewake.
Guardrails we'd expect: per-turn note cap, size cap, and clear attribution ("background hook note") so the model treats it as observation, not instruction — the opposite of a channel message, which is explicitly meant to be acted on and replied to.
Alternatives considered
- Channels (shipped): right primitive for external chat/webhook sources that expect a reply; wrong shape for an internal watcher's silent, capped notes — using it here would turn every advisory fact into something the model feels obligated to act on or answer.
send_message/ cross-session inject: same "full message" shape as channels, plus a currently open delivery-reliability gap (#87653, #87323) unrelated to the semantic gap this issue describes.- Polling a file from
CLAUDE.mdinstructions: works, but costs context on every turn checking a file that's usually empty, and is exactly the workaround projects keep re-inventing (see the polling-vs-inotify discussion on #24947).
Evidence of demand
#24947's thread (58 comments before lock) is full of people building the same workaround (file-based polling, tmux capture-pane, external inbox daemons) for full message injection; #87653/#87323 show the closest shipped primitive (send_message) still isn't reliable for content actually reaching context. Related: #64898 (hooks spawning background agents — the dispatch half of this), #30492.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗