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

Inbox alerts (how an agent gets your attention) (part 3)

Part 3 of the Inbox alerts page: the Alerts screen and an alert's life after it arrives — the cooldown that keeps a dismissed problem quiet, the recently-dismissed view, starting a session from an alert, automatic remediation, provenance, and the once-a-day digest.

What it is

This is part 3 of the Inbox alerts page. It covers what happens around an alert rather than what one looks like: how a dismissed problem is kept quiet, where dismissed alerts go, the ways an alert can start work for you, and the daily digest that summarises the lot.

Where to find it

Mostly the Alerts screen, reached from the inbox. It is laid out like any inbox: the alert list — including the recently dismissed ones — sits in a sidebar on the left, and the alert you pick opens on the right while the list stays in view, so you can move from one to the next. The daily digest arrives in the inbox as its own item.

How it behaves

Re-raise cooldown (a dismissed problem alert stays quiet for 24 h by default)

Deduplication keeps a still-open alert to one row; a re-raise cooldown stops a dismissed problem alert from bouncing straight back. Once you archive a problem/advisory alert, the same one (same dedupKey) cannot reappear for 24 hours — enforced once at the shared createAlert chokepoint, so every problem alert inherits it, including ones added later. Within the window a fresh raise is silently suppressed (nothing new in the inbox, no notification); past it, the alert can surface again. 24 h is the default, not the only value — a handful of cards are deliberately slower (see "Cards that wait longer" below).

This generalizes the hand-rolled once-a-day caps the low-memory and AI-cost cards already carried into one floor beneath them — it never fights those (they already cap at 24 h) and it covers the ones that had no cap of their own (prompted by the "low disk space" card, which re-appeared every 15 minutes after each dismissal).

A once-a-day card holds its day, whatever archived it

The cooldown above is anchored on a card a person put away. A card the system withdrew — the producer clearing its own condition, or an agent archiving over POST /alert/:id/archive — deliberately buys no quiet at all, so a genuinely recurring problem is never silenced just because its producer retracted it.

That leaves a gap for a card whose dedupKey names a content period — "this card IS today's digest", and the card's content does not depend on a condition the producer re-reads. Archive it as anyone other than the person and the next tick posts the period's content a second time, so the card can never be cleared. Cards of that shape (the daily Fleet health digest) therefore hold their own period: once the period's card has left the inbox, nothing posts it again, whoever removed it. While it is still in the inbox it behaves exactly as it always did — the row is refreshed in place. The next period is a new key, so tomorrow still posts. (inbox-alert-contract I39.) Measured 2026-10-02: the day's digest was archived by an agent at 10:23 and re-posted at 11:02, then every hour after that, for the rest of the day.

A day-stamped key alone does not make a card one of these. A producer that stamps the day purely to CAP its own nag rate — the inbox backlog nudge, the low-memory and spend cards — is a condition card, and it withdraws its own card the moment the condition reads false. Its withdrawal is recorded the same way an agent's archive is, so holding the period on it would silence a condition that genuinely came back the same day — the exact bug findLatestCappingByDedupKey exists to prevent (measured 2026-09-17: a morning storm's card was withdrawn on recovery, and the afternoon storm raised nothing). Those producers keep the ordinary cooldown and are deliberately NOT changed.

A small set of persistent-infrastructure cards get a shorter floor instead. A condition that stays broken for days — a stalled auto-lander, a stale test baseline, a red production deploy pipeline — is exactly what a day-long silence hides: the card is swept up in a routine bulk-dismiss of the inbox and cannot come back until the next day, long after the thing it warns about has stopped being a surprise. Those families re-raise every 6 hours. The clock is anchored to the card's creation, not the dismissal, so a dismissal buys at most floor − (dismissed − created): measured on 2026-09-14, a deploy card 7.8 h old when dismissed was then silent for a further 16.2 h on the 24 h floor, and re-raises on the next producer tick on the 6 h floor. (A card already older than the floor when dismissed is not suppressed by it at all — it can return immediately. That is independent of the floor's length and is why the length is the lever that matters here.) The exact per-card value is derived, never hand-maintained — the re-raise column in ALERT_CATALOG.generated.md is the one authority; the auto-lander rebuilds it after every land (npm run alerts:catalog is the same run by hand), so a producer that changes that value shows up there rather than arriving as a second copy here. A branch never regenerates or commits it.

Cards that list branches stay quiet for every branch you have already dismissed. The auto-lander's grouped cards ("N branches need a hand to finish landing", "a ready branch is blocked by a safety check", the stuck card) name their branches. Inside the 6-hour window such a card comes back early only for a branch that was on none of the cards you dismissed in that window (I26-member). The "branches are parked" card is plain visibility and takes the ordinary 6-hour quiet. Until 2026-09-23 the check looked only at your most recent dismissal, so archiving one card could instantly bring back the branches from the card before it — 49 of that day's 96 give-up cards reappeared within 2 minutes of an archive.

The "needs a hand to finish landing" card is never raised for a branch that is already being handled. A branch that is back in the landing queue — tagged ready at the commit it is on — is left to the lander, however many times it was handed back before. A branch a live session is working (yours, its author's, or the lander's rescue — even mid-rebase) holds the card off while that session stays active; only one that has gone silent escalates. Until 2026-09-24 the card judged all three from stale bookkeeping, and 21 of the 40 branches it had given up on since 09-22 went on to land.

