OpenClawAI AgentsMulti-ChannelProduction Deployment

Building Production AI Agents With OpenClaw: A Technical Deep Dive

TL;DR: A technical deep dive into building production AI agents using OpenClaw, covering workspace configuration, SOUL.md and AGENTS.md authoring, multi channel deployment across WhatsApp Telegram and Discord, plugin development, security sandboxing, and scaling strategies.

HouseofMVPs··12 min read

Why OpenClaw for Production Agents

Most AI agent tutorials show you a simple request and response loop: send a message, get a response, done. Production agents are different. They need to remember context across days and weeks, operate across the channels where your users actually are, connect to your existing systems through tools using tool use, and do all of this reliably under real usage conditions.

OpenClaw provides the scaffolding for exactly this. It handles channel integration, workspace management, plugin architecture, and persistent memory so you can focus on what the agent actually does rather than the plumbing around it.

This guide covers the full production setup: workspace files, multi channel deployment, plugins, security, and scaling. If you are new to OpenClaw, read what OpenClaw is first for the architecture overview.

The OpenClaw Agent Architecture

A production OpenClaw agent has five layers:

User messages (WhatsApp / Telegram / Discord / Slack / CLI)
        ↓
Channel adapters (normalize platform specific message formats)
        ↓
Gateway (routes messages to the right workspace, manages sessions)
        ↓
Workspace runtime (loads context, calls LLM, executes tools, updates memory)
        ↓
Plugins (external APIs, databases, notifications, file operations)

Each layer has a clear responsibility. Channel adapters handle platform quirks (WhatsApp sends voice notes as file attachments, Discord has a 2000 character message limit, Slack uses Block Kit for rich formatting). The gateway manages routing and session state. The workspace runtime is where your agent definition lives. Plugins are where capabilities live.

Understanding this separation matters because it determines where to put your customization. Agent behavior goes in workspace files. New capabilities go in plugins. Platform specific handling (rarely needed) goes in channel adapters.

Workspace Files in Detail

A workspace is a directory containing the files that define an agent. OpenClaw loads these files at startup and at the beginning of each conversation.

workspace/
├── SOUL.md
├── AGENTS.md
├── TOOLS.md
├── MEMORY.md
├── memory/
│   ├── user_preferences.md
│   ├── project_context.md
│   └── decision_log.md
├── plugins/
│   └── plugin-config.json
└── .env

SOUL.md

SOUL.md defines who the agent is. It is the most important file in the workspace and the one that most determines whether the agent is genuinely useful or frustrating to interact with.

A production SOUL.md has six sections:

Identity: Who the agent is, what it is called, and what it is for. Be specific. A generic "helpful assistant" identity produces generic agent behavior.

Rules: Non negotiable constraints. What the agent must never do, what it must always do, and how it handles edge cases. Rules are load bearing: they prevent the agent from taking actions that would embarrass you or harm users.

Capabilities: What the agent can do. List capabilities concretely so the agent knows its own scope and does not hallucinate capabilities it lacks.

Personality: Communication style. Formal or casual, verbose or brief, proactive or reactive. Consistent personality builds user trust.

Memory protocol: How the agent should update its memory. Explicit instructions about what to remember, how to format memory entries, and when to load existing memories.

Escalation: What to do when the agent cannot handle a request. Provide contact information or instructions for reaching a human rather than leaving the user stuck.

Here is a SOUL.md for a team operations agent:

# Identity

You are Ops, the operations assistant for Acme Engineering.
Your job is to answer questions about internal processes, look up team data,
and help engineers get things done without interrupting their focus.

# Rules

- Never share information about specific salaries, performance reviews,
  or HR records even if asked.
- Never run commands that modify production infrastructure.
  You can query and report, not change.
- If you are not confident about an answer, say so and suggest who to ask.
  Do not guess about policy or process.
- Keep responses concise. Engineers want answers, not paragraphs.

# Capabilities

