Channels/Allowlists — Command Gating

Focus Deep-Dive

Slash Command Authorization & Access Group Enforcement

Command gating restricts which users can execute slash commands (/reset, /model, /continue, etc.). It combines access group membership with per-authorizer configuration to produce a binary allow/block decision for each control command.

Access Groups Dual-Text Gate Configurable Modes Zero-Config Default

📚 What is this & why does it matter?

Slash commands can reset context, change models, or modify agent behavior. In group chats with multiple users, you don’t want everyone to have that power. Command gating ensures only authorized users (typically the agent owner) can execute control commands, while still allowing everyone to send regular messages.

💡 Real-world analogy

Think of a shared office printer. Anyone can print documents (send messages), but only IT administrators can change printer settings, clear the queue, or install updates (slash commands). The command gating system is the admin password check that runs before any configuration change is applied.

Contents

Type System

export type CommandAuthorizer = {
  configured: boolean;  // Whether this authorizer has any entries
  allowed: boolean;     // Whether the current sender matches
};

export type CommandGatingModeWhenAccessGroupsOff = "allow" | "deny" | "configured";

Resolution Algorithm

flowchart TD
    START["resolveCommandAuthorizedFromAuthorizers()"]
    START --> AG{"useAccessGroups?"}
    AG -->|Yes| ANY_MATCH{"Any authorizer
configured AND allowed?"}
    ANY_MATCH -->|Yes| ALLOW["authorized = true"]
    ANY_MATCH -->|No| DENY["authorized = false"]
    AG -->|No| MODE{"modeWhenAccessGroupsOff?"}
    MODE -->|"allow"| ALLOW
    MODE -->|"deny"| DENY
    MODE -->|"configured"| CONFIGURED{"Any authorizer
configured?"}
    CONFIGURED -->|No| ALLOW
    CONFIGURED -->|Yes| FALLBACK{"Any configured
authorizer allowed?"}
    FALLBACK -->|Yes| ALLOW
    FALLBACK -->|No| DENY
export function resolveCommandAuthorizedFromAuthorizers(params: {
  useAccessGroups: boolean;
  authorizers: CommandAuthorizer[];
  modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff;
}): boolean {
  const { useAccessGroups, authorizers } = params;
  const mode = params.modeWhenAccessGroupsOff ?? "allow";
  if (!useAccessGroups) {
    if (mode === "allow") return true;
    if (mode === "deny") return false;
    // mode === "configured"
    const anyConfigured = authorizers.some((e) => e.configured);
    if (!anyConfigured) return true;
    return authorizers.some((e) => e.configured && e.allowed);
  }
  return authorizers.some((e) => e.configured && e.allowed);
}

Control Command Gate

The control command gate adds the shouldBlock decision, which considers whether the message is a control command at all:

export function resolveControlCommandGate(params: {
  useAccessGroups: boolean;
  authorizers: CommandAuthorizer[];
  allowTextCommands: boolean;
  hasControlCommand: boolean;
  modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff;
}): { commandAuthorized: boolean; shouldBlock: boolean } {
  const commandAuthorized = resolveCommandAuthorizedFromAuthorizers({
    useAccessGroups: params.useAccessGroups,
    authorizers: params.authorizers,
    modeWhenAccessGroupsOff: params.modeWhenAccessGroupsOff,
  });
  const shouldBlock = params.allowTextCommands
    && params.hasControlCommand && !commandAuthorized;
  return { commandAuthorized, shouldBlock };
}

Dual-Text Control Command Gate

For channels with primary and secondary authorizer sources:

flowchart LR
    PRI["Primary Authorizer
(owner/admin)"]
    SEC["Secondary Authorizer
(access group)"]
    PRI --> MERGE["Merge into authorizers[]"]
    SEC --> MERGE
    MERGE --> GATE["resolveControlCommandGate()"]
    GATE --> RESULT["{commandAuthorized, shouldBlock}"]
export function resolveDualTextControlCommandGate(params: {
  useAccessGroups: boolean;
  primaryConfigured: boolean;
  primaryAllowed: boolean;
  secondaryConfigured: boolean;
  secondaryAllowed: boolean;
  hasControlCommand: boolean;
  modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff;
}): { commandAuthorized: boolean; shouldBlock: boolean }

Gating Modes Comparison

ModeAccess Groups ONAccess Groups OFFUse Case
allowCheck authorizersAlways allowDefault; open access when no access groups
denyCheck authorizersAlways denyLocked down; require explicit access group setup
configuredCheck authorizersCheck if any configured; if yes, require matchGraceful fallback; respect existing config

Interaction with Mention Gating

Command gating and mention gating interact: an authorized control command can bypass the mention requirement in group chats. This is handled by resolveMentionGatingWithBypass(), which checks whether the user is authorized for the detected command and, if so, sets shouldBypassMention = true.