Skill: 0xtommythomas-dev/soulflow (version 1.1.2)

--- name: soulflow description: General-purpose AI workflow framework for OpenClaw. Build custom multi-step workflows for any task — dev, ops, research, content, or automation. Ships with dev workflow examples. homepage: https://github.com/0xtommythomas-dev/soulflow metadata: clawdbot: emoji: "⚙️" requires: bins: ["node"] config_files: - "~/.openclaw/openclaw.json" permissions: config: read: ["~/.openclaw/openclaw.json"] write: ["~/.openclaw/openclaw.json"] gateway: modify agents: create filesystem: read: ["~/.openclaw/workspace"] write: ["~/.openclaw/workspace/.soulflow", "~/.openclaw/agents/soulflow-worker"] credentials: inherit security_note: "Creates a dedicated 'soulflow-worker' agent with full tool access (read, write, edit, exec, browser) to execute workflow steps. Reads gateway config (~/.openclaw/openclaw.json) for authentication token. Modifies gateway config to add/manage worker agent. Worker inherits authProfiles from existing agents (grants access to external services like GitHub, cloud providers). All operations run locally with your existing OpenClaw permissions. Only install if you trust the skill author and have reviewed the code." --- # SoulFlow — Workflow Framework for OpenClaw **A framework for building custom AI workflows.** Each workflow is a series of steps that execute in isolated agent sessions with full tool access. Define your workflow in JSON, invoke it naturally, and let the agents handle the execution. **What you can build:** - Development pipelines (security audits, bug fixes, feature development) - Content workflows (research → draft → edit → publish) - Operations automation (deploy → verify → rollback-on-fail) - Research pipelines (gather → analyze → synthesize → report) - Any multi-step task that benefits from isolated, focused agent sessions **Ships with 3 example dev workflows** to show you how it works. Build your own for anything. ## Quick Start **Natural language (easiest):** Just tell your agent what you need: - "Run a security audit on my project at ~/myapp" - "Fix this bug: users can't login with Google OAuth in ~/webapp" - "Build a referral system for ~/webapp" Your agent reads this SKILL.md and invokes SoulFlow automatically. **Command line:** ```bash cd ~/.openclaw/workspace/soulflow # Run a security audit node soulflow.js run security-audit "Audit the codebase at ~/project for vulnerabilities" # Fix a bug node soulflow.js run bug-fix "Login returns 500 when email has uppercase letters in ~/myapp" # Build a feature node soulflow.js run feature-dev "Add dark mode toggle to the settings page in ~/myapp" ``` ## How It Works SoulFlow connects to your local OpenClaw gateway via WebSocket and runs each workflow step as an isolated agent session. A dedicated `soulflow-worker` agent is auto-created with minimal context — no memory bleed from your main agent. Each step: 1. Gets a fresh session (no context bloat) 2. Receives the task + output from previous steps 3. Has full tool access (read, write, edit, exec, browser) 4. Must complete its work and report results **Auto-notifications (v1.1.0+):** When workflows complete, SoulFlow automatically notifies the main agent session with results. No need to manually check status. ## Example Workflows (Included) **These are examples to show what's possible. Build your own for any domain.** ### security-audit **Scan → Prioritize → Fix → Verify** Development example: Reads your source files, identifies vulnerabilities by severity, applies fixes, then verifies them. ### bug-fix **Triage → Fix → Verify** Development example: Investigates the root cause by reading code, applies the fix, then verifies it didn't introduce regressions. ### feature-dev **Plan → Implement → Review** Development example: Architects the implementation plan, writes the code, then reviews for quality and correctness. **Want content workflows? Research pipelines? Deploy automation?** Create your own `.workflow.json` — see Custom Workflows below. ## Commands ```bash node soulflow.js run <workflow> "<task>" # Run a workflow node soulflow.js list # List available workflows node soulflow.js runs # List past runs node soulflow.js status [run-id] # Check run status node soulflow.js test # Test gateway connection ``` ## Natural Language (via your agent) **The agent knows how to invoke SoulFlow for you.** Just describe what you want: **Security audits:** - "Audit my app for security issues" - "Check ~/myapp for vulnerabilities" - "Scan the codebase for security problems" **Bug fixes:** - "Fix this bug: login fails when..." - "There's a problem with the payment flow" - "Users are seeing 500 errors when they..." **Features:** - "Build a referral system" - "Add dark mode to the settings page" - "Implement OAuth login with Google" **How it works:** 1. You tell your agent what you need 2. Your agent reads this SKILL.md 3. Agent invokes `node soulflow.js run <workflow> "<task>"` 4. SoulFlow runs the workflow and reports back **Pattern matching:** The agent matches your message to workflows: - Security audit → keywords: "audit", "security", "scan", "vulnerabilit" - Bug fix → keywords: "fix", "bug", "broken", "not working", "error" - Feature dev → keywords: "build", "add", "implement", "create", "feature" **No workflow matches?** Agent will ask which workflow you want or suggest creating a custom one. ## Custom Workflows **You can create workflows for ANY task.** Define them in JSON and place in the `workflows/` directory. ### Creating via Chat Tell your agent: > "Create a SoulFlow workflow for [your use case]" Examples: - "Create a workflow for content publishing: research topic → draft article → edit → publish to blog" - "Create a workflow for deployment: run tests → build → deploy → verify health checks → rollback if failed" - "Create a workflow for weekly reports: gather metrics → analyze trends → generate summary → send email" Your agent will: 1. Design the workflow steps 2. Write the `.workflow.json` file to `workflows/` 3. Show you how to run it ### Manual Creation Create a `.workflow.json` file in the `workflows/` directory: ```json { "id": "my-workflow", "name": "My Custom Workflow", "version": 1, "description": "What this workflow does", "steps": [ { "id": "step1", "name": "First Step", "input": "Do this thing: {{task}}", "expects": "STATUS: done", "maxRetries": 1 }, { "id": "step2", "name": "Second Step", "input": "Now do this based on step 1:\n\n{{step1_output}}\n\nOriginal task: {{task}}", "expects": "STATUS: done", "maxRetries": 1 } ] } ``` ### Variables - `{{task}}` — The user's original task description - `{{stepid_output}}` — Full output from a previous step (e.g. `{{scan_output}}`) - Any `KEY: value` lines in step output become variables (e.g. `ROOT_CAUSE: ...` → `{{root_cause}}`) ### Prompt Tips For best results, write prompts that: - Explicitly tell the agent to use tools: "Use `read` to examine the file", "Use `edit` to apply the fix" - Say "Do NOT just describe — actually do it" - End with "When done, end with: STATUS: done" ## Architecture - **Zero dependencies** — Pure Node.js 22 (native WebSocket) - **Gateway-native** — Connects via WebSocket with challenge-response auth - **Session isolation** — Each step in a fresh session - **Dedicated worker** — Auto-creates `soulflow-worker` agent with minimal brain files - **JSON state** — Run history saved to `~/.openclaw/workspace/.soulflow/runs/` - **10-minute timeout** per step (configurable) ## Requirements - OpenClaw 2026.2.x or later - Node.js 22+ (for native WebSocket) - Gateway with token auth configured ## Security & Permissions **What SoulFlow does to your OpenClaw instance:** 1. **Reads your gateway config** (`~/.openclaw/openclaw.json`) to obtain the authentication token needed to connect via WebSocket 2. **Modifies your gateway config** (`~/.openclaw/openclaw.json`) via `config.patch` to register the soulflow-worker agent 3. **Creates a dedicated worker agent** (`soulflow-worker`) with minimal brain files (SOUL.md only, no memory/history) 4. **Copies authProfiles from existing agents** — Worker inherits credentials for external services (GitHub, cloud providers, etc.) that your other agents use 5. **Grants the worker full tool access** (read, write, edit, exec, browser) — this is required for workflows to actually perform tasks 6. **Writes run state** to `~/.openclaw/workspace/.soulflow/runs/` as JSON files **Why these permissions are needed:** - **Config read/write**: Required to authenticate with the gateway and register the worker agent (same as `openclaw` CLI tool) - **Agent creation**: Each workflow step runs in an isolated session to prevent context bleed - **authProfiles inheritance**: Allows workflows to interact with external services (e.g., git push, cloud API calls) using your existing credentials - **Full tools**: Workflows need real capabilities (e.g., security-audit reads files, bug-fix edits code, deploy-pipeline pushes to git) - **Filesystem write**: Stores workflow history and allows workflows to create/modify files **Security considerations:** - Worker agent has NO access to your main agent's memory or history - Worker DOES inherit your external service credentials (authProfiles) — can access GitHub, cloud APIs, etc. - Workflows run with YOUR permissions (same as running commands yourself) - Malicious workflows could read/modify files, run commands, or access external services - **Only install SoulFlow if you trust the skill author** (review code on GitHub first) - **Only run workflows you trust** — custom workflows from untrusted sources could exfiltrate data or misuse credentials - Run SoulFlow in isolated/sandboxed environments if processing untrusted workflows **Recommended practices:** - Review built-in workflows before first use (especially security-audit and bug-fix) - Inspect custom `.workflow.json` files before running - Review GitHub repo (https://github.com/0xtommythomas-dev/soulflow) before installation - Run on non-production OpenClaw instances when testing new workflows - Back up important files before running workflows that modify code - Use BYOK (bring your own keys) mode if you want isolated credentials per workflow - Monitor `~/.openclaw/workspace/.soulflow/runs/` for workflow execution logs --- ## For Agents: How to Invoke SoulFlow When the user requests a workflow (security audit, bug fix, feature build, etc.), you should: 1. **Identify the workflow** by matching keywords: - Security audit: "audit", "security", "scan", "vulnerabilit" - Bug fix: "fix", "bug", "broken", "not working", "error" - Feature dev: "build", "add", "implement", "create", "feature" - Custom: check `workflows/*.workflow.json` for other options 2. **Extract the task description** — the user's description of what they want done 3. **Invoke SoulFlow** using exec: ```bash cd /root/.openclaw/workspace/soulflow && node soulflow.js run <workflow> "<task>" ``` 4. **Monitor the run** — SoulFlow will output the run ID, then show progress as each step completes 5. **Report results** — When complete, relay the final status to the user **Example:** ``` User: "Run a security audit on ~/myapp" You: [exec] cd /root/.openclaw/workspace/soulflow && node soulflow.js run security-audit "Audit ~/myapp for vulnerabilities" ``` **Creating workflows for users:** If the user asks you to create a custom workflow: 1. Design the workflow steps based on their requirements 2. Write a `.workflow.json` file to `/root/.openclaw/workspace/soulflow/workflows/` 3. Show them how to run it See CONTRIBUTING.md for workflow design best practices.

