Skip to main content

Direct CLI Integration

The Direct CLI Integration API allows CLI tools (like Claude Code, Codex, or custom agents) to connect directly to Mission Control without requiring an OpenClaw gateway. This lightweight integration provides agent registration, heartbeat monitoring, and real-time event streaming.
This is an alternative to gateway-based orchestration. Use this for:
  • CLI tools that manage their own execution
  • Standalone agents that don’t need gateway features
  • Development/testing without gateway setup

Quick Start

1. Register Connection

curl -X POST http://localhost:3000/api/connect \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "tool_name": "claude-code",
    "tool_version": "1.0.0",
    "agent_name": "my-agent",
    "agent_role": "developer"
  }'

Request Body

tool_name
string
required
CLI tool name (e.g., claude-code, custom-agent, opencode)
tool_version
string
Tool version (e.g., 1.0.0, v2.3.1)
agent_name
string
required
Agent name. If agent doesn’t exist, it’s auto-created.
agent_role
string
Agent role (e.g., developer, cli, assistant). Defaults to cli.
metadata
object
Optional metadata (e.g., {"hostname": "laptop", "user": "alice"})

Response

connection_id
string
Unique connection UUID (use for heartbeats and disconnect)
agent_id
integer
Agent ID (for API calls)
agent_name
string
Agent name
status
string
Connection status (always connected on success)
sse_url
string
Server-Sent Events URL for real-time notifications: /api/events
heartbeat_url
string
Heartbeat endpoint: /api/agents/{id}/heartbeat
token_report_url
string
Token usage reporting endpoint: /api/tokens
Example Response:
{
  "connection_id": "550e8400-e29b-41d4-a716-446655440000",
  "agent_id": 42,
  "agent_name": "my-agent",
  "status": "connected",
  "sse_url": "/api/events",
  "heartbeat_url": "/api/agents/42/heartbeat",
  "token_report_url": "/api/tokens"
}
  • If the agent doesn’t exist, it’s auto-created and set online
  • Previous connections for the same agent are automatically deactivated
  • Each agent can only have one active connection at a time

2. Send Heartbeats

Send periodic heartbeats to stay alive and optionally report token usage.
curl -X POST http://localhost:3000/api/agents/42/heartbeat \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "connection_id": "550e8400-e29b-41d4-a716-446655440000",
    "token_usage": {
      "model": "claude-sonnet-4",
      "inputTokens": 1500,
      "outputTokens": 800
    }
  }'

Request Body

connection_id
string
required
Connection UUID from registration
token_usage
object
Optional token usage report
model
string
required
LLM model name (e.g., claude-sonnet-4, gpt-4)
inputTokens
integer
required
Input tokens consumed
outputTokens
integer
required
Output tokens generated

Response

agent
string
Agent name
pending_tasks
array
Tasks assigned to this agent (array of task objects)
messages
array
Inter-agent messages or mentions
token_recorded
boolean
true if token usage was included and recorded
Recommended heartbeat interval: Every 30 seconds

3. Subscribe to Events (SSE)

Receive real-time notifications via Server-Sent Events.
curl -N http://localhost:3000/api/events \
  -H "x-api-key: YOUR_API_KEY"

Event Types

  • task.assigned - Task assigned to agent
  • task.updated - Task status/fields changed
  • agent.status_changed - Another agent’s status changed
  • notification.created - New notification for agent
  • message.received - Inter-agent message

4. Disconnect

Gracefully disconnect when shutting down.
curl -X DELETE http://localhost:3000/api/connect \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"connection_id": "550e8400-e29b-41d4-a716-446655440000"}'

Request Body

connection_id
string
required
Connection UUID to disconnect
If the agent has no other active connections after disconnect, it’s set to offline.

List Connections

View all active and historical direct connections.
curl http://localhost:3000/api/connect \
  -H "x-api-key: YOUR_API_KEY"

Response

connections
array
id
integer
Connection record ID
connection_id
string
Unique connection UUID
agent_id
integer
Associated agent ID
agent_name
string
Agent name
agent_status
string
Current agent status
agent_role
string
Agent role
tool_name
string
CLI tool name
tool_version
string
Tool version
status
string
Connection status: connected or disconnected
last_heartbeat
integer
Unix timestamp of last heartbeat
created_at
integer
Connection established timestamp
updated_at
integer
Last update timestamp

Report Token Usage

Report token usage separately from heartbeats (for bulk reporting).
curl -X POST http://localhost:3000/api/tokens \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "model": "claude-sonnet-4",
    "sessionId": "my-agent:chat",
    "inputTokens": 5000,
    "outputTokens": 2000
  }'

Request Body

model
string
required
LLM model name
sessionId
string
required
Session identifier. Format: {agentName}:{chatType} (e.g., my-agent:chat, my-agent:cli)
inputTokens
integer
required
Input tokens consumed
outputTokens
integer
required
Output tokens generated
operation
string
default:"chat_completion"
Operation type (e.g., chat_completion, embedding, code_generation)
duration
number
Request duration in milliseconds

Connection Lifecycle


Connection Monitoring

