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.
The Inbox — no phone required
Section titled “The Inbox — no phone required”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.
What it costs
Section titled “What it costs”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.
What gets pushed
Section titled “What gets pushed”| Event | Priority | When |
|---|---|---|
| Approval needed | High | A MUTATE/DESTRUCT action is waiting in Pending Approvals |
| Nova needs you | High | A task hit a human checkpoint (CAPTCHA, verification code, judgment call) and parked until you respond in Pending Approvals |
| Task failed | High | Any pipeline task fails |
| Needs review | High | A task escalated to human review |
| Needs clarification | High | A task is blocked on a question |
| Task complete | Default | Autonomous work only (goal-linked or cortex-dispatched), with the task’s output in the body — interactive chat tasks don’t buzz your phone |
| Agent message | Default | An agent explicitly sent you something via the send_push tool (the morning briefing arrives this way) |
The morning briefing
Section titled “The morning briefing”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 torequest_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”.
- Install the ntfy app (Android / iOS).
- In the app, add your Nova server as a custom server:
http://<nova-host>:8290. - 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. - Press Send test notification in Settings to confirm delivery.
Reaching ntfy from your phone
Section titled “Reaching ntfy from your phone”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.envand restart — the server listens onhttp://<host-lan-ip>:8290. - Anywhere: run the Tailscale sidecar and point the app at your tailnet hostname.
Lockscreen actions
Section titled “Lockscreen actions”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.
Configuration
Section titled “Configuration”Runtime config (Settings UI / platform config):
| Key | Default | Meaning |
|---|---|---|
notify.enabled | true | Master switch for push delivery |
notify.ntfy_url | http://ntfy | Where the orchestrator publishes (in-network) |
notify.ntfy_topic | seeded nova-<hex> | The topic / subscription secret |
notify.action_base_url | empty (disabled) | Phone-reachable dashboard URL — enables lockscreen action buttons |
notify.action_key | seeded 64-hex | HMAC key signing action links (internal, never share) |
Compose-level:
| Env | Default | Meaning |
|---|---|---|
NTFY_BIND | 127.0.0.1: | Host bind prefix for port 8290 |
NTFY_BASE_URL | http://localhost:8290 | Public base URL ntfy embeds in links |
NTFY_UPSTREAM_BASE_URL | empty (off) | Set to https://ntfy.sh for instant iOS delivery (free wake-up relay; content stays local) |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/notify/config | Admin | Current channel config + subscribe hint + connected subscriber count |
| GET | /api/v1/notify/log | Admin | Recent delivery receipts (accepted / rejected / suppressed, with reason) |
| GET | /api/v1/notify/inbox | Admin | Inbox: full message bodies + read state + unread count |
| POST | /api/v1/notify/inbox/read | Admin | Mark messages read ({"ids": [..]} or {"all": true}) |
| POST | /api/v1/notify/test | Admin | Send a test notification |
| POST | /api/v1/notify/actions/decide | Signed token | Decide 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.