๊ตฌ์ฑ ๐ง
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)
}
}
}
}
ํด๊ฒฐ ์์:
- DM๋ณ ์ฌ์ ์: channels.<provider>.dms[userId].historyLimit
- ๊ณต๊ธ์ ๊ธฐ๋ณธ๊ฐ: channels.<provider>.dmHistoryLimit
- ์ ํ ์์(๋ชจ๋ ํ์คํ ๋ฆฌ ์ ์ง)
์ง์๋๋ ๊ณต๊ธ์: 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(์ ํ ์ฌํญ; ์ฑ๋๋ณ)
๊ฒฐ์ ๋ก ์ ์ผ์น ์์:
- match.peer
- match.guildId
- match.teamId
- match.accountId(์ ํ, peer/guild/team ์์)
- match.accountId: "*"(์ฑ๋ ์ ์ฒด, peer/guild/team ์์)
- ๊ธฐ๋ณธ ์์ด์ ํธ(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 ๊ทธ๋ฃน | ๊ธฐ๋ณธ๊ฐ | ๋น๊ณ |
|---|---|---|
| reactions | enabled | ๋ฐ์ ์ถ๊ฐ + ๋ฐ์ ๋ชฉ๋ก ์กฐํ |
| messages | enabled | ์ฝ๊ธฐ/์ ์ก/ํธ์ง/์ญ์ |
| pins | enabled | ๊ณ ์ /๊ณ ์ ํด์ /๋ชฉ๋ก |
| memberInfo | enabled | ๋ฉค๋ฒ ์ ๋ณด |
| emojiList | enabled | ์ปค์คํ ์ด๋ชจ์ง ๋ชฉ๋ก |
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๋ ํญ๋ชฉ๋ณ๋ก ์ฌ์ ์๋ ์ ์์ต๋๋ค.
- Provider ํญ๋ชฉ (type: "provider" ๋๋ ์๋ต):
๋ชจ๋ธ์ด ๊ตฌ์ฑ๋์ง ์์๊ฑฐ๋ (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 ๐ฆ