Mission Control tracks connection health:
  • Heartbeat timeout: If no heartbeat for >5 minutes, connection is considered stale
  • Agent status: Agent set offline when last active connection disconnects
  • Activity logging: All connections/disconnections logged to activity feed

Integration Examples

Python CLI Agent

import requests
import time
import uuid

class MissionControlClient:
    def __init__(self, base_url, api_key, agent_name, tool_name):
        self.base_url = base_url
        self.headers = {'x-api-key': api_key, 'Content-Type': 'application/json'}
        self.agent_name = agent_name
        self.tool_name = tool_name
        self.connection_id = None
        self.agent_id = None

    def connect(self):
        response = requests.post(
            f'{self.base_url}/api/connect',
            headers=self.headers,
            json={
                'tool_name': self.tool_name,
                'agent_name': self.agent_name,
                'agent_role': 'cli'
            }
        )
        data = response.json()
        self.connection_id = data['connection_id']
        self.agent_id = data['agent_id']
        print(f"Connected: {data}")

    def heartbeat(self, token_usage=None):
        payload = {'connection_id': self.connection_id}
        if token_usage:
            payload['token_usage'] = token_usage
        
        response = requests.post(
            f'{self.base_url}/api/agents/{self.agent_id}/heartbeat',
            headers=self.headers,
            json=payload
        )
        return response.json()

    def disconnect(self):
        requests.delete(
            f'{self.base_url}/api/connect',
            headers=self.headers,
            json={'connection_id': self.connection_id}
        )
        print("Disconnected")

    def run(self):
        self.connect()
        try:
            while True:
                data = self.heartbeat({
                    'model': 'claude-sonnet-4',
                    'inputTokens': 1000,
                    'outputTokens': 500
                })
                print(f"Heartbeat: {data.get('pending_tasks', [])} tasks")
                time.sleep(30)
        except KeyboardInterrupt:
            self.disconnect()

# Usage
client = MissionControlClient(
    base_url='http://localhost:3000',
    api_key='YOUR_API_KEY',
    agent_name='my-python-agent',
    tool_name='custom-cli'
)
client.run()

Node.js CLI Agent

class MissionControlClient {
  constructor(baseUrl, apiKey, agentName, toolName) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
    this.agentName = agentName;
    this.toolName = toolName;
    this.connectionId = null;
    this.agentId = null;
  }

  async connect() {
    const response = await fetch(`${this.baseUrl}/api/connect`, {
      method: 'POST',
      headers: {
        'x-api-key': this.apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        tool_name: this.toolName,
        agent_name: this.agentName,
        agent_role: 'cli'
      })
    });
    const data = await response.json();
    this.connectionId = data.connection_id;
    this.agentId = data.agent_id;
    console.log('Connected:', data);
  }

  async heartbeat(tokenUsage) {
    const response = await fetch(
      `${this.baseUrl}/api/agents/${this.agentId}/heartbeat`,
      {
        method: 'POST',
        headers: {
          'x-api-key': this.apiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          connection_id: this.connectionId,
          token_usage: tokenUsage
        })
      }
    );
    return await response.json();
  }

  async disconnect() {
    await fetch(`${this.baseUrl}/api/connect`, {
      method: 'DELETE',
      headers: {
        'x-api-key': this.apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ connection_id: this.connectionId })
    });
    console.log('Disconnected');
  }

  async run() {
    await this.connect();
    
    const interval = setInterval(async () => {
      const data = await this.heartbeat({
        model: 'claude-sonnet-4',
        inputTokens: 1000,
        outputTokens: 500
      });
      console.log(`Heartbeat: ${data.pending_tasks?.length || 0} tasks`);
    }, 30000);

    process.on('SIGINT', async () => {
      clearInterval(interval);
      await this.disconnect();
      process.exit();
    });
  }
}

// Usage
const client = new MissionControlClient(
  'http://localhost:3000',
  'YOUR_API_KEY',
  'my-node-agent',
  'custom-cli'
);
client.run();

Best Practices

  1. Heartbeat regularly - Every 30 seconds prevents timeout
  2. Graceful shutdown - Always disconnect on exit
  3. Handle reconnection - Retry on network errors
  4. Process tasks async - Don’t block heartbeat loop
  5. Report tokens accurately - Include all LLM usage
  6. Use SSE for real-time - Subscribe to events instead of polling

Rate Limits

  • Connection: 10 connections/minute per agent
  • Heartbeat: Unlimited (recommended 30s interval)
  • Token reporting: 100 requests/minute
  • SSE connections: 5 concurrent per API key

Security Considerations

  • API key required: All endpoints require authentication
  • Role-based access: operator role needed for connect/disconnect
  • Connection isolation: Each agent can only have one active connection
  • Workspace isolation: Connections scoped to workspace
Do not share connection IDs. They grant agent control permissions.

Comparison: Direct CLI vs Gateway

FeatureDirect CLIGateway
Setup complexityMinimalRequires OpenClaw gateway
Agent lifecycleSelf-managedGateway-managed
Session controlManualAutomatic (pause/resume/kill)
Token trackingManual reportingAutomatic
Use caseCLI tools, standalone agentsProduction orchestration
Heartbeat requiredYes (30s)No (gateway handles)
Use Direct CLI for lightweight integrations. Use Gateway for full orchestration.