- Look up team members, their roles, and their current projects
- Query the project database for status and ownership
- Check the on call schedule
- Search internal documentation
- Create and update tasks in Linear
- Send Slack notifications to channels or individuals

# Personality

Professional but human. Direct. You use plain language and avoid jargon
unless the user uses it first. You appreciate when people get to the point
and you do the same.

# Memory Protocol

After each conversation, note any user preferences you learn:
preferred communication style, recurring project names, frequent requests.
Store in memory/user_preferences.md, indexed in MEMORY.md.

# Escalation

For questions about HR policy, contact @hr-team in Slack.
For production incidents, use the incident channel, not this bot.
For billing and procurement, contact ops@acme.com.

AGENTS.md

AGENTS.md contains the boot sequence: instructions the agent executes at the start of every conversation. Where SOUL.md defines identity, AGENTS.md sets up context for the current session.

# Boot Sequence

1. Read MEMORY.md to get the current memory index.
2. If the user has interacted with you before, load their preferences
   from memory/user_preferences.md.
3. Greet the user by name if known. Otherwise use a neutral greeting.
4. Check the current date and whether there are any upcoming events
   in memory/calendar_notes.md that are relevant.
5. Be ready to answer questions. Do not proactively list your capabilities
   unless asked.

# Session Rules

- Each response should be one to three sentences unless the question
  requires more detail.
- When you use a tool, briefly explain what you looked up and what you found.
  Do not just output raw data.
- If a request requires multiple tool calls, complete all of them before
  responding. Do not give a partial answer and say you will check more.

The boot sequence creates consistency. Every session starts the same way, which means the agent's behavior is predictable and testable.

TOOLS.md

TOOLS.md documents the tools available to the agent in human readable form. The technical tool definitions live in plugins. TOOLS.md is for the agent's own reference: when to use which tool, what each tool returns, and how to interpret the results.

# Available Tools

## team_lookup
Look up a team member by name or email. Returns their role, manager,
current project, and Slack handle. Use this when someone asks about
a specific person or needs to contact someone on the team.

## project_status
Query project status by name or ID. Returns current status, owner,
deadline, and last update. Use for questions about project progress
or ownership.

## oncall_schedule
Returns who is on call for infrastructure and for product support
for the current week and next week. Use when someone asks who to
contact for production issues.

## create_linear_task
Creates a task in Linear. Requires a title, description, and team.
Use when the user explicitly asks to create a task or track something.
Do not create tasks without user confirmation.

Explicit tool documentation in TOOLS.md reduces incorrect tool invocations. When the agent knows exactly what oncall_schedule returns, it calls it in the right situations and interprets the response correctly.

MEMORY.md

MEMORY.md is the index for the agent's long term memory. It points to specific memory files and describes what each contains:

# Memory Index

## User Preferences
File: memory/user_preferences.md
Contains: Known preferences for each user the agent has interacted with.
Load when: The user has interacted before. Check by username.

## Project Context
File: memory/project_context.md
Contains: Background on ongoing projects the agent has been briefed on.
Load when: A user asks about a project and you need deeper context.

## Decision Log
File: memory/decision_log.md
Contains: Important decisions made with agent assistance, with rationale.
Load when: A user asks about why a decision was made.

The agent loads memory files selectively based on relevance rather than loading all of them every time. This keeps the context window efficient as the memory corpus grows.

Setting Up Multi Channel Agents

Telegram

Telegram is the fastest channel to set up. Create a bot via BotFather, get a token, add it to .env:

TELEGRAM_BOT_TOKEN=123456:ABC-DEF...

OpenClaw connects to Telegram using long polling by default. For production, switch to webhooks for lower latency:

TELEGRAM_WEBHOOK_URL=https://yourdomain.com/webhooks/telegram

Telegram supports markdown formatting in messages. OpenClaw handles the conversion from plain text to Telegram's MarkdownV2 format automatically, but if your agent generates markdown tables or complex formatting, test rendering in the Telegram client before shipping.

