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.
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
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
Plugin name identifier (e.g. "desktop", "slack", "webhook").
notify
(event: OrchestratorEvent) => Promise<void>
required
Push a notification to the human.Parameters:
event - Orchestrator event with type, priority, session ID, message, data
notifyWithActions
(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
post
(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
OrchestratorEvent
export interface OrchestratorEvent {
id: string;
type: EventType;
priority: EventPriority;
sessionId: SessionId;
projectId: string;
timestamp: Date;
message: string;
data: Record<string, unknown>;
}
Event type (e.g. "session.needs_input", "ci.failing", "merge.ready")
Priority level: "urgent", "action", "warning", or "info"
Session that generated this event
data
Record<string, unknown>
required
Event-specific data (PR URL, error details, etc.)
EventType
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
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
export interface NotifyAction {
label: string;
url?: string;
callbackEndpoint?: string;
}
Button/link label (e.g. "View PR", "Approve", "Kill Session")
URL to open (for simple links)
API endpoint to call when action is clicked (for interactive buttons)
NotifyContext
export interface NotifyContext {
sessionId?: SessionId;
projectId?: string;
prUrl?: string;
channel?: string;
}
PR URL to include in message
Channel/thread to post to (Slack, Discord)
Usage Examples
Implementing a Notifier Plugin
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
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
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:
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