A branch whose automatic rescues have run out is set aside quietly, not carded. Each stuck branch gets up to three automatic rescue sessions — two, plus one last attempt that waits for a free slot and a calm machine rather than being skipped. If all three end without landing it and nothing is still working on it, the lander marks the branch blocked with the reason written on the branch, shows it in the Dev Pipeline panel's couldn't-land list, and raises no inbox card (owner decision 2026-09-24). The commits and the worktree are untouched, the daily lifecycle report counts it as handled, and running /ready-to-merge on it again puts it back in the queue. It is never set aside while a check is still running on it or while it is tagged ready at its current commit. If the blocked tag cannot be written, the card appears as before, and the lander switch lander-giveup-set-aside (AMC_DISABLE_LANDER_GIVEUP_SET_ASIDE=1) brings the card back for every spent branch.

It is scoped to genuine problem alerts and never swallows real content:

  • Message / content alerts are exempt. A team-chat "new message" notice reuses one row per channel, so a second message must never be suppressed — those are exempted centrally (isReRaiseThrottleExemptDedupKey). Drip items are exempt too (their own surface). Reused-key status alerts (a broken RSS feed, a failing poll) are not exempt — for those, "at most once a day" is exactly what you want.
  • Urgent alerts bypass it. A genuine spend runaway bypasses both the AI-cost card's own daily cap and the global cooldown (bypassThrottle), so a real money emergency always surfaces — unless you have turned AI cost alerts off, which silences the runaway as well (ai-spend-alerts.md).
  • You can turn it off. Settings → Notifications → Limit repeat problem alerts (problemAlertReRaiseThrottleEnabled, on by default). Off → a dismissed problem alert can re-appear on its producer's own cadence; the per-card caps (low-memory, spend) still apply.

Like the per-card caps, the cooldown reads the most-recent prior card's timestamp straight from the database (counting a card you already dismissed), so it survives an app restart and a dismiss-then-spike the same day. See inbox-alert-contract.md (I26).

Cards that wait longer than 24 h

A few cards report a standing situation rather than an evolving incident — something you either fix once or knowingly accept. Their producers re-check on a schedule you don't control (every app launch, settings change or boot scan), so the 24 h default would turn each one into a daily nag about a decision you already made — and a card you dismiss on sight is a card you have stopped reading. These get a longer floor in the central cadence registry (alert-reraise-throttle.ts):

Card Waits Why
"Backups are on the same disk as your data" (backup-colocated) 90 days Re-raised each launch and backup-settings change; backup posture is a standing choice.
"Mobile Access is on" (tunnel-public-exposure) 30 days Re-raised each boot auto-start and tunnel re-heal, for a feature you chose to turn on.
"Monthly AI allowance used" (pooled-ai-monthly-limit) 30 days The allowance is monthly, so the fact cannot change again until it resets.
"{engine} may be getting too many skills" (skill-budget-over:*) 30 days The scan re-runs ~60 s after every boot; the card itself is titled "Monthly reminder".
The watching-agent leverage recap 7 days Meant to land at most once a week.

Two things this is not. It is not permanent silence — each card returns on its own cadence for as long as the situation lasts. And it never delays the all-clear: fixing the underlying thing (enabling an off-machine backup, turning Mobile Access off) clears the card immediately, because clearing archives the row directly and never goes through the cooldown.

A longer floor also takes that card out of the "a problem alert keeps quietly recurring" meta-notice — for these, being suppressed is the cadence working, not a hidden recurrence.

"A check is running long" — one card per kind of check

What it says. That a check an agent asked for still has no result, and has gone far past how long that kind of check normally takes on this machine. The card names the kind of check, how many are still open, where the oldest one is, and how far over it is ("has not finished in 40m — normally under 20m").

It says "no result yet", not "still running." All the record shows is that no closing entry arrived — an attempt whose owning session has ended looks the same, so the card deliberately does not claim a live process.

Where the number comes from. The last 24 hours of this machine's own runs, per kind of check. The card raises when a running check passes twice the 95th-percentile time for its kind — never sooner than 15 minutes, and never later than 90, so a bad week cannot push the line so high that the card stops speaking.

One card per kind of check (tests, lint, typecheck, gate-launch, guard, …), each carrying its own key, so the card about a stuck typecheck is not hiding behind one about tests.

It clears itself. The card is refreshed in place while the check stays running, and taken down the moment that check finishes. You never have to archive it to make it go away.

It says when it cannot measure. A kind of check with too few recent runs to have a normal time is left alone rather than guessed at, and if the alarm itself stops running — an app restart, a wedged tick — the next card opens by saying how many minutes went unmeasured instead of showing you a stale figure.

It does not confuse a leak for slowness. An attempt whose closing record never arrived is reported as that missing record, counted on the card, and never alarmed on as a slow check.

Its re-raise floor is 6 hours, not the usual 24 — a wedged check is a standing condition, so a day of silence after one dismissal would hide exactly what the card reports while the check is still stuck.

What the Alerts screen lists — and what it does not

The screen lists real alerts only: the cards Omniscio's own monitors raise. A row an agent session raised is a message, not an alert, and it is deliberately not listed here — its home is the inbox (and, when it names one, the project hub its session is working in), where the conversation around it lives. The split is the SAME predicate that paints the row's colour (isAppRaisedAlert: a row carrying a sourceSessionId is a message), applied as a placement rule in one gate both halves of the screen read — the live list and the "Recently dismissed" section below it — so the two can never disagree (alerts-hub-items.ts, hubVisibleAlerts + hubDismissedAlerts; contract inbox-alert-contract.md the-alerts-panel-lists-only-real-alerts).