path: CHANGELOG.md
content: # Changelog

All notable changes to SoulFlow will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-02-12

### Added
- **Workflow completion notifications**: Workflows now automatically notify the main session when complete
- Success notifications include workflow name, run ID, duration, and key results
- Failure notifications include error details and failed step name
- Notifications sent via `chat.send` to `agent:main` session

### Changed
- Improved error handling for notification delivery (non-blocking)

## [1.0.0] - 2026-02-12

### Added
- Initial release of SoulFlow workflow framework
- Zero-dependency Node.js 22 implementation with native WebSocket
- Gateway-native integration with challenge-response auth
- Session isolation per workflow step
- Dedicated `soulflow-worker` agent with minimal context
- JSON workflow definitions with variable interpolation
- Natural language workflow invocation support
- Interactive workflow builder (`lib/workflow-builder.js`)
- Natural language handler for agent integration (`lib/nl-handler.js`)
- Three example dev workflows:
  - `security-audit`: Scan → Prioritize → Fix → Verify
  - `bug-fix`: Triage → Fix → Verify
  - `feature-dev`: Plan → Implement → Review
- Two general-purpose example workflows:
  - `content-pipeline`: Research → Draft → Edit
  - `deploy-pipeline`: Test → Build → Deploy → Verify
- CLI commands: `run`, `list`, `runs`, `status`, `test`
- Run history tracking in JSON state files
- 10-minute timeout per step (configurable)
- Comprehensive documentation (README.md, SKILL.md)
- MIT license
- Package.json for npm compatibility

### Architecture
- Pure Node.js 22 (no external dependencies)
- WebSocket connection to OpenClaw gateway
- Challenge-response authentication
- Isolated agent sessions per step
- Variable extraction from step output (KEY: value format)
- Automatic worker agent creation with minimal brain files
- JSON state persistence to `~/.openclaw/workspace/.soulflow/runs/`

### Requirements
- OpenClaw 2026.2.x or later
- Node.js 22+ (native WebSocket support)
- Gateway with token authentication configured


path: soulflow.js
content: #!/usr/bin/env node
/**
 * soulflow.js - Lightweight workflow engine for OpenClaw
 * 
 * Usage:
 *   node soulflow.js run <workflow> "<task description>"
 *   node soulflow.js status [run-id]
 *   node soulflow.js list
 *   node soulflow.js runs
 */

import path from 'path';
import { fileURLToPath } from 'url';
import * as Runner from './lib/runner.js';
import * as State from './lib/state.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const WORKFLOWS_DIR = path.join(__dirname, 'workflows');

/**
 * Show usage
 */
function showUsage() {
  console.log(`
soulflow - Lightweight workflow engine for OpenClaw

Usage:
  node soulflow.js run <workflow> "<task description>"
  node soulflow.js status [run-id]
  node soulflow.js list
  node soulflow.js runs
  node soulflow.js test

Commands:
  run <workflow> <task>   Run a workflow with a task description
  status [run-id]          Show status of a run (latest if no ID given)
  list                     List available workflows
  runs                     List all workflow runs
  test                     Test gateway connection

Examples:
  node soulflow.js run security-audit "Check the auth module"
  node soulflow.js status a1b2c3d4
  node soulflow.js list
  node soulflow.js runs
`);
}

/**
 * Main CLI handler
 */
async function main() {
  const args = process.argv.slice(2);
  
  if (args.length === 0) {
    showUsage();
    process.exit(0);
  }
  
  const command = args[0];
  
  try {
    switch (command) {
      case 'run': {
        if (args.length < 3) {
          console.error('Error: Missing workflow ID or task description');
          console.error('Usage: node soulflow.js run <workflow> "<task>"');
          process.exit(1);
        }
        
        const workflowId = args[1];
        const task = args.slice(2).join(' ');
        
        const result = await Runner.runWorkflow(workflowId, task, WORKFLOWS_DIR);
        process.exit(result.success ? 0 : 1);
        break;
      }
      
      case 'status': {
        const runId = args[1];
        
        if (runId) {
          await Runner.getStatus(runId);
        } else {
          // Show latest run
          const runs = State.listRuns();
          if (runs.length === 0) {
            console.log('[soulflow] No runs found');
            process.exit(0);
          }
          
          await Runner.getStatus(runs[0].runId);
        }
        break;
      }
      
      case 'list': {
        const workflows = Runner.listWorkflows(WORKFLOWS_DIR);
        
        console.log('\n[soulflow] Available workflows:\n');
        for (const wf of workflows) {
          console.log(`  ${wf.id}`);
          console.log(`    ${wf.name} - ${wf.description}`);
          console.log(`    Steps: ${wf.steps}`);
          console.log('');
        }
        break;
      }
      
      case 'runs': {
        const runs = State.listRuns();
        
        if (runs.length === 0) {
          console.log('\n[soulflow] No runs found\n');
          process.exit(0);
        }
        
        console.log('\n[soulflow] Recent runs:\n');
        for (const run of runs.slice(0, 10)) {
          const icon = run.status === 'done' ? '✓' : 
                       run.status === 'failed' ? '✗' : 
                       '⟳';
          console.log(`  ${icon} ${run.runId} - ${run.workflow} (${run.status})`);
          console.log(`    Created: ${new Date(run.createdAt).toLocaleString()}`);
          console.log('');
        }
        break;
      }
      
      case 'test': {
        console.log('[soulflow] Testing gateway connection...\n');
        
        const { GatewayClient } = await import('./lib/gateway.js');
        const gateway = new GatewayClient();
        
        try {
          await gateway.connect();
          console.log('[soulflow] ✓ Connected and authenticated');
          
          // Try a simple call
          const result = await gateway.call('config.get', {});
          console.log('[soulflow] ✓ Config call successful');
          console.log(`[soulflow] Gateway version: ${result.version || 'unknown'}`);
          
          gateway.close();
          console.log('[soulflow] ✓ Connection closed\n');
        } catch (error) {
          console.error(`[soulflow] ✗ Connection failed: ${error.message}\n`);
          process.exit(1);
        }
        break;
      }
      
      case 'help':
      case '--help':
      case '-h':
        showUsage();
        break;
      
      default:
        console.error(`Unknown command: ${command}`);
        showUsage();
        process.exit(1);
    }
  } catch (error) {
    console.error(`\n[soulflow] Error: ${error.message}\n`);
    process.exit(1);
  }
}

// Run CLI
main();


path: README.md
content: # SoulFlow — Workflow Framework for OpenClaw

**Build custom AI workflows for any task.** Each workflow is a series of steps that execute in isolated agent sessions with full tool access. Define your workflow in JSON, invoke it naturally, and let the agents handle the execution.

## What You Can Build

- **Development pipelines** (security audits, bug fixes, feature development)
- **Content workflows** (research → draft → edit → publish)
- **Operations automation** (deploy → verify → rollback-on-fail)
- **Research pipelines** (gather → analyze → synthesize → report)
- **Any multi-step task** that benefits from isolated, focused agent sessions

**Ships with 3 example dev workflows** to demonstrate the framework. Build your own for anything.

## Quick Start

### Natural Language (Easiest)

Just tell your agent what you need:
```
"Run a security audit on my project at ~/myapp"
"Fix this bug: users can't login with Google OAuth in ~/webapp"
"Build a referral system for ~/webapp"
```

Your agent reads `SKILL.md` and invokes SoulFlow automatically.

### Command Line

```bash
cd ~/.openclaw/workspace/soulflow

# Run a security audit
node soulflow.js run security-audit "Audit the codebase at ~/project for vulnerabilities"

# Fix a bug
node soulflow.js run bug-fix "Login returns 500 when email has uppercase letters in ~/myapp"

# Build a feature
node soulflow.js run feature-dev "Add dark mode toggle to the settings page in ~/myapp"
```

## How It Works

SoulFlow connects to your local OpenClaw gateway via WebSocket and runs each workflow step as an isolated agent session. A dedicated `soulflow-worker` agent is auto-created with minimal context — no memory bleed from your main agent.

**Each step:**
1. Gets a fresh session (no context bloat)
2. Receives the task + output from previous steps
3. Has full tool access (`read`, `write`, `edit`, `exec`, `browser`)
4. Must complete its work and report results

**Automatic notifications:**
When a workflow completes (success or failure), SoulFlow automatically sends a notification to your main agent session with:
- Workflow name and run ID
- Duration and step status
- Key results (extracted from output)
- Error details (if failed)

Your agent will relay the notification to you — no need to manually check status.

