Maestro

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

OperationPurposeStatus
send_eventLog an informational notification — agent reporting that something happened. No human action expected.Shipped
send_attentionSend a notification that needs the operator’s review or action. Surfaces with attention-level styling.Shipped

When to use which

SituationUse
”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:

FieldRequiredNotes
titleyesUnder 80 chars, concrete. The operator scans these to verify the agent is working — vague titles waste their attention.
messagenoOne or two sentences for context. Shows below the title in the dropdown.
link_urlnoWhere to send the operator if they click the CTA. Relative URLs route within Maestro (/pipelines/pl_saas), absolute URLs (https://...) navigate the browser.
link_labelnoThe CTA button text. Defaults to “View” if link_url is set without a label.
payloadnoFree-form JSON for downstream consumers. Not displayed.
agent_idnoSource agent — the runtime fills this when an agent calls the skill.
run_idnoSource 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_url is 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-empty title.
  • 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.
  • 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_attention in the same step.