Implementation proposal · 7 October 2026 · No implementation started

Native notifications for Neuron on macOS

Notify when a turn finishes, an agent needs input, or approval is required. Deliver on the device running the client, and open the correct environment and thread when clicked.

This is a moderate feature across the server, client runtime, Electron shell, and desktop settings UI. Most of the work is reliable event handling and macOS integration. Settings and persistence are the smaller pieces. The React UI under apps/web is also the desktop renderer, so changes there support the Mac app.

macOS desktop onlyNative Electron deliveryConfiguration: per desktop client

First release and its boundary

Provide three independently configurable triggers, a master switch, a sound preference, an option to suppress notifications for the thread currently being viewed, and a test notification. Existing in-app indicators remain available when system notifications cannot be shown.

Included

  • Successful completion of a user-directed turn.
  • Structured input requests and approvals that require a user response.
  • Background and minimized desktop windows, with the client still running and connected.
  • Local, direct network, and SSH environments already connected to that client.
  • Clicking a notification reveals Neuron and opens the target thread.
  • Settings search and a command-palette shortcut to notification settings.

Separate future work

  • New notifications after the Mac desktop app quits.
  • Cloud push, a managed relay, or a new always-running helper.
  • Inline approval or reply directly inside an OS notification.
  • Reminder schedules, a notification inbox, and per-project overrides.
  • Notifications for every assistant message, tool result, or subagent completion.
  • Coordination that chooses only one device across all of a user's devices.
Recommended lifecycle. Hide or minimize the Mac app to keep receiving notifications. Quitting keeps its current meaning. This feature does not add browser or Linux notification delivery.

What the checkout already supplies

Platform proof comes first. Electron requires a code-signed app for macOS notifications. Neuron's daily installer applies ad hoc signing. Its ability to deliver and activate notifications on the target Mac remains unverified. A successful API call or isSupported() is not proof that a person saw a notification.

One policy and one macOS delivery service

  1. 1. Persisted eventsProvider activity becomes authoritative turn and request events.
  2. 2. Compact signalsThe server streams only notification-relevant identities and transitions.
  3. 3. Client policyShared logic applies preferences, replay rules, visibility, and duplicate prevention.
  4. 4. macOS deliveryElectron main displays the native notification and handles its click.

Server and contracts

Add a small typed notification-signal subscription, using the existing event store and replay machinery. Signals identify the event, sequence, thread, turn or request, kind, and time. Attach the live tail before reading a snapshot or catch-up, and mark when replay becomes live. Do not add one subscription per thread or stream token deltas to notification consumers.

Map request activities with their real request IDs. First trace how each successful terminal turn survives ingestion and persistence. If the durable domain events cannot distinguish completion from readiness, interruption, or cancellation, add an explicit terminal-turn event through the existing command, decider, and projector flow. Do not infer completion from an idle session or a spinner disappearing.

Keep old clients and servers interoperable through a capability flag and additive schemas. Older servers retain in-app indicators and report native trigger support as unavailable rather than guessing from sidebar booleans. Server-side OS delivery would notify the remote host, so all actual presentation stays in the client.

Notification coordinator

Create a small module in packages/client-runtime with injected native delivery, presence, settings, and local persistence. Start it once in the macOS desktop runtime above individual chat routes and retain one lightweight subscription per connected environment while notifications are enabled. It must keep working while Settings is open or another thread is selected. Other clients do not start the coordinator.

Use environment plus event/request identity for duplicate prevention. Keep a bounded record of handled identities and cursors in client persistence. Process every signal before advancing its cursor, including suppressed events, so revisiting a thread or re-enabling settings does not produce historical alerts. Validate cursors against the environment's current event history after data restoration or replacement.

Delivery is best effort, with duplicate avoidance preferred over automatic retry storms. Reserve an identity before requesting native display and record the result. A crash between reservation and display can lose a notification; do not claim exactly-once delivery. Current thread state remains the source of truth.

macOS Electron service