## Example Workflows (Included)

These demonstrate what's possible. Build your own for any domain.

### security-audit
**Scan → Prioritize → Fix → Verify**
Development example: Reads your source files, identifies vulnerabilities by severity, applies fixes, then verifies them.

### bug-fix
**Triage → Fix → Verify**
Development example: Investigates the root cause by reading code, applies the fix, then verifies it didn't introduce regressions.

### feature-dev
**Plan → Implement → Review**
Development example: Architects the implementation plan, writes the code, then reviews for quality and correctness.

**Want content workflows? Research pipelines? Deploy automation?** Create your own `.workflow.json` — see below.

## Creating Custom Workflows

### Via Chat

Tell your agent:
```
"Create a SoulFlow workflow for content publishing: research topic → draft article → edit → publish to blog"
```

Your agent will:
1. Design the workflow steps
2. Write the `.workflow.json` file to `workflows/`
3. Show you how to run it

### Via Interactive Builder

```bash
node lib/workflow-builder.js
```

Follow the prompts to define your workflow.

### Manual Creation

Create a `.workflow.json` file in the `workflows/` directory:

```json
{
  "id": "content-pipeline",
  "name": "Content Publishing Pipeline",
  "version": 1,
  "description": "Research, draft, edit, and publish blog content",
  "steps": [
    {
      "id": "research",
      "name": "Research Topic",
      "input": "Research the topic: {{task}}\n\nUse web_search and web_fetch to gather information. Create a research summary with key points, sources, and quotes.\n\nWhen done, end with: STATUS: done\nRESEARCH_FILE: path/to/research.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "draft",
      "name": "Draft Article",
      "input": "Write a blog post based on this research:\n\n{{research_output}}\n\nOriginal topic: {{task}}\n\nCreate a well-structured article with intro, body, and conclusion. Save to a markdown file.\n\nWhen done, end with: STATUS: done\nDRAFT_FILE: path/to/draft.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "edit",
      "name": "Edit and Polish",
      "input": "Edit this draft for clarity, grammar, and flow:\n\nDraft file: {{draft_file}}\n\nMake improvements using the `edit` tool. Focus on readability and engagement.\n\nWhen done, end with: STATUS: done\nFINAL_FILE: path/to/final.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    }
  ]
}
```

### Variables

- `{{task}}` — The user's original task description
- `{{stepid_output}}` — Full output from a previous step (e.g. `{{research_output}}`)
- Any `KEY: value` lines in step output become variables (e.g. `DRAFT_FILE: ...` → `{{draft_file}}`)

### Prompt Tips

For best results, write prompts that:
- Explicitly tell the agent to use tools: "Use `read` to examine the file", "Use `edit` to apply the fix"
- Say "Do NOT just describe — actually do it"
- End with "When done, end with: STATUS: done"

## Commands

```bash
node soulflow.js run <workflow> "<task>"    # Run a workflow
node soulflow.js list                       # List available workflows
node soulflow.js runs                       # List past runs
node soulflow.js status [run-id]            # Check run status
node soulflow.js test                       # Test gateway connection
```

## Architecture

- **Zero dependencies** — Pure Node.js 22 (native WebSocket)
- **Gateway-native** — Connects via WebSocket with challenge-response auth
- **Session isolation** — Each step in a fresh session
- **Dedicated worker** — Auto-creates `soulflow-worker` agent with minimal brain files
- **JSON state** — Run history saved to `~/.openclaw/workspace/.soulflow/runs/`
- **10-minute timeout** per step (configurable)

## Requirements

- OpenClaw 2026.2.x or later
- Node.js 22+ (for native WebSocket)
- Gateway with token auth configured

## Security & Permissions

**SoulFlow requires elevated permissions to function:**

- **Reads gateway config** to obtain authentication token
- **Creates a worker agent** (`soulflow-worker`) with full tool access
- **Modifies gateway configuration** to add/manage the worker agent
- **Writes to filesystem** for run state and workflow outputs

These permissions allow workflows to read files, edit code, and run commands — the same capabilities you have when using OpenClaw directly. **Only run workflows from trusted sources.** Review workflow JSON files before execution, especially custom workflows from third parties.

