Table of Contents

OpenClaw Integration

OpenClaw is an open-source multi-AI gateway that orchestrates conversations across different AI providers and messaging platforms. The JD.AI ↔ OpenClaw integration connects the two gateways via the OpenClawBridgeChannel, enabling agents on either platform to communicate, share context, and route messages across boundaries.

OpenClaw routing visibility in the JD.AI dashboard

Why integrate with OpenClaw

Scenario Without integration With integration
Multi-gateway routing Each gateway manages its own channels Messages flow between gateways
Provider diversity Limited to one gateway's providers Combine both provider ecosystems
Platform coverage Each gateway's channels only Span platforms connected to either gateway
Failover Single point of failure Route through alternate gateway

Architecture

┌─────────────────────────────────────────────────┐
│                 JD.AI Gateway                    │
│                                                  │
│  ┌──────────┐ ┌──────────┐ ┌─────────────────┐  │
│  │ Discord  │ │ Telegram │ │ OpenClaw Bridge │──┼──┐
│  │ Channel  │ │ Channel  │ │    Channel      │  │  │
│  └────┬─────┘ └────┬─────┘ └────────┬────────┘  │  │
│       │             │                │           │  │
│  ┌────▼─────────────▼────────────────▼────────┐  │  │
│  │              Channel Registry              │  │  │
│  └────────────────────┬───────────────────────┘  │  │
│                       │                          │  │  HTTP
│  ┌────────────────────▼───────────────────────┐  │  │  (poll + post)
│  │              Agent Pool Service            │  │  │
│  └────────────────────────────────────────────┘  │  │
└──────────────────────────────────────────────────┘  │
                                                      │
┌─────────────────────────────────────────────────────▼──┐
│                   OpenClaw Gateway                      │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐               │
│  │  Signal  │ │  Slack   │ │  Matrix  │  ...           │
│  └──────────┘ └──────────┘ └──────────┘               │
└─────────────────────────────────────────────────────────┘

Communication protocol

  • Outbound (JD.AI → OpenClaw): POST /api/messages with channel, content, sender, and metadata
  • Inbound (OpenClaw → JD.AI): GET /api/messages?since={timestamp}&channel={source} polled at configurable interval

Messages include origin metadata to prevent routing loops:

{
  "channel": "jdai-outbound",
  "content": "Here is the code review...",
  "sender": "jdai",
  "metadata": {
    "source": "jdai-gateway",
    "original_channel": "discord-123456"
  }
}

Bridge hijack vs native channels

JD.AI supports two distinct integration shapes:

Path Transport owner Who answers user messages Best fit
Native channel adapters (Discord/Signal/Slack/Telegram/Web) JD.AI JD.AI agent mapped to that channel Primary JD.AI deployment
OpenClaw bridge + Passthrough OpenClaw OpenClaw agent (JD.AI observes) Keep OpenClaw as primary runtime
OpenClaw bridge + Intercept/Proxy OpenClaw transport, JD.AI execution JD.AI (OpenClaw run is aborted or bypassed) "Hijack" mode where JD.AI should answer
OpenClaw bridge + Sidecar Shared OpenClaw by default, JD.AI on trigger Mixed mode with selective JD.AI takeover

Operationally, "hijacking OpenClaw" means using Intercept or Proxy so JD.AI generates the assistant response while OpenClaw remains the session transport.

Detecting active override mode

Use gateway status endpoints to determine whether JD.AI is currently overriding OpenClaw traffic:

  • GET /api/gateway/status
  • GET /api/gateway/openclaw/status

Both endpoints now include:

  • openClaw.defaultMode
  • openClaw.overrideActive
  • openClaw.overrideChannels

If overrideActive=true, at least one routed path is running in Intercept, Proxy, or Sidecar (not pure Passthrough).

Fast operator controls

Use daemon commands for quick bridge control without manually editing JSON:

jdai-daemon bridge status
jdai-daemon bridge enable
jdai-daemon bridge disable
jdai-daemon bridge passthrough

Fast switching and handoff commands

You can switch routing/provider behavior at runtime without restarting either gateway.

Native channels (Discord/Slack/Signal)

Use native channel commands:

jdai-routes
jdai-route
jdai-route <agent-or-provider-or-model-fragment>
jdai-provider
jdai-provider <provider-name>
jdai-providers

Examples:

jdai-route ollama
jdai-provider openai
jdai-routes

OpenClaw bridge sessions

Inside an OpenClaw conversation, send /jdai-... commands. The bridge intercepts them, executes in JD.AI, and injects the result back into the session:

/jdai-help
/jdai-routes
/jdai-route ollama
/jdai-provider openai
/jdai-status

Discord model bang commands (!model ..., including mention+bang forms) are supported in bridge sessions for compatibility.

Primary architecture note: direct Discord gateway integration is the canonical runtime path for this behavior. OpenClaw reuses the same shared gateway command dispatcher so command semantics stay consistent.

See Channels for the primary direct-Discord behavior and wiring.

