Skip to content

Add OpenClaw integration with dapr workflows#778

Open
yaron2 wants to merge 4 commits intomainfrom
openclaw
Open

Add OpenClaw integration with dapr workflows#778
yaron2 wants to merge 4 commits intomainfrom
openclaw

Conversation

@yaron2
Copy link
Copy Markdown
Member

@yaron2 yaron2 commented Apr 30, 2026

This PR adds a Dapr Workflow integration to OpenClaw - allowing users to make their openclaw agents immortal and recover from failures. In addition, this enables cross-device work which OpenClaw doesn't natively support today.

Added here is a GitHub workflow to publish to dapr/openclaw on NPM.

cc @WhitWaldo

Signed-off-by: yaron2 <schneider.yaron@live.com>
@yaron2 yaron2 requested review from a team as code owners April 30, 2026 21:40
@yaron2 yaron2 changed the title Add openclaw integration with dapr workflows Add OpenClaw integration with dapr workflows Apr 30, 2026
@WhitWaldo WhitWaldo requested a review from Copilot May 3, 2026 01:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new @dapr/openclaw extension plus an @dapr/openclaw-plugin package to add Dapr Workflow-backed durability to OpenClaw agent runs, and adds a dedicated GitHub Actions workflow to build/publish those packages. It fits into the repo as a new extension module that bridges OpenClaw agent execution with the existing Dapr workflow runtime.

