> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ComposioHQ/agent-orchestrator/llms.txt
> Use this file to discover all available pages before exploring further.

# Notifier

> Plugin interface for push notifications (desktop, Slack, webhooks) - the primary interface between orchestrator and humans

## Overview

The `Notifier` interface is the **primary interface between the orchestrator and the human**. Humans walk away after spawning agents; notifications bring them back when their judgment is needed.

**Core principle:** Push, not pull. The human never polls.

**Plugin Slot:** `notifier`\
**Default Plugin:** `desktop`

## Interface Definition

```typescript theme={null}
export interface Notifier {
  readonly name: string;
  
  notify(event: OrchestratorEvent): Promise<void>;
  notifyWithActions?(event: OrchestratorEvent, actions: NotifyAction[]): Promise<void>;
  post?(message: string, context?: NotifyContext): Promise<string | null>;
}
```

## Methods

<ResponseField name="name" type="string" required>
  Plugin name identifier (e.g. `"desktop"`, `"slack"`, `"webhook"`).
</ResponseField>

<ResponseField name="notify" type="(event: OrchestratorEvent) => Promise<void>" required>
  Push a notification to the human.

  **Parameters:**

  * `event` - Orchestrator event with type, priority, session ID, message, data
</ResponseField>

<ResponseField name="notifyWithActions" type="(event: OrchestratorEvent, actions: NotifyAction[]) => Promise<void>">
  Optional: Push a notification with actionable buttons/links.

  **Parameters:**

  * `event` - Orchestrator event
  * `actions` - Array of actions with labels and URLs/callbacks
</ResponseField>

<ResponseField name="post" type="(message: string, context?: NotifyContext) => Promise<string | null>">
  Optional: Post a message to a channel (for team-visible notifiers like Slack).

  **Parameters:**

  * `message` - Message text
  * `context` - Optional context (session ID, project ID, PR URL, channel)

  **Returns:** Message ID/URL if posted, null otherwise
</ResponseField>

## Related Types

### OrchestratorEvent

```typescript theme={null}
export interface OrchestratorEvent {
  id: string;
  type: EventType;
  priority: EventPriority;
  sessionId: SessionId;
  projectId: string;
  timestamp: Date;
  message: string;
  data: Record<string, unknown>;
}
```

<ParamField path="id" type="string" required>
  Unique event identifier
</ParamField>

<ParamField path="type" type="EventType" required>
  Event type (e.g. `"session.needs_input"`, `"ci.failing"`, `"merge.ready"`)
</ParamField>

<ParamField path="priority" type="EventPriority" required>
  Priority level: `"urgent"`, `"action"`, `"warning"`, or `"info"`
</ParamField>

<ParamField path="sessionId" type="SessionId" required>
  Session that generated this event
</ParamField>

<ParamField path="projectId" type="string" required>
  Project identifier
</ParamField>

<ParamField path="timestamp" type="Date" required>
  When event occurred
</ParamField>

<ParamField path="message" type="string" required>
  Human-readable message
</ParamField>

<ParamField path="data" type="Record<string, unknown>" required>
  Event-specific data (PR URL, error details, etc.)
</ParamField>

### EventType

```typescript theme={null}
export type EventType =
  // Session lifecycle
  | "session.spawned" | "session.working" | "session.exited"
  | "session.killed" | "session.stuck" | "session.needs_input"
  | "session.errored"
  // PR lifecycle
  | "pr.created" | "pr.updated" | "pr.merged" | "pr.closed"
  // CI
  | "ci.passing" | "ci.failing" | "ci.fix_sent" | "ci.fix_failed"
  // Reviews
  | "review.pending" | "review.approved" | "review.changes_requested"
  | "review.comments_sent" | "review.comments_unresolved"
  // Automated reviews
  | "automated_review.found" | "automated_review.fix_sent"
  // Merge
  | "merge.ready" | "merge.conflicts" | "merge.completed"
  // Reactions
  | "reaction.triggered" | "reaction.escalated"
  // Summary
  | "summary.all_complete";
```

### EventPriority

```typescript theme={null}
export type EventPriority = "urgent" | "action" | "warning" | "info";
```

* **urgent** - Immediate attention required (session stuck, CI failing repeatedly)
* **action** - Human decision needed (merge ready, changes requested)
* **warning** - Something to be aware of (session exited, CI fix sent)
* **info** - FYI only (session spawned, PR created)

### NotifyAction

```typescript theme={null}
export interface NotifyAction {
  label: string;
  url?: string;
  callbackEndpoint?: string;
}
```

<ParamField path="label" type="string" required>
  Button/link label (e.g. `"View PR"`, `"Approve"`, `"Kill Session"`)
</ParamField>

<ParamField path="url" type="string">
  URL to open (for simple links)
</ParamField>

<ParamField path="callbackEndpoint" type="string">
  API endpoint to call when action is clicked (for interactive buttons)
</ParamField>

### NotifyContext

```typescript theme={null}
export interface NotifyContext {
  sessionId?: SessionId;
  projectId?: string;
  prUrl?: string;
  channel?: string;
}
```

<ParamField path="sessionId" type="SessionId">
  Session ID for context
</ParamField>

<ParamField path="projectId" type="string">
  Project ID for context