Practical switching behavior

  • In Sidecar mode, plain messages continue to OpenClaw; /jdai-... commands and configured triggers route to JD.AI.
  • In Intercept/Proxy mode, JD.AI is the active responder for routed messages.
  • In Passthrough mode, OpenClaw remains responder; use /jdai-... commands for targeted control operations.

Setup

Prerequisites

  • A running JD.AI Gateway instance
  • A running OpenClaw instance with HTTP API enabled
  • Network connectivity between the two gateways

Step 1: Configure OpenClaw channels

Create two channels on the OpenClaw side:

  • jdai-inbound — OpenClaw posts messages here for JD.AI
  • jdai-outbound — JD.AI posts messages here for OpenClaw

Step 2: Register the bridge

Option A: DI registration

builder.Services.AddOpenClawBridge(config =>
{
    config.BaseUrl = "http://openclaw-host:3000";
    config.InstanceName = "production";
    config.ApiKey = "your-openclaw-api-key";
    config.TargetChannel = "jdai-outbound";
    config.SourceChannel = "jdai-inbound";
    config.PollIntervalMs = 1000;
});

Option B: Gateway configuration

{
  "Gateway": {
    "Channels": [
      {
        "Type": "openclaw",
        "Name": "OpenClaw Production",
        "Settings": {
          "BaseUrl": "http://openclaw-host:3000",
          "InstanceName": "production",
          "ApiKey": "your-openclaw-api-key",
          "TargetChannel": "jdai-outbound",
          "SourceChannel": "jdai-inbound",
          "PollIntervalMs": "1000"
        }
      }
    ]
  }
}

Step 3: Connect and verify

curl -X POST http://localhost:18789/api/channels/openclaw/connect \
     -H "X-API-Key: your-jdai-key"

curl http://localhost:18789/api/channels \
     -H "X-API-Key: your-jdai-key"

Step 4: Route messages to an agent

var registry = app.Services.GetRequiredService<IChannelRegistry>();
var pool = app.Services.GetRequiredService<AgentPoolService>();
var openClaw = registry.GetChannel("openclaw")!;

openClaw.MessageReceived += async (msg) =>
{
    var response = await pool.SendMessageAsync(targetAgentId, msg.Content);
    if (response is not null)
        await openClaw.SendMessageAsync(msg.ChannelId, response);
};

Configuration reference

Property Type Default Description
BaseUrl string http://localhost:3000 OpenClaw HTTP API base URL
InstanceName string local Friendly name for this instance
ApiKey string? null API key for OpenClaw authentication
TargetChannel string default Channel for outbound messages (JD.AI → OpenClaw)
SourceChannel string default Channel for inbound messages (OpenClaw → JD.AI)
PollIntervalMs int 1000 Milliseconds between inbound polls

Orchestration patterns

Cross-platform agent routing

Route users from any platform to a JD.AI agent:

Discord (JD.AI) ──→ JD.AI Agent ←── Signal (OpenClaw)
Telegram (JD.AI) ──→              ←── Slack (OpenClaw)

Provider failover

Use OpenClaw's providers when JD.AI's primary is unavailable:

openClaw.MessageReceived += async (msg) =>
{
    var providers = await providerRegistry.DetectProvidersAsync(ct);
    if (providers.Any(p => p.IsAvailable))
    {
        var response = await pool.SendMessageAsync(localAgentId, msg.Content);
        await openClaw.SendMessageAsync(msg.ChannelId, response ?? "No response");
    }
    else
    {
        await openClaw.SendMessageAsync(msg.ChannelId, $"/route-to-agent {msg.Content}");
    }
};

Platform-specific routing

openClaw.MessageReceived += async (msg) =>
{
    var response = msg.Metadata.GetValueOrDefault("source_platform") switch
    {
        "signal" => await pool.SendMessageAsync(securityAgentId, msg.Content),
        "slack" => await pool.SendMessageAsync(devOpsAgentId, msg.Content),
        _ => await pool.SendMessageAsync(generalAgentId, msg.Content)
    };

    if (response is not null)
        await openClaw.SendMessageAsync(msg.ChannelId, response);
};

Error handling and resilience

  • Connection verificationConnectAsync checks OpenClaw reachability before starting the poll loop
  • Poll failure backoff — 5-second wait after poll errors
  • Graceful disconnectionDisconnectAsync cancels the poll loop cleanly
  • HttpClient managementAddHttpClient<T> provides connection pooling and DNS refresh

Monitoring

await foreach (var evt in eventHub.StreamEvents("channel.*"))
{
    Console.WriteLine($"[{evt.Timestamp:HH:mm:ss}] {evt.Type}: {evt.SourceId}");
}

Health checks

curl http://localhost:18789/api/channels | jq '.[] | select(.channelType == "openclaw")'

Troubleshooting

Problem Check
Bridge connects but no messages Verify SourceChannel matches OpenClaw config
Messages sent but not received Verify TargetChannel and BaseUrl
Connection fails on startup Ensure OpenClaw is running and reachable
Duplicate messages Ensure only one gateway polls the same channel

See also