← Back to Plugins
Integration

Router Bridge

openclawroman By openclawroman 👁 8 views ▲ 0 votes

OpenClaw plugin: router execution backend switch

GitHub

Configuration Example

{
  "backendMode": "native",
  "scopeMode": "thread",
  "routerCommand": "python3 /tmp/openclaw-router/bin/ai-code-runner",
  "routerConfigPath": "/tmp/openclaw-router/config/router.config.json",
  "fallbackToNativeOnError": true,
  "healthCacheTtlMs": 30000,
  "targetHarnessId": "default",
  "threadBindingMode": "per-thread",
  "acpSessionKey": null
}

README

# router-bridge

An [OpenClaw](https://github.com/openclaw/openclaw) plugin that switches the execution backend for coding tasks β€” from the built-in agent (native) to an external [openclaw-router](https://github.com/openclawroman/openclaw-router) CLI via subprocess, or to an ACP coding harness in Phase 2.

## What It Does

router-bridge is an **execution backend switch plugin**. It controls *which backend* executes coding tasks:

| Mode | Backend | What runs |
|------|---------|-----------|
| `native` | Built-in agent | The model handles tasks directly |
| `router-bridge` | [openclaw-router](https://github.com/openclawroman/openclaw-router) | Delegates to an external routing layer that classifies and dispatches tasks across multiple executors (Codex, Claude Code, OpenRouter) |
| `router-acp` | ACP session | Direct connection to a persistent coding harness (Phase 2) |

## How It Works

```
User: "write me a function to parse JSON"
  ↓
  OpenClaw session β†’ before_prompt_build hook fires
  ↓
  router-bridge plugin:
    1. classifyTask("write me a function...") β†’ coding task
    2. shouldDelegateToExecutionBackend() → checks scope (thread→session→global), delegates
    3. SubprocessRouterAdapter.execute() β†’ spawn openclaw-router CLI
    4. CLI routes to Codex/Claude/MiniMax
    5. Result injected as ctx.routerResult
  ↓
User sees the result (or falls back to native on error)
```

When the backend is set to `native`, the model handles tasks directly β€” no delegation.

## Commands

### `/router on`
Enable router-bridge delegation for this thread/session.

### `/router off`
Switch back to native execution.

### `/router status`
Show current backend, scope, health diagnostics, and config.

## Natural Language

You can also use natural language (via the skill handler):

- "switch to external routing layer in this thread" β†’ `/router on`
- "turn off router in this chat" β†’ `/router off`
- "is router on?" β†’ `/router status`

These map to the same handlers as `/router on|off|status` β€” single source of truth.

## Architecture

```
index.ts                  ← Plugin entry: registers command + skill + service
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ commands.ts       ← /router on|off|status handlers
β”‚   β”œβ”€β”€ skill.ts          ← Natural language β†’ command handler mapping
β”‚   β”œβ”€β”€ policy.ts         ← classifyTask() + shouldDelegateToExecutionBackend()
β”‚   β”œβ”€β”€ store.ts          ← Scoped state persistence (thread β†’ session β†’ global)
β”‚   β”œβ”€β”€ types.ts          ← Enums, interfaces, defaults
β”‚   └── adapters/
β”‚       β”œβ”€β”€ base.ts       ← RouterExecutionAdapter interface
β”‚       β”œβ”€β”€ subprocess.ts ← SubprocessRouterAdapter (Phase 1)
β”‚       β”œβ”€β”€ acp.ts        ← AcpRouterAdapter (Phase 2 stub)
β”‚       β”œβ”€β”€ factory.ts    ← createAdapter(config) β€” picks adapter by backendMode
β”‚       └── index.ts      ← Barrel exports
β”œβ”€β”€ skills/router/
β”‚   └── SKILL.md          ← Trigger patterns for natural language
β”œβ”€β”€ docs/
β”‚   └── MIGRATION.md      ← Phase 1 β†’ Phase 2 migration guide
β”œβ”€β”€ tests/                ← 127 tests across 8 files
└── openclaw.plugin.json  ← Manifest with configSchema
```

## Scope Resolution

The plugin uses scoped state with fallback precedence:

1. **Thread** β€” checked first (if `threadId` is present)
2. **Session** β€” checked second (if `sessionId` is present)
3. **Global** β€” fallback when no thread/session scope is set

The delegation policy uses `getEffective()` to resolve the active backend at runtime, ensuring that `/router on` in a specific thread only affects that thread, while `/router on --scope global` affects all sessions.

## Limitations

- **One-shot execution**: The router CLI is invoked per-task via subprocess. There is no persistent session or streaming β€” each call is a complete stdin JSON β†’ stdout JSON round-trip.
- **No cancellation**: Once spawned, a router task cannot be cancelled mid-flight.
- **Scope-only control**: The plugin does not register as an OpenClaw model provider. It uses execution hooks and delegation, not `/model` switching.

For persistent sessions with streaming and cancellation, see Phase 2 (ACP) in [docs/MIGRATION.md](docs/MIGRATION.md).

## Configuration

```json
{
  "backendMode": "native",
  "scopeMode": "thread",
  "routerCommand": "python3 /tmp/openclaw-router/bin/ai-code-runner",
  "routerConfigPath": "/tmp/openclaw-router/config/router.config.json",
  "fallbackToNativeOnError": true,
  "healthCacheTtlMs": 30000,
  "targetHarnessId": "default",
  "threadBindingMode": "per-thread",
  "acpSessionKey": null
}
```

### Config Fields

| Field | Default | Description |
|-------|---------|-------------|
| `backendMode` | `native` | `native`, `router-bridge`, or `router-acp` |
| `scopeMode` | `thread` | Where overrides apply: `thread`, `session`, or `global` |
| `routerCommand` | `python3 .../ai-code-runner` | Shell command to invoke openclaw-router |
| `routerConfigPath` | `.../router.config.json` | Path to openclaw-router JSON config |
| `fallbackToNativeOnError` | `true` | Auto-fallback to native if router errors |
| `healthCacheTtlMs` | `30000` | Health check cache duration |
| `targetHarnessId` | `default` | Harness ID for ACP adapter (Phase 2) |
| `threadBindingMode` | `per-thread` | ACP session binding mode (Phase 2) |
| `acpSessionKey` | `null` | Pre-configured ACP session key (Phase 2) |

## Health Diagnostics

`/router status` runs 4 health checks:

1. **binary_exists** β€” Is the router CLI binary found? (PATH resolution for relative names, `fs.existsSync` for absolute paths)
2. **config_valid** β€” Does the config file exist?
3. **env_sufficient** β€” Is PATH set? Is the temp directory writable?
4. **subprocess_health** β€” Does `routerCommand --health` return success?

If any check fails, the plugin falls back to native execution (when `fallbackToNativeOnError: true`).

## openclaw-router Integration

The plugin delegates to [openclaw-router](https://github.com/openclawroman/openclaw-router), an external routing layer that:

- Classifies tasks by type (coding, review, chat, planning)
- Selects the best executor (Codex CLI, Claude Code, OpenRouter/MiniMax)
- Handles fallback chains and logging
- Supports config-driven executor selection

The communication is via **stdin JSON payload**:

```json
{
  "task": "Write a hello world function",
  "task_id": "task-42",
  "task_meta": {
    "type": "coding",
    "task_id": "task-42",
    "task_class": "code_generation",
    "risk": "medium",
    "modality": "text",
    "requires_repo_write": true
  },
  "prompt": "Write a TypeScript function...",
  "scope": {
    "scope_id": "thread-abc",
    "thread_id": "t-123",
    "session_id": "s-456"
  },
  "context": {
    "working_directory": "/tmp/openclaw-router",
    "git_branch": "main"
  },
  "timeout_ms": 60000
}
```

The router returns a JSON response with `success`, `output`, `duration_ms`, `cost_usd`, `tokens_used`, etc.

## Phase 2 (ACP)

In Phase 2, only the **transport layer changes**:

| Component | Phase 1 | Phase 2 |
|-----------|---------|---------|
| Adapter | `SubprocessRouterAdapter` | `AcpRouterAdapter` |
| Transport | Subprocess + JSON via stdin | ACP session API |
| Everything else | Unchanged | Unchanged |

See [docs/MIGRATION.md](docs/MIGRATION.md) for the full migration guide.

## License

Part of the [OpenClaw](https://github.com/openclaw/openclaw) project.
integration

Comments

Sign in to leave a comment

Loading comments...