Notify
The Notify skill lets agents ping the operator when something happens or needs attention. Both operations are deterministic tools — they just write a row to the notifications table, which streams in real time to the bell icon in the Maestro topbar.
What this skill does
| Operation | Purpose | Status |
|---|---|---|
send_event | Log an informational notification — agent reporting that something happened. No human action expected. | Shipped |
send_attention | Send a notification that needs the operator’s review or action. Surfaces with attention-level styling. | Shipped |
When to use which
| Situation | Use |
|---|---|
| ”Cold-leads run complete: 3 emails sent” | send_event |
| ”Reply triage processed 7 threads” | send_event |
| ”3 cold-outreach drafts ready for review” | send_attention |
| ”Sarah Chen replied — needs response” | send_attention |
| ”Reply classified as needs_review (low confidence)“ | send_attention |
| ”Apollo enrichment skipped 2 candidates with missing emails” | send_event |
The rule of thumb: does the operator need to do something now? If yes, send_attention. If no, send_event. The bell badge only counts unread notifications, regardless of level — but attention-level rows render with a brass dot and the dropdown surfaces them more prominently.
Setup
No setup needed. The skill reads DATABASE_URL from the runtime’s environment and writes directly to the notifications table. No new secrets, no new keys, no new third-party services.
Inputs
Both operations take the same inputs:
| Field | Required | Notes |
|---|---|---|
title | yes | Under 80 chars, concrete. The operator scans these to verify the agent is working — vague titles waste their attention. |
message | no | One or two sentences for context. Shows below the title in the dropdown. |
link_url | no | Where to send the operator if they click the CTA. Relative URLs route within Maestro (/pipelines/pl_saas), absolute URLs (https://...) navigate the browser. |
link_label | no | The CTA button text. Defaults to “View” if link_url is set without a label. |
payload | no | Free-form JSON for downstream consumers. Not displayed. |
agent_id | no | Source agent — the runtime fills this when an agent calls the skill. |
run_id | no | Source run — same. |
Without link_url, the notification is a dead end (the operator sees it but can’t act on it). For send_attention especially, providing a link is strongly recommended — the whole point is to direct the operator to the thing that needs their action.
Output
{
notification: {
id: "ntf_abc123",
level: "event" | "attention",
title: "...",
message: "...",
link_url: "...",
link_label: "...",
created_at_ms: 1778010099136
}
}
The agent receives the inserted row’s id so it can reference the notification later if needed (rare).
How notifications appear
The bell icon in the topbar shows a numeric badge with the unread count (capped at “99+”). Clicking the bell opens a dropdown listing the most recent 30 notifications, newest first. Each row shows:
- A colored dot — brass for attention-level, neutral for event-level. Dimmed when the row has been read.
- The title in slightly bolder type
- The message below it (if present)
- Relative time (“just now”, “5m ago”, “2h ago”, “3d ago”, or a date for older)
- The CTA link if
link_urlis set
Hovering a row reveals a check icon (mark read) and a trash icon (dismiss). The bottom of the dropdown has a “Mark all read” affordance when there’s anything unread.
Real-time
Every insert and read-state update fires a pg_notify on the maestro_notifications channel. The api’s SSE hub forwards events to subscribed clients; the bell-icon UI invalidates its TanStack Query cache on every event, so notifications appear within ~1 second of the agent’s send_event / send_attention call.
Common usage patterns
Cold-leads run summary
After a cold-leads run finishes, fire one summary send_event:
await notify.send_event(
title="Cold-leads run complete",
message="3 emails sent · 0 errors",
link_url="/pipelines/pl_saas",
link_label="View pipeline",
)
Drafts ready for review (review mode)
When the cold-leads agent runs in review mode (drafts but doesn’t send), one send_attention per run is better than one per draft:
await notify.send_attention(
title="3 cold-outreach drafts ready for review",
message="Sarah Chen, Marcus Rodriguez, Priya Patel — open the contact pages to review and send.",
link_url="/pipelines/pl_saas",
link_label="Review drafts",
)
Reply triage escalations
When a reply classifies as interested or needs_review, fire one send_attention per contact so the operator can drill in:
await notify.send_attention(
title=f"{contact.name} replied — needs response",
message=f"Classified as '{intent}' (confidence {confidence:.0%}). {explanation}",
link_url=f"/pipelines/pl_saas/contacts/{contact.id}",
link_label="Open contact",
)
For out_of_office, wrong_person, unsubscribe, and clearly-not-interested replies, skip the notification — the operator doesn’t need to act, and noisy notifications train them to ignore the bell.
Failure modes
bad_input— title is required: pass a non-emptytitle.internal— DATABASE_URL is not set: env config issue. Should never happen on a normally-configured install.remote_unavailable: Postgres is down or the pool can’t open. Retryable.
Related
- Skills and tools — both notify operations are tools (deterministic), even though they live in a “skill package.”
- Skills overview — how skill packages work in general.
- Pipeline — agents typically chain
pipeline.log_activity+notify.send_attentionin the same step.