# Claw-WS Parallel Transport Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a parallel `claw-ws` transport path that keeps the current pipe mode intact while introducing a long-lived `sg_claw` local service, an interactive `sg_claw_client`, and a browser WebSocket backend at `ws://127.0.0.1:12345`.

**Architecture:** First extract a transport-agnostic submit-task runner and browser backend abstraction from the current pipe-coupled flow. Keep the existing pipe path as one adapter/backend, then add a fixed-protocol browser WebSocket backend plus a small service/session layer and an interactive CLI client that reuse the same runtime, orchestration, and browser-facing tool adapters.

**Tech Stack:** Rust 2021, current sgclaw compat runtime, zeroclaw runtime engine, `serde`/`serde_json`, existing `MacPolicy`, and a blocking WebSocket crate for v1 (`tungstenite` preferred over a broad async rewrite).

---

## Scope Guardrails

- Keep the current pipe mode entrypoint and behavior working.
- Do **not** replace the existing browser pipe path.
- Add a **parallel** WebSocket path only.
- v1 supports **one active client session** only.
- Reuse existing tool names and runtime behavior whenever possible.
- If WS `Eval` support is incomplete, disable eval-dependent browser-script skill exposure in WS mode rather than shipping partial behavior.
- Do not broaden v1 with task queues, multi-client support, or admin endpoints.

---

## File Structure

### Existing files to reuse

- Modify: `src/lib.rs` — current pipe bootstrap and receive loop; keep as the legacy pipe entrypoint.
- Modify: `src/agent/mod.rs` — current `BrowserMessage::SubmitTask` entrypoint and config-loading flow.
- Modify: `src/compat/runtime.rs` — compat runtime and tool assembly.
- Modify: `src/compat/orchestration.rs` — direct workflow vs compat runtime routing.
- Modify: `src/compat/browser_tool_adapter.rs` — exposes `browser_action` and `superrpa_browser`.
- Modify: `src/compat/browser_script_skill_tool.rs` — browser-script skill execution.
- Modify: `src/compat/workflow_executor.rs` — direct browser workflows such as Zhihu flows.
- Reuse: `src/pipe/browser_tool.rs` — current browser command executor; retain as the pipe backend implementation.
- Reuse: `src/pipe/protocol.rs` — `BrowserMessage`, `AgentMessage`, `Action`, `ExecutionSurfaceMetadata`.
- Reuse: `src/security/mac_policy.rs` — local action/domain guardrails.
- Modify: `src/config/settings.rs` — minimal new config surface for WS mode.
- Optional modify: `src/runtime/engine.rs` — only if backend capability wiring requires it.

### New files to create

- Create: `src/agent/task_runner.rs` — shared submit-task execution entrypoint.
- Create: `src/browser/mod.rs` — browser backend exports.
- Create: `src/browser/backend.rs` — `BrowserBackend` trait and helpers.
- Create: `src/browser/pipe_backend.rs` — wrapper around existing `BrowserPipeTool`.
- Create: `src/browser/ws_protocol.rs` — fixed browser WS request/response codec.
- Create: `src/browser/ws_backend.rs` — browser WS backend with blocking invoke semantics.
- Create: `src/service/mod.rs` — service exports.
- Create: `src/service/protocol.rs` — client/service WS message types.
- Create: `src/service/server.rs` — single-session `sg_claw` server.
- Create: `src/bin/sg_claw.rs` — service binary.
- Create: `src/bin/sg_claw_client.rs` — interactive CLI client.
- Create: `tests/task_runner_test.rs` — shared submit-task runner regressions.
- Create: `tests/browser_backend_capability_test.rs` — backend capability/tool exposure tests.
- Create: `tests/browser_ws_protocol_test.rs` — browser WS protocol tests.
- Create: `tests/browser_ws_backend_test.rs` — browser WS backend tests.
- Create: `tests/service_ws_session_test.rs` — single-session server tests.
- Create: `tests/service_task_flow_test.rs` — client/service task flow tests.

---

## Task 1: Extract a shared submit-task runner

**Files:**
- Create: `src/agent/task_runner.rs`
- Modify: `src/agent/mod.rs`
- Modify: `src/lib.rs`
- Test: `tests/task_runner_test.rs`
- Reuse: `src/compat/runtime.rs`, `src/compat/orchestration.rs`

- [ ] **Step 1: Write a failing runner regression test**

