# Channel mention policy

> Split inbound mention handling into plugin-owned evidence gathering and shared policy evaluation

- 网址：https://funcoding.ai/agents/openclaw/plugins/sdk-channel-plugins/mention-policy/
- 来源：OpenClaw 官方文档原文（英文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/plugins/sdk-channel-plugins/mention-policy

---
Decide when an inbound message counts as a mention, without reimplementing
the shared policy. Part of the [Building channel
plugins](https://funcoding.ai/agents/openclaw/plugins/sdk-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:

1. Compute local mention facts.
2. Pass those facts into `resolveInboundMentionDecision({ facts, policy })`.
3. Use `decision.effectiveWasMentioned`, `decision.shouldBypassMention`, and
   `decision.shouldSkip` in your inbound gate.

```typescript
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:

```typescript
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.
