Skip to main content
Notification policy lets you decide, per recipient, what the gateway may notify you about — filter by category, honour quiet hours in the recipient’s own timezone, and redact content before it leaves the gateway. Set it once in gateway.yaml and every scheduled agent result is gated for you.
This is the policy layer — the whether and what to notify. It sits above Push Delivery Store (delivery guarantee) and before Real-Time Push Notifications (transport).

Quick Start

1

Turn it on with one YAML block

Add gateway.notifications to your gateway.yaml. Scheduled agent results are now gated automatically.
With these defaults a scheduled ops-agent result (agent_finished) is not opted in, so it is suppressed.
2

Set a per-recipient preference

Key a recipient by platform:channel_id and override any default.
telegram:12345 now receives full scheduled results, except between 22:00 and 07:30 London time.
3

Validate and hot-reload

A bad entry fails at startup, not at send time:
Edit gateway.yaml and reload. The new policy applies without a restart:

How Categorised Sends Flow Through the Gateway

Every proactive send that carries a category passes through the gate before the idempotency check and the channel.
  • Suppressed sends count as handled — deliver() returns True, so the scheduler and outbox do not retry, and no idempotency key is claimed.
  • Uncategorised sends are never gated — direct replies, hook replies and restart notices pass straight through.
  • Quiet hours use the recipient’s timezone — the gate passes an aware UTC now, so the window is checked in the recipient’s timezone, not the gateway host’s clock.
  • No notifications block, no gating — behaviour is unchanged when the block is absent.

What Gets Categorised Automatically

Today only scheduled results (agent_finished) are wired. The gateway does not mark sends as urgent, so urgent_overrides_quiet has no effect on gateway sends yet — it applies when you call evaluate(..., is_urgent=True) yourself.

Where Recipients Come From

A recipient key is the resolved platform:channel_id of the delivery target — the same pair the gateway logs on each proactive send (Delivered scheduled result to telegram:12345, or notification suppressed for telegram:12345 when gated). A recipient with no entry falls back to defaults, and anything missing from defaults falls back to DEFAULT_PREFERENCE.

The Three Concerns

Categories

A recipient opts into each event class independently; an un-opted category is suppressed with reason category_not_subscribed.

Quiet Hours

A per-recipient local window suppresses non-urgent categories; urgent events override when urgent_overrides_quiet is true.
The window is half-open [start, end). When start == end the window is empty (never quiet). When start > end it wraps past midnight — (22:00, 07:30) means 22:00–24:00 ∪ 00:00–07:30. A naive now is treated as already local; an aware now is converted to prefs.timezone via stdlib zoneinfo. When calling evaluate() directly, an unknown timezone name is ignored silently and now is left unconverted (so an aware UTC now is checked in UTC); the gateway rejects it at config load instead.

Detail Levels

Three levels control how much content leaves the gateway.

Which Detail Level Should I Pick?

Match the level to how trusted the destination surface is.

YAML Reference

All keys live under gateway.notifications.
<entry> is defaults or any recipients entry. A key missing from a recipient entry inherits from defaults; a key missing from defaults inherits from DEFAULT_PREFERENCE.

Validation and Hot-Reload

  • Fails at startup — loading gateway.yaml builds the gate, so an unknown category or detail, a malformed quiet_hours, or an unknown timezone raises before the gateway serves traffic.
  • Published schema — praisonai gateway schema prints the JSON schema, including the notifications block, for editor validation.
  • Hot-reload — on file change or SIGHUP, the gateway applies the new notifications block to the live delivery router. Removing the block clears gating. See Gateway Hot Reload.

Under the Hood: evaluate()

The gate wraps one pure function, which you can also call from your own notifier before delivery; the decision carries the send flag and the already-redacted content. evaluate() is pure and side-effect-free, so any process can call it at delivery-decision time without I/O. Pass prefs=None to fall back to DEFAULT_PREFERENCE; pass now=None to use the host’s naive local clock (prefs.timezone is not applied). To honour the recipient’s timezone, pass an aware now such as datetime.now(timezone.utc), as the gateway does.

Python Reference

NotificationPreference

Also exposes .allows(category) → bool, whether this recipient opted into category.

NotificationDecision

evaluate(...)

DEFAULT_PREFERENCE

Applied when a recipient has set nothing: the two interaction-blocking categories are on, at the identified (no-content) detail level, with no quiet hours.

Common Patterns

Mute scheduled results overnight, keep approvals — agent_finished is suppressed inside the window.
Privacy-first mobile recipient — detail: private so the lock-screen never shows content.
The patterns below call evaluate() directly from your own notifier. Gate a scheduler completion notification — the recipient opts into AGENT_FINISHED, and it is not urgent.
Approval request during quiet hours — the recipient is inside their window, but the urgent flag overrides it.
Privacy-first mobile push — DetailLevel.PRIVATE so the lock-screen never shows content.

Best Practices

The gateway looks up each send by the resolved platform:channel_id (e.g. telegram:12345). A key that does not match — a username, a channel name — silently falls back to defaults. Copy the pair from the gateway’s delivery log line.
Edit gateway.yaml and save, or send SIGHUP / systemctl reload praisonai-gateway. The new policy is applied to the live delivery router without restarting the gateway.
Match the default detail to where notifications land. Use PRIVATE for mobile lock-screens where anyone nearby can read the preview, IDENTIFIED for shared team channels that route by agent, and DETAILED only for trusted single-user desktops. DEFAULT_PREFERENCE uses IDENTIFIED as a safe middle ground.
Treat APPROVAL_REQUESTED and AGENT_QUESTION as urgent by convention — they block an agent from continuing until you answer. Pass is_urgent=True for these so quiet hours never strand a paused agent. Keep failure alerts (SCHEDULED_TASK_FAILED, BACKGROUND_TASK_FAILED) non-urgent unless your operations demand overnight paging.
The window is half-open [start, end). A same-day window needs start < end (e.g. (09:00, 17:00)); an overnight window needs start > end (e.g. (22:00, 07:30)), which wraps across midnight. Setting start == end produces an empty window — never quiet. Always set timezone to the recipient’s IANA zone and pass an aware now so it localises correctly; a naive (or None) now is assumed already local, and an unknown zone leaves now unconverted.
Lower detail protects content but forces the recipient to open the app to act. If a notification only exists to prompt an approval, IDENTIFIED is usually enough — the recipient knows which agent to open. Reserve DETAILED for events where the content itself is the action (e.g. a failure stack trace) and the destination is trusted.

User Interaction Flow

An operator sets a preference once in gateway.yaml; later scheduled results are gated automatically.

Real-Time Push Notifications

The PushClient / WebSocket transport this policy sits above

Push Delivery Store

The durable at-least-once delivery this policy composes with

Gateway Schedules

Scheduled jobs — the producer of agent_finished sends

Gateway Hot Reload

How file-change and SIGHUP reloads apply a new policy