Alarms
The Alarms feature: natural-language reminders that ring inside Omniscio at a wall-clock time you choose, with snooze and dismiss, recurrence shapes, per-alarm sounds and folders, a missed-fire inbox row, and the CLI routes an agent drives them with.
What it is
Alarms are natural-language scheduled reminders that ring inside Omniscio at a wall-clock time you pick. You type "remind me to take meds at 8 PM" or "every weekday at 7:30 AM stand-up" into a single textarea and Omniscio saves the alarm, schedules the fire, and surfaces it however you've asked it to surface (full-screen modal, toast banner, or silent inbox row). When the alarm fires you get the standard phone-style affordances — Snooze for a few minutes, or Dismiss to clear it. Recurring alarms re-arm themselves automatically; one-off alarms vanish from the sidebar the moment they fire (phone-alarm semantics) — only the inbox alarm-fired row remains, where you can Snooze or Dismiss the missed fire like any other inbox card.
Alarms are a separate feature from cron jobs even though both are time-triggered. The line is that cron jobs run code (a script, a recipe, a session spawn) and post their result to the inbox; alarms only ever ring at the user. There is no command, no working directory, no exit code, no cost, no agent spawned. You can think of cron as the scheduler for Omniscio's automation surface and alarms as the scheduler for Omniscio's reminder surface. They live in different sidebar rows, different SQLite tables, and the UIs share nothing except the visual language of the rest of the app.
The feature ships behind a master toggle. Settings → Notifications → Alarms → Enable Alarms is off by default; while it is off the sidebar row is hidden, the rest of the settings are suppressed, the in-process scheduler does not tick, and the control-server routes that would schedule a ring (create, update, toggle-on) refuse with a 403 instead of saving an alarm that could never ring.
Where to find it
Where it lives in the UI
The Alarms entry sits in the Omniscio sidebar group (the same column as Cron, Skills, Recipes, Ask Omniscio). Clicking it opens the alarms virtual project with the usual 3-pane layout:
- Pane 2 (sidebar) — the natural-language input at the top, then time-based sections: Today, This Week, Recurring, Disabled. Below those, an optional Folders area shows your own user-defined groupings (see "Organizing alarms with folders" below) and an Uncategorized bucket for alarms not filed into any folder. Each row shows the label, the next fire time, and a small recurrence chip ("Daily" / "Weekdays" / "Custom" etc.); rows in folders also show a tiny folder badge so you can tell at a glance where an alarm lives. Recurring alarm rows carry an on/off toggle so you can mute the schedule without deleting it; one-off alarm rows have no toggle — they're inherently single-use, so the only meaningful actions are "open the detail pane to edit or cancel" and "let it fire" (after which it auto-deletes). Click an alarm to load it into Pane 3.
- Pane 3 (detail) — a full editor for the selected alarm: label, time, recurrence, folder picker (dropdown listing your folders + Uncategorized), sound picker with preview button, snooze duration, fire-limit (or "fire forever"), plus an Advanced section for the three per-alarm overrides (assertiveness / bring-to-foreground / pierce Focus Mode).
There is also a settings section at Settings → Notifications → Alarms that holds the global defaults — these are the values any new alarm inherits unless its per-alarm overrides say otherwise.
The sidebar row, the virtual project pane, and the settings section are wired to the registry in src/shared/integration-registry.ts (entry id: 'alarms').
How to create an alarm
- Open the Alarms virtual project in the Omniscio sidebar.
- Type the alarm in plain English into the textarea at the top of Pane 2. The placeholder shows an example:
Try "every weekday at 7:30 AM". - Press Enter to save. Shift+Enter inserts a newline if you want to spread the description across multiple lines, but Enter alone always submits.
- The new alarm appears in the appropriate section — Today, This Week, Recurring, or Disabled — and Pane 3 loads its editor. You can refine the label, switch the sound, change the snooze duration, or flip individual override toggles from there.
If the parser can't make sense of what you typed, the textarea shows an inline hint underneath ("Could not parse — try at 8pm or every Monday at 9am"). The hint is informational only — nothing has been saved, no toast pops, and your input stays in the textarea so you can edit it.
The Pane 2 implementation is AlarmsSidebar.tsx; the Pane 3 editor is AlarmDetail.tsx.
Quick add from the header (toolbar button + Ctrl+Shift+A)
For the common case "I just want to add an alarm right now, I don't need to navigate to the Alarms project first", Omniscio has a header quick-add affordance. It's available whenever Enable Alarms is on.
- Toolbar button — a
BellPlusicon labelled "New alarm" lives in the toolbar item list. Pin it to your header (Settings → Widgets → drag it into the pinned area, or use the overflow menu) and clicking it opens the quick-add modal in the centre of the screen. - Keyboard shortcut — press Ctrl+Shift+A from anywhere in Omniscio (including while typing in another field — the shortcut is global) and the same modal opens.
The modal has:
- A natural-language textarea at the top — same parser as Pane 2's box. Type something like
every weekday at 7amand press Enter to fill the structured fields below. Enter parses + fills, it does NOT save — so you can tweak before committing. - Editable form fields — Label, Time, Recurrence, plus a one-off Date picker (when Recurrence = Once) or a row of weekday chips (when Recurrence = Custom days). You can skip NL entirely and fill these directly; you can also tweak what the parser produced before saving.
- Footer buttons — Cancel (or Esc / backdrop click / X) closes without saving; Create alarm (or Ctrl+Enter from anywhere in the dialog) saves. On success the modal closes and the alarm joins your list. On failure (validation error, IPC error) the modal stays open with an inline hint so you can fix the input and retry.
The defaults on each open are time = 08:00, recurrence = daily — a single Ctrl+Enter from a blank modal creates a daily 8 AM alarm called "New alarm". Folder is always Uncategorized (move the alarm into a folder from the sidebar afterwards if you want).
The quick-add modal lives at QuickAddAlarmModal.tsx. The toolbar item id is 'alarm-quick-add' in toolbar-items.ts; the shortcut id is 'openAlarmQuickAdd' in keybindings.ts. Both routes funnel through useAlarmsStore.openQuickAdd() which flips the quickAddOpen slice — the modal is mounted globally in App.tsx and reacts to that flag.
Natural-language examples that work
The parser handles the shapes you'd expect from a phone alarm app, plus a few Omniscio-specific ones:
at 8pm— one-off, today (or tomorrow if 8 PM has already passed).805p/8p/1230a— compact shorthand for8:05 PM/8:00 PM/12:30 AM. A normalizer expands these toH:MM AM/PMbefore the parser runs, so compact times resolve on the free offline path (no AI call). The am/pm letter is required — a bare1530or411is left alone so a room or extension number is never read as a time.tomorrow at 9:30am— one-off on a named day.every day at 7am— recurring, daily.every weekday at 7:30 AM stand-up— recurring, Mon–Fri only, label "stand-up".every Monday at 9am— recurring, single weekday.Mon Wed Fri at 6:45 PM yoga— recurring, Custom (specific weekdays).weekends at 10am— recurring, Sat + Sun.remind me to take meds at 8pm— the label-hint phrase ("take meds") is preserved alongside the time.tonight at 11:45 take out the trash— one-off, descriptive label.2025-12-25 at 7am open presents— one-off, ISO date.
How the parser works (under the hood)
The parser at src/main/services/alarm/alarm-nlp-parser.ts is a two-stage offline-first hybrid:
- Chrono first (free, offline). The chrono-node library parses the time and recurrence locally. If chrono produces a confident time and the input doesn't trip the recurrence-indicator regex, the alarm saves with zero network calls and zero cost. This is the path for the vast majority of phrasings.
- Haiku fallback (paid, capped). When the input is ambiguous, hints at recurrence chrono can't infer (e.g. "every weekday"), or chrono fails outright, Omniscio calls Claude Haiku with a tight schema-constrained prompt and a 200-token cap. The model returns a
ParsedAlarmobject that the same downstream save path consumes. Failure is soft — if Haiku errors or the daily cost cap is exceeded, the inline hint surfaces and nothing is saved.
Input is capped at 500 characters before either parser sees it, so a paste of an entire email body will be truncated rather than billed.
How it behaves
Recurrence options
Alarms support five recurrence shapes, exposed in the editor as a segmented control:
| Recurrence | Meaning | NL trigger phrase examples |
|---|---|---|
| Once | Fires once at the chosen date+time, then stops. | "at 8pm", "tomorrow at 9am", ISO dates |
| Daily | Fires every day at the chosen time. | "every day", "daily" |
| Weekdays | Fires Mon–Fri only. | "every weekday", "weekdays" |
| Weekends | Fires Sat + Sun only. | "weekends" |
| Custom | Fires on the specific days-of-week you pick (e.g. M/W/F). | "every Monday", "Mon Wed Fri", "Tue & Thu" |
In the editor, picking Custom reveals a row of seven day-of-week chips (Sun through Sat) that you toggle on or off. The full list lives in AlarmDetail.tsx (the RECURRENCE_OPTIONS and DAY_LABELS constants).
Organizing alarms with folders
The time-based sections at the top of Pane 2 (Today / This Week / Recurring / Disabled) show every alarm you have. Below them, you can create flat user-defined folders to group related alarms together — "Work", "Meds", "Kids", whatever makes sense for you. Folders are purely cosmetic: an alarm in the "Meds" folder fires identically to an alarm with no folder, and an alarm shows up in BOTH its time section AND its folder section (they're two ways of looking at the same row, not a move).
Folder layout in the sidebar
The folder area appears beneath the time sections with these rows:
- + New folder — a button at the top that opens an inline text input. Type a name, press Enter, the folder appears.
- Uncategorized — a built-in bucket for alarms that aren't in any user folder. Hidden when empty AND you have at least one user folder defined; visible otherwise so it's always discoverable.
- Your folders — one section per user folder, in your chosen order. Each folder's header has a left-side disclosure caret (collapse/expand), the folder name, and on hover two action icons appear: a pencil (rename inline) and a trash can (delete with confirm dialog).
Collapsing a folder hides its alarm rows but keeps the header visible so you can drop new alarms in. The collapsed state is per-session — restarting Omniscio re-expands everything.
Two ways to file an alarm into a folder
1. Drag and drop (sidebar). Grab any alarm row in Pane 2 — works in either the time sections or in a folder section — and drag it onto a folder header. The header highlights as you hover. Drop to file. Dragging onto Uncategorized removes the alarm from whatever folder it was in. Dragging onto an alarm's current folder is a no-op (no IPC round-trip, no visual flicker). Drag-and-drop uses a custom MIME type so random text drags or file drops can't accidentally trigger a folder highlight.
2. Folder dropdown (Pane 3 editor). Open any alarm in the detail editor and the Folder field is a normal dropdown listing all your folders plus "Uncategorized" at the top. Picking a folder saves on the next debounce tick like every other field in the editor.
Both paths route through the same update(id, { folderId }) action on the store, so the result is identical no matter which you use.
Creating, renaming, deleting folders
- Create — click + New folder, type a name, press Enter. Escape cancels. Empty names are rejected by the schema (no folder is created).
- Rename — hover the folder header, click the pencil. The name turns into an input. Type, press Enter to save, Escape to cancel; clicking away (blur) also saves. Renames affect only the folder's display name — the alarms inside it are untouched.
- Delete — hover the folder header, click the trash can, confirm in the dialog. Alarms in the folder are NOT deleted. They lose their folder assignment (the row's
folder_idis set to NULL) and reappear in the Uncategorized bucket. This is a single atomic SQLite transaction at the backend, so neither half can land without the other — you can't get into a state where the folder is deleted but its alarms still point at the dead folder id.
The natural-language quick-add box at the top of Pane 2 doesn't accept a folder hint today. New alarms created via NL always land in Uncategorized; if you want them in a folder, drag the row down or pick a folder in Pane 3 afterward.
What folders deliberately don't do
- No nesting. Folders are flat — you can't put a folder inside a folder. The use case (group ~10 related alarms together) doesn't need tree structure, and a flat list keeps drag-and-drop unambiguous.
- No fire-time inheritance. A folder is just a label. Folders don't have their own settings, sound, or fire rules — every alarm carries its own settings.
- No mobile editing. Folder management (create / rename / delete / reorder / drag-to-assign) is desktop-only, same as the rest of the alarm editor. Mobile sees alarm rows in their time sections via the inbox; folder grouping is a desktop-side organization feature.
Where folders live in code
- DB schema: migration v199 in src/main/db/database.ts adds the
alarm_folderstable (id, name, display_order, is_deleted, created_at, updated_at) and thefolder_idcolumn onalarms. - CRUD: src/main/db/queries-alarm-folders.ts (list/get/insert/rename/delete/reorder). Delete runs in a transaction that also sets
alarms.folder_id = NULLfor any row that referenced the deleted folder. - IPC: channels
ALARM_FOLDERS_LIST/ALARM_FOLDER_CREATE/ALARM_FOLDER_RENAME/ALARM_FOLDER_DELETE/ALARM_FOLDERS_REORDERin src/shared/ipc-channels/index.ts; handlers in src/main/ipc/alarms-handlers.ts. Push channelALARM_FOLDERS_CHANGEDfans changes out to other renderers. - Renderer state: the
foldersslice onuseAlarmsStorein src/renderer/src/stores/alarms-store.ts, with a 150 ms debounce on push-driven reloads (independent of the alarm-list debounce so both can fire in parallel). - UI: folder sections + drag-and-drop in src/renderer/src/features/alarms/AlarmsSidebar.tsx; folder dropdown in src/renderer/src/features/alarms/AlarmDetail.tsx.
What happens when an alarm fires
The scheduler is an in-process loop that ticks every 30 seconds. When the tick passes an alarm's nextFireAt timestamp, Omniscio routes the fire through whatever assertiveness the alarm resolves to:
- Modal (default) — a full-screen
AlarmRingModalopens: a bell badge in an accent-tinted disc, the alarm's label as the headline, the live wall clock on a quiet line under it, a one-line hint describing what the tone will do (goes quiet after N seconds / plays once / rings until dismissed), and two buttons — Snooze 9m and Dismiss. The label leads deliberately: an earlier layout made the clock atext-6xlheadline and pushed the alarm's own wording underneath, which made a ring read like any other confirmation dialog. The hint deliberately describes only the SOUND; it never promises the popup will stay, because the Max Ringing Time cap closes the ring on its own. The sound starts playing on a loop and stops itself after the resolved sound duration (30s by default) while the popup stays. Ctrl+Enter (or Cmd+Enter on Mac) is bound to Dismiss as the primary action. Escape closes the dialog the same way Dismiss does. Only one modal can be visible at a time; if a second alarm fires while the first is open, it queues until the first resolves. - Banner — a non-blocking toast appears at the top of the Omniscio window with the label and the same Snooze / Dismiss affordances. The sound plays once. This is the right choice for alarms you want to be aware of but don't want to interrupt typing or video calls.
- Silent banner — same toast as banner, but no sound. For passive reminders ("water plants", "stretch") that you want to notice when you look at the screen but never want to hear.
The modal lives at AlarmRingModal.tsx. The sound-playback gating happens in the main process — the renderer just renders whatever assertiveness the fired-event payload says.
Bring Omniscio to foreground
When the Bring Omniscio to foreground setting (or per-alarm override) is on, Omniscio raises its own window above other apps once, at the moment the alarm starts ringing, so you actually see the modal even if you've been working in another window — it does not keep pulling you back on every scheduler tick while the alarm continues to ring (the scheduler re-serves a still-ringing alarm every 30s; the foreground raise fires only at ring-start). This uses the same forceForegroundWindow() helper that the rest of Omniscio's notification surface uses — see src/main/services/alarm/alarm-service.ts.
Interaction with Focus Mode
Focus Mode batches non-urgent alerts into a single periodic notification instead of letting each one interrupt you. Alarms run through Focus Mode's evaluate() gate before they ring — and most alarms WILL be suppressed during a focus block, because by default alarms are subject to the same batching rules as every other notification surface.
The Pierce Focus Mode setting (or per-alarm override) is the escape hatch. With pierce on, the alarm bypasses Focus Mode entirely and fires immediately even mid-focus-block. Use it for the alarms you genuinely never want to miss — meds, picking up kids, hard meeting times — and leave it off for everything else.
When Focus Mode suppresses an alarm, the alarm doesn't disappear: it's recorded as a missed fire and surfaces in the inbox via the alarm-fired row (see "What happens if you're away" below).
Snooze and Dismiss
Snooze delays the alarm for N minutes. The duration comes from alarmsDefaultSnoozeMinutes (5 / 9 / 15 / 30 — same options as a phone alarm app) or the per-alarm snoozeMinutes override. Snoozing a recurring alarm does NOT skip its next regular fire — if your 7 AM alarm rings and you snooze it 9 minutes, it'll ring again at 7:09 AND at 7 AM tomorrow morning.
Dismiss advances the schedule. For recurring alarms, Dismiss schedules the next occurrence — tomorrow at 7 AM, next Monday at 9 AM, etc. — and clears the current ring. For one-off alarms, the schedule-advance step soft-deletes the alarm row (phone-alarm semantics — a one-off vanishes from the sidebar the instant it fires), so Dismiss on a one-off is really just acknowledging the inbox card. The alarm-fired inbox row preserves the event regardless, so you can always still see that it happened.
Both actions are idempotent at the service layer. If a double-click or a flaky network produces two Snooze events for the same fire, the second one no-ops cleanly. You can't accidentally double-snooze.
Snooze or Dismiss resolves the alarm on every device you have open, not just the one you clicked. Dismiss it on your phone and the ring clears on the desktop too — the backend owns the ringing state and announces the ring ending, and each client drops it. A device that was asleep or offline when that happened clears the stale ring the next time its window regains focus, by re-checking with the backend which alarm (if any) is actually ringing.
Max ringing time
If you walk away mid-ring without clicking Snooze or Dismiss, Omniscio won't ring forever. The Max Ringing Time setting (default 15 minutes, range 1–60) caps how long the alarm rings before Omniscio ends the ring and leaves it as a missed fire in the inbox. This is the watchdog timer in alarm-service.ts, and it announces the ring ending with its own ring-cap reason rather than reusing dismissed — that distinction is what tells the renderer to close the popup but KEEP the missed-alarm inbox row, since nobody was there to see it.
Sounds
Each alarm picks a sound from three sources, all surfaced in the same picker dropdown:
- Built-in — the small set of bundled WAV files that ship with Omniscio. The default for new alarms is
rising-chime, one of two purpose-built rising chimes (rising-chime,bright-ascent) that climb a four-note figure on a bell tone instead of the single urgentmission-alertblip. Every id MUST match aBUILTIN_SOUNDSentry — an id no source carries resolves to nothing and rings silently, the bug the oldneeds-you-defaultdefault caused. - System — Windows / macOS system notification sounds discovered by the OS sound enumerator.
- Custom — any audio file you've added under Omniscio's user sound directory (
<userData>/sounds/). The Settings → Notifications tab is where custom sounds are added today.
Never silent. When an alarm fires, the renderer resolves its sound id and — if the id genuinely can't be found (a stale default, a since-deleted custom sound) — falls back to a guaranteed built-in via resolveAlarmSoundUrl, so an alarm always rings rather than failing silently. The fallback runs only after the sound cache has loaded, so a valid-but-not-yet-cached system/custom sound still plays correctly, and sound-preview buttons keep using buildSoundUrl directly (a preview plays exactly the chosen sound, never a substitute). A deliberately silent alarm (silent-banner assertiveness) still plays nothing — the fallback lives only on the audible ring/banner paths.
Next to the picker is a Play button that previews the selected sound at the current notification volume. The preview uses Omniscio's sound:// protocol (a custom Electron protocol that resolves builtin/system/custom prefixes to their on-disk paths). Selecting a sound saves immediately; the preview is a sanity check, not a separate save step.
The ringing alarm modal also carries a View alarms link in its bottom-left: it opens the Alarms list and honestly resolves the current ring (snoozing it while snoozing is allowed, else dismissing) so it is never left in a stuck "ringing" state.
The sound-list IPC is IPC.NOTIFICATION_SOUNDS_LIST and the picker component (SoundPicker) lives inline at AlarmsSettings.tsx and is mirrored inside AlarmDetail.tsx for per-alarm overrides.
Per-alarm overrides vs. global defaults
Many settings can be overridden per alarm. The pattern is uniform: T | null where null means "inherit the global default at fire time" — the global value is read fresh each fire, so changing the global default later updates every alarm that didn't override it. Override one alarm and only that alarm changes; flip the global and every alarm with null for that field picks up the new value on its next ring.
| Setting | Global (Settings → Notifications → Alarms) | Per-alarm (AlarmDetail) |
|---|---|---|
| Sound | alarmsDefaultSound |
soundId (null = inherit) |
| Snooze duration | alarmsDefaultSnoozeMinutes |
snoozeMinutes (null = inherit) |
| Assertiveness | alarmsAssertiveness |
assertivenessOverride (null) |
| Bring to foreground | alarmsBringToForeground |
bringToForegroundOverride (null) |
| Pierce Focus Mode | alarmsPierceFocusMode |
pierceFocusModeOverride (null) |
| Sound play mode | alarmsDefaultSoundPlayMode |
soundPlayMode (null = inherit) |
| Sound duration | alarmsDefaultSoundDurationSeconds |
soundDurationSeconds (null) |
| Fade-in seconds | alarmsDefaultSoundFadeInSeconds |
soundFadeInSeconds (null) |
| Volume override | notificationSoundVolume (shared) |
soundVolumeOverride (null) |
| Speak label aloud | alarmsDefaultSpeakLabel |
speakLabel (null = inherit) |
| Max snoozes | alarmsDefaultMaxSnoozes |
maxSnoozes (null = inherit) |
| Require math to dismiss | (no global — per-alarm opt-in) | requireMathToDismiss (null = off) |
The Alarm type in src/shared/types.ts is the source of truth for the column shapes. The seven sound-config columns landed in schema v236; every existing alarm starts with all seven set to NULL and inherits the global defaults — no upgrade step required.
Sound play mode, duration, and fade-in
When an alarm fires, three knobs together control how the sound is shaped:
- Sound play mode —
onceplays the sound file one time and stops.loop(the default) plays the sound on a loop. Loop applies to both ring shapes: amodalalarm withloopkeeps looping while the modal is open; abanner(toast) alarm withloopalso keeps looping in the background while the toast is visible. This matters for assertive reminders ("I want it to keep nagging me until I dismiss it") even if you've picked the lighter-weight banner assertiveness. - Sound duration — caps how long the sound is allowed to ring for. Presets are 5s / 10s / 30s / 1m / 2m / 5m / Forever. The default is 30 seconds, so an unattended alarm falls quiet on its own rather than looping until you deal with it — the popup stays up either way, so nothing is lost. Once mode usually doesn't need this — the sound stops on its own when the file ends. Loop mode does: without a duration cap, a
loopalarm rings until you click Snooze or Dismiss (or hit the global Max Ringing Time cap). SettingForevermakes the duration cap a no-op so the modal's Max Ringing Time is the only stop signal. - Fade-in seconds — ramps the volume up over N seconds at the start of the ring.
0(default) starts at full volume.2is a quick crescendo,5is a noticeable rise,15/30/60are a gentle wake. Omniscio stepsaudio.volumein software, so any sound file works — no special encoding required. The fade-in starts from silence and ends at the resolved volume (per-alarm volume override or globalnotificationSoundVolume).
These three combine for a few common shapes:
| Goal | play mode | duration | fade-in |
|---|---|---|---|
| Phone-style "loop until I dismiss it" | loop |
Forever | 0 |
| Loud-but-quick (5s loop, no fade) | loop |
5s | 0 |
| Gentle wake (60s ramp + loop forever) | loop |
Forever | 60 |
| Default | once |
30s | 0 |
| Quick crescendo | once |
30s | 2 or 5 |
Both controls live in Pane 3 → Advanced for the per-alarm override and in Settings → Notifications → Alarms for the global defaults.
Volume override per alarm
The global notification volume slider (Settings → Notifications) sets the system-wide audible level for all of Omniscio's notification sounds. Individual alarms can override that for ring time only — useful for a "wake me up loudly" alarm in a household where you've otherwise turned Omniscio's volume down.
The per-alarm control is a preset dropdown (10% / 25% / 50% / 75% / 100%) plus "Use default (global)" for the inherit case. It only affects this one alarm's ring — chat notifications, toast pops, and other Omniscio sounds still ride the global slider.
The volume value flows through the same playOneShot() audio path the rest of Omniscio uses, so the resolved volume applies to the fade-in's end point too: a 100% override with a 5-second fade-in ramps from silence to 100%, regardless of where the global slider is set.
Speak label aloud
Turn on Speak label aloud (per alarm or globally) and Omniscio speaks the alarm's label text via the browser's SpeechSynthesisUtterance API at fire time, on top of whatever sound the alarm plays. The voice is the OS-default voice that the renderer picks up — no Omniscio-side voice configuration is exposed today.
This is handy for two cases:
- Accessibility — a user who can't read the modal at fire time still gets the alarm's purpose announced.
- Across-the-room reminders — an alarm called "take meds at 8 PM" speaking that phrase from a desk in another room is more useful than a generic ring.
The label-to-speech happens on the renderer side (the same process that plays the sound). The main process does not invoke any TTS. Speaking is additive to the sound — picking a silent banner with speakLabel: true is the closest Omniscio has to a "voice-only" alarm, but the chosen sound (if any) still plays alongside.
Where to look when it goes wrong
- An alarm didn't ring at all — first check Settings → Notifications → Alarms → Enable Alarms is on, then check the alarm's
enabledflag in Pane 3. If both are on, check whether Focus Mode is active and the alarm doesn't have Pierce Focus Mode set — that's the most common silent suppression. The scheduler tick logs live in<userData>/logs/main.logfilterable by[AlarmService]. - The ring modal opened but no sound played — likely the alarm's assertiveness resolved to
silent-banner, or the system notification volume in Settings is at 0. Try the Play preview button next to the sound picker; if the preview is silent too, the issue is the system-volume side, not the alarm. - "Could not parse" hint on every input — likely the NLP daily cost cap has been hit. Check today's spend in the Omniscio cost dashboard (look for
source = alarm-nlp). Raise the cap in Settings → Notifications → Alarms if needed. - Missed alarms never appear in the inbox — push channel may have been disabled. The
ALARM_FIREDandALARMS_CHANGEDchannels must be wired through the registry and web-access push filter — see src/shared/integration-registry.ts for the alarms entry, and src/main/services/web/web-access-push-filter.ts for the channel allow-list. - A recurring alarm fires at the wrong time after DST — the
fireTimeLocalis local wall-clock time, not UTC, so a 7 AM alarm rings at 7 AM both before and after the clocks change. If you're seeing drift across DST, file a bug.
Related
- Cron jobs — the time-triggered automation surface (runs scripts/recipes/sessions); compare with alarms (rings a reminder, no agent spawned).
- Focus Mode — the batching gate that alarms route through; per-alarm Pierce Focus Mode is the bypass.
- Mobile Access — how the inbox
alarm-firedrow reaches your phone. - Chat attachments — unrelated, but a useful cross-reference for the "where do features live in settings vs. virtual projects" pattern.
- Alarms (part 2) — the rest of this page: the advanced scheduling behaviour and edge cases (how many times an alarm may be snoozed, the math challenge that gates Dismiss, Test fire, fire limits, 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.
Last verified 2026-10-04