๊ตฌ์„ฑ ๐Ÿ”ง

OpenClaw๋Š” ~/.openclaw/openclaw.json์—์„œ ์„ ํƒ์  JSON5 ๊ตฌ์„ฑ์„ ์ฝ์Šต๋‹ˆ๋‹ค(์ฃผ์„ + ํ›„ํ–‰ ์‰ผํ‘œ ํ—ˆ์šฉ).

ํŒŒ์ผ์ด ์—†์œผ๋ฉด OpenClaw๋Š” ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์ž„๋ฒ ๋””๋“œ Pi ์—์ด์ „ํŠธ + ๋ฐœ์‹ ์ž๋ณ„ ์„ธ์…˜ + ์ž‘์—… ๊ณต๊ฐ„ ~/.openclaw/workspace). ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋งŒ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  • ๋ด‡์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ œํ•œ(channels.whatsapp.allowFrom, channels.telegram.allowFrom ๋“ฑ)
  • ๊ทธ๋ฃน ํ—ˆ์šฉ ๋ชฉ๋ก + ๋ฉ˜์…˜ ๋™์ž‘ ์ œ์–ด(channels.whatsapp.groups, channels.telegram.groups, channels.discord.guilds, agents.list[].groupChat)
  • ๋ฉ”์‹œ์ง€ ์ ‘๋‘์‚ฌ ์‚ฌ์šฉ์ž ์ง€์ •(messages)
  • ์—์ด์ „ํŠธ์˜ ์ž‘์—… ๊ณต๊ฐ„ ์„ค์ •(agents.defaults.workspace ๋˜๋Š” agents.list[].workspace)
  • ์ž„๋ฒ ๋””๋“œ ์—์ด์ „ํŠธ ๊ธฐ๋ณธ๊ฐ’(agents.defaults) ๋ฐ ์„ธ์…˜ ๋™์ž‘(session) ์กฐ์ •
  • ์—์ด์ „ํŠธ๋ณ„ ์ •์ฒด์„ฑ ์„ค์ •(agents.list[].identity)

๊ตฌ์„ฑ์ด ์ฒ˜์Œ์ด์‹ ๊ฐ€์š”? ์ž์„ธํ•œ ์„ค๋ช…์ด ํฌํ•จ๋œ ์ „์ฒด ์˜ˆ์ œ๋Š” ๊ตฌ์„ฑ ์˜ˆ์ œ ๊ฐ€์ด๋“œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”!

์—„๊ฒฉํ•œ ๊ตฌ์„ฑ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

OpenClaw๋Š” ์Šคํ‚ค๋งˆ์™€ ์™„์ „ํžˆ ์ผ์น˜ํ•˜๋Š” ๊ตฌ์„ฑ๋งŒ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์•Œ ์ˆ˜ ์—†๋Š” ํ‚ค, ์ž˜๋ชป๋œ ํ˜•์‹์˜ ํƒ€์ž… ๋˜๋Š” ์ž˜๋ชป๋œ ๊ฐ’์ด ์žˆ์œผ๋ฉด Gateway๊ฐ€ ์•ˆ์ „์„ ์œ„ํ•ด ์‹œ์ž‘์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ•˜๋ฉด:

  • Gateway๊ฐ€ ๋ถ€ํŒ…๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ง„๋‹จ ๋ช…๋ น๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค(์˜ˆ: openclaw doctor, openclaw logs, openclaw health, openclaw status, openclaw service, openclaw help).
  • openclaw doctor๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ •ํ™•ํ•œ ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
  • openclaw doctor --fix(๋˜๋Š” --yes)๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜/์ˆ˜๋ฆฌ๋ฅผ ์ ์šฉํ•˜์„ธ์š”.

Doctor๋Š” ๋ช…์‹œ์ ์œผ๋กœ --fix/--yes๋ฅผ ์„ ํƒํ•˜์ง€ ์•Š๋Š” ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ + UI ํžŒํŠธ

Gateway๋Š” UI ํŽธ์ง‘๊ธฐ๋ฅผ ์œ„ํ•ด config.schema๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑ์˜ JSON Schema ํ‘œํ˜„์„ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. Control UI๋Š” ์ด ์Šคํ‚ค๋งˆ์—์„œ ํผ์„ ๋ Œ๋”๋งํ•˜๋ฉฐ, Raw JSON ํŽธ์ง‘๊ธฐ๋ฅผ ํƒˆ์ถœ๊ตฌ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ฑ„๋„ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐ ํ™•์žฅ์€ ๊ตฌ์„ฑ์— ๋Œ€ํ•œ ์Šคํ‚ค๋งˆ + UI ํžŒํŠธ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฑ„๋„ ์„ค์ •์ด ํ•˜๋“œ์ฝ”๋”ฉ๋œ ํผ ์—†์ด ์•ฑ ์ „์ฒด์—์„œ ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

ํžŒํŠธ(๋ ˆ์ด๋ธ”, ๊ทธ๋ฃนํ™”, ๋ฏผ๊ฐํ•œ ํ•„๋“œ)๋Š” ์Šคํ‚ค๋งˆ์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ตฌ์„ฑ ์ง€์‹์„ ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ ๋„ ๋” ๋‚˜์€ ํผ์„ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Apply + restart (RPC)

config.apply๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด ๊ตฌ์„ฑ์„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + ์ž‘์„ฑํ•˜๊ณ  Gateway๋ฅผ ํ•œ ๋ฒˆ์— ์žฌ์‹œ์ž‘ํ•˜์„ธ์š”. ์žฌ์‹œ์ž‘ ์„ผํ‹ฐ๋„์„ ์ž‘์„ฑํ•˜๊ณ  Gateway๊ฐ€ ๋‹ค์‹œ ์‹œ์ž‘๋œ ํ›„ ๋งˆ์ง€๋ง‰ ํ™œ์„ฑ ์„ธ์…˜์— ํ•‘์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

