mirror of
https://github.com/block/buzz.git
synced 2026-11-13 21:01:53 +00:00
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>
96 lines
3.7 KiB
Markdown
96 lines
3.7 KiB
Markdown
# 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](../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:
|
|
|
|
```json
|
|
{"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`.
|