Skill: abhinas90/multi-agent-deployment (version 1.0.1)

--- name: Multi-Agent Deployment Skill for OpenClaw slug: multi-agent-deployment version: 1.0.1 description: "Deploy a production-ready multi-agent fleet in OpenClaw. Includes step-by-step setup guide, workspace templates, and Python automation scripts for agent creation, routing config, memory sync, and cloud deployment — based on a real working 4-agent production setup." --- ## What This Skill Does Guides you through deploying 3-5 specialized AI agents in OpenClaw that work as a coordinated fleet. Based on a real production setup running on a Hostinger VPS with Docker. ## Included Files | File | Purpose | |------|---------| | `agent_setup.py` | Creates workspace directory structure for any number of agents | | `routing_config.py` | Generates openclaw.json agent entries with model routing and fallbacks | | `memory_sync.py` | Syncs Cross-Agent Intel sections across all agent MEMORY.md files | | `deploy.sh` | Uploads workspace files to VPS and restarts the container | ## Step-by-Step Setup ### 1. Create Workspace Structure ```bash python3 agent_setup.py --agents pat scout publisher builder --base /data/.openclaw ``` Creates `workspace-{agent}/` with `SOUL.md`, `MEMORY.md`, `drafts/`, `skills/`, `.claude/settings.json`, `.claudeignore`. ### 2. Define Each Agent's Role Edit each `workspace-{agent}/SOUL.md`: - Set the agent's mission and responsibilities - Define which tools it uses - Add hard limits and escalation rules ### 3. Generate Routing Config ```bash # Preview output python3 routing_config.py --agents main scout publisher builder # Write directly to openclaw.json python3 routing_config.py --agents main scout publisher builder \ --output /data/.openclaw/openclaw.json ``` Configures model routing with OpenRouter fallbacks (minimax → deepseek → kimi). ### 4. Set Up Cron Jobs Add to your `cron/jobs.json` for each agent: ```json { "name": "Agent: Daily Run", "agentId": "scout", "schedule": { "expr": "0 10 * * *" }, "enabled": true } ``` ### 5. Deploy to VPS ```bash bash deploy.sh --vps root@your-vps-ip --key ~/.ssh/your_key ``` ### 6. Sync Agent Memory Run nightly or manually to propagate cross-agent intelligence: ```bash python3 memory_sync.py --base /data/.openclaw --agents pat scout publisher builder ``` ## Architecture Pattern ``` Coordinator (main) — always-on Telegram, approval queue, briefings ├── Scout — market intel, inbound monitoring, trends ├── Publisher — content drafts for Twitter/LinkedIn/video └── Builder — skill development, marketplace research ``` Each agent has: - Isolated workspace with its own SOUL.md and memory - Separate cron schedule - Model routing with fallbacks via OpenRouter - Shared memory sync via Cross-Agent Intel ## Requirements - OpenClaw running on a VPS (Docker) - OpenRouter API key (for model routing) - SSH access to your VPS ## What Makes This Different - **Real production patterns** — not examples, this is a live setup - **Isolation by design** — each agent has its own workspace and memory - **Fallback routing** — agents keep running if a model goes down - **Memory persistence** — agents remember context across sessions and compaction

path: agent_setup.py
content: #!/usr/bin/env python3
"""
agent_setup.py — Create OpenClaw agent workspace directory structure.
Usage: python3 agent_setup.py --agents pat scout publisher builder --base /data/.openclaw
"""
import os
import json
import argparse
import datetime

SOUL_TEMPLATE = """# Soul

You are {name}, an AI agent in a multi-agent OpenClaw fleet.

## Mission
[Define this agent's specific role and responsibilities]

## Every Session
1. Read SOUL.md
2. Read USER.md
3. MEMORY.md loads automatically
4. Read latest memory/YYYY-MM-DD.md
5. Check drafts/ for pending items

## Hard Limits
- NEVER act without explicit approval for public-facing actions
- NEVER fabricate data or metrics
"""

MEMORY_TEMPLATE = """Last updated: {date}

## Active Priorities

## Key Decisions

## Lessons Learned

## Cross-Agent Intel
"""

SETTINGS = {
    "hooks": {
        "internal": {"enabled": True},
        "session-memory": {"enabled": True, "messages": 25},
        "bootstrap-extra-files": {"enabled": True, "paths": ["memory/*.md"]}
    }
}

CLAUDEIGNORE = "*.db\n*.jsonl\narchive-v1/\n*.bak*\n*.pyc\n__pycache__/\n*.log\n"

def create_workspace(base_path, agent_name):
    ws = os.path.join(base_path, f"workspace-{agent_name.lower()}")
    for d in [ws, os.path.join(ws, "memory"), os.path.join(ws, "drafts"),
              os.path.join(ws, "skills"), os.path.join(ws, ".claude")]:
        os.makedirs(d, exist_ok=True)
        print(f"  mkdir: {d}")

    today = datetime.date.today().isoformat()
    files = {
        "SOUL.md": SOUL_TEMPLATE.format(name=agent_name),
        "memory/MEMORY.md": MEMORY_TEMPLATE.format(date=today),
        ".claude/settings.json": json.dumps(SETTINGS, indent=2),
        ".claudeignore": CLAUDEIGNORE,
    }
    for fname, content in files.items():
        path = os.path.join(ws, fname)
        with open(path, "w") as f:
            f.write(content)
        print(f"  wrote: {fname}")
    print(f"  done: {ws}")

def main():
    parser = argparse.ArgumentParser(description="Create OpenClaw agent workspaces")
    parser.add_argument("--agents", nargs="+", required=True)
    parser.add_argument("--base", default="/data/.openclaw")
    args = parser.parse_args()
    for agent in args.agents:
        print(f"\nSetting up: {agent}")
        create_workspace(args.base, agent)
    print("\nDone. Edit each SOUL.md to define agent missions.")

if __name__ == "__main__":
    main()


path: deploy.sh
content: #!/bin/bash
# deploy.sh — Upload agent workspaces to VPS and restart OpenClaw container.
# Usage: bash deploy.sh --vps root@your-vps-ip --key ~/.ssh/your_key --data /docker/openclaw/data

set -e

VPS=""
KEY="~/.ssh/id_rsa"
VPS_DATA="/docker/openclaw-pkpz/data/.openclaw"
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"

while [[ $# -gt 0 ]]; do
    case $1 in
        --vps)  VPS="$2"; shift 2 ;;
        --key)  KEY="$2"; shift 2 ;;
        --data) VPS_DATA="$2"; shift 2 ;;
        *) echo "Unknown option: $1"; exit 1 ;;
    esac
done

if [ -z "$VPS" ]; then echo "Error: --vps required"; exit 1; fi

SSH="ssh -i $KEY $VPS"
SCP="scp -i $KEY"

echo "=== OpenClaw Multi-Agent Deployment ==="
echo "VPS: $VPS | Data: $VPS_DATA"

echo ""
echo "--- Creating workspace directories ---"
$SSH "mkdir -p $VPS_DATA/workspace-{pat,scout,publisher,builder}/{memory,drafts,skills,.claude}"

echo ""
echo "--- Uploading workspace files ---"
for agent in pat scout publisher builder; do
    if [ -d "$LOCAL_DIR/workspace-$agent" ]; then
        echo "  $agent..."
        for f in SOUL.md IDENTITY.md USER.md AGENTS.md MEMORY.md .claudeignore; do
            [ -f "$LOCAL_DIR/workspace-$agent/$f" ] && \
                $SCP "$LOCAL_DIR/workspace-$agent/$f" "$VPS:$VPS_DATA/workspace-$agent/$f"
        done
        [ -f "$LOCAL_DIR/workspace-$agent/.claude/settings.json" ] && \
            $SCP "$LOCAL_DIR/workspace-$agent/.claude/settings.json" \
                 "$VPS:$VPS_DATA/workspace-$agent/.claude/settings.json"
    fi
done

echo ""
echo "--- Uploading utility scripts ---"
for script in agent_setup.py routing_config.py memory_sync.py; do
    [ -f "$LOCAL_DIR/$script" ] && $SCP "$LOCAL_DIR/$script" "$VPS:$VPS_DATA/$script"
done

echo ""
echo "--- Restarting container ---"
CONTAINER=$($SSH "docker ps --filter name=openclaw --format '{{.Names}}' | head -1")
$SSH "docker restart $CONTAINER"
echo "Restarted: $CONTAINER"

echo ""
echo "=== Done ==="


path: memory_sync.py
content: #!/usr/bin/env python3
"""
memory_sync.py — Sync Cross-Agent Intel sections across all agent MEMORY.md files.
Usage: python3 memory_sync.py --base /data/.openclaw --agents pat scout publisher builder
"""
import os
import re
import argparse
import datetime

def read_section(path, section_name):
    if not os.path.exists(path):
        return ""
    content = open(path).read()
    pattern = rf"## {re.escape(section_name)}\n(.*?)(?=\n## |\Z)"
    m = re.search(pattern, content, re.DOTALL)
    return m.group(1).strip() if m else ""

def write_section(path, section_name, new_content):
    if not os.path.exists(path):
        print(f"  SKIP (not found): {path}")
        return
    content = open(path).read()
    new_block = f"## {section_name}\n{new_content}\n"
    pattern = rf"## {re.escape(section_name)}\n.*?(?=\n## |\Z)"
    if re.search(pattern, content, re.DOTALL):
        content = re.sub(pattern, new_block.rstrip(), content, flags=re.DOTALL)
    else:
        content = content.rstrip() + f"\n\n{new_block}"
    with open(path, "w") as f:
        f.write(content)
    print(f"  Updated: {path}")

