Integration
Router Bridge
OpenClaw plugin: router execution backend switch
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