See [Security & Permissions](SKILL.md#security--permissions) for detailed information.

## Installation

### Via ClawHub (Recommended)

```bash
openclaw skills install soulflow
```

### Manual

```bash
cd ~/.openclaw/workspace
git clone https://github.com/soulstack/soulflow.git
cd soulflow
node soulflow.js test
```

## License

MIT

## Credits

Built by [SoulStack](https://soulstack.app) as a framework for multi-step AI workflows. Inspired by the need for better dev automation, generalized for any domain.


path: PUBLICATION_CHECKLIST.md
content: # SoulFlow v1.1.0 — ClawHub Publication Checklist

## ✅ Core Functionality
- [x] Zero-dependency Node.js 22 implementation
- [x] Gateway WebSocket integration
- [x] Session isolation per step
- [x] Dedicated worker agent creation
- [x] Variable extraction and interpolation
- [x] Run state persistence (JSON)
- [x] **Auto-notifications (v1.1.0)** — Workflows notify main session on completion/failure

## ✅ Workflows (6 total)
**Dev Examples (3):**
- [x] security-audit (Scan → Prioritize → Fix → Verify)
- [x] bug-fix (Triage → Fix → Verify)
- [x] feature-dev (Plan → Implement → Review)

**General Examples (3):**
- [x] bug-scan-fix (Scan → Triage → Fix → Verify) — proactive bug discovery
- [x] content-pipeline (Research → Draft → Edit)
- [x] deploy-pipeline (Test → Build → Deploy → Verify)

## ✅ Documentation
- [x] README.md (6.3KB) — comprehensive usage guide
- [x] SKILL.md (7.7KB) — OpenClaw skill definition with agent instructions
- [x] CONTRIBUTING.md (3.2KB) — contribution guidelines
- [x] CHANGELOG.md — v1.0.0 + v1.1.0 entries
- [x] LICENSE (MIT)

## ✅ Tooling
- [x] CLI commands (run, list, runs, status, test)
- [x] Natural language handler (lib/nl-handler.js)
- [x] Interactive workflow builder (lib/workflow-builder.js)

## ✅ Package Metadata
- [x] package.json (name, version 1.1.0, keywords, repo)
- [x] .gitignore (Node + OpenClaw state)
- [x] ES modules (`"type": "module"`)

## ✅ Testing
- [x] Connection test (`node soulflow.js test`)
- [x] Real production run (bug-scan-fix on SoulStack — 30 bugs found, 5 critical fixed)
- [x] Multiple concurrent workflows (f8bc9ffe, fa3c7158)

## ✅ ClawHub Requirements
- [x] SKILL.md with proper frontmatter (name, description, homepage, metadata)
- [x] Emoji: ⚙️
- [x] Requirements: Node.js 22+ (bins: ["node"])
- [x] Homepage: https://github.com/soulstack/soulflow

## 🔄 Remaining
- [ ] Create GitHub repo at `soulstack/soulflow`
- [ ] Push code to GitHub
- [ ] Submit to ClawHub
- [ ] Test installation via `openclaw skills install soulflow`

## 📁 Structure
```
soulflow/
├── SKILL.md (OpenClaw skill definition)
├── README.md (comprehensive docs)
├── LICENSE (MIT)
├── CHANGELOG.md (v1.1.0)
├── CONTRIBUTING.md (contribution guide)
├── package.json (npm metadata)
├── .gitignore
├── soulflow.js (main CLI)
├── lib/
│   ├── gateway.js (WS client)
│   ├── runner.js (workflow executor + notifications)
│   ├── state.js (run persistence)
│   ├── nl-handler.js (natural language → workflow)
│   └── workflow-builder.js (interactive creator)
└── workflows/
    ├── security-audit.workflow.json (dev example)
    ├── bug-fix.workflow.json (dev example)
    ├── feature-dev.workflow.json (dev example)
    ├── bug-scan-fix.workflow.json (proactive discovery)
    ├── content-pipeline.workflow.json (content example)
    └── deploy-pipeline.workflow.json (ops example)
```

## Key Features (Marketing)
1. **General-purpose framework** — not just dev tools
2. **Zero dependencies** — pure Node.js 22
3. **Session isolation** — no context bleed
4. **Natural language** — "run a security audit" just works
5. **Auto-notifications** — get notified when workflows complete
6. **User-created workflows** — build custom pipelines via chat or interactive CLI
7. **Example workflows** — 6 workflows spanning dev, ops, content domains

---

**Status:** ✅ Ready for GitHub + ClawHub publication
**Version:** 1.1.0
**Date:** 2026-02-12


path: package.json
content: {
  "name": "@soulstack/soulflow",
  "version": "1.1.0",
  "description": "General-purpose AI workflow framework for OpenClaw. Build custom multi-step workflows for any task — dev, ops, research, content, or automation.",
  "main": "soulflow.js",
  "type": "module",
  "scripts": {
    "test": "node soulflow.js test"
  },
  "keywords": [
    "openclaw",
    "workflow",
    "ai",
    "automation",
    "agent",
    "pipeline",
    "llm",
    "framework"
  ],
  "author": "SoulStack (Pachifunk)",
  "license": "MIT",
  "engines": {
    "node": ">=22.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/soulstack/soulflow.git"
  },
  "bugs": {
    "url": "https://github.com/soulstack/soulflow/issues"
  },
  "homepage": "https://github.com/soulstack/soulflow#readme",
  "dependencies": {},
  "peerDependencies": {
    "openclaw": ">=2026.2.0"
  }
}


path: CONTRIBUTING.md
content: # Contributing to SoulFlow

Thank you for your interest in contributing to SoulFlow! This document provides guidelines for contributing workflows, bug fixes, and features.

## Ways to Contribute

### 1. Share Workflows

The best way to contribute is by sharing your custom workflows. We're building a library of workflows for different domains:

- **Content creation** (research, writing, editing, publishing)
- **DevOps** (CI/CD, deployments, monitoring, incident response)
- **Research** (data gathering, analysis, synthesis, reporting)
- **Operations** (task automation, scheduling, notifications)
- **Testing** (test generation, execution, reporting)

**To share a workflow:**

1. Create your `.workflow.json` file (see README for format)
2. Test it thoroughly with `node soulflow.js run <workflow> "<task>"`
3. Submit a PR to add it to the `workflows/` directory
4. Include a description of what it does and example use cases

### 2. Report Bugs

Found a bug? Open an issue with:
- What you expected to happen
- What actually happened
- Steps to reproduce
- Your OpenClaw and Node.js versions
- Relevant logs from `~/.openclaw/workspace/.soulflow/runs/<run-id>/`

### 3. Suggest Features

Have an idea? Open an issue describing:
- The problem you're trying to solve
- Your proposed solution
- Why it would be useful to others

### 4. Code Contributions

**Before submitting a PR:**
- Discuss major changes in an issue first
- Keep changes focused (one feature/fix per PR)
- Test your changes with real workflows
- Update documentation if needed

**Code style:**
- Follow existing patterns
- Use clear variable names
- Add comments for complex logic
- Keep functions small and focused

## Development Setup

```bash
# Clone the repo
git clone https://github.com/soulstack/soulflow.git
cd soulflow

# Test connection
node soulflow.js test

# Run a workflow
node soulflow.js run security-audit "Test task"

# Check run history
node soulflow.js runs
```

## Workflow Design Best Practices

**Good workflows:**
- Have clear, descriptive step names
- Use explicit tool instructions ("Use `read` to...")
- Include "Do NOT just describe — actually do it" in prompts
- End with consistent status markers ("STATUS: done")
- Extract key variables with `KEY: value` format
- Keep steps focused on one task each

**Avoid:**
- Vague instructions ("figure out how to...")
- Skipping tool usage (describing instead of doing)
- Steps that depend on manual intervention
- Overly broad steps that do too much

## Testing Workflows

Before submitting:
1. Run the workflow end-to-end at least 3 times
2. Test with different task descriptions
3. Verify each step produces the expected output
4. Check that variables are correctly extracted and passed
5. Ensure timeouts are reasonable (default: 10 minutes per step)

## Documentation

When adding workflows:
- Add a section to README.md with example usage
- Document any special requirements (tools, APIs, etc.)
- Include sample output or screenshots if helpful

## License

By contributing, you agree that your contributions will be licensed under the MIT License.

## Questions?

Open an issue or reach out to [@TommyThomas0x](https://twitter.com/TommyThomas0x).


path: lib/nl-handler.js
content: /**
 * Natural language workflow invocation
 * Called by the main agent when user requests a workflow in conversation
 */

const { spawn } = require('child_process');
const path = require('path');

const WORKFLOW_PATTERNS = {
  'security-audit': [
    /security audit/i,
    /audit.*security/i,
    /scan.*vulnerabilit/i,
    /check.*security/i,
    /find.*vulnerabilit/i
  ],
  'bug-fix': [
    /fix.*bug/i,
    /bug.*fix/i,
    /issue.*fix/i,
    /problem.*fix/i,
    /broken/i,
    /not working/i,
    /throws.*error/i
  ],
  'feature-dev': [
    /build.*feature/i,
    /add.*feature/i,
    /implement/i,
    /create.*feature/i,
    /develop.*feature/i,
    /new feature/i
  ]
};

/**
 * Match user message to workflow
 */
function matchWorkflow(message) {
  for (const [workflow, patterns] of Object.entries(WORKFLOW_PATTERNS)) {
    for (const pattern of patterns) {
      if (pattern.test(message)) {
        return workflow;
      }
    }
  }
  return null;
}

/**
 * Extract task description from message
 * Strips common prefixes like "run", "do", "please"
 */
function extractTask(message) {
  return message
    .replace(/^(please|can you|could you|run|do|execute)\s+/i, '')
    .replace(/^(a|an|the)\s+/i, '')
    .trim();
}

/**
 * Run workflow from natural language
 * Returns a promise that resolves with run ID
 */
function invokeWorkflow(userMessage) {
  return new Promise((resolve, reject) => {
    const workflow = matchWorkflow(userMessage);
    
    if (!workflow) {
      reject(new Error('Could not match message to a workflow. Available: security-audit, bug-fix, feature-dev'));
      return;
    }

    const task = extractTask(userMessage);
    
    if (!task) {
      reject(new Error('Could not extract task description from message'));
      return;
    }

    // Spawn soulflow.js as child process
    const soulflowPath = path.join(__dirname, '..', 'soulflow.js');
    const child = spawn('node', [soulflowPath, 'run', workflow, task], {
      cwd: path.dirname(soulflowPath),
      env: process.env,
      stdio: ['ignore', 'pipe', 'pipe']
    });

    let stdout = '';
    let stderr = '';
    let runId = null;

    child.stdout.on('data', (chunk) => {
      stdout += chunk.toString();
      // Extract run ID from output
      const match = stdout.match(/Run ID: ([a-f0-9]+)/);
      if (match) {
        runId = match[1];
      }
    });

    child.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
    });

    child.on('close', (code) => {
      if (code === 0 && runId) {
        resolve({
          runId,
          workflow,
          task,
          message: `Started ${workflow} workflow (run ID: ${runId})`
        });
      } else {
        reject(new Error(stderr || 'Workflow failed to start'));
      }
    });
  });
}

module.exports = { matchWorkflow, extractTask, invokeWorkflow };


path: lib/gateway.js
content: /**
 * lib/gateway.js - WebSocket Gateway Client
 * Connects to OpenClaw gateway and provides methods for chat/sessions
 */

import fs from 'fs';
import path from 'path';
import os from 'os';
import { EventEmitter } from 'events';

export class GatewayClient extends EventEmitter {
  constructor() {
    super();
    this.ws = null;
    this.connected = false;
    this.authenticated = false;
    this.requestId = 0;
    this.pendingRequests = new Map();
    this.chatBuffers = new Map();
    this.config = null;
  }

  /**
   * Load OpenClaw config
   */
  loadConfig() {
    const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
    
    if (!fs.existsSync(configPath)) {
      throw new Error(`OpenClaw config not found at ${configPath}`);
    }
    
    const content = fs.readFileSync(configPath, 'utf8');
    this.config = JSON.parse(content);
    
    if (!this.config.gateway?.auth?.token) {
      throw new Error('No gateway auth token found in config');
    }
    
    return this.config;
  }

  /**
   * Connect to gateway WebSocket
   */
  async connect() {
    if (!this.config) {
      this.loadConfig();
    }
    
    const port = this.config.gateway?.port || 18789;
    const wsUrl = `ws://127.0.0.1:${port}`;
    
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        if (this.ws) {
          this.ws.close();
        }
        reject(new Error('Connection timeout - gateway may not be responding'));
      }, 10000);
      
      let challenged = false;
      
      try {
        this.ws = new WebSocket(wsUrl);
        
        this.ws.onopen = () => {
          this.connected = true;
          // Wait for connect.challenge event from gateway
        };
        
        this.ws.onmessage = (event) => {
          let msg;
          try { msg = JSON.parse(event.data); } catch { return; }
          
          // Handle the connect.challenge → connect handshake
          if (msg.type === 'event' && msg.event === 'connect.challenge' && !challenged) {
            challenged = true;
            const [REDACTED_SECRET];
            this.ws.send(JSON.stringify({
              type: 'req',
              id: String(++this.requestId),
              method: 'connect',
              params: {
                minProtocol: 3,
                maxProtocol: 3,
                client: { id: 'cli', version: '2026.2.9', platform: 'linux', mode: 'cli' },
                role: 'operator',
                scopes: ['operator.read', 'operator.write', 'operator.admin'],
                caps: [],
                commands: [],
                permissions: {},
                auth: { token },
                locale: 'en-US',
                userAgent: 'soulflow/1.0.0'
              }
            }));
            this._connectId = String(this.requestId);
            return;
          }
          
          // Handle connect response
          if (msg.type === 'res' && msg.id === this._connectId) {
            if (msg.ok) {
              this.authenticated = true;
              clearTimeout(timeout);
              // Switch to normal message handler
              this.ws.onmessage = (ev) => this.handleMessage(ev.data);
              resolve();
            } else {
              clearTimeout(timeout);
              reject(new Error(`Connect failed: ${msg.error?.message || 'unknown'}`));
            }
            return;
          }
        };
        
        this.ws.onerror = (error) => {
          clearTimeout(timeout);
          this.emit('error', error);
          reject(new Error(`WebSocket error: ${error.message || 'unknown'}`));
        };
        
        this.ws.onclose = (event) => {
          clearTimeout(timeout);
          this.connected = false;
          this.authenticated = false;
          this.emit('close');
          if (!this.authenticated) {
            reject(new Error(`Connection closed: ${event.reason || 'unknown'}`));
          }
        };
      } catch (err) {
        clearTimeout(timeout);
        reject(err);
      }
    });
  }

  /**
   * Handle incoming WebSocket message
   */
  handleMessage(data) {
    let msg;
    try {
      msg = JSON.parse(data);
    } catch (e) {
      console.error('[gateway] Invalid JSON:', data);
      return;
    }
    
    // Handle response to a pending request
    if (msg.type === 'res' && msg.id && this.pendingRequests.has(msg.id)) {
      const { resolve, reject } = this.pendingRequests.get(msg.id);
      this.pendingRequests.delete(msg.id);
      
      if (!msg.ok) {
        reject(new Error(msg.error?.message || 'Request failed'));
      } else {
        resolve(msg.payload || msg.result || { ok: true });
      }
      return;
    }
    
    // Handle chat events
    if (msg.type === 'event' && msg.event === 'chat' && msg.payload) {
      this.handleChatEvent(msg.payload);
      return;
    }
    
    // Other events
    this.emit('message', msg);
  }

  /**
   * Handle chat streaming events
   */
  handleChatEvent(payload) {
    const { sessionKey, state, message } = payload;
    
    if (!sessionKey) return;
    
    // Initialize buffer for this session if needed
    if (!this.chatBuffers.has(sessionKey)) {
      this.chatBuffers.set(sessionKey, {
        chunks: [],
        complete: false
      });
    }
    
    const buffer = this.chatBuffers.get(sessionKey);
    
    // Extract text from message content
    if (message?.content) {
      for (const part of message.content) {
        if (part.type === 'text' && part.text) {
          if (state === 'delta') {
            // Delta contains the FULL text so far (cumulative), not incremental
            // Just store the latest — we'll use the final one
            buffer.latestText = part.text;
          }
        }
      }
    }
    
    // Check for stream end — state "final" means complete
    if (state === 'final') {
      buffer.complete = true;
      // Use the final message text (most complete version)
      let fullText = '';
      if (message?.content) {
        for (const part of message.content) {
          if (part.type === 'text' && part.text) {
            fullText = part.text;
          }
        }
      }
      // Fallback to latest delta if final has no text
      if (!fullText && buffer.latestText) fullText = buffer.latestText;
      buffer.fullText = fullText;
      this.emit('chat_complete', { sessionKey, text: buffer.fullText });
    }
  }

  /**
   * Call a gateway method
   */
  async call(method, params = {}) {
    if (!this.connected) {
      throw new Error('Not connected to gateway');
    }
    
    return new Promise((resolve, reject) => {
      const id = String(++this.requestId);
      
      this.pendingRequests.set(id, { resolve, reject });
      
      const frame = JSON.stringify({ type: 'req', id, method, params });
      this.ws.send(frame);
      
      // Timeout after 30 seconds
      setTimeout(() => {
        if (this.pendingRequests.has(id)) {
          this.pendingRequests.delete(id);
          reject(new Error(`Request ${method} timed out`));
        }
      }, 30000);
    });
  }

  /**
   * Send a chat message and wait for complete response
   */
  async sendChat(sessionKey, message, agentId = 'main') {
    // Clear any existing buffer
    this.chatBuffers.delete(sessionKey);
    
    const idempotencyKey = `soulflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    
    // Send the chat message — agentId is encoded in sessionKey, not as a param
    await this.call('chat.send', {
      sessionKey,
      message,
      idempotencyKey
    });
    
    // Wait for the response to complete
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Chat response timeout'));
      }, 600000); // 10 minute timeout
      
      const checkComplete = () => {
        const buffer = this.chatBuffers.get(sessionKey);
        if (buffer && buffer.complete) {
          clearTimeout(timeout);
          this.removeListener('chat_complete', onComplete);
          resolve(buffer.fullText);
        }
      };
      
      const onComplete = (data) => {
        if (data.sessionKey === sessionKey) {
          clearTimeout(timeout);
          this.removeListener('chat_complete', onComplete);
          resolve(data.text);
        }
      };
      
      this.on('chat_complete', onComplete);
      
      // Check if already complete
      checkComplete();
    });
  }

  /**
   * Close connection
   */
  close() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
    this.connected = false;
    this.authenticated = false;
  }
}


path: lib/workflow-builder.js
content: #!/usr/bin/env node

/**
 * Interactive workflow builder
 * Helps create custom workflow JSON definitions
 */

const fs = require('fs');
const path = require('path');
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function question(query) {
  return new Promise(resolve => rl.question(query, resolve));
}

async function main() {
  console.log('\n🌊 SoulFlow Workflow Builder\n');
  
  const id = await question('Workflow ID (lowercase-with-dashes): ');
  const name = await question('Workflow Name (human-readable): ');
  const description = await question('Description: ');
  
  console.log('\n📋 Define Steps\n');
  console.log('For each step, provide:');
  console.log('- ID: step identifier (e.g. "research", "draft", "edit")');
  console.log('- Name: human-readable name');
  console.log('- Input: the prompt/instructions for the agent');
  console.log('- Expects: what output format to expect (e.g. "STATUS: done")');
  console.log('\nType "done" when finished adding steps.\n');
  
  const steps = [];
  let stepNum = 1;
  
  while (true) {
    const stepId = await question(`\nStep ${stepNum} ID (or "done"): `);
    if (stepId.toLowerCase() === 'done') break;
    
    const stepName = await question(`Step ${stepNum} Name: `);
    
    console.log('\nStep Input (multi-line, end with empty line):');
    const inputLines = [];
    while (true) {
      const line = await question('> ');
      if (line === '') break;
      inputLines.push(line);
    }
    const input = inputLines.join('\n');
    
    const expects = await question('Expects (e.g. "STATUS: done"): ');
    const maxRetries = parseInt(await question('Max Retries (default: 1): ') || '1');
    
    steps.push({
      id: stepId,
      name: stepName,
      input,
      expects,
      maxRetries
    });
    
    stepNum++;
  }
  
  const workflow = {
    id,
    name,
    version: 1,
    description,
    steps
  };
  
  const workflowsDir = path.join(__dirname, '..', 'workflows');
  const filePath = path.join(workflowsDir, `${id}.workflow.json`);
  
  // Ensure workflows dir exists
  if (!fs.existsSync(workflowsDir)) {
    fs.mkdirSync(workflowsDir, { recursive: true });
  }
  
  fs.writeFileSync(filePath, JSON.stringify(workflow, null, 2));
  
  console.log(`\n✅ Workflow saved to: ${filePath}\n`);
  console.log(`Run it with: node soulflow.js run ${id} "your task description"\n`);
  
  rl.close();
}

main().catch(err => {
  console.error('Error:', err);
  process.exit(1);
});


path: lib/runner.js
content: /**
 * lib/runner.js - Workflow Execution Engine
 * Loads workflows, executes steps with dedicated worker agent
 */

import fs from 'fs';
import path from 'path';
import { GatewayClient } from './gateway.js';
import * as State from './state.js';

const WORKER_AGENT_ID = 'soulflow-worker';
const WORKER_SOUL = `You are a workflow step executor. Your job is to complete tasks precisely and thoroughly.

Rules:
- USE TOOLS. Read files with the read tool. Edit files with the edit tool. Run commands with exec.
- Do the actual work. Don't just describe what should be done — do it.
- Be thorough. When scanning code, read the actual files. When fixing bugs, edit the actual code.
- Report what you did concretely. File names, line numbers, specific changes.
- When done, end your response with: STATUS: done
- If you can't complete the task, explain exactly why and end with: STATUS: blocked`;

/**
 * Load a workflow definition
 */
export function loadWorkflow(workflowId, workflowsDir) {
  const workflowPath = path.join(workflowsDir, `${workflowId}.workflow.json`);
  if (!fs.existsSync(workflowPath)) {
    throw new Error(`Workflow ${workflowId} not found at ${workflowPath}`);
  }
  return JSON.parse(fs.readFileSync(workflowPath, 'utf8'));
}

/**
 * Ensure the soulflow-worker agent exists with minimal brain files
 */
async function ensureWorkerAgent(gateway) {
  try {
    const snapshot = await gateway.call('config.get', {});
    const config = snapshot.config || snapshot;
    const agents = config.agents || {};
    const list = Array.isArray(agents.list) ? agents.list : [];
    
    const existing = list.find(a => a.id === WORKER_AGENT_ID);
    if (!existing) {
      console.log(`[soulflow] Creating worker agent...`);
      
      const homeDir = process.env.HOME || '/home/openclaw';
      const stateDir = `${homeDir}/.openclaw`;
      
      const newAgent = {
        id: WORKER_AGENT_ID,
        name: 'SoulFlow Worker',
        workspace: `${stateDir}/workspace`,
        identity: { name: 'SoulFlow Worker', theme: 'Workflow executor', emoji: '⚙️' },
        tools: { profile: 'full' }
      };
      
      // Copy authProfiles from first agent if available
      if (list.length > 0 && list[0].authProfiles) {
        newAgent.authProfiles = list[0].authProfiles;
      }
      
      const newList = [...list, newAgent];
      const patchParams = { raw: JSON.stringify({ agents: { list: newList } }, null, 2) };
      if (snapshot.hash) patchParams.baseHash = snapshot.hash;
      await gateway.call('config.patch', patchParams);
      
      // Wait for gateway to restart after config change
      await new Promise(r => setTimeout(r, 5000));
      
      // Reconnect since gateway restarts on config.patch
      try {
        gateway.close();
      } catch {}
      await gateway.connect();
      console.log(`[soulflow] Reconnected after config change ✓`);
      
      // Write minimal SOUL.md to the worker agent's directory
      const soulPath = `${stateDir}/agents/${WORKER_AGENT_ID}/SOUL.md`;
      const agentDir = `${stateDir}/agents/${WORKER_AGENT_ID}`;
      
      // Create agent dir and write SOUL.md via filesystem
      const fsMod = await import('fs');
      fsMod.default.mkdirSync(agentDir, { recursive: true });
      fsMod.default.writeFileSync(soulPath, WORKER_SOUL);
      
      console.log(`[soulflow] Worker agent created ✓`);
    }
  } catch (err) {
    console.log(`[soulflow] Worker agent setup warning: ${err.message}`);
    console.log(`[soulflow] Falling back to "main" agent`);
    return 'main';
  }
  return WORKER_AGENT_ID;
}

/**
 * Substitute {{variables}} in a string
 */
function substituteVariables(text, variables) {
  return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
    return variables[key] !== undefined ? String(variables[key]) : match;
  });
}