Add an Effect service around Electron's main-process Notification, with typed bridge methods for capability, test/send, dismissal, presence, and activation. Keep notification objects alive until their lifecycle ends. Report supported, unsupported, attempted, shown, and failed states accurately; OS settings or Focus can still suppress display.

Use structured environment/thread references for activation. Reveal the existing window and hold the target until the renderer and target environment are ready. Reuse the activation broker pattern, while extending it for existing threads and secondary environments rather than assuming the primary environment. A click only navigates; it never submits an approval.

The first implementation uses event-driven client processing and native display in Electron main. The early hidden-window proof decides whether that subscription needs to move into an Electron-owned background service. If it does, reuse the shared coordinator and existing environment/authentication ownership; keep one authoritative subscriber. Do not globally disable window throttling to make notifications work.

Gate notification controls and runtime startup on the macOS desktop capability. There is no browser permission flow, browser-tab ownership, Linux adapter, or cross-platform fallback to implement. The shared renderer still needs to decode the additive settings safely on other clients.

Rules worth agreeing on before coding

SituationPlanned behavior
DefaultsNotifications start disabled. Once enabled, all three triggers are available, sound starts off, and viewing the target thread suppresses its notification by default.
Successful turn completionOne alert per turn identity. Never label failed, cancelled, or interrupted turns as completed. Turn completion does not claim all background work has finished.
Input or approvalOne alert per actionable request. Distinct requests still count when a prior request is pending. Skip requests already resolved before dispatch.
Free-form questions and custom formsArbitrary assistant text and a rendered custom form do not automatically imply a blocking input request. Audit the custom form path; explicit support requires a typed actionable signal. Do not parse prose for question marks.
Queued follow-up begins immediatelySuppress the routine completion alert if the next queued turn is already running when the candidate is checked. Preserve required-input and approval alerts. Define “finished” against persisted turn state.
Visible target threadSuppress only when the app is focused and the exact environment/thread is being viewed. Another thread or Settings does not count as seeing the target.
Startup and reconnectionNo backlog of historical completion alerts. Reconcile still-pending requests after synchronization, with one reminder per unseen request. Ignore replayed, imported, reverted, and already-handled history.
Dismiss, resolve, or disableDismissal does not answer a request. Resolution withdraws the associated notification where the platform permits. Turning notifications off stops new delivery and tears down unused subscriptions.
Click targets missing or offlineReveal the client and explain that the thread is unavailable or the environment must reconnect. Never navigate to a same-ID thread in another environment.
Content and soundUse concise thread context and the trigger label. Exclude full prompts, tool output, and approval details. Use Electron's native sound/silent option and respect macOS notification settings.

Implementation order

Prove the macOS path

Build a focused throwaway probe outside the checkout. Verify native display, silence, failure reporting, click activation, and minimized/hidden delivery with the packaged local Mac build. Check the installed Electron API types before using newer notification-history methods.

Exit: Evidence for macOS signing and hidden-window delivery, plus a decision on renderer-owned versus Electron-owned subscription processing. No broad UI work before this gate.

Define reliable signals and replay

Specify the typed subscription and capability. Map persisted actionable requests and successful terminal turns. Add only the durable event information that current records lack. Reuse event storage, authorization scopes, replay bounds, and stream backpressure.

Exit: Focused backend tests prove no live/replay gaps, distinct request identity, correct terminal states, and compatibility with old clients. Tests wait for typed receipts and worker drains.

Build shared policy and persistence

Implement candidate classification, suppression, synchronization handling, cursor/identity persistence, pending-request reconciliation, and shutdown. Wire lifecycle to environment registration and disconnection. Keep notification policy out of React chat components.

Exit: Deterministic tests cover replay, reconnect, collisions across environments, multiple requests, queued turns, setting changes, and deletion. A crash/retry policy is explicit.

Integrate the macOS delivery service

Add the Electron service/layers, preload and IPC contracts, click routing, and capability reporting. Ensure the macOS runtime has one authoritative notification owner and cleans up listeners and notification objects.

Exit: Service tests prove native lifecycle behavior, deferred activation, sound/silent configuration, unavailable/failed delivery, and prevention of duplicate display.

Add settings and task guidance

Extend existing client settings and desktop persistence with decode defaults. Add macOS-only notification settings, Enable/Test actions, settings-search terms, and a command-palette entry. Add a concise user-guide section explaining how to enable delivery, macOS permissions, and the running-client boundary. No new keybinding is needed initially.

Exit: Settings persist across restarts, disabling takes effect immediately, old settings still decode, and notification controls are available only in the Mac desktop app.

Validate complete Mac journeys and ship

Run focused tests, lint, and typechecks for touched packages. With explicit permission for computer use, perform one integrated pass in the macOS desktop app after integration. Verify the actual notification and click result rather than treating mocked IPC as platform proof.

Exit: macOS display and activation evidence is recorded, the feature is opt-in, and there are no extra background loops or lost environment targets. Updating the installed daily app is a separate requested action.

Acceptance matrix

AreaMinimum proof
Entry pointsGlobal processing from chat and Settings; discoverable settings search and palette entry; test notification; no new keybinding.
ClientsPackaged macOS app with actual native delivery and click handling. A focused capability-gating check confirms other clients do not start notification processing or expose its controls; no browser or Linux delivery acceptance work.
ProvidersAudit Codex, Claude, Cursor, Grok, OpenCode, and Antigravity individually for completion, input, and approval signals. Add focused adapter fixtures where mappings are missing. Explicitly mark unsupported request types; do not assume every provider exposes all three triggers.
ConnectionsLocal, direct network, SSH, secondary environments, offline-to-online recovery, and same thread IDs in different environments. Notification delivery stays on the client host.
LifecycleFocused thread suppression, another thread, Settings, minimized/hidden window, reload, renderer readiness, shutdown, and no delivery after quit. Check existing background-policy scope so it cannot pause notification-relevant streams.
History and racesInitial snapshot, imported history, reconnection replay, events during snapshot load, fast request resolution, consecutive requests, queued follow-ups, event-store replacement, and bounded backpressure.
Reverse actionsDisable, re-enable, resolve request, dismiss notification, disconnect environment, and remove thread. None of those paths causes historical notifications to flood back.
Platform evidenceActual OS display, sound preference, notification click target, signed build, denial/unsupported behavior, and retained in-app state. Focus/Do Not Disturb can suppress presentation even when the OS accepts the request.

Use targeted tests and package-scoped checks. Do not run repository-wide checks. Do not launch browsers or perform computer use without explicit permission. Automated tests should use receipts, drains, and deterministic synchronization rather than sleeps.

Expected size and remaining decisions

Planning estimate: roughly 15–25 source and test files across contracts, server streaming, client runtime, Electron, desktop settings in the shared renderer, and documentation. This narrower scope removes browser permission handling, tab coordination, and Linux delivery verification. It is an estimate from the identified seams, not a measured patch. The size grows if desktop subscription processing must move to a new main-process service or if provider terminal-event mappings need repair.

Database work: no new notification table is planned. Preferences use existing client persistence. Delivery cursors and a bounded handled-event record are local to the client. New durable orchestration event types, if required, can use the existing event store; verify replay compatibility before concluding that no migration is needed.

Native work: use Electron's existing macOS notification API. No custom Swift or Rust notification implementation is planned. If the current local signing cannot satisfy macOS delivery, resolve that requirement before increasing feature scope.

Recommendations to adopt: opt-in enablement, silent default, viewing-thread suppression, successful turns only, and delivery while the client is running. Closed-app delivery would be a separate architecture decision involving background execution, credentials, and platform lifecycle.

The implementing agent owns the phases in dependency order. Maintainer input is needed only if platform proof forces a scope change or closed-app delivery becomes a requirement. Planning does not change application code or ongoing work in other checkouts.

Source checks

Repository findings were checked against the current local source on 7 October 2026. These relative file names are code pointers, not public repository links.