iMessage access control and routing
iMessage DM and group policy, mention gating, per-group prompts, ACP bindings, and config writes
Who is admitted, how messages route to sessions, and which chats can write config.
Access control and routing
DM policy
channels.imessage.dmPolicy controls direct messages:
pairing(default)allowlist(requires at least oneallowFromentry)open(requiresallowFromto include"*")disabled
Allowlist field: channels.imessage.allowFrom.
Allowlist entries must identify senders: handles or static sender access groups (accessGroup:<name>). Use channels.imessage.groupAllowFrom for chat targets such as chat_id:*, chat_guid:*, or chat_identifier:*; use channels.imessage.groups for numeric chat_id registry keys.
Group policy + mentions
channels.imessage.groupPolicy controls group handling:
allowlist(default)opendisabled
Group sender allowlist: channels.imessage.groupAllowFrom.
groupAllowFrom entries can also reference static sender access groups (accessGroup:<name>).
Runtime fallback: if groupAllowFrom is unset, iMessage group sender checks use allowFrom; set groupAllowFrom when DM and group admission should differ. An explicitly empty groupAllowFrom: [] does not fall back — it blocks all group senders under allowlist.
Runtime note: if channels.imessage is completely missing, runtime falls back to groupPolicy="allowlist" and logs a warning (even if channels.defaults.groupPolicy is set).
Group routing under groupPolicy: "allowlist" runs two gates back-to-back:
- Sender allowlist (
channels.imessage.groupAllowFrom) — handle,accessGroup:<name>,chat_guid,chat_identifier, orchat_id. An empty effective list (nogroupAllowFromand noallowFromfallback) blocks every group sender. - Group registry (
channels.imessage.groups) — enforced once the map has entries: the chat must match an explicit per-chat_identry or agroups: { "*": { ... } }wildcard. Whengroupsis empty or missing, the sender allowlist alone decides admission.
If no effective group sender allowlist is configured, every group message is dropped before the registry gate. Each gate has its own warn-level signal at the default log level, and each names a different fix:
- one-time 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 by settingchannels.imessage.groupAllowFrom(orallowFrom); addinggroupsentries alone leaves gate 1 blocking every sender. - one-time per
chat_idat runtime, when a sender passed gate 1 but the chat is missing from a populatedgroupsregistry:imessage: dropping group message from chat_id=<id> ...— fix by adding thatchat_id(or"*") underchannels.imessage.groups.
DMs are unaffected — they take a different code path.
Recommended config for group flow under groupPolicy: "allowlist":
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: { "*": { "requireMention": true } },
},
},
}groupAllowFrom alone admits those senders in any group; add the groups block to scope which chats are allowed (and to set per-chat options like requireMention).
Mention gating for groups:
- iMessage has no native mention metadata
- mention detection uses
agents.entries.*.groupChat.mentionPatterns, thenmessages.groupChat.mentionPatterns; when neither is set, patterns are derived from the routed agent'sidentity.nameandidentity.emoji - groups require a mention by default, even when no patterns were explicitly configured; an allowlisted sender's message can therefore be skipped unless it contains the agent's name or emoji
- an explicit
mentionPatterns: []at the selected agent or global level suppresses identity-derived patterns; iMessage cannot enforce mention gating when no usable patterns remain - control commands from authorized senders bypass mention gating
To process every message from allowed senders in one group, set that chat's requireMention to false:
{
channels: {
imessage: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123", "+15555550124"],
groups: {
"*": {},
"123": { requireMention: false },
},
},
},
}Replace 123 with the numeric chat ID from imsg chats --limit 20 --json. Edit the map already supplying that account's group policy: channels.imessage.groups, or channels.imessage.accounts.<account-id>.groups when it overrides the root map. This also applies to accounts.default.groups; merely having an account entry does not mean its own groups map is needed. An empty account map inherits the root map only when at most one account is configured.
To allow unmentioned follow-ups only in native reply threads started by OpenClaw, set requireMentionInBotThreads: false alongside requireMention: true in that group entry. The exact group setting overrides groups["*"].requireMentionInBotThreads. Set it to true to require mentions in those threads, or omit it to preserve normal mention behavior. Sender and group restrictions still apply.
An explicit requireMentionInBotThreads: true remains enforced when mention patterns are disabled. Configure usable mention patterns to address the bot in those threads; otherwise they stay quiet except for authorized control commands.
Thread ownership comes from the native thread root GUID and OpenClaw's account- and conversation-scoped sent-message cache. The cache retains up to 2,000 messages for six hours and survives restarts within that window. Unknown or evicted roots keep the normal mention requirement. Replying to a bot message inside someone else's thread does not make that thread bot-owned.
Preserve the existing wildcard and every per-group setting, changing only the target chat's requireMention. Account maps replace the whole inherited map, so if you intentionally create an account-specific override, first copy the complete inherited map, including all wildcard and per-group policies. When no map previously applied, "*": {} preserves admission to other groups while keeping their default mention requirement. Keep a restricted map restricted. groupAllowFrom still controls sender access.
A skipped message with no mention produces a warning at the default log level with the chat ID and the requireMention: false fix. Repeated warnings for the same chat are suppressed by a bounded in-memory cache; restarting the channel or evicting a cache entry allows the warning again.
Per-group systemPrompt:
Each entry under channels.imessage.groups.* accepts an optional systemPrompt string, injected into the agent's system prompt on every turn that handles a message in that group. Resolution mirrors channels.whatsapp.groups:
- Group-specific system prompt (
groups["<chat_id>"].systemPrompt): used when the specific group entry exists in the map and itssystemPromptkey is defined. IfsystemPromptis an empty string ("") the wildcard is suppressed and no system prompt is applied to that group. - Group wildcard system prompt (
groups["*"].systemPrompt): used when the specific group entry is absent from the map entirely, or when it exists but defines nosystemPromptkey.
{
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 only apply to group messages — direct messages are unaffected.
Sessions and deterministic replies
- DMs use direct routing; groups use group routing.
- With default
session.dmScope=main, iMessage DMs collapse into the agent main session. - Group sessions are isolated (
agent:<agentId>:imessage:group:<chat_id>). - Replies route back to iMessage using originating channel/target metadata.
Group-ish thread behavior:
Some multi-participant iMessage threads can arrive with is_group=false.
If that chat_id is explicitly configured under channels.imessage.groups, OpenClaw treats it as group traffic (group gating + group session isolation).
ACP conversation bindings
iMessage chats can be bound to ACP sessions.
Fast operator flow:
- Run
/acp spawn codex --bind hereinside the DM or allowed group chat. - Future messages in that same iMessage conversation route to the spawned ACP session.
/newand/resetreset the same bound ACP session in place./acp closecloses the ACP session and removes the binding.
Configured persistent bindings use top-level bindings[] entries with type: "acp" and match.channel: "imessage".
match.peer.id can use:
- normalized DM handle such as
+15555550123or[email protected] 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" },
},
],
}See ACP Agents for shared ACP binding behavior.
Config writes
iMessage allows channel-initiated config writes by default (for /config set|unset when commands.config: true).
Disable:
{
channels: {
imessage: {
configWrites: false,
},
},
}