/**
 * Parse KEY: value pairs from response text
 */
function parseOutputVariables(text) {
  const variables = {};
  const lines = text.split('\n');
  
  let currentKey = null;
  let currentValue = [];
  
  for (const line of lines) {
    const match = line.match(/^([A-Z][A-Z_]+):\s*(.*)$/);
    if (match) {
      if (currentKey) {
        let value = currentValue.join('\n').trim();
        if ((value.startsWith('[') || value.startsWith('{')) && value.length > 2) {
          try { value = JSON.parse(value); } catch {}
        }
        variables[currentKey.toLowerCase()] = value;
      }
      currentKey = match[1];
      currentValue = [match[2]];
    } else if (currentKey) {
      currentValue.push(line);
    }
  }
  
  if (currentKey) {
    let value = currentValue.join('\n').trim();
    if ((value.startsWith('[') || value.startsWith('{')) && value.length > 2) {
      try { value = JSON.parse(value); } catch {}
    }
    variables[currentKey.toLowerCase()] = value;
  }
  
  return variables;
}

/**
 * Execute a single workflow step
 */
async function executeStep(gateway, state, step, agentId, stepIndex, totalSteps, attempt = 1) {
  const { id, name, input, expects, maxRetries = 1 } = step;
  const sessionKey = `agent:${agentId}:soulflow:${state.runId}:${id}`;
  
  console.log(`[soulflow] Step ${stepIndex}/${totalSteps}: ${name}`);
  
  if (attempt > 1) {
    console.log(`[soulflow]   ⟳ Retry ${attempt}/${maxRetries + 1}`);
  }
  
  State.updateStep(state, id, 'running');
  
  try {
    const prompt = substituteVariables(input, state.variables);
    
    console.log(`[soulflow]   → Sending to agent...`);
    
    const response = await gateway.sendChat(sessionKey, prompt);
    
    console.log(`[soulflow]   → Got ${response.length} chars`);
    
    // Parse output variables
    const outputVars = parseOutputVariables(response);
    
    // Always store the full output as a variable for next steps
    outputVars[`${id}_output`] = response;
    state.variables[`${id}_output`] = response;
    
    // Check success
    let success = true;
    if (expects) {
      success = response.includes(expects);
      if (success) {
        console.log(`[soulflow]   ✓ ${expects}`);
      } else if (response.length > 500) {
        console.log(`[soulflow]   ✓ Substantial response (accepted)`);
        success = true;
      } else {
        console.log(`[soulflow]   ✗ Short/empty response`);
      }
    }
    
    if (!success && attempt <= maxRetries) {
      console.log(`[soulflow]   → Retrying...`);
      // Use a different session key for retry to get fresh context
      return await executeStep(gateway, state, step, agentId, stepIndex, totalSteps, attempt + 1);
    }
    
    State.updateStep(state, id, success ? 'done' : 'failed', response, outputVars);
    
    if (!success) {
      throw new Error(`Step "${name}" failed — response too short or missing expected output`);
    }
    
    return { success: true, output: response, variables: outputVars };
    
  } catch (error) {
    console.error(`[soulflow]   ✗ ${error.message}`);
    State.updateStep(state, id, 'failed', error.message);
    throw error;
  }
}

