Skip to content

Notifications

Nova ships its own push-notification server (ntfy, bundled as the ntfy container) so autonomous work can reach you instead of waiting silently in a dashboard tab. No cloud account, no phone-number registration — your phone subscribes directly to your Nova instance.

Everything Nova sends you also lands in the dashboard Inbox (sidebar → Inbox, with an unread badge): morning briefings, agent messages, task outcomes, approval and checkpoint notices — full message bodies, read/unread state, mark-all-read. The push channel is an optional delivery leg; the Inbox is the canonical surface. If you never connect a phone, email, or ntfy client, nothing is lost — messages that couldn’t be pushed are flagged “not pushed” but are fully readable there.

Nothing. The bundled server is the open-source ntfy running on your machine, and the ntfy phone apps are free. The paid plans you may see on ntfy.sh (ntfy Pro) are for their hosted service — Nova doesn’t use it, and you don’t need an ntfy.sh account.

One platform caveat: iOS can’t hold background connections to arbitrary self-hosted servers, so instant delivery on iPhones requires relaying a wake-up ping through ntfy.sh. It’s free and opt-in: set NTFY_UPSTREAM_BASE_URL=https://ntfy.sh in .env and restart. Message content never leaves your server — the relay only tells the app “poll your server now.” Android and desktop/web clients need nothing.

EventPriorityWhen
Approval neededHighA MUTATE/DESTRUCT action is waiting in Pending Approvals
Nova needs youHighA task hit a human checkpoint (CAPTCHA, verification code, judgment call) and parked until you respond in Pending Approvals
Task failedHighAny pipeline task fails
Needs reviewHighA task escalated to human review
Needs clarificationHighA task is blocked on a question
Task completeDefaultAutonomous work only (goal-linked or cortex-dispatched), with the task’s output in the body — interactive chat tasks don’t buzz your phone
Agent messageDefaultAn agent explicitly sent you something via the send_push tool (the morning briefing arrives this way)

Nova seeds a standing Morning briefing goal: once a day it distills yesterday’s journal and fresh intel into a single push — what happened, what needs your attention, and up to three notable ecosystem items. Quiet day, one line; never padding.

  • Fires at 11:00 UTC by default (07:00 US Eastern in summer). Change the schedule anytime in Goals — it’s a normal scheduled goal.
  • Requires the brain (cortex) to be enabled — the briefing is composed by an agent, not a template.
  • Delivery rides send_push, a tool any agent can use for informational messages that don’t need an answer (the counterpart to request_human_checkpoint, which parks the task and waits for you). Agent pushes are storm-braked at 10 per hour.

Delivery status — how you know a push actually went somewhere

Section titled “Delivery status — how you know a push actually went somewhere”

“Sent” can mean two different things, and Nova shows you both:

  • Accepted: the ntfy server took the publish. Every attempt — including suppressed ones (notifications disabled, no topic) — is recorded as a delivery receipt, visible under Settings → Notifications → Recent deliveries (and GET /api/v1/notify/log).
  • Received: a device subscribed to the topic actually got it. The Settings page shows the live connected subscriber count (from ntfy’s metrics); when it’s 0, a warning tells you pushes are being cached into the void. iOS clients poll rather than hold a connection, so they don’t appear in the count.

Agents get the same honesty: a successful send_push tool call reports “accepted by the ntfy server”, not “delivered”.

  1. Install the ntfy app (Android / iOS).
  2. In the app, add your Nova server as a custom server: http://<nova-host>:8290.
  3. Subscribe to your instance’s topic — shown (with a copy button) in Settings → Notifications. The topic name (nova-xxxxxxxx) is randomly seeded at first boot and acts as the subscription secret: treat it like a password.
  4. Press Send test notification in Settings to confirm delivery.

The ntfy port is loopback-only by default (like every non-essential Nova port). Pick one:

  • Same network: set NTFY_BIND=0.0.0.0: in .env and restart — the server listens on http://<host-lan-ip>:8290.
  • Anywhere: run the Tailscale sidecar and point the app at your tailnet hostname.

Approval and checkpoint pushes can carry Approve/Deny buttons (Continue/ Decline for checkpoints) that decide directly from the notification — no dashboard, no login. Enable them in Settings → Notifications → Lockscreen actions by entering the dashboard URL your phone can reach (a LAN IP like http://192.168.1.20:3000, or your tailnet name).

How it stays safe without putting credentials on your phone:

  • Each button carries a signed one-shot link: an HMAC over that single approval id + decision + expiry, minted with a random server-side key seeded at first boot (notify.action_key). The key never leaves the server; the admin secret is never embedded in a push.
  • A token authorizes exactly one decision on exactly one approval and dies with it — tampering, decision-swapping, expiry, and replay after a decision are all rejected.
  • Checkpoint buttons cover the yes/no case (“solved the CAPTCHA — continue”). When Nova needs a typed reply (a verification code), tap Open to answer from the dashboard’s reply box instead.

Runtime config (Settings UI / platform config):

KeyDefaultMeaning
notify.enabledtrueMaster switch for push delivery
notify.ntfy_urlhttp://ntfyWhere the orchestrator publishes (in-network)
notify.ntfy_topicseeded nova-<hex>The topic / subscription secret
notify.action_base_urlempty (disabled)Phone-reachable dashboard URL — enables lockscreen action buttons
notify.action_keyseeded 64-hexHMAC key signing action links (internal, never share)

Compose-level:

EnvDefaultMeaning
NTFY_BIND127.0.0.1:Host bind prefix for port 8290
NTFY_BASE_URLhttp://localhost:8290Public base URL ntfy embeds in links
NTFY_UPSTREAM_BASE_URLempty (off)Set to https://ntfy.sh for instant iOS delivery (free wake-up relay; content stays local)
MethodPathAuthDescription
GET/api/v1/notify/configAdminCurrent channel config + subscribe hint + connected subscriber count
GET/api/v1/notify/logAdminRecent delivery receipts (accepted / rejected / suppressed, with reason)
GET/api/v1/notify/inboxAdminInbox: full message bodies + read state + unread count
POST/api/v1/notify/inbox/readAdminMark messages read ({"ids": [..]} or {"all": true})
POST/api/v1/notify/testAdminSend a test notification
POST/api/v1/notify/actions/decideSigned tokenDecide an approval from a push action button

Delivery is best-effort by design: a push failure is logged as a warning and never blocks consent decisions or pipeline execution.