Create `tests/task_runner_test.rs` covering:
- empty instruction returns the same `TaskComplete` failure summary
- missing LLM config still returns the same summary shape
- the pipe adapter still emits `LogEntry` before `TaskComplete`

- [ ] **Step 2: Run the targeted regression tests first**

Run:

```bash
cargo test --test runtime_task_flow_test --test task_runner_test
```

Expected: `task_runner_test` fails because the shared runner does not exist yet.

- [ ] **Step 3: Define the transport-neutral request model**

Create `src/agent/task_runner.rs` with a request struct that mirrors the current pipe payload:

```rust
pub struct SubmitTaskRequest {
    pub instruction: String,
    pub conversation_id: Option<String>,
    pub messages: Vec<ConversationMessage>,
    pub page_url: Option<String>,
    pub page_title: Option<String>,
}
```

Normalize empty strings to `None` at the adapter boundary.

- [ ] **Step 4: Define an event sink abstraction**

Add a small trait that can emit the current agent events without depending on a specific transport:

```rust
pub trait AgentEventSink {
    fn send(&self, message: &AgentMessage) -> Result<(), PipeError>;
}
```

The existing pipe transport should implement this first.

- [ ] **Step 5: Move submit-task execution into a shared function**

Extract the body currently inside `BrowserMessage::SubmitTask` handling from `src/agent/mod.rs` into a shared function such as:

```rust
pub fn run_submit_task(
    sink: &dyn AgentEventSink,
    browser_backend: Arc<dyn BrowserBackend>,
    context: &AgentRuntimeContext,
    request: SubmitTaskRequest,
) -> Result<(), PipeError>
```

This function must still:
- validate empty instruction
- load sgclaw settings
- log runtime/config info
- choose orchestration vs compat runtime
- emit `AgentMessage::TaskComplete`

- [ ] **Step 6: Keep pipe mode as a thin adapter**

Refactor `handle_browser_message_with_context(...)` in `src/agent/mod.rs` so it only:
- pattern matches `BrowserMessage`
- converts `SubmitTask` into `SubmitTaskRequest`
- forwards into `run_submit_task(...)`

- [ ] **Step 7: Re-run the runner regressions**

Run:

```bash
cargo test --test runtime_task_flow_test --test task_runner_test
```

Expected: both tests pass and pipe behavior remains unchanged.

- [ ] **Step 8: Commit**

```bash
git add src/agent/mod.rs src/agent/task_runner.rs src/lib.rs tests/task_runner_test.rs
git commit -m "refactor: extract shared submit-task runner"
```

---

## Task 2: Introduce a browser backend abstraction and wrap the current pipe implementation

**Files:**
- Create: `src/browser/mod.rs`
- Create: `src/browser/backend.rs`
- Create: `src/browser/pipe_backend.rs`
- Modify: `src/lib.rs`
- Modify: `src/compat/browser_tool_adapter.rs`
- Modify: `src/compat/browser_script_skill_tool.rs`
- Modify: `src/compat/runtime.rs`
- Modify: `src/compat/orchestration.rs`
- Modify: `src/compat/workflow_executor.rs`
- Test: `tests/browser_backend_capability_test.rs`
- Reuse: `src/pipe/browser_tool.rs`, `src/security/mac_policy.rs`

- [ ] **Step 1: Add a failing backend capability test**

Create `tests/browser_backend_capability_test.rs` to verify:
- pipe backend still exposes privileged surface metadata
- pipe backend still supports `Eval`
- browser-script tool exposure is disabled when `supports_eval()` is false

- [ ] **Step 2: Run the current browser adapter tests first**

Run:

```bash
cargo test --test browser_tool_test --test compat_browser_tool_test --test browser_backend_capability_test
```

Expected: new capability test fails because the backend abstraction does not exist yet.

- [ ] **Step 3: Define the shared browser interface**

Create `src/browser/backend.rs`:

```rust
pub trait BrowserBackend: Send + Sync {
    fn invoke(
        &self,
        action: Action,
        params: Value,
        expected_domain: &str,
    ) -> Result<CommandOutput, PipeError>;

    fn surface_metadata(&self) -> ExecutionSurfaceMetadata;

    fn supports_eval(&self) -> bool {
        true
    }
}
```

- [ ] **Step 4: Implement the pipe backend as a wrapper**

Create `src/browser/pipe_backend.rs` that stores the current `BrowserPipeTool<T>` and forwards `invoke(...)` and `surface_metadata()` unchanged.

Pipe mode must continue using:
- `perform_handshake(...)`
- `MacPolicy::load_from_path(...)`
- `BrowserPipeTool::new(...).with_response_timeout(...)`

