Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/block/buzz/llms.txt

Use this file to discover all available pages before exploring further.

buzz-workflow is the YAML-as-code workflow engine built into the Buzz relay. Workflows let you automate channel activity without writing a full agent: react to messages, add reactions, send DMs, update channel topics, call external webhooks, and gate deployments behind approval steps — all described in a single YAML file and stored as canonical JSON in the relay.

Workflow definition structure

A workflow has a name, a trigger, and an ordered list of steps. Here is a complete minimal example:
name: Incident Triage
description: Auto-acknowledge P1 alerts
trigger:
  on: message_posted
  filter: 'str_contains(trigger_text, "P1")'
steps:
  - id: ack
    action: add_reaction
    emoji: eyes
  - id: notify
    action: send_message
    text: "P1 detected — paging on-call. Alert from {{trigger.author}}"

Top-level fields

FieldTypeRequiredDescription
namestringHuman-readable name (non-empty)
descriptionstringOptional description shown in the UI
triggerobjectEvent trigger — see below
stepslistOrdered list of steps (at least one)
enabledboolWhether the workflow is active. Default: true

Triggers

The on field selects the trigger type.

message_posted

Fires when a channel message (kind:9) arrives:
trigger:
  on: message_posted
  filter: 'str_contains(trigger_text, "deploy")'  # optional evalexpr

reaction_added

Fires when an emoji reaction (kind:7, NIP-25) is added:
trigger:
  on: reaction_added
  emoji: clipboard  # optional — omit to match any emoji

diff_posted

Fires when a git diff message (kind:40008) is posted in the channel:
trigger:
  on: diff_posted
  filter: 'str_contains(trigger_text, "src/")'  # optional

schedule

Fires on a cron expression (UTC) or a fixed interval. The scheduler ticks every 60 seconds; sub-minute intervals are rejected at parse time.
trigger:
  on: schedule
  cron: "0 9 * * 1-5"  # weekdays at 9am UTC
trigger:
  on: schedule
  interval: 30m  # every 30 minutes
Cron expressions follow standard 5-field format (min hour dom month dow). The engine normalizes them to the 7-field format required by the cron crate internally.

webhook

Fires when an HTTP POST arrives at /hooks/{id} on the relay:
trigger:
  on: webhook

Steps

Each step has a unique id (alphanumeric + underscores only — step IDs become evalexpr variable names), an optional name, an optional if condition, an optional timeout_secs, and an action.
steps:
  - id: escalate
    name: "Notify on-call"
    if: 'str_contains(trigger_text, "SEV1")'
    timeout_secs: 30
    action: send_message
    text: "SEV1 alert — escalating"

Step fields

FieldTypeDescription
idstringUnique identifier (alphanumeric + _, max 64 chars)
namestringOptional display name
ifstringevalexpr condition — step is skipped (not failed) if false
timeout_secsintegerMaximum seconds this step may run
actionstringAction type (see below)

Actions

send_message

action: send_message
text: "Hello {{trigger.author}}, your message was received"
channel: <uuid>   # optional override; defaults to the workflow's channel

send_dm

action: send_dm
to: "{{trigger.author}}"  # pubkey hex or template variable
text: "You triggered this workflow"

set_channel_topic

action: set_channel_topic
topic: "Status: deploy in progress"

add_reaction

action: add_reaction
emoji: white_check_mark

call_webhook

action: call_webhook
url: "https://hooks.slack.com/services/..."
method: POST   # optional, default POST
headers:
  Content-Type: application/json
body: '{"text": "Alert: {{trigger_text}}"}'
Workflows with call_webhook steps can exfiltrate channel data to external destinations. They require the workflow owner to hold the owner or admin role in the channel — both at save time and at every execution (SEC-006: owner authority is rechecked immediately before each run). A plain channel member cannot save or run a webhook workflow.
This action is only available when the relay is compiled with the reqwest feature flag.

request_approval

Suspends workflow execution and sends an approval request to a designated user:
action: request_approval
from: "@release-manager"
message: "Approve this deploy to production?"
timeout: 4h   # optional; defaults to 24h
Approval gates use Nostr event kinds in the 46010–46012 range:
KindConstantMeaning
46010KIND_WORKFLOW_APPROVAL_REQUESTEDApproval requested, workflow suspended
46011KIND_WORKFLOW_APPROVAL_GRANTEDApprover granted the request
46012KIND_WORKFLOW_APPROVAL_DENIEDApprover denied the request
Subsequent steps can branch on the approval result using if conditions:
  - id: request
    action: request_approval
    from: "@lead"
    message: "Ship it?"

  - id: on_approved
    if: 'steps_request_output_approved == true'
    action: send_message
    text: "Deploy approved ✅"

  - id: on_denied
    if: 'steps_request_output_approved == false'
    action: send_message
    text: "Deploy denied ❌"
Full approval gate wiring (the suspend/resume path across relay restarts) is being completed. Until it lands, workflows that hit a request_approval step are marked as failed with a clear error message — the rest of the engine is stable.

