Event System
Event triggers let workflows react to things that happen -- emails arriving, workflows completing, platform changes detected. Events are the connective tissue between workflows, enabling fan-out pipelines and multi-agent coordination.
Event sources
Events can originate from four distinct sources, each representing a different mechanism for producing events within the system.
| Source | Mechanism | Example values |
|---|---|---|
| emit | Activity calls emit tool | email.customer-service, lead.qualified |
| watch | Watch trigger auto-emits | gws.email.new, gws.calendar.changed |
| platform | Platform capabilities | calendar.changed, email.received |
| system | Nebo lifecycle | workflow.{id}.completed, workflow.{id}.failed |
Event envelope
Every event flows through the system wrapped in a standard envelope. The payload becomes the triggered workflow's inputs, available as _event_payload.
{
"source": "email.customer-service",
"payload": { "from": "j@example.com", "subject": "Order issue" },
"origin": "workflow:email-triage:run-550e8400",
"timestamp": 1709740800
}| Field | Type | Description |
|---|---|---|
source | string | Dot-namespaced event identifier used for matching against trigger sources |
payload | object | Arbitrary JSON data passed as _event_payload to the triggered workflow's inputs |
origin | string | Trace identifier showing where the event was produced (workflow:{binding}:run-{id}) |
timestamp | number | Unix epoch seconds when the event was emitted |
Source matching
Event triggers use the sources array to define which events activate the workflow. Patterns support exact match and trailing wildcard.
| Pattern | Matches |
|---|---|
email.urgent | Exact match only |
email.* | Any event starting with email. |
A trigger configuration using source matching:
{
"type": "event",
"sources": ["email.urgent", "calendar.*"]
}sources array is valid JSON but the trigger will never fire -- always include at least one source.Emit namespace
Events emitted from activities use the source name as-is -- there is no automatic namespace prefix. Activities must include the full source name to avoid collisions across agents.
emit(source: "executive-assistant.briefing.ready", payload: {...})Workflow-level emit
In addition to the emit tool inside activities, workflow bindings support a top-level emit field that fires automatically on completion:
{
"workflows": {
"morning-briefing": {
"trigger": { "type": "schedule", "cron": "0 7 * * *" },
"description": "Daily morning briefing",
"activities": [...],
"emit": "briefing.ready"
}
}
}By convention, prefix with the agent slug (e.g., executive-assistant.briefing.ready) to avoid cross-agent collisions.
Emitting from activities
The emit tool is a built-in tool always available inside activities. It emits an event into the EventBus, where other agents can subscribe to it.
emit(source: "executive-assistant.email.customer-service", payload: {
"from": "j@example.com",
"subject": "..."
})Each emit call is a discrete event. If an activity emits 5 events, each fires independently against all active subscriptions. This enables fan-out pipelines.
Built-in tools
Two tools are always available inside activities:
| Tool | Purpose |
|---|---|
emit | Emit an event into the EventBus. Other agents can subscribe to it. |
exit | Stop the workflow early with a reason (clean termination, not failure). |
System events
The system automatically emits lifecycle events for every workflow run. These enable workflow chaining -- when workflow A completes, workflow B can trigger.
| Event | When |
|---|---|
workflow.{id}.completed | A workflow run finishes successfully |
workflow.{id}.failed | A workflow run fails |
System events enable workflow chaining: A completes, then B triggers. For example, a workflow can subscribe to workflow.morning-briefing.completed to run after the morning briefing finishes.
Progress events
In addition to completion events, the engine emits progress events for live monitoring. These events are broadcast over WebSocket, enabling the UI to show real-time workflow progress.
| Event | When | Payload |
|---|---|---|
ActivityStarted | Each activity begins executing | Activity ID, workflow run ID |
TaskUpdated | During activity execution | Progress details, current activity state |
Event-only watches
A watch with event set but no activities is valid. It relays plugin output into the EventBus without processing anything inline. This is useful for decoupling event production from event consumption -- one agent produces events, other agents subscribe and react.
{
"email-relay": {
"trigger": {
"type": "watch",
"plugin": "gws",
"event": "email.new",
"command": "gmail +watch --format ndjson"
},
"description": "Relay new email events into the EventBus"
}
}Other agents subscribe to the emitted events:
{
"handle-emails": {
"trigger": { "type": "event", "sources": ["gws.email.new"] },
"activities": [...]
}
}event is set on a watch trigger, each NDJSON line output by the plugin auto-emits into the EventBus as {plugin}.{event} (e.g., gws.email.new).Example: email triage pipeline
A complete multi-workflow pipeline using events. The watcher monitors Gmail, triage classifies and emits, and each emission triggers the appropriate handler with the email data as inputs.
{
"workflows": {
"email-watcher": {
"trigger": {
"type": "watch",
"plugin": "gws",
"event": "email.new",
"command": "gmail +watch --format ndjson"
},
"description": "React to new emails in real-time",
"activities": [
{
"id": "triage",
"intent": "Classify the incoming email and route it",
"steps": [
"Read the email content from _watch_payload",
"Classify as: customer-service, sales-inquiry, or ignore",
"If not ignore, emit the appropriate event with email data"
],
"token_budget": { "max": 2048 }
}
],
"budget": { "total_per_run": 2048 }
},
"handle-cs": {
"trigger": { "type": "event", "sources": ["email.customer-service"] },
"description": "Handle customer service emails",
"activities": [
{
"id": "respond",
"intent": "Draft a helpful response to the customer service email",
"skills": ["@acme/skills/cs-templates"],
"token_budget": { "max": 3000 }
}
],
"budget": { "total_per_run": 3000 }
},
"handle-sales": {
"trigger": { "type": "event", "sources": ["email.sales-inquiry"] },
"description": "Handle inbound sales inquiries",
"activities": [
{
"id": "qualify",
"intent": "Qualify the lead and add to CRM",
"skills": ["@acme/skills/lead-qualification"],
"token_budget": { "max": 3000 }
}
],
"budget": { "total_per_run": 3000 }
}
}
}Read top to bottom: the watcher monitors Gmail. For each email, triage classifies and emits. Each emission triggers the appropriate handler with the email data as inputs.
Validation rules
The following validation rules apply to event-related workflow configuration. Invalid bindings are skipped with a warning -- they do not prevent the agent from loading.
- Each binding must have a
trigger(required) - Either
reforactivitiesmust be present (one defines what runs) - Trigger
typemust be one of:schedule,heartbeat,event,watch,folder,manual - Event triggers should have at least one entry in
sources - Watch triggers must have a
pluginand eithereventorcommand - Activity IDs must be unique within each binding
- If
budget.total_per_run > 0, the sum of all activitytoken_budget.maxmust not exceed it - All
{{key}}placeholders in commands must match an inputkeyexactly - An Agent with no workflows is valid (chat-only + persona + skills)
Related pages
- Workflows Overview -- workflow binding structure, budget math, and the full workflow model
- Activities -- how activities execute, context chaining, error handling, and cancellation
- Triggers -- schedule, heartbeat, event, watch, and manual trigger types