Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Alarms (part 2)

The advanced half of the Alarms page: the snooze cap, the math challenge that gates Dismiss, Test fire previews, fire limits, missed alarms in the inbox, the mobile versus desktop split, the natural-language parse cost cap, and the CLI routes.

What it is

This is part 2 of the Alarms page. It carries the advanced scheduling behaviour and the edge cases: how many times an alarm may be snoozed, the math challenge that gates Dismiss, the Test fire preview, fire limits, what happens to a fire you were away for, the mobile versus desktop split, the natural-language parse cost cap, and the CLI routes an agent uses to drive alarms.

Where to find it

Alarms live in the Alarms virtual project in the Omniscio sidebar, with the global defaults at Settings → Notifications → Alarms; Alarms walks that surface end to end, including the header quick-add, folders, recurrence and sounds. Everything on this page is reached from the same Alarms detail editor (Pane 3), the Settings → Notifications → Alarms section, or the local CLI control server on 127.0.0.1:19519.

How it behaves

Max snoozes per alarm

The Max snoozes setting caps how many times an alarm can be snoozed during a single ring chain — a defense against the human reflex of mashing Snooze, Snooze, Snooze and not actually waking up.

  • Unlimited (-1, default) — Phone-alarm convention. The Snooze button is always visible.
  • Dismiss-only (0) — The Snooze button is hidden from the very first ring; only Dismiss is available.
  • N (any positive integer; the UI offers 1 / 2 / 3 / 5 / 10) — N snoozes allowed. After the Nth snooze the Snooze button disappears on the next fire of that chain and only Dismiss is available.

The count is per ring chain, not per alarm-lifetime. A recurring 7 AM alarm with maxSnoozes: 3 gives you 3 snoozes today, then resets to 3 fresh snoozes tomorrow morning. The count is held in memory in the alarm service and is cleared on Dismiss (or when Omniscio restarts) — it isn't persisted, so the user never lands in a state where Dismiss-then-restart silently still blocks Snooze.