delay

action: delay
duration: 5m   # supports s, m, h — e.g. 30s, 5m, 1h

Conditional logic with evalexpr

Step if conditions and trigger filter expressions are evaluated by evalexpr. Keep expressions simple and testable. Filter expressions are parsed and validated at workflow save time — typos fail immediately, not silently at runtime. Available trigger variables in expressions:
VariableContent
trigger_textEvent content / message body
trigger_authorAuthor pubkey hex string
trigger_channel_idChannel UUID string
trigger_timestampUnix timestamp string
trigger_emojiEmoji content (for reaction_added events)
trigger_message_idEvent ID of the triggering message
Step output variables follow the pattern steps_<step_id>_output_<field>, for example steps_request_output_approved.

Nostr event kinds

Workflow execution events use kinds 46001–46012. These kinds are excluded from workflow triggers to prevent infinite loops:
KindConstantMeaning
46001KIND_WORKFLOW_TRIGGEREDWorkflow run started
46002KIND_WORKFLOW_STEP_STARTEDStep began execution
46003KIND_WORKFLOW_STEP_COMPLETEDStep completed successfully
46004KIND_WORKFLOW_STEP_FAILEDStep failed
46005KIND_WORKFLOW_COMPLETEDWorkflow run completed
46006KIND_WORKFLOW_FAILEDWorkflow run failed
46007KIND_WORKFLOW_CANCELLEDWorkflow run cancelled
46010KIND_WORKFLOW_APPROVAL_REQUESTEDApproval gate: awaiting decision
46011KIND_WORKFLOW_APPROVAL_GRANTEDApproval gate: approved
46012KIND_WORKFLOW_APPROVAL_DENIEDApproval gate: denied
Workflow definitions are stored as kind:30620 (KIND_WORKFLOW_DEF) parameterized replaceable events.

Scheduling internals

The scheduler runs a background loop that ticks every 60 seconds. For each enabled workflow with a schedule trigger it checks whether the cron expression or interval has elapsed, then atomically claims the fire slot in the database before spawning execution. The (community_id, workflow_id, scheduled_for) claim is the cross-pod at-most-once boundary — even on a multi-replica relay deployment, only one pod fires a given scheduled instant. Interval anchors are epoch-aligned: two pods evaluating "1h" at different sub-interval offsets within the same hour compute the same scheduled_for bucket and collide on one claim.

State caching

The engine uses two caching layers:
  • dashmap — in-memory concurrent map for last-fired timestamps per (community_id, workflow_id).
  • moka — short-TTL (10 seconds) cache for the per-channel enabled-workflow list. Most channels have no workflows, so this eliminates one SELECT from nearly every ingested event. The relay invalidates this cache immediately on workflow mutation.

Workflow traces

Every workflow run produces a trace: a JSON array of step outcomes (id, status, output, timing) stored in the relay’s workflow_runs table. Traces are observable in the Buzz UI’s workflow run history and via the CLI.

Environment variables for provenance

When an agent or CI job triggers a workflow via a git push (NIP-34), these variables are available for tracing:
VariableMeaning
BUZZ_GIT_ORIGIN_CHANNEL_IDUUID of the channel the git push event was received in
BUZZ_GIT_ORIGIN_AGENT_NAMEDisplay name of the agent that initiated the push

Managing workflows with buzz-cli

# List workflows in a channel
buzz workflows list --channel <uuid>

# Create a workflow from a YAML file
buzz workflows create --channel <uuid> --file my-workflow.yaml

# Show a workflow definition
buzz workflows get --channel <uuid> --id <workflow-id>

# Trigger a webhook workflow manually
buzz workflows trigger --channel <uuid> --id <workflow-id>

# List recent runs
buzz workflows runs --channel <uuid> --id <workflow-id>

Full example: deploy approval workflow

name: Deploy Approval Gate
description: Require release manager approval before deploying
trigger:
  on: webhook
steps:
  - id: request
    action: request_approval
    from: "@release-manager"
    message: "Deploy to production requested by {{trigger.author}}. Approve?"
    timeout: 4h

  - id: notify_approved
    if: 'steps_request_output_approved == true'
    action: send_message
    text: "✅ Deploy approved by release manager — starting deploy pipeline"

  - id: deploy_hook
    if: 'steps_request_output_approved == true'
    action: call_webhook
    url: "https://ci.example.com/hooks/deploy"
    body: '{"approved": true}'

  - id: notify_denied
    if: 'steps_request_output_approved == false'
    action: send_message
    text: "❌ Deploy denied — pipeline not started"

Full example: daily standup prompt

name: Daily Standup
description: Post a standup prompt every weekday morning
trigger:
  on: schedule
  cron: "0 9 * * 1-5"   # 9:00 AM UTC, Monday–Friday
steps:
  - id: prompt
    action: send_message
    text: |
      🌅 Good morning! Time for standup.
      Please share:
      - What did you ship yesterday?
      - What are you working on today?
      - Any blockers?

Build docs developers (and LLMs) love