Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"WebFetch(domain:anthropic.com)",
"WebFetch(domain:platform.claude.com)",
"WebFetch(domain:docs.anthropic.com)",
"WebFetch(domain:docs.openclaw.ai)",
"WebFetch(domain:github.com)",
"Bash(ls:*)",
"Bash(grep:*)",
"Bash(find:*)",
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ For bonus points, try `/deepwork learn` after running your workflow as well, and
</details>


### OpenClaw

OpenClaw support ships as a [Codex bundle](https://docs.openclaw.ai/plugins/bundles) in [`plugins/openclaw/`](./plugins/openclaw/). The bundle launches the DeepWork runtime via `uvx`, so you do not need to install `deepwork` separately — just have `uv` on your PATH.

```bash
git clone https://github.com/Unsupervisedcom/deepwork.git
openclaw plugins install ./deepwork/plugins/openclaw
openclaw gateway restart
```

Start a new OpenClaw session after the restart. See [plugins/openclaw/README.md](./plugins/openclaw/README.md) for the short install guide and [doc/openclaw_design.md](./doc/openclaw_design.md) for how the integration works, limitations, and troubleshooting.

---

## The Problem
Expand Down
124 changes: 124 additions & 0 deletions doc/openclaw_design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# DeepWork on OpenClaw — Design Doc

This document describes how DeepWork integrates with [OpenClaw](https://openclaw.ai), why the integration is shaped the way it is, and how the moving parts fit together.

Primary reference: [OpenClaw — Building Plugins](https://docs.openclaw.ai/plugins/building-plugins). Related pages: [Plugin Bundles](https://docs.openclaw.ai/plugins/bundles), [Hooks](https://docs.openclaw.ai/automation/hooks), [Skills](https://docs.openclaw.ai/tools/skills), [mcp CLI](https://docs.openclaw.ai/cli/mcp), [Agent Bootstrapping](https://docs.openclaw.ai/start/bootstrapping).

## Goal

Let an OpenClaw session use DeepWork's workflow engine and review system through MCP, with no DeepWork-specific pre-install on the host, and no Claude-specific machinery.

## Why a Codex bundle, not a native OpenClaw plugin

OpenClaw supports two plugin formats:

- **Native plugins** — npm package with `package.json`, `openclaw.plugin.json`, and a TypeScript entry point that calls `api.registerTool(...)`, `api.registerHook(...)`, etc. Plugins run in-process in the OpenClaw gateway.
- **Codex bundles** — content packs that OpenClaw maps onto its native features. A bundle is identified by a `.codex-plugin/plugin.json` marker and may ship `skills/`, `hooks/`, `.mcp.json`, and `.app.json`. No entry point, no npm package, no TypeScript. See [Plugin Bundles](https://docs.openclaw.ai/plugins/bundles).

DeepWork is an out-of-process MCP server written in Python. It does not need to run inside the OpenClaw gateway. The Codex bundle format fits exactly: declare the MCP server in `.mcp.json`, ship skill instructions the agent reads when the user invokes `/deepwork` or `/review`, and ship one bootstrap hook so the agent sees the right `session_id` up front. This is strictly less machinery than a native plugin and gives the same runtime behavior.

## File layout

```
plugins/openclaw/
├── .codex-plugin/
│ └── plugin.json # Codex bundle marker
├── .mcp.json # Registers the DeepWork MCP server
├── skills/
│ ├── deepwork/SKILL.md # /deepwork skill — workflow execution
│ └── review/SKILL.md # /review skill — DeepWork reviews
└── hooks/
└── deepwork-openclaw-bootstrap/
├── HOOK.md # Declares the agent:bootstrap hook
└── handler.ts # Injects session/resume context
```

Nothing else in this bundle is required. No `package.json`. No `index.ts`. No `openclaw.plugin.json`. Those belong to native plugins, not Codex bundles.

## Install flow (what the user actually does)

1. Install `uv` (so `uvx` is on PATH).
2. Clone DeepWork, or otherwise get the bundle on disk.
3. `openclaw plugins install /path/to/deepwork/plugins/openclaw`
4. Restart the OpenClaw gateway and start a new session.

No `uv tool install deepwork`. No `pip install`. The MCP config uses `uvx deepwork serve --platform openclaw`, so `uvx` fetches and caches the published `deepwork` package on first launch and auto-resolves updates on subsequent runs. Users get updates by letting `uvx` refresh the cache (or by running `uv cache clean` if they want to force a refresh); they do not need to re-run an install step.

The "bundle still has to be on disk" part is the one remaining pre-install step, and the right fix is publishing the bundle to ClawHub or npm so `openclaw plugins install clawhub:unsupervisedcom/deepwork` works. Until then, pointing `openclaw plugins install` at the cloned repo path is the intended flow.

## Runtime contract

DeepWork's MCP tools require a `session_id` on every call. In OpenClaw, we map that to the current session's `sessionId`. In Claude Code, we map it to `CLAUDE_CODE_SESSION_ID`. The MCP server itself is unchanged between platforms — `--platform openclaw` only affects the adapter that formats review output, not the tool shapes.

Session state lives under the workspace at:

```
.deepwork/tmp/sessions/openclaw/session-<sessionId>/state.json
```

This scoping means resume works inside a single OpenClaw session. Spawned child sessions get their own session directories; they do not yet share a DeepWork workflow stack with their parent the way Claude Code does. That shared-root model depends on parent/root session metadata that OpenClaw's current bootstrap hook surface does not expose. We'll revisit when it does.

## The bootstrap hook

Skills tell the agent what to do when the user invokes `/deepwork` or `/review`. The bootstrap hook exists because the agent also needs to know *which* `session_id` to use before it makes any MCP call — and the agent cannot read that from the skill file alone.

`HOOK.md` registers a hook on the [`agent:bootstrap`](https://docs.openclaw.ai/automation/hooks) event. `handler.ts` receives the event, reads `context.sessionId`, `context.sessionKey`, `context.agentId`, and `context.workspaceDir`, and appends a synthetic bootstrap file to `context.bootstrapFiles` telling the agent:

- the exact `session_id` to pass to every DeepWork MCP call
- whether DeepWork state already exists for this session (detected by checking for `state.json` on disk) — if so, prompt the agent to call `deepwork__get_active_workflow` before starting a new workflow
- the review-spawn conventions specific to OpenClaw (`sessions_spawn` + `sessions_yield`, no `timeoutSeconds` unless `0`, workspace-relative instruction paths)

The handler also writes the same note to disk as a best-effort fallback, but the in-memory `bootstrapFiles` injection is the load-bearing mechanism.

## Reviews on OpenClaw

DeepWork quality gates can return review tasks that the agent needs to run in parallel. Claude Code runs these as sub-tasks via the Task tool. OpenClaw runs them as parallel sub-agents via [`sessions_spawn`](https://docs.openclaw.ai/) + [`sessions_yield`](https://docs.openclaw.ai/).

The review instructions returned by the MCP server are platform-neutral. The OpenClaw-specific skill file (`skills/review/SKILL.md`) tells the agent how to dispatch them as OpenClaw sub-agents. The Claude formatter path in the DeepWork runtime is separate and unchanged.

## Troubleshooting

### DeepWork tools don't appear

- Confirm `openclaw plugins inspect deepwork` shows a bundle with an MCP server.
- Restart the OpenClaw gateway and start a fresh session.
- Verify `uvx deepwork --version` works in your shell. If `uvx` is not on PATH, install `uv`.

### OpenClaw launches the wrong DeepWork binary

Add a top-level `mcp.servers.deepwork` override in OpenClaw's config that points at an exact executable:

```json
{
"mcp": {
"servers": {
"deepwork": {
"command": "/absolute/path/to/deepwork",
"args": ["serve", "--platform", "openclaw"]
}
}
}
}
```

Restart the gateway after the change. This is a rare case — `uvx` resolution is normally correct.

### Testing unreleased DeepWork changes from a local checkout

Register the checkout as an editable `uv` tool so `uvx deepwork` resolves to your local source:

```bash
uv tool install -e /path/to/deepwork
```

If you want the gateway to launch a specific binary regardless of `PATH`, use the top-level `mcp.servers.deepwork` override above.

### Agent starts a second workflow instead of resuming

Tell the agent to call `deepwork__get_active_workflow` first. The bootstrap hook already prompts this when it detects prior state on disk; if the hook did not run, the skill file will still produce the correct behavior when the agent reads it.

## Known limitations

- **Session-scoped state.** Parent/child OpenClaw sessions do not yet share one DeepWork workflow stack. Revisit when OpenClaw exposes parent/root session metadata in the bootstrap context.
- **Bundle distribution.** Until the bundle is published to ClawHub or npm, the install step requires a local clone of DeepWork.
- **No auto-update for MCP servers.** OpenClaw does not refresh plugin-declared MCP servers automatically. `uvx`'s own cache handles version updates, but a gateway restart may be needed to pick up a new binary.
7 changes: 7 additions & 0 deletions plugins/openclaw/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "deepwork",
"description": "Framework for AI-powered multi-step workflows with quality gates in OpenClaw",
"version": "0.14.0",
"skills": ["skills"],
"hooks": ["hooks"]
}
8 changes: 8 additions & 0 deletions plugins/openclaw/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"deepwork": {
"command": "uvx",
"args": ["deepwork", "serve", "--platform", "openclaw"]
}
}
}
48 changes: 48 additions & 0 deletions plugins/openclaw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# DeepWork for OpenClaw

This directory is an OpenClaw [Codex bundle](https://docs.openclaw.ai/plugins/bundles) that lets OpenClaw sessions use DeepWork's workflow and review MCP tools.

For how the integration works, why it is shaped this way, and troubleshooting beyond the basics, see [`doc/openclaw_design.md`](../../doc/openclaw_design.md).

## Prerequisites

- OpenClaw with bundle support
- [`uv`](https://docs.astral.sh/uv/) installed (so `uvx` is on PATH)
- a Git repository for the target project (DeepWork stores job definitions under `.deepwork/` and may create work branches)

You do **not** need to install the `deepwork` CLI separately. The bundle launches it via `uvx deepwork serve --platform openclaw`, which auto-fetches the published package on first use.

## Install

```bash
git clone https://github.com/Unsupervisedcom/deepwork.git
openclaw plugins install ./deepwork/plugins/openclaw
openclaw gateway restart
```

Then start a new OpenClaw session. Verify with:

```bash
openclaw plugins inspect deepwork
```

You should see bundle subtype `codex`, skill roots from `skills/`, a hook pack from `hooks/`, and an MCP server named `deepwork`.

## First run

Typical prompts:

- `Use DeepWork to create a workflow for shipping release notes.`
- `Use DeepWork to run the tutorial_writer workflow.`
- `Use DeepWork review on this change set.`

The bundled bootstrap hook will have already told the agent the correct `session_id` to use for DeepWork MCP calls and whether prior DeepWork state exists for the session.

## Runtime layout

- `.codex-plugin/plugin.json` — Codex bundle marker
- `.mcp.json` — declares the `deepwork` MCP server (`uvx deepwork serve --platform openclaw`)
- `skills/deepwork/SKILL.md`, `skills/review/SKILL.md` — agent-facing instructions for `/deepwork` and `/review`
- `hooks/deepwork-openclaw-bootstrap/` — `agent:bootstrap` hook that injects session and resume guidance

See the [design doc](../../doc/openclaw_design.md) for how these pieces fit together, known limitations (notably: session-scoped state across spawned sub-sessions), and troubleshooting for pinning a specific `deepwork` binary.
19 changes: 19 additions & 0 deletions plugins/openclaw/hooks/deepwork-openclaw-bootstrap/HOOK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: deepwork-openclaw-bootstrap
description: "Inject DeepWork session and resume guidance into OpenClaw bootstrap context"
metadata:
{
"openclaw":
{
"emoji": "🧭",
"events": ["agent:bootstrap"],
"install": [{ "id": "deepwork", "kind": "bundled", "label": "Bundled with DeepWork OpenClaw plugin" }],
},
}
---

# DeepWork OpenClaw Bootstrap

Injects a small synthetic bootstrap note that tells the agent which `session_id`
to use for DeepWork MCP tools in the current OpenClaw session, and whether it
should try `deepwork__get_active_workflow` to restore prior DeepWork state.
109 changes: 109 additions & 0 deletions plugins/openclaw/hooks/deepwork-openclaw-bootstrap/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import fs from "node:fs";
import path from "node:path";

const SYNTHETIC_NOTES = [
{
name: "BOOTSTRAP.md",
relativePath: ".deepwork/tmp/openclaw/DEEPWORK_OPENCLAW_BOOTSTRAP.md",
},
{
name: "TOOLS.md",
relativePath: ".deepwork/tmp/openclaw/DEEPWORK_OPENCLAW.md",
},
] as const;

function readTrimmedString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}

const handler = async (event: any) => {
if (event?.type !== "agent" || event?.action !== "bootstrap") {
return;
}

const context = event.context ?? {};
if (!Array.isArray(context.bootstrapFiles)) {
return;
}

const workspaceDir = readTrimmedString(context.workspaceDir);
const sessionId = readTrimmedString(context.sessionId);
if (!workspaceDir || !sessionId) {
return;
}

const sessionKey = readTrimmedString(context.sessionKey) || "(unknown)";
const agentId = readTrimmedString(context.agentId) || "(unknown)";
const syntheticNotes = SYNTHETIC_NOTES.map((note) => ({
...note,
path: path.join(workspaceDir, note.relativePath),
}));
const statePath = path.join(
workspaceDir,
".deepwork",
"tmp",
"sessions",
"openclaw",
`session-${sessionId}`,
"state.json",
);
const hasActiveState = fs.existsSync(statePath);

const content = `# DeepWork OpenClaw Runtime

Use these values when calling DeepWork MCP tools from this OpenClaw session:

- session_id: \`${sessionId}\`
- session_key: \`${sessionKey}\`
- agent_id: \`${agentId}\`
- workspace_dir: \`${workspaceDir}\`

Guidance:

- Use the current OpenClaw session's \`sessionId\` as DeepWork \`session_id\`.
- Ignore any stale \`BOOTSTRAP.md\` files or hardcoded \`session_id\` values elsewhere in the workspace. The current OpenClaw session values above win.
- In OpenClaw, leave DeepWork \`agent_id\` unset unless you intentionally want a separate agent-scoped DeepWork state file.
- DeepWork relative paths are rooted at \`workspace_dir\`, not the plugin bundle directory.
- ${
hasActiveState
? "DeepWork state already exists for this session. Call `deepwork__get_active_workflow` before starting a new workflow unless you are sure you want a second one."
: "No DeepWork state has been detected for this session yet."
}
- Before \`deepwork__finished_step\`, compare your outputs to \`step_expected_outputs\` or call \`deepwork__validate_step_outputs\`.
- For review work returned by DeepWork quality gates, prefer parallel OpenClaw sub-agents via \`sessions_spawn\`.
- Spawn all requested review sub-agents before waiting for completions.
- Keep DeepWork instruction paths workspace-relative; do not rewrite them as absolute host paths.
- Omit \`timeoutSeconds\` on review spawns so the runtime default applies. If a timeout value is required, use \`0\`.
- After all review spawns are accepted, use \`sessions_yield\` while you wait for completion events.
`;

for (const note of syntheticNotes) {
try {
fs.mkdirSync(path.dirname(note.path), { recursive: true });
fs.writeFileSync(note.path, content, "utf8");
} catch {
// Best-effort persistence for runtime session hints. The in-memory bootstrap
// injection below still gives the model the same guidance if disk writes fail.
}
}

context.bootstrapFiles = context.bootstrapFiles
.filter((file: any) => {
const filePath = readTrimmedString(file?.path);
const fileName = readTrimmedString(file?.name);
if (fileName === "BOOTSTRAP.md") {
return false;
}
return !syntheticNotes.some((note) => note.path === filePath);
})
.concat(
syntheticNotes.map((note) => ({
name: note.name,
path: note.path,
content,
missing: false,
})),
);
};

export default handler;
48 changes: 48 additions & 0 deletions plugins/openclaw/skills/deepwork/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: deepwork
description: "Start or continue DeepWork workflows in OpenClaw using MCP tools"
---

# DeepWork Workflow Manager

Execute multi-step DeepWork workflows in OpenClaw.

## Runtime Contract

- Read the injected DeepWork OpenClaw bootstrap note before using the tools.
- Use the `session_id` shown there for all DeepWork MCP calls in the current OpenClaw session.
- If the bootstrap note says prior DeepWork state may exist, call `deepwork__get_active_workflow` first to restore context before starting anything new.

## How to Use

1. Call `deepwork__get_workflows` to discover available workflows.
2. If resuming, call `deepwork__get_active_workflow`.
3. Call `deepwork__start_workflow` with `goal`, `job_name`, `workflow_name`, and `session_id`.
4. Follow the returned step instructions.
5. If `begin_step.step_inputs` shows any required input with `value: null`, stop before doing step work.
6. If the missing input is already clear from the user's request, restart the workflow with `deepwork__start_workflow(..., inputs={...})` so the value is populated from the beginning.
7. If the missing input is not clear, ask the user instead of fabricating outputs or calling `deepwork__finished_step`.
8. Never call `deepwork__finished_step` for a step whose required inputs are still missing.
9. Before submitting outputs, compare them to `step_expected_outputs` or call `deepwork__validate_step_outputs`.
10. Call `deepwork__finished_step` with your outputs when the step is done.
11. Handle the response: `needs_work`, `next_step`, or `workflow_complete`.

## Quality Gates

- DeepWork may require reviews before a step can advance.
- In OpenClaw, prefer launching those reviews as parallel sub-agents with `sessions_spawn`, then use `sessions_yield` to wait for completions.
- Spawn all review sub-agents before waiting, keep instruction paths workspace-relative, and do not set `timeoutSeconds` on review spawns unless you must use `0`.
- After applying any fixes, call `deepwork__finished_step` again.

## Navigation

- Use `deepwork__abort_workflow` if a workflow cannot be completed.
- Use `deepwork__go_to_step` to revisit an earlier step and clear later progress.

## Intent Parsing

When the user invokes `/deepwork`:

1. Always call `deepwork__get_workflows`.
2. If the request clearly matches one workflow, start it.
3. If multiple workflows could fit, summarize the closest matches and ask the user which one they want.
Loading
Loading