def sync(base, agents):
    today = datetime.date.today().isoformat()
    intel_parts = []
    for agent in agents:
        mem_path = os.path.join(base, f"workspace-{agent}", "MEMORY.md")
        intel = read_section(mem_path, "Cross-Agent Intel")
        if intel and intel.strip() not in ("", "—"):
            intel_parts.append(f"### From {agent} ({today})\n{intel}")

    if not intel_parts:
        print("No Cross-Agent Intel found to sync.")
        return

    combined = "\n\n".join(intel_parts)
    for agent in agents:
        mem_path = os.path.join(base, f"workspace-{agent}", "MEMORY.md")
        write_section(mem_path, "Cross-Agent Intel", combined)
    print(f"\nSynced intel across {len(agents)} agents.")

def main():
    parser = argparse.ArgumentParser(description="Sync cross-agent memory")
    parser.add_argument("--base", default="/data/.openclaw")
    parser.add_argument("--agents", nargs="+", default=["pat","scout","publisher","builder"])
    args = parser.parse_args()
    sync(args.base, args.agents)

if __name__ == "__main__":
    main()


path: routing_config.py
content: #!/usr/bin/env python3
"""
routing_config.py — Generate openclaw.json agent entries for a multi-agent fleet.
Usage: python3 routing_config.py --output /data/.openclaw/openclaw.json
"""
import json
import argparse
import os

AGENT_TEMPLATES = {
    "main": {
        "name": "main", "agentId": "main",
        "workspace": "/data/.openclaw/workspace-pat",
        "model": {
            "primary": "openrouter/minimax/minimax-m2.5",
            "fallbacks": ["openrouter/deepseek/deepseek-v3.2", "openrouter/moonshotai/kimi-k2.5"]
        }
    },
    "scout": {
        "name": "Scout", "agentId": "scout",
        "workspace": "/data/.openclaw/workspace-scout",
        "model": {
            "primary": "openrouter/deepseek/deepseek-v3.2",
            "fallbacks": ["openrouter/minimax/minimax-m2.5", "openrouter/moonshotai/kimi-k2.5"]
        }
    },
    "publisher": {
        "name": "Publisher", "agentId": "publisher",
        "workspace": "/data/.openclaw/workspace-publisher",
        "model": {
            "primary": "openrouter/minimax/minimax-m2.5",
            "fallbacks": ["openrouter/deepseek/deepseek-v3.2", "openrouter/moonshotai/kimi-k2.5"]
        }
    },
    "builder": {
        "name": "Builder", "agentId": "builder",
        "workspace": "/data/.openclaw/workspace-builder",
        "model": {
            "primary": "openrouter/deepseek/deepseek-v3.2",
            "fallbacks": ["openrouter/minimax/minimax-m2.5", "openrouter/moonshotai/kimi-k2.5"]
        }
    }
}

CRON_TEMPLATES = [
    {"name": "Morning Briefing", "agentId": "main", "schedule": {"expr": "0 7 * * *"}},
    {"name": "Evening Summary",  "agentId": "main", "schedule": {"expr": "0 20 * * *"}},
    {"name": "Scout: Marketplace", "agentId": "scout", "schedule": {"expr": "0 10 * * *"}},
    {"name": "Scout: Inbound",     "agentId": "scout", "schedule": {"expr": "0 16 * * *"}},
    {"name": "Publisher: Tweet",   "agentId": "publisher", "schedule": {"expr": "0 8 * * *"}},
    {"name": "Publisher: Content", "agentId": "publisher", "schedule": {"expr": "0 9 * * 1,3,5"}},
    {"name": "Publisher: Video",   "agentId": "publisher", "schedule": {"expr": "0 10 * * 2,4"}},
    {"name": "Builder: Daily",     "agentId": "builder",   "schedule": {"expr": "0 10 * * *"}},
]

def generate(agents, output_path):
    config = {"agents": {"list": [AGENT_TEMPLATES[a] for a in agents if a in AGENT_TEMPLATES]}}
    if output_path:
        if os.path.exists(output_path):
            existing = json.load(open(output_path))
            existing["agents"]["list"] = config["agents"]["list"]
            config = existing
        with open(output_path, "w") as f:
            json.dump(config, f, indent=2)
        print(f"Written to {output_path}")
    else:
        print(json.dumps(config, indent=2))

def main():
    parser = argparse.ArgumentParser(description="Generate OpenClaw routing config")
    parser.add_argument("--agents", nargs="+", default=["main","scout","publisher","builder"])
    parser.add_argument("--output", default=None, help="Path to openclaw.json (prints if omitted)")
    args = parser.parse_args()
    generate(args.agents, args.output)

if __name__ == "__main__":
    main()


path: skill.json
content: {
  "slug": "multi-agent-deployment",
  "name": "Multi-Agent Deployment Skill for OpenClaw",
  "version": "1.0.1",
  "description": "A comprehensive skill that guides users through setting up multi-agent fleets in OpenClaw, similar to Abhi's own production setup. This skill packages proven patterns for deploying specialized AI agents with distinct responsibilities, workspaces, and routing.",
  "author": "Abhinas Neupane",
  "tags": [
    "agents",
    "multi-agent",
    "deployment",
    "openclaw",
    "ai"
  ],
  "pricing": {
    "type": "one-time",
    "value": 25
  }
}

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal:  | SkillSpector: suspicious (CRITICAL)