On this page

On this page

Gateway

Configuration examples

Examples below are aligned with the current config schema. For the exhaustive reference and per-field notes, see Configuration.

Doctor migrates legacy agents.list arrays and default markers into keyed agents.entries with explicit surface owners. The updater runs this migration through its backup flow. After replacing the binary directly, run openclaw doctor --fix before starting the Gateway.

Quick start

Absolute minimum

json5
{  agents: { defaults: { workspace: "~/.openclaw/workspace" } },  channels: { whatsapp: { allowFrom: ["+15555550123"] } },}

Save to ~/.openclaw/openclaw.json and you can DM the bot from that number.

json5
{  agents: {    defaults: {      workspace: "~/.openclaw/workspace",      model: { primary: "anthropic/claude-sonnet-4-6" },    },    entries: {      main: {        identity: {          name: "Clawd",          theme: "helpful assistant",          emoji: "🦞",        },      },    },  },  channels: {    whatsapp: {      allowFrom: ["+15555550123"],      groups: { "*": { requireMention: true } },    },  },  messages: {    visibleReplies: "automatic",    groupChat: {      visibleReplies: "message_tool", // opt-in; visible output requires message(action=send)      unmentionedInbound: "room_event",    },  },}

Expanded example (major options)

JSON5 lets you use comments and trailing commas. Regular JSON works too.

This example leaves agents.defaults.timeoutSeconds unset, so each model attempt uses the 48-hour default. Set a different per-attempt budget only when your workflow needs one. Configured fallbacks each receive a fresh budget; see Agent runtime.

json5
{  // Environment + shell  env: {    vars: {      OPENROUTER_API_KEY: "sk-or-...",      GROQ_API_KEY: "gsk-...",    },    shellEnv: {      enabled: true,      timeoutMs: 15000,    },  },   // Auth profile metadata (secrets live in SQLite auth stores)  auth: {    profiles: {      "anthropic:default": { provider: "anthropic", mode: "api_key" },      "anthropic:work": { provider: "anthropic", mode: "api_key" },      "openai:default": { provider: "openai", mode: "api_key" },      "openai:personal": { provider: "openai", mode: "oauth" },    },    order: {      anthropic: ["anthropic:default", "anthropic:work"],      openai: ["openai:personal", "openai:default"],    },  },   // Identity is per agent — set it on agents.entries.<id>.identity below.   // Logging  logging: {    level: "info",    file: "/tmp/openclaw/openclaw.log",    consoleLevel: "info",    consoleStyle: "pretty",  },   // Message formatting  messages: {    visibleReplies: "automatic",    responsePrefix: ">",    ackReaction: "👀",    ackReactionScope: "group-mentions",    groupChat: {      historyLimit: 50,      visibleReplies: "message_tool", // opt in for shared rooms with tool-reliable models      unmentionedInbound: "room_event",    },    queue: {      mode: "followup",      cap: 20,      drop: "summarize",      byChannel: {        whatsapp: "followup",        telegram: "followup",        discord: "collect",        slack: "collect",        signal: "followup",        imessage: "followup",        webchat: "followup",      },    },  },   // Session behavior  session: {    scope: "per-sender",    dmScope: "per-channel-peer", // recommended for multi-user inboxes    reset: {      mode: "daily",      atHour: 4,      idleMinutes: 60,    },    resetByChannel: {      discord: { mode: "idle", idleMinutes: 10080 },    },    resetTriggers: ["/new", "/reset"],    store: "~/.openclaw/agents/main/sessions/sessions.json",    maintenance: {      mode: "warn",      pruneAfter: "30d",      maxEntries: 500,      resetArchiveRetention: "30d", // duration or false      maxDiskBytes: "500mb", // optional      highWaterBytes: "400mb", // optional (defaults to 80% of maxDiskBytes)    },    sendPolicy: {      default: "allow",      rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }],    },  },   // Channels  channels: {    whatsapp: {      dmPolicy: "pairing",      allowFrom: ["+15555550123"],      groupPolicy: "allowlist",      groupAllowFrom: ["+15555550123"],      groups: { "*": { requireMention: true } },    },     telegram: {      enabled: true,      botToken: "YOUR_TELEGRAM_BOT_TOKEN",      allowFrom: ["123456789"],      groupPolicy: "allowlist",      groupAllowFrom: ["123456789"],      groups: { "*": { requireMention: true } },    },     discord: {      enabled: true,      token: "YOUR_DISCORD_BOT_TOKEN",      dmPolicy: "allowlist",      allowFrom: ["123456789012345678"],      guilds: {        "123456789012345678": {          slug: "friends-of-openclaw",          requireMention: false,          channels: {            general: { enabled: true },            help: { enabled: true, requireMention: true },          },        },      },    },     slack: {      enabled: true,      botToken: "xoxb-REPLACE_ME",      appToken: "xapp-REPLACE_ME",      channels: {        "#general": { enabled: true, requireMention: true },      },      dmPolicy: "allowlist",      allowFrom: ["U123"],      slashCommand: {        enabled: true,        name: "openclaw",        sessionPrefix: "slack:slash",        ephemeral: true,      },    },  },   // Agent runtime  agents: {    ownership: "explicit",    defaults: {      workspace: "~/.openclaw/workspace",      systemAgent: { agentId: "main" },      sessionStore: { agentId: "main" },      userTimezone: "America/Chicago",      model: {        primary: "anthropic/claude-sonnet-4-6",        fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.4"],      },      imageModel: {        primary: "openrouter/anthropic/claude-sonnet-4-6",      },      models: {        "anthropic/claude-opus-4-6": { alias: "opus" },        "anthropic/claude-sonnet-4-6": { alias: "sonnet" },        "openai/gpt-5.4": { alias: "gpt" },      },      skills: ["github", "weather"], // inherited by agents that omit entries.*.skills      thinkingDefault: "low",      verboseDefault: "off",      toolProgressDetail: "explain",      reasoningDefault: "off",      elevatedDefault: "on",      blockStreamingDefault: "off",      blockStreamingBreak: "text_end",      blockStreamingChunk: {        minChars: 800,        maxChars: 1200,        breakPreference: "paragraph",      },      blockStreamingCoalesce: {        idleMs: 1000,      },      humanDelay: {        mode: "natural",      },      mediaMaxMb: 5,      typingIntervalSeconds: 5,      maxConcurrent: 3,      heartbeat: {        every: "30m",        model: "anthropic/claude-sonnet-4-6",        target: "whatsapp",        directPolicy: "allow", // allow (default) | block        to: "+15555550123",        prompt: "HEARTBEAT",      },      sandbox: {        mode: "non-main",        scope: "session", // preferred over legacy perSession: true        workspaceRoot: "~/.openclaw/sandboxes",        docker: {          image: "openclaw-sandbox:bookworm-slim",          workdir: "/workspace",          readOnlyRoot: true,          tmpfs: ["/tmp", "/var/tmp", "/run"],          network: "none",          user: "1000:1000",        },        browser: {          enabled: false,        },      },    },    entries: {      main: {        workspace: "~/.openclaw/workspace",        identity: {          name: "Samantha",          theme: "helpful sloth",          emoji: "🦥",        },        // inherits defaults.skills -> github, weather        groupChat: {          mentionPatterns: ["@openclaw", "openclaw"],        },        thinkingDefault: "high", // per-agent thinking override        reasoningDefault: "on", // per-agent reasoning visibility        fastModeDefault: false, // per-agent fast mode      },      quick: {        skills: [], // no skills for this agent        fastModeDefault: true, // this agent always runs fast        thinkingDefault: "off",      },    },  },   bindings: [    { agentId: "main", match: { channel: "discord", accountId: "*" } },    { agentId: "main", match: { channel: "slack", accountId: "*" } },    { agentId: "main", match: { channel: "telegram", accountId: "*" } },    { agentId: "main", match: { channel: "whatsapp", accountId: "*" } },  ],  talk: { agentId: "main" },   memory: {    search: {      provider: "gemini",      model: "gemini-embedding-001",      remote: {        apiKey: "${GEMINI_API_KEY}",      },      extraPaths: ["../team-docs", "/srv/shared-notes"],    },  },   tools: {    media: {      models: [        { provider: "openai", model: "gpt-4o-transcribe", capabilities: ["audio"] },        { provider: "google", model: "gemini-3-flash-preview", capabilities: ["video"] },      ],      audio: { enabled: true, maxBytes: 20971520, timeoutSeconds: 120 },      video: { enabled: true, maxBytes: 52428800 },    },    allow: ["exec", "process", "read", "write", "edit", "apply_patch"],    deny: ["browser", "canvas"],    exec: {      backgroundMs: 10000,      timeoutSeconds: 1800,      cleanupMs: 1800000,    },    elevated: {      enabled: true,      allowFrom: {        whatsapp: ["+15555550123"],        telegram: ["123456789"],        discord: ["123456789012345678"],        slack: ["U123"],        signal: ["+15555550123"],        imessage: ["user@example.com"],        webchat: ["session:demo"],      },    },  },   // Custom model providers  models: {    mode: "merge",    providers: {      "custom-proxy": {        baseUrl: "http://localhost:4000/v1",        apiKey: "LITELLM_KEY",        api: "openai-completions",        authHeader: true,        headers: { "X-Proxy-Region": "us-west" },        models: [          {            id: "llama-3.1-8b",            name: "Llama 3.1 8B",            api: "openai-completions",            reasoning: false,            input: ["text"],            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },            contextWindow: 128000,            maxTokens: 32000,          },        ],      },    },  },   // Cron jobs  cron: {    enabled: true,    sessionRetention: "24h",  },   // Webhooks  hooks: {    enabled: true,    path: "/hooks",    token: "shared-secret",    presets: ["gmail"],    transformsDir: "~/.openclaw/hooks/transforms",    mappings: [      {        id: "gmail-hook",        match: { path: "gmail" },        action: "agent",        wakeMode: "now",        name: "Gmail",        // One dispatch per pushed email; templates see the current message.        forEach: "messages",        sessionKey: "hook:gmail:{{messages[0].id}}",        messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}",        textTemplate: "{{messages[0].snippet}}",        deliver: true,        channel: "last",        to: "+15555550123",        thinking: "low",        timeoutSeconds: 300,        transform: {          module: "gmail.js",          export: "transformGmail",        },      },    ],    gmail: {      account: "openclaw@gmail.com",      label: "INBOX",      topic: "projects/<project-id>/topics/gog-gmail-watch",      subscription: "gog-gmail-watch-push",      pushToken: "shared-push-token",      hookUrl: "http://127.0.0.1:18789/hooks/gmail",      includeBody: true,      maxBytes: 20000,      renewEveryMinutes: 720,      serve: { bind: "127.0.0.1", port: 8788, path: "/" },      tailscale: { mode: "funnel", path: "/gmail-pubsub" },    },  },   // Gateway + networking  gateway: {    mode: "local",    port: 18789,    bind: "loopback",    controlUi: { enabled: true, basePath: "/openclaw" },    auth: {      mode: "token",      token: "gateway-token",      allowTailscale: true,    },    tailscale: { mode: "serve" },    remote: { url: "ws://gateway-host.ts.net:18789", token: "remote-token" },    reload: { mode: "hybrid" },  },   skills: {    allowBundled: ["gemini", "peekaboo"],    load: {      extraDirs: ["~/path/to/agent-scripts/skills"],      allowSymlinkTargets: ["~/path/to/agent-scripts/skills"],    },    install: {      preferBrew: true,      nodeManager: "npm", // npm | pnpm | yarn | bun      allowUploadedArchives: false,    },    entries: {      "image-lab": {        enabled: true,        apiKey: "GEMINI_KEY_HERE",        env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },      },      peekaboo: { enabled: true },    },  },}

Symlinked sibling skill repo

Use this when a built-in skill root contains a symlink into a sibling repo, for example ~/.agents/skills/manager -> ~/path/to/skills.

json5
{  skills: {    load: {      extraDirs: ["~/path/to/skills"],      allowSymlinkTargets: ["~/path/to/skills"],    },  },}
  • extraDirs scans the sibling repo as an explicit skill root.
  • allowSymlinkTargets lets symlinked skill folders resolve into that trusted real target root without allowing arbitrary symlink escapes.
  • Skill Workshop does not use these configured symlink targets. It writes only inside the active agent's <state-dir>/agents/<agentId>/agent/workshop-skills.

Common patterns

Shared skill baseline with one override

json5
{  agents: {    ownership: "explicit",    defaults: {      workspace: "~/.openclaw/workspace",      skills: ["github", "weather"],      heartbeat: { agentId: "main" },      systemAgent: { agentId: "main" },    },    entries: {      main: { workspace: "~/.openclaw/workspace" },      docs: { workspace: "~/.openclaw/workspace-docs", skills: ["docs-search"] },    },  },  talk: { agentId: "main" },}
  • agents.defaults.skills is the shared baseline.
  • agents.entries.*.skills replaces that baseline for one agent.
  • Use skills: [] when an agent should see no skills.

Multi-platform setup

json5
{  agents: { defaults: { workspace: "~/.openclaw/workspace" } },  channels: {    whatsapp: { allowFrom: ["+15555550123"], responsePrefix: "[openclaw]" },    telegram: {      enabled: true,      botToken: "YOUR_TOKEN",      allowFrom: ["123456789"],    },    discord: {      enabled: true,      token: "YOUR_TOKEN",      allowFrom: ["123456789012345678"],    },  },}

Trusted node network auto-approval

Keep device pairing manual unless you control the network path. For a dedicated lab or tailnet subnet, you can opt in to first-time node device auto-approval with exact CIDRs or IPs:

json5
{  gateway: {    nodes: {      pairing: {        autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],      },    },  },}

This remains off when unset. It only applies to fresh role: node pairing with no requested scopes. Operator/browser clients and role, scope, metadata, or public-key upgrades still require manual approval.

Secure DM mode (shared inbox / multi-user DMs)

If more than one person can DM your bot (multiple entries in allowFrom, pairing approvals for multiple people, or dmPolicy: "open"), enable secure DM mode so DMs from different senders don't share one context by default:

json5
{  // Secure DM mode (recommended for multi-user or sensitive DM agents)  session: { dmScope: "per-channel-peer" },   channels: {    // Example: WhatsApp multi-user inbox    whatsapp: {      dmPolicy: "allowlist",      allowFrom: ["+15555550123", "+15555550124"],    },     // Example: Discord multi-user inbox    discord: {      enabled: true,      token: "YOUR_DISCORD_BOT_TOKEN",      allowFrom: ["123456789012345678", "987654321098765432"],    },  },}

For Discord/Google Chat/IRC/Mattermost/Microsoft Teams/Slack, sender authorization is ID-first by default. Only enable direct mutable name/email/nick matching with each channel's dangerouslyAllowNameMatching: true if you explicitly accept that risk.

Anthropic API key + MiniMax fallback

json5
{  auth: {    profiles: {      "anthropic:api": {        provider: "anthropic",        mode: "api_key",      },    },    order: {      anthropic: ["anthropic:api"],    },  },  models: {    providers: {      minimax: {        baseUrl: "https://api.minimax.io/anthropic",        api: "anthropic-messages",        apiKey: "${MINIMAX_API_KEY}",      },    },  },  agents: {    defaults: {      workspace: "~/.openclaw/workspace",      model: {        primary: "anthropic/claude-opus-4-6",        fallbacks: ["minimax/MiniMax-M2.7"],      },    },  },}

Work bot (restricted access)

json5
{  agents: {    defaults: {      workspace: "~/work-openclaw",      elevatedDefault: "off",    },    entries: {      main: {        identity: {          name: "WorkBot",          theme: "professional assistant",        },      },    },  },  channels: {    slack: {      enabled: true,      botToken: "xoxb-...",      channels: {        "#engineering": { enabled: true, requireMention: true },        "#general": { enabled: true, requireMention: true },      },    },  },}

Local models only

json5
{  agents: {    defaults: {      workspace: "~/.openclaw/workspace",      model: { primary: "lmstudio/my-local-model" },    },  },  models: {    mode: "merge",    providers: {      lmstudio: {        baseUrl: "http://127.0.0.1:1234/v1",        apiKey: "lmstudio",        api: "openai-responses",        models: [          {            id: "my-local-model",            name: "Local Model",            reasoning: false,            input: ["text"],            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },            contextWindow: 196608,            maxTokens: 8192,          },        ],      },    },  },}

Tips

  • If you set dmPolicy: "open", the matching allowFrom list must include "*".
  • Provider IDs differ (phone numbers, user IDs, channel IDs). Use the provider docs to confirm the format.
  • Optional sections to add later: web, browser, ui, discovery, plugins, talk, signal, imessage.
  • See Providers and Troubleshooting for deeper setup notes.
Was this useful?