Files
buzz/docs/operator-workflow-agent-mentions.md
Will Pfleger fc8c802253 feat(workflow): add owner-only agent mentions for workflow messages (#9227)
Adds owner-only agent mentions, a relay setting,
`BUZZ_WORKFLOW_AGENT_MENTIONS`. Its values are `any`, the default and
today's behaviour, and `owner`. Any other value fails startup. With
`owner`, a workflow message can direct an agent only if the run's cause
and definition allow it. The setting is relay-wide.

**Recording what caused each run.** `workflow_runs` gets `cause_source`
and `cause_pubkey` columns. Migration `0087` adds them with named checks
marked `NOT VALID`, so it does not scan existing rows under its lock;
`0088` validates the checks separately. `create_workflow_run` now takes
a required `RunCause`, so every place that creates a run has to set one:
- **Event trigger:** the cause is the signer of the message, reaction or
diff. For a reaction, that is the reactor, not the author of the message
being reacted to.
- **Cron and interval schedules:** the cause is the timer.
- **Webhook:** the caller is unknown.
- **Manual trigger:** the cause is the invoking pubkey.

Rows written before this change have no cause. A stored source without
the pubkey it requires, or with one it forbids, is read as no cause.

**Deciding eligibility.** A run's messages may direct agents only when
all of these hold:
- the cause is a schedule, or an event or manual trigger whose pubkey is
the workflow owner's;
- every step of the definition that is running is `send_message` or
`delay`. This is an allowlist, so any other action, including one added
later, makes the definition ineligible, even when an `if:` skips that
step;
- the run did not resume after an approval.

Webhook runs and runs with no recorded cause are never eligible. The
executor decides this from `WorkflowConfig::agent_mentions`, and passes
`ActionSink::send_message` the owner's stored step text with a
`may_direct_agents` flag. The sink adds `buzz:workflow-mention` tags
only when the flag is set, and returns `SentMessage { event_id,
agent_mentions_withheld }`, which is true only when it left out a tag
that a member named in both the stored and rendered text would otherwise
have received. Webhook and manual runs start through `execute_run`, so
`execute_from_step` is used only to resume after an approval.

**The gate.** With `owner`, the relay adds `buzz:workflow-mention` only
to messages from eligible runs. Messages from other runs still post with
their plain `p` tags and still notify people. With `any`, mention tags
are added exactly as before.

**Making it visible.** When a `send_message` step leaves the tags out:
- its output in the run trace gets `"agent_mentions": {"withheld": true,
"reasons": [...]}`, listing each reason that applies: `cause` (not
started by the owner or a timer), `steps` (a step other than
`send_message` or `delay`), `resumed` (resumed after an approval). The
key is absent when nothing was withheld. The trace is what the app's run
history shows;
- the relay logs it at info level under the
`buzz_relay::workflow_agent_mentions` target (visible under the default
filter) with the community, workflow, run, step and reasons;
- the counter `buzz_workflow_agent_mentions_withheld_total` increases
once per reason, labelled `reason`.

**Discovery.** The NIP-11 document always carries
`"workflow_agent_mentions": "any" | "owner"`, so a missing field means
an older relay.

`NOSTR.md` now specifies `buzz:workflow-mention` for clients: what the
tag means, the checks an agent makes before acting, that a relay may
leave it out under a policy, and the NIP-11 `workflow_agent_mentions`
field. The relay policy itself is in the new operator guide,
`docs/operator-workflow-agent-mentions.md`: the setting and its values,
what counts as a run's cause for each trigger, which steps are allowed,
the trace key, the log and metric, the NIP-11 field, when to use
`owner`, rollout across pods, and what it doesn't cover. The
`TESTING.md` and `.env.example` entries link to it.

---------

Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-11-12 11:34:30 -08:00

3.7 KiB

Owner-Only Agent Mentions

A workflow message carries a buzz:workflow-mention tag for each agent the owner's stored step text names. That tag lets the message direct the agent on the owner's behalf (see "Workflow Mentions" in NOSTR.md). Owner-only agent mentions keep the tag off messages from runs that someone other than the owner could have steered.

Setting Values Default
BUZZ_WORKFLOW_AGENT_MENTIONS any, owner any

Any other value fails startup. With any, the relay adds the tags for every run. With owner, a send_message step adds them only when the run is eligible: its cause qualifies and its steps qualify. The setting is relay-wide: it applies to every community the relay serves.

Cause

The relay records what caused each run when it creates it.

Trigger Cause Eligible
Event (message, reaction, diff) the event's signer; for a reaction, the reactor, not the author of the message reacted to only if that is the owner
Schedule (cron or interval) the timer yes
Webhook unknown never
Manual whoever ran it only if that is the owner

Only the owner or a timer makes a run eligible. The owner's other agents count as someone else.

Runs created before causes were recorded have no cause, and are never eligible.

Steps

Every step of the workflow definition must be send_message or delay. Any other step makes the run ineligible, even one the run skips. A run that resumes after an approval is ineligible too.

What people see

An ineligible run's messages still post, with their p tags, so mentioned people are still notified. No agent acts on them.

When an ineligible run's message names a channel member in the workflow's stored text, and that name is also in the posted text, the relay leaves off that member's buzz:workflow-mention tag. The step's output in the run's trace, which the owner sees in the app's run history, then records why:

{"sent": true, "event_id": "…", "agent_mentions": {"withheld": true, "reasons": ["cause", "steps"]}}

reasons lists every reason that applies, in this order:

Reason Meaning
cause Neither the owner nor a timer started the run.
steps The definition has a step other than send_message or delay.
resumed The run resumed after an approval.

The key is absent when no tag was left off: on eligible runs, with any, and for messages with no such mention, such as plain text, a mention that comes only from substituted text, or a name that matches no member or more than one.

Logs and metrics

Each withheld step logs at info level owner-only agent mentions withheld buzz:workflow-mention tags, with community, workflow, run_id, step and reasons. The counter buzz_workflow_agent_mentions_withheld_total increases by one for each reason, labelled reason (cause, steps or resumed), so one step with two reasons counts under both.

Discovery

The NIP-11 relay information document always carries "workflow_agent_mentions": "any" or "owner". A client that finds no field is talking to a relay older than this setting.

When to use owner

When people other than a workflow's owner can trigger it: by posting or reacting in its channel, or through its webhook.

Rollout

Any pod set to any still adds the tags, so the setting protects agents only once every relay pod runs with owner. Keep client-side defenses in place until then.

What it does not cover

  • An agent that acts on a workflow message still reads thread, pull request and repository text that anyone can write, the same as for a direct mention.
  • A replayed workflow message behaves as it does with any.