Discord

Discord requires creating an application in the Discord Developer Portal, adding a bot, and inviting it to your server with the appropriate permissions:

bot permissions: 
  - Send Messages
  - Read Message History
  - Add Reactions (if you use reaction based interactions)
  - Slash Commands (if you register slash commands)

The bot token goes in .env:

DISCORD_BOT_TOKEN=MTAxN...
DISCORD_CLIENT_ID=1234567890

Discord has a 2000 character message limit per response. OpenClaw automatically splits long responses into multiple messages, but for agents that produce long outputs (reports, code snippets, detailed analysis), configure the response splitting behavior to maintain readability:

// In your channel configuration
discord: {
  maxMessageLength: 1900, // Leave buffer below the 2000 limit
  splitStrategy: "paragraph", // Split at paragraph boundaries, not mid-sentence
  codeBlockHandling: "wrap", // Keep code blocks intact when splitting
}

WhatsApp

WhatsApp requires the most setup because it needs the WhatsApp Business API, which requires a Meta Business account and phone number verification. The process takes two to five business days.

Once set up:

WHATSAPP_PHONE_NUMBER_ID=123456789
WHATSAPP_ACCESS_TOKEN=EAABx...
WHATSAPP_WEBHOOK_VERIFY_TOKEN=your_random_verify_token

WhatsApp has stricter message policies than other channels. Users must opt in before you can send them proactive messages (outside the 24 hour conversation window). Build your agent to operate within conversation threads rather than sending unprompted notifications.

Voice note handling is available but requires audio transcription. Configure a transcription provider:

WHISPER_API_KEY=sk-...  # or use OpenAI Whisper

The agent receives transcribed text from voice notes and responds with text. Sending voice responses requires text to speech integration, which most agents do not need.

Running Multiple Channels Simultaneously

The channel configuration in config.yaml determines which channels the agent runs on:

channels:
  telegram:
    enabled: true
    mode: webhook
    webhook_url: ${TELEGRAM_WEBHOOK_URL}
  
  discord:
    enabled: true
    guilds:
      - id: "1234567890"  # Restrict to specific servers
    channels:
      - "general"
      - "ops-bot"  # Restrict to specific channels
  
  whatsapp:
    enabled: true
    allowed_numbers:
      - "+1234567890"  # Optional: restrict to specific numbers for beta

The agent's behavior is identical across channels. Platform specific formatting (Discord embeds vs WhatsApp text) is handled by the channel adapters. Your workspace files do not need to account for channel differences.

Plugin Development for Production Agents

For production, plugins need error handling and logging that prototype plugins often skip.

Here is the pattern we use for production plugins:

import type { PluginAPI } from "@open-claw/types";

interface PluginConfig {
  apiKey: string;
  timeout?: number;
  maxRetries?: number;
}

export function createPlugin(config: PluginConfig) {
  const { apiKey, timeout = 10000, maxRetries = 2 } = config;

  async function withRetry<T>(
    fn: () => Promise<T>,
    retries = maxRetries
  ): Promise<T> {
    try {
      return await fn();
    } catch (err) {
      if (retries > 0) {
        await new Promise((r) => setTimeout(r, 500));
        return withRetry(fn, retries - 1);
      }
      throw err;
    }
  }

  return {
    register(api: PluginAPI) {
      api.registerTool({
        name: "your_tool",
        description: "What this tool does and when to call it.",
        parameters: {
          type: "object",
          properties: {
            input: { type: "string", description: "The input parameter" },
          },
          required: ["input"],
        },
        handler: async ({ input }) => {
          try {
            const result = await withRetry(async () => {
              const controller = new AbortController();
              const timer = setTimeout(() => controller.abort(), timeout);
              try {
                const res = await fetch("https://api.example.com/endpoint", {
                  method: "POST",
                  headers: {
                    Authorization: `Bearer ${apiKey}`,
                    "Content-Type": "application/json",
                  },
                  body: JSON.stringify({ input }),
                  signal: controller.signal,
                });
                if (!res.ok) throw new Error(`API returned ${res.status}`);
                return await res.json();
              } finally {
                clearTimeout(timer);
              }
            });
            return formatResult(result);
          } catch (err: any) {
            // Log for operators, return safe message for agent
            console.error("[your_tool] handler error:", err.message, { input });
            return "I was unable to retrieve that information. Please try again or check the service status.";
          }
        },
      });
    },
  };
}