</ParamField>

<ParamField path="prUrl" type="string">
  PR URL to include in message
</ParamField>

<ParamField path="channel" type="string">
  Channel/thread to post to (Slack, Discord)
</ParamField>

## Usage Examples

### Implementing a Notifier Plugin

```typescript theme={null}
import type { Notifier, OrchestratorEvent, NotifyAction } from "@composio/ao-core";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

export function create(): Notifier {
  return {
    name: "desktop",
    
    async notify(event: OrchestratorEvent): Promise<void> {
      const title = `[${event.priority.toUpperCase()}] ${event.type}`;
      const body = `${event.message}\n\nSession: ${event.sessionId}`;
      
      // macOS notification
      await execFileAsync("osascript", [
        "-e",
        `display notification "${body}" with title "${title}"`
      ], { timeout: 5_000 });
    },
    
    async notifyWithActions(event: OrchestratorEvent, actions: NotifyAction[]): Promise<void> {
      // For desktop, open URLs in browser
      await this.notify(event);
      
      if (actions.length > 0 && actions[0].url) {
        await execFileAsync("open", [actions[0].url], { timeout: 5_000 });
      }
    }
  };
}
```

### Slack Notifier Example

```typescript theme={null}
import type { Notifier, OrchestratorEvent, NotifyAction, NotifyContext } from "@composio/ao-core";

export function create(config: { webhookUrl: string; channel?: string }): Notifier {
  return {
    name: "slack",
    
    async notify(event: OrchestratorEvent): Promise<void> {
      const color = {
        urgent: "danger",
        action: "warning",
        warning: "warning",
        info: "good"
      }[event.priority];
      
      await fetch(config.webhookUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          channel: config.channel,
          attachments: [{
            color,
            title: event.type,
            text: event.message,
            fields: [
              { title: "Session", value: event.sessionId, short: true },
              { title: "Project", value: event.projectId, short: true }
            ],
            footer: "Agent Orchestrator",
            ts: Math.floor(event.timestamp.getTime() / 1000)
          }]
        })
      });
    },
    
    async notifyWithActions(event: OrchestratorEvent, actions: NotifyAction[]): Promise<void> {
      await fetch(config.webhookUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          channel: config.channel,
          text: event.message,
          attachments: [{
            callback_id: event.id,
            actions: actions.map(a => ({
              type: "button",
              text: a.label,
              url: a.url,
              value: a.callbackEndpoint
            }))
          }]
        })
      });
    },
    
    async post(message: string, context?: NotifyContext): Promise<string | null> {
      const response = await fetch(config.webhookUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          channel: context?.channel || config.channel,
          text: message,
          context: context ? {
            session: context.sessionId,
            project: context.projectId,
            pr: context.prUrl
          } : undefined
        })
      });
      
      return response.ok ? "posted" : null;
    }
  };
}
```

### Using Notifiers in Lifecycle Manager

```typescript theme={null}
import type { Notifier, OrchestratorEvent } from "@composio/ao-core";

// Get notifiers for this priority level
const priority = "urgent";
const notifierNames = config.notificationRouting[priority]; // ["desktop", "slack"]
const notifiers = notifierNames.map(name => registry.get("notifier", name));

// Create event
const event: OrchestratorEvent = {
  id: crypto.randomUUID(),
  type: "session.needs_input",
  priority: "urgent",
  sessionId: session.id,
  projectId: session.projectId,
  timestamp: new Date(),
  message: `Session ${session.id} is waiting for your input`,
  data: {
    workspacePath: session.workspacePath,
    branch: session.branch
  }
};

// Notify all configured notifiers
await Promise.all(notifiers.map(n => n.notify(event)));

// Or with actions
const actions = [
  { label: "Open Session", url: `http://localhost:3000/sessions/${session.id}` },
  { label: "Kill", callbackEndpoint: `/api/sessions/${session.id}/kill` }
];

for (const notifier of notifiers) {
  if (notifier.notifyWithActions) {
    await notifier.notifyWithActions(event, actions);
  } else {
    await notifier.notify(event);
  }
}
```

## Implementation Notes

### Notification Routing

Notifications are routed by priority in `agent-orchestrator.yaml`:

```yaml theme={null}
notificationRouting:
  urgent: [desktop, slack]      # Immediate attention
  action: [desktop]             # Human decision needed
  warning: [slack]              # FYI for team
  info: []                      # No notifications
```

### Rate Limiting

Notifiers should implement rate limiting to avoid spam:

* Group similar events (multiple CI failures)
* Debounce rapid events (agent activity)
* Respect platform limits (Slack API rate limits)

### Error Handling

Notifiers should fail gracefully:

* Log errors but don't throw (notification failure shouldn't crash orchestrator)
* Retry with backoff for transient failures
* Fall back to simpler notifiers (desktop if Slack fails)

### Built-in Plugins

* **desktop** - macOS/Linux desktop notifications (default)
* **slack** - Slack webhook/API integration
* **composio** - Composio notification API
* **webhook** - Generic webhook POST

Future plugins could support Discord, Email, SMS, PagerDuty, etc.

## See Also

* [Session](/api/session) - Session interface
* [Terminal](/api/terminal) - Terminal UI interface


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.