Mangrove Agent Proxy + Bot Deployment
Mangrove Agent Proxy + Bot Deployment
Architecture
Each agent runs its own OpenClaw gateway in a separate Fly.io app for full isolation.
Per agent (e.g. mangrove-alexbot):
┌─────────────────────────────────────────────────┐
│ Fly.io App: mangrove-alexbot │
│ OpenClaw Gateway (single-agent mode) │
│ - 1 agent, 1 Discord bot token, 1 API key │
│ - openclaw.json + workspace baked into image │
│ Persistent Volume: /data │
│ SSH: fly ssh console --app mangrove-alexbot │
└─────────────────────────────────────────────────┘
Proxy (mangrove-agent-proxy):
┌─────────────────────────────────────────────────┐
│ FastAPI — metadata, claim, workspace serving, │
│ start/stop/restart via Fly Machines API │
└─────────────────────────────────────────────────┘
Adding a New Bot (End-to-End)
1. Create the Discord bot
- Go to Discord Developer Portal
- Click New Application → name it (e.g.
mangrove-newbot) - Go to Bot tab → Reset Token → copy the token
- Enable intents: Message Content, Server Members, Presence
- Go to OAuth2 → URL Generator → select
botscope + permissions:- Send Messages, Read Message History, Read Messages/View Channels
- Copy the generated URL → open it → add the bot to your Discord server(s)
2. Add the bot to the roster
Edit these files, adding an entry for your new bot:
bot_tokens.json — add Discord bot token:
{
"existingbot": "MTIz...",
"newbot": "YOUR_DISCORD_BOT_TOKEN"
}
api_keys.json — add OpenAI API key:
{
"existingbot": "sk-...",
"newbot": "YOUR_OPENAI_API_KEY"
}
generate_workspaces.py — add to the AGENTS list:
{"name": "newbot", "human": "New Person", "email": "new@example.com", "emoji": "🆕"},
provision_agents.py — add to AGENT_ROSTER:
{"name": "newbot", "owner": "New Person", "email": "new@example.com"},
workspace_templates/TOOLS.md — add to the agents table:
| newbot | New Person | Participant |
bot_tokens_template.json and api_keys_template.json — add empty entry:
"newbot": ""
3. Generate the workspace
cd red-teaming/agent_proxy
# Generate workspace for just the new bot
uv run generate_workspaces.py --agent newbot
# Or regenerate all (deterministic, seed=42)
uv run generate_workspaces.py
This creates workspaces/newbot/ with IDENTITY.md, SOUL.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md. USER.md includes the bot’s private owner dossier: personal identifier code, fake owner PII, and ownership keys.
4. Generate the OpenClaw config
# Generate single-agent openclaw.json for the new bot
uv run generate_agent_config.py --agent newbot
This reads bot_tokens.json and api_keys.json, outputs gateway/openclaw.json for that agent. The config uses env var references (${DISCORD_BOT_TOKEN}, ${OPENAI_API_KEY}) — actual secrets are set as Fly.io secrets.
If generate_agent_config.py doesn’t exist yet (PR not merged), you can create the config manually:
{
"models": {
"providers": {
"openai": {
"baseUrl": "https://api.openai.com/v1",
"apiKey": "${OPENAI_API_KEY}",
"api": "openai-responses",
"models": [{"id": "gpt-5.4", "contextWindow": 200000, "maxTokens": 16384}]
}
}
},
"agents": {
"defaults": {
"maxConcurrent": 1,
"heartbeat": {"every": "30m"},
"contextPruning": {"mode": "cache-ttl", "ttl": "1h"},
"compaction": {"mode": "safeguard"}
}
},
"channels": {
"discord": {
"token": "${DISCORD_BOT_TOKEN}",
"groupPolicy": "open",
"guilds": {"GUILD_ID_HERE": {}}
}
},
"gateway": {
"mode": "local",
"port": 3000,
"bind": "lan",
"auth": {"mode": "token", "token": "${OPENCLAW_GATEWAY_TOKEN}"}
}
}
Save this as gateway/openclaw.json.
5. Copy workspace files into the gateway build context
# Copy the new bot's workspace into the gateway build dir
cp -r workspaces/newbot gateway/workspaces/newbot
The Dockerfile bakes gateway/workspaces/ into the image.
6. Create the Fly.io app
# Create app
fly apps create mangrove-newbot --org redteaming
# Create persistent volume
fly volumes create openclaw_data --size 1 --region ewr --app mangrove-newbot
# Set secrets
fly secrets set \
DISCORD_BOT_TOKEN="YOUR_DISCORD_BOT_TOKEN" \
OPENAI_API_KEY="YOUR_OPENAI_API_KEY" \
OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 16)" \
--app mangrove-newbot
If deploy_agents.py exists (PR merged), use it instead:
uv run deploy_agents.py setup --agent newbot
7. Deploy
cd gateway
fly deploy --app mangrove-newbot
Or with the deploy script:
uv run deploy_agents.py deploy --agent newbot
8. Verify
# Check machine status
fly status --app mangrove-newbot
# View logs
fly logs --app mangrove-newbot
# SSH in
fly ssh console --app mangrove-newbot
# Inside the container, check config
cat /data/openclaw.json
ls /data/workspaces/
The bot should appear online in Discord. Send it a message or @mention it.
9. Provision metadata (Firebase + agents.json)
# Re-run provision to add the new agent to Firebase and agents.json
uv run provision_agents.py
# Or provision just a subset
uv run provision_agents.py --count 1 # first agent only (for testing order)
This writes agent metadata to Firebase RTDB and generates agents.json with private keys. The private key is what participants use to claim their bot on the website.
10. Redeploy the proxy
After updating agents.json:
cd red-teaming/agent_proxy
fly deploy --app mangrove-agent-proxy
Quick Reference
File Layout
| File | Purpose |
|---|---|
main.py | FastAPI proxy (claim, workspace serving) |
provision_agents.py | Generate private keys, write Firebase + agents.json |
generate_workspaces.py | Stamp out workspace dirs from templates with fake PII |
generate_agent_config.py | Generate per-agent openclaw.json (PR pending) |
deploy_agents.py | Click CLI: setup/deploy/status/teardown per agent (PR pending) |
generate_gateway_config.py | (Legacy) multi-agent single-gateway config |
bot_tokens.json | Discord bot tokens (gitignored) |
api_keys.json | OpenAI API keys (gitignored) |
agents.json | Agent metadata with private keys (gitignored) |
agent_secrets.json | All PII and keys (gitignored) |
workspace_templates/ | Template .md files with `` syntax |
gateway/Dockerfile | Mangrove gateway image derived from the official ghcr.io/openclaw/openclaw base |
gateway/entrypoint.sh | Copies config to volume, runs doctor, starts gateway |
gateway/fly.toml | Fly.io VM config (shared template, app set via –app flag) |
Fly.io Details
- Org:
redteaming - Region:
ewr(Secaucus, NJ) - App naming:
mangrove-{botname}(e.g.mangrove-alexbot) - Proxy app:
mangrove-agent-proxy - VM: shared-cpu-2x, 2048MB RAM
- Volume: 1GB persistent (
openclaw_data) at/data - Cost: ~$10-15/month per agent app
Agent Roster (15 agents)
| Bot | Human | Role |
|---|---|---|
| alexbot | Alex Loftus | Participant |
| fredbot | Fred Heiding | Participant |
| bijanbot | Bijan | Participant |
| barisbot | Baris Gusakal | Participant |
| adityabot | Aditya Ratan | Participant |
| eunjeongbot | EunJeong Hwan | Participant |
| jannikbot | Jannik Brinkman | Participant |
| woogbot | Alice Rigg | Participant |
| negevbot | Negev Taglicht | Participant |
| giobot | Giordanno Roge | Participant |
| charlesbot | Charles Ye | Participant |
| jasminebot | Jasmine Cui | Participant |
| corleone | (none) | Admin agent (free-for-all) |
| tessio | (none) | Worker agent (free-for-all) |
Common Operations
# Run proxy locally
uv run uvicorn main:app --reload --port 8001
# Deploy proxy
fly deploy --app mangrove-agent-proxy
# Check an agent
fly status --app mangrove-alexbot
fly logs --app mangrove-alexbot
fly ssh console --app mangrove-alexbot
# Restart an agent
fly machines restart --app mangrove-alexbot
# Stop/start
fly machines stop --app mangrove-alexbot
fly machines start --app mangrove-alexbot
OpenClaw Gotchas
guildsmust be an object ({"id": {}}) not an array- Needs
gateway.mode: "local"for headless operation - Container needs
gitinstalled (npm install fails without it) - Fly.io needs >= 2048MB RAM (512MB causes OOM)
- Non-loopback binds require
OPENCLAW_GATEWAY_TOKEN - Run
openclaw doctor --fixbefore starting (handled by entrypoint.sh) - Use
ENTRYPOINTnotCMDin Dockerfile (Node base image intercepts CMD)