Changes:

  • Adds the OpenClaw runtime extension (extensions/openclaw/src/*) that patches agent execution to run through Dapr Workflows and activities.
  • Adds the distributable OpenClaw plugin package, metadata, docs, and local crash-recovery demo assets.
  • Adds a dedicated GitHub Actions workflow to build and publish the OpenClaw runtime/plugin packages.

Reviewed changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
extensions/openclaw/tsconfig.json TypeScript build config for the new runtime package.
extensions/openclaw/test/plugins/crasher-tool/package.json Defines the test-only crash/retry plugin package.
extensions/openclaw/test/plugins/crasher-tool/openclaw.plugin.json Plugin metadata for the crash-recovery demo tool.
extensions/openclaw/test/plugins/crasher-tool/index.js Implements demo tools used to force and verify crash recovery.
extensions/openclaw/test/e2e/run-cli-crash-e2e.sh Adds a two-phase CLI crash-recovery demo harness.
extensions/openclaw/test/components/statestore.yaml Provides Redis-backed Dapr state store config for workflows.
extensions/openclaw/src/workflow.ts Defines the durable agent-loop workflow orchestration.
extensions/openclaw/src/types.ts Declares workflow/activity/runtime types for the extension.
extensions/openclaw/src/registry.ts Adds in-memory workflow-to-agent runtime context registry.
extensions/openclaw/src/patch.ts Patches OpenClaw agent execution to schedule/resume workflows.
extensions/openclaw/src/index.ts Exposes enable/disable entry points and starts the workflow runtime.
extensions/openclaw/src/activities.ts Implements LLM, tool, and event workflow activities.
extensions/openclaw/plugin/package.json Defines the publishable OpenClaw plugin package.
extensions/openclaw/plugin/package-lock.json Locks plugin package dependencies for local/plugin packaging.
extensions/openclaw/plugin/openclaw.plugin.json Plugin manifest for OpenClaw discovery.
extensions/openclaw/plugin/index.js Plugin entry point that resolves Agent and enables durable execution.
extensions/openclaw/plugin/README.md Usage docs for installing/enabling the plugin package.
extensions/openclaw/plugin/LICENSE License file for the plugin package.
extensions/openclaw/package.json Defines the publishable OpenClaw runtime package.
extensions/openclaw/README.md Main extension documentation and crash-recovery walkthrough.
extensions/openclaw/LICENSE License file for the runtime package.
.github/workflows/openclaw.yml Adds CI/publish automation for the new OpenClaw packages.
Files not reviewed (1)
  • extensions/openclaw/plugin/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread extensions/openclaw/src/activities.ts Outdated
Comment on lines +39 to +48
// Drain the Agent's steering queue (messages injected while the loop runs)
if (rt.config.getSteeringMessages) {
const steering: any[] = await rt.config.getSteeringMessages();
if (steering?.length) {
for (const msg of steering) {
await rt.emit({ type: "message_start", message: msg });
await rt.emit({ type: "message_end", message: msg });
messages.push(msg);
}
}
Comment on lines +72 to +86
- name: Install runtime dependencies
working-directory: extensions/openclaw
run: npm install

- name: Build runtime
working-directory: extensions/openclaw
run: npm run build

- name: Pack smoke test (@dapr/openclaw)
working-directory: extensions/openclaw
run: npm pack --dry-run

- name: Pack smoke test (@dapr/openclaw-plugin)
working-directory: extensions/openclaw/plugin
run: npm pack --dry-run
Comment thread extensions/openclaw/src/patch.ts Outdated
Comment on lines +67 to +68
const sessionId = this.sessionId || "default";
const workflowId = `openclaw-${sessionId}`;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a per-prompt workflow ID, but couldn't use idempotencyKey directl since it doesn't reach the Agent in current OpenClaw.
Agent.prompt(input, images) doesn't forward options (pi-agent-core@0.65.0/agent.js:213), and hookCtx exposes runId but not idempotencyKey (and runId is randomUUID() per attempt, so it'd break resume).
Used a SHA-256 over {messages, systemPrompt, toolNames} instead (with timestamp stripped). Same content + retry → same workflow (resume works); different prompts → different workflows. Two distinct calls with an identical content requires an upstream Openclaw cooperation which is a no-go

Comment thread extensions/openclaw/src/patch.ts
Comment thread extensions/openclaw/src/activities.ts
Comment thread extensions/openclaw/src/activities.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 23 changed files in this pull request and generated 8 comments.

Files not reviewed (1)
  • extensions/openclaw/plugin/package-lock.json: Language not supported

Comment on lines +68 to +79
// No tool calls → done
if (!llmResult.toolCalls.length || llmResult.error) {
yield ctx.callActivity(emitEvent, {
workflowId,
event: {
type: "turn_end",
message: llmResult.assistantMessage,
toolResults: [],
},
} as EmitEventInput);
break;
}
Comment on lines +47 to +52
while (iteration < input.maxIterations) {
// --- turn_start ---
yield ctx.callActivity(emitEvent, {
workflowId,
event: { type: "turn_start" },
} as EmitEventInput);
Comment on lines +94 to +98
// Deterministic per-prompt workflow ID — stable for retries of this
// exact prompt, distinct from other prompts in the same session.
const sessionId = this.sessionId || "default";
const workflowId = buildWorkflowId(sessionId, messages, context.systemPrompt, toolNames);

Comment on lines +35 to +46
function buildWorkflowId(sessionId: string, messages: any[], systemPrompt: string, toolNames: string[]): string {
// Strip volatile fields before hashing. Agent.normalizePromptInput in
// pi-agent-core stamps `timestamp: Date.now()` on every wrapped user
// message — including this in the hash would make every retry produce a
// different workflow ID, breaking crash recovery. The content + role
// identify the prompt; the timestamp does not.
const stable = JSON.stringify({ messages, systemPrompt, toolNames }, (key, value) =>
key === "timestamp" ? undefined : value,
);
const fingerprint = createHash("sha256").update(stable).digest("hex").slice(0, 16);
return `openclaw-${sessionId}-${fingerprint}`;
}
}

proto.runPromptMessages = async function runPromptMessagesDapr(messages: any[], options: any = {}): Promise<void> {
console.log("[dapr] runPromptMessages intercepted — routing through Dapr Workflow");
@@ -0,0 +1,129 @@
/*
Copyright 2025 The Dapr Authors
* and tool call becomes a durable activity with automatic crash recovery.
*
* Installation (canonical):
* openclaw plugin install @dapr/openclaw-plugin
Comment on lines +108 to +116
try {
// Check if a previous run left a workflow in RUNNING state (crash recovery).
let instanceId = workflowId;
let isResume = false;
try {
const existing = await workflowClient.getWorkflowState(workflowId, true);
if (existing && existing.runtimeStatus === WorkflowRuntimeStatus.RUNNING) {
console.log(`[dapr] Resuming existing workflow ${workflowId} (crash recovery)`);
isResume = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants