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

  1. Go to Discord Developer Portal
  2. Click New Application → name it (e.g. mangrove-newbot)
  3. Go to Bot tab → Reset Token → copy the token
  4. Enable intents: Message Content, Server Members, Presence
  5. Go to OAuth2 → URL Generator → select bot scope + permissions:
    • Send Messages, Read Message History, Read Messages/View Channels
  6. 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

FilePurpose
main.pyFastAPI proxy (claim, workspace serving)
provision_agents.pyGenerate private keys, write Firebase + agents.json
generate_workspaces.pyStamp out workspace dirs from templates with fake PII
generate_agent_config.pyGenerate per-agent openclaw.json (PR pending)
deploy_agents.pyClick CLI: setup/deploy/status/teardown per agent (PR pending)
generate_gateway_config.py(Legacy) multi-agent single-gateway config
bot_tokens.jsonDiscord bot tokens (gitignored)
api_keys.jsonOpenAI API keys (gitignored)
agents.jsonAgent metadata with private keys (gitignored)
agent_secrets.jsonAll PII and keys (gitignored)
workspace_templates/Template .md files with `` syntax
gateway/DockerfileMangrove gateway image derived from the official ghcr.io/openclaw/openclaw base
gateway/entrypoint.shCopies config to volume, runs doctor, starts gateway
gateway/fly.tomlFly.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)

BotHumanRole
alexbotAlex LoftusParticipant
fredbotFred HeidingParticipant
bijanbotBijanParticipant
barisbotBaris GusakalParticipant
adityabotAditya RatanParticipant
eunjeongbotEunJeong HwanParticipant
jannikbotJannik BrinkmanParticipant
woogbotAlice RiggParticipant
negevbotNegev TaglichtParticipant
giobotGiordanno RogeParticipant
charlesbotCharles YeParticipant
jasminebotJasmine CuiParticipant
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

  • guilds must be an object ({"id": {}}) not an array
  • Needs gateway.mode: "local" for headless operation
  • Container needs git installed (npm install fails without it)
  • Fly.io needs >= 2048MB RAM (512MB causes OOM)
  • Non-loopback binds require OPENCLAW_GATEWAY_TOKEN
  • Run openclaw doctor --fix before starting (handled by entrypoint.sh)
  • Use ENTRYPOINT not CMD in Dockerfile (Node base image intercepts CMD)