Channel mention policy
Split inbound mention handling into plugin-owned evidence gathering and shared policy evaluation
Decide when an inbound message counts as a mention, without reimplementing the shared policy. Part of the Building channel plugins guide.
Inbound mention policy
Keep inbound mention handling split in two layers:
- plugin-owned evidence gathering
- shared policy evaluation
Use openclaw/plugin-sdk/channel-mention-gating for mention-policy decisions.
Use openclaw/plugin-sdk/channel-inbound only when you need the broader
inbound helper barrel.
Good fit for plugin-local logic:
- reply-to-bot detection
- quoted-bot detection
- thread-participation checks
- bot-owned thread detection and channel-specific config inheritance
- service/system-message exclusions
- platform-native caches needed to prove bot participation
Good fit for the shared helper:
requireMention- explicit mention result
- implicit mention allowlist
- command bypass
- final skip decision
For a qualified group-thread entry, compute explicit participant mention facts
once with resolveGroupThreadMentionFacts({ cfg, channel, peerId, text, sessionKey, acpBinding })
from openclaw/plugin-sdk/channel-inbound, including direct conversations. Pass
the resolved session key and whether a configured ACP binding owns the route.
It returns undefined for an exclusive ACP route or when no qualified entry
applies, otherwise the resolved group and mentionedAgentIds. If routing changes
after preparation, use isGroupThreadRouteExclusive({ sessionKey, acpBinding })
to discard participant facts for an ACP-owned destination and reevaluate admission
using only the final route’s ordinary mention and command facts.
Merge a non-empty participant match with the routed agent’s local mention facts before
the ordinary gate, and carry the same selection facts into dispatch. A mention
of a non-routed participant must not be dropped by a single-agent gate.
Participant selection requires an @-style match; a bare name or emoji does not
select an agent. Keep sender authorization and command policy unchanged.
Set the optional replyOptions.groupThreadReplyFormatter(text, participant) to
apply the adapter’s participant label to source-conversation message-tool replies.
The participant contains agentId and name; reuse the same transport formatter
used for ordinary replies with participant delivery metadata.
For plugin-owned sends, read getGroupThreadDeliverySession() from the same SDK
at delivery entry. When present, use its agentId and sessionKey for media
roots, internal hooks, and transcript mirrors, including unlabeled single-agent
groups. Keep transport account ownership unchanged. Shared durable delivery
selects the active participant context and run identity in core.
Preferred flow:
- Compute local mention facts.
- Pass those facts into
resolveInboundMentionDecision({ facts, policy }). - Use
decision.effectiveWasMentioned,decision.shouldBypassMention, anddecision.shouldSkipin your inbound gate.
import {
implicitMentionKindWhen,
matchesMentionWithExplicit,
resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";
import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
const wasMentioned = matchesMentionWithExplicit({
text,
mentionRegexes,
explicit: {
hasAnyMention,
isExplicitlyMentioned,
canResolveExplicit,
},
});
const facts = {
canDetectMention: true,
wasMentioned,
hasAnyMention,
implicitMentionKinds: [
...implicitMentionKindWhen("reply_to_bot", isReplyToBot),
...implicitMentionKindWhen("quoted_bot", isQuoteOfBot),
],
};
const implicitMentions = resolveChannelImplicitMentions({
cfg,
channel: channelId,
accountId,
});
const decision = resolveInboundMentionDecision({
facts,
policy: {
isGroup,
requireMention,
implicitMentions,
allowTextCommands,
hasControlCommand,
commandAuthorized,
},
});
if (decision.shouldSkip) return;matchesMentionWithExplicit(...) returns a boolean. hasAnyMention,
isExplicitlyMentioned, and canResolveExplicit come from the channel's own
native mention metadata (message entities, reply-to-bot flags, and similar);
supply false/undefined values when your platform cannot detect them.
api.runtime.channel.mentions exposes the same shared mention helpers for
bundled channel plugins that already depend on runtime injection:
buildMentionRegexes, matchesMentionPatterns, matchesMentionWithExplicit,
implicitMentionKindWhen, resolveInboundMentionDecision.
If you only need implicitMentionKindWhen and resolveInboundMentionDecision,
import from openclaw/plugin-sdk/channel-mention-gating to avoid loading
unrelated inbound runtime helpers.
Bot-owned threads
Channels with native thread ownership can expose requireMentionInBotThreads.
An omitted value preserves the channel's existing mention behavior. false
allows messages without a mention in threads started by the configured bot.
true requires a mention there, including when the ordinary requireMention
setting is false; replying to or quoting the bot and prior bot participation
do not satisfy that requirement. Explicit mentions, native mention evidence,
and authorized command bypass retain their ordinary behavior.
The plugin owns proof that the current bot started the thread and resolves the
most specific config value before calling resolveBotThreadMentionPolicy from
openclaw/plugin-sdk/channel-mention-gating. Bot participation alone is not
ownership. For unknown ownership, foreign threads, and ordinary channel
messages, pass isBotOwnedThread: false to preserve existing behavior.
Apply the helper after gathering mention evidence and before the shared decision:
const threadPolicy = resolveBotThreadMentionPolicy({
isBotOwnedThread,
requireMentionInBotThreads,
requireMention,
implicitMentionKinds: facts.implicitMentionKinds,
});
const decision = resolveInboundMentionDecision({
facts: { ...facts, implicitMentionKinds: threadPolicy.implicitMentionKinds },
policy: { ...policy, requireMention: threadPolicy.requireMention },
});This override changes mention admission only. Preserve the existing sender allowlists, channel access policy, and command authorization. Channels without reliable native ownership evidence should not infer ownership from cached participation or expose an override they cannot enforce.
Recheck mutable admission rules before handing the turn to shared dispatch. After admission, retain the turn's captured policy for its replies rather than using mention or sender gates to cancel delivery. Account, run, and transport liveness remain separate responsibilities of their existing owners.