The Snooze button gates on the cap in two places: the ring modal/banner hides the button once the cap is reached, and the backend snooze() IPC handler also early-returns past the cap (belt-and-suspenders — a stale UI state can't sneak past the limit).

Require math to dismiss (anti-snooze)

For alarms where you really need to wake up (e.g. early-morning standup, kids' school run), the per-alarm Require math to dismiss toggle gates the Dismiss button behind a small arithmetic challenge. When the alarm fires, the modal/banner shows a problem like 7 + 8 = ? with a text input; you have to type the correct answer before Dismiss becomes clickable. Snooze is unaffected — you can still snooze without solving.

The math is intentionally small (two-digit addition / subtraction, no multiplication or division) — it isn't meant to be a brain teaser, it's meant to be enough of a friction nudge that you have to be actually awake to clear it.

This is per-alarm opt-in only — there is no global alarmsDefaultRequireMathToDismiss setting. A global default that gates dismiss across every alarm would be hard to recover from if forgotten (imagine a poorly-aimed click on a settings toggle blocking every alarm's dismiss tomorrow morning). The per-alarm scope keeps the risk surface narrow and the user's intent explicit.

The math gate is forced off when test-firing the alarm (see "Test fire" below) — you pressed a preview button, you didn't consent to being gated behind arithmetic just to clear the preview.

Test fire — preview an alarm without scheduling it

The alarm detail editor (Pane 3) has a Test fire button in the footer, alongside Delete. Click it and the alarm fires immediately — the modal opens, the sound plays, fade-in ramps, the label is spoken if you've turned that on, the Snooze and Dismiss buttons render — but the alarm's schedule is untouched. The fire isn't counted toward fireLimit, next_fire_at isn't advanced, and no alarm-fired inbox row is created.

This is the right tool for:

  • Picking a sound — preview the sound at the actual fade-in / volume / loop config you've set, not just the picker's "Play" button which uses the global volume and no fade-in.
  • Sanity-checking loop + duration — confirm a 5-second loop really does what you expect before going to bed.
  • Hearing the spoken label — verify the OS voice pronounces your alarm label correctly.
  • Testing the math gate — see how hard the math is and whether you can solve it half-asleep, without waiting for 6 AM.

A few mechanical things to know:

  • Snooze and Dismiss in the preview are local-only. They close the modal/toast like a real fire would, but they don't fire any backend handler — the alarm service never set ringingId for the preview, so a real snooze(id) IPC against the preview's alarmId is a no-op. This is what keeps the schedule untouched.
  • The requireMathToDismiss toggle is forced off for previews, even if the alarm has it set. Math-gated alarms would otherwise gate the preview behind arithmetic the user didn't sign up for when they clicked Test fire.
  • Focus Mode is bypassed. A normal fire routes through the Focus Mode evaluate() gate (suppress / batch / fire-immediately); a test fire skips that and fires straight to the UI. You asked to preview the alarm — Focus Mode shouldn't decide otherwise.
  • Bring Omniscio to foreground is also bypassed for previews. The window is already focused (that's where the click came from), so re-foregrounding it is redundant and would steal focus from any other Omniscio pane the user was using.

Implementation: alarm-service.testFire(id) in src/main/services/alarm/alarm-service.ts emits the same ALARM_FIRED payload that a real fire would, with test: true and requireMath: false spread over the top. The renderer's useIpcListener(IPC.ALARM_FIRED) consumes both real and test fires identically; the test: true flag tells the alarms store to skip pushing onto the firedAlarms slice (avoiding a phantom inbox row).

Fire-limit and "fire forever"

Recurring alarms default to "fire forever" — they keep re-arming until you disable or delete them. If you want a recurring alarm to stop after N fires (e.g. "stand-up at 9am, but only this week — 5 fires then stop"), there's a Fire forever toggle in the editor's Advanced section. Turn it off and a number input appears for fireLimit. The alarm's fireCount increments on each fire; when fireCount >= fireLimit the alarm auto-disables.

This is independent of the recurrence shape — you can pair "Daily" with fireLimit: 5 to get five consecutive days then auto-stop.

What happens if you're away

If the Omniscio window is closed when an alarm fires, or if the ring modal times out at the max-ringing cap, or if the modal was dismissed without picking Snooze/Dismiss, the alarm surfaces as an inbox row in Omniscio's inbox. The row shows an amber AlarmClock icon, the alarm label, a relative timestamp ("3m ago"), and Snooze 9m + Dismiss buttons inline — so you can act on the missed fire without re-opening the ring modal.

A row whose RING HAS ALREADY ENDED — the one kept when the max-ringing cap expires with nobody there — offers Dismiss only. The backend's snooze and dismiss both early-return once nothing is ringing, so a Snooze on an ended row would remove the row and never re-arm the alarm: a button that looks like it worked and did nothing. Dismiss genuinely clears the row, so the missed fire is still acknowledged; the row exists to tell you the alarm fired, not to re-arm it. A row for a ring that is STILL ringing keeps both buttons, unchanged.

Middle-click on the row also dismisses it (matches Omniscio's universal inbox-card contract). The row routes through the canonical inbox dispatcher (resolveInboxAction('alarm-fired', alarmId, …)) which captures the next nav target before the IPC await, so dismissing auto-advances your inbox cursor to the next item like every other inbox row.

The inbox surface is a generic inbox row plus the per-alarm AlarmDetailPane.tsx; the handler mapping lives in src/renderer/src/stores/inbox-handlers/alarm-fired-handlers.ts.

Mobile vs. desktop

The split is "manage on desktop, react anywhere":

  • Desktop only: the Alarms virtual project's Pane 2/3 (natural-language input, edit form, sound picker with preview, all per-alarm overrides). Creating and editing alarms is a desktop affordance.
  • Both desktop and mobile: the inbox alarm-fired row. If you're triaging your inbox from your phone via Tailscale Funnel or LAN access (mobile-remote-access.md) and a missed alarm landed there, you'll see it with the Snooze / Dismiss buttons and can act on it from the phone.

Mobile reach for the inbox row depends on the ALARM_FIRED and ALARMS_CHANGED push channels being in INBOX_ALLOWED_CHANNELS in src/main/services/web/web-access-push-filter.ts — they are, so missed-alarm rows reach the phone the same way SMS rows and other inbox surfaces do.

The full-screen ring modal is desktop only by design — a phone in your pocket can't reasonably present a full-screen modal Omniscio controls. The inbox row is the mobile-side surface for that case.

NLP cost cap

The Haiku fallback for natural-language parsing costs a small amount of money per call. To keep Omniscio from accidentally billing $100 because some pasted bug report tripped the parser, the daily spend is capped.

Settings → Notifications → Alarms → NLP Daily Cost Cap ($) controls the cap. Default is $0.10, range is $0.05 to $5.00 (one-cent steps). At Haiku's pricing this works out to roughly a few hundred parser calls per day at the default — comfortable for normal use, hard to overshoot by accident.

When the cap is hit, the parser short-circuits before calling Haiku. The chrono.js fast-path keeps working (it's free), so simple shapes like at 8pm — and compact shorthand like 805p, which a normalizer rewrites to 8:05 PM before chrono sees it — still parse. Ambiguous inputs fail soft with the same inline hint the user sees on a chrono miss — "Could not parse — try at 8pm…". The cap resets at midnight local time.

Cost tracking goes through the standard api_cost_log table (source label alarm-nlp) via trackApiCost() in src/main/services/api-cost-tracker.ts so the cost dashboard shows alarm parsing spend alongside everything else.

For agents

CLI access

The alarms subsystem is accessible via the CLI Control REST API (http://127.0.0.1:19519). This allows external AI agents (like Claude Code) to create, list, update, delete, toggle, and NLP-parse alarms programmatically.

Routes

Method Path Purpose
GET /alarms/list List all alarms. Optional query params: ?folderId=<id>, ?enabledOnly=true
POST /alarms/create Create an alarm (body = alarmCreateSchema)
POST /alarms/update Update an alarm (body = alarmUpdateSchema: { id, patch })
POST /alarms/delete Soft-delete (body = { id })
POST /alarms/toggle Enable/disable (body = { id, enabled })
POST /alarms/parse NLP parse (body = { text }). Returns structured alarm fields or null
GET /alarms/folders/list List all folders
POST /alarms/folders/create Create a folder (body = { name })

All routes require Bearer token auth. Mutations share the 10/minute rate limit; reads share the 60/minute budget.

Mutations apply immediately — no approval gate. The scheduler picks up changes on its next 30-second tick — but only while Alarms is switched on (alarmsEnabled, off by default). While it is off, POST /alarms/create, /alarms/update and /alarms/toggle with enabled: true answer 403 { ok: false, disabled: true, error } and store nothing; delete, toggle-off, list, parse, snooze, dismiss and test-fire still work. GET /alarms/list carries data.alarmsEnabled. Switch it on with PATCH /settings/alarmsEnabled {"value": true} — applied at once from an Omniscio session, otherwise queued for the user to approve in the inbox.

For full endpoint documentation including request/response examples, see the CLI skill doc at .claude/skills/omniscio-control/alarms.md.

Related

This page is the companion to Alarms, which covers the feature itself: what it is, where to find it, how to create an alarm, recurrence, folders, sounds and the ring behaviour. For the time-triggered automation surface that runs code rather than ringing at you, see Cron jobs; for the batching gate alarms route through, see Focus Mode; and for how a missed fire reaches your phone, see Mobile Access.

Last verified 2026-10-04