- [ ] **Step 5: Refactor runtime and tool adapters to depend on `Arc<dyn BrowserBackend>`**

Update:
- `src/compat/browser_tool_adapter.rs`
- `src/compat/browser_script_skill_tool.rs`
- `src/compat/runtime.rs`
- `src/compat/orchestration.rs`
- `src/compat/workflow_executor.rs`

Preserve external tool names:
- `browser_action`
- `superrpa_browser`

- [ ] **Step 6: Add capability gating for eval-dependent script tools**

If `supports_eval()` is false, do **not** expose browser-script skill tools from `build_browser_script_skill_tools(...)` in that backend mode.

- [ ] **Step 7: Re-run browser adapter tests**

Run:

```bash
cargo test --test browser_tool_test --test compat_browser_tool_test --test browser_backend_capability_test
```

Expected: all three pass.

- [ ] **Step 8: Commit**

```bash
git add src/browser src/lib.rs src/compat/browser_tool_adapter.rs src/compat/browser_script_skill_tool.rs src/compat/runtime.rs src/compat/orchestration.rs src/compat/workflow_executor.rs tests/browser_backend_capability_test.rs
git commit -m "refactor: abstract browser backend from pipe transport"
```

---

## Task 3: Implement the fixed browser WebSocket protocol codec in isolation

**Files:**
- Create: `src/browser/ws_protocol.rs`
- Test: `tests/browser_ws_protocol_test.rs`
- Reuse: `docs/_tmp_sgbrowser_ws_api_doc.txt`

- [ ] **Step 1: Write failing protocol codec tests**

Create `tests/browser_ws_protocol_test.rs` covering:
- exact outbound frame encoding
- callback payload decoding
- unknown callback format rejection
- mapping coverage for every supported v1 action

- [ ] **Step 2: Run the protocol tests first**

Run:

```bash
cargo test --test browser_ws_protocol_test
```

Expected: fail because the WS protocol codec does not exist yet.

- [ ] **Step 3: Encode the exact browser frame shapes**

Create `src/browser/ws_protocol.rs` so it can build exact array-form payloads such as:

```rust
[requesturl, "sgBrowserExcuteJsCodeByArea", target_url, js_code, area]
```

Serialize to the JSON string format required by the browser service.

- [ ] **Step 4: Define the v1 action mapping table**

Support only the actions already needed by current sgclaw flows:
- `Navigate`
- `GetText`
- `Click`
- `Type`
- `Eval`

Document which browser functions each one maps to and what assumptions they rely on.

- [ ] **Step 5: Define callback parsing and correlation rules**

Represent callback-bearing operations explicitly, including the callback function naming or request-correlation strategy the backend will depend on.

- [ ] **Step 6: Reject unsupported or malformed shapes early**

Fail fast for:
- unsupported actions
- malformed callback payloads
- missing request correlation metadata

- [ ] **Step 7: Re-run the protocol tests**

Run:

```bash
cargo test --test browser_ws_protocol_test
```

Expected: pass with no network dependency.

- [ ] **Step 8: Commit**

```bash
git add src/browser/ws_protocol.rs tests/browser_ws_protocol_test.rs
git commit -m "test: codify fixed browser websocket protocol"
```

---

## Task 4: Build the browser WS backend with synchronous invoke semantics

**Files:**
- Create: `src/browser/ws_backend.rs`
- Modify: `src/browser/mod.rs`
- Test: `tests/browser_ws_backend_test.rs`
- Reuse: `CommandOutput`, `PipeError`, `ExecutionSurfaceMetadata`, `MacPolicy`

- [ ] **Step 1: Write failing backend behavior tests**

Create `tests/browser_ws_backend_test.rs` covering:
- zero return + no callback => success
- non-zero return => failure
- zero return + callback => success with normalized `CommandOutput`
- callback timeout => timeout error
- dropped socket => clear failure

- [ ] **Step 2: Run backend tests first**

Run:

```bash
cargo test --test browser_ws_backend_test
```

Expected: fail because the WS backend does not exist yet.

- [ ] **Step 3: Build a long-lived browser connection manager**

Implement `src/browser/ws_backend.rs` to connect to `ws://127.0.0.1:12345` and expose blocking `invoke(...)` calls.

Use a dedicated connection loop plus request/response coordination instead of scattering raw socket calls through the runtime.

- [ ] **Step 4: Preserve local guardrails before send**

Validate `MacPolicy` before translating an action into the browser WS protocol, matching current pipe backend behavior.

- [ ] **Step 5: Normalize immediate status returns and delayed callbacks**

For each `invoke(...)` call:
- fail immediately on non-zero return codes
- succeed immediately for operations with no data callback
- wait for the matching callback for result-bearing operations
- convert the final outcome into `CommandOutput`

- [ ] **Step 6: Keep v1 concurrency intentionally serialized**

Allow only one in-flight browser request at a time unless the browser callback protocol proves a stable request-id guarantee.

- [ ] **Step 7: Re-run backend tests**

Run:

```bash
cargo test --test browser_ws_backend_test
```

Expected: pass using mocks/fakes, not the real browser.

- [ ] **Step 8: Commit**

```bash
git add src/browser/mod.rs src/browser/ws_backend.rs tests/browser_ws_backend_test.rs
git commit -m "feat: add browser websocket backend"
```

---

## Task 5: Add the `sg_claw` service protocol and single-session server

**Files:**
- Create: `src/service/mod.rs`
- Create: `src/service/protocol.rs`
- Create: `src/service/server.rs`
- Create: `src/bin/sg_claw.rs`
- Modify: `src/lib.rs`
- Modify: `Cargo.toml`
- Test: `tests/service_ws_session_test.rs`
- Reuse: `AgentMessage::LogEntry`, `AgentMessage::TaskComplete`, `SubmitTaskRequest`, `run_submit_task(...)`

- [ ] **Step 1: Write failing service session tests**

Create `tests/service_ws_session_test.rs` to verify:
- first client attaches
- second client gets `Busy`
- disconnect resets session state
- overlapping task submission is rejected clearly

- [ ] **Step 2: Run the session tests first**

Run:

```bash
cargo test --test service_ws_session_test
```

Expected: fail because the service layer does not exist yet.

- [ ] **Step 3: Define a thin client/service WS protocol**

In `src/service/protocol.rs`, reuse existing task/event shapes as much as possible:

```rust
ClientMessage::SubmitTask { instruction, conversation_id, messages, page_url, page_title }
ClientMessage::Ping
ServiceMessage::LogEntry { level, message }
ServiceMessage::TaskComplete { success, summary }
ServiceMessage::Busy { message }
```

- [ ] **Step 4: Add the service event sink adapter**

Implement `AgentEventSink` for the service session writer so the shared task runner can stream `LogEntry` and `TaskComplete` over the service WebSocket.

- [ ] **Step 5: Implement single-active-client session state**

Model explicit states such as:
- `Idle`
- `ClientAttached`
- `TaskRunning`

Reject a second client with `ServiceMessage::Busy` and close the socket. Reject overlapping tasks instead of queueing them.

- [ ] **Step 6: Add the service binary**

Create `src/bin/sg_claw.rs` that:
- loads config
- creates the browser WS backend
- listens for local client connections
- routes `SubmitTask` into `run_submit_task(...)`

Keep `src/main.rs` and the existing `sgclaw::run()` pipe path unchanged.

- [ ] **Step 7: Re-run the session tests**

Run:

```bash
cargo test --test service_ws_session_test
```

Expected: pass without the real browser.

- [ ] **Step 8: Commit**

```bash
git add src/service src/bin/sg_claw.rs src/lib.rs Cargo.toml tests/service_ws_session_test.rs
git commit -m "feat: add claw-ws service entrypoint"
```

---

## Task 6: Add the `sg_claw_client` interactive CLI

**Files:**
- Create: `src/bin/sg_claw_client.rs`
- Modify: `Cargo.toml`
- Test: `tests/service_task_flow_test.rs`
- Reuse: `src/service/protocol.rs`

- [ ] **Step 1: Write failing client/service task flow tests**

Create `tests/service_task_flow_test.rs` to verify:
- the submit-task request reaches the service
- log entries stream in order
- the final summary arrives exactly once
- disconnect after task completion is handled cleanly

- [ ] **Step 2: Run the flow tests first**

Run:

```bash
cargo test --test service_task_flow_test
```

Expected: fail because the client does not exist yet.

- [ ] **Step 3: Implement a thin interactive client loop**

Create `src/bin/sg_claw_client.rs` that:
- connects to the local `sg_claw` service
- reads a line of user input
- sends `ClientMessage::SubmitTask`
- prints streamed `LogEntry` events as they arrive
- ends the turn on `TaskComplete`

- [ ] **Step 4: Keep the client intentionally dumb**

Do **not** duplicate runtime logic in the client. Browser access, skills, orchestration, and task execution remain entirely inside the service.

- [ ] **Step 5: Re-run the flow tests**

Run:

```bash
cargo test --test service_task_flow_test
```

Expected: pass without the real browser.

- [ ] **Step 6: Build the new binaries explicitly**

Run:

```bash
cargo build --bin sg_claw --bin sg_claw_client
```

Expected: both binaries compile successfully.

- [ ] **Step 7: Commit**

```bash
git add src/bin/sg_claw_client.rs Cargo.toml tests/service_task_flow_test.rs
git commit -m "feat: add interactive claw-ws client"
```

---

## Task 7: Finish wiring, preserve pipe mode, and verify end-to-end behavior

**Files:**
- Modify: `Cargo.toml`
- Modify: `src/lib.rs`
- Modify: `src/config/settings.rs`
- Optional modify: `src/runtime/engine.rs`
- Reuse: `tests/browser_tool_test.rs`, `tests/runtime_task_flow_test.rs`, `tests/compat_runtime_test.rs`

- [ ] **Step 1: Add only the minimum config surface for v1**

Add settings such as:
- `browser_ws_url` defaulting to `ws://127.0.0.1:12345`
- `service_ws_listen_addr` defaulting to local loopback

Do **not** change the meaning of existing browser backend/profile settings just to represent service mode.

- [ ] **Step 2: Keep external browser tool naming stable**

Verify that the runtime still exposes:
- `superrpa_browser`
- `browser_action`

under both pipe and WS modes where the backend supports them.

- [ ] **Step 3: Re-run the current pipe regression suite**

Run:

```bash
cargo test --test browser_tool_test --test compat_browser_tool_test --test runtime_task_flow_test --test pipe_handshake_test --test pipe_protocol_test --test task_protocol_test
```

Expected: all existing pipe-oriented tests still pass unchanged.

- [ ] **Step 4: Run the new WS-focused suite**

Run:

```bash
cargo test --test task_runner_test --test browser_ws_protocol_test --test browser_ws_backend_test --test browser_backend_capability_test --test service_ws_session_test --test service_task_flow_test
```

Expected: all new tests pass without launching the real browser.

- [ ] **Step 5: Run a full Rust test sweep**

Run:

```bash
cargo test --tests
```

Expected: all Rust tests pass.

- [ ] **Step 6: Build all three binaries**

Run:

```bash
cargo build --bin sgclaw --bin sg_claw --bin sg_claw_client
```

Expected: all three binaries compile.

- [ ] **Step 7: Perform a manual local smoke test**

Manual test:
1. Start the browser app so `ws://127.0.0.1:12345` is available.
2. Run `cargo run --bin sg_claw`.
3. In another terminal, run `cargo run --bin sg_claw_client`.
4. Submit a simple browser task such as opening a page or fetching visible text.
5. Confirm the client prints streaming logs and exactly one final completion summary.
6. Confirm the old pipe-mode entry still starts via `cargo run`.

Expected: both modes work side-by-side.

- [ ] **Step 8: Commit**

```bash
git add Cargo.toml src/lib.rs src/config/settings.rs src/runtime/engine.rs
git commit -m "feat: wire parallel claw-ws transport"
```

---

## Verification Checklist

### Fast regression checks

```bash
cargo test --test browser_tool_test --test compat_browser_tool_test --test runtime_task_flow_test
```

Expected: current pipe/browser runtime behavior remains green.

### Full Rust test sweep

```bash
cargo test --tests
```

Expected: all Rust tests pass.

### Binary build verification

```bash
cargo build --bin sgclaw --bin sg_claw --bin sg_claw_client
```

Expected: all three binaries compile.

### Manual end-to-end verification

- Browser app listening on `ws://127.0.0.1:12345`
- `cargo run --bin sg_claw`
- `cargo run --bin sg_claw_client`
- submit one browser task
- verify streaming logs, final completion, and single-client lock behavior
- verify `cargo run` still preserves old pipe bootstrap

---

## Notes for Implementation

- Keep the current pipe bootstrap in `src/lib.rs` intact until the shared runner and pipe backend wrapper are both green.
- Prefer small commits at each task boundary.
- Keep the new WS path additive and isolated.
- Do not ship partial browser capabilities under stable tool names.
- Treat `docs/_tmp_sgbrowser_ws_api_doc.txt` as the browser WS protocol source of truth while implementing `src/browser/ws_protocol.rs`.