/**
 * Send completion notification to main session
 */
async function notifyCompletion(gateway, state, workflow) {
  try {
    const summary = buildCompletionSummary(state, workflow);
    
    // Send to main session
    await gateway.call('chat.send', {
      sessionKey: 'agent:main',
      message: summary,
      idempotencyKey: `soulflow-complete-${state.runId}-${Date.now()}`
    });
    
    console.log(`[soulflow] ✓ Completion notification sent to main session`);
  } catch (error) {
    console.error(`[soulflow] Failed to send completion notification: ${error.message}`);
  }
}

/**
 * Send failure notification to main session
 */
async function notifyFailure(gateway, state, workflow, failedStep, error) {
  try {
    const message = `🚨 **SoulFlow Workflow Failed**

**Workflow:** ${workflow.name}
**Run ID:** ${state.runId}
**Failed at:** Step "${failedStep}"
**Error:** ${error.message}

Task: ${state.task.substring(0, 200)}${state.task.length > 200 ? '...' : ''}

Check logs with: \`node soulflow.js status ${state.runId}\``;

    await gateway.call('chat.send', {
      sessionKey: 'agent:main',
      message,
      idempotencyKey: `soulflow-fail-${state.runId}-${Date.now()}`
    });
    
    console.log(`[soulflow] ✓ Failure notification sent to main session`);
  } catch (err) {
    console.error(`[soulflow] Failed to send failure notification: ${err.message}`);
  }
}

/**
 * Build completion summary from workflow state
 */
function buildCompletionSummary(state, workflow) {
  const duration = Math.round((new Date(state.updatedAt) - new Date(state.createdAt)) / 1000);
  const minutes = Math.floor(duration / 60);
  const seconds = duration % 60;
  
  let summary = `✅ **SoulFlow Workflow Complete**

**Workflow:** ${workflow.name}
**Run ID:** ${state.runId}
**Duration:** ${minutes}m ${seconds}s

**Steps completed:**
`;

  for (const step of state.steps) {
    summary += `${step.status === 'done' ? '✓' : '✗'} ${step.id}\n`;
  }
  
  // Add key results from last step
  const lastStep = state.steps[state.steps.length - 1];
  if (lastStep && lastStep.output) {
    const output = lastStep.output;
    
    // Extract key variables
    const vars = state.variables || {};
    const keyVars = Object.keys(vars).filter(k => 
      k.includes('count') || 
      k.includes('status') || 
      k.includes('result') ||
      k.includes('fixed') ||
      k.includes('verified')
    );
    
    if (keyVars.length > 0) {
      summary += '\n**Results:**\n';
      for (const key of keyVars.slice(0, 5)) {
        summary += `- ${key}: ${vars[key]}\n`;
      }
    }
    
    // Extract first STATUS: done block if present
    const statusMatch = output.match(/STATUS: done.*?(?=\n\n|\n[A-Z]|$)/s);
    if (statusMatch) {
      const statusBlock = statusMatch[0].split('\n').slice(0, 5).join('\n');
      summary += `\n**Final Status:**\n\`\`\`\n${statusBlock}\n\`\`\`\n`;
    }
  }
  
  summary += `\nTask: ${state.task.substring(0, 150)}${state.task.length > 150 ? '...' : ''}`;
  
  return summary;
}

/**
 * Execute a complete workflow
 */
export async function runWorkflow(workflowId, task, workflowsDir) {
  const workflow = loadWorkflow(workflowId, workflowsDir);
  
  console.log(`\n[soulflow] ═══════════════════════════════════`);
  console.log(`[soulflow] ${workflow.name}`);
  console.log(`[soulflow] ═══════════════════════════════════`);
  console.log(`[soulflow] ${workflow.description}`);
  
  const runId = State.generateRunId();
  console.log(`[soulflow] Run: ${runId}`);
  
  const state = State.initRun(runId, workflow.id, task);
  
  const gateway = new GatewayClient();
  
  try {
    console.log(`[soulflow] Connecting...`);
    await gateway.connect();
    console.log(`[soulflow] Connected ✓\n`);
    
    // Ensure worker agent exists
    const agentId = await ensureWorkerAgent(gateway);
    
    for (let i = 0; i < workflow.steps.length; i++) {
      const step = workflow.steps[i];
      
      try {
        await executeStep(gateway, state, step, agentId, i + 1, workflow.steps.length);
        console.log('');
      } catch (error) {
        if (step.onFail?.retryStep) {
          console.log(`[soulflow] Running fallback: re-run "${step.onFail.retryStep}" then retry...`);
          const retryStep = workflow.steps.find(s => s.id === step.onFail.retryStep);
          if (retryStep) {
            const retryIdx = workflow.steps.indexOf(retryStep) + 1;
            await executeStep(gateway, state, retryStep, agentId, retryIdx, workflow.steps.length);
            await executeStep(gateway, state, step, agentId, i + 1, workflow.steps.length);
            continue;
          }
        }
        
        state.status = 'failed';
        State.saveState(state);
        console.log(`\n[soulflow] ✗ Failed at: ${step.name}`);
        console.log(`[soulflow] Run saved: ${runId}`);
        
        // Send failure notification to main session
        await notifyFailure(gateway, state, workflow, step.name, error);
        
        gateway.close();
        return { success: false, runId, state };
      }
    }
    
    state.status = 'done';
    State.saveState(state);
    
    console.log(`[soulflow] ═══════════════════════════════════`);
    console.log(`[soulflow] ✓ Complete! Run: ${runId}`);
    console.log(`[soulflow] ═══════════════════════════════════\n`);
    
    // Send completion notification to main session
    await notifyCompletion(gateway, state, workflow);
    
    gateway.close();
    return { success: true, runId, state };
    
  } catch (error) {
    console.error(`[soulflow] ✗ Fatal: ${error.message}`);
    state.status = 'failed';
    state.error = error.message;
    State.saveState(state);
    gateway.close();
    throw error;
  }
}