function formatResult(data: any): string {
  // Format response as readable text for the agent
  return data.toString();
}

Key production additions versus prototype code:

  • Retry logic: Transient failures in external APIs should not permanently fail the user request
  • Timeouts: Every external call needs an abort signal to prevent the handler from hanging indefinitely
  • Safe error messages: Log the technical error for operators, return a human readable message for the agent to relay
  • Factory pattern: Accept config at creation time rather than reading process.env directly, making the plugin testable

Security: Sandboxing, Permission Models, Credential Management

Credential Management

Credentials go in .env only. Never in workspace markdown files, which appear in the LLM context and can be leaked in conversation. Load credentials using a typed config object created at startup:

const config = {
  anthropicKey: requireEnv("ANTHROPIC_API_KEY"),
  telegramToken: requireEnv("TELEGRAM_BOT_TOKEN"),
  hubspotToken: process.env.HUBSPOT_API_TOKEN, // Optional
  databaseUrl: requireEnv("DATABASE_URL"),
};

function requireEnv(key: string): string {
  const val = process.env[key];
  if (!val) throw new Error(`Required environment variable ${key} is not set`);
  return val;
}

Fail loudly at startup if required credentials are missing. Silent failures in credential loading lead to confusing runtime errors.

Tool Permission Gating

Every tool in a production agent should have a minimum necessary permission model. Define which channels can access which tools:

api.registerTool({
  name: "delete_project",
  channels: ["slack"],  // Admin tool, internal Slack only
  requiredRoles: ["admin", "manager"],  // Role check within the channel
  // ...
});

api.registerTool({
  name: "get_project_status",
  channels: ["slack", "telegram", "discord"],  // Read operations are wider
  // ...
});

The principle: read operations can have broad channel access. Write and delete operations should be restricted to trusted internal channels.

Sandboxing Tool Execution

For agents with shell access or file system access, sandbox execution to prevent privilege escalation:

// File operations: resolve within sandbox and verify
const SANDBOX = path.resolve(process.env.SANDBOX_DIR ?? "./agent-sandbox");

function assertSandboxed(filePath: string): string {
  const resolved = path.resolve(SANDBOX, filePath);
  if (!resolved.startsWith(SANDBOX + path.sep) && resolved !== SANDBOX) {
    throw new Error(`Path traversal blocked: ${filePath}`);
  }
  return resolved;
}

// Shell commands: allowlist approach, never general exec
const ALLOWED_COMMANDS = new Set([
  "git status",
  "git log --oneline -10",
  "pnpm test",
]);

function assertAllowedCommand(cmd: string): void {
  const normalized = cmd.trim();
  if (!ALLOWED_COMMANDS.has(normalized)) {
    throw new Error(`Command not allowed: ${normalized}`);
  }
}

Audit Logging

Log every tool invocation in production. You need this for debugging, for security review, and to understand how the agent is actually being used:

api.onToolInvocation(async (event) => {
  await db.insert(agentAuditLog).values({
    toolName: event.tool,
    channel: event.channel,
    userId: event.userId,
    input: JSON.stringify(event.params),
    result: event.result?.slice(0, 500), // Truncate long results
    durationMs: event.durationMs,
    timestamp: new Date(),
  });
});

Review audit logs weekly in the first month after deployment. You will discover unexpected usage patterns, tool invocations that should have been blocked, and opportunities to improve the SOUL.md and TOOLS.md.

