Positioning
One script deploys any customized Agent project to any remote machine.
Not specific to tian_agent, not specific to Mac, not specific to Feishu. Any OpenClaw Agent project, as long as it is organized according to the standard directory structure, can be deployed with a single command.
Use cases
- Deploy customized Agent teams for customers/teams
- Push from local development environment to production machines
- Batch deploy the same set of Agents to multiple machines
- Incremental redeployment after project updates
Core concept
项目仓库(本地)──tar/SSH──→ 远程机器
├── ~/projects/<name>/ ← 源码(可 git pull 更新)
├── ~/.openclaw/agents/ ← Agent 运行时配置
├── ~/.openclaw/skills/ ← Skills
└── ~/.openclaw/openclaw.json ← 自动注入 provider/channel/agent
Project directory structure (standard)
my_project/
├── deploy.json ← 部署清单(声明 provider/channel/agent/权限)
├── agents/
│ ├── assistant/
│ │ └── agent/
│ │ ├── agent.json
│ │ ├── AGENTS.md
│ │ └── SOUL.md
│ ├── support/
│ │ └── agent/
│ └── ...
├── skills/ ← 共享 skills(可选)
│ └── my-custom-skill/
│ └── SKILL.md
└── workspace/ ← 工作区文件(可选)
├── AGENTS.md
├── SOUL.md
└── USER.md
deploy.json — Declarative deployment manifest
Place one in each project root directory deploy.json, the script automatically reads it and injects the remote openclaw.json。
{
"project_name": "tian_agent",
"providers": {
"google": {
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
"apiKey": "AIzaSy...",
"api": "google-genai",
"models": [{"id": "gemini-2.5-pro"}, {"id": "gemini-2.5-flash"}]
}
},
"agents": [
{"id": "assistant", "model": {"primary": "google/gemini-2.5-pro", "fallbacks": ["zai/glm-5"]}},
{"id": "media", "model": {"primary": "google/gemini-2.5-pro"}},
{"id": "operator", "model": {"primary": "google/gemini-3.1-pro-preview"}},
{"id": "service", "model": {"primary": "google/gemini-3.1-pro-preview"}}
],
"channels": {
"feishu": {
"accounts": {
"default": {"appId": "cli_xxx", "appSecret": "SEC", "agent": "assistant"},
"media": {"appId": "cli_yyy", "appSecret": "SEC", "agent": "media"},
"operator": {"appId": "cli_zzz", "appSecret": "SEC", "agent": "operator"},
"service": {"appId": "cli_www", "appSecret": "SEC", "agent": "service"}
}
}
},
"tools": {
"profile": "full",
"exec": {"security": "full", "ask": "off"},
"elevated": {"enabled": true}
},
"workspace": "${HOME}/projects/tian_agent",
"config_patches": {
"agents.defaults.thinking": "high"
}
}
deploy.json field descriptions
| Field | Purpose | Merge strategy |
|---|---|---|
providers |
Model provider | Do not overwriteExisting provider with the same name |
agents |
Agent list + model | Do not overwriteExisting agent with the same id |
channels |
Channel account binding | Do not overwriteExisting account, but supplement the missing agent routes |
tools |
Permission configuration | Directly overwrite |
workspace |
Default workspace | Support ${HOME} ${PROJECT} variables |
config_patches |
Arbitrary dot-path patch | Set precisely to the specified path |
Safety principles: Existing configuration is not overwritten, to avoid breaking existing services on the remote machine.
Usage
Basic deployment
./skills/remote-openclaw-deploy/scripts/deploy.sh \
laotian@100.91.44.116 \
~/projects/private_tian_agent \
~/.ssh/id_ed25519
No deploy.json (only transfer files + basic permissions)
If the project has no deploy.json, the script only does:
- Transfer agents/ skills/ workspace/
- Set tools.profile=full, exec=full, elevated=true
- Set workspace path
- Restart Gateway
Provider and Channel need to be configured manually.
Update deployment
# 本地改完代码后
cd ~/projects/my_project && git commit -am "update"
# 重新部署(幂等,不会覆盖已有配置)
./deploy.sh user@ip ~/projects/my_project
5 Phase process
| Phase | Action | Cross-platform |
|---|---|---|
| 1. Environment detection | SSH connectivity, OS/memory/disk, OpenClaw version, Gateway status | ✅ macOS + Linux |
| 2. Transfer files | agents/ → ~/.openclaw/agents/, skills/ → ~/.openclaw/skills/, project → ~/projects/ | ✅ Automatically detect HOME |
| 3. Configuration injection | Read deploy.json → merge into openclaw.json (provider/agent/channel/tools/workspace) | ✅ Python3 |
| 4. Restart Gateway | SIGUSR1 hot reload → fall back to gateway install/start | ✅ |
| 5. Verification | Configuration errors, WS connection count, Agent list, Channel account binding | ✅ |
Pitfall list (practical summary)
| Pitfall | Symptom | Correct approach |
|---|---|---|
Feishu account has no agent field |
All messages routed to main | Each account must explicitly "agent": "<id>" |
workspace place at top level |
Unknown key, Gateway crashes | Use agents.defaults.workspace |
agent.cwd |
Legacy key warning | Deprecated, use agents.defaults.workspace |
tools.elevated write boolean value |
Type error | Must be {"enabled": true} object |
tools.profile: "default" |
Invalid value | Can only be minimal/coding/messaging/full |
| xingsuancode 503 | No available accounts | Switch to fallback, original model downgraded |
| Tailscale + Clash fake-ip | SSH cannot connect to domain | Use IP directly 100.x.x.x |
/Users/$USER hardcoded |
Wrong path on Linux | Use $HOME auto-detect |
Template
templates/deploy.json.template— deploy.json template, copy to project root and modify
Prerequisites
- SSH reachable (Tailscale networking recommended)
- OpenClaw installed on target machine
- Python3 on target machine
- Project files + deploy.json locally
FAQ
Q: Agent not replying after deployment?
A: Troubleshoot: ① Does the Channel account have the agent field? ② Is the model returning 503? ③ Is tools.profile sufficient?
Q: How to support Telegram/Discord?
A: In deploy.json's channels , add the corresponding channel configuration, following the OpenClaw documentation format.
Q: Deploying the same project on multiple machines? A: Just run the script in a loop:
for host in user1@ip1 user2@ip2; do
./deploy.sh $host ~/projects/my_project
done
Q: How to roll back?
A: On the remote machine cd ~/projects/<name> && git checkout <commit> → rerun the script.