๊ฒฝ๊ณ : config.apply๋Š” ์ „์ฒด ๊ตฌ์„ฑ์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๋ช‡ ๊ฐœ์˜ ํ‚ค๋งŒ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด config.patch ๋˜๋Š” openclaw config set์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ~/.openclaw/openclaw.json์˜ ๋ฐฑ์—…์„ ์œ ์ง€ํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜:

  • raw (๋ฌธ์ž์—ด) โ€” ์ „์ฒด ๊ตฌ์„ฑ์— ๋Œ€ํ•œ JSON5 ํŽ˜์ด๋กœ๋“œ
  • baseHash (์„ ํƒ ์‚ฌํ•ญ) โ€” config.get์˜ ๊ตฌ์„ฑ ํ•ด์‹œ(๊ตฌ์„ฑ์ด ์ด๋ฏธ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•„์ˆ˜)
  • sessionKey (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ํ•‘์„ ์œ„ํ•œ ๋งˆ์ง€๋ง‰ ํ™œ์„ฑ ์„ธ์…˜ ํ‚ค
  • note (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ์„ผํ‹ฐ๋„์— ํฌํ•จํ•  ๋ฉ”๋ชจ
  • restartDelayMs (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ์ „ ์ง€์—ฐ(๊ธฐ๋ณธ๊ฐ’ 2000)

์˜ˆ์ œ(gateway call์„ ํ†ตํ•ด):

openclaw gateway call config.get --params '{}' # capture payload.hash
openclaw gateway call config.apply --params '{
  "raw": "{\\n  agents: { defaults: { workspace: \\"~/.openclaw/workspace\\" } }\\n}\\n",
  "baseHash": "<hash-from-config.get>",
  "sessionKey": "agent:main:whatsapp:dm:+15555550123",
  "restartDelayMs": 1000
}'

๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ (RPC)

config.patch๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ จ ์—†๋Š” ํ‚ค๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ  ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ธฐ์กด ๊ตฌ์„ฑ์— ๋ณ‘ํ•ฉํ•˜์„ธ์š”. JSON ๋ณ‘ํ•ฉ ํŒจ์น˜ ์˜๋ฏธ ์ฒด๊ณ„๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ๊ฐ์ฒด๋Š” ์žฌ๊ท€์ ์œผ๋กœ ๋ณ‘ํ•ฉ
  • null์€ ํ‚ค ์‚ญ์ œ
  • ๋ฐฐ์—ด์€ ๋Œ€์ฒด config.apply์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ  ๊ตฌ์„ฑ์„ ์ž‘์„ฑํ•˜๋ฉฐ ์žฌ์‹œ์ž‘ ์„ผํ‹ฐ๋„์„ ์ €์žฅํ•˜๊ณ  Gateway ์žฌ์‹œ์ž‘์„ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค(sessionKey๊ฐ€ ์ œ๊ณต๋œ ๊ฒฝ์šฐ ์„ ํƒ์  ์žฌ์‹œ์ž‘ ์•Œ๋ฆผ ํฌํ•จ).

๋งค๊ฐœ๋ณ€์ˆ˜:

  • raw (๋ฌธ์ž์—ด) โ€” ๋ณ€๊ฒฝํ•  ํ‚ค๋งŒ ํฌํ•จํ•˜๋Š” JSON5 ํŽ˜์ด๋กœ๋“œ
  • baseHash (ํ•„์ˆ˜) โ€” config.get์˜ ๊ตฌ์„ฑ ํ•ด์‹œ
  • sessionKey (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ํ•‘์„ ์œ„ํ•œ ๋งˆ์ง€๋ง‰ ํ™œ์„ฑ ์„ธ์…˜ ํ‚ค
  • note (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ์„ผํ‹ฐ๋„์— ํฌํ•จํ•  ๋ฉ”๋ชจ
  • restartDelayMs (์„ ํƒ ์‚ฌํ•ญ) โ€” ์žฌ์‹œ์ž‘ ์ „ ์ง€์—ฐ(๊ธฐ๋ณธ๊ฐ’ 2000)

์˜ˆ์ œ:

openclaw gateway call config.get --params '{}' # capture payload.hash
openclaw gateway call config.patch --params '{
  "raw": "{\\n  channels: { telegram: { groups: { \\"*\\": { requireMention: false } } } }\\n}\\n",
  "baseHash": "<hash-from-config.get>",
  "sessionKey": "agent:main:whatsapp:dm:+15555550123",
  "restartDelayMs": 1000
}'

์ตœ์†Œ ๊ตฌ์„ฑ (๊ถŒ์žฅ ์‹œ์ž‘์ )

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

๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค:

scripts/sandbox-setup.sh

์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ (๊ทธ๋ฃน ์ œ์–ด์— ๊ถŒ์žฅ)

๊ทธ๋ฃน์—์„œ WhatsApp @-๋ฉ˜์…˜์— ์‘๋‹ตํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋ ค๋ฉด(ํŠน์ • ํ…์ŠคํŠธ ํŠธ๋ฆฌ๊ฑฐ์—๋งŒ ์‘๋‹ต):

{
  agents: {
    defaults: { workspace: "~/.openclaw/workspace" },
    list: [
      {
        id: "main",
        groupChat: { mentionPatterns: ["@openclaw", "reisponde"] }
      }
    ]
  },
  channels: {
    whatsapp: {
      // ํ—ˆ์šฉ ๋ชฉ๋ก์€ DM ์ „์šฉ์ž…๋‹ˆ๋‹ค; ์ž์‹ ์˜ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜๋ฉด ์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
      allowFrom: ["+15555550123"],
      groups: { "*": { requireMention: true } }
    }
  }
}

๊ตฌ์„ฑ ํฌํ•จ ($include)

$include ์ง€์‹œ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์„ฑ์„ ์—ฌ๋Ÿฌ ํŒŒ์ผ๋กœ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ๋Œ€๊ทœ๋ชจ ๊ตฌ์„ฑ ๊ตฌ์„ฑ(์˜ˆ: ํด๋ผ์ด์–ธํŠธ๋ณ„ ์—์ด์ „ํŠธ ์ •์˜)
  • ํ™˜๊ฒฝ ๊ฐ„ ๊ณตํ†ต ์„ค์ • ๊ณต์œ 
  • ๋ฏผ๊ฐํ•œ ๊ตฌ์„ฑ์„ ๋ณ„๋„๋กœ ์œ ์ง€

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

// ~/.openclaw/openclaw.json
{
  gateway: { port: 18789 },
  
  // ๋‹จ์ผ ํŒŒ์ผ ํฌํ•จ(ํ‚ค์˜ ๊ฐ’์„ ๋Œ€์ฒด)
  agents: { "$include": "./agents.json5" },
  
  // ์—ฌ๋Ÿฌ ํŒŒ์ผ ํฌํ•จ(์ˆœ์„œ๋Œ€๋กœ ๊นŠ์ด ๋ณ‘ํ•ฉ)
  broadcast: { 
    "$include": [
      "./clients/mueller.json5",
      "./clients/schmidt.json5"
    ]
  }
}
// ~/.openclaw/agents.json5
{
  defaults: { sandbox: { mode: "all", scope: "session" } },
  list: [
    { id: "main", workspace: "~/.openclaw/workspace" }
  ]
}

๋ณ‘ํ•ฉ ๋™์ž‘

  • ๋‹จ์ผ ํŒŒ์ผ: $include๊ฐ€ ํฌํ•จ๋œ ๊ฐ์ฒด๋ฅผ ๋Œ€์ฒด
  • ํŒŒ์ผ ๋ฐฐ์—ด: ํŒŒ์ผ์„ ์ˆœ์„œ๋Œ€๋กœ ๊นŠ์ด ๋ณ‘ํ•ฉ(๋‚˜์ค‘ ํŒŒ์ผ์ด ์ด์ „ ํŒŒ์ผ์„ ์žฌ์ •์˜)
  • ํ˜•์ œ ํ‚ค ํฌํ•จ: ํฌํ•จ ํ›„ ํ˜•์ œ ํ‚ค๊ฐ€ ๋ณ‘ํ•ฉ๋ฉ๋‹ˆ๋‹ค(ํฌํ•จ๋œ ๊ฐ’์„ ์žฌ์ •์˜)
  • ํ˜•์ œ ํ‚ค + ๋ฐฐ์—ด/์›์‹œ๊ฐ’: ์ง€์›๋˜์ง€ ์•Š์Œ(ํฌํ•จ๋œ ์ฝ˜ํ…์ธ ๋Š” ๊ฐ์ฒด์—ฌ์•ผ ํ•จ)
// ํ˜•์ œ ํ‚ค๋Š” ํฌํ•จ๋œ ๊ฐ’์„ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค
{
  "$include": "./base.json5",   // { a: 1, b: 2 }
  b: 99                          // ๊ฒฐ๊ณผ: { a: 1, b: 99 }
}

์ค‘์ฒฉ ํฌํ•จ

ํฌํ•จ๋œ ํŒŒ์ผ ์ž์ฒด์— $include ์ง€์‹œ๋ฌธ์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์ตœ๋Œ€ 10๋ ˆ๋ฒจ ๊นŠ์ด):

// clients/mueller.json5
{
  agents: { "$include": "./mueller/agents.json5" },
  broadcast: { "$include": "./mueller/broadcast.json5" }
}

๊ฒฝ๋กœ ํ•ด์„

  • ์ƒ๋Œ€ ๊ฒฝ๋กœ: ํฌํ•จํ•˜๋Š” ํŒŒ์ผ์„ ๊ธฐ์ค€์œผ๋กœ ํ•ด์„
  • ์ ˆ๋Œ€ ๊ฒฝ๋กœ: ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
  • ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ: ../ ์ฐธ์กฐ๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™
{ "$include": "./sub/config.json5" }      // ์ƒ๋Œ€ ๊ฒฝ๋กœ
{ "$include": "/etc/openclaw/base.json5" } // ์ ˆ๋Œ€ ๊ฒฝ๋กœ
{ "$include": "../shared/common.json5" }   // ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ

์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

  • ํŒŒ์ผ ๋ˆ„๋ฝ: ํ•ด์„๋œ ๊ฒฝ๋กœ์™€ ํ•จ๊ป˜ ๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜
  • ํŒŒ์‹ฑ ์˜ค๋ฅ˜: ์‹คํŒจํ•œ ํฌํ•จ ํŒŒ์ผ ํ‘œ์‹œ
  • ์ˆœํ™˜ ํฌํ•จ: ํฌํ•จ ์ฒด์ธ๊ณผ ํ•จ๊ป˜ ๊ฐ์ง€ ๋ฐ ๋ณด๊ณ 

์˜ˆ์ œ: ๋‹ค์ค‘ ํด๋ผ์ด์–ธํŠธ ๋ฒ•๋ฅ  ์„ค์ •

// ~/.openclaw/openclaw.json
{
  gateway: { port: 18789, auth: { token: "secret" } },
  
  // ๊ณตํ†ต ์—์ด์ „ํŠธ ๊ธฐ๋ณธ๊ฐ’
  agents: {
    defaults: {
      sandbox: { mode: "all", scope: "session" }
    },
    // ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์˜ ์—์ด์ „ํŠธ ๋ชฉ๋ก ๋ณ‘ํ•ฉ
    list: { "$include": [
      "./clients/mueller/agents.json5",
      "./clients/schmidt/agents.json5"
    ]}
  },
  
  // ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๊ตฌ์„ฑ ๋ณ‘ํ•ฉ
  broadcast: { "$include": [
    "./clients/mueller/broadcast.json5",
    "./clients/schmidt/broadcast.json5"
  ]},
  
  channels: { whatsapp: { groupPolicy: "allowlist" } }
}
// ~/.openclaw/clients/mueller/agents.json5
[
  { id: "mueller-transcribe", workspace: "~/clients/mueller/transcribe" },
  { id: "mueller-docs", workspace: "~/clients/mueller/docs" }
]
// ~/.openclaw/clients/mueller/broadcast.json5
{
  "[email protected]": ["mueller-transcribe", "mueller-docs"]
}

๊ณตํ†ต ์˜ต์…˜

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ + .env

OpenClaw๋Š” ์ƒ์œ„ ํ”„๋กœ์„ธ์Šค(์…ธ, launchd/systemd, CI ๋“ฑ)์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋‹ค์Œ์„ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค:

  • ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ์˜ .env(์žˆ๋Š” ๊ฒฝ์šฐ)
  • ~/.openclaw/.env(์ฆ‰, $OPENCLAW_STATE_DIR/.env)์˜ ์ „์—ญ ๋Œ€์ฒด .env

๋‘ .env ํŒŒ์ผ ๋ชจ๋‘ ๊ธฐ์กด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ตฌ์„ฑ์—์„œ ์ธ๋ผ์ธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ”„๋กœ์„ธ์Šค ํ™˜๊ฒฝ์— ํ‚ค๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค(๋™์ผํ•œ ๋น„์žฌ์ •์˜ ๊ทœ์น™):

{
  env: {
    OPENROUTER_API_KEY: "sk-or-...",
    vars: {
      GROQ_API_KEY: "gsk-..."
    }
  }
}

์ „์ฒด ์šฐ์„  ์ˆœ์œ„ ๋ฐ ์†Œ์Šค๋Š” /environment๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

env.shellEnv (์„ ํƒ ์‚ฌํ•ญ)

์˜ตํŠธ์ธ ํŽธ์˜ ๊ธฐ๋Šฅ: ํ™œ์„ฑํ™”๋˜๊ณ  ์˜ˆ์ƒ ํ‚ค๊ฐ€ ์•„์ง ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ OpenClaw๋Š” ๋กœ๊ทธ์ธ ์…ธ์„ ์‹คํ–‰ํ•˜๊ณ  ๋ˆ„๋ฝ๋œ ์˜ˆ์ƒ ํ‚ค๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค(์žฌ์ •์˜ํ•˜์ง€ ์•Š์Œ). ์ด๋Š” ์…ธ ํ”„๋กœํ•„์„ ํšจ๊ณผ์ ์œผ๋กœ ์†Œ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

{
  env: {
    shellEnv: {
      enabled: true,
      timeoutMs: 15000
    }
  }
}

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ๊ฐ€๋ฌผ:

  • OPENCLAW_LOAD_SHELL_ENV=1
  • OPENCLAW_SHELL_ENV_TIMEOUT_MS=15000

๊ตฌ์„ฑ์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์น˜ํ™˜

${VAR_NAME} ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์„ฑ ๋ฌธ์ž์—ด ๊ฐ’์—์„œ ์ง์ ‘ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€์ˆ˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ „ ๊ตฌ์„ฑ ๋กœ๋“œ ์‹œ ์น˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

{
  models: {
    providers: {
      "vercel-gateway": {
        apiKey: "${VERCEL_GATEWAY_API_KEY}"
      }
    }
  },
  gateway: {
    auth: {
      token: "${OPENCLAW_GATEWAY_TOKEN}"
    }
  }
}

๊ทœ์น™:

  • ๋Œ€๋ฌธ์ž ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ด๋ฆ„๋งŒ ์ผ์น˜: [A-Z_][A-Z0-9_]*
  • ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ๋น„์–ด ์žˆ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ๊ตฌ์„ฑ ๋กœ๋“œ ์‹œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค
  • $${VAR}๋กœ ์ด์Šค์ผ€์ดํ”„ํ•˜์—ฌ ๋ฆฌํ„ฐ๋Ÿด ${VAR} ์ถœ๋ ฅ
  • $include์™€ ํ•จ๊ป˜ ์ž‘๋™(ํฌํ•จ๋œ ํŒŒ์ผ๋„ ์น˜ํ™˜ ๊ฐ€๋Šฅ)

์ธ๋ผ์ธ ์น˜ํ™˜:

{
  models: {
    providers: {
      custom: {
        baseUrl: "${CUSTOM_API_BASE}/v1"  // โ†’ "https://api.example.com/v1"
      }
    }
  }
}

์ธ์ฆ ์Šคํ† ๋ฆฌ์ง€ (OAuth + API ํ‚ค)

OpenClaw๋Š” ์—์ด์ „ํŠธ๋ณ„ ์ธ์ฆ ํ”„๋กœํ•„(OAuth + API ํ‚ค)์„ ๋‹ค์Œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค:

  • <agentDir>/auth-profiles.json(๊ธฐ๋ณธ๊ฐ’: ~/.openclaw/agents/<agentId>/agent/auth-profiles.json)

์ฐธ์กฐ: /concepts/oauth

๋ ˆ๊ฑฐ์‹œ OAuth ๊ฐ€์ ธ์˜ค๊ธฐ:

  • ~/.openclaw/credentials/oauth.json(๋˜๋Š” $OPENCLAW_STATE_DIR/credentials/oauth.json)

์ž„๋ฒ ๋””๋“œ Pi ์—์ด์ „ํŠธ๋Š” ๋‹ค์Œ ์œ„์น˜์—์„œ ๋Ÿฐํƒ€์ž„ ์บ์‹œ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค:

  • <agentDir>/auth.json(์ž๋™์œผ๋กœ ๊ด€๋ฆฌ๋จ; ์ˆ˜๋™์œผ๋กœ ํŽธ์ง‘ํ•˜์ง€ ๋งˆ์„ธ์š”)

๋ ˆ๊ฑฐ์‹œ ์—์ด์ „ํŠธ ๋””๋ ‰ํ† ๋ฆฌ(๋‹ค์ค‘ ์—์ด์ „ํŠธ ์ด์ „):

  • ~/.openclaw/agent/*(openclaw doctor์— ์˜ํ•ด ~/.openclaw/agents/<defaultAgentId>/agent/*๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋จ)

์žฌ์ •์˜:

  • OAuth ๋””๋ ‰ํ† ๋ฆฌ(๋ ˆ๊ฑฐ์‹œ ๊ฐ€์ ธ์˜ค๊ธฐ๋งŒ): OPENCLAW_OAUTH_DIR
  • ์—์ด์ „ํŠธ ๋””๋ ‰ํ† ๋ฆฌ(๊ธฐ๋ณธ ์—์ด์ „ํŠธ ๋ฃจํŠธ ์žฌ์ •์˜): OPENCLAW_AGENT_DIR(๊ถŒ์žฅ), PI_CODING_AGENT_DIR(๋ ˆ๊ฑฐ์‹œ)

์ฒซ ์‚ฌ์šฉ ์‹œ OpenClaw๋Š” oauth.json ํ•ญ๋ชฉ์„ auth-profiles.json์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

auth

์ธ์ฆ ํ”„๋กœํ•„์— ๋Œ€ํ•œ ์„ ํƒ์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๋น„๋ฐ€์„ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค; ํ”„๋กœํ•„ ID๋ฅผ ๊ณต๊ธ‰์ž + ๋ชจ๋“œ(๋ฐ ์„ ํƒ์  ์ด๋ฉ”์ผ)์— ๋งคํ•‘ํ•˜๊ณ  ์žฅ์•  ์กฐ์น˜์— ์‚ฌ์šฉ๋˜๋Š” ๊ณต๊ธ‰์ž ์ˆœํ™˜ ์ˆœ์„œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

{
  auth: {
    profiles: {
      "anthropic:[email protected]": { provider: "anthropic", mode: "oauth", email: "[email protected]" },
      "anthropic:work": { provider: "anthropic", mode: "api_key" }
    },
    order: {
      anthropic: ["anthropic:[email protected]", "anthropic:work"]
    }
  }
}

agents.list[].identity

๊ธฐ๋ณธ๊ฐ’ ๋ฐ UX์— ์‚ฌ์šฉ๋˜๋Š” ์„ ํƒ์  ์—์ด์ „ํŠธ๋ณ„ ์ •์ฒด์„ฑ์ž…๋‹ˆ๋‹ค. ์ด๋Š” macOS ์˜จ๋ณด๋”ฉ ์–ด์‹œ์Šคํ„ดํŠธ์— ์˜ํ•ด ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค.

์„ค์ •๋œ ๊ฒฝ์šฐ OpenClaw๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ํŒŒ์ƒํ•ฉ๋‹ˆ๋‹ค(๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋งŒ):

  • ํ™œ์„ฑ ์—์ด์ „ํŠธ์˜ identity.emoji์—์„œ messages.ackReaction(๐Ÿ‘€๋กœ ๋Œ€์ฒด)
  • ์—์ด์ „ํŠธ์˜ identity.name/identity.emoji์—์„œ agents.list[].groupChat.mentionPatterns(Telegram/Slack/Discord/Google Chat/iMessage/WhatsApp์˜ ๊ทธ๋ฃน์—์„œ "@Samantha"๊ฐ€ ์ž‘๋™ํ•˜๋„๋ก)
  • identity.avatar๋Š” ์ž‘์—… ๊ณต๊ฐ„ ์ƒ๋Œ€ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ ๋˜๋Š” ์›๊ฒฉ URL/๋ฐ์ดํ„ฐ URL์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ ํŒŒ์ผ์€ ์—์ด์ „ํŠธ ์ž‘์—… ๊ณต๊ฐ„ ๋‚ด์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

identity.avatar๋Š” ๋‹ค์Œ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ์ž‘์—… ๊ณต๊ฐ„ ์ƒ๋Œ€ ๊ฒฝ๋กœ(์—์ด์ „ํŠธ ์ž‘์—… ๊ณต๊ฐ„ ๋‚ด์— ์žˆ์–ด์•ผ ํ•จ)
  • http(s) URL
  • data: URI
{
  agents: {
    list: [
      {
        id: "main",
        identity: {
          name: "Samantha",
          theme: "helpful sloth",
          emoji: "๐Ÿฆฅ",
          avatar: "avatars/samantha.png"
        }
      }
    ]
  }
}

wizard

CLI ๋งˆ๋ฒ•์‚ฌ(onboard, configure, doctor)์— ์˜ํ•ด ์ž‘์„ฑ๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.

{
  wizard: {
    lastRunAt: "2026-01-01T00:00:00.000Z",
    lastRunVersion: "2026.1.4",
    lastRunCommit: "abc1234",
    lastRunCommand: "configure",
    lastRunMode: "local"
  }
}

logging

  • ๊ธฐ๋ณธ ๋กœ๊ทธ ํŒŒ์ผ: /tmp/openclaw/openclaw-YYYY-MM-DD.log
  • ์•ˆ์ •์ ์ธ ๊ฒฝ๋กœ๋ฅผ ์›ํ•˜๋ฉด logging.file์„ /tmp/openclaw/openclaw.log๋กœ ์„ค์ •ํ•˜์„ธ์š”.
  • ์ฝ˜์†” ์ถœ๋ ฅ์€ ๋‹ค์Œ์„ ํ†ตํ•ด ๋ณ„๋„๋กœ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • logging.consoleLevel(๊ธฐ๋ณธ๊ฐ’ info, --verbose์ผ ๋•Œ debug๋กœ ์ฆ๊ฐ€)
    • logging.consoleStyle(pretty | compact | json)
  • ํˆด ์š”์•ฝ์€ ๋น„๋ฐ€ ์œ ์ถœ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • logging.redactSensitive(off | tools, ๊ธฐ๋ณธ๊ฐ’: tools)
    • logging.redactPatterns(์ •๊ทœ์‹ ๋ฌธ์ž์—ด ๋ฐฐ์—ด; ๊ธฐ๋ณธ๊ฐ’ ์žฌ์ •์˜)
{
  logging: {
    level: "info",
    file: "/tmp/openclaw/openclaw.log",
    consoleLevel: "info",
    consoleStyle: "pretty",
    redactSensitive: "tools",
    redactPatterns: [
      // ์˜ˆ์ œ: ์ž์ฒด ๊ทœ์น™์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’ ์žฌ์ •์˜.
      "\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1",
      "/\\bsk-[A-Za-z0-9_-]{8,}\\b/gi"
    ]
  }
}

channels.whatsapp.dmPolicy

WhatsApp ๋‹ค์ด๋ ‰ํŠธ ์ฑ„ํŒ…(DM)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค:

  • "pairing"(๊ธฐ๋ณธ๊ฐ’): ์•Œ ์ˆ˜ ์—†๋Š” ๋ฐœ์‹ ์ž๋Š” ํŽ˜์–ด๋ง ์ฝ”๋“œ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค; ์†Œ์œ ์ž๊ฐ€ ์Šน์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • "allowlist": channels.whatsapp.allowFrom(๋˜๋Š” ํŽ˜์–ด๋ง๋œ ํ—ˆ์šฉ ์ €์žฅ์†Œ)์˜ ๋ฐœ์‹ ์ž๋งŒ ํ—ˆ์šฉ
  • "open": ๋ชจ๋“  ์ธ๋ฐ”์šด๋“œ DM ํ—ˆ์šฉ(channels.whatsapp.allowFrom์— "*"๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•จ)
  • "disabled": ๋ชจ๋“  ์ธ๋ฐ”์šด๋“œ DM ๋ฌด์‹œ

ํŽ˜์–ด๋ง ์ฝ”๋“œ๋Š” 1์‹œ๊ฐ„ ํ›„์— ๋งŒ๋ฃŒ๋ฉ๋‹ˆ๋‹ค; ๋ด‡์€ ์ƒˆ ์š”์ฒญ์ด ์ƒ์„ฑ๋  ๋•Œ๋งŒ ํŽ˜์–ด๋ง ์ฝ”๋“œ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ๋Œ€๊ธฐ ์ค‘์ธ DM ํŽ˜์–ด๋ง ์š”์ฒญ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฑ„๋„๋‹น 3๊ฐœ๋กœ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.

ํŽ˜์–ด๋ง ์Šน์ธ:

  • openclaw pairing list whatsapp
  • openclaw pairing approve whatsapp <code>

channels.whatsapp.allowFrom

WhatsApp ์ž๋™ ์‘๋‹ต์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” E.164 ์ „ํ™”๋ฒˆํ˜ธ์˜ ํ—ˆ์šฉ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค(DM ์ „์šฉ). ๋น„์–ด ์žˆ๊ณ  channels.whatsapp.dmPolicy="pairing"์ธ ๊ฒฝ์šฐ ์•Œ ์ˆ˜ ์—†๋Š” ๋ฐœ์‹ ์ž๋Š” ํŽ˜์–ด๋ง ์ฝ”๋“œ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฃน์˜ ๊ฒฝ์šฐ channels.whatsapp.groupPolicy + channels.whatsapp.groupAllowFrom์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

{
  channels: {
    whatsapp: {
      dmPolicy: "pairing", // pairing | allowlist | open | disabled
      allowFrom: ["+15555550123", "+447700900123"],
      textChunkLimit: 4000, // ์„ ํƒ์  ์•„์›ƒ๋ฐ”์šด๋“œ ์ฒญํฌ ํฌ๊ธฐ(๋ฌธ์ž)
      chunkMode: "length", // ์„ ํƒ์  ์ฒญํ‚น ๋ชจ๋“œ(length | newline)
      mediaMaxMb: 50 // ์„ ํƒ์  ์ธ๋ฐ”์šด๋“œ ๋ฏธ๋””์–ด ์ œํ•œ(MB)
    }
  }
}

channels.whatsapp.sendReadReceipts

์ธ๋ฐ”์šด๋“œ WhatsApp ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์Œ์œผ๋กœ ํ‘œ์‹œํ• ์ง€(ํŒŒ๋ž€์ƒ‰ ์ฒดํฌ ํ‘œ์‹œ) ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’: true.

์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ๋Š” ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์–ด๋„ ํ•ญ์ƒ ์ฝ์Œ ํ™•์ธ์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.

๊ณ„์ •๋ณ„ ์žฌ์ •์˜: channels.whatsapp.accounts.<id>.sendReadReceipts.

{
  channels: {
    whatsapp: { sendReadReceipts: false }
  }
}

channels.whatsapp.accounts (๋‹ค์ค‘ ๊ณ„์ •)

ํ•˜๋‚˜์˜ Gateway์—์„œ ์—ฌ๋Ÿฌ WhatsApp ๊ณ„์ •์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค:

{
  channels: {
    whatsapp: {
      accounts: {
        default: {}, // ์„ ํƒ ์‚ฌํ•ญ; ๊ธฐ๋ณธ ID๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์œ ์ง€
        personal: {},
        biz: {
          // ์„ ํƒ์  ์žฌ์ •์˜. ๊ธฐ๋ณธ๊ฐ’: ~/.openclaw/credentials/whatsapp/biz
          // authDir: "~/.openclaw/credentials/whatsapp/biz",
        }
      }
    }
  }
}

์ฐธ๊ณ :

  • ์•„์›ƒ๋ฐ”์šด๋“œ ๋ช…๋ น์€ default ๊ณ„์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค; ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ๋กœ ๊ตฌ์„ฑ๋œ ๊ณ„์ • ID(์ •๋ ฌ๋จ)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋ ˆ๊ฑฐ์‹œ ๋‹จ์ผ ๊ณ„์ • Baileys ์ธ์ฆ ๋””๋ ‰ํ† ๋ฆฌ๋Š” openclaw doctor์— ์˜ํ•ด whatsapp/default๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋ฉ๋‹ˆ๋‹ค.

channels.telegram.accounts / channels.discord.accounts / channels.googlechat.accounts / channels.slack.accounts / channels.mattermost.accounts / channels.signal.accounts / channels.imessage.accounts

์ฑ„๋„๋‹น ์—ฌ๋Ÿฌ ๊ณ„์ •์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค(๊ฐ ๊ณ„์ •์—๋Š” ์ž์ฒด accountId์™€ ์„ ํƒ์  name์ด ์žˆ์Œ):

{
  channels: {
    telegram: {
      accounts: {
        default: {
          name: "Primary bot",
          botToken: "123456:ABC..."
        },
        alerts: {
          name: "Alerts bot",
          botToken: "987654:XYZ..."
        }
      }
    }
  }
}

์ฐธ๊ณ :

  • accountId๊ฐ€ ์ƒ๋žต๋˜๋ฉด default๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค(CLI + ๋ผ์šฐํŒ…).
  • ํ™˜๊ฒฝ ํ† ํฐ์€ ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ์ฑ„๋„ ์„ค์ •(๊ทธ๋ฃน ์ •์ฑ…, ๋ฉ˜์…˜ ๊ฒŒ์ดํŒ… ๋“ฑ)์€ ๊ณ„์ •๋ณ„๋กœ ์žฌ์ •์˜ํ•˜์ง€ ์•Š๋Š” ํ•œ ๋ชจ๋“  ๊ณ„์ •์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • bindings[].match.accountId๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ๊ณ„์ •์„ ๋‹ค๋ฅธ agents.defaults๋กœ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฃน ์ฑ„ํŒ… ๋ฉ˜์…˜ ๊ฒŒ์ดํŒ… (agents.list[].groupChat + messages.groupChat)

๊ทธ๋ฃน ๋ฉ”์‹œ์ง€๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฉ˜์…˜ ํ•„์š”(๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฉ˜์…˜ ๋˜๋Š” ์ •๊ทœ์‹ ํŒจํ„ด)์ž…๋‹ˆ๋‹ค. WhatsApp, Telegram, Discord, Google Chat ๋ฐ iMessage ๊ทธ๋ฃน ์ฑ„ํŒ…์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

๋ฉ˜์…˜ ํƒ€์ž…:

  • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฉ˜์…˜: ๋„ค์ดํ‹ฐ๋ธŒ ํ”Œ๋žซํผ @-๋ฉ˜์…˜(์˜ˆ: WhatsApp ํƒญํ•˜์—ฌ ๋ฉ˜์…˜). WhatsApp ์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ์—์„œ ๋ฌด์‹œ๋จ(channels.whatsapp.allowFrom ์ฐธ์กฐ).
  • ํ…์ŠคํŠธ ํŒจํ„ด: agents.list[].groupChat.mentionPatterns์— ์ •์˜๋œ ์ •๊ทœ์‹ ํŒจํ„ด. ์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ์™€ ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค.
  • ๋ฉ˜์…˜ ๊ฒŒ์ดํŒ…์€ ๋ฉ˜์…˜ ๊ฐ์ง€๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค(๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ˜์…˜ ๋˜๋Š” ์ตœ์†Œ ํ•˜๋‚˜์˜ mentionPattern).
{
  messages: {
    groupChat: { historyLimit: 50 }
  },
  agents: {
    list: [
      { id: "main", groupChat: { mentionPatterns: ["@openclaw", "openclaw"] } }
    ]
  }
}

messages.groupChat.historyLimit์€ ๊ทธ๋ฃน ํžˆ์Šคํ† ๋ฆฌ ์ปจํ…์ŠคํŠธ์˜ ์ „์—ญ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ฑ„๋„์€ channels.<channel>.historyLimit(๋˜๋Š” ๋‹ค์ค‘ ๊ณ„์ •์˜ ๊ฒฝ์šฐ channels.<channel>.accounts.*.historyLimit)์œผ๋กœ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํžˆ์Šคํ† ๋ฆฌ ๋ž˜ํ•‘์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 0์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”.

DM ํžˆ์Šคํ† ๋ฆฌ ์ œํ•œ

DM ๋Œ€ํ™”๋Š” ์—์ด์ „ํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์„ธ์…˜ ๊ธฐ๋ฐ˜ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. DM ์„ธ์…˜๋‹น ์œ ์ง€๋˜๋Š” ์‚ฌ์šฉ์ž ํ„ด ์ˆ˜๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

{
  channels: {
    telegram: {
      dmHistoryLimit: 30,  // DM ์„ธ์…˜์„ 30 ์‚ฌ์šฉ์ž ํ„ด์œผ๋กœ ์ œํ•œ
      dms: {
        "123456789": { historyLimit: 50 }  // ์‚ฌ์šฉ์ž๋ณ„ ์žฌ์ •์˜(์‚ฌ์šฉ์ž ID)
      }
    }
  }
}

ํ•ด๊ฒฐ ์ˆœ์„œ:

  1. DM๋ณ„ ์žฌ์ •์˜: channels.<provider>.dms[userId].historyLimit
  2. ๊ณต๊ธ‰์ž ๊ธฐ๋ณธ๊ฐ’: channels.<provider>.dmHistoryLimit
  3. ์ œํ•œ ์—†์Œ(๋ชจ๋“  ํžˆ์Šคํ† ๋ฆฌ ์œ ์ง€)

์ง€์›๋˜๋Š” ๊ณต๊ธ‰์ž: telegram, whatsapp, discord, slack, signal, imessage, msteams.

์—์ด์ „ํŠธ๋ณ„ ์žฌ์ •์˜(์„ค์ •๋˜๋ฉด ์šฐ์„  ์ˆœ์œ„๊ฐ€ ๋†’์Œ, []๋„ ํฌํ•จ):

{
  agents: {
    list: [
      { id: "work", groupChat: { mentionPatterns: ["@workbot", "\\+15555550123"] } },
      { id: "personal", groupChat: { mentionPatterns: ["@homebot", "\\+15555550999"] } }
    ]
  }
}

๋ฉ˜์…˜ ๊ฒŒ์ดํŒ… ๊ธฐ๋ณธ๊ฐ’์€ ์ฑ„๋„๋ณ„๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค(channels.whatsapp.groups, channels.telegram.groups, channels.imessage.groups, channels.discord.guilds). *.groups๊ฐ€ ์„ค์ •๋˜๋ฉด ๊ทธ๋ฃน ํ—ˆ์šฉ ๋ชฉ๋ก์œผ๋กœ๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค; ๋ชจ๋“  ๊ทธ๋ฃน์„ ํ—ˆ์šฉํ•˜๋ ค๋ฉด "*"๋ฅผ ํฌํ•จํ•˜์„ธ์š”.

ํŠน์ • ํ…์ŠคํŠธ ํŠธ๋ฆฌ๊ฑฐ์—๋งŒ ์‘๋‹ตํ•˜๋ ค๋ฉด(๋„ค์ดํ‹ฐ๋ธŒ @-๋ฉ˜์…˜ ๋ฌด์‹œ):

{
  channels: {
    whatsapp: {
      // ์ž์‹ ์˜ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜์—ฌ ์…€ํ”„ ์ฑ„ํŒ… ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค(๋„ค์ดํ‹ฐ๋ธŒ @-๋ฉ˜์…˜ ๋ฌด์‹œ).
      allowFrom: ["+15555550123"],
      groups: { "*": { requireMention: true } }
    }
  },
  agents: {
    list: [
      {
        id: "main",
        groupChat: {
          // ์ด ํ…์ŠคํŠธ ํŒจํ„ด๋งŒ ์‘๋‹ต์„ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
          mentionPatterns: ["reisponde", "@openclaw"]
        }
      }
    ]
  }
}

๊ทธ๋ฃน ์ •์ฑ… (์ฑ„๋„๋ณ„)

channels.*.groupPolicy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ทธ๋ฃน/๋ฃธ ๋ฉ”์‹œ์ง€๋ฅผ ์ „ํ˜€ ๋ฐ›์•„๋“ค์ผ์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค:

{
  channels: {
    whatsapp: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"]
    },
    telegram: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["tg:123456789", "@alice"]
    },
    signal: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"]
    },
    imessage: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["chat_id:123"]
    },
    msteams: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["[email protected]"]
    },
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        "GUILD_ID": {
          channels: { help: { allow: true } }
        }
      }
    },
    slack: {
      groupPolicy: "allowlist",
      channels: { "#general": { allow: true } }
    }
  }
}

์ฐธ๊ณ :

  • "open": ๊ทธ๋ฃน์ด ํ—ˆ์šฉ ๋ชฉ๋ก์„ ์šฐํšŒํ•ฉ๋‹ˆ๋‹ค; ๋ฉ˜์…˜ ๊ฒŒ์ดํŒ…์€ ์—ฌ์ „ํžˆ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • "disabled": ๋ชจ๋“  ๊ทธ๋ฃน/๋ฃธ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • "allowlist": ๊ตฌ์„ฑ๋œ ํ—ˆ์šฉ ๋ชฉ๋ก๊ณผ ์ผ์น˜ํ•˜๋Š” ๊ทธ๋ฃน/๋ฃธ๋งŒ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • channels.defaults.groupPolicy๋Š” ๊ณต๊ธ‰์ž์˜ groupPolicy๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • WhatsApp/Telegram/Signal/iMessage/Microsoft Teams๋Š” groupAllowFrom์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(๋Œ€์ฒด: ๋ช…์‹œ์  allowFrom).
  • Discord/Slack์€ ์ฑ„๋„ ํ—ˆ์šฉ ๋ชฉ๋ก์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(channels.discord.guilds.*.channels, channels.slack.channels).
  • ๊ทธ๋ฃน DM(Discord/Slack)์€ ์—ฌ์ „ํžˆ dm.groupEnabled + dm.groupChannels์— ์˜ํ•ด ์ œ์–ด๋ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ๊ฐ’์€ groupPolicy: "allowlist"์ž…๋‹ˆ๋‹ค(channels.defaults.groupPolicy๋กœ ์žฌ์ •์˜ํ•˜์ง€ ์•Š๋Š” ํ•œ); ํ—ˆ์šฉ ๋ชฉ๋ก์ด ๊ตฌ์„ฑ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ทธ๋ฃน ๋ฉ”์‹œ์ง€๊ฐ€ ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์ค‘ ์—์ด์ „ํŠธ ๋ผ์šฐํŒ… (agents.list + bindings)

ํ•˜๋‚˜์˜ Gateway ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ๊ฒฉ๋ฆฌ๋œ ์—์ด์ „ํŠธ(๋ณ„๋„์˜ ์ž‘์—… ๊ณต๊ฐ„, agentDir, ์„ธ์…˜)๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋Š” ๋ฐ”์ธ๋”ฉ์„ ํ†ตํ•ด ์—์ด์ „ํŠธ๋กœ ๋ผ์šฐํŒ…๋ฉ๋‹ˆ๋‹ค.

  • agents.list[]: ์—์ด์ „ํŠธ๋ณ„ ์žฌ์ •์˜.
    • id: ์•ˆ์ •์ ์ธ ์—์ด์ „ํŠธ ID(ํ•„์ˆ˜).
    • default: ์„ ํƒ ์‚ฌํ•ญ; ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ ์ฒซ ๋ฒˆ์งธ๊ฐ€ ์šฐ์„ ๋˜๊ณ  ๊ฒฝ๊ณ ๊ฐ€ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋ชฉ๋ก์˜ ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์ด ๊ธฐ๋ณธ ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค.
    • name: ์—์ด์ „ํŠธ์˜ ํ‘œ์‹œ ์ด๋ฆ„.
    • workspace: ๊ธฐ๋ณธ๊ฐ’ ~/.openclaw/workspace-<agentId>(main์˜ ๊ฒฝ์šฐ agents.defaults.workspace๋กœ ๋Œ€์ฒด).
    • agentDir: ๊ธฐ๋ณธ๊ฐ’ ~/.openclaw/agents/<agentId>/agent.
    • model: ์—์ด์ „ํŠธ๋ณ„ ๊ธฐ๋ณธ ๋ชจ๋ธ, ํ•ด๋‹น ์—์ด์ „ํŠธ์˜ agents.defaults.model์„ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
      • ๋ฌธ์ž์—ด ํ˜•์‹: "provider/model", agents.defaults.model.primary๋งŒ ์žฌ์ •์˜
      • ๊ฐ์ฒด ํ˜•์‹: { primary, fallbacks }(fallbacks๋Š” agents.defaults.model.fallbacks๋ฅผ ์žฌ์ •์˜; []๋Š” ํ•ด๋‹น ์—์ด์ „ํŠธ์˜ ์ „์—ญ fallbacks๋ฅผ ๋น„ํ™œ์„ฑํ™”)
    • identity: ์—์ด์ „ํŠธ๋ณ„ ์ด๋ฆ„/ํ…Œ๋งˆ/์ด๋ชจ์ง€(๋ฉ˜์…˜ ํŒจํ„ด + ack ๋ฐ˜์‘์— ์‚ฌ์šฉ).
    • groupChat: ์—์ด์ „ํŠธ๋ณ„ ๋ฉ˜์…˜ ๊ฒŒ์ดํŒ…(mentionPatterns).
    • sandbox: ์—์ด์ „ํŠธ๋ณ„ ์ƒŒ๋“œ๋ฐ•์Šค ๊ตฌ์„ฑ(agents.defaults.sandbox ์žฌ์ •์˜).
      • mode: "off" | "non-main" | "all"
      • workspaceAccess: "none" | "ro" | "rw"
      • scope: "session" | "agent" | "shared"
      • workspaceRoot: ์‚ฌ์šฉ์ž ์ง€์ • ์ƒŒ๋“œ๋ฐ•์Šค ์ž‘์—… ๊ณต๊ฐ„ ๋ฃจํŠธ
      • docker: ์—์ด์ „ํŠธ๋ณ„ ๋„์ปค ์žฌ์ •์˜(์˜ˆ: image, network, env, setupCommand, ์ œํ•œ; scope: "shared"์ผ ๋•Œ ๋ฌด์‹œ๋จ)
      • browser: ์—์ด์ „ํŠธ๋ณ„ ์ƒŒ๋“œ๋ฐ•์Šค ๋ธŒ๋ผ์šฐ์ € ์žฌ์ •์˜(scope: "shared"์ผ ๋•Œ ๋ฌด์‹œ๋จ)
      • prune: ์—์ด์ „ํŠธ๋ณ„ ์ƒŒ๋“œ๋ฐ•์Šค ์ •๋ฆฌ ์žฌ์ •์˜(scope: "shared"์ผ ๋•Œ ๋ฌด์‹œ๋จ)
    • subagents: ์—์ด์ „ํŠธ๋ณ„ ์„œ๋ธŒ ์—์ด์ „ํŠธ ๊ธฐ๋ณธ๊ฐ’.
      • allowAgents: ์ด ์—์ด์ „ํŠธ์—์„œ sessions_spawn์„ ์œ„ํ•œ ์—์ด์ „ํŠธ ID์˜ ํ—ˆ์šฉ ๋ชฉ๋ก(["*"] = ๋ชจ๋“  ํ—ˆ์šฉ; ๊ธฐ๋ณธ๊ฐ’: ๋™์ผํ•œ ์—์ด์ „ํŠธ๋งŒ)
    • tools: ์—์ด์ „ํŠธ๋ณ„ ๋„๊ตฌ ์ œํ•œ(์ƒŒ๋“œ๋ฐ•์Šค ๋„๊ตฌ ์ •์ฑ… ์ „์— ์ ์šฉ).
      • profile: ๊ธฐ๋ณธ ๋„๊ตฌ ํ”„๋กœํ•„(allow/deny ์ „์— ์ ์šฉ)
      • allow: ํ—ˆ์šฉ๋œ ๋„๊ตฌ ์ด๋ฆ„ ๋ฐฐ์—ด
      • deny: ๊ฑฐ๋ถ€๋œ ๋„๊ตฌ ์ด๋ฆ„ ๋ฐฐ์—ด(๊ฑฐ๋ถ€๊ฐ€ ์šฐ์„ )
  • agents.defaults: ๊ณต์œ  ์—์ด์ „ํŠธ ๊ธฐ๋ณธ๊ฐ’(๋ชจ๋ธ, ์ž‘์—… ๊ณต๊ฐ„, ์ƒŒ๋“œ๋ฐ•์Šค ๋“ฑ).
  • bindings[]: ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋ฅผ agentId๋กœ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค.
    • match.channel(ํ•„์ˆ˜)
    • match.accountId(์„ ํƒ ์‚ฌํ•ญ; * = ๋ชจ๋“  ๊ณ„์ •; ์ƒ๋žต = ๊ธฐ๋ณธ ๊ณ„์ •)
    • match.peer(์„ ํƒ ์‚ฌํ•ญ; { kind: dm|group|channel, id })
    • match.guildId / match.teamId(์„ ํƒ ์‚ฌํ•ญ; ์ฑ„๋„๋ณ„)

๊ฒฐ์ •๋ก ์  ์ผ์น˜ ์ˆœ์„œ:

  1. match.peer
  2. match.guildId
  3. match.teamId
  4. match.accountId(์ •ํ™•, peer/guild/team ์—†์Œ)
  5. match.accountId: "*"(์ฑ„๋„ ์ „์ฒด, peer/guild/team ์—†์Œ)
  6. ๊ธฐ๋ณธ ์—์ด์ „ํŠธ(agents.list[].default, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ ๋ชฉ๋ก ํ•ญ๋ชฉ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด "main")

๊ฐ ์ผ์น˜ ๊ณ„์ธต ๋‚ด์—์„œ bindings์˜ ์ฒซ ๋ฒˆ์งธ ์ผ์น˜ ํ•ญ๋ชฉ์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.

์—์ด์ „ํŠธ๋ณ„ ์•ก์„ธ์Šค ํ”„๋กœํ•„ (๋‹ค์ค‘ ์—์ด์ „ํŠธ)

๊ฐ ์—์ด์ „ํŠธ๋Š” ์ž์ฒด ์ƒŒ๋“œ๋ฐ•์Šค + ๋„๊ตฌ ์ •์ฑ…์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋‚˜์˜ Gateway์—์„œ ์•ก์„ธ์Šค ์ˆ˜์ค€์„ ํ˜ผํ•ฉํ•ฉ๋‹ˆ๋‹ค:

  • ์ „์ฒด ์•ก์„ธ์Šค(๊ฐœ์ธ ์—์ด์ „ํŠธ)
  • ์ฝ๊ธฐ ์ „์šฉ ๋„๊ตฌ + ์ž‘์—… ๊ณต๊ฐ„
  • ํŒŒ์ผ ์‹œ์Šคํ…œ ์•ก์„ธ์Šค ์—†์Œ(๋ฉ”์‹œ์ง•/์„ธ์…˜ ๋„๊ตฌ๋งŒ)

์šฐ์„  ์ˆœ์œ„ ๋ฐ ์ถ”๊ฐ€ ์˜ˆ์ œ๋Š” ๋‹ค์ค‘ ์—์ด์ „ํŠธ ์ƒŒ๋“œ๋ฐ•์Šค ๋ฐ ๋„๊ตฌ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ „์ฒด ์•ก์„ธ์Šค(์ƒŒ๋“œ๋ฐ•์Šค ์—†์Œ):

{
  agents: {
    list: [
      {
        id: "personal",
        workspace: "~/.openclaw/workspace-personal",
        sandbox: { mode: "off" }
      }
    ]
  }
}

์ฝ๊ธฐ ์ „์šฉ ๋„๊ตฌ + ์ฝ๊ธฐ ์ „์šฉ ์ž‘์—… ๊ณต๊ฐ„:

{
  agents: {
    list: [
      {
        id: "family",
        workspace: "~/.openclaw/workspace-family",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "ro"
        },
        tools: {
          allow: ["read", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
          deny: ["write", "edit", "apply_patch", "exec", "process", "browser"]
        }
      }
    ]
  }
}

ํŒŒ์ผ ์‹œ์Šคํ…œ ์•ก์„ธ์Šค ์—†์Œ(๋ฉ”์‹œ์ง•/์„ธ์…˜ ๋„๊ตฌ ํ™œ์„ฑํ™”):

{
  agents: {
    list: [
      {
        id: "public",
        workspace: "~/.openclaw/workspace-public",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "none"
        },
        tools: {
          allow: ["sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status", "whatsapp", "telegram", "slack", "discord", "gateway"],
          deny: ["read", "write", "edit", "apply_patch", "exec", "process", "browser", "canvas", "nodes", "cron", "gateway", "image"]
        }
      }
    ]
  }
}

์˜ˆ์ œ: ๋‘ ๊ฐœ์˜ WhatsApp ๊ณ„์ • โ†’ ๋‘ ๊ฐœ์˜ ์—์ด์ „ํŠธ:

{
  agents: {
    list: [
      { id: "home", default: true, workspace: "~/.openclaw/workspace-home" },
      { id: "work", workspace: "~/.openclaw/workspace-work" }
    ]
  },
  bindings: [
    { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
    { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } }
  ],
  channels: {
    whatsapp: {
      accounts: {
        personal: {},
        biz: {},
      }
    }
  }
}

tools.agentToAgent (์„ ํƒ ์‚ฌํ•ญ)

์—์ด์ „ํŠธ ๊ฐ„ ๋ฉ”์‹œ์ง•์€ ์˜ตํŠธ์ธ์ž…๋‹ˆ๋‹ค:

{
  tools: {
    agentToAgent: {
      enabled: false,
      allow: ["home", "work"]
    }
  }
}

messages.queue

์—์ด์ „ํŠธ ์‹คํ–‰์ด ์ด๋ฏธ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

{
  messages: {
    queue: {
      mode: "collect", // steer | followup | collect | steer-backlog (steer+backlog ok) | interrupt (queue=steer legacy)
      debounceMs: 1000,
      cap: 20,
      drop: "summarize", // old | new | summarize
      byChannel: {
        whatsapp: "collect",
        telegram: "collect",
        discord: "collect",
        imessage: "collect",
        webchat: "collect"
      }
    }
  }
}

messages.inbound

๋™์ผํ•œ ๋ฐœ์‹ ์ž์˜ ๋น ๋ฅธ ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋ฅผ ๋””๋ฐ”์šด์Šคํ•˜์—ฌ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์—ฐ์† ๋ฉ”์‹œ์ง€๊ฐ€ ๋‹จ์ผ ์—์ด์ „ํŠธ ํ„ด์ด ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋””๋ฐ”์šด์‹ฑ์€ ์ฑ„๋„ + ๋Œ€ํ™”๋ณ„๋กœ ๋ฒ”์œ„๊ฐ€ ์ง€์ •๋˜๋ฉฐ ๋‹ต์žฅ ์Šค๋ ˆ๋”ฉ/ID์— ๊ฐ€์žฅ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

{
  messages: {
    inbound: {
      debounceMs: 2000, // 0์€ ๋น„ํ™œ์„ฑํ™”
      byChannel: {
        whatsapp: 5000,
        slack: 1500,
        discord: 1500
      }
    }
  }
}

์ฐธ๊ณ :

  • ๋””๋ฐ”์šด์Šค๋Š” ํ…์ŠคํŠธ ์ „์šฉ ๋ฉ”์‹œ์ง€๋ฅผ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค; ๋ฏธ๋””์–ด/์ฒจ๋ถ€ ํŒŒ์ผ์€ ์ฆ‰์‹œ ํ”Œ๋Ÿฌ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • ์ œ์–ด ๋ช…๋ น(์˜ˆ: /queue, /new)์€ ๋””๋ฐ”์šด์‹ฑ์„ ์šฐํšŒํ•˜์—ฌ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

commands (์ฑ„ํŒ… ๋ช…๋ น ์ฒ˜๋ฆฌ)

์ปค๋„ฅํ„ฐ ์ „์ฒด์—์„œ ์ฑ„ํŒ… ๋ช…๋ น์ด ํ™œ์„ฑํ™”๋˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

{
  commands: {
    native: "auto",         // ์ง€์›๋˜๋Š” ๊ฒฝ์šฐ ๋„ค์ดํ‹ฐ๋ธŒ ๋ช…๋ น ๋“ฑ๋ก(auto)
    text: true,             // ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€์—์„œ ์Šฌ๋ž˜์‹œ ๋ช…๋ น ํŒŒ์‹ฑ
    bash: false,            // ! (๋ณ„์นญ: /bash) ํ—ˆ์šฉ(ํ˜ธ์ŠคํŠธ ์ „์šฉ; tools.elevated ํ—ˆ์šฉ ๋ชฉ๋ก ํ•„์š”)
    bashForegroundMs: 2000, // bash ํฌ๊ทธ๋ผ์šด๋“œ ์œˆ๋„์šฐ(0์€ ์ฆ‰์‹œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ)
    config: false,          // /config ํ—ˆ์šฉ(๋””์Šคํฌ์— ์ž‘์„ฑ)
    debug: false,           // /debug ํ—ˆ์šฉ(๋Ÿฐํƒ€์ž„ ์ „์šฉ ์žฌ์ •์˜)
    restart: false,         // /restart + gateway restart ๋„๊ตฌ ํ—ˆ์šฉ
    useAccessGroups: true   // ๋ช…๋ น์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ๊ทธ๋ฃน ํ—ˆ์šฉ ๋ชฉ๋ก/์ •์ฑ… ์ ์šฉ
  }
}

์ฐธ๊ณ :

  • ํ…์ŠคํŠธ ๋ช…๋ น์€ ๋…๋ฆฝ์ ์ธ ๋ฉ”์‹œ์ง€๋กœ ์ „์†ก๋˜์–ด์•ผ ํ•˜๋ฉฐ ์„ ํ–‰ /๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์ผ๋ฐ˜ ํ…์ŠคํŠธ ๋ณ„์นญ ์—†์Œ).
  • commands.text: false๋Š” ๋ช…๋ น์— ๋Œ€ํ•œ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ํŒŒ์‹ฑ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • commands.native: "auto"(๊ธฐ๋ณธ๊ฐ’)๋Š” Discord/Telegram์— ๋Œ€ํ•ด ๋„ค์ดํ‹ฐ๋ธŒ ๋ช…๋ น์„ ์ผœ๊ณ  Slack์€ ๋•๋‹ˆ๋‹ค; ์ง€์›๋˜์ง€ ์•Š๋Š” ์ฑ„๋„์€ ํ…์ŠคํŠธ ์ „์šฉ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  • commands.native: true|false๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ชจ๋‘ ๊ฐ•์ œํ•˜๊ฑฐ๋‚˜ ์ฑ„๋„๋ณ„๋กœ channels.discord.commands.native, channels.telegram.commands.native, channels.slack.commands.native๋กœ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค(bool ๋˜๋Š” "auto"). false๋Š” ์‹œ์ž‘ ์‹œ Discord/Telegram์—์„œ ์ด์ „์— ๋“ฑ๋ก๋œ ๋ช…๋ น์„ ์ง€์›๋‹ˆ๋‹ค; Slack ๋ช…๋ น์€ Slack ์•ฑ์—์„œ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • channels.telegram.customCommands๋Š” ์ถ”๊ฐ€ Telegram ๋ด‡ ๋ฉ”๋‰ด ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฆ„์€ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค; ๋„ค์ดํ‹ฐ๋ธŒ ๋ช…๋ น๊ณผ ์ถฉ๋Œํ•˜๋ฉด ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.
  • commands.bash: true๋Š” ! <cmd>๋ฅผ ํ™œ์„ฑํ™”ํ•˜์—ฌ ํ˜ธ์ŠคํŠธ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค(/bash <cmd>๋„ ๋ณ„์นญ์œผ๋กœ ์ž‘๋™). tools.elevated.enabled ๋ฐ tools.elevated.allowFrom.<channel>์—์„œ ๋ฐœ์‹ ์ž๋ฅผ ํ—ˆ์šฉ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • commands.bashForegroundMs๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ์ „ํ™˜ํ•˜๊ธฐ ์ „์— bash๊ฐ€ ๋Œ€๊ธฐํ•˜๋Š” ์‹œ๊ฐ„์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. bash ์ž‘์—…์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์ƒˆ ! <cmd> ์š”์ฒญ์€ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค(ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ).
  • commands.config: true๋Š” /config๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค(openclaw.json ์ฝ๊ธฐ/์“ฐ๊ธฐ).
  • channels.<provider>.configWrites๋Š” ํ•ด๋‹น ์ฑ„๋„์—์„œ ์‹œ์ž‘๋œ ๊ตฌ์„ฑ ๋ณ€๊ฒฝ์„ ๊ฒŒ์ดํŠธํ•ฉ๋‹ˆ๋‹ค(๊ธฐ๋ณธ๊ฐ’: true). ์ด๋Š” /config set|unset ๋ฐ ๊ณต๊ธ‰์ž๋ณ„ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜(Telegram ์Šˆํผ๊ทธ๋ฃน ID ๋ณ€๊ฒฝ, Slack ์ฑ„๋„ ID ๋ณ€๊ฒฝ)์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • commands.debug: true๋Š” /debug๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค(๋Ÿฐํƒ€์ž„ ์ „์šฉ ์žฌ์ •์˜).
  • commands.restart: true๋Š” /restart ๋ฐ ๊ฒŒ์ดํŠธ์›จ์ด ๋„๊ตฌ ์žฌ์‹œ์ž‘ ์ž‘์—…์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • commands.useAccessGroups: false๋Š” ๋ช…๋ น์ด ์•ก์„ธ์Šค ๊ทธ๋ฃน ํ—ˆ์šฉ ๋ชฉ๋ก/์ •์ฑ…์„ ์šฐํšŒํ•˜๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์Šฌ๋ž˜์‹œ ๋ช…๋ น ๋ฐ ์ง€์‹œ๋ฌธ์€ ์Šน์ธ๋œ ๋ฐœ์‹ ์ž์— ๋Œ€ํ•ด์„œ๋งŒ ์กด์ค‘๋ฉ๋‹ˆ๋‹ค. ์Šน์ธ์€ ์ฑ„๋„ ํ—ˆ์šฉ ๋ชฉ๋ก/ํŽ˜์–ด๋ง๊ณผ commands.useAccessGroups์—์„œ ํŒŒ์ƒ๋ฉ๋‹ˆ๋‹ค.

web (WhatsApp ์›น ์ฑ„๋„ ๋Ÿฐํƒ€์ž„)

WhatsApp์€ ๊ฒŒ์ดํŠธ์›จ์ด์˜ ์›น ์ฑ„๋„(Baileys Web)์„ ํ†ตํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋œ ์„ธ์…˜์ด ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋„๋ ค๋ฉด web.enabled: false๋กœ ์„ค์ •ํ•˜์„ธ์š”.

{
  web: {
    enabled: true,
    heartbeatSeconds: 60,
    reconnect: {
      initialMs: 2000,
      maxMs: 120000,
      factor: 1.4,
      jitter: 0.2,
      maxAttempts: 0
    }
  }
}

channels.telegram (๋ด‡ ์ „์†ก)

OpenClaw๋Š” channels.telegram ๊ตฌ์„ฑ ์„น์…˜์ด ์žˆ์„ ๋•Œ๋งŒ Telegram์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋ด‡ ํ† ํฐ์€ channels.telegram.botToken(๋˜๋Š” channels.telegram.tokenFile)์—์„œ ํ•ด์„๋˜๋ฉฐ, ๊ธฐ๋ณธ ๊ณ„์ •์˜ ๊ฒฝ์šฐ TELEGRAM_BOT_TOKEN์ด ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค. ์ž๋™ ์‹œ์ž‘์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด channels.telegram.enabled: false๋กœ ์„ค์ •ํ•˜์„ธ์š”. ๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.telegram.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค(์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ). ํ™˜๊ฒฝ ํ† ํฐ์€ ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. Telegram์—์„œ ์‹œ์ž‘๋œ ๊ตฌ์„ฑ ์“ฐ๊ธฐ(์Šˆํผ๊ทธ๋ฃน ID ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ /config set|unset ํฌํ•จ)๋ฅผ ์ฐจ๋‹จํ•˜๋ ค๋ฉด channels.telegram.configWrites: false๋กœ ์„ค์ •ํ•˜์„ธ์š”.

{
  channels: {
    telegram: {
      enabled: true,
      botToken: "your-bot-token",
      dmPolicy: "pairing",                 // pairing | allowlist | open | disabled
      allowFrom: ["tg:123456789"],         // ์„ ํƒ ์‚ฌํ•ญ; "open"์€ ["*"] ํ•„์š”
      groups: {
        "*": { requireMention: true },
        "-1001234567890": {
          allowFrom: ["@admin"],
          systemPrompt: "Keep answers brief.",
          topics: {
            "99": {
              requireMention: false,
              skills: ["search"],
              systemPrompt: "Stay on topic."
            }
          }
        }
      },
      customCommands: [
        { command: "backup", description: "Git backup" },
        { command: "generate", description: "Create an image" }
      ],
      historyLimit: 50,                     // ์ปจํ…์ŠคํŠธ๋กœ ๋งˆ์ง€๋ง‰ N๊ฐœ์˜ ๊ทธ๋ฃน ๋ฉ”์‹œ์ง€ ํฌํ•จ(0์€ ๋น„ํ™œ์„ฑํ™”)
      replyToMode: "first",                 // off | first | all
      linkPreview: true,                   // ์•„์›ƒ๋ฐ”์šด๋“œ ๋งํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ† ๊ธ€
      streamMode: "partial",               // off | partial | block (๋“œ๋ž˜ํ”„ํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ; ๋ธ”๋ก ์ŠคํŠธ๋ฆฌ๋ฐ๊ณผ ๋ณ„๊ฐœ)
      draftChunk: {                        // ์„ ํƒ ์‚ฌํ•ญ; streamMode=block์—๋งŒ ํ•ด๋‹น
        minChars: 200,
        maxChars: 800,
        breakPreference: "paragraph"       // paragraph | newline | sentence
      },
      actions: { reactions: true, sendMessage: true }, // ๋„๊ตฌ ์ž‘์—… ๊ฒŒ์ดํŠธ(false๋Š” ๋น„ํ™œ์„ฑํ™”)
      reactionNotifications: "own",   // off | own | all
      mediaMaxMb: 5,
      retry: {                             // ์•„์›ƒ๋ฐ”์šด๋“œ ์žฌ์‹œ๋„ ์ •์ฑ…
        attempts: 3,
        minDelayMs: 400,
        maxDelayMs: 30000,
        jitter: 0.1
      },
      network: {                           // ์ „์†ก ์žฌ์ •์˜
        autoSelectFamily: false
      },
      proxy: "socks5://localhost:9050",
      webhookUrl: "https://example.com/telegram-webhook",
      webhookSecret: "secret",
      webhookPath: "/telegram-webhook"
    }
  }
}

๋“œ๋ž˜ํ”„ํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ ์ฐธ๊ณ  ์‚ฌํ•ญ:

  • Telegram sendMessageDraft๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(๋“œ๋ž˜ํ”„ํŠธ ๋ฒ„๋ธ”, ์‹ค์ œ ๋ฉ”์‹œ์ง€ ์•„๋‹˜).
  • ๋น„๊ณต๊ฐœ ์ฑ„ํŒ… ํ† ํ”ฝ ํ•„์š”(DM์˜ message_thread_id; ๋ด‡์— ํ† ํ”ฝ ํ™œ์„ฑํ™”).
  • /reasoning stream์€ ๋“œ๋ž˜ํ”„ํŠธ๋กœ ์ถ”๋ก ์„ ์ŠคํŠธ๋ฆฌ๋ฐํ•œ ๋‹ค์Œ ์ตœ์ข… ๋‹ต๋ณ€์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ์ •์ฑ… ๊ธฐ๋ณธ๊ฐ’ ๋ฐ ๋™์ž‘์€ ์žฌ์‹œ๋„ ์ •์ฑ…์— ๋ฌธ์„œํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

channels.discord (๋ด‡ ์ „์†ก)

๋ด‡ ํ† ํฐ ๋ฐ ์„ ํƒ์  ๊ฒŒ์ดํŒ…์„ ์„ค์ •ํ•˜์—ฌ Discord ๋ด‡์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค: ๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.discord.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค(์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ). ํ™˜๊ฒฝ ํ† ํฐ์€ ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

{
  channels: {
    discord: {
      enabled: true,
      token: "your-bot-token",
      mediaMaxMb: 8,                          // ์ธ๋ฐ”์šด๋“œ ๋ฏธ๋””์–ด ํฌ๊ธฐ ์ œํ•œ
      allowBots: false,                       // ๋ด‡ ์ž‘์„ฑ ๋ฉ”์‹œ์ง€ ํ—ˆ์šฉ
      actions: {                              // ๋„๊ตฌ ์ž‘์—… ๊ฒŒ์ดํŠธ(false๋Š” ๋น„ํ™œ์„ฑํ™”)
        reactions: true,
        stickers: true,
        polls: true,
        permissions: true,
        messages: true,
        threads: true,
        pins: true,
        search: true,
        memberInfo: true,
        roleInfo: true,
        roles: false,
        channelInfo: true,
        voiceStatus: true,
        events: true,
        moderation: false
      },
      replyToMode: "off",                     // off | first | all
      dm: {
        enabled: true,                        // false์ผ ๋•Œ ๋ชจ๋“  DM ๋น„ํ™œ์„ฑํ™”
        policy: "pairing",                    // pairing | allowlist | open | disabled
        allowFrom: ["1234567890", "steipete"], // ์„ ํƒ์  DM ํ—ˆ์šฉ ๋ชฉ๋ก("open"์€ ["*"] ํ•„์š”)
        groupEnabled: false,                 // ๊ทธ๋ฃน DM ํ™œ์„ฑํ™”
        groupChannels: ["openclaw-dm"]          // ์„ ํƒ์  ๊ทธ๋ฃน DM ํ—ˆ์šฉ ๋ชฉ๋ก
      },
      guilds: {
        "123456789012345678": {               // ๊ธธ๋“œ ID(๊ถŒ์žฅ) ๋˜๋Š” ์Šฌ๋Ÿฌ๊ทธ
          slug: "friends-of-openclaw",
          requireMention: false,              // ๊ธธ๋“œ๋ณ„ ๊ธฐ๋ณธ๊ฐ’
          reactionNotifications: "own",       // off | own | all | allowlist
          users: ["987654321098765432"],      // ์„ ํƒ์  ๊ธธ๋“œ๋ณ„ ์‚ฌ์šฉ์ž ํ—ˆ์šฉ ๋ชฉ๋ก
          channels: {
            general: { allow: true },
            help: {
              allow: true,
              requireMention: true,
              users: ["987654321098765432"],
              skills: ["docs"],
              systemPrompt: "Short answers only."
            }
          }
        }
      },
      historyLimit: 20,                       // ์ปจํ…์ŠคํŠธ๋กœ ๋งˆ์ง€๋ง‰ N๊ฐœ์˜ ๊ธธ๋“œ ๋ฉ”์‹œ์ง€ ํฌํ•จ
      textChunkLimit: 2000,                   // ์„ ํƒ์  ์•„์›ƒ๋ฐ”์šด๋“œ ํ…์ŠคํŠธ ์ฒญํฌ ํฌ๊ธฐ(๋ฌธ์ž)
      chunkMode: "length",                    // ์„ ํƒ์  ์ฒญํ‚น ๋ชจ๋“œ(length | newline)
      maxLinesPerMessage: 17,                 // ๋ฉ”์‹œ์ง€๋‹น ์†Œํ”„ํŠธ ์ตœ๋Œ€ ์ค„ ์ˆ˜(Discord UI ํด๋ฆฌํ•‘)
      retry: {                                // ์•„์›ƒ๋ฐ”์šด๋“œ ์žฌ์‹œ๋„ ์ •์ฑ…
        attempts: 3,
        minDelayMs: 500,
        maxDelayMs: 30000,
        jitter: 0.1
      }
    }
  }
}

OpenClaw๋Š” channels.discord ๊ตฌ์„ฑ ์„น์…˜์ด ์žˆ์„ ๋•Œ๋งŒ Discord๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ์€ channels.discord.token์—์„œ ํ•ด์„๋˜๋ฉฐ, ๊ธฐ๋ณธ ๊ณ„์ •์˜ ๊ฒฝ์šฐ DISCORD_BOT_TOKEN์ด ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค(channels.discord.enabled๊ฐ€ false๊ฐ€ ์•„๋‹Œ ํ•œ). cron/CLI ๋ช…๋ น์˜ ์ „๋‹ฌ ๋Œ€์ƒ์„ ์ง€์ •ํ•  ๋•Œ user:<id>(DM) ๋˜๋Š” channel:<id>(๊ธธ๋“œ ์ฑ„๋„)๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”; ๋‹จ์ˆœ ์ˆซ์ž ID๋Š” ๋ชจํ˜ธํ•˜๋ฉฐ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ๊ธธ๋“œ ์Šฌ๋Ÿฌ๊ทธ๋Š” ์†Œ๋ฌธ์ž์ด๋ฉฐ ๊ณต๋ฐฑ์ด -๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค; ์ฑ„๋„ ํ‚ค๋Š” ์Šฌ๋Ÿฌ๊ทธ๋œ ์ฑ„๋„ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์„ ํ–‰ # ์—†์Œ). ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋ชจํ˜ธ์„ฑ์„ ํ”ผํ•˜๋ ค๋ฉด ๊ธธ๋“œ ID๋ฅผ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ด‡ ์ž‘์„ฑ ๋ฉ”์‹œ์ง€๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค. channels.discord.allowBots๋กœ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”(์ž์ฒด ๋ฉ”์‹œ์ง€๋Š” ์—ฌ์ „ํžˆ ํ•„ํ„ฐ๋ง๋˜์–ด ์ž์ฒด ์‘๋‹ต ๋ฃจํ”„๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค). ๋ฐ˜์‘ ์•Œ๋ฆผ ๋ชจ๋“œ:

  • off: ๋ฐ˜์‘ ์ด๋ฒคํŠธ ์—†์Œ.
  • own: ๋ด‡ ์ž์ฒด ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๋ฐ˜์‘(๊ธฐ๋ณธ๊ฐ’).
  • all: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์˜ ๋ชจ๋“  ๋ฐ˜์‘.
  • allowlist: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์˜ guilds.<id>.users์˜ ๋ฐ˜์‘(๋นˆ ๋ชฉ๋ก์€ ๋น„ํ™œ์„ฑํ™”). ์•„์›ƒ๋ฐ”์šด๋“œ ํ…์ŠคํŠธ๋Š” channels.discord.textChunkLimit(๊ธฐ๋ณธ๊ฐ’ 2000)๋กœ ์ฒญํฌ๋ฉ๋‹ˆ๋‹ค. ๊ธธ์ด ์ฒญํ‚น ์ „์— ๋นˆ ์ค„(๋‹จ๋ฝ ๊ฒฝ๊ณ„)์—์„œ ๋ถ„ํ• ํ•˜๋ ค๋ฉด channels.discord.chunkMode="newline"๋กœ ์„ค์ •ํ•˜์„ธ์š”. Discord ํด๋ผ์ด์–ธํŠธ๋Š” ๋งค์šฐ ๋†’์€ ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ฆฌํ•‘ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ channels.discord.maxLinesPerMessage(๊ธฐ๋ณธ๊ฐ’ 17)๋Š” 2000์ž ๋ฏธ๋งŒ์ด๋”๋ผ๋„ ๊ธด ์—ฌ๋Ÿฌ ์ค„ ์‘๋‹ต์„ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ์ •์ฑ… ๊ธฐ๋ณธ๊ฐ’ ๋ฐ ๋™์ž‘์€ ์žฌ์‹œ๋„ ์ •์ฑ…์— ๋ฌธ์„œํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

channels.googlechat (Chat API ์›นํ›…)

Google Chat์€ ์•ฑ ์ˆ˜์ค€ ์ธ์ฆ(์„œ๋น„์Šค ๊ณ„์ •)๊ณผ ํ•จ๊ป˜ HTTP ์›นํ›…์„ ํ†ตํ•ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.googlechat.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค(์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ). ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

{
  channels: {
    "googlechat": {
      enabled: true,
      serviceAccountFile: "/path/to/service-account.json",
      audienceType: "app-url",             // app-url | project-number
      audience: "https://gateway.example.com/googlechat",
      webhookPath: "/googlechat",
      botUser: "users/1234567890",        // ์„ ํƒ ์‚ฌํ•ญ; ๋ฉ˜์…˜ ๊ฐ์ง€ ๊ฐœ์„ 
      dm: {
        enabled: true,
        policy: "pairing",                // pairing | allowlist | open | disabled
        allowFrom: ["users/1234567890"]   // ์„ ํƒ ์‚ฌํ•ญ; "open"์€ ["*"] ํ•„์š”
      },
      groupPolicy: "allowlist",
      groups: {
        "spaces/AAAA": { allow: true, requireMention: true }
      },
      actions: { reactions: true },
      typingIndicator: "message",
      mediaMaxMb: 20
    }
  }
}

์ฐธ๊ณ :

  • ์„œ๋น„์Šค ๊ณ„์ • JSON์€ ์ธ๋ผ์ธ(serviceAccount) ๋˜๋Š” ํŒŒ์ผ ๊ธฐ๋ฐ˜(serviceAccountFile)์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ๊ณ„์ •์˜ ํ™˜๊ฒฝ ๋Œ€์ฒด: GOOGLE_CHAT_SERVICE_ACCOUNT ๋˜๋Š” GOOGLE_CHAT_SERVICE_ACCOUNT_FILE.
  • audienceType + audience๋Š” Chat ์•ฑ์˜ ์›นํ›… ์ธ์ฆ ๊ตฌ์„ฑ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ „๋‹ฌ ๋Œ€์ƒ์„ ์„ค์ •ํ•  ๋•Œ spaces/<spaceId> ๋˜๋Š” users/<userId|email>์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

channels.slack (socket mode)

Slack์€ Socket Mode๋กœ ์‹คํ–‰๋˜๋ฉฐ bot token๊ณผ app token์ด ๋ชจ๋‘ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

{
  channels: {
    slack: {
      enabled: true,
      botToken: "xoxb-...",
      appToken: "xapp-...",
      dm: {
        enabled: true,
        policy: "pairing", // pairing | allowlist | open | disabled
        allowFrom: ["U123", "U456", "*"], // optional; "open" requires ["*"]
        groupEnabled: false,
        groupChannels: ["G123"]
      },
      channels: {
        C123: { allow: true, requireMention: true, allowBots: false },
        "#general": {
          allow: true,
          requireMention: true,
          allowBots: false,
          users: ["U123"],
          skills: ["docs"],
          systemPrompt: "Short answers only."
        }
      },
      historyLimit: 50,          // include last N channel/group messages as context (0 disables)
      allowBots: false,
      reactionNotifications: "own", // off | own | all | allowlist
      reactionAllowlist: ["U123"],
      replyToMode: "off",           // off | first | all
      thread: {
        historyScope: "thread",     // thread | channel
        inheritParent: false
      },
      actions: {
        reactions: true,
        messages: true,
        pins: true,
        memberInfo: true,
        emojiList: true
      },
      slashCommand: {
        enabled: true,
        name: "openclaw",
        sessionPrefix: "slack:slash",
        ephemeral: true
      },
      textChunkLimit: 4000,
      chunkMode: "length",
      mediaMaxMb: 20
    }
  }
}

๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.slack.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค (์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ). ํ™˜๊ฒฝ๋ณ€์ˆ˜ token์€ ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

OpenClaw๋Š” provider๊ฐ€ ํ™œ์„ฑํ™”๋˜๊ณ  ๋‘ token์ด ์„ค์ •๋˜๋ฉด (config ๋˜๋Š” SLACK_BOT_TOKEN + SLACK_APP_TOKEN์„ ํ†ตํ•ด) Slack์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. cron/CLI ๋ช…๋ น์˜ ์ „์†ก ๋Œ€์ƒ์„ ์ง€์ •ํ•  ๋•Œ๋Š” user:<id> (DM) ๋˜๋Š” channel:<id>๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. Slack์—์„œ ์‹œ์ž‘๋œ config ์“ฐ๊ธฐ๋ฅผ ์ฐจ๋‹จํ•˜๋ ค๋ฉด (channel ID ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ /config set|unset ํฌํ•จ) channels.slack.configWrites: false๋กœ ์„ค์ •ํ•˜์„ธ์š”.

Bot์ด ์ž‘์„ฑํ•œ ๋ฉ”์‹œ์ง€๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค. channels.slack.allowBots ๋˜๋Š” channels.slack.channels.<id>.allowBots๋กœ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.

๋ฐ˜์‘ ์•Œ๋ฆผ ๋ชจ๋“œ:

  • off: ๋ฐ˜์‘ ์ด๋ฒคํŠธ ์—†์Œ.
  • own: bot ์ž์‹ ์˜ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๋ฐ˜์‘๋งŒ (๊ธฐ๋ณธ๊ฐ’).
  • all: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์˜ ๋ชจ๋“  ๋ฐ˜์‘.
  • allowlist: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์—์„œ channels.slack.reactionAllowlist์˜ ๋ฐ˜์‘๋งŒ (๋นˆ ๋ชฉ๋ก์€ ๋น„ํ™œ์„ฑํ™”).

Thread ์„ธ์…˜ ๊ฒฉ๋ฆฌ:

  • channels.slack.thread.historyScope๋Š” thread ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ thread๋ณ„๋กœ ๋ถ„๋ฆฌ๋˜๋Š”์ง€ (thread, ๊ธฐ๋ณธ๊ฐ’) ๋˜๋Š” ์ฑ„๋„ ์ „์ฒด์—์„œ ๊ณต์œ ๋˜๋Š”์ง€ (channel) ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • channels.slack.thread.inheritParent๋Š” ์ƒˆ thread ์„ธ์…˜์ด ๋ถ€๋ชจ ์ฑ„๋„ transcript๋ฅผ ์ƒ์†๋ฐ›๋Š”์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’: false).

Slack action ๊ทธ๋ฃน (slack tool action ์ œ์–ด):

Action ๊ทธ๋ฃน๊ธฐ๋ณธ๊ฐ’๋น„๊ณ 
reactionsenabled๋ฐ˜์‘ ์ถ”๊ฐ€ + ๋ฐ˜์‘ ๋ชฉ๋ก ์กฐํšŒ
messagesenabled์ฝ๊ธฐ/์ „์†ก/ํŽธ์ง‘/์‚ญ์ œ
pinsenabled๊ณ ์ •/๊ณ ์ • ํ•ด์ œ/๋ชฉ๋ก
memberInfoenabled๋ฉค๋ฒ„ ์ •๋ณด
emojiListenabled์ปค์Šคํ…€ ์ด๋ชจ์ง€ ๋ชฉ๋ก

channels.mattermost (bot token)

Mattermost๋Š” plugin์œผ๋กœ ์ œ๊ณต๋˜๋ฉฐ core ์„ค์น˜์— ๋ฒˆ๋“ค๋กœ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋จผ์ € ์„ค์น˜ํ•˜์„ธ์š”: openclaw plugins install @openclaw/mattermost (๋˜๋Š” git checkout์—์„œ ./extensions/mattermost).

Mattermost๋Š” bot token๊ณผ ์„œ๋ฒ„์˜ base URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

{
  channels: {
    mattermost: {
      enabled: true,
      botToken: "mm-token",
      baseUrl: "https://chat.example.com",
      dmPolicy: "pairing",
      chatmode: "oncall", // oncall | onmessage | onchar
      oncharPrefixes: [">", "!"],
      textChunkLimit: 4000,
      chunkMode: "length"
    }
  }
}

OpenClaw๋Š” ๊ณ„์ •์ด ๊ตฌ์„ฑ๋˜๊ณ  (bot token + base URL) ํ™œ์„ฑํ™”๋˜๋ฉด Mattermost๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. token + base URL์€ channels.mattermost.botToken + channels.mattermost.baseUrl ๋˜๋Š” ๊ธฐ๋ณธ ๊ณ„์ •์˜ MATTERMOST_BOT_TOKEN + MATTERMOST_URL์—์„œ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค (channels.mattermost.enabled๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ).

์ฑ„ํŒ… ๋ชจ๋“œ:

  • oncall (๊ธฐ๋ณธ๊ฐ’): @๋ฉ˜์…˜๋œ ๊ฒฝ์šฐ์—๋งŒ ์ฑ„๋„ ๋ฉ”์‹œ์ง€์— ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
  • onmessage: ๋ชจ๋“  ์ฑ„๋„ ๋ฉ”์‹œ์ง€์— ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
  • onchar: ๋ฉ”์‹œ์ง€๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ ์ ‘๋‘์‚ฌ๋กœ ์‹œ์ž‘ํ•  ๋•Œ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค (channels.mattermost.oncharPrefixes, ๊ธฐ๋ณธ๊ฐ’ [">", "!"]).

์ ‘๊ทผ ์ œ์–ด:

  • ๊ธฐ๋ณธ DM: channels.mattermost.dmPolicy="pairing" (์•Œ ์ˆ˜ ์—†๋Š” ๋ฐœ์‹ ์ž๋Š” pairing code๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค).
  • ๊ณต๊ฐœ DM: channels.mattermost.dmPolicy="open" + channels.mattermost.allowFrom=["*"].
  • ๊ทธ๋ฃน: ๊ธฐ๋ณธ์ ์œผ๋กœ channels.mattermost.groupPolicy="allowlist" (๋ฉ˜์…˜ ๊ฒŒ์ดํŠธ). ๋ฐœ์‹ ์ž๋ฅผ ์ œํ•œํ•˜๋ ค๋ฉด channels.mattermost.groupAllowFrom์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.mattermost.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค (์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ). ํ™˜๊ฒฝ๋ณ€์ˆ˜๋Š” ๊ธฐ๋ณธ ๊ณ„์ •์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ „์†ก ๋Œ€์ƒ์„ ์ง€์ •ํ•  ๋•Œ channel:<id> ๋˜๋Š” user:<id> (๋˜๋Š” @username)๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”; ๋‹จ์ˆœ id๋Š” channel id๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

channels.signal (signal-cli)

Signal ๋ฐ˜์‘์€ ์‹œ์Šคํ…œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๊ณต์œ  ๋ฐ˜์‘ ๋„๊ตฌ):

{
  channels: {
    signal: {
      reactionNotifications: "own", // off | own | all | allowlist
      reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
      historyLimit: 50 // include last N group messages as context (0 disables)
    }
  }
}

๋ฐ˜์‘ ์•Œ๋ฆผ ๋ชจ๋“œ:

  • off: ๋ฐ˜์‘ ์ด๋ฒคํŠธ ์—†์Œ.
  • own: bot ์ž์‹ ์˜ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๋ฐ˜์‘๋งŒ (๊ธฐ๋ณธ๊ฐ’).
  • all: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์˜ ๋ชจ๋“  ๋ฐ˜์‘.
  • allowlist: ๋ชจ๋“  ๋ฉ”์‹œ์ง€์—์„œ channels.signal.reactionAllowlist์˜ ๋ฐ˜์‘๋งŒ (๋นˆ ๋ชฉ๋ก์€ ๋น„ํ™œ์„ฑํ™”).

channels.imessage (imsg CLI)

OpenClaw๋Š” imsg rpc (stdio๋ฅผ ํ†ตํ•œ JSON-RPC)๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ๋ชฌ์ด๋‚˜ ํฌํŠธ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "imsg",
      dbPath: "~/Library/Messages/chat.db",
      remoteHost: "user@gateway-host", // SCP for remote attachments when using SSH wrapper
      dmPolicy: "pairing", // pairing | allowlist | open | disabled
      allowFrom: ["+15555550123", "[email protected]", "chat_id:123"],
      historyLimit: 50,    // include last N group messages as context (0 disables)
      includeAttachments: false,
      mediaMaxMb: 16,
      service: "auto",
      region: "US"
    }
  }
}

๋‹ค์ค‘ ๊ณ„์ • ์ง€์›์€ channels.imessage.accounts ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค (์œ„์˜ ๋‹ค์ค‘ ๊ณ„์ • ์„น์…˜ ์ฐธ์กฐ).

์ฐธ๊ณ  ์‚ฌํ•ญ:

  • Messages DB์— ๋Œ€ํ•œ Full Disk Access ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒซ ์ „์†ก ์‹œ Messages ์ž๋™ํ™” ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
  • chat_id:<id> ๋Œ€์ƒ์„ ์„ ํ˜ธํ•˜์„ธ์š”. ์ฑ„ํŒ… ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด imsg chats --limit 20์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • channels.imessage.cliPath๋Š” wrapper ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ: imsg rpc๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋‹ค๋ฅธ Mac์œผ๋กœ ssh); ๋น„๋ฐ€๋ฒˆํ˜ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ”ผํ•˜๋ ค๋ฉด SSH ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ์›๊ฒฉ SSH wrapper์˜ ๊ฒฝ์šฐ, includeAttachments๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ์„ ๋•Œ SCP๋ฅผ ํ†ตํ•ด ์ฒจ๋ถ€ ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๋„๋ก channels.imessage.remoteHost๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.

Wrapper ์˜ˆ์‹œ:

#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"

agents.defaults.workspace

agent๊ฐ€ ํŒŒ์ผ ์ž‘์—…์— ์‚ฌ์šฉํ•˜๋Š” ๋‹จ์ผ ์ „์—ญ workspace ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’: ~/.openclaw/workspace.

{
  agents: { defaults: { workspace: "~/.openclaw/workspace" } }
}

agents.defaults.sandbox๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, non-main ์„ธ์…˜์€ agents.defaults.sandbox.workspaceRoot ์•„๋ž˜์˜ ์ž์ฒด scope๋ณ„ workspace๋กœ ์ด๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

agents.defaults.repoRoot

์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์˜ Runtime ์ค„์— ํ‘œ์‹œํ•  ์„ ํƒ์  repository root์ž…๋‹ˆ๋‹ค. ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด OpenClaw๋Š” workspace (๋ฐ ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ)์—์„œ ์œ„์ชฝ์œผ๋กœ .git ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. ๊ฒฝ๋กœ๋Š” ์‚ฌ์šฉ๋˜๋ ค๋ฉด ์กด์žฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

{
  agents: { defaults: { repoRoot: "~/Projects/openclaw" } }
}

agents.defaults.skipBootstrap

workspace bootstrap ํŒŒ์ผ (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, BOOTSTRAP.md)์˜ ์ž๋™ ์ƒ์„ฑ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

workspace ํŒŒ์ผ์ด repository์—์„œ ์ œ๊ณต๋˜๋Š” pre-seeded ๋ฐฐํฌ์— ์ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

{
  agents: { defaults: { skipBootstrap: true } }
}

agents.defaults.bootstrapMaxChars

์ž˜๋ฆผ ์ „์— ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…๋˜๋Š” ๊ฐ workspace bootstrap ํŒŒ์ผ์˜ ์ตœ๋Œ€ ๋ฌธ์ž ์ˆ˜์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’: 20000.

ํŒŒ์ผ์ด ์ด ์ œํ•œ์„ ์ดˆ๊ณผํ•˜๋ฉด OpenClaw๋Š” ๊ฒฝ๊ณ ๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋งˆ์ปค๊ฐ€ ์žˆ๋Š” ์ž˜๋ฆฐ head/tail์„ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

{
  agents: { defaults: { bootstrapMaxChars: 20000 } }
}

agents.defaults.userTimezone

์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์‚ฌ์šฉ์ž์˜ timezone์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค (๋ฉ”์‹œ์ง€ envelope์˜ ํƒ€์ž„์Šคํƒฌํ”„์šฉ์ด ์•„๋‹˜). ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด OpenClaw๋Š” ๋Ÿฐํƒ€์ž„์— ํ˜ธ์ŠคํŠธ timezone์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

{
  agents: { defaults: { userTimezone: "America/Chicago" } }
}

agents.defaults.timeFormat

์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์˜ Current Date & Time ์„น์…˜์— ํ‘œ์‹œ๋˜๋Š” ์‹œ๊ฐ„ ํ˜•์‹์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’: auto (OS ๊ธฐ๋ณธ ์„ค์ •).

{
  agents: { defaults: { timeFormat: "auto" } } // auto | 12 | 24
}

messages

์ธ๋ฐ”์šด๋“œ/์•„์›ƒ๋ฐ”์šด๋“œ ์ ‘๋‘์‚ฌ ๋ฐ ์„ ํƒ์  ack ๋ฐ˜์‘์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๊ธฐ์—ด, ์„ธ์…˜ ๋ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์ปจํ…์ŠคํŠธ๋Š” Messages๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

{
  messages: {
    responsePrefix: "๐Ÿฆž", // or "auto"
    ackReaction: "๐Ÿ‘€",
    ackReactionScope: "group-mentions",
    removeAckAfterReply: false
  }
}

responsePrefix๋Š” ์ด๋ฏธ ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ฑ„๋„ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋ชจ๋“  ์•„์›ƒ๋ฐ”์šด๋“œ ์‘๋‹ต (tool ์š”์•ฝ, ๋ธ”๋ก ์ŠคํŠธ๋ฆฌ๋ฐ, ์ตœ์ข… ์‘๋‹ต)์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

messages.responsePrefix๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ‘๋‘์‚ฌ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. WhatsApp self-chat ์‘๋‹ต์€ ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค: ์„ค์ •๋œ ๊ฒฝ์šฐ [{identity.name}]์ด ๊ธฐ๋ณธ๊ฐ’์ด๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด [openclaw]์ด๋ฏ€๋กœ ๋™์ผ ์ „ํ™” ๋Œ€ํ™”๊ฐ€ ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํŒ…๋œ agent์˜ [{identity.name}]๋ฅผ ํŒŒ์ƒํ•˜๋ ค๋ฉด "auto"๋กœ ์„ค์ •ํ•˜์„ธ์š” (์„ค์ •๋œ ๊ฒฝ์šฐ).

Template ๋ณ€์ˆ˜

responsePrefix ๋ฌธ์ž์—ด์€ ๋™์ ์œผ๋กœ ํ•ด๊ฒฐ๋˜๋Š” template ๋ณ€์ˆ˜๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

๋ณ€์ˆ˜์„ค๋ช…์˜ˆ์‹œ
{model}์งง์€ ๋ชจ๋ธ ์ด๋ฆ„claude-opus-4-5, gpt-4o
{modelFull}์ „์ฒด ๋ชจ๋ธ ์‹๋ณ„์žanthropic/claude-opus-4-5
{provider}Provider ์ด๋ฆ„anthropic, openai
{thinkingLevel}ํ˜„์žฌ thinking ๋ ˆ๋ฒจhigh, low, off
{identity.name}Agent identity ์ด๋ฆ„("auto" ๋ชจ๋“œ์™€ ๋™์ผ)

๋ณ€์ˆ˜๋Š” ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค ({MODEL} = {model}). {think}๋Š” {thinkingLevel}์˜ ๋ณ„์นญ์ž…๋‹ˆ๋‹ค. ํ•ด๊ฒฐ๋˜์ง€ ์•Š์€ ๋ณ€์ˆ˜๋Š” ๋ฆฌํ„ฐ๋Ÿด ํ…์ŠคํŠธ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

{
  messages: {
    responsePrefix: "[{model} | think:{thinkingLevel}]"
  }
}

์˜ˆ์‹œ ์ถœ๋ ฅ: [claude-opus-4-5 | think:high] Here's my response...

WhatsApp ์ธ๋ฐ”์šด๋“œ ์ ‘๋‘์‚ฌ๋Š” channels.whatsapp.messagePrefix๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค (deprecated: messages.messagePrefix). ๊ธฐ๋ณธ๊ฐ’์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ: channels.whatsapp.allowFrom์ด ๋น„์–ด ์žˆ์œผ๋ฉด "[openclaw]", ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด "" (์ ‘๋‘์‚ฌ ์—†์Œ). "[openclaw]"๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ผ์šฐํŒ…๋œ agent์— identity.name์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด OpenClaw๋Š” ๋Œ€์‹  [{identity.name}]๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ackReaction์€ ๋ฐ˜์‘์„ ์ง€์›ํ•˜๋Š” ์ฑ„๋„ (Slack/Discord/Telegram/Google Chat)์—์„œ ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด best-effort ์ด๋ชจ์ง€ ๋ฐ˜์‘์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์„ค์ •๋œ ๊ฒฝ์šฐ ํ™œ์„ฑ agent์˜ identity.emoji๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด "๐Ÿ‘€"์ž…๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ""๋กœ ์„ค์ •ํ•˜์„ธ์š”.

ackReactionScope๋Š” ๋ฐ˜์‘์ด ๋ฐœ์ƒํ•˜๋Š” ์‹œ๊ธฐ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค:

  • group-mentions (๊ธฐ๋ณธ๊ฐ’): ๊ทธ๋ฃน/room์ด ๋ฉ˜์…˜์„ ์š”๊ตฌํ•˜๊ณ  ๊ทธ๋ฆฌ๊ณ  bot์ด ๋ฉ˜์…˜๋œ ๊ฒฝ์šฐ์—๋งŒ
  • group-all: ๋ชจ๋“  ๊ทธ๋ฃน/room ๋ฉ”์‹œ์ง€
  • direct: ์ง์ ‘ ๋ฉ”์‹œ์ง€๋งŒ
  • all: ๋ชจ๋“  ๋ฉ”์‹œ์ง€

removeAckAfterReply๋Š” ์‘๋‹ต์ด ์ „์†ก๋œ ํ›„ bot์˜ ack ๋ฐ˜์‘์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค (Slack/Discord/Telegram/Google Chat๋งŒ ํ•ด๋‹น). ๊ธฐ๋ณธ๊ฐ’: false.

messages.tts

์•„์›ƒ๋ฐ”์šด๋“œ ์‘๋‹ต์— ๋Œ€ํ•œ text-to-speech๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ํ™œ์„ฑํ™”๋˜๋ฉด OpenClaw๋Š” ElevenLabs ๋˜๋Š” OpenAI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋””์˜ค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‘๋‹ต์— ์ฒจ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. Telegram์€ Opus voice note๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋‹ค๋ฅธ ์ฑ„๋„์€ MP3 ์˜ค๋””์˜ค๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

{
  messages: {
    tts: {
      auto: "always", // off | always | inbound | tagged
      mode: "final", // final | all (include tool/block replies)
      provider: "elevenlabs",
      summaryModel: "openai/gpt-4.1-mini",
      modelOverrides: {
        enabled: true
      },
      maxTextLength: 4000,
      timeoutMs: 30000,
      prefsPath: "~/.openclaw/settings/tts.json",
      elevenlabs: {
        apiKey: "elevenlabs_api_key",
        baseUrl: "https://api.elevenlabs.io",
        voiceId: "voice_id",
        modelId: "eleven_multilingual_v2",
        seed: 42,
        applyTextNormalization: "auto",
        languageCode: "en",
        voiceSettings: {
          stability: 0.5,
          similarityBoost: 0.75,
          style: 0.0,
          useSpeakerBoost: true,
          speed: 1.0
        }
      },
      openai: {
        apiKey: "openai_api_key",
        model: "gpt-4o-mini-tts",
        voice: "alloy"
      }
    }
  }
}

์ฐธ๊ณ  ์‚ฌํ•ญ:

  • messages.tts.auto๋Š” autoโ€‘TTS๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค (off, always, inbound, tagged).
  • /tts off|always|inbound|tagged๋Š” ์„ธ์…˜๋ณ„ auto ๋ชจ๋“œ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค (config๋ฅผ ์žฌ์ •์˜).
  • messages.tts.enabled๋Š” ๋ ˆ๊ฑฐ์‹œ์ด๋ฉฐ; doctor๊ฐ€ ์ด๋ฅผ messages.tts.auto๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.
  • prefsPath๋Š” ๋กœ์ปฌ ์žฌ์ •์˜ (provider/limit/summarize)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • maxTextLength๋Š” TTS ์ž…๋ ฅ์˜ hard cap์ž…๋‹ˆ๋‹ค; ์š”์•ฝ์€ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ์ž˜๋ฆฝ๋‹ˆ๋‹ค.
  • summaryModel์€ auto-summary๋ฅผ ์œ„ํ•ด agents.defaults.model.primary๋ฅผ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    • provider/model ๋˜๋Š” agents.defaults.models์˜ ๋ณ„์นญ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • modelOverrides๋Š” [[tts:...]] ํƒœ๊ทธ์™€ ๊ฐ™์€ ๋ชจ๋ธ ๊ธฐ๋ฐ˜ ์žฌ์ •์˜๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”).
  • /tts limit๊ณผ /tts summary๋Š” ์‚ฌ์šฉ์ž๋ณ„ ์š”์•ฝ ์„ค์ •์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • apiKey ๊ฐ’์€ ELEVENLABS_API_KEY/XI_API_KEY ๋ฐ OPENAI_API_KEY๋กœ ํด๋ฐฑ๋ฉ๋‹ˆ๋‹ค.
  • elevenlabs.baseUrl์€ ElevenLabs API base URL์„ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • elevenlabs.voiceSettings๋Š” stability/similarityBoost/style (0..1), useSpeakerBoost, speed (0.5..2.0)๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

talk

Talk ๋ชจ๋“œ์˜ ๊ธฐ๋ณธ๊ฐ’ (macOS/iOS/Android). Voice ID๋Š” ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฉด ELEVENLABS_VOICE_ID ๋˜๋Š” SAG_VOICE_ID๋กœ ํด๋ฐฑ๋ฉ๋‹ˆ๋‹ค. apiKey๋Š” ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฉด ELEVENLABS_API_KEY (๋˜๋Š” gateway์˜ shell profile)๋กœ ํด๋ฐฑ๋ฉ๋‹ˆ๋‹ค. voiceAliases๋Š” Talk ์ง€์‹œ๋ฌธ์ด ์นœ๊ทผํ•œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: "voice":"Clawd").

{
  talk: {
    voiceId: "elevenlabs_voice_id",
    voiceAliases: {
      Clawd: "EXAVITQu4vr4xnSDxMaL",
      Roger: "CwhRBWXzGAHq8TQ4Fs17"
    },
    modelId: "eleven_v3",
    outputFormat: "mp3_44100_128",
    apiKey: "elevenlabs_api_key",
    interruptOnSpeech: true
  }
}

agents.defaults

๋‚ด์žฅ agent ๋Ÿฐํƒ€์ž„ (model/thinking/verbose/timeout)์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. agents.defaults.models๋Š” ๊ตฌ์„ฑ๋œ ๋ชจ๋ธ ์นดํƒˆ๋กœ๊ทธ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  /model์˜ allowlist ์—ญํ• ์„ ํ•จ). agents.defaults.model.primary๋Š” ๊ธฐ๋ณธ ๋ชจ๋ธ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค; agents.defaults.model.fallbacks๋Š” ์ „์—ญ failover์ž…๋‹ˆ๋‹ค. agents.defaults.imageModel์€ ์„ ํƒ ์‚ฌํ•ญ์ด๋ฉฐ primary ๋ชจ๋ธ์— image ์ž…๋ ฅ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ agents.defaults.models ํ•ญ๋ชฉ์€ ๋‹ค์Œ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • alias (์„ ํƒ์  ๋ชจ๋ธ ๋‹จ์ถ•ํ‚ค, ์˜ˆ: /opus).
  • params (๋ชจ๋ธ ์š”์ฒญ์— ์ „๋‹ฌ๋˜๋Š” ์„ ํƒ์  provider๋ณ„ API ๋งค๊ฐœ๋ณ€์ˆ˜).

params๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ์‹คํ–‰ (๋‚ด์žฅ agent + ์••์ถ•)์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ง€์›๋˜๋Š” ํ‚ค: temperature, maxTokens. ์ด๋“ค์€ ํ˜ธ์ถœ ์‹œ์  ์˜ต์…˜๊ณผ ๋ณ‘ํ•ฉ๋˜๋ฉฐ; ํ˜ธ์ถœ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ฐ’์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. temperature๋Š” ๊ณ ๊ธ‰ ๋…ธ๋ธŒ์ž…๋‹ˆ๋‹คโ€”๋ชจ๋ธ์˜ ๊ธฐ๋ณธ๊ฐ’์„ ์•Œ๊ณ  ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด ์„ค์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.

์˜ˆ์‹œ:

{
  agents: {
    defaults: {
      models: {
        "anthropic/claude-sonnet-4-5-20250929": {
          params: { temperature: 0.6 }
        },
        "openai/gpt-5.2": {
          params: { maxTokens: 8192 }
        }
      }
    }
  }
}

Z.AI GLM-4.x ๋ชจ๋ธ์€ ๋‹ค์Œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด ์ž๋™์œผ๋กœ thinking ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค:

  • --thinking off๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜
  • ์ง์ ‘ agents.defaults.models["zai/<model>"].params.thinking์„ ์ •์˜ํ•˜๋Š” ๊ฒฝ์šฐ.

OpenClaw๋Š” ๋˜ํ•œ ๋ช‡ ๊ฐ€์ง€ ๋‚ด์žฅ ๋ณ„์นญ ๋‹จ์ถ•์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ ๋ชจ๋ธ์ด ์ด๋ฏธ agents.defaults.models์— ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค:

  • opus -> anthropic/claude-opus-4-5
  • sonnet -> anthropic/claude-sonnet-4-5
  • gpt -> openai/gpt-5.2
  • gpt-mini -> openai/gpt-5-mini
  • gemini -> google/gemini-3-pro-preview
  • gemini-flash -> google/gemini-3-flash-preview

๋™์ผํ•œ ๋ณ„์นญ ์ด๋ฆ„์„ (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์ด) ์ง์ ‘ ๊ตฌ์„ฑํ•˜๋ฉด ๊ท€ํ•˜์˜ ๊ฐ’์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’์€ ์žฌ์ •์˜ํ•˜์ง€ ์•Š์Œ).

์˜ˆ์‹œ: Opus 4.5 primary์™€ MiniMax M2.1 fallback (ํ˜ธ์ŠคํŒ…๋œ MiniMax):

{
  agents: {
    defaults: {
      models: {
        "anthropic/claude-opus-4-5": { alias: "opus" },
        "minimax/MiniMax-M2.1": { alias: "minimax" }
      },
      model: {
        primary: "anthropic/claude-opus-4-5",
        fallbacks: ["minimax/MiniMax-M2.1"]
      }
    }
  }
}

MiniMax ์ธ์ฆ: MINIMAX_API_KEY (env)๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ models.providers.minimax๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”.

agents.defaults.cliBackends (CLI fallback)

ํ…์ŠคํŠธ ์ „์šฉ fallback ์‹คํ–‰ (tool ํ˜ธ์ถœ ์—†์Œ)์„ ์œ„ํ•œ ์„ ํƒ์  CLI backend์ž…๋‹ˆ๋‹ค. API provider๊ฐ€ ์‹คํŒจํ•  ๋•Œ ๋ฐฑ์—… ๊ฒฝ๋กœ๋กœ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ํ—ˆ์šฉํ•˜๋Š” imageArg๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด image pass-through๊ฐ€ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ  ์‚ฌํ•ญ:

  • CLI backend๋Š” ํ…์ŠคํŠธ ์šฐ์„ ์ด๋ฉฐ; tool์€ ํ•ญ์ƒ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.
  • sessionArg๊ฐ€ ์„ค์ •๋˜๋ฉด ์„ธ์…˜์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค; ์„ธ์…˜ id๋Š” backend๋ณ„๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  • claude-cli์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. PATH๊ฐ€ ์ตœ์†Œํ•œ (launchd/systemd)์ธ ๊ฒฝ์šฐ command ๊ฒฝ๋กœ๋ฅผ ์žฌ์ •์˜ํ•˜์„ธ์š”.

์˜ˆ์‹œ:

{
  agents: {
    defaults: {
      cliBackends: {
        "claude-cli": {
          command: "/opt/homebrew/bin/claude"
        },
        "my-cli": {
          command: "my-cli",
          args: ["--json"],
          output: "json",
          modelArg: "--model",
          sessionArg: "--session",
          sessionMode: "existing",
          systemPromptArg: "--system",
          systemPromptWhen: "first",
          imageArg: "--image",
          imageMode: "repeat"
        }
      }
    }
  }
}
{
  agents: {
    defaults: {
      models: {
        "anthropic/claude-opus-4-5": { alias: "Opus" },
        "anthropic/claude-sonnet-4-1": { alias: "Sonnet" },
        "openrouter/deepseek/deepseek-r1:free": {},
        "zai/glm-4.7": {
          alias: "GLM",
          params: {
            thinking: {
              type: "enabled",
              clear_thinking: false
            }
          }
        }
      },
      model: {
        primary: "anthropic/claude-opus-4-5",
        fallbacks: [
          "openrouter/deepseek/deepseek-r1:free",
          "openrouter/meta-llama/llama-3.3-70b-instruct:free"
        ]
      },
      imageModel: {
        primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
        fallbacks: [
          "openrouter/google/gemini-2.0-flash-vision:free"
        ]
      },
      thinkingDefault: "low",
      verboseDefault: "off",
      elevatedDefault: "on",
      timeoutSeconds: 600,
      mediaMaxMb: 5,
      heartbeat: {
        every: "30m",
        target: "last"
      },
      maxConcurrent: 3,
      subagents: {
        model: "minimax/MiniMax-M2.1",
        maxConcurrent: 1,
        archiveAfterMinutes: 60
      },
      exec: {
        backgroundMs: 10000,
        timeoutSec: 1800,
        cleanupMs: 1800000
      },
      contextTokens: 200000
    }
  }
}

agents.defaults.contextPruning (tool-result ์ •๋ฆฌ)

agents.defaults.contextPruning์€ ์š”์ฒญ์ด LLM์— ์ „์†ก๋˜๊ธฐ ์ง์ „์— ๋ฉ”๋ชจ๋ฆฌ ๋‚ด ์ปจํ…์ŠคํŠธ์—์„œ ์˜ค๋ž˜๋œ tool ๊ฒฐ๊ณผ๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋””์Šคํฌ์˜ ์„ธ์…˜ ํžˆ์Šคํ† ๋ฆฌ๋Š” ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค (*.jsonl์€ ์™„์ „ํžˆ ์œ ์ง€๋จ).

์ด๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ํฐ tool ์ถœ๋ ฅ์„ ์ถ•์ ํ•˜๋Š” ์ˆ˜๋‹ค์Šค๋Ÿฌ์šด agent์˜ token ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ด๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ƒ์œ„ ๋ ˆ๋ฒจ:

  • user/assistant ๋ฉ”์‹œ์ง€๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋งˆ์ง€๋ง‰ keepLastAssistants assistant ๋ฉ”์‹œ์ง€๋ฅผ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค (๊ทธ ์‹œ์  ์ดํ›„์˜ tool ๊ฒฐ๊ณผ๋Š” ์ •๋ฆฌ๋˜์ง€ ์•Š์Œ).
  • bootstrap ์ ‘๋‘์‚ฌ๋ฅผ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค (์ฒซ ๋ฒˆ์งธ user ๋ฉ”์‹œ์ง€ ์ด์ „์˜ ๊ฒƒ์€ ์ •๋ฆฌ๋˜์ง€ ์•Š์Œ).
  • ๋ชจ๋“œ:
    • adaptive: ์˜ˆ์ƒ ์ปจํ…์ŠคํŠธ ๋น„์œจ์ด softTrimRatio๋ฅผ ๋„˜์œผ๋ฉด ์ดˆ๊ณผ ํฌ๊ธฐ tool ๊ฒฐ๊ณผ๋ฅผ soft-trimํ•ฉ๋‹ˆ๋‹ค (head/tail ์œ ์ง€). ๊ทธ๋Ÿฐ ๋‹ค์Œ ์˜ˆ์ƒ ์ปจํ…์ŠคํŠธ ๋น„์œจ์ด hardClearRatio๋ฅผ ๋„˜๊ณ  ๊ทธ๋ฆฌ๊ณ  ์ •๋ฆฌ ๊ฐ€๋Šฅํ•œ tool-result ๋ณผ๋ฅจ์ด ์ถฉ๋ถ„ํ•  ๋•Œ (minPrunableToolChars) ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ์ ๊ฒฉ tool ๊ฒฐ๊ณผ๋ฅผ hard-clearํ•ฉ๋‹ˆ๋‹ค.
    • aggressive: ์ปท์˜คํ”„ ์ด์ „์˜ ์ ๊ฒฉ tool ๊ฒฐ๊ณผ๋ฅผ ํ•ญ์ƒ hardClear.placeholder๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค (๋น„์œจ ๊ฒ€์‚ฌ ์—†์Œ).

Soft vs hard ์ •๋ฆฌ (LLM์— ์ „์†ก๋˜๋Š” ์ปจํ…์ŠคํŠธ์—์„œ ๋ณ€๊ฒฝ๋˜๋Š” ๋‚ด์šฉ):

  • Soft-trim: ์ดˆ๊ณผ ํฌ๊ธฐ tool ๊ฒฐ๊ณผ๋งŒ ํ•ด๋‹น. ์‹œ์ž‘ + ๋์„ ์œ ์ง€ํ•˜๊ณ  ์ค‘๊ฐ„์— ...์„ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.
    • ์ด์ „: toolResult("โ€ฆ๋งค์šฐ ๊ธด ์ถœ๋ ฅโ€ฆ")
    • ์ดํ›„: toolResult("HEADโ€ฆ\n...\nโ€ฆTAIL\n\n[Tool result trimmed: โ€ฆ]")
  • Hard-clear: ์ „์ฒด tool ๊ฒฐ๊ณผ๋ฅผ placeholder๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
    • ์ด์ „: toolResult("โ€ฆ๋งค์šฐ ๊ธด ์ถœ๋ ฅโ€ฆ")
    • ์ดํ›„: toolResult("[Old tool result content cleared]")

์ฐธ๊ณ  ์‚ฌํ•ญ / ํ˜„์žฌ ์ œํ•œ ์‚ฌํ•ญ:

  • image ๋ธ”๋ก์„ ํฌํ•จํ•˜๋Š” tool ๊ฒฐ๊ณผ๋Š” ํ˜„์žฌ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค (trim/clear๋˜์ง€ ์•Š์Œ).
  • ์˜ˆ์ƒ "์ปจํ…์ŠคํŠธ ๋น„์œจ"์€ ๋ฌธ์ž (๊ทผ์‚ฌ์น˜)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์ •ํ™•ํ•œ token์ด ์•„๋‹™๋‹ˆ๋‹ค.
  • ์„ธ์…˜์ด ์•„์ง ์ตœ์†Œ keepLastAssistants assistant ๋ฉ”์‹œ์ง€๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉด ์ •๋ฆฌ๊ฐ€ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
  • aggressive ๋ชจ๋“œ์—์„œ๋Š” hardClear.enabled๊ฐ€ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค (์ ๊ฒฉ tool ๊ฒฐ๊ณผ๋Š” ํ•ญ์ƒ hardClear.placeholder๋กœ ๋Œ€์ฒด๋จ).

๊ธฐ๋ณธ๊ฐ’ (adaptive):

{
  agents: { defaults: { contextPruning: { mode: "adaptive" } } }
}

๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด:

{
  agents: { defaults: { contextPruning: { mode: "off" } } }
}

๊ธฐ๋ณธ๊ฐ’ (mode๊ฐ€ "adaptive" ๋˜๋Š” "aggressive"์ธ ๊ฒฝ์šฐ):

  • keepLastAssistants: 3
  • softTrimRatio: 0.3 (adaptive๋งŒ ํ•ด๋‹น)
  • hardClearRatio: 0.5 (adaptive๋งŒ ํ•ด๋‹น)
  • minPrunableToolChars: 50000 (adaptive๋งŒ ํ•ด๋‹น)
  • softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 } (adaptive๋งŒ ํ•ด๋‹น)
  • hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" }

์˜ˆ์‹œ (aggressive, ์ตœ์†Œ):

{
  agents: { defaults: { contextPruning: { mode: "aggressive" } } }
}

์˜ˆ์‹œ (adaptive ์กฐ์ •):

{
  agents: {
    defaults: {
      contextPruning: {
        mode: "adaptive",
        keepLastAssistants: 3,
        softTrimRatio: 0.3,
        hardClearRatio: 0.5,
        minPrunableToolChars: 50000,
        softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 },
        hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" },
        // ์„ ํƒ ์‚ฌํ•ญ: ํŠน์ • tool๋กœ ์ •๋ฆฌ ์ œํ•œ (deny๊ฐ€ ์šฐ์„ ; "*" ์™€์ผ๋“œ์นด๋“œ ์ง€์›)
        tools: { deny: ["browser", "canvas"] },
      }
    }
  }
}

๋™์ž‘ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ /concepts/session-pruning์„ ์ฐธ์กฐํ•˜์„ธ์š”.

agents.defaults.compaction (reserve headroom + memory flush)

agents.defaults.compaction.mode๋Š” compaction ์š”์•ฝ ์ „๋žต์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ default์ด๋ฉฐ; ๋งค์šฐ ๊ธด ํžˆ์Šคํ† ๋ฆฌ์— ๋Œ€ํ•œ ์ฒญํฌ ์š”์•ฝ์„ ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด safeguard๋กœ ์„ค์ •ํ•˜์„ธ์š”. /concepts/compaction์„ ์ฐธ์กฐํ•˜์„ธ์š”.

agents.defaults.compaction.reserveTokensFloor๋Š” Pi compaction์— ๋Œ€ํ•œ ์ตœ์†Œ reserveTokens ๊ฐ’์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’: 20000). floor๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 0์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”.

agents.defaults.compaction.memoryFlush๋Š” auto-compaction ์ „์— silent agentic turn์„ ์‹คํ–‰ํ•˜์—ฌ ๋ชจ๋ธ์ด ๋””์Šคํฌ์— ์˜๊ตฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ €์žฅํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: memory/YYYY-MM-DD.md). ์„ธ์…˜ token ์ถ”์ •์ด compaction ์ œํ•œ ์•„๋ž˜์˜ soft threshold๋ฅผ ๋„˜์œผ๋ฉด ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

๋ ˆ๊ฑฐ์‹œ ๊ธฐ๋ณธ๊ฐ’:

  • memoryFlush.enabled: true
  • memoryFlush.softThresholdTokens: 4000
  • memoryFlush.prompt / memoryFlush.systemPrompt: NO_REPLY๊ฐ€ ์žˆ๋Š” ๋‚ด์žฅ ๊ธฐ๋ณธ๊ฐ’
  • ์ฐธ๊ณ : ์„ธ์…˜ workspace๊ฐ€ ์ฝ๊ธฐ ์ „์šฉ์ธ ๊ฒฝ์šฐ memory flush๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค (agents.defaults.sandbox.workspaceAccess: "ro" ๋˜๋Š” "none").

์˜ˆ์‹œ (์กฐ์ •):

{
  agents: {
    defaults: {
      compaction: {
        mode: "safeguard",
        reserveTokensFloor: 24000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 6000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
        }
      }
    }
  }
}

๋ธ”๋ก ์ŠคํŠธ๋ฆฌ๋ฐ:

  • agents.defaults.blockStreamingDefault: "on"/"off" (๊ธฐ๋ณธ๊ฐ’ off).
  • Channel ์žฌ์ •์˜: *.blockStreaming (๋ฐ ๊ณ„์ •๋ณ„ ๋ณ€ํ˜•)์œผ๋กœ block streaming์„ ๊ฐ•์ œ๋กœ on/offํ•ฉ๋‹ˆ๋‹ค. Telegram์ด ์•„๋‹Œ ์ฑ„๋„์€ block ์‘๋‹ต์„ ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ๋ช…์‹œ์ ์ธ *.blockStreaming: true๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • agents.defaults.blockStreamingBreak: "text_end" ๋˜๋Š” "message_end" (๊ธฐ๋ณธ๊ฐ’: text_end).
  • agents.defaults.blockStreamingChunk: ์ŠคํŠธ๋ฆฌ๋ฐ๋œ ๋ธ”๋ก์— ๋Œ€ํ•œ soft ์ฒญํ‚น. ๊ธฐ๋ณธ๊ฐ’์€ 800โ€“1200 ๋ฌธ์ž์ด๋ฉฐ, ๋‹จ๋ฝ ๊ตฌ๋ถ„ (\n\n)์„ ์„ ํ˜ธํ•˜๊ณ , ๊ทธ ๋‹ค์Œ ์ค„ ๋ฐ”๊ฟˆ, ๊ทธ ๋‹ค์Œ ๋ฌธ์žฅ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ:
    {
      agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } }
    }
    
  • agents.defaults.blockStreamingCoalesce: ์ „์†ก ์ „์— ์ŠคํŠธ๋ฆฌ๋ฐ๋œ ๋ธ”๋ก์„ ๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ { idleMs: 1000 }์ด๊ณ  blockStreamingChunk์—์„œ minChars๋ฅผ ์ƒ์†ํ•˜๋ฉฐ maxChars๋Š” channel ํ…์ŠคํŠธ ์ œํ•œ์œผ๋กœ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค. Signal/Slack/Discord/Google Chat์€ ๊ธฐ๋ณธ์ ์œผ๋กœ minChars: 1500์ด๋ฉฐ ์žฌ์ •์˜๋˜์ง€ ์•Š๋Š” ํ•œ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. Channel ์žฌ์ •์˜: channels.whatsapp.blockStreamingCoalesce, channels.telegram.blockStreamingCoalesce, channels.discord.blockStreamingCoalesce, channels.slack.blockStreamingCoalesce, channels.mattermost.blockStreamingCoalesce, channels.signal.blockStreamingCoalesce, channels.imessage.blockStreamingCoalesce, channels.msteams.blockStreamingCoalesce, channels.googlechat.blockStreamingCoalesce (๋ฐ ๊ณ„์ •๋ณ„ ๋ณ€ํ˜•).
  • agents.defaults.humanDelay: ์ฒซ ๋ฒˆ์งธ ์ดํ›„ ๋ธ”๋ก ์‘๋‹ต ์‚ฌ์ด์˜ ๋ฌด์ž‘์œ„ ์ผ์‹œ ์ค‘์ง€. ๋ชจ๋“œ: off (๊ธฐ๋ณธ๊ฐ’), natural (800โ€“2500ms), custom (minMs/maxMs ์‚ฌ์šฉ). Agent๋ณ„ ์žฌ์ •์˜: agents.list[].humanDelay. ์˜ˆ์‹œ:
    {
      agents: { defaults: { humanDelay: { mode: "natural" } } }
    }
    

๋™์ž‘ + ์ฒญํ‚น ์„ธ๋ถ€ ์‚ฌํ•ญ์€ /concepts/streaming์„ ์ฐธ์กฐํ•˜์„ธ์š”.

ํƒ€์ดํ•‘ ํ‘œ์‹œ:

  • agents.defaults.typingMode: "never" | "instant" | "thinking" | "message". ๊ธฐ๋ณธ๊ฐ’์€ ์ง์ ‘ ์ฑ„ํŒ… / ๋ฉ˜์…˜์˜ ๊ฒฝ์šฐ instant์ด๊ณ  ๋ฉ˜์…˜๋˜์ง€ ์•Š์€ ๊ทธ๋ฃน ์ฑ„ํŒ…์˜ ๊ฒฝ์šฐ message์ž…๋‹ˆ๋‹ค.
  • session.typingMode: ๋ชจ๋“œ์— ๋Œ€ํ•œ ์„ธ์…˜๋ณ„ ์žฌ์ •์˜.
  • agents.defaults.typingIntervalSeconds: ํƒ€์ดํ•‘ ์‹ ํ˜ธ๊ฐ€ ์ƒˆ๋กœ ๊ณ ์ณ์ง€๋Š” ๋นˆ๋„ (๊ธฐ๋ณธ๊ฐ’: 6์ดˆ).
  • session.typingIntervalSeconds: ์ƒˆ๋กœ ๊ณ ์นจ ๊ฐ„๊ฒฉ์— ๋Œ€ํ•œ ์„ธ์…˜๋ณ„ ์žฌ์ •์˜. ๋™์ž‘ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ /concepts/typing-indicators๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

agents.defaults.model.primary๋Š” provider/model๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: anthropic/claude-opus-4-5). ๋ณ„์นญ์€ agents.defaults.models.*.alias์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค (์˜ˆ: Opus). provider๋ฅผ ์ƒ๋žตํ•˜๋ฉด OpenClaw๋Š” ํ˜„์žฌ ์ž„์‹œ deprecation fallback์œผ๋กœ anthropic์„ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. Z.AI ๋ชจ๋ธ์€ zai/<model>๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ (์˜ˆ: zai/glm-4.7) ํ™˜๊ฒฝ์— ZAI_API_KEY (๋˜๋Š” ๋ ˆ๊ฑฐ์‹œ Z_AI_API_KEY)๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

agents.defaults.heartbeat๋Š” ์ฃผ๊ธฐ์ ์ธ heartbeat ์‹คํ–‰์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • every: ๊ธฐ๊ฐ„ ๋ฌธ์ž์—ด (ms, s, m, h); ๊ธฐ๋ณธ ๋‹จ์œ„๋Š” ๋ถ„์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’: 30m. ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 0m๋กœ ์„ค์ •ํ•˜์„ธ์š”.
  • model: heartbeat ์‹คํ–‰์„ ์œ„ํ•œ ์„ ํƒ์  ์žฌ์ •์˜ ๋ชจ๋ธ (provider/model).
  • includeReasoning: true์ด๋ฉด heartbeat๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ๋ณ„๋„์˜ Reasoning: ๋ฉ”์‹œ์ง€๋„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค (/reasoning on๊ณผ ๋™์ผํ•œ ํ˜•ํƒœ). ๊ธฐ๋ณธ๊ฐ’: false.
  • session: heartbeat๊ฐ€ ์‹คํ–‰๋˜๋Š” ์„ธ์…˜์„ ์ œ์–ดํ•˜๋Š” ์„ ํƒ์  ์„ธ์…˜ ํ‚ค. ๊ธฐ๋ณธ๊ฐ’: main.
  • to: ์„ ํƒ์  ์ˆ˜์‹ ์ž ์žฌ์ •์˜ (์ฑ„๋„๋ณ„ id, ์˜ˆ: WhatsApp์˜ E.164, Telegram์˜ chat id).
  • target: ์„ ํƒ์  ์ „์†ก ์ฑ„๋„ (last, whatsapp, telegram, discord, slack, msteams, signal, imessage, none). ๊ธฐ๋ณธ๊ฐ’: last.
  • prompt: heartbeat ๋ณธ๋ฌธ์— ๋Œ€ํ•œ ์„ ํƒ์  ์žฌ์ •์˜ (๊ธฐ๋ณธ๊ฐ’: Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.). ์žฌ์ •์˜๋Š” ๊ทธ๋Œ€๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค; ํŒŒ์ผ์„ ์—ฌ์ „ํžˆ ์ฝ์œผ๋ ค๋ฉด Read HEARTBEAT.md ์ค„์„ ํฌํ•จํ•˜์„ธ์š”.
  • ackMaxChars: ์ „์†ก ์ „์— HEARTBEAT_OK ์ดํ›„ ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€ ๋ฌธ์ž ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 300).

Agent๋ณ„ heartbeat:

  • agents.list[].heartbeat๋ฅผ ์„ค์ •ํ•˜์—ฌ ํŠน์ • agent์— ๋Œ€ํ•œ heartbeat ์„ค์ •์„ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • agent ํ•ญ๋ชฉ์ด heartbeat๋ฅผ ์ •์˜ํ•˜๋ฉด ํ•ด๋‹น agent๋งŒ heartbeat๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค; ๊ธฐ๋ณธ๊ฐ’์€ ํ•ด๋‹น agent์— ๋Œ€ํ•œ ๊ณต์œ  ๊ธฐ์ค€์ด ๋ฉ๋‹ˆ๋‹ค.

Heartbeat๋Š” ์ „์ฒด agent turn์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์งง์€ ๊ฐ„๊ฒฉ์€ ๋” ๋งŽ์€ token์„ ์†Œ๋ชจํ•ฉ๋‹ˆ๋‹ค; every๋ฅผ ์œ ๋…ํ•˜๊ณ , HEARTBEAT.md๋ฅผ ์ž‘๊ฒŒ ์œ ์ง€ํ•˜๊ณ , ๊ทธ๋ฆฌ๊ณ /๋˜๋Š” ๋” ์ €๋ ดํ•œ model์„ ์„ ํƒํ•˜์„ธ์š”.

tools.exec๋Š” background exec ๊ธฐ๋ณธ๊ฐ’์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • backgroundMs: auto-background ์ „ ์‹œ๊ฐ„ (ms, ๊ธฐ๋ณธ๊ฐ’ 10000)
  • timeoutSec: ์ด ๋Ÿฐํƒ€์ž„ ํ›„ ์ž๋™ ์ข…๋ฃŒ (์ดˆ, ๊ธฐ๋ณธ๊ฐ’ 1800)
  • cleanupMs: ์™„๋ฃŒ๋œ ์„ธ์…˜์„ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€ํ•˜๋Š” ์‹œ๊ฐ„ (ms, ๊ธฐ๋ณธ๊ฐ’ 1800000)
  • notifyOnExit: background exec์ด ์ข…๋ฃŒ๋  ๋•Œ ์‹œ์Šคํ…œ ์ด๋ฒคํŠธ๋ฅผ enqueueํ•˜๊ณ  heartbeat๋ฅผ ์š”์ฒญ (๊ธฐ๋ณธ๊ฐ’ true)
  • applyPatch.enabled: ์‹คํ—˜์  apply_patch ํ™œ์„ฑํ™” (OpenAI/OpenAI Codex๋งŒ ํ•ด๋‹น; ๊ธฐ๋ณธ๊ฐ’ false)
  • applyPatch.allowModels: ๋ชจ๋ธ id์˜ ์„ ํƒ์  allowlist (์˜ˆ: gpt-5.2 ๋˜๋Š” openai/gpt-5.2) ์ฐธ๊ณ : applyPatch๋Š” tools.exec ์•„๋ž˜์—๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

tools.web์€ web search + fetch tool์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • tools.web.search.enabled (๊ธฐ๋ณธ๊ฐ’: ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด true)
  • tools.web.search.apiKey (๊ถŒ์žฅ: openclaw configure --section web์„ ํ†ตํ•ด ์„ค์ •ํ•˜๊ฑฐ๋‚˜ BRAVE_API_KEY env var ์‚ฌ์šฉ)
  • tools.web.search.maxResults (1โ€“10, ๊ธฐ๋ณธ๊ฐ’ 5)
  • tools.web.search.timeoutSeconds (๊ธฐ๋ณธ๊ฐ’ 30)
  • tools.web.search.cacheTtlMinutes (๊ธฐ๋ณธ๊ฐ’ 15)
  • tools.web.fetch.enabled (๊ธฐ๋ณธ๊ฐ’ true)
  • tools.web.fetch.maxChars (๊ธฐ๋ณธ๊ฐ’ 50000)
  • tools.web.fetch.timeoutSeconds (๊ธฐ๋ณธ๊ฐ’ 30)
  • tools.web.fetch.cacheTtlMinutes (๊ธฐ๋ณธ๊ฐ’ 15)
  • tools.web.fetch.userAgent (์„ ํƒ์  ์žฌ์ •์˜)
  • tools.web.fetch.readability (๊ธฐ๋ณธ๊ฐ’ true; ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ๊ธฐ๋ณธ HTML ์ •๋ฆฌ๋งŒ ์‚ฌ์šฉ)
  • tools.web.fetch.firecrawl.enabled (๊ธฐ๋ณธ๊ฐ’ API ํ‚ค๊ฐ€ ์„ค์ •๋˜๋ฉด true)
  • tools.web.fetch.firecrawl.apiKey (์„ ํƒ ์‚ฌํ•ญ; ๊ธฐ๋ณธ๊ฐ’์€ FIRECRAWL_API_KEY)
  • tools.web.fetch.firecrawl.baseUrl (๊ธฐ๋ณธ๊ฐ’ https://api.firecrawl.dev)
  • tools.web.fetch.firecrawl.onlyMainContent (๊ธฐ๋ณธ๊ฐ’ true)
  • tools.web.fetch.firecrawl.maxAgeMs (์„ ํƒ ์‚ฌํ•ญ)
  • tools.web.fetch.firecrawl.timeoutSeconds (์„ ํƒ ์‚ฌํ•ญ)

tools.media๋Š” ์ธ๋ฐ”์šด๋“œ ๋ฏธ๋””์–ด ์ดํ•ด (image/audio/video)๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • tools.media.models: ๊ณต์œ  ๋ชจ๋ธ ๋ชฉ๋ก (capability ํƒœ๊ทธ; capability๋ณ„ ๋ชฉ๋ก ์ดํ›„ ์‚ฌ์šฉ).
  • tools.media.concurrency: ์ตœ๋Œ€ ๋™์‹œ capability ์‹คํ–‰ (๊ธฐ๋ณธ๊ฐ’ 2).
  • tools.media.image / tools.media.audio / tools.media.video:
    • enabled: opt-out ์Šค์œ„์น˜ (๋ชจ๋ธ์ด ๊ตฌ์„ฑ๋˜๋ฉด ๊ธฐ๋ณธ๊ฐ’ true).
    • prompt: ์„ ํƒ์  ํ”„๋กฌํ”„ํŠธ ์žฌ์ •์˜ (image/video๋Š” ์ž๋™์œผ๋กœ maxChars ํžŒํŠธ ์ถ”๊ฐ€).
    • maxChars: ์ตœ๋Œ€ ์ถœ๋ ฅ ๋ฌธ์ž ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’ image/video๋Š” 500; audio๋Š” ์„ค์ • ์•ˆ ๋จ).
    • maxBytes: ์ „์†กํ•  ์ตœ๋Œ€ ๋ฏธ๋””์–ด ํฌ๊ธฐ (๊ธฐ๋ณธ๊ฐ’: image 10MB, audio 20MB, video 50MB).
    • timeoutSeconds: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (๊ธฐ๋ณธ๊ฐ’: image 60์ดˆ, audio 60์ดˆ, video 120์ดˆ).
    • language: ์„ ํƒ์  audio ํžŒํŠธ.
    • attachments: ์ฒจ๋ถ€ ํŒŒ์ผ ์ •์ฑ… (mode, maxAttachments, prefer).
    • scope: ์„ ํƒ์  gating (์ฒซ ๋ฒˆ์งธ ์ผ์น˜๊ฐ€ ์šฐ์„ ) with match.channel, match.chatType, or match.keyPrefix.
    • models: ๋ชจ๋ธ ํ•ญ๋ชฉ์˜ ์ˆœ์„œ ๋ชฉ๋ก; ์‹คํŒจ ๋˜๋Š” ์ดˆ๊ณผ ํฌ๊ธฐ ๋ฏธ๋””์–ด๋Š” ๋‹ค์Œ ํ•ญ๋ชฉ์œผ๋กœ fallbackํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ models[] ํ•ญ๋ชฉ:
    • Provider ํ•ญ๋ชฉ (type: "provider" ๋˜๋Š” ์ƒ๋žต):
      • provider: API provider id (openai, anthropic, google/gemini, groq ๋“ฑ).
      • model: ๋ชจ๋ธ id ์žฌ์ •์˜ (image์— ํ•„์š”; audio provider์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์€ gpt-4o-mini-transcribe/whisper-large-v3-turbo, video์˜ ๊ฒฝ์šฐ gemini-3-flash-preview).
      • profile / preferredProfile: auth profile ์„ ํƒ.
    • CLI ํ•ญ๋ชฉ (type: "cli"):
      • command: ์‹คํ–‰ํ•  ์‹คํ–‰ ํŒŒ์ผ.
      • args: ํ…œํ”Œ๋ฆฟ args ({{MediaPath}}, {{Prompt}}, {{MaxChars}} ๋“ฑ ์ง€์›).
    • capabilities: ๊ณต์œ  ํ•ญ๋ชฉ์„ ๊ฒŒ์ดํŠธํ•˜๋Š” ์„ ํƒ์  ๋ชฉ๋ก (image, audio, video). ์ƒ๋žต ์‹œ ๊ธฐ๋ณธ๊ฐ’: openai/anthropic/minimax โ†’ image, google โ†’ image+audio+video, groq โ†’ audio.
    • prompt, maxChars, maxBytes, timeoutSeconds, language๋Š” ํ•ญ๋ชฉ๋ณ„๋กœ ์žฌ์ •์˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋ธ์ด ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ (enabled: false์ธ ๊ฒฝ์šฐ) ์ดํ•ด๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค; ๋ชจ๋ธ์€ ์—ฌ์ „ํžˆ ์›๋ณธ ์ฒจ๋ถ€ ํŒŒ์ผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

Provider auth๋Š” ํ‘œ์ค€ ๋ชจ๋ธ auth ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค (auth profile, OPENAI_API_KEY/GROQ_API_KEY/GEMINI_API_KEY์™€ ๊ฐ™์€ env var, ๋˜๋Š” models.providers.*.apiKey).

์˜ˆ์‹œ:

{
  tools: {
    media: {
      audio: {
        enabled: true,
        maxBytes: 20971520,
        scope: {
          default: "deny",
          rules: [{ action: "allow", match: { chatType: "direct" } }]
        },
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }
        ]
      },
      video: {
        enabled: true,
        maxBytes: 52428800,
        models: [{ provider: "google", model: "gemini-3-flash-preview" }]
      }
    }
  }
}

agents.defaults.subagents๋Š” sub-agent ๊ธฐ๋ณธ๊ฐ’์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค:

  • model: ์ƒ์„ฑ๋œ sub-agent์˜ ๊ธฐ๋ณธ ๋ชจ๋ธ (๋ฌธ์ž์—ด ๋˜๋Š” { primary, fallbacks }). ์ƒ๋žตํ•˜๋ฉด sub-agent๋Š” agent๋ณ„ ๋˜๋Š” ํ˜ธ์ถœ๋ณ„๋กœ ์žฌ์ •์˜๋˜์ง€ ์•Š๋Š” ํ•œ ํ˜ธ์ถœ์ž์˜ ๋ชจ๋ธ์„ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค.
  • maxConcurrent: ์ตœ๋Œ€ ๋™์‹œ sub-agent ์‹คํ–‰ (๊ธฐ๋ณธ๊ฐ’ 1)
  • archiveAfterMinutes: N๋ถ„ ํ›„ sub-agent ์„ธ์…˜ ์ž๋™ ์•„์นด์ด๋ธŒ (๊ธฐ๋ณธ๊ฐ’ 60; ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด 0์œผ๋กœ ์„ค์ •)
  • Subagent๋ณ„ tool ์ •์ฑ…: tools.subagents.tools.allow / tools.subagents.tools.deny (deny๊ฐ€ ์šฐ์„ )

tools.profile์€ tools.allow/tools.deny ์ „์— ๊ธฐ๋ณธ tool allowlist๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:

  • minimal: session_status๋งŒ
  • coding: group:fs, group:runtime, group:sessions, group:memory, image
  • messaging: group:messaging, sessions_list, sessions_history, sessions_send, session_status
  • full: ์ œํ•œ ์—†์Œ (์„ค์ • ์•ˆ ํ•œ ๊ฒƒ๊ณผ ๋™์ผ)

Agent๋ณ„ ์žฌ์ •์˜: agents.list[].tools.profile.

์˜ˆ์‹œ (๊ธฐ๋ณธ์ ์œผ๋กœ messaging๋งŒ, Slack + Discord tool๋„ ํ—ˆ์šฉ):

{
  tools: {
    profile: "messaging",
    allow: ["slack", "discord"]
  }
}

์˜ˆ์‹œ (coding profile, ํ•˜์ง€๋งŒ ์–ด๋””์„œ๋‚˜ exec/process ๊ฑฐ๋ถ€):

{
  tools: {
    profile: "coding",
    deny: ["group:runtime"]
  }
}

tools.byProvider๋Š” ํŠน์ • provider (๋˜๋Š” ๋‹จ์ผ provider/model)์— ๋Œ€ํ•œ tool์„ ์ถ”๊ฐ€๋กœ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Agent๋ณ„ ์žฌ์ •์˜: agents.list[].tools.byProvider.

์ˆœ์„œ: ๊ธฐ๋ณธ profile โ†’ provider profile โ†’ allow/deny ์ •์ฑ…. Provider ํ‚ค๋Š” provider (์˜ˆ: google-antigravity) ๋˜๋Š” provider/model (์˜ˆ: openai/gpt-5.2)๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ (์ „์—ญ coding profile ์œ ์ง€, ํ•˜์ง€๋งŒ Google Antigravity๋Š” ์ตœ์†Œ tool):

{
  tools: {
    profile: "coding",
    byProvider: {
      "google-antigravity": { profile: "minimal" }
    }
  }
}

์˜ˆ์‹œ (provider/model๋ณ„ allowlist):

{
  tools: {
    allow: ["group:fs", "group:runtime", "sessions_list"],
    byProvider: {
      "openai/gpt-5.2": { allow: ["group:fs", "sessions_list"] }
    }
  }
}

tools.allow / tools.deny๋Š” ์ „์—ญ tool allow/deny ์ •์ฑ…์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค (deny๊ฐ€ ์šฐ์„ ). ๋งค์นญ์€ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์œผ๋ฉฐ * ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค ("*"๋Š” ๋ชจ๋“  tool์„ ์˜๋ฏธ). ์ด๊ฒƒ์€ Docker sandbox๊ฐ€ off์ธ ๊ฒฝ์šฐ์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ (์–ด๋””์„œ๋‚˜ browser/canvas ๋น„ํ™œ์„ฑํ™”):

{
  tools: { deny: ["browser", "canvas"] }
}

Tool ๊ทธ๋ฃน (๋‹จ์ถ•์–ด)์€ ์ „์—ญ ๋ฐ agent๋ณ„ tool ์ •์ฑ…์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

  • group:runtime: exec, bash, process
  • group:fs: read, write, edit, apply_patch
  • group:sessions: sessions_list, sessions_history, sessions_send, sessions_spawn, session_status
  • group:memory: memory_search, memory_get
  • group:web: web_search, web_fetch
  • group:ui: browser, canvas
  • group:automation: cron, gateway
  • group:messaging: message
  • group:nodes: nodes
  • group:openclaw: ๋ชจ๋“  ๋‚ด์žฅ OpenClaw tool (provider plugin ์ œ์™ธ)

tools.elevated๋Š” elevated (host) exec ์ ‘๊ทผ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค:

  • enabled: elevated ๋ชจ๋“œ ํ—ˆ์šฉ (๊ธฐ๋ณธ๊ฐ’ true)
  • allowFrom: ์ฑ„๋„๋ณ„ allowlist (๋น„์–ด ์žˆ์Œ = ๋น„ํ™œ์„ฑํ™”)
    • whatsapp: E.164 ๋ฒˆํ˜ธ
    • telegram: chat id ๋˜๋Š” username
    • discord: user id ๋˜๋Š” username (channels.discord.dm.allowFrom์œผ๋กœ ํด๋ฐฑ, ์ƒ๋žต ์‹œ)
    • signal: E.164 ๋ฒˆํ˜ธ
    • imessage: handle/chat id
    • webchat: session id ๋˜๋Š” username

์˜ˆ์‹œ:

{
  tools: {
    elevated: {
      enabled: true,
      allowFrom: {
        whatsapp: ["+15555550123"],
        discord: ["steipete", "1234567890123"]
      }
    }
  }
}

Agent๋ณ„ ์žฌ์ •์˜ (์ถ”๊ฐ€ ์ œํ•œ):

{
  agents: {
    list: [
      {
        id: "family",
        tools: {
          elevated: { enabled: false }
        }
      }
    ]
  }
}

์ฐธ๊ณ  ์‚ฌํ•ญ:

  • tools.elevated๋Š” ์ „์—ญ ๊ธฐ์ค€์ž…๋‹ˆ๋‹ค. agents.list[].tools.elevated๋Š” ์ถ”๊ฐ€๋กœ๋งŒ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๋‘˜ ๋‹ค ํ—ˆ์šฉํ•ด์•ผ ํ•จ).
  • /elevated on|off|ask|full์€ ์„ธ์…˜ ํ‚ค๋ณ„๋กœ ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค; ์ธ๋ผ์ธ ์ง€์‹œ๋ฌธ์€ ๋‹จ์ผ ๋ฉ”์‹œ์ง€์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • Elevated exec๋Š” ํ˜ธ์ŠคํŠธ์—์„œ ์‹คํ–‰๋˜๊ณ  sandboxing์„ ์šฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  • Tool ์ •์ฑ…์€ ์—ฌ์ „ํžˆ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค; exec๊ฐ€ ๊ฑฐ๋ถ€๋˜๋ฉด elevated๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

agents.defaults.maxConcurrent๋Š” ์„ธ์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋‚ด์žฅ agent ์‹คํ–‰ ์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์„ธ์…˜์€ ์—ฌ์ „ํžˆ ์ง๋ ฌํ™”๋ฉ๋‹ˆ๋‹ค (์„ธ์…˜ ํ‚ค๋‹น ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์‹คํ–‰). ๊ธฐ๋ณธ๊ฐ’: 1.

agents.defaults.sandbox

๋‚ด์žฅ agent๋ฅผ ์œ„ํ•œ ์„ ํƒ์  Docker sandboxing์ž…๋‹ˆ๋‹ค. non-main ์„ธ์…˜์ด ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋„๋ก ์˜๋„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์„ธ๋ถ€ ์‚ฌํ•ญ: Sandboxing

๊ธฐ๋ณธ๊ฐ’ (ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ):

  • scope: "agent" (agent๋‹น ํ•˜๋‚˜์˜ ์ปจํ…Œ์ด๋„ˆ + workspace)
  • Debian bookworm-slim ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€
  • agent workspace ์ ‘๊ทผ: workspaceAccess: "none" (๊ธฐ๋ณธ๊ฐ’)
    • "none": ~/.openclaw/sandboxes ์•„๋ž˜์˜ scope๋ณ„ sandbox workspace ์‚ฌ์šฉ
  • "ro": sandbox workspace๋ฅผ /workspace์— ์œ ์ง€ํ•˜๊ณ , agent workspace๋ฅผ /agent์— ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋งˆ์šดํŠธ (write/edit/apply_patch ๋น„ํ™œ์„ฑํ™”)
    • "rw": agent workspace๋ฅผ /workspace์— ์ฝ๊ธฐ/์“ฐ๊ธฐ๋กœ ๋งˆ์šดํŠธ
  • auto-prune: idle > 24์‹œ๊ฐ„ ๋˜๋Š” age > 7์ผ
  • tool ์ •์ฑ…: exec, process, read, write, edit, apply_patch, sessions_list, sessions_history, sessions_send, sessions_spawn, session_status๋งŒ ํ—ˆ์šฉ (deny๊ฐ€ ์šฐ์„ )
    • tools.sandbox.tools๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑ, agent๋ณ„๋กœ agents.list[].tools.sandbox.tools๋ฅผ ํ†ตํ•ด ์žฌ์ •์˜
    • sandbox ์ •์ฑ…์—์„œ ์ง€์›๋˜๋Š” tool ๊ทธ๋ฃน ๋‹จ์ถ•์–ด: group:runtime, group:fs, group:sessions, group:memory (Sandbox vs Tool Policy vs Elevated ์ฐธ์กฐ)
  • ์„ ํƒ์  sandboxed browser (Chromium + CDP, noVNC ๊ด€์ฐฐ์ž)
  • ๊ฐ•ํ™” ๋…ธ๋ธŒ: network, user, pidsLimit, memory, cpus, ulimits, seccompProfile, apparmorProfile

๊ฒฝ๊ณ : scope: "shared"๋Š” ๊ณต์œ  ์ปจํ…Œ์ด๋„ˆ์™€ ๊ณต์œ  workspace๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์„ธ์…˜ ๊ฐ„ ๊ฒฉ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์„ธ์…˜๋ณ„ ๊ฒฉ๋ฆฌ๋ฅผ ์œ„ํ•ด scope: "session"์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

๋ ˆ๊ฑฐ์‹œ: perSession์€ ์—ฌ์ „ํžˆ ์ง€์›๋ฉ๋‹ˆ๋‹ค (true โ†’ scope: "session", false โ†’ scope: "shared").

setupCommand๋Š” ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ƒ์„ฑ๋œ ํ›„ ํ•œ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค (์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ sh -lc๋ฅผ ํ†ตํ•ด). ํŒจํ‚ค์ง€ ์„ค์น˜์˜ ๊ฒฝ์šฐ ๋„คํŠธ์›Œํฌ egress, ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•œ root FS ๋ฐ root ์‚ฌ์šฉ์ž๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main", // off | non-main | all
        scope: "agent", // session | agent | shared (agent is default)
        workspaceAccess: "none", // none | ro | rw
        workspaceRoot: "~/.openclaw/sandboxes",
        docker: {
          image: "openclaw-sandbox:bookworm-slim",
          containerPrefix: "openclaw-sbx-",
          workdir: "/workspace",
          readOnlyRoot: true,
          tmpfs: ["/tmp", "/var/tmp", "/run"],
          network: "none",
          user: "1000:1000",
          capDrop: ["ALL"],
          env: { LANG: "C.UTF-8" },
          setupCommand: "apt-get update && apt-get install -y git curl jq",
          // Agent๋ณ„ ์žฌ์ •์˜ (multi-agent): agents.list[].sandbox.docker.*
          pidsLimit: 256,
          memory: "1g",
          memorySwap: "2g",
          cpus: 1,
          ulimits: {
            nofile: { soft: 1024, hard: 2048 },
            nproc: 256
          },
          seccompProfile: "/path/to/seccomp.json",
          apparmorProfile: "openclaw-sandbox",
          dns: ["1.1.1.1", "8.8.8.8"],
          extraHosts: ["internal.service:10.0.0.5"],
          binds: ["/var/run/docker.sock:/var/run/docker.sock", "/home/user/source:/source:rw"]
        },
        browser: {
          enabled: false,
          image: "openclaw-sandbox-browser:bookworm-slim",
          containerPrefix: "openclaw-sbx-browser-",
          cdpPort: 9222,
          vncPort: 5900,
          noVncPort: 6080,
          headless: false,
          enableNoVnc: true,
          allowHostControl: false,
          allowedControlUrls: ["http://10.0.0.42:18791"],
          allowedControlHosts: ["browser.lab.local", "10.0.0.42"],
          allowedControlPorts: [18791],
          autoStart: true,
          autoStartTimeoutMs: 12000
        },
        prune: {
          idleHours: 24,  // 0 disables idle pruning
          maxAgeDays: 7   // 0 disables max-age pruning
        }
      }
    }
  },
  tools: {
    sandbox: {
      tools: {
        allow: ["exec", "process", "read", "write", "edit", "apply_patch", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
        deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
      }
    }
  }
}

๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ sandbox ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ ๋นŒ๋“œํ•˜์„ธ์š”:

scripts/sandbox-setup.sh

์ฐธ๊ณ : sandbox ์ปจํ…Œ์ด๋„ˆ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ network: "none"์ž…๋‹ˆ๋‹ค; agent๊ฐ€ ์•„์›ƒ๋ฐ”์šด๋“œ ์ ‘๊ทผ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ agents.defaults.sandbox.docker.network๋ฅผ "bridge" (๋˜๋Š” ์ปค์Šคํ…€ ๋„คํŠธ์›Œํฌ)๋กœ ์„ค์ •ํ•˜์„ธ์š”.

์ฐธ๊ณ : ์ธ๋ฐ”์šด๋“œ ์ฒจ๋ถ€ ํŒŒ์ผ์€ media/inbound/*์˜ ํ™œ์„ฑ workspace๋กœ ์Šคํ…Œ์ด์ง•๋ฉ๋‹ˆ๋‹ค. workspaceAccess: "rw"๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŒŒ์ผ์ด agent workspace์— ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ : docker.binds๋Š” ์ถ”๊ฐ€ ํ˜ธ์ŠคํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค; ์ „์—ญ ๋ฐ agent๋ณ„ bind๊ฐ€ ๋ณ‘ํ•ฉ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ ํƒ์  browser ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜์„ธ์š”:

scripts/sandbox-browser-setup.sh

agents.defaults.sandbox.browser.enabled=true์ผ ๋•Œ browser tool์€ sandboxed Chromium ์ธ์Šคํ„ด์Šค (CDP)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. noVNC๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด (headless=false์ผ ๋•Œ ๊ธฐ๋ณธ๊ฐ’), noVNC URL์ด ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์ฃผ์ž…๋˜๋ฏ€๋กœ agent๊ฐ€ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ main config์—์„œ browser.enabled๋ฅผ ํ•„์š”๋กœ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค; sandbox ์ œ์–ด URL์€ ์„ธ์…˜๋ณ„๋กœ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค.

agents.defaults.sandbox.browser.allowHostControl (๊ธฐ๋ณธ๊ฐ’: false)์€ sandboxed ์„ธ์…˜์ด browser tool์„ ํ†ตํ•ด ํ˜ธ์ŠคํŠธ browser ์ œ์–ด ์„œ๋ฒ„๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค (target: "host"). ์—„๊ฒฉํ•œ sandbox ๊ฒฉ๋ฆฌ๋ฅผ ์›ํ•˜๋ฉด ์ด๊ฒƒ์„ ๊บผ๋‘์„ธ์š”.

์›๊ฒฉ ์ œ์–ด๋ฅผ ์œ„ํ•œ Allowlist:

  • allowedControlUrls: target: "custom"์— ํ—ˆ์šฉ๋˜๋Š” ์ •ํ™•ํ•œ ์ œ์–ด URL.
  • allowedControlHosts: ํ—ˆ์šฉ๋˜๋Š” ํ˜ธ์ŠคํŠธ ์ด๋ฆ„ (ํ˜ธ์ŠคํŠธ ์ด๋ฆ„๋งŒ, ํฌํŠธ ์ œ์™ธ).
  • allowedControlPorts: ํ—ˆ์šฉ๋˜๋Š” ํฌํŠธ (๊ธฐ๋ณธ๊ฐ’: http=80, https=443). ๊ธฐ๋ณธ๊ฐ’: ๋ชจ๋“  allowlist๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ (์ œํ•œ ์—†์Œ). allowHostControl์€ ๊ธฐ๋ณธ์ ์œผ๋กœ false์ž…๋‹ˆ๋‹ค.

models (์‚ฌ์šฉ์ž ์ •์˜ provider + base URL)

OpenClaw๋Š” pi-coding-agent ๋ชจ๋ธ ์นดํƒˆ๋กœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. LiteLLM, ๋กœ์ปฌ OpenAI ํ˜ธํ™˜ ์„œ๋ฒ„, Anthropic ํ”„๋ก์‹œ ๋“ฑ์˜ ์‚ฌ์šฉ์ž ์ •์˜ provider๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ~/.openclaw/agents/<agentId>/agent/models.json ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ OpenClaw ์„ค์ • ๋‚ด์—์„œ models.providers ์•„๋ž˜์— ๋™์ผํ•œ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. Provider๋ณ„ ๊ฐœ์š” ๋ฐ ์˜ˆ์ œ: /concepts/model-providers.

models.providers๊ฐ€ ์žˆ์œผ๋ฉด OpenClaw๋Š” ์‹œ์ž‘ ์‹œ ~/.openclaw/agents/<agentId>/agent/์— models.json์„ ์ž‘์„ฑ/๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค:

  • ๊ธฐ๋ณธ ๋™์ž‘: ๋ณ‘ํ•ฉ (๊ธฐ์กด provider๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์ด๋ฆ„์œผ๋กœ ๋ฎ์–ด์”๋‹ˆ๋‹ค)
  • models.mode: "replace"๋กœ ์„ค์ •ํ•˜๋ฉด ํŒŒ์ผ ๋‚ด์šฉ์„ ์™„์ „ํžˆ ๋ฎ์–ด์”๋‹ˆ๋‹ค

๋ชจ๋ธ์€ agents.defaults.model.primary (provider/model)๋ฅผ ํ†ตํ•ด ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

{
  agents: {
    defaults: {
      model: { primary: "custom-proxy/llama-3.1-8b" },
      models: {
        "custom-proxy/llama-3.1-8b": {}
      }
    }
  },
  models: {
    mode: "merge",
    providers: {
      "custom-proxy": {
        baseUrl: "http://localhost:4000/v1",
        apiKey: "LITELLM_KEY",
        api: "openai-completions",
        models: [
          {
            id: "llama-3.1-8b",
            name: "Llama 3.1 8B",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            maxTokens: 32000
          }
        ]
      }
    }
  }
}

OpenCode Zen (๋ฉ€ํ‹ฐ ๋ชจ๋ธ ํ”„๋ก์‹œ)

OpenCode Zen์€ ๋ชจ๋ธ๋ณ„ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฉ€ํ‹ฐ ๋ชจ๋ธ ๊ฒŒ์ดํŠธ์›จ์ด์ž…๋‹ˆ๋‹ค. OpenClaw๋Š” pi-ai์˜ ๋‚ด์žฅ opencode provider๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, https://opencode.ai/auth ์—์„œ OPENCODE_API_KEY (๋˜๋Š” OPENCODE_ZEN_API_KEY)๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ ์‚ฌํ•ญ:

  • ๋ชจ๋ธ ์ฐธ์กฐ๋Š” opencode/<modelId> ํ˜•์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: opencode/claude-opus-4-5).
  • agents.defaults.models๋ฅผ ํ†ตํ•ด ํ—ˆ์šฉ ๋ชฉ๋ก์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฐ ๋ชจ๋ธ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹จ์ถ•ํ‚ค: openclaw onboard --auth-choice opencode-zen.
{
  agents: {
    defaults: {
      model: { primary: "opencode/claude-opus-4-5" },
      models: { "opencode/claude-opus-4-5": { alias: "Opus" } }
    }
  }
}

Z.AI (GLM-4.7) โ€” provider ๋ณ„์นญ ์ง€์›

Z.AI ๋ชจ๋ธ์€ ๋‚ด์žฅ zai provider๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ์— ZAI_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ณ  provider/model๋กœ ๋ชจ๋ธ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๋‹จ์ถ•ํ‚ค: openclaw onboard --auth-choice zai-api-key.

{
  agents: {
    defaults: {
      model: { primary: "zai/glm-4.7" },
      models: { "zai/glm-4.7": {} }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • z.ai/* ๋ฐ z-ai/*๋Š” ํ—ˆ์šฉ๋˜๋Š” ๋ณ„์นญ์ด๋ฉฐ zai/*๋กœ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค.
  • ZAI_API_KEY๊ฐ€ ์—†์œผ๋ฉด zai/* ์š”์ฒญ์€ ๋Ÿฐํƒ€์ž„์— ์ธ์ฆ ์˜ค๋ฅ˜๋กœ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์ œ ์˜ค๋ฅ˜: No API key found for provider "zai".
  • Z.AI์˜ ์ผ๋ฐ˜ API ์—”๋“œํฌ์ธํŠธ๋Š” https://api.z.ai/api/paas/v4์ž…๋‹ˆ๋‹ค. GLM ์ฝ”๋”ฉ ์š”์ฒญ์€ ์ „์šฉ Coding ์—”๋“œํฌ์ธํŠธ https://api.z.ai/api/coding/paas/v4๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์žฅ zai provider๋Š” Coding ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด models.providers์— base URL์„ ์žฌ์ •์˜ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ provider๋ฅผ ์ •์˜ํ•˜์„ธ์š” (์œ„์˜ ์‚ฌ์šฉ์ž ์ •์˜ provider ์„น์…˜ ์ฐธ์กฐ).
  • ๋ฌธ์„œ/์„ค์ •์—์„œ๋Š” ๊ฐ€์งœ ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ค์ œ API key๋Š” ์ ˆ๋Œ€ ์ปค๋ฐ‹ํ•˜์ง€ ๋งˆ์„ธ์š”.

Moonshot AI (Kimi)

Moonshot์˜ OpenAI ํ˜ธํ™˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

{
  env: { MOONSHOT_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "moonshot/kimi-k2.5" },
      models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } }
    }
  },
  models: {
    mode: "merge",
    providers: {
      moonshot: {
        baseUrl: "https://api.moonshot.ai/v1",
        apiKey: "${MOONSHOT_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "kimi-k2.5",
            name: "Kimi K2.5",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192
          }
        ]
      }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • ํ™˜๊ฒฝ์— MOONSHOT_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ openclaw onboard --auth-choice moonshot-api-key๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ๋ชจ๋ธ ์ฐธ์กฐ: moonshot/kimi-k2.5.
  • ์ค‘๊ตญ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด https://api.moonshot.cn/v1์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

Kimi Code

Kimi Code์˜ ์ „์šฉ OpenAI ํ˜ธํ™˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š” (Moonshot๊ณผ ๋ณ„๊ฐœ):

{
  env: { KIMICODE_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "kimi-code/kimi-for-coding" },
      models: { "kimi-code/kimi-for-coding": { alias: "Kimi Code" } }
    }
  },
  models: {
    mode: "merge",
    providers: {
      "kimi-code": {
        baseUrl: "https://api.kimi.com/coding/v1",
        apiKey: "${KIMICODE_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "kimi-for-coding",
            name: "Kimi For Coding",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 262144,
            maxTokens: 32768,
            headers: { "User-Agent": "KimiCLI/0.77" },
            compat: { supportsDeveloperRole: false }
          }
        ]
      }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • ํ™˜๊ฒฝ์— KIMICODE_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ openclaw onboard --auth-choice kimi-code-api-key๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ๋ชจ๋ธ ์ฐธ์กฐ: kimi-code/kimi-for-coding.

Synthetic (Anthropic ํ˜ธํ™˜)

Synthetic์˜ Anthropic ํ˜ธํ™˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

{
  env: { SYNTHETIC_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.1" },
      models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.1": { alias: "MiniMax M2.1" } }
    }
  },
  models: {
    mode: "merge",
    providers: {
      synthetic: {
        baseUrl: "https://api.synthetic.new/anthropic",
        apiKey: "${SYNTHETIC_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "hf:MiniMaxAI/MiniMax-M2.1",
            name: "MiniMax M2.1",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 192000,
            maxTokens: 65536
          }
        ]
      }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • SYNTHETIC_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ openclaw onboard --auth-choice synthetic-api-key๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ๋ชจ๋ธ ์ฐธ์กฐ: synthetic/hf:MiniMaxAI/MiniMax-M2.1.
  • Anthropic ํด๋ผ์ด์–ธํŠธ๊ฐ€ /v1์„ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ Base URL์—์„œ /v1์„ ์ƒ๋žตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋กœ์ปฌ ๋ชจ๋ธ (LM Studio) โ€” ๊ถŒ์žฅ ์„ค์ •

ํ˜„์žฌ ๋กœ์ปฌ ๊ฐ€์ด๋“œ๋Š” /gateway/local-models๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”. ์š”์•ฝ: ๊ฐ•๋ ฅํ•œ ํ•˜๋“œ์›จ์–ด์—์„œ LM Studio Responses API๋ฅผ ํ†ตํ•ด MiniMax M2.1์„ ์‹คํ–‰ํ•˜๊ณ , ํด๋ฐฑ์„ ์œ„ํ•ด ํ˜ธ์ŠคํŒ… ๋ชจ๋ธ์„ ๋ณ‘ํ•ฉํ•ด ๋‘์„ธ์š”.

MiniMax M2.1

LM Studio ์—†์ด MiniMax M2.1์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์„ธ์š”:

{
  agent: {
    model: { primary: "minimax/MiniMax-M2.1" },
    models: {
      "anthropic/claude-opus-4-5": { alias: "Opus" },
      "minimax/MiniMax-M2.1": { alias: "Minimax" }
    }
  },
  models: {
    mode: "merge",
    providers: {
      minimax: {
        baseUrl: "https://api.minimax.io/anthropic",
        apiKey: "${MINIMAX_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "MiniMax-M2.1",
            name: "MiniMax M2.1",
            reasoning: false,
            input: ["text"],
            // ๊ฐ€๊ฒฉ: ์ •ํ™•ํ•œ ๋น„์šฉ ์ถ”์ ์ด ํ•„์š”ํ•˜๋ฉด models.json์—์„œ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.
            cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 },
            contextWindow: 200000,
            maxTokens: 8192
          }
        ]
      }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • MINIMAX_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ openclaw onboard --auth-choice minimax-api๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ: MiniMax-M2.1 (๊ธฐ๋ณธ๊ฐ’).
  • ์ •ํ™•ํ•œ ๋น„์šฉ ์ถ”์ ์ด ํ•„์š”ํ•˜๋ฉด models.json์—์„œ ๊ฐ€๊ฒฉ์„ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.

Cerebras (GLM 4.6 / 4.7)

Cerebras์˜ OpenAI ํ˜ธํ™˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

{
  env: { CEREBRAS_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: {
        primary: "cerebras/zai-glm-4.7",
        fallbacks: ["cerebras/zai-glm-4.6"]
      },
      models: {
        "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
        "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" }
      }
    }
  },
  models: {
    mode: "merge",
    providers: {
      cerebras: {
        baseUrl: "https://api.cerebras.ai/v1",
        apiKey: "${CEREBRAS_API_KEY}",
        api: "openai-completions",
        models: [
          { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
          { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" }
        ]
      }
    }
  }
}

์ฐธ๊ณ ์‚ฌํ•ญ:

  • Cerebras์˜ ๊ฒฝ์šฐ cerebras/zai-glm-4.7์„ ์‚ฌ์šฉํ•˜๊ณ , Z.AI ์ง์ ‘ ์—ฐ๊ฒฐ์˜ ๊ฒฝ์šฐ zai/glm-4.7์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ํ™˜๊ฒฝ์ด๋‚˜ ์„ค์ •์— CEREBRAS_API_KEY๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.

์ฐธ๊ณ ์‚ฌํ•ญ:

  • ์ง€์›๋˜๋Š” API: openai-completions, openai-responses, anthropic-messages, google-generative-ai
  • ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ์ด ํ•„์š”ํ•˜๋ฉด authHeader: true + headers๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • models.json๋ฅผ ๋‹ค๋ฅธ ๊ณณ์— ์ €์žฅํ•˜๋ ค๋ฉด OPENCLAW_AGENT_DIR (๋˜๋Š” PI_CODING_AGENT_DIR)๋กœ agent ์„ค์ • ๋ฃจํŠธ๋ฅผ ์žฌ์ •์˜ํ•˜์„ธ์š” (๊ธฐ๋ณธ๊ฐ’: ~/.openclaw/agents/main/agent).

session

์„ธ์…˜ ๋ฒ”์œ„ ์ง€์ •, ์žฌ์„ค์ • ์ •์ฑ…, ์žฌ์„ค์ • ํŠธ๋ฆฌ๊ฑฐ ๋ฐ ์„ธ์…˜ ์ €์žฅ์†Œ ์œ„์น˜๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

{
  session: {
    scope: "per-sender",
    dmScope: "main",
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"]
    },
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 60
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      dm: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 }
    },
    resetTriggers: ["/new", "/reset"],
    // ๊ธฐ๋ณธ๊ฐ’์€ ์ด๋ฏธ ~/.openclaw/agents/<agentId>/sessions/sessions.json ์•„๋ž˜์˜ agent๋ณ„ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค
    // {agentId} ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    // ์ง์ ‘ ์ฑ„ํŒ…์€ agent:<agentId>:<mainKey>๋กœ ์ถ•์†Œ๋ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’: "main").
    mainKey: "main",
    agentToAgent: {
      // ์š”์ฒญ์ž/๋Œ€์ƒ ๊ฐ„ ์ตœ๋Œ€ ํ•‘ํ ์‘๋‹ต ํ„ด ์ˆ˜ (0โ€“5).
      maxPingPongTurns: 5
    },
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } }
      ],
      default: "allow"
    }
  }
}

ํ•„๋“œ:

  • mainKey: ์ง์ ‘ ์ฑ„ํŒ… ๋ฒ„ํ‚ท ํ‚ค (๊ธฐ๋ณธ๊ฐ’: "main"). agentId๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋ณธ DM ์Šค๋ ˆ๋“œ์˜ "์ด๋ฆ„์„ ๋ณ€๊ฒฝ"ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • Sandbox ์ฐธ๊ณ : agents.defaults.sandbox.mode: "non-main"์€ ์ด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์ธ ์„ธ์…˜์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. mainKey์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ์„ธ์…˜ ํ‚ค (๊ทธ๋ฃน/์ฑ„๋„)๋Š” ์ƒŒ๋“œ๋ฐ•์Šค ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • dmScope: DM ์„ธ์…˜ ๊ทธ๋ฃนํ™” ๋ฐฉ์‹ (๊ธฐ๋ณธ๊ฐ’: "main").
    • main: ๋ชจ๋“  DM์ด ์—ฐ์†์„ฑ์„ ์œ„ํ•ด ๋ฉ”์ธ ์„ธ์…˜์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
    • per-peer: ์ฑ„๋„ ์ „์ฒด์—์„œ ๋ฐœ์‹ ์ž ID๋ณ„๋กœ DM์„ ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    • per-channel-peer: ์ฑ„๋„ + ๋ฐœ์‹ ์ž๋ณ„๋กœ DM์„ ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค (๋ฉ€ํ‹ฐ ์‚ฌ์šฉ์ž ๋ฐ›์€ํŽธ์ง€ํ•จ์— ๊ถŒ์žฅ).
    • per-account-channel-peer: ๊ณ„์ • + ์ฑ„๋„ + ๋ฐœ์‹ ์ž๋ณ„๋กœ DM์„ ๊ฒฉ๋ฆฌํ•ฉ๋‹ˆ๋‹ค (๋ฉ€ํ‹ฐ ๊ณ„์ • ๋ฐ›์€ํŽธ์ง€ํ•จ์— ๊ถŒ์žฅ).
  • identityLinks: ํ‘œ์ค€ ID๋ฅผ provider ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ํ”ผ์–ด์— ๋งคํ•‘ํ•˜์—ฌ per-peer, per-channel-peer ๋˜๋Š” per-account-channel-peer๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋™์ผํ•œ ์‚ฌ๋žŒ์ด ์ฑ„๋„ ๊ฐ„์— DM ์„ธ์…˜์„ ๊ณต์œ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ์ œ: alice: ["telegram:123456789", "discord:987654321012345678"].
  • reset: ๊ธฐ๋ณธ ์žฌ์„ค์ • ์ •์ฑ…. ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฒŒ์ดํŠธ์›จ์ด ํ˜ธ์ŠคํŠธ์˜ ํ˜„์ง€ ์‹œ๊ฐ„์œผ๋กœ ์˜ค์ „ 4์‹œ์— ๋งค์ผ ์žฌ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
    • mode: daily ๋˜๋Š” idle (๊ธฐ๋ณธ๊ฐ’: reset์ด ์žˆ์„ ๋•Œ daily).
    • atHour: ์ผ์ผ ์žฌ์„ค์ • ๊ฒฝ๊ณ„์˜ ํ˜„์ง€ ์‹œ๊ฐ„ (0-23).
    • idleMinutes: ์Šฌ๋ผ์ด๋”ฉ ์œ ํœด ์œˆ๋„์šฐ (๋ถ„). daily์™€ idle์ด ๋ชจ๋‘ ๊ตฌ์„ฑ๋˜๋ฉด ๋จผ์ € ๋งŒ๋ฃŒ๋˜๋Š” ๊ฒƒ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • resetByType: dm, group, thread์— ๋Œ€ํ•œ ์„ธ์…˜๋ณ„ ์žฌ์ •์˜.
    • reset/resetByType ์—†์ด ๋ ˆ๊ฑฐ์‹œ session.idleMinutes๋งŒ ์„ค์ •ํ•˜๋ฉด OpenClaw๋Š” ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ํœด ์ „์šฉ ๋ชจ๋“œ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  • heartbeatIdleMinutes: ํ•˜ํŠธ๋น„ํŠธ ํ™•์ธ์— ๋Œ€ํ•œ ์„ ํƒ์  ์œ ํœด ์žฌ์ •์˜ (์ผ์ผ ์žฌ์„ค์ •์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์œผ๋ฉด ์—ฌ์ „ํžˆ ์ ์šฉ๋จ).
  • agentToAgent.maxPingPongTurns: ์š”์ฒญ์ž/๋Œ€์ƒ ๊ฐ„ ์ตœ๋Œ€ ์‘๋‹ต ํ„ด ์ˆ˜ (0โ€“5, ๊ธฐ๋ณธ๊ฐ’ 5).
  • sendPolicy.default: ๊ทœ์น™์ด ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๋•Œ allow ๋˜๋Š” deny ํด๋ฐฑ.
  • sendPolicy.rules[]: channel, chatType (direct|group|room) ๋˜๋Š” keyPrefix (์˜ˆ: cron:)๋กœ ์ผ์น˜. ์ฒซ ๋ฒˆ์งธ ๊ฑฐ๋ถ€๊ฐ€ ์šฐ์„ ํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

skills (skills ์„ค์ •)

๋ฒˆ๋“ค ํ—ˆ์šฉ ๋ชฉ๋ก, ์„ค์น˜ ๊ธฐ๋ณธ ์„ค์ •, ์ถ”๊ฐ€ skill ํด๋” ๋ฐ skill๋ณ„ ์žฌ์ •์˜๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ๋“ค skill๊ณผ ~/.openclaw/skills์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค (์›Œํฌ์ŠคํŽ˜์ด์Šค skill์€ ์ด๋ฆ„ ์ถฉ๋Œ ์‹œ ์—ฌ์ „ํžˆ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค).

ํ•„๋“œ:

  • allowBundled: ๋ฒˆ๋“ค skill ์ „์šฉ ์„ ํƒ์  ํ—ˆ์šฉ ๋ชฉ๋ก. ์„ค์ •ํ•˜๋ฉด ํ•ด๋‹น ๋ฒˆ๋“ค skill๋งŒ ์ ๊ฒฉ์ž…๋‹ˆ๋‹ค (๊ด€๋ฆฌํ˜•/์›Œํฌ์ŠคํŽ˜์ด์Šค skill์€ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค).
  • load.extraDirs: ์Šค์บ”ํ•  ์ถ”๊ฐ€ skill ๋””๋ ‰ํ† ๋ฆฌ (๊ฐ€์žฅ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„).
  • install.preferBrew: ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ brew ์„ค์น˜ ํ”„๋กœ๊ทธ๋žจ ์„ ํ˜ธ (๊ธฐ๋ณธ๊ฐ’: true).
  • install.nodeManager: node ์„ค์น˜ ํ”„๋กœ๊ทธ๋žจ ๊ธฐ๋ณธ ์„ค์ • (npm | pnpm | yarn, ๊ธฐ๋ณธ๊ฐ’: npm).
  • entries.<skillKey>: skill๋ณ„ ์„ค์ • ์žฌ์ •์˜.

skill๋ณ„ ํ•„๋“œ:

  • enabled: skill์ด ๋ฒˆ๋“ค/์„ค์น˜๋˜์–ด ์žˆ์–ด๋„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด false๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • env: agent ์‹คํ–‰์— ์ฃผ์ž…๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (์ด๋ฏธ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋งŒ).
  • apiKey: ๊ธฐ๋ณธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๋Š” skill์„ ์œ„ํ•œ ์„ ํƒ์  ํŽธ์˜ ๊ธฐ๋Šฅ (์˜ˆ: nano-banana-pro โ†’ GEMINI_API_KEY).

์˜ˆ์ œ:

{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: [
        "~/Projects/agent-scripts/skills",
        "~/Projects/oss/some-skill-pack/skills"
      ]
    },
    install: {
      preferBrew: true,
      nodeManager: "npm"
    },
    entries: {
      "nano-banana-pro": {
        apiKey: "GEMINI_KEY_HERE",
        env: {
          GEMINI_API_KEY: "GEMINI_KEY_HERE"
        }
      },
      peekaboo: { enabled: true },
      sag: { enabled: false }
    }
  }
}

plugins (ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ)

ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฒ€์ƒ‰, ํ—ˆ์šฉ/๊ฑฐ๋ถ€ ๋ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ๋ณ„ ์„ค์ •์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ~/.openclaw/extensions, <workspace>/.openclaw/extensions ๋ฐ ๋ชจ๋“  plugins.load.paths ํ•ญ๋ชฉ์—์„œ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ์„ค์ • ๋ณ€๊ฒฝ ์‹œ ๊ฒŒ์ดํŠธ์›จ์ด ์žฌ์‹œ์ž‘์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ์‚ฌ์šฉ๋ฒ•์€ /plugin์„ ์ฐธ์กฐํ•˜์„ธ์š”.

ํ•„๋“œ:

  • enabled: ํ”Œ๋Ÿฌ๊ทธ์ธ ๋กœ๋”ฉ์„ ์œ„ํ•œ ๋งˆ์Šคํ„ฐ ํ† ๊ธ€ (๊ธฐ๋ณธ๊ฐ’: true).
  • allow: ํ”Œ๋Ÿฌ๊ทธ์ธ ID์˜ ์„ ํƒ์  ํ—ˆ์šฉ ๋ชฉ๋ก; ์„ค์ •ํ•˜๋ฉด ๋‚˜์—ด๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ๋งŒ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.
  • deny: ํ”Œ๋Ÿฌ๊ทธ์ธ ID์˜ ์„ ํƒ์  ๊ฑฐ๋ถ€ ๋ชฉ๋ก (๊ฑฐ๋ถ€๊ฐ€ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค).
  • load.paths: ๋กœ๋“œํ•  ์ถ”๊ฐ€ ํ”Œ๋Ÿฌ๊ทธ์ธ ํŒŒ์ผ ๋˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ (์ ˆ๋Œ€ ๊ฒฝ๋กœ ๋˜๋Š” ~).
  • entries.<pluginId>: ํ”Œ๋Ÿฌ๊ทธ์ธ๋ณ„ ์žฌ์ •์˜.
    • enabled: ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด false๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    • config: ํ”Œ๋Ÿฌ๊ทธ์ธ๋ณ„ ์„ค์ • ๊ฐ์ฒด (์ œ๊ณต๋œ ๊ฒฝ์šฐ ํ”Œ๋Ÿฌ๊ทธ์ธ์— ์˜ํ•ด ๊ฒ€์ฆ๋จ).

์˜ˆ์ œ:

{
  plugins: {
    enabled: true,
    allow: ["voice-call"],
    load: {
      paths: ["~/Projects/oss/voice-call-extension"]
    },
    entries: {
      "voice-call": {
        enabled: true,
        config: {
          provider: "twilio"
        }
      }
    }
  }
}

browser (openclaw ๊ด€๋ฆฌ ๋ธŒ๋ผ์šฐ์ €)

OpenClaw๋Š” openclaw๋ฅผ ์œ„ํ•œ ์ „์šฉ ๊ฒฉ๋ฆฌ๋œ Chrome/Brave/Edge/Chromium ์ธ์Šคํ„ด์Šค๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์ž‘์€ ๋ฃจํ”„๋ฐฑ ์ œ์–ด ์„œ๋น„์Šค๋ฅผ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœํ•„์€ profiles.<name>.cdpUrl์„ ํ†ตํ•ด ์›๊ฒฉ Chromium ๊ธฐ๋ฐ˜ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์›๊ฒฉ ํ”„๋กœํ•„์€ ์—ฐ๊ฒฐ ์ „์šฉ์ž…๋‹ˆ๋‹ค (์‹œ์ž‘/์ค‘์ง€/์žฌ์„ค์ •์€ ๋น„ํ™œ์„ฑํ™”๋จ).

browser.cdpUrl์€ ๋ ˆ๊ฑฐ์‹œ ๋‹จ์ผ ํ”„๋กœํ•„ ์„ค์ •๊ณผ cdpPort๋งŒ ์„ค์ •ํ•˜๋Š” ํ”„๋กœํ•„์˜ ๊ธฐ๋ณธ ์Šคํ‚ค๋งˆ/ํ˜ธ์ŠคํŠธ๋กœ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’:

  • enabled: true
  • evaluateEnabled: true (act:evaluate ๋ฐ wait --fn์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด false๋กœ ์„ค์ •)
  • ์ œ์–ด ์„œ๋น„์Šค: ๋ฃจํ”„๋ฐฑ ์ „์šฉ (ํฌํŠธ๋Š” gateway.port์—์„œ ํŒŒ์ƒ๋จ, ๊ธฐ๋ณธ๊ฐ’ 18791)
  • CDP URL: http://127.0.0.1:18792 (์ œ์–ด ์„œ๋น„์Šค + 1, ๋ ˆ๊ฑฐ์‹œ ๋‹จ์ผ ํ”„๋กœํ•„)
  • ํ”„๋กœํ•„ ์ƒ‰์ƒ: #FF4500 (๋ž์Šคํ„ฐ ์˜ค๋ Œ์ง€)
  • ์ฐธ๊ณ : ์ œ์–ด ์„œ๋ฒ„๋Š” ์‹คํ–‰ ์ค‘์ธ ๊ฒŒ์ดํŠธ์›จ์ด (OpenClaw.app ๋ฉ”๋‰ด๋ฐ” ๋˜๋Š” openclaw gateway)์— ์˜ํ•ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.
  • ์ž๋™ ๊ฐ์ง€ ์ˆœ์„œ: ๊ธฐ๋ณธ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ Chromium ๊ธฐ๋ฐ˜์ด๋ฉด ๊ทธ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด Chrome โ†’ Brave โ†’ Edge โ†’ Chromium โ†’ Chrome Canary.
{
  browser: {
    enabled: true,
    evaluateEnabled: true,
    // cdpUrl: "http://127.0.0.1:18792", // ๋ ˆ๊ฑฐ์‹œ ๋‹จ์ผ ํ”„๋กœํ•„ ์žฌ์ •์˜
    defaultProfile: "chrome",
    profiles: {
      openclaw: { cdpPort: 18800, color: "#FF4500" },
      work: { cdpPort: 18801, color: "#0066CC" },
      remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }
    },
    color: "#FF4500",
    // ๊ณ ๊ธ‰:
    // headless: false,
    // noSandbox: false,
    // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
    // attachOnly: false, // ์›๊ฒฉ CDP๋ฅผ localhost๋กœ ํ„ฐ๋„๋งํ•  ๋•Œ true๋กœ ์„ค์ •
  }
}

ui (๋ชจ์–‘)

๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์ด UI ํฌ๋กฌ (์˜ˆ: Talk Mode ๋ฒ„๋ธ” ์ƒ‰์กฐ)์— ์‚ฌ์šฉํ•˜๋Š” ์„ ํƒ์  ๊ฐ•์กฐ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค.

์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” ์€์€ํ•œ ์—ฐํ•œ ํŒŒ๋ž€์ƒ‰์œผ๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.

{
  ui: {
    seamColor: "#FF4500", // hex (RRGGBB ๋˜๋Š” #RRGGBB)
    // ์„ ํƒ์‚ฌํ•ญ: Control UI ์–ด์‹œ์Šคํ„ดํŠธ ์•„์ด๋ดํ‹ฐํ‹ฐ ์žฌ์ •์˜ ์ œ์–ด.
    // ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด Control UI๋Š” ํ™œ์„ฑ agent ์•„์ด๋ดํ‹ฐํ‹ฐ (์„ค์ • ๋˜๋Š” IDENTITY.md)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    assistant: {
      name: "OpenClaw",
      avatar: "CB" // ์ด๋ชจ์ง€, ์งง์€ ํ…์ŠคํŠธ ๋˜๋Š” ์ด๋ฏธ์ง€ URL/data URI
    }
  }
}

gateway (Gateway ์„œ๋ฒ„ ๋ชจ๋“œ + bind)

gateway.mode๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋จธ์‹ ์ด Gateway๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’:

  • mode: ์„ค์ •๋˜์ง€ ์•Š์Œ ("์ž๋™ ์‹œ์ž‘ ์•ˆ ํ•จ"์œผ๋กœ ์ฒ˜๋ฆฌ๋จ)
  • bind: loopback
  • port: 18789 (WS + HTTP์šฉ ๋‹จ์ผ ํฌํŠธ)
{
  gateway: {
    mode: "local", // ๋˜๋Š” "remote"
    port: 18789, // WS + HTTP ๋‹ค์ค‘ํ™”
    bind: "loopback",
    // controlUi: { enabled: true, basePath: "/openclaw" }
    // auth: { mode: "token", token: "your-token" } // token์œผ๋กœ WS + Control UI ์•ก์„ธ์Šค ๊ฒŒ์ดํŒ…
    // tailscale: { mode: "off" | "serve" | "funnel" }
  }
}

Control UI ๊ธฐ๋ณธ ๊ฒฝ๋กœ:

  • gateway.controlUi.basePath๋Š” Control UI๊ฐ€ ์ œ๊ณต๋˜๋Š” URL ์ ‘๋‘์‚ฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์ œ: "/ui", "/openclaw", "/apps/openclaw".
  • ๊ธฐ๋ณธ๊ฐ’: ๋ฃจํŠธ (/) (๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ).
  • gateway.controlUi.allowInsecureAuth๋Š” ์žฅ์น˜ ์•„์ด๋ดํ‹ฐํ‹ฐ๊ฐ€ ์ƒ๋žต๋œ ๊ฒฝ์šฐ (์ผ๋ฐ˜์ ์œผ๋กœ HTTP๋ฅผ ํ†ตํ•ด) Control UI์— ๋Œ€ํ•œ token ์ „์šฉ ์ธ์ฆ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’: false. HTTPS (Tailscale Serve) ๋˜๋Š” 127.0.0.1์„ ์„ ํ˜ธํ•˜์„ธ์š”.
  • gateway.controlUi.dangerouslyDisableDeviceAuth๋Š” Control UI์— ๋Œ€ํ•œ ์žฅ์น˜ ์•„์ด๋ดํ‹ฐํ‹ฐ ํ™•์ธ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค (token/password๋งŒ). ๊ธฐ๋ณธ๊ฐ’: false. ๋น„์ƒ์‹œ์—๋งŒ ์‚ฌ์šฉํ•˜์„ธ์š”.

๊ด€๋ จ ๋ฌธ์„œ:

์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋ก์‹œ:

  • gateway.trustedProxies: Gateway ์•ž์—์„œ TLS๋ฅผ ์ข…๋ฃŒํ•˜๋Š” ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ IP ๋ชฉ๋ก.
  • ์ด๋Ÿฌํ•œ IP ์ค‘ ํ•˜๋‚˜์—์„œ ์—ฐ๊ฒฐ์ด ์˜ค๋ฉด OpenClaw๋Š” x-forwarded-for (๋˜๋Š” x-real-ip)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ์ปฌ ํŽ˜์–ด๋ง ํ™•์ธ ๋ฐ HTTP ์ธ์ฆ/๋กœ์ปฌ ํ™•์ธ์„ ์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ IP๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์™„์ „ํžˆ ์ œ์–ดํ•˜๋Š” ํ”„๋ก์‹œ๋งŒ ๋‚˜์—ดํ•˜๊ณ  ๋“ค์–ด์˜ค๋Š” x-forwarded-for๋ฅผ ๋ฎ์–ด์“ฐ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

์ฐธ๊ณ ์‚ฌํ•ญ:

  • openclaw gateway๋Š” gateway.mode๊ฐ€ local๋กœ ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฉด (๋˜๋Š” ์žฌ์ •์˜ ํ”Œ๋ž˜๊ทธ๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์œผ๋ฉด) ์‹œ์ž‘์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.
  • gateway.port๋Š” WebSocket + HTTP (control UI, hooks, A2UI)์— ์‚ฌ์šฉ๋˜๋Š” ๋‹จ์ผ ๋‹ค์ค‘ํ™” ํฌํŠธ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
  • OpenAI Chat Completions ์—”๋“œํฌ์ธํŠธ: ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋จ; gateway.http.endpoints.chatCompletions.enabled: true๋กœ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • ์šฐ์„ ์ˆœ์œ„: --port > OPENCLAW_GATEWAY_PORT > gateway.port > ๊ธฐ๋ณธ๊ฐ’ 18789.
  • Gateway ์ธ์ฆ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค (token/password ๋˜๋Š” Tailscale Serve ์•„์ด๋ดํ‹ฐํ‹ฐ). ๋ฃจํ”„๋ฐฑ์ด ์•„๋‹Œ ๋ฐ”์ธ๋“œ๋Š” ๊ณต์œ  token/password๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜จ๋ณด๋”ฉ ๋งˆ๋ฒ•์‚ฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ (๋ฃจํ”„๋ฐฑ์—์„œ๋„) ๊ฒŒ์ดํŠธ์›จ์ด token์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • gateway.remote.token์€ ์›๊ฒฉ CLI ํ˜ธ์ถœ ์ „์šฉ์ž…๋‹ˆ๋‹ค; ๋กœ์ปฌ ๊ฒŒ์ดํŠธ์›จ์ด ์ธ์ฆ์„ ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. gateway.token์€ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.

์ธ์ฆ ๋ฐ Tailscale:

  • gateway.auth.mode๋Š” ํ•ธ๋“œ์…ฐ์ดํฌ ์š”๊ตฌ์‚ฌํ•ญ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค (token ๋˜๋Š” password). ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด token ์ธ์ฆ์ด ๊ฐ€์ •๋ฉ๋‹ˆ๋‹ค.
  • gateway.auth.token์€ token ์ธ์ฆ์„ ์œ„ํ•œ ๊ณต์œ  token์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค (๋™์ผํ•œ ๋จธ์‹ ์˜ CLI์—์„œ ์‚ฌ์šฉ).
  • gateway.auth.mode๊ฐ€ ์„ค์ •๋˜๋ฉด ํ•ด๋‹น ๋ฐฉ๋ฒ•๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค (์„ ํƒ์  Tailscale ํ—ค๋” ์ถ”๊ฐ€).
  • gateway.auth.password๋Š” ์—ฌ๊ธฐ์— ์„ค์ •ํ•˜๊ฑฐ๋‚˜ OPENCLAW_GATEWAY_PASSWORD๋ฅผ ํ†ตํ•ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๊ถŒ์žฅ).
  • gateway.auth.allowTailscale์€ x-forwarded-for, x-forwarded-proto ๋ฐ x-forwarded-host์™€ ํ•จ๊ป˜ ๋ฃจํ”„๋ฐฑ์— ๋„์ฐฉํ•˜๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด Tailscale Serve ์•„์ด๋ดํ‹ฐํ‹ฐ ํ—ค๋” (tailscale-user-login)๊ฐ€ ์ธ์ฆ์„ ์ถฉ์กฑํ•˜๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. OpenClaw๋Š” ์ˆ˜๋ฝํ•˜๊ธฐ ์ „์— tailscale whois๋ฅผ ํ†ตํ•ด x-forwarded-for ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•˜์—ฌ ์•„์ด๋ดํ‹ฐํ‹ฐ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. true์ด๋ฉด Serve ์š”์ฒญ์— token/password๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค; token/password ๋ช…์‹œ์  ์ž๊ฒฉ ์ฆ๋ช…์„ ์š”๊ตฌํ•˜๋ ค๋ฉด false๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. tailscale.mode = "serve"์ด๊ณ  ์ธ์ฆ ๋ชจ๋“œ๊ฐ€ password๊ฐ€ ์•„๋‹ ๋•Œ ๊ธฐ๋ณธ๊ฐ’์€ true์ž…๋‹ˆ๋‹ค.
  • gateway.tailscale.mode: "serve"๋Š” Tailscale Serve๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (tailnet ์ „์šฉ, ๋ฃจํ”„๋ฐฑ ๋ฐ”์ธ๋“œ).
  • gateway.tailscale.mode: "funnel"์€ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ณต๊ฐœ์ ์œผ๋กœ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค; ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • gateway.tailscale.resetOnExit์€ ์ข…๋ฃŒ ์‹œ Serve/Funnel ์„ค์ •์„ ์žฌ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

์›๊ฒฉ ํด๋ผ์ด์–ธํŠธ ๊ธฐ๋ณธ๊ฐ’ (CLI):

  • gateway.remote.url์€ gateway.mode = "remote"์ผ ๋•Œ CLI ํ˜ธ์ถœ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ Gateway WebSocket URL์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • gateway.remote.transport๋Š” macOS ์›๊ฒฉ ์ „์†ก์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค (ssh ๊ธฐ๋ณธ๊ฐ’, ws/wss์˜ ๊ฒฝ์šฐ direct). direct์ผ ๋•Œ gateway.remote.url์€ ws:// ๋˜๋Š” wss://์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ws://host๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํฌํŠธ 18789๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • gateway.remote.token์€ ์›๊ฒฉ ํ˜ธ์ถœ์„ ์œ„ํ•œ token์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค (์ธ์ฆ์ด ์—†์œผ๋ฉด ์„ค์ •ํ•˜์ง€ ์•Š์Œ).
  • gateway.remote.password๋Š” ์›๊ฒฉ ํ˜ธ์ถœ์„ ์œ„ํ•œ password๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค (์ธ์ฆ์ด ์—†์œผ๋ฉด ์„ค์ •ํ•˜์ง€ ์•Š์Œ).

macOS ์•ฑ ๋™์ž‘:

  • OpenClaw.app์€ ~/.openclaw/openclaw.json์„ ๊ฐ์‹œํ•˜๊ณ  gateway.mode ๋˜๋Š” gateway.remote.url์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋“œ๋ฅผ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • gateway.mode๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์ง€๋งŒ gateway.remote.url์ด ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด macOS ์•ฑ์€ ์ด๋ฅผ ์›๊ฒฉ ๋ชจ๋“œ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • macOS ์•ฑ์—์„œ ์—ฐ๊ฒฐ ๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด gateway.mode (๋ฐ ์›๊ฒฉ ๋ชจ๋“œ์—์„œ gateway.remote.url + gateway.remote.transport)๋ฅผ ์„ค์ • ํŒŒ์ผ์— ๋‹ค์‹œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
{
  gateway: {
    mode: "remote",
    remote: {
      url: "ws://gateway.tailnet:18789",
      token: "your-token",
      password: "your-password"
    }
  }
}

Direct ์ „์†ก ์˜ˆ์ œ (macOS ์•ฑ):

{
  gateway: {
    mode: "remote",
    remote: {
      transport: "direct",
      url: "wss://gateway.example.ts.net",
      token: "your-token"
    }
  }
}

gateway.reload (์„ค์ • ํ•ซ ๋ฆฌ๋กœ๋“œ)

Gateway๋Š” ~/.openclaw/openclaw.json (๋˜๋Š” OPENCLAW_CONFIG_PATH)์„ ๊ฐ์‹œํ•˜๊ณ  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“œ:

  • hybrid (๊ธฐ๋ณธ๊ฐ’): ์•ˆ์ „ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ์ฆ‰์‹œ ์ ์šฉํ•˜๊ณ , ์ค‘์š”ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•ด์„œ๋Š” Gateway๋ฅผ ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • hot: ํ•ซ ์•ˆ์ „ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๋งŒ ์ ์šฉํ•˜๊ณ , ์žฌ์‹œ์ž‘์ด ํ•„์š”ํ•  ๋•Œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
  • restart: ์„ค์ • ๋ณ€๊ฒฝ ์‹œ Gateway๋ฅผ ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • off: ํ•ซ ๋ฆฌ๋กœ๋“œ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
{
  gateway: {
    reload: {
      mode: "hybrid",
      debounceMs: 300
    }
  }
}

ํ•ซ ๋ฆฌ๋กœ๋“œ ๋งคํŠธ๋ฆญ์Šค (ํŒŒ์ผ + ์˜ํ–ฅ)

๊ฐ์‹œ๋˜๋Š” ํŒŒ์ผ:

  • ~/.openclaw/openclaw.json (๋˜๋Š” OPENCLAW_CONFIG_PATH)

์ฆ‰์‹œ ์ ์šฉ (์ „์ฒด ๊ฒŒ์ดํŠธ์›จ์ด ์žฌ์‹œ์ž‘ ์—†์Œ):

  • hooks (webhook ์ธ์ฆ/๊ฒฝ๋กœ/๋งคํ•‘) + hooks.gmail (Gmail ๊ฐ์‹œ์ž ์žฌ์‹œ์ž‘)
  • browser (๋ธŒ๋ผ์šฐ์ € ์ œ์–ด ์„œ๋ฒ„ ์žฌ์‹œ์ž‘)
  • cron (cron ์„œ๋น„์Šค ์žฌ์‹œ์ž‘ + ๋™์‹œ์„ฑ ์—…๋ฐ์ดํŠธ)
  • agents.defaults.heartbeat (ํ•˜ํŠธ๋น„ํŠธ ๋Ÿฌ๋„ˆ ์žฌ์‹œ์ž‘)
  • web (WhatsApp ์›น ์ฑ„๋„ ์žฌ์‹œ์ž‘)
  • telegram, discord, signal, imessage (์ฑ„๋„ ์žฌ์‹œ์ž‘)
  • agent, models, routing, messages, session, whatsapp, logging, skills, ui, talk, identity, wizard (๋™์  ์ฝ๊ธฐ)

์ „์ฒด Gateway ์žฌ์‹œ์ž‘ ํ•„์š”:

  • gateway (ํฌํŠธ/๋ฐ”์ธ๋“œ/์ธ์ฆ/control UI/tailscale)
  • bridge (๋ ˆ๊ฑฐ์‹œ)
  • discovery
  • canvasHost
  • plugins
  • ์•Œ ์ˆ˜ ์—†๊ฑฐ๋‚˜ ์ง€์›๋˜์ง€ ์•Š๋Š” ์„ค์ • ๊ฒฝ๋กœ (์•ˆ์ „์„ ์œ„ํ•ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์žฌ์‹œ์ž‘)

๋ฉ€ํ‹ฐ ์ธ์Šคํ„ด์Šค ๊ฒฉ๋ฆฌ

ํ•œ ํ˜ธ์ŠคํŠธ์—์„œ ์—ฌ๋Ÿฌ ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด (์ค‘๋ณต์„ฑ ๋˜๋Š” ๊ตฌ์กฐ ๋ด‡์„ ์œ„ํ•ด) ์ธ์Šคํ„ด์Šค๋ณ„ ์ƒํƒœ + ์„ค์ •์„ ๊ฒฉ๋ฆฌํ•˜๊ณ  ๊ณ ์œ ํ•œ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

  • OPENCLAW_CONFIG_PATH (์ธ์Šคํ„ด์Šค๋ณ„ ์„ค์ •)
  • OPENCLAW_STATE_DIR (์„ธ์…˜/์ž๊ฒฉ ์ฆ๋ช…)
  • agents.defaults.workspace (๋ฉ”๋ชจ๋ฆฌ)
  • gateway.port (์ธ์Šคํ„ด์Šค๋ณ„ ๊ณ ์œ )

ํŽธ์˜ ํ”Œ๋ž˜๊ทธ (CLI):

  • openclaw --dev โ€ฆ โ†’ ~/.openclaw-dev๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๊ธฐ๋ณธ 19001์—์„œ ํฌํŠธ๋ฅผ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค
  • openclaw --profile <name> โ€ฆ โ†’ ~/.openclaw-<name>์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (ํฌํŠธ๋Š” ์„ค์ •/env/ํ”Œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด)

ํŒŒ์ƒ๋œ ํฌํŠธ ๋งคํ•‘ (gateway/browser/canvas)์€ Gateway ๋Ÿฐ๋ถ์„ ์ฐธ์กฐํ•˜์„ธ์š”. ๋ธŒ๋ผ์šฐ์ €/CDP ํฌํŠธ ๊ฒฉ๋ฆฌ ์„ธ๋ถ€ ์ •๋ณด๋Š” ์—ฌ๋Ÿฌ ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์˜ˆ์ œ:

OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \
OPENCLAW_STATE_DIR=~/.openclaw-a \
openclaw gateway --port 19001

hooks (Gateway webhook)

Gateway HTTP ์„œ๋ฒ„์—์„œ ๊ฐ„๋‹จํ•œ HTTP webhook ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’:

  • enabled: false
  • path: /hooks
  • maxBodyBytes: 262144 (256 KB)
{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    presets: ["gmail"],
    transformsDir: "~/.openclaw/hooks",
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate:
          "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
        deliver: true,
        channel: "last",
        model: "openai/gpt-5.2-mini",
      },
    ],
  }
}

์š”์ฒญ์—๋Š” hook token์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • Authorization: Bearer <token> ๋˜๋Š”
  • x-openclaw-token: <token> ๋˜๋Š”
  • ?token=<token>

์—”๋“œํฌ์ธํŠธ:

  • POST /hooks/wake โ†’ { text, mode?: "now"|"next-heartbeat" }
  • POST /hooks/agent โ†’ { message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }
  • POST /hooks/<name> โ†’ hooks.mappings๋ฅผ ํ†ตํ•ด ํ™•์ธ

/hooks/agent๋Š” ํ•ญ์ƒ ๋ฉ”์ธ ์„ธ์…˜์— ์š”์•ฝ์„ ๊ฒŒ์‹œํ•ฉ๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ์„ ํƒ์ ์œผ๋กœ wakeMode: "now"๋ฅผ ํ†ตํ•ด ์ฆ‰๊ฐ์ ์ธ ํ•˜ํŠธ๋น„ํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).

๋งคํ•‘ ์ฐธ๊ณ ์‚ฌํ•ญ:

  • match.path๋Š” /hooks ์ดํ›„์˜ ํ•˜์œ„ ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: /hooks/gmail โ†’ gmail).
  • match.source๋Š” ํŽ˜์ด๋กœ๋“œ ํ•„๋“œ์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: { source: "gmail" }) ๋”ฐ๋ผ์„œ ์ผ๋ฐ˜ /hooks/ingest ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • {{messages[0].subject}}์™€ ๊ฐ™์€ ํ…œํ”Œ๋ฆฟ์€ ํŽ˜์ด๋กœ๋“œ์—์„œ ์ฝ์Šต๋‹ˆ๋‹ค.
  • transform์€ hook ์•ก์…˜์„ ๋ฐ˜ํ™˜ํ•˜๋Š” JS/TS ๋ชจ๋“ˆ์„ ๊ฐ€๋ฆฌํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • deliver: true๋Š” ์ตœ์ข… ์‘๋‹ต์„ ์ฑ„๋„๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค; channel์€ ๊ธฐ๋ณธ์ ์œผ๋กœ last์ž…๋‹ˆ๋‹ค (WhatsApp์œผ๋กœ ํด๋ฐฑ).
  • ์ด์ „ ๋ฐฐ๋‹ฌ ๊ฒฝ๋กœ๊ฐ€ ์—†์œผ๋ฉด channel + to๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š” (Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams์— ํ•„์š”).
  • model์€ ์ด hook ์‹คํ–‰์— ๋Œ€ํ•œ LLM์„ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค (provider/model ๋˜๋Š” ๋ณ„์นญ; agents.defaults.models๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ ํ—ˆ์šฉ๋˜์–ด์•ผ ํ•จ).

Gmail ๋„์šฐ๋ฏธ ์„ค์ • (openclaw webhooks gmail setup / run์—์„œ ์‚ฌ์šฉ):

{
  hooks: {
    gmail: {
      account: "[email protected]",
      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" },

      // ์„ ํƒ์‚ฌํ•ญ: Gmail hook ์ฒ˜๋ฆฌ์— ๋” ์ €๋ ดํ•œ ๋ชจ๋ธ ์‚ฌ์šฉ
      // ์ธ์ฆ/์†๋„ ์ œํ•œ/์‹œ๊ฐ„ ์ดˆ๊ณผ ์‹œ agents.defaults.model.fallbacks, ๊ทธ ๋‹ค์Œ primary๋กœ ํด๋ฐฑ
      model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
      // ์„ ํƒ์‚ฌํ•ญ: Gmail hook์˜ ๊ธฐ๋ณธ thinking ๋ ˆ๋ฒจ
      thinking: "off",
    }
  }
}

Gmail hook์— ๋Œ€ํ•œ ๋ชจ๋ธ ์žฌ์ •์˜:

  • hooks.gmail.model์€ Gmail hook ์ฒ˜๋ฆฌ์— ์‚ฌ์šฉํ•  ๋ชจ๋ธ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’์€ ์„ธ์…˜ primary).
  • provider/model ์ฐธ์กฐ ๋˜๋Š” agents.defaults.models์˜ ๋ณ„์นญ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ์ฆ/์†๋„ ์ œํ•œ/์‹œ๊ฐ„ ์ดˆ๊ณผ ์‹œ agents.defaults.model.fallbacks, ๊ทธ ๋‹ค์Œ agents.defaults.model.primary๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.
  • agents.defaults.models๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ hooks ๋ชจ๋ธ์„ ํ—ˆ์šฉ ๋ชฉ๋ก์— ํฌํ•จํ•˜์„ธ์š”.
  • ์‹œ์ž‘ ์‹œ ๊ตฌ์„ฑ๋œ ๋ชจ๋ธ์ด ๋ชจ๋ธ ์นดํƒˆ๋กœ๊ทธ๋‚˜ ํ—ˆ์šฉ ๋ชฉ๋ก์— ์—†์œผ๋ฉด ๊ฒฝ๊ณ ํ•ฉ๋‹ˆ๋‹ค.
  • hooks.gmail.thinking์€ Gmail hook์˜ ๊ธฐ๋ณธ thinking ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•˜๋ฉฐ hook๋ณ„ thinking์— ์˜ํ•ด ์žฌ์ •์˜๋ฉ๋‹ˆ๋‹ค.

Gateway ์ž๋™ ์‹œ์ž‘:

  • hooks.enabled=true์ด๊ณ  hooks.gmail.account๊ฐ€ ์„ค์ •๋˜๋ฉด Gateway๋Š” ๋ถ€ํŒ… ์‹œ gog gmail watch serve๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  watch๋ฅผ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž๋™ ์‹œ์ž‘์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด OPENCLAW_SKIP_GMAIL_WATCHER=1์„ ์„ค์ •ํ•˜์„ธ์š” (์ˆ˜๋™ ์‹คํ–‰์šฉ).
  • Gateway์™€ ํ•จ๊ป˜ ๋ณ„๋„์˜ gog gmail watch serve๋ฅผ ์‹คํ–‰ํ•˜์ง€ ๋งˆ์„ธ์š”; listen tcp 127.0.0.1:8788: bind: address already in use๋กœ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ : tailscale.mode๊ฐ€ ์ผœ์ ธ ์žˆ์œผ๋ฉด OpenClaw๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ serve.path๋ฅผ /๋กœ ์„ค์ •ํ•˜์—ฌ Tailscale์ด /gmail-pubsub์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ”„๋ก์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค (์„ค์ • ๊ฒฝ๋กœ ์ ‘๋‘์‚ฌ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค). ๋ฐฑ์—”๋“œ๊ฐ€ ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ๊ฒฝ๋กœ๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ hooks.gmail.tailscale.target์„ ์ „์ฒด URL๋กœ ์„ค์ •ํ•˜์„ธ์š” (๊ทธ๋ฆฌ๊ณ  serve.path๋ฅผ ์ •๋ ฌํ•˜์„ธ์š”).

canvasHost (LAN/tailnet Canvas ํŒŒ์ผ ์„œ๋ฒ„ + ๋ผ์ด๋ธŒ ๋ฆฌ๋กœ๋“œ)

Gateway๋Š” HTML/CSS/JS ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ HTTP๋ฅผ ํ†ตํ•ด ์ œ๊ณตํ•˜๋ฏ€๋กœ iOS/Android ๋…ธ๋“œ๊ฐ€ ๋‹จ์ˆœํžˆ canvas.navigateํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๋ฃจํŠธ: ~/.openclaw/workspace/canvas
๊ธฐ๋ณธ ํฌํŠธ: 18793 (openclaw ๋ธŒ๋ผ์šฐ์ € CDP ํฌํŠธ 18792๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์„ ํƒ๋จ)
์„œ๋ฒ„๋Š” ๋…ธ๋“œ๊ฐ€ ๋„๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก gateway bind ํ˜ธ์ŠคํŠธ (LAN ๋˜๋Š” Tailnet)์—์„œ ์ˆ˜์‹  ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„:

  • canvasHost.root ์•„๋ž˜์˜ ํŒŒ์ผ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค
  • ์ œ๊ณต๋˜๋Š” HTML์— ์ž‘์€ ๋ผ์ด๋ธŒ ๋ฆฌ๋กœ๋“œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค
  • ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ฐ์‹œํ•˜๊ณ  /__openclaw__/ws์˜ WebSocket ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด ๋ฆฌ๋กœ๋“œ๋ฅผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค
  • ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ์‹œ์ž‘ index.html์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค (์ฆ‰์‹œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก)
  • /__openclaw__/a2ui/์—์„œ A2UI๋„ ์ œ๊ณตํ•˜๋ฉฐ canvasHostUrl๋กœ ๋…ธ๋“œ์— ๊ด‘๊ณ ๋ฉ๋‹ˆ๋‹ค (ํ•ญ์ƒ Canvas/A2UI์— ๋Œ€ํ•ด ๋…ธ๋“œ์—์„œ ์‚ฌ์šฉ๋จ)

๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ํฌ๊ฑฐ๋‚˜ EMFILE์— ๋„๋‹ฌํ•˜๋ฉด ๋ผ์ด๋ธŒ ๋ฆฌ๋กœ๋“œ (๋ฐ ํŒŒ์ผ ๊ฐ์‹œ)๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜์„ธ์š”:

  • ์„ค์ •: canvasHost: { liveReload: false }
{
  canvasHost: {
    root: "~/.openclaw/workspace/canvas",
    port: 18793,
    liveReload: true
  }
}

canvasHost.* ๋ณ€๊ฒฝ ์‹œ ๊ฒŒ์ดํŠธ์›จ์ด ์žฌ์‹œ์ž‘์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค (์„ค์ • ๋ฆฌ๋กœ๋“œ๊ฐ€ ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค).

๋น„ํ™œ์„ฑํ™”:

  • ์„ค์ •: canvasHost: { enabled: false }
  • ํ™˜๊ฒฝ: OPENCLAW_SKIP_CANVAS_HOST=1

bridge (๋ ˆ๊ฑฐ์‹œ TCP ๋ธŒ๋ฆฌ์ง€, ์ œ๊ฑฐ๋จ)

ํ˜„์žฌ ๋นŒ๋“œ์—๋Š” ๋” ์ด์ƒ TCP ๋ธŒ๋ฆฌ์ง€ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค; bridge.* ์„ค์ • ํ‚ค๋Š” ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค. ๋…ธ๋“œ๋Š” Gateway WebSocket์„ ํ†ตํ•ด ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ด ์„น์…˜์€ ์—ญ์‚ฌ์  ์ฐธ์กฐ๋ฅผ ์œ„ํ•ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

๋ ˆ๊ฑฐ์‹œ ๋™์ž‘:

  • Gateway๋Š” ๋…ธ๋“œ (iOS/Android)๋ฅผ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ TCP ๋ธŒ๋ฆฌ์ง€๋ฅผ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ํฌํŠธ 18790์—์„œ ์ œ๊ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’:

  • enabled: true
  • port: 18790
  • bind: lan (0.0.0.0์— ๋ฐ”์ธ๋”ฉ)

๋ฐ”์ธ๋“œ ๋ชจ๋“œ:

  • lan: 0.0.0.0 (LAN/Wiโ€‘Fi ๋ฐ Tailscale์„ ํฌํ•จํ•œ ๋ชจ๋“  ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๋„๋‹ฌ ๊ฐ€๋Šฅ)
  • tailnet: ๋จธ์‹ ์˜ Tailscale IP์—๋งŒ ๋ฐ”์ธ๋”ฉ (Vienna โ‡„ London์— ๊ถŒ์žฅ)
  • loopback: 127.0.0.1 (๋กœ์ปฌ ์ „์šฉ)
  • auto: tailnet IP๊ฐ€ ์žˆ์œผ๋ฉด ์„ ํ˜ธํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด lan

TLS:

  • bridge.tls.enabled: ๋ธŒ๋ฆฌ์ง€ ์—ฐ๊ฒฐ์— ๋Œ€ํ•ด TLS ํ™œ์„ฑํ™” (ํ™œ์„ฑํ™”๋˜๋ฉด TLS ์ „์šฉ).
  • bridge.tls.autoGenerate: ์ธ์ฆ์„œ/ํ‚ค๊ฐ€ ์—†์„ ๋•Œ ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ์ƒ์„ฑ (๊ธฐ๋ณธ๊ฐ’: true).
  • bridge.tls.certPath / bridge.tls.keyPath: ๋ธŒ๋ฆฌ์ง€ ์ธ์ฆ์„œ + ๊ฐœ์ธ ํ‚ค์˜ PEM ๊ฒฝ๋กœ.
  • bridge.tls.caPath: ์„ ํƒ์  PEM CA ๋ฒˆ๋“ค (์‚ฌ์šฉ์ž ์ •์˜ ๋ฃจํŠธ ๋˜๋Š” ํ–ฅํ›„ mTLS).

TLS๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด Gateway๋Š” ๋…ธ๋“œ๊ฐ€ ์ธ์ฆ์„œ๋ฅผ ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฒ€์ƒ‰ TXT ๋ ˆ์ฝ”๋“œ์—์„œ bridgeTls=1 ๋ฐ bridgeTlsSha256์„ ๊ด‘๊ณ ํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜๋™ ์—ฐ๊ฒฐ์€ ์•„์ง ์ €์žฅ๋œ ์ง€๋ฌธ์ด ์—†์œผ๋ฉด ์ตœ์ดˆ ์‹ ๋ขฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ž๋™ ์ƒ์„ฑ๋œ ์ธ์ฆ์„œ์—๋Š” PATH์— openssl์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค; ์ƒ์„ฑ์— ์‹คํŒจํ•˜๋ฉด ๋ธŒ๋ฆฌ์ง€๊ฐ€ ์‹œ์ž‘๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

{
  bridge: {
    enabled: true,
    port: 18790,
    bind: "tailnet",
    tls: {
      enabled: true,
      // ์ƒ๋žตํ•˜๋ฉด ~/.openclaw/bridge/tls/bridge-{cert,key}.pem์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
      // certPath: "~/.openclaw/bridge/tls/bridge-cert.pem",
      // keyPath: "~/.openclaw/bridge/tls/bridge-key.pem"
    }
  }
}

discovery.mdns (Bonjour / mDNS ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ชจ๋“œ)

LAN mDNS ๊ฒ€์ƒ‰ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ (_openclaw-gw._tcp)๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

  • minimal (๊ธฐ๋ณธ๊ฐ’): TXT ๋ ˆ์ฝ”๋“œ์—์„œ cliPath + sshPort ์ƒ๋žต
  • full: TXT ๋ ˆ์ฝ”๋“œ์— cliPath + sshPort ํฌํ•จ
  • off: mDNS ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”
  • ํ˜ธ์ŠคํŠธ๋ช…: ๊ธฐ๋ณธ๊ฐ’์€ openclaw (openclaw.local์„ ๊ด‘๊ณ ). OPENCLAW_MDNS_HOSTNAME์œผ๋กœ ์žฌ์ •์˜.
{
  discovery: { mdns: { mode: "minimal" } }
}

discovery.wideArea (Wide-Area Bonjour / ์œ ๋‹ˆ์บ์ŠคํŠธ DNSโ€‘SD)

ํ™œ์„ฑํ™”๋˜๋ฉด Gateway๋Š” ๊ตฌ์„ฑ๋œ ๊ฒ€์ƒ‰ ๋„๋ฉ”์ธ (์˜ˆ: openclaw.internal.)์—์„œ _openclaw-gw._tcp์— ๋Œ€ํ•œ ์œ ๋‹ˆ์บ์ŠคํŠธ DNS-SD ์กด์„ ~/.openclaw/dns/์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

iOS/Android๊ฐ€ ๋„คํŠธ์›Œํฌ ๊ฐ„์— ๊ฒ€์ƒ‰ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด (Vienna โ‡„ London) ๋‹ค์Œ๊ณผ ํŽ˜์–ด๋งํ•˜์„ธ์š”:

  • ์„ ํƒํ•œ ๋„๋ฉ”์ธ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒŒ์ดํŠธ์›จ์ด ํ˜ธ์ŠคํŠธ์˜ DNS ์„œ๋ฒ„ (CoreDNS ๊ถŒ์žฅ)
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฒŒ์ดํŠธ์›จ์ด DNS ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๋„๋ฉ”์ธ์„ ํ™•์ธํ•˜๋„๋ก Tailscale split DNS

์ผํšŒ์„ฑ ์„ค์ • ๋„์šฐ๋ฏธ (๊ฒŒ์ดํŠธ์›จ์ด ํ˜ธ์ŠคํŠธ):

openclaw dns setup --apply
{
  discovery: { wideArea: { enabled: true } }
}

ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜

ํ…œํ”Œ๋ฆฟ ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋Š” tools.media.*.models[].args ๋ฐ tools.media.models[].args (๋ฐ ํ–ฅํ›„ ํ…œํ”Œ๋ฆฟํ™”๋œ ์ธ์ˆ˜ ํ•„๋“œ)์—์„œ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค.

๋ณ€์ˆ˜์„ค๋ช…
{{Body}}์ „์ฒด ์ˆ˜์‹  ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ
{{RawBody}}์›์‹œ ์ˆ˜์‹  ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ (๊ธฐ๋ก/๋ฐœ์‹ ์ž ๋ž˜ํผ ์—†์Œ; ๋ช…๋ น ํŒŒ์‹ฑ์— ๊ฐ€์žฅ ์ ํ•ฉ)
{{BodyStripped}}๊ทธ๋ฃน ๋ฉ˜์…˜์ด ์ œ๊ฑฐ๋œ ๋ณธ๋ฌธ (agent์˜ ๊ธฐ๋ณธ ์ตœ์„ )
{{From}}๋ฐœ์‹ ์ž ์‹๋ณ„์ž (WhatsApp์˜ ๊ฒฝ์šฐ E.164; ์ฑ„๋„์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ)
{{To}}๋Œ€์ƒ ์‹๋ณ„์ž
{{MessageSid}}์ฑ„๋„ ๋ฉ”์‹œ์ง€ ID (์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ)
{{SessionId}}ํ˜„์žฌ ์„ธ์…˜ UUID
{{IsNewSession}}์ƒˆ ์„ธ์…˜์ด ์ƒ์„ฑ๋˜์—ˆ์„ ๋•Œ "true"
{{MediaUrl}}์ˆ˜์‹  ๋ฏธ๋””์–ด ์˜์‚ฌ URL (์žˆ๋Š” ๊ฒฝ์šฐ)
{{MediaPath}}๋กœ์ปฌ ๋ฏธ๋””์–ด ๊ฒฝ๋กœ (๋‹ค์šด๋กœ๋“œ๋œ ๊ฒฝ์šฐ)
{{MediaType}}๋ฏธ๋””์–ด ์œ ํ˜• (image/audio/document/โ€ฆ)
{{Transcript}}์˜ค๋””์˜ค ์ „์‚ฌ (ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ)
{{Prompt}}CLI ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ๋œ ๋ฏธ๋””์–ด ํ”„๋กฌํ”„ํŠธ
{{MaxChars}}CLI ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ๋œ ์ตœ๋Œ€ ์ถœ๋ ฅ ๋ฌธ์ž ์ˆ˜
{{ChatType}}"direct" ๋˜๋Š” "group"
{{GroupSubject}}๊ทธ๋ฃน ์ œ๋ชฉ (์ตœ์„  ๋…ธ๋ ฅ)
{{GroupMembers}}๊ทธ๋ฃน ๊ตฌ์„ฑ์› ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ตœ์„  ๋…ธ๋ ฅ)
{{SenderName}}๋ฐœ์‹ ์ž ํ‘œ์‹œ ์ด๋ฆ„ (์ตœ์„  ๋…ธ๋ ฅ)
{{SenderE164}}๋ฐœ์‹ ์ž ์ „ํ™”๋ฒˆํ˜ธ (์ตœ์„  ๋…ธ๋ ฅ)
{{Provider}}Provider ํžŒํŠธ (whatsapp

Cron (Gateway ์Šค์ผ€์ค„๋Ÿฌ)

Cron์€ ๊นจ์šฐ๊ธฐ ๋ฐ ์˜ˆ์•ฝ๋œ ์ž‘์—…์„ ์œ„ํ•œ Gateway ์†Œ์œ  ์Šค์ผ€์ค„๋Ÿฌ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ ๊ฐœ์š” ๋ฐ CLI ์˜ˆ์ œ๋Š” Cron jobs๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

{
  cron: {
    enabled: true,
    maxConcurrentRuns: 2
  }
}

๋‹ค์Œ: Agent Runtime ๐Ÿฆž