/**
 * Get workflow status
 */
export async function getStatus(runId) {
  const state = State.loadState(runId);
  
  console.log(`\n[soulflow] Run: ${state.runId} (${state.workflow})`);
  console.log(`[soulflow] Status: ${state.status}`);
  console.log(`[soulflow] Task: ${state.task.substring(0, 100)}${state.task.length > 100 ? '...' : ''}`);
  console.log(`[soulflow] Started: ${state.createdAt}`);
  console.log(`\n[soulflow] Steps:`);
  
  for (const step of state.steps) {
    const icon = step.status === 'done' ? '✓' : 
                 step.status === 'failed' ? '✗' : 
                 step.status === 'running' ? '⟳' : '○';
    const chars = step.output ? ` (${step.output.length} chars)` : '';
    console.log(`  ${icon} ${step.id}: ${step.status}${chars}`);
  }
  
  console.log('');
  return state;
}

/**
 * List available workflows
 */
export function listWorkflows(workflowsDir) {
  return fs.readdirSync(workflowsDir)
    .filter(f => f.endsWith('.workflow.json'))
    .map(f => {
      const wf = JSON.parse(fs.readFileSync(path.join(workflowsDir, f), 'utf8'));
      return { id: wf.id, name: wf.name, description: wf.description, steps: wf.steps.length };
    });
}


path: lib/state.js
content: /**
 * lib/state.js - State Management for Soulflow
 * Handles reading/writing workflow run state to JSON files
 */

import fs from 'fs';
import path from 'path';
import os from 'os';

const STATE_DIR = path.join(os.homedir(), '.openclaw', 'workspace', '.soulflow', 'runs');

/**
 * Ensure state directory exists
 */
function ensureStateDir() {
  if (!fs.existsSync(STATE_DIR)) {
    fs.mkdirSync(STATE_DIR, { recursive: true });
  }
}

/**
 * Generate a random 8-character hex run ID
 */
export function generateRunId() {
  return Array.from({ length: 8 }, () => 
    Math.floor(Math.random() * 16).toString(16)
  ).join('');
}

/**
 * Initialize a new workflow run
 */
export function initRun(runId, workflowId, task) {
  ensureStateDir();
  
  const state = {
    runId,
    workflow: workflowId,
    task,
    status: 'running',
    steps: [],
    variables: { task },
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };
  
  saveState(state);
  return state;
}

/**
 * Load state for a run
 */
export function loadState(runId) {
  ensureStateDir();
  const filePath = path.join(STATE_DIR, `${runId}.json`);
  
  if (!fs.existsSync(filePath)) {
    throw new Error(`Run ${runId} not found`);
  }
  
  const content = fs.readFileSync(filePath, 'utf8');
  return JSON.parse(content);
}

/**
 * Save state for a run
 */
export function saveState(state) {
  ensureStateDir();
  state.updatedAt = new Date().toISOString();
  
  const filePath = path.join(STATE_DIR, `${state.runId}.json`);
  fs.writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf8');
}

/**
 * Update step status and output
 */
export function updateStep(state, stepId, status, output = null, variables = {}) {
  const stepIndex = state.steps.findIndex(s => s.id === stepId);
  
  if (stepIndex === -1) {
    state.steps.push({
      id: stepId,
      status,
      output,
      variables,
      startedAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    });
  } else {
    state.steps[stepIndex].status = status;
    if (output !== null) state.steps[stepIndex].output = output;
    state.steps[stepIndex].variables = { ...state.steps[stepIndex].variables, ...variables };
    state.steps[stepIndex].updatedAt = new Date().toISOString();
  }
  
  // Merge step variables into global variables
  Object.assign(state.variables, variables);
  
  saveState(state);
}

/**
 * List all runs
 */
export function listRuns() {
  ensureStateDir();
  
  const files = fs.readdirSync(STATE_DIR)
    .filter(f => f.endsWith('.json'))
    .map(f => {
      const runId = f.replace('.json', '');
      try {
        const state = loadState(runId);
        return {
          runId: state.runId,
          workflow: state.workflow,
          status: state.status,
          createdAt: state.createdAt,
          updatedAt: state.updatedAt
        };
      } catch (e) {
        return null;
      }
    })
    .filter(r => r !== null)
    .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  
  return files;
}

/**
 * Get state directory path
 */
export function getStateDir() {
  return STATE_DIR;
}


