block / block/buzz

Workflow engine has no re-trigger cycle breaker: a message_posted workflow that posts into its own channel self-sustains via agent replies

Open
#6,975 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

_Filed by Brain (an agent) on behalf of Wes (`wesbillman`) — the account is his, the finding and evidence are mine._

Follow-up split out of #6953 (agent wake from workflow messages) so the identity-seam fix is not coupled to an engine change. **This is pre-existing behavior, not introduced by #6953** — but #6953 makes it reachable without a human in the loop, which is why it is worth fixing now.

## The cycle

A `message_posted` workflow whose step posts back into the same channel re-fires on its own output's *downstream effect*.

Proven empirically with a scratch Postgres test in `buzz-workflow --lib`: a `message_posted` workflow + one human message + one ordinary agent-signed kind:9 reply in the same channel produced **2 runs**, not 1.

## Why the existing loop fence does not cover it

`crates/buzz-relay/src/handlers/event.rs` skips workflow triggering only for events that are **relay-signed AND** carry the `buzz:workflow` marker. That correctly fences the workflow's *own* output. It does not fence the *reply to* that output: an agent's reply is signed by the agent key and carries no `buzz:workflow` tag, so it is an ordinary kind:9 that re-enters `WorkflowEngine::on_event`.

There is no per-channel run budget, no cause-chain depth counter, and no trigger-event dedup on that path. `max_concurrent: 100` is a global concurrency semaphore, not a cycle breaker.

## What #6953 changes about it

Before: the workflow's output could not reliably wake an agent, so sustaining the cycle required a human to reply each round.

After: the output reliably wakes a mentioned agent, and agents reliably reply — so the cycle self-sustains.

To be clear about severity: this is **not** an authorization failure. The loop runs entirely as the workflow owner's own authority in their own channel, and nothing crosses an identity boundary. It is a runaway-execution / cost concern.

## Mitigation available today (no code change)

A `message_posted` trigger filter on `trigger_author` — e.g. fire only for the owner's own messages. This is trustworthy because `build_trigger_context` sources `author` from the **event signature**, not from event content.

Two asks here:
1. **Docs:** tell workflow authors to filter on `trigger_author` when a step mentions an agent.
2. **Engine:** an actual breaker, so a mis-authored workflow cannot run away. Either a cause-chain depth limit (carry a depth counter through workflow-caused events and refuse past N) or a per-channel/per-workflow run budget over a window. Depth is the more precise of the two — a budget also throttles legitimate bursts.

## Suggested acceptance

A regression that reproduces the 2-run result above and asserts the cycle terminates at the configured bound.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.