In the unified inbox the same split decides the SECTION. A projectless card Omniscio raised itself ("The app froze…", "What's new in Omniscio", "Claude Code needs an update") files under its own System section instead of the shared Alerts section, so the app's own notices no longer blend with the cards your sessions send (feature request 310d4871, 2026-10-02). System always sits at the bottom of the inbox — on screen and in keyboard next/previous order — and starts folded (its header still shows the count); open it with its chevron and it stays open until you fold it again. "Collapse all" folds it and "Expand all" opens it, "Dismiss all" works there, and clicking its header opens this Alerts screen. What stays put: a card filed under a project, the Tasks / Team Chat / Cron Jobs sections, a session's own projectless card (still Alerts), and the two first-run welcome cards (still Alerts, so a new user's first guidance is never folded away), and any Omniscio card that asks you something — a question or an approve/reject verdict, such as an email waiting on a reply or a workflow approval (still Alerts, so nothing waiting on your answer is folded away; alertAsksForAnAnswer). System holds plain information only. Code: alertInboxGroupId in alert-inbox-group.ts (the SYSTEM_ALERTS_PROJECT_ID group key), the bottom sink in inbox-items.ts, and the default-folded group in inbox-collapse.ts; contract inbox-alert-invariants-2-contract.md app-notices-file-under-system.

Two consequences worth knowing. The sidebar count for the Alerts row reads that same gate, so the number and the rows always agree. And a session's POST /alert card is covered by the same rule, not only a crew lead's — the whole family stopped being announced here, which is what the owner asked for: an agent's message belongs in the hub where that agent works.

Recently-dismissed view (the Alerts screen, last 7 days)

The dedicated "Agent Alerts" screen (the Alerts virtual-project row, gated by alertsSidebarEnabled) keeps its list in a sidebar, like every inbox-style integration (AlertsSidebar.tsx, the registry's sidebarComponent). The list shows your active alerts at the top — each with a Dismiss button (revealed on hover) that archives it in place, without opening it — and, below them, a collapsed "Recently dismissed (N)" section: agent alerts you archived within the last DISMISSED_ALERT_WINDOW_DAYS = 7 days (alert-types.ts), muted (theme-safe text-surface-500), each with a Restore button that un-dismisses it (reusing the ALERT_UNARCHIVE chokepoint).

The list gate is hubVisibleAlerts — ONE definition shared by this sidebar, the reading pane, the sidebar badge and the nav list that walks it. It drops snoozed rows (the universal snooze verb) and, since 2026-09-30, every team-chat notice: a chat message is a conversation rather than a standing alert an agent needs looked at, and it keeps every surface it had — the inbox, the channel's unread badge, the OS toast and the phone push. The badge counts that same filtered list, so the hub's number always matches the rows it shows.

That sidebar carries two tabs — "Alerts" and "Sessions". Alerts is everything described here. Sessions is the other half: the Agent Alerts hub is itself a session host, so agents you start in its context (the New session + while the hub is open, or the New session button on the tab) run in the hub's own managed workdir (<userData>/alerts-agent) and are listed there, through the same session list every other hub uses. The Sessions tab carries the usual running / waiting-for-you / error count, so you can see at a glance whether an agent is working without leaving the alert list. Picking a session opens its chat in the main pane; the alert reading pane comes back the moment you switch back to Alerts. (AlertsSidebar.tsx, alerts-session-host.ts; see session-host-contract.md.)

Clicking an active alert opens it in the usual alert viewer (AlertInboxViewer, the same pane the unified inbox uses). Clicking a dismissed one opens a read-only copy in the reading pane (AlertsVirtualProject.tsx, through the shared ArchivedAlertDetail) — never the live viewer, which resolves only active alerts and would sit on "Loading alert" forever. Its Restore button keeps the alert open, now as a live one. With nothing open the pane says "Select an alert", or "No alerts" when the list is empty. The list and the pane are separate mounts, so which dismissed alert is open lives in a small shared store (alerts-hub-selection-store.ts); selecting any live alert — from the list, keyboard nav, the archive advance, the unified inbox or a deep link — closes it, so one you moved on from can never resurface. On a phone the list is the landing screen and a tap opens the alert full-screen.

In a popped-out Alerts window (Open in new window) the list and the pane come along, but no main-window overlay exists there — so the reading pane draws the live alert itself (it asks isMainAppShell()), Go to session opens that session in the main window, and an HTML-file alert shows its normal file card instead of the live preview, whose <webview> a pop-out window cannot host. The popped-out list stays live on its own — it starts the alert sync and the snooze cache that only the main window's shell starts — and an alert archived elsewhere while it is open on the Agent Alerts hub (in either window) is reconciled instead of spinning on "Loading alert". Its buttons that lead elsewhere open in the main window: a Settings link on the exact page, Open channel on the exact Team Chat channel, any other hub on that hub, and Start session plus the actions that need the main app (Review updates, Resume them now, Finish setup, the app tour, Restart) or pick a view inside a hub (View your records, View spend dashboard, Open this job, Review emails) open the alert there, where they work. Details: pop-out-project-window.md.

Middle-click dismisses an active row on this screen too, as everywhere else in the inbox (the Archive action above). It routes through the same archiveInboxItem store action, so it inherits the cursor advance, the Ctrl+Z undo and the rollback-on-failure. A row in "Recently dismissed" is already archived, so the gesture is a deliberate no-op there — use Restore instead.

It is a pure VIEW window over data that already persists — archived alerts are NEVER purged (the retention sweeps skip inbox_alert_items), so nothing new is stored. Backed by the read-only listRecentlyDismissedAlerts (queries-inbox-alerts.ts — source_kind='agent' so it never poaches drip-sourced archived rows, the same project-liveness filter as listActiveAlerts, archived_at >= cutoff bounded at 200, newest-first) over the ALERT_DISMISSED_LIST read channel. The renderer keeps a separate dismissedItems store slice (loadDismissedItems, epoch-guarded) refreshed only AFTER an archive/restore commits, so the active list and its I12 reload-reconcile are untouched. A future retention purge of inbox_alert_items MUST respect this 7-day window. See inbox-alert-contract.md (I24).

Advancing to the next alert on the Alerts screen — the trap

Dismissing an alert from the Agent Alerts screen moves you to the next alert, the way archiving a session moves you to the next one that needs you — so a pile can be cleared without returning to the list between every row. On mobile the detail panel swaps its content in place and only slides back to the list once nothing is left below, so you never land on a blank full-screen detail.

That did not used to work, and the reason is a trap worth knowing before touching any hub. The advance is the ordinary one — advanceInboxCursorAfterDismiss resolves the next row with getNavigableItems(view, 'attention') + findNextItem — but it only advances if it gets a nav list at all. The Alerts screen is a virtual project, so no session carries its UUID, and its rows are alerts whose own projectId is the __alerts__ sentinel (or their source project, or the team-chat / tasks-v2 sentinel) — never this hub's UUID, which is all selectProjectAttentionItems matches on. The generic project path therefore returned an empty list here, and an empty nav list silently disables the advance: findNextItem returns null, the selection is cleared, and on mobile the panel pops back to the list.

The fix is the seam, not the symptom: getNavigableItems recognises the hub (isAlertsHubProject) and walks the hub's own queue from alerts-hub-items.ts — a verbatim projection of the rows the panel renders, in the same updated_at DESC order, so nav order IS rendered order. That one seam also covers keyboard nav in the hub. Deliberately not alertInboxSelectItems: that one additionally drops snoozed rows and desktop-only nudges because the global inbox hides them, and nav must walk exactly what this screen shows. Do not re-derive the queue anywhere else — the single-source wall is nav-group-single-source.test.ts, and the behaviour is pinned by alerts-hub-nav.test.ts.

This is the identical bug the Approvals hub had — see approvals-hub.md § Advancing to the next approval. Any new virtual-project hub that lists its own rows needs the same branch, or it inherits the same silent stranding.

Reading-pane appearance (severity medallion)

Open an alert and the reading pane leads with a tinted icon "medallion" beside the title, and the panel carries a matching thin left accent rail + a whisper-soft header wash — all coloured by the alert's kind:

  • green — an "Omniscio did a cleanup / self-heal" notice (the worktree-lock and install-orphan reapers, Auto-Tidy). An explicit allow-list — a real problem is never shown green. The two lowest-value self-heal FYIs — "cloud-enforce auto-enabled" and "cleared stuck worktree locks" — are silenced by default as noise (the cleanup + its audit log still run; only the FYI card is hidden). Turn on showSelfHealNotices to see them; the genuine stuck-reaper warning is never silenced by it.
  • amber — an "action needed" notice (a reconnect prompt; a low-memory / low-spec / sync-drift / low-power warning; a Tasks reminder).
  • red — an incident (an Anthropic API status incident, a broken cloud base).
  • neutral accent (indigo) — the default for every other or unrecognised alert.

The colour + icon come from one pure classifier, alert-visuals.ts (getAlertVisual), keyed off the alert's dedupKey. Colours route through the themeable status-* / accent tokens, so a custom theme recolours the medallions automatically, and the medallion is decorative (screen-reader-hidden) — the title text carries the meaning, so colour is never the only signal. The accent is alerts-only: it rides InboxDetailShell's optional, default-off accentRailClass / accentWashClass, so the approval panes and drip items render unchanged. The compact inbox row is unchanged (still an amber dot). See inbox-alert-contract.md (I23).

On a phone, a cut-off title is tap-to-expand. The reading pane clamps the title to 2 lines to keep the header compact, so a long title ("Anthropic API issue: Fable 5…") shows an ellipsis instead of eating the screen. Tap the title to reveal the whole thing; tap again to re-collapse. A title that already fits stays a plain heading (nothing to tap), and it's fully keyboard- and screen-reader-friendly (the full title is always the button's spoken label, even while it looks clamped). Desktop is unchanged. See inbox-alert-contract.md (I25).

Start a session from an alert

Every agent-raised alert can kick off a Claude Code session about it. Open the alert and click Start session (the message-plus icon in the card's bottom action bar — available on desktop and mobile; Archive stays in the top-right corner of the header). A small dialog opens with two text boxes, a target-repo picker and a model/harness picker:

  • a Message box — empty at open. It is your own instruction, and it owns the attachment affordances (paste / drop / paperclip) because it is the composer. Plain Enter submits from here, per your send-key setting; and
  • an Alert details box — collapsed by default, and still fully editable once you expand it: a non-blank per-alert preset (sessionPrompt) leads, then the alert's title and content body (text / link / file), so the new session starts already briefed instead of with just the headline. Clicking the Alert details header reveals it. The alert text is reference material most sessions never edit and it pushed the rest of the dialog off screen, so it starts folded — the collapse changes what you see, never what is sent: the same text is attached to your message whether or not you open the box. Plain Enter stays a newline here, because it is a document to read and edit rather than a composer; and
  • a target repo — the alert's saved sessionProjectId if it still exists and is spawnable, otherwise the project the alert is grouped under, falling back to the Claude default. Several auto-lander notices set sessionProjectId so Start session opens in the affected repo — the in-process supervisor already holds the repo's project id, so the paused — uncommitted changes and stuck — nothing landing alerts set it directly. The paused alert waits out the self-heal window before it appears — a base-dirty pause is almost always a transient the auto-lander repairs itself within ~10 min, so the inbox row is held until the folder stays dirty past that window (a genuinely stuck checkout), then reads with a one-click Open Diagnostics button (the lander's status card) instead of the old "commit or stash" wording a non-programmer can't act on. Both clear themselves when the repo recovers — the paused row the moment the checkout goes clean again, the stuck row when the backlog lands or empties (mirroring the other self-clearing maintenance alerts above). A second, longer-window stuck escalation ("stuck — over 15 minutes") fires alongside the ~5-minute one when a wedge persists on the wall clock — a backstop that still catches a lander whose supervision ticks are being silently skipped under heavy load; it sets sessionProjectId and clears on recovery the same way. When the blocker is a foreign half-finished git operation in the repo's folder (a leftover merge / rebase that never clears itself), the stuck copy names exactly that instead of the old misleading "a land is already running", and both stuck alerts point you at Start session to clear it. (They carry no Open Diagnostics button — the whole auto-lander status/pileup family lost it on 2026-08-18 because it steered non-developers into a raw diagnostics screen; only the base-dirty paused, auto-preserved and tick-failing cards still deep-link there.) See auto-lander-contract.md (S13/S14/S20/S21). When the auto-lander cannot finish branches on its own — automatic rescues switched off, a rescue that could not even start, a session that went silent while holding the branch, or a set-aside it could not write (a branch whose rescues simply ran out is set aside quietly instead, see above) — the give-up notice ("N branches need a hand to finish landing", coalesced into one card per repo) is written for a non-programmer: it reassures you the work is safe and points you at Start session to see why each branch stalled (no Open Diagnostics button: buttonless status/pileup family) and decide whether to land them or set them aside. It deliberately does not pre-fill a one-click rebase-and-land agent — the automatic retry it would re-run has already failed — so the copy no longer promises an agent can "finish landing them for you". The universal Start-session button remains, and since 2026-09-29 it hands the agent a triage brief instead of nothing: it points at the card's LIVE branch set, says the card collects every give-up reason so two branches on it can differ, and tells the agent to read each branch's OWN recorded reason before acting, land what can land through the normal path, and say why in one line and set aside whatever genuinely cannot — never a blind re-run of the retry that already failed. It self-clears a branch the moment that branch has nothing left to land: it lands, its work already reached master through other branches (a net-empty / superseded duplicate, which is also retired), or its ref is gone — so a branch superseded after it was carded never lingers as a false "needs a hand" row (E5/E14). The paused ("uncommitted changes") and stuck ("nothing landing" / "over N minutes") alerts carry the same one-click treatment: clicking Start session launches an agent already briefed to resolve that specific blocker — safely, since the paused case touches a checkout with uncommitted changes: the agent is told to preserve any real work (stash / recovery branch) and never run a destructive git command on changes it didn't make, while the stuck-agent aborts only a clearly-abandoned half-finished merge/rebase (E14). The same one-click treatment covers the last two per-branch notices: a branch that moved after tagging (new commits since its ready-to-merge tag), whose agent re-runs the checks and re-tags it, and one blocked by a safety check (a possible secret in the diff, or missing translations), whose agent runs the translation step for missing translations or, for a flagged secret, removes and rotates it safely rather than leaking it (the alert never contains the secret value) (E14). A companion "N branches are waiting to land" card coalesces every branch that has stayed ready-but-unlanded past a short threshold into one card per repo (each branch's exact wait time + reason live on the lander's Diagnostics status card); it sets sessionProjectId + a paused-lander preset so Start session opens in the affected repo, is a buttonless Start-session card (status/pileup family), and self-clears as its branches land — see auto-lander-wait-visibility-contract.md (I3). The auto-lander's check its folder card (raised when a watched repo's folder looks moved, missing, or polluted) likewise self-clears once the repo checks out healthy again — and both its false-alarm shapes are caught before they alarm: a folder-health git probe killed under a startup load spike degrades to a quiet retry, and a "far more untracked files" overflow that is really the repo's OWN ignored deps + tracked source mis-scanned while a fleet of agents churns the checkout is confirmed against git's committed history (HEAD + the committed .gitignore, which a live scan race can't corrupt) before it alarms and otherwise degrades to a quiet retry — so neither leaves a stale "update your folder path" card (same contract, S18). Untracked clutter sitting INSIDE one of the repo's own tracked folders — an audit's saved copies, a tool's scratch — is never counted as a polluted folder at all, and a repo the lander does skip is named, with the reason, in its status line instead of reading as an idle sweep (S177). The "main checkout out of sync" notice arrives with no project (the merge worker can't know the project UUID), so POST /alert fills its sessionProjectId at ingest — resolving Omniscio's own checkout to its project (path-matched, spawnable-gated) — so Start session pre-selects Omniscio. The same ingest-time resolution covers the other projectless Omniscio-self maintenance cards keyed by their dedupKey: the cloud-base-broken card and the worktree-cleanup card ("Unmerged worktrees need inspection" / "Worktree triage", dedupKey: unmerged-inspection-queue, raised by a scheduled cleanup cron that likewise can't know Omniscio's project UUID). The seven cloud-fleet self-cards raised by the cloud-queue monitor do the same — three health verdicts ("Cloud fleet health degraded", "…failing systemically", "…split-brain") plus four informational status cards ("VM disk saturated", "GCP capacity exhausted", "spot-preemption wave", "queue fall-open storm"), each about Omniscio's own cloud test-offload fleet. Omniscio's two self-health alerts — "Unusual number of errors" (dedupKey: telemetry-error-spike) and "Sessions are failing to start" (dedupKey: telemetry-spawn-failures) — do the same, resolved in-process by the health monitor: best-effort, so a resolution hiccup never suppresses the alert, and off a packaged build it falls back to the Claude default. An explicit sessionProjectId still wins. See inbox-alert-contract.md (I14).
  • a model & harness picker — collapsed to the project's resolved default engine and model, so you can read what will run without touching anything. It is optional in the strong sense: an untouched picker forwards nothing, so a launch that ignores it is byte-identical to one made before the picker existed. Change it only to run this session on a different engine or model; it uses the same shared chips as the New Session screen and the mobile composer. The Model list here spans the whole harness, not just the engine your default resolves to: on a DeepSeek default you can still pick Opus, Sonnet, GLM, Kimi or any other brain the Claude Code harness runs, one row per model grouped by maker, and the pick carries its engine along with it. (Unlike the in-session picker, this dialog has no separate Provider control, so a provider-only list would leave everything but your default engine unreachable.)

The two boxes compose ONE opening message: your Message leads, the alert text follows, and a blank side is dropped. A left-empty Message box therefore sends exactly the alert text — what this dialog sent before the second box existed — and an emptied Alert details box sends your message alone.

The message the session actually receives also carries an alert-context block the boxes never show, appended at launch time by buildAlertContextNote (src/shared/alert-session-prompt.ts): the alert's type code, the subsystem and repo file(s) that raise it (resolved from the generated alert catalog, so the agent can go read the code behind the card instead of guessing from its prose), when it was first raised and how many times, and the GET /alert/:id route to read the row back. On a folded card — several same-title reports merged onto one row, which reads "N separate reports share this heading" — the block opens with one more line: how many reports the card lists, that starting the session dismissed all of them, and that the card's own instructions apply to every report, so the session handles each one rather than only the newest.

Edit any of them, then confirm to spawn the session (launch source alert-start-session). Confirming is optimistic: the dialog closes immediately and the alert is dismissed the instant you click — never blocking behind a spinning "Starting…" button or a ~1s pause — moving you straight to the next item that needs you while the session starts in the background, without pulling you into it. Your prompt and the agent's reply stream into the new session in the sidebar. (A rare failure to start brings the alert back with a toast so you can retry, since the dialog is already gone.) The button shows on every agent alert — the only exemptions the guard sanctions are a team-chat notice and a card whose bottom the reply box occupies (inbox-alerts.md) — and the agent does not have to attach a prompt for it to appear. The app-generated alerts listed above (stuck-task, low-spec, Low Power Mode, sync-drift, Gmail reconnect, GitHub reconnect, account re-auth, team-chat new message, release-notes, Anthropic status, …) each also show their own feature-specific button alongside it, never instead of it, with Start session leftmost in the bar so each card's own action stays rightmost. If no spawnable repo exists the button is disabled (never hidden) with a tooltip. The alert text is built by buildAlertSessionPrompt and the two boxes are joined by composeAlertSessionPrompt (both in src/shared/alert-session-prompt.ts), mirroring Drip's launch. This "every alert, apart from the two sanctioned exemptions" rule is build-enforced: alert-start-session-universal.test.ts (in the cross-cutting guard lane) fails the build if any change would let an alert ship without the button — the gate narrowed to exclude an alert, the button re-gated at its render site, or the button removed. See inbox-alert-contract.md (I8) and alert-start-session-optimistic-contract.md (the optimistic close + no-double-spawn invariants).

Auto-remediation (enrolled infra alerts auto-start a fix session)

For a curated set of Omniscio-infra alerts — cloud fleet degraded / failing-systemically / split-brain / offload-down, direct-SSH refused / firewall-failed, cq-monitor crashed, ops-deploy drift, Firebase degraded, the auto-lander / auto-merge daemon stalled, the worktree reconcile / trash-sweeper stuck, WAL checkpoint failing, a degraded freeze-prevention gate, and a Firebase website that failed to publish — Omniscio doesn't wait for you to click Start session: it auto-spawns the remediation session itself, in the background, into the Omniscio project, then silent-archives the card (the spawned session is the only signal). This generalizes the four bespoke cloud self-healers (cloud-base / seed / chain / deploy, which fire from the POST /alert route) to the shared createAlert chokepoint, so an in-process infra alert is covered too — enrollment is the autoRemediate flag on an alert type's row in alert-type-registry.ts (always a subset of isAmcSelfAlertDedupKey).

It is cost- and freeze-safe by construction: only a freshly-created enrolled alert spawns (a dedup bump never does); a 3-minute boot-grace suppresses the startup re-detection storm; a 6-hour per-condition cooldown (in-memory, surviving the card archive) stops a flapping alert re-spawning; and a global burst cap bounds a simultaneous multi-condition failure. The spawn also rides the existing daily spend-cap + disk/memory pacing in the session spawner. Deliberately excluded (they stay passive cards): informational / self-heal notices, anything that needs YOU (reconnect / reauth / low-spec / unsaved-changes worktree), the memory/CPU perf/pressure alerts (spawning under load would worsen a freeze), and the spawn-queue alerts (a remediation spawn could itself raise them — reentrancy).

One fault, one fixer. A single outage often raises several different alert types within minutes, and each used to start its own fix session. Now, before an auto-fix session — or one of the four cloud self-healers — starts, Omniscio looks for sessions that were started from an alert in the same area within the last two hours and are still running, and names up to five of them at the top of the new session's instructions. The new session reads what they are doing and stands down (handing its alert to that session) only if one is already working the same fault; otherwise it carries on and names the related session in its report. It never blocks a fix: if the lookup fails or takes more than two seconds, the session starts exactly as before. Every automatically started fix session also carries the same clickable Spawned from link to its alert that a card-started session has.

Turn it off at Settings → Lab → "Auto-fix infrastructure alerts" (autoRemediateAlertsEnabled, on by default) for a passive card you act on yourself — a fix session costs money, so the off switch lets you approve each one; the emergency env kill-switch is AMC_DISABLE_ALERT_AUTO_REMEDIATION=1. See alert-auto-remediation-contract.md.

Where an alert came from (provenance)

Every alert says where it came from. Open any alert and its detail view (AlertInboxViewer) names the origin right under the title, in one of two forms:

  • "Generated by <session name>" — when a session raised it. Captured from the X-AMC-Source-Session-Id header (Omniscio-spawned agents send it automatically) and persisted as the source_session_id snapshot column on inbox_alert_items. Click it to jump straight to that session.
  • "From <feature>" — when a background service raised it, which is the case for every built-in advisory (session load balancing, the spend monitor, the status pollers). These carry no session, so before this they showed no origin at all. The feature is named in plain words — never an internal id — and the name is a link to Settings → Notifications, so every card also carries a route to manage or turn off notifications, not just the few with their own off-switch. Resolved by describeAlertSource (alert-source.ts), which falls back to "Omniscio" for a producer it doesn't recognize.

The origin is detail-only — it never appears on the compact inbox row. A team-chat notice is the one exception to the two forms above: its header already leads with the sender's name and avatar, which is its provenance. See session provenance and inbox-alert-contract.md (I11 / I15c).

The daily lifecycle digest (landing and workspace notices arrive once a day)

Omniscio's landing and workspace machinery used to be by far the loudest thing in the inbox: measured over the week to 2026-09-09, it raised 41 cards a day, spread across 60+ different kinds of notice, most of which happened less than once a day. That spread is why fixing them one at a time never worked; none of them was big enough to matter on its own, and together they buried everything else.

They are now split by a rule:

  • A few genuinely need you. Six kinds still arrive as their own card, the moment they happen — a branch that changes dependencies and needs your OK, a broken build on master, a merge that could not land, a conflict the auto-resolver gave up on, workspace creation failing, and nowhere left on disk to put a workspace.

  • Most of the rest arrive once a day. The "landing is behind" family, cleanup pacing, "your unsaved work is kept safe", inventory counts — all gathered into one card per area per day: one for landing, one for workspaces. Each lists what happened, how many times, when it started and stopped, and how much of it fixed itself. That is about 31 notices a day folded into 2 cards.

  • A third group is deliberately left alone. Around 21 kinds of notice live in the same part of the app but are not part of this split — genuine infrastructure faults that almost never fire, so there was no evidence to justify quieting them. They behave as they always did, about 4 cards a day.

    One of them is "a project document could not be rebuilt after the last merge" (2026-09-23). Some documents in a project are written by the machine rather than by a person — a list of every script the project ships, for instance — and Omniscio rebuilds them automatically after each merge. When a rebuild fails, the saved copy may go out of date, and if it does, the next branch that touches that area gets turned away when it tries to merge, for rows it did not cause. Nobody working on that branch can see it coming, because those documents are only checked at merge time. So the failure now gets a card of its own, naming which documents may be out of date, instead of only a line in a log nobody reads. It is one card per project however many documents are affected, it updates itself as documents come back, and it disappears on its own once the last one is rebuilt. A rebuild that failed only because the machine was momentarily out of resources is retried automatically first, so the card appears only for a problem that is really stuck. A rebuild the machine stopped before it could finish — it ran out of time on a busy machine, or the machine refused to start it — tells us nothing about the document, so it is simply tried again on the next pass and raises no card; the card appears only if that keeps happening — three passes in a row, over at least half an hour (2026-09-26: a single timeout had raised it over merge helpers that were all up to date).

Altogether: about 13 cards a day instead of 41.

The big win is what you never see at all: 91% of the gathered notices resolve on their own within a day, so they are archived by the system before the digest is even written. A problem that fixes itself was never yours to do.

A day where everything fixed itself sends you nothing at all (2026-09-12). The digest is only written when at least one thing is still going on. It used to send a card on those days too — titled "everything sorted itself out", with nothing under it but a list of things that had already resolved — which is a card whose own title tells you not to bother reading it. The owner's rule: an alert that doesn't change what you'd do isn't worth sending.

The landing notices were skipping this until 2026-09-23. The hold that sends a notice to the daily digest lived inside one of the app's ways of raising a card, and the auto-lander raised its own cards another way. So "Auto-lander stuck", "a ready branch is blocked by a safety check" and the other landing notices listed above went straight to the inbox instead — 216 of them between 2026-09-08 and 2026-09-23, about 90% of which fixed themselves within minutes. The hold is now applied where every card is written, so no route can skip it.

"A branch was stopped past its time limit" joined the daily summary on 2026-09-24. Its own text always said the branch would be retried automatically — and it was: 24 of the last 30 such branches landed on that retry, typically about half an hour later — yet each card stayed until you archived it by hand. It is now one card per repository listing the branches, a branch comes off the moment it lands, and the card is held for the daily summary, so a branch that lands on its retry never interrupts you and one that never lands is named every day until it does.

Three changes on 2026-09-25. "Auto-lander is not reaching your waiting branches" now waits for the digest (29 of its last 32 cleared themselves within minutes), while unfinished work nobody is coming back for and a blocked branch landed with its author's sign-off keep their own card. A notice still going on after a day stays in the digest, named every day until it stops, instead of arriving as its own card two days in (17 did, 2026-09-10 to 09-25). A one-off heads-up that can never "clear", such as a landed branch had no session to notify, counts as settled and sends no card.

Six more kinds of notice joined the digest on 2026-09-28, and they are the ones there was genuinely nothing for you to do about. A session that could not be resumed after a restart (it tries again next time the app restarts), idle sessions parked to protect memory (that protection stands down by itself), a test result that could not be delivered (the retry and the session's own end handle it), the test-baseline readout (a status, not a fault), cloud test-machine faults repeating (the fix is a developer's command), and guard-baseline debt growing on master (also a developer's job). Measured over the eight days before the change: 57 of these arrived, every one cleared by hand, and not one of them cleared itself.

The readout is worth knowing about: it reports the result of each test-baseline run, and the digest shows the latest one each day rather than each run as it finishes. It also reports on healthy runs, so its card appears most days — that is deliberate, it is the one item here you asked to keep seeing.

On 2026-09-29 the digest took on the biggest cluster of all: the background machinery talking about itself. A daily card called Background jobs and housekeeping now carries the scheduled jobs that have not finished a run (they are still being tried on their normal schedule), the ones that keep failing (the notice clears the moment one run succeeds), a job that has stopped being attempted, a fleet status that keeps re-paging, background jobs using more of the computer than they should, a "still working" notice for a long wait, a tool with a newer version, and the "this PR body needs its real delta" / "nobody picked up that PR reply" cards — both of which tell a developer to run a command, not you. Alongside them, six finishing-touches notices from the workspace machinery ("a step is running but achieving nothing", "a step is running slow", a reclaim backlog, slow workspace creation, a git repository growing). Measured over the seven days before the change: these families sent 369 cards and every one was cleared by hand; not one of them cleared itself, and none of them asked you anything.

What deliberately stayed a card, because the rule says so rather than because of how loud it is: everything about your money (including the metered-vendor spend cards, which now come with a fix — see below), every notice that carries a button you can press, work that is at risk of being lost, and the performance notices. Those last ones are the app telling you your own machine was slow, and slowing you down to say it would be the wrong trade — so they keep arriving when they happen.

One bug came out of this pass. The "spend today is unusually high" card for a metered vendor was coming back every 15–45 minutes after you had already dismissed it — six fresh cards for one provider in a single day, each one closed by hand in the seven days measured. The cause was in the card's own escape hatch: a runaway is allowed to skip the once-a-day wait so it is never missed, and that skip was also skipping your dismissal. A card you close now stays closed for the rest of that day, and a genuinely new runaway another day still arrives immediately.

Nothing is hidden by that. The self-resolved notices are not thrown away — they are held, and if something real does turn up later the same day, that day's card appears with the new item and the earlier self-resolved ones listed under it. A condition that keeps happening is named in every day's digest until it stops, and if the digest itself ever stops running, the individual cards come straight back — the system is built to fail toward noise, never toward silence.

Turning a built-in notice off from the card

Omniscio's own advisory cards each carry a quiet "Turn off these notices" button in the card's bottom bar (pushed far left so it never out-shouts the card's main action). One click turns that one kind of card off, confirms with a toast, and archives the card; Settings → Notifications has the matching toggle to turn it back on. Turning a notice off only silences the card — the underlying work keeps running (turning off the load-balancing notice does not stop sessions being spread across your logins, exactly as turning off the stuck-install notice does not stop the cleanup).

Cards with their own off-switch today: low-memory warnings, AI-cost alerts, stuck-install cleanup notices, screen-grab block notices, inbox-backlog nudges, watch-time recaps, input-method shortcut warnings, and session load-balancing notices (the "your <login> is carrying N active sessions" card — loadBalancingNoticeEnabled). Alerts that report a genuine failure you need to act on — reconnect Gmail, sign in again, a dead keyboard shortcut — deliberately have no per-card off-switch, so a real problem can't be silently hidden.

Relationship to Drip

Drip is a second producer of the same primitive. On release, the scanner calls the same createAlert service, writing rows into the shared inbox_alert_items table with source_kind = 'drip'. Those rows surface via the drip inbox integration (listDripSourcedInboxItems, filtered by source_kind), NOT the alert integration — each agent-raised row has source_kind = 'agent', so the two integrations never overlap in the inbox.

A drip-sourced alert reuses its drip_item's id, so the inbox row (alert) and Drip's detail-view row (drip_item) are addressable by one id: DRIP_ITEM_ARCHIVE/UNARCHIVE dual-write both tables to keep the unified inbox and DripDetailView in sync. The synthetic "drip finished" completion row is the one item that stays a drip_item only (no alert), so it remains excluded from the cross-drip inbox as before. See inbox-alert-contract.md (I9, I10).

Related

What an alert is and how you answer one is the Inbox alerts page, and how one is authored is part 2. The rules that can suppress alerts automatically are on Inbox rules.

Last verified 2026-10-05