Notification Presets
SQLite-backed notification fanout presets, cursor replay, filters, and bridge suppression.
Notification presets are daemon-owned fanout policies stored in notification_presets. They are
managed through HTTP, UDS, CLI, native tools, and the web settings surface; config.toml does not
define mutable preset rows.
Built-in preset definitions are shared. Enablement is resolved separately for each profile and defaults to enabled when no exception row exists:
| Preset | Events |
|---|---|
task_terminal | task.run_* |
session_unhealthy | session.unhealthy, session.hung, session.recovered |
provider_failure | provider.auth_required, provider.rate_limited, provider.permission_denied, provider.unavailable |
Each built-in carries default_version, default_hash, user_modified, and
default_update_available. Reconciliation updates untouched defaults and reports default drift
without overwriting operator-modified rows.
Dispatch Contract
Preset dispatch consumes the canonical event registry. A preset matches when:
- the event is notification-eligible,
- the event type matches one of the preset patterns, and
- the filter expression evaluates true.
Filter fields are severity, workspace, agent, event_type, provider, task, and run.
Operators are =, !=, >=, >, <=, and <; expressions support AND, OR, and
parentheses.
Suffix wildcards stay inside one public event family. The built-in task_terminal preset matches
notification-eligible task run events such as task.run_completed, task.run_failed, and
task.run_operator_retry; it does not match session or provider events.
Each concrete target occurrence uses a notification cursor. The cursor preserves the source event's
structured scope (global, or workspace plus workspace_id) and uses the event's exact ID as its
subject. All scope, bridge, route, peer, thread, group, and delivery identities are opaque valid UTF-8
values: clients preserve them byte-for-byte and never trim, split, case-fold, or reinterpret them.
scope = event.scope
consumer_id = opaque identity for this target occurrence
stream_name = event.type
subject_id = event.idPreset dispatch processes targets sequentially and applies the configured bridge delivery timeout to each target. The cursor advances only after confirmed delivery, deterministic skip, or bridge suppression. A delivery error records cursor diagnostics and leaves the cursor unchanged so replay can retry the same event.
The v1 dispatcher intentionally does not fan out one preset's targets in parallel. This keeps cursor ordering deterministic and makes each target's retry point explicit.
Duplicate targets are not collapsed. If a preset contains [T, T], it produces two ordered delivery
attempts and two independent cursors. A confirmed delivery or skip for the first occurrence never
advances the second occurrence.
For its --target input only, the CLI uses bridge_id:canonical_route: it separates the first
colon as syntax and preserves both resulting components verbatim. API and native-tool payloads carry
the two fields separately. Everything after the first CLI colon is the canonical route, so routes
such as channel:ops are valid.
Bridge Suppression
bridge_instances.notification_suppress is a live bridge flag. When enabled, preset dispatch treats
that bridge target as a deterministic skip, advances the target cursor, and performs no outbound
delivery. This keeps replay monotonic while letting operators suppress a noisy or degraded bridge
without deleting presets.
Bridge create/update exposes the flag as --notification-suppress, and API payloads project the
same notification_suppress field.
Manage Presets
The CLI group is agent-operable and supports -o json|jsonl|toon:
compozy notifications presets list -o json
compozy --profile marketing notifications presets list -o json
compozy notifications preset show task_terminal -o json
compozy --profile marketing notifications preset disable task_terminal -o json
compozy notifications preset create provider_failures --event provider.* --filter "severity >= warning" -o json
compozy --profile marketing notifications preset enable task_terminal -o json
compozy notifications preset delete provider_failures -o jsonHTTP and UDS expose the same CRUD contract:
| Method | Path |
|---|---|
GET | /api/notifications/presets |
POST | /api/notifications/presets |
GET | /api/notifications/presets/{name} |
PUT | /api/notifications/presets/{name} |
DELETE | /api/notifications/presets/{name} |
PUT | /api/notifications/presets/{name}/enablement |
GET /api/notifications/presets?profile=marketing returns each shared definition with that profile's
effective enabled value. The enablement mutation accepts {"profile":"marketing","enabled":false}.
Disabling a preset in one profile does not change delivery in another profile.
See also Notification Cursors for replay and cursor diagnostics.
Acknowledge notifications in the app
The header bell and Home's Needs you area offer Mark as read on each notification and Clear all for the current notification set. Acknowledgement persists across reloads and removes that occurrence from the list and count. A new attention occurrence can notify again.
The bell spans all workspaces and source profiles; read status belongs to the selected destination profile. Home uses its selected workspace (or Global) and profile view. Clear all includes rows beyond the visible slice, using the set captured when the list was loaded. Events that arrive later remain unread. If saving fails, the app retains/reloads the rows and displays an error.
Acknowledgement leaves the underlying work unchanged. Pending approvals, questions and terminal input remain available in Sessions, Tasks, Loops or Terminal. It does not approve, reject, retry, cancel, delete work, or change notification preset delivery rules.
Operator HTTP/UDS clients can read /api/notifications/attention?profile=default and submit
{ "snapshot": "<returned snapshot>", "id": "<occurrence id>" } to
/api/notifications/attention/acknowledge?profile=default. Omit id to acknowledge the full snapshot.
For Home, use its overview attention.snapshot and surface=home with the same workspace,
profile/all_profiles and receipt_profile parameters. A 409 requires refreshing the list;
snapshots expire after 24 hours. Acknowledgements are atomic and repeatable.