iMessage Access Control, DM and Group Routing Rules
Covers iMessage DM and group policy, mention gating, per-group prompts, ACP bindings, and config writes. Useful for operators configuring who can reach the bot and which chats can change settings.
Read this when
- Deciding who can message the bot
- Debugging ignored DMs or ignored group messages
- Binding an iMessage chat to an ACP session
Which senders get in, how incoming messages map to sessions, and which chats are allowed to write config.
Access control and routing
DM policy
Direct messages are governed by channels.imessage.dmPolicy:
pairing(default)allowlist(needs at least oneallowFromentry)open(needsallowFromto contain"*")disabled
Allowlist field: channels.imessage.allowFrom.
Every allowlist entry has to name senders: handles or static sender access groups (accessGroup:<name>). For chat targets like chat_id:*, chat_guid:*, or chat_identifier:*, use channels.imessage.groupAllowFrom; for numeric chat_id registry keys, use channels.imessage.groups.
Group policy + mentions
Group handling is governed by channels.imessage.groupPolicy:
allowlist(default)opendisabled
Group sender allowlist: channels.imessage.groupAllowFrom.
Entries in groupAllowFrom may also point at static sender access groups (accessGroup:<name>).
Runtime fallback: when groupAllowFrom is unset, iMessage group sender checks rely on allowFrom; set groupAllowFrom if DM and group admission ought to differ. An explicitly empty groupAllowFrom: [] triggers no fallback: under allowlist it blocks all group senders.
Runtime note: when channels.imessage is entirely absent, runtime logs a warning and falls back to groupPolicy="allowlist" (even if channels.defaults.groupPolicy is set).
Warning
Under
groupPolicy: "allowlist", group routing passes through two gates in sequence:
- Sender allowlist (
channels.imessage.groupAllowFrom), a handle,accessGroup:<name>,chat_guid,chat_identifier, orchat_id. If the effective list is empty (nogroupAllowFromand noallowFromfallback), every group sender is blocked.- Group registry (
channels.imessage.groups), enforced once the map holds entries: the chat has to match either an explicit per-chat_identry or agroups: { "*": { ... } }wildcard. Whilegroupsis empty or missing, the sender allowlist alone decides admission.When no effective group sender allowlist is configured, every group message is dropped before the registry gate is reached. At the default log level each gate emits its own
warn-level signal, and each one points to a different fix:
- once per account at startup, when the effective group sender allowlist is empty:
imessage: groupPolicy="allowlist" for account "<id>" but no group sender allowlist is configured ..., fix it by settingchannels.imessage.groupAllowFrom(orallowFrom); addinggroupsentries on their own leaves gate 1 blocking every sender.- once per
chat_idat runtime, when a sender has passed gate 1 but the chat is absent from a populatedgroupsregistry:imessage: dropping group message from chat_id=<id> ..., fix it by adding thatchat_id(or"*") underchannels.imessage.groups.DMs take a different code path, so they are unaffected.
Recommended config for group flow under
groupPolicy: "allowlist":{ channels: { imessage: { groupPolicy: "allowlist", groupAllowFrom: ["+15555550123"], groups: { "*": { "requireMention": true } }, }, }, }On its own,
groupAllowFromadmits those senders in any group; add thegroupsblock to scope which chats are allowed (and to set per-chat options such asrequireMention).
Group mention gating:
- iMessage provides no native mention metadata
- Detection of mentions relies on
agents.entries.*.groupChat.mentionPatterns, then onmessages.groupChat.mentionPatterns; if neither is configured, the patterns come from the routed agent'sidentity.nameandidentity.emoji - By default a group requires a mention, even when no patterns were set explicitly; a message from an allowlisted sender can therefore be dropped unless it carries the agent's name or emoji
- An explicit
mentionPatterns: []set at the selected agent or at global level suppresses patterns derived from identity; when no usable patterns are left, iMessage cannot enforce mention gating - Control commands sent by authorized senders are exempt from mention gating
To have every message from allowed senders in a given group processed, set that chat's requireMention to false:
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123", "+15555550124"],
groups: {
"*": {},
"123": { requireMention: false },
},
},
},
}
Swap 123 for the numeric chat ID taken from imsg chats --limit 20 --json. Modify the map that already provides that account's group policy: channels.imessage.groups, or channels.imessage.accounts.<account-id>.groups where it overrides the root map. The same holds for accounts.default.groups; an account entry on its own does not mean that account needs its own groups map. An account map that is empty inherits the root map only when one account or fewer is configured.
To permit unmentioned follow-ups only inside native reply threads that OpenClaw started, add requireMentionInBotThreads: false next to requireMention: true in that group's entry. A setting on the exact group takes precedence over groups["*"].requireMentionInBotThreads. Assign true to demand mentions in those threads, or leave it out to keep the usual mention behavior. Sender and group restrictions continue to apply.
An explicit requireMentionInBotThreads: true is still enforced even when mention patterns are turned off. Set up usable mention patterns so the bot can be addressed in those threads; otherwise they remain silent apart from authorized control commands.
Thread ownership is determined by the native thread root GUID together with OpenClaw's sent-message cache, which is scoped per account and per conversation. That cache holds as many as 2,000 messages for six hours and persists across restarts inside that window. Roots that are unknown or have been evicted still require a mention as usual. If you reply to a bot message within a thread owned by someone else, that thread does not become bot-owned.
Keep the existing wildcard and all per-group settings intact, and change only the target chat's requireMention. Because account maps replace the entire inherited map, copy the full inherited map first, wildcard and per-group policies included, if you deliberately create an account-specific override. Where no map applied before, "*": {} keeps admission to the other groups while leaving their default mention requirement in place. A restricted map must stay restricted. Sender access is still governed by groupAllowFrom.
When a message is skipped for lacking a mention, a warning is logged at the default level, including the chat ID and the requireMention: false fix. A bounded in-memory cache suppresses repeated warnings for the same chat; restarting the channel or evicting a cache entry lets the warning appear again.
Per-group systemPrompt:
Every entry beneath channels.imessage.groups.* takes an optional systemPrompt string, which is injected into the agent's system prompt on each turn that handles a message in that group. Resolution works the same way as channels.whatsapp.groups:
- Group-specific system prompt (
groups["<chat_id>"].systemPrompt): applied when the map contains the specific group entry and itssystemPromptkey is defined. ShouldsystemPromptbe an empty string (""), the wildcard is suppressed and that group receives no system prompt. - Group wildcard system prompt (
groups["*"].systemPrompt): applied when the specific group entry is missing from the map altogether, or when it is present but has nosystemPromptkey defined.
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: {
"*": { systemPrompt: "Use British spelling." },
"8421": {
requireMention: true,
systemPrompt: "This is the on-call rotation chat. Keep replies under 3 sentences.",
},
"9907": {
// explicit suppression: the wildcard "Use British spelling." does not apply here
systemPrompt: "",
},
},
},
},
}
Per-group prompts affect group messages only; direct messages are not touched.
Sessions and reply routing
- Direct routing serves DMs; group routing serves groups.
- Under the default
session.dmScope=main, iMessage DMs are collapsed into the agent main session. - Group sessions stay isolated (
agent:<agentId>:imessage:group:<chat_id>). - Replies are routed back to iMessage through the originating channel/target metadata.
chat_id,chat_guid, andchat_identifieract as delivery selectors rather than group markers. Outbound session routing looks up the chat in the account's Messages chat list: one-to-one chats map to the direct session of the normalized handle, while groups map to the numeric chat ID. If a selector cannot be resolved, no session is guessed.- Earlier versions could create extra group sessions holding delivery mirrors for one-to-one chats. This fix stops new misclassified mirrors from appearing; it keeps existing session history as is instead of merging or deleting those rows automatically.
Group-ish thread behavior:
Certain multi-participant iMessage threads may arrive carrying is_group=false.
When that chat_id is configured explicitly under channels.imessage.groups, OpenClaw handles it as group traffic (group gating plus group session isolation).
ACP conversation bindings
ACP sessions can be bound to iMessage chats.
Quick operator steps:
- Inside the DM or an allowed group chat, run
/acp spawn codex --bind here. - Messages sent later in that same iMessage conversation are routed to the spawned ACP session.
/newand/resetreset that same bound ACP session in place./acp closecloses the ACP session and drops the binding.
Persistent bindings configured ahead of time use top-level bindings[] entries containing type: "acp" and match.channel: "imessage".
match.peer.id accepts:
- a normalized DM handle, for example
+15555550123oruser@example.com chat_id:<id>(recommended for stable group bindings)chat_guid:<guid>chat_identifier:<identifier>
Example:
{
agents: {
entries: {
codex: {
runtime: {
type: "acp",
acp: { agent: "codex", backend: "acpx", mode: "persistent" },
},
},
},
},
bindings: [
{
type: "acp",
agentId: "codex",
match: {
channel: "imessage",
accountId: "default",
peer: { kind: "group", id: "chat_id:123" },
},
acp: { label: "codex-group" },
},
],
}
Shared ACP binding behavior is covered in ACP Agents.
Config writes
By default, iMessage permits config writes started by the channel (in the case of /config set|unset, when commands.config: true).
To turn this off:
{
channels: {
imessage: {
configWrites: false,
},
},
}