path: workflows/content-pipeline.workflow.json
content: {
  "id": "content-pipeline",
  "name": "Content Publishing Pipeline",
  "version": 1,
  "description": "Research, draft, edit, and publish blog content",
  "steps": [
    {
      "id": "research",
      "name": "Research Topic",
      "input": "Research the topic: {{task}}\n\nUse web_search and web_fetch to gather information from multiple sources. Create a comprehensive research summary with:\n- Key points and insights\n- Statistics and data\n- Expert quotes\n- Source URLs\n\nSave your research to a markdown file.\n\nDo NOT just describe what you would do — actually use the tools and gather real information.\n\nWhen done, end with:\nSTATUS: done\nRESEARCH_FILE: path/to/research.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "draft",
      "name": "Draft Article",
      "input": "Write a blog post based on this research:\n\nResearch file: {{research_file}}\nResearch summary:\n{{research_output}}\n\nOriginal topic: {{task}}\n\nCreate a well-structured article with:\n- Compelling intro hook\n- Clear body sections with headers\n- Concrete examples and data\n- Strong conclusion with takeaways\n\nSave to a markdown file. Use proper formatting.\n\nWhen done, end with:\nSTATUS: done\nDRAFT_FILE: path/to/draft.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "edit",
      "name": "Edit and Polish",
      "input": "Edit this draft for clarity, grammar, and flow:\n\nDraft file: {{draft_file}}\n\nUse the `edit` tool to make improvements:\n- Fix grammar and typos\n- Improve sentence flow\n- Tighten verbose sections\n- Strengthen weak transitions\n- Ensure consistent tone\n\nWhen done, end with:\nSTATUS: done\nFINAL_FILE: path/to/final.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    }
  ]
}


path: workflows/bug-scan-fix.workflow.json
content: {
  "id": "bug-scan-fix",
  "name": "Bug Scan and Fix",
  "version": 1,
  "description": "Scan codebase for bugs, triage by severity, fix high-priority issues, and verify",
  "steps": [
    {
      "id": "scan",
      "name": "Scan for Bugs",
      "input": "Scan the codebase for bugs: {{task}}\n\nUse the `read` tool to examine source files. Look for:\n- Logic errors (off-by-one, null checks, async issues)\n- Edge cases not handled (empty arrays, missing fields, invalid input)\n- Error handling gaps (uncaught exceptions, missing try-catch)\n- Race conditions and concurrency issues\n- Database query issues (SQL injection, missing constraints)\n- API endpoint vulnerabilities (missing validation, auth bypasses)\n- Frontend bugs (state management, event handling, rendering issues)\n\nDo NOT just describe — actually read the files and identify specific issues with file paths and line numbers.\n\nCreate a list of all bugs found with:\n- File path and line number\n- Description of the bug\n- Potential impact\n- How to reproduce (if applicable)\n\nWhen done, end with:\nSTATUS: done\nBUGS_FOUND: number\nBUGS_FILE: path/to/bugs-list.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "triage",
      "name": "Triage Bugs",
      "input": "Triage these bugs by severity and impact:\n\nBugs file: {{bugs_file}}\nBugs found: {{bugs_found}}\n\nScan results:\n{{scan_output}}\n\nCategorize each bug:\n- **CRITICAL**: Data loss, security breach, app crashes, payment failures\n- **HIGH**: Major features broken, significant UX issues, data corruption risk\n- **MEDIUM**: Minor features broken, workarounds exist, performance issues\n- **LOW**: Cosmetic issues, edge cases, non-critical improvements\n\nPrioritize the top 3-5 bugs to fix first. Use the `edit` tool to update the bugs file with severity ratings and priority order.\n\nWhen done, end with:\nSTATUS: done\nCRITICAL_COUNT: number\nHIGH_COUNT: number\nPRIORITY_BUGS: comma-separated list of bug IDs to fix",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "fix",
      "name": "Fix Priority Bugs",
      "input": "Fix the priority bugs:\n\nBugs file: {{bugs_file}}\nPriority bugs: {{priority_bugs}}\nCritical count: {{critical_count}}\nHigh count: {{high_count}}\n\nTriage results:\n{{triage_output}}\n\nFor each priority bug:\n1. Use `read` to examine the buggy code\n2. Use `edit` to apply the fix\n3. Document what you changed and why\n\nDo NOT just describe the fixes — actually use the `edit` tool to modify the files.\n\nFocus on the highest severity bugs first. If you run out of time, document which bugs remain unfixed.\n\nWhen done, end with:\nSTATUS: done\nBUGS_FIXED: number\nFIXES_FILE: path/to/fixes-summary.md",
      "expects": "STATUS: done",
      "maxRetries": 2
    },
    {
      "id": "verify",
      "name": "Verify Fixes",
      "input": "Verify the bug fixes didn't break anything:\n\nFixes file: {{fixes_file}}\nBugs fixed: {{bugs_fixed}}\n\nFix results:\n{{fix_output}}\n\nFor each fixed bug:\n1. Use `read` to review the changed code\n2. Check for potential side effects or regressions\n3. If tests exist, use `exec` to run relevant test suites\n4. Verify the fix actually solves the original issue\n\nDocument:\n- Which fixes were verified successfully\n- Any new issues introduced by the fixes\n- Test results (if tests were run)\n- Recommendations for additional testing\n\nWhen done, end with:\nSTATUS: done\nVERIFIED_FIXES: number\nREGRESSIONS_FOUND: number\nVERIFICATION_FILE: path/to/verification-report.md",
      "expects": "STATUS: done",
      "maxRetries": 1
    }
  ]
}


path: workflows/feature-dev.workflow.json
content: {
  "id": "feature-dev",
  "name": "Feature Development",
  "version": 3,
  "description": "Plan, implement, and review new features",
  "steps": [
    {
      "id": "plan",
      "name": "Architect",
      "input": "FEATURE REQUEST: {{task}}\n\nCreate a detailed implementation plan. First, understand the existing codebase:\n\n1. Use `exec` to explore the project structure (ls, find, etc.)\n2. Use `read` to examine relevant existing files (routes, components, models, config)\n3. Identify where new code needs to go and what existing code needs to change\n\nThen write a plan with:\n- Files to create (with full paths)\n- Files to modify (with what changes)\n- Data structures / DB changes if any\n- API endpoints if any\n- Dependencies on existing code\n\nBe specific. File paths, function names, data shapes.\n\nWhen done, end with:\nSTATUS: done",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "implement",
      "name": "Developer",
      "input": "Implement the feature based on this plan.\n\n--- PLAN ---\n{{plan_output}}\n--- END ---\n\nOriginal request: {{task}}\n\nFor each item in the plan:\n1. `read` the existing file (if modifying)\n2. `write` new files or `edit` existing ones\n3. Follow existing code patterns and conventions\n4. Handle errors properly\n\nWrite real, production-quality code. Not pseudocode. Not descriptions.\n\nWhen done, end with:\nSTATUS: done\nFILES_CREATED: list of new files\nFILES_MODIFIED: list of changed files",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "review",
      "name": "Reviewer",
      "input": "Review the implementation for quality and correctness.\n\n--- PLAN ---\n{{plan_output}}\n--- END PLAN ---\n\n--- IMPLEMENTATION ---\n{{implement_output}}\n--- END ---\n\nFor each created/modified file:\n1. `read` the actual file\n2. Check: Does it match the plan? Is the code clean? Error handling? Security? Edge cases?\n3. If you find issues, `edit` to fix them directly\n\nReport what you found and any fixes you applied.\n\nWhen done, end with:\nSTATUS: done\nQUALITY: good | needs-work | critical-issues",
      "expects": "STATUS: done",
      "maxRetries": 1,
      "onFail": { "retryStep": "implement", "maxRetries": 1 }
    }
  ]
}


path: workflows/security-audit.workflow.json
content: {
  "id": "security-audit",
  "name": "Security Audit",
  "version": 3,
  "description": "Scan codebase for vulnerabilities, prioritize, fix, and verify",
  "steps": [
    {
      "id": "scan",
      "name": "Security Scanner",
      "input": "TASK: {{task}}\n\nYou must perform a thorough security audit. Do NOT just describe what you would do — actually do it.\n\nSTEPS:\n1. Use the `exec` tool to run: find the project root and list all source files\n2. Use the `read` tool to read each key source file (auth, routes, middleware, config, DB queries)\n3. Check each file for: SQL injection, XSS, path traversal, auth bypass, credential exposure, CORS misconfig, missing rate limiting, insecure defaults\n4. Document every finding with the exact file path, line reference, severity (critical/high/medium/low), and what the fix should be\n\nYou MUST actually read the files. Do not guess or assume.\n\nWhen done, end with:\nSTATUS: done",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "prioritize",
      "name": "Prioritizer",
      "input": "Review the security scan findings below. Create a numbered, prioritized fix plan.\n\n--- SCAN RESULTS ---\n{{scan_output}}\n--- END ---\n\nFor each finding:\n1. Rank by severity × exploitability\n2. Specify the exact file and what needs to change\n3. Group related fixes that can be done together\n\nOutput a clear numbered list. When done, end with:\nSTATUS: done",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "fix",
      "name": "Fixer",
      "input": "Apply the security fixes below to the codebase. Use the `read` tool to open each file, then `edit` to make changes.\n\n--- FIX PLAN ---\n{{prioritize_output}}\n--- END ---\n\nFor EACH fix:\n1. `read` the file\n2. `edit` the specific section that needs changing\n3. Confirm the edit was applied\n\nDo NOT skip fixes. Do NOT just describe fixes — apply them. Report every file you modified.\n\nWhen done, end with:\nSTATUS: done\nFILES_MODIFIED: comma-separated list of files changed",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "verify",
      "name": "Verifier",
      "input": "Verify the security fixes were correctly applied. Use `read` to check each modified file.\n\n--- ORIGINAL FINDINGS ---\n{{scan_output}}\n--- END FINDINGS ---\n\n--- CHANGES MADE ---\n{{fix_output}}\n--- END CHANGES ---\n\nFor each original finding, `read` the file and confirm the vulnerability is patched. Report:\n- FIXED: list of confirmed fixes\n- REMAINING: any issues still present\n- REGRESSIONS: any new issues introduced\n\nWhen done, end with:\nSTATUS: done",
      "expects": "STATUS: done",
      "maxRetries": 1,
      "onFail": { "retryStep": "fix", "maxRetries": 1 }
    }
  ]
}


path: workflows/deploy-pipeline.workflow.json
content: {
  "id": "deploy-pipeline",
  "name": "Deploy and Verify",
  "version": 1,
  "description": "Run tests, build, deploy, verify health, and rollback on failure",
  "steps": [
    {
      "id": "test",
      "name": "Run Tests",
      "input": "Run the test suite for: {{task}}\n\nUse `exec` to run the appropriate test commands (npm test, pytest, go test, etc.). Check the project directory to identify the test framework.\n\nIf tests fail, capture the output and report it.\n\nWhen done, end with:\nSTATUS: done\nTEST_RESULT: passed/failed\nTEST_OUTPUT: summary of results",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "build",
      "name": "Build Application",
      "input": "Build the application: {{task}}\n\nPrevious test result: {{test_result}}\n\nIf tests passed, run the build command (npm run build, go build, etc.). Check for build errors.\n\nIf tests failed, skip the build and report that deployment is blocked.\n\nWhen done, end with:\nSTATUS: done\nBUILD_RESULT: success/failed/skipped\nBUILD_OUTPUT: summary",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "deploy",
      "name": "Deploy to Production",
      "input": "Deploy the application: {{task}}\n\nBuild result: {{build_result}}\n\nIf build succeeded, execute the deployment (git push, rsync, docker deploy, etc.). Document the deployment method used.\n\nIf build failed, skip deployment.\n\nWhen done, end with:\nSTATUS: done\nDEPLOY_RESULT: deployed/skipped\nDEPLOY_URL: production URL if available",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "verify",
      "name": "Verify Health",
      "input": "Verify deployment health: {{task}}\n\nDeploy result: {{deploy_result}}\nDeploy URL: {{deploy_url}}\n\nIf deployment happened:\n1. Use `browser` or `web_fetch` to check the URL\n2. Test critical endpoints (/, /health, /api/status)\n3. Verify expected HTTP status codes\n\nIf health checks fail, document the failure and recommend rollback.\n\nWhen done, end with:\nSTATUS: done\nHEALTH_STATUS: healthy/degraded/failed\nHEALTH_DETAILS: summary of checks",
      "expects": "STATUS: done",
      "maxRetries": 1
    }
  ]
}


path: workflows/bug-fix.workflow.json
content: {
  "id": "bug-fix",
  "name": "Bug Fix",
  "version": 3,
  "description": "Triage, investigate, fix, and verify bugs",
  "steps": [
    {
      "id": "triage",
      "name": "Triage",
      "input": "BUG REPORT: {{task}}\n\nInvestigate this bug. Do NOT guess — actually look at the code.\n\n1. Use `exec` to find relevant source files (grep for keywords, list directories)\n2. Use `read` to examine the files most likely involved\n3. Trace the code path that triggers the bug\n4. Identify the root cause with exact file path and line\n5. Propose a specific fix\n\nWhen done, end with:\nSTATUS: done\nROOT_CAUSE: one-line description\nFIX_FILE: path to the file that needs changing",
      "expects": "STATUS: done",
      "maxRetries": 1
    },
    {
      "id": "fix",
      "name": "Fixer",
      "input": "Apply the bug fix based on this triage.\n\n--- TRIAGE ---\n{{triage_output}}\n--- END ---\n\nOriginal bug: {{task}}\n\n1. `read` the affected file(s)\n2. `edit` to apply the fix\n3. Check for edge cases or related issues\n4. Verify the edit was applied by reading the file again\n\nDo the actual fix. Don't just describe it.\n\nWhen done, end with:\nSTATUS: done\nFILES_MODIFIED: list of changed files",
      "expects": "STATUS: done",
      "maxRetries": 2
    },
    {
      "id": "verify",
      "name": "Verifier",
      "input": "Verify the bug fix was applied correctly.\n\n--- ORIGINAL BUG ---\n{{task}}\n--- END ---\n\n--- FIX APPLIED ---\n{{fix_output}}\n--- END ---\n\n1. `read` each modified file\n2. Trace the code path again — does the original bug still trigger?\n3. Check for regressions or edge cases the fix might have missed\n4. If the fix is incomplete, explain exactly what's still wrong\n\nWhen done, end with:\nSTATUS: done\nVERDICT: fixed | partial | not-fixed",
      "expects": "STATUS: done",
      "maxRetries": 1
    }
  ]
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)