Install
openclaw skills install @mikoribbit/odsh-interopA formatted Skills.md file attached to the Openclaw side, one-command installable for any OpenClaw operator. used to allow Openclaw to call DeepSeek Harness through ODSH-Bridge and control the local PC/VMs.
openclaw skills install @mikoribbit/odsh-interopYou are the router between do it myself and relay to DSH. Decide before working, and re-decide if a task grows while you work.
Estimate the task weight from: number of steps, external systems touched, data volume, duration, and how much context it would add to this conversation.
Compare the predicted token cost of doing it yourself versus relaying to DSH and pick the smaller side:
| Cost model | OpenClaw does it | DSH does it |
|---|---|---|
| conversation context growth | high — tools calls/outputs accumulate in your window | low — you only see the envelope + result summary |
| execution context | your window only | separate process, own context (theirs), your context stays stable |
| handoff overhead | none | one envelope write + one result read (small, fixed) |
standard DSH can execute Windows desktop tasks via Cua Driver (see
docs/CUA-EXECUTION.md): real screenshot, browser automation (CDP), click/type/
hotkey, app launch — focus-safe, over SSH to the host. So:
⚠️ Target routing — mark the envelope
targetcorrectly: aT-*.jsonwithtarget: dshis consumed by the DSH daemon automatically;target: openclawis left inInput/for an OpenClaw-side consumer (DSH's daemon never reads OpenClaw's mail). If it is a task for DSH, settarget: dsh(or omit it); only settarget: openclawfor something you yourself will pick up.
| Path | Purpose |
|---|---|
<BRIDGE>/Input/ | Task entry: T-*.json envelopes (SHARED) |
<BRIDGE>/output/ | Result exit: T-*_result.json (SHARED) |
<BRIDGE>/DSH-Workspace/ | DSH private zone (OpenClaw must not modify) |
<BRIDGE>/openclaw-workspace/ | OpenClaw private zone (DSH must not modify) |
| configured notification channel (e.g. Discord) | real-time status between both sides |
<BRIDGE> = the shared mount path both containers use (repo default /root/ODSH-bridge).
input/T-<YYMMDD>-<seq>.json; fields (see docs/BRIDGE-SPEC.md):
taskId / type / status / requester / target / createdMs / payload / context / result.queued -> running -> done | failed | cancelled..tmp → rename (never write a half file).output/<taskId>_result.json with status / finishedMs / by / payload / human / error.input/_T-*.json (copy the template below), including your
routing decision in context:
"context": { "channel": "<id>", "routingDecision": "relay-dsh: heavy, token-cost favourable" }
output/:
human summary to the operator via your normal channel.<bridge>/openclaw-workspace/dream-feed/ for your dreaming pipeline.{
"schema": "odsh-envelope/v1",
"taskId": "T-YYMMDD-XX",
"type": "execute",
"status": "queued",
"requester": "openclaw",
"target": "dsh",
"createdMs": 0,
"payload": {
"kind": "run-command",
"command": "<command>",
"args": {}
},
"context": {
"channel": "<notificationChannelId>",
"routingDecision": "relayed_dsh: heavy task, predicted token cost lower on DSH"
}
}
| User says | Weight | Route | Why |
|---|---|---|---|
| "hi / who are you / tell me a story" | light | yourself | persona/dialogue, one round |
| "summarise my memory of last week" | light | yourself | one memory call, reply in place |
| "download this page and extract the tables" | heavy | DSH | network + file + parsing, would bloat your window |
| "run this analysis over 40 CSV files and give me the totals" | heavy | DSH | batch file work, DSH context stays clean |
| "search X for posts about LLM agents" | light-heavy | yourself (one web call) or DSH if multiple iterations | compare: one search = you; multi-iteration crawl = DSH |
| "open the browser, log into the dashboard, take a receipt" | heavy (windows desktop) | relay to DSH when the Cua channel is enabled | real desktop execution via Cua Driver |
Place this skill in your skills directory (e.g. /root/.openclaw/skills/odsh-interop/SKILL.md
or your skills path), then the collaboration works as long as:
<BRIDGE> path);context.channel /
DSH .env DISCORD_CHANNEL_ID).Since v1.2, the DSH container auto-starts the bridge daemon at boot
(scripts/dsh-entrypoint.sh → src/dshtrigger.mjs daemon). You can assume DSH is
continuously watching Input/ and will pick up any T-*.json envelope you drop —
no operator needs to start anything. If a result does not arrive, check that the DSH
container is running and the bridge mount is shared; a restart re-arms the daemon automatically.
When the DSH bridge runs on Node >=22.5 (built-in node:sqlite) with BRIDGE_SQLITE=1 in .env, the DSH daemon mirrors every processed envelope into one lightweight file: <BRIDGE>/DSH-Workspace/dsh.db. This is optional, additive and fail-soft — if it is absent (old Node, or disabled), the JSON file store stays the source of truth.
You may READ this ledger directly to answer questions about bridge health/stats. It is read-only for you — never write to DSH-Workspace/ (DSH owns that zone).
| table / view | purpose |
|---|---|
dsh_envelopes | one row per envelope: taskId, type, status, requester, target, createdMs / expiresMs / finishedMs, generated duration (= finishedMs - createdMs), trace_id / span_id / parent_span_id, raw_envelope |
dsh_events | status-transition audit log (id, taskId, fromStatus, toStatus, timestamp) |
dsh_errors | failed-task detail (taskId, error, traceback, timestamp) |
dsh_bridge_stats | view — total_tasks, completed, failed, running, first_created, last_finished |
<BRIDGE>=(the shared mount path; default /root/ODSH-bridge)
node --input-type=module -e "import('node:sqlite').then(({DatabaseSync})=>{const db=new DatabaseSync('<BRIDGE>/DSH-Workspace/dsh.db',{readOnly:true});console.log(db.prepare('SELECT * FROM dsh_bridge_stats').get());db.close();}).catch(()=>console.log('dsh.db not available - fall back to Output/*_result.json'))"
If you only have Python, the stdlib works: python3 -c "import sqlite3; for r in sqlite3.connect('<BRIDGE>/DSH-Workspace/dsh.db').execute('SELECT * FROM dsh_bridge_stats'): print(r)". Prefer Node (node:sqlite) when available.
SELECT * FROM dsh_bridge_stats;SELECT taskId,status,duration,datetime(createdMs/1000,'unixepoch') AS created FROM dsh_envelopes ORDER BY createdMs DESC LIMIT 15;dsh_envelopes with dsh_errors on taskId where status='failed'.SELECT fromStatus,toStatus,timestamp FROM dsh_events WHERE taskId='T-YYMMDD-XX' ORDER BY id;SELECT taskId,span_id,parent_span_id,status FROM dsh_envelopes WHERE trace_id='...';readOnly; never INSERT/UPDATE/DROP in DSH-Workspace/.duration is NULL when finishedMs is empty (still queued/running) — treat NULL, not 0.dsh.db is missing or the query throws, fall back to scanning Output/*_result.json and tell the operator SQLite is off/old-Node — the file store is always authoritative.docs/PROTOCOL.md, docs/BRIDGE-SPEC.md, MAINTENANCE.md, src/).