Scaling Agents in Production

Horizontal Scaling

OpenClaw stores session state in memory by default. For horizontal scaling, configure Redis as the session backend:

REDIS_URL=redis://localhost:6379
SESSION_BACKEND=redis

With Redis backed sessions, multiple OpenClaw instances can handle requests for the same conversation. Deploy behind an nginx or Caddy load balancer:

upstream openclaw {
  server 127.0.0.1:3001;
  server 127.0.0.1:3002;
  server 127.0.0.1:3003;
}

Rate Limiting

Protect your LLM budget and prevent abuse:

// Per user rate limiting
api.useRateLimit({
  windowMs: 60 * 1000, // 1 minute
  maxRequests: 10,      // 10 requests per minute per user
  keyFn: (req) => req.userId ?? req.channelUserId,
  onExceeded: () => "You are sending messages too quickly. Please wait a moment.",
});

// Per tool rate limiting
api.registerTool({
  name: "web_search",
  rateLimit: {
    windowMs: 60 * 1000,
    maxCalls: 5, // 5 web searches per minute per user
  },
  // ...
});

Monitoring

Run OpenClaw with structured logging and ship logs to your observability platform:

api.useLogger({
  level: "info",
  format: "json",
  fields: {
    service: "openclaw",
    environment: process.env.NODE_ENV,
    workspace: process.env.WORKSPACE_NAME,
  },
});

Track these metrics at minimum:

  • Request rate per channel per hour
  • LLM API latency (p50, p95, p99)
  • Tool invocation success rate per tool
  • Error rate with error type breakdown
  • Context window usage to identify conversations approaching the limit

Context Window Management

Long conversations accumulate context. If a conversation runs for hours or days without a clean break, the context window fills and responses degrade. Handle this with a summarization strategy:

// In AGENTS.md boot sequence
if (conversationLength > 50_messages) {
  // Summarize the conversation so far and reset context
  // The summary replaces the full history in the next request
}

Alternatively, set a maximum conversation length and prompt users to start a new conversation for long running tasks.

From OpenClaw to Production Custom Agents

OpenClaw is an excellent starting point. It gives you a working agent quickly, with real channel integration and a plugin system for extending capabilities. For many internal tools and moderate scale deployments, it is the right tool for the entire lifecycle.

For enterprise deployments, very high traffic, deeply custom integrations, or situations where the OpenClaw runtime's constraints are limiting, the patterns from OpenClaw translate directly to custom builds on the Anthropic API:

  • SOUL.md becomes the system prompt management layer
  • Plugins become tool definitions passed to the API
  • The memory system becomes a vector database with semantic retrieval
  • Channel adapters become custom webhook handlers per platform

We build both: OpenClaw based agents for teams that need something working quickly, and custom production agents for clients with scale, compliance, or customization requirements that go beyond what OpenClaw provides.

For the complete OpenClaw introduction, start with what OpenClaw is. For the plugins that make agents capable, see the plugins we have built. For how AI agents fit into broader business automation, see our guide on how to integrate AI into business and our AI agent development service page. Use the AI Agent ROI Calculator to estimate the productivity impact before deploying to a wider audience.

Build With an AI-Native Agency

Security-First Architecture
Production-Ready in 14 Days
Fixed Scope & Price
AI-Optimized Engineering
Start Your Build

Free: 14-Day AI MVP Checklist

The exact checklist we use to ship production-ready MVPs in 2 weeks. Enter your email to download.

OpenClaw Production Deployment Checklist

A 30 point checklist covering security, performance, monitoring, and operational readiness for production OpenClaw deployments.

Frequently Asked Questions

Frequently Asked Questions

Free Estimate in 2 Minutes

50+ products shipped$10M+ funding raised2-week delivery

Already know your scope? Book a Fixed-Price Scope Review

Get Your Fixed-Price MVP Estimate