Skill: frank-bot07/openclaw-orchestration (version 1.0.0)

# Orchestration Skill Multi-agent task orchestration framework. Agents delegate tasks via a shared SQLite queue, with .md interchange files for visibility. ## Quick Start ```bash cd skills/orchestration npm install node src/cli.js agent register my-agent --capabilities "coding,research" node src/cli.js task create "Build feature X" --desc "..." --priority high node src/cli.js task claim <task-id> --agent my-agent node src/cli.js task complete <task-id> --summary "Done" node src/cli.js refresh ``` ## Design - **DB is source of truth** — .md files are read-only projections - **Atomic claims** — only one agent can claim a pending task - **Dependencies** — tasks can depend on other tasks - **Timeout + retry** — `sweep` handles stale tasks - **Interchange** — `refresh` generates .md files via @openclaw/interchange

path: CODEX_REVIEW.md
content: # Codex Review: @openclaw/orchestration

## Summary
12 issues found (1 critical, 4 high, 5 medium, 2 low)

## Strengths
- **Solid transaction usage** in `claimTask` and `sweep` — atomic claim with dependency check prevents most race conditions
- **WAL mode + foreign keys** enabled by default — good SQLite hygiene
- **Migration system** is clean with checksums and transactional application
- **Test coverage** is good — covers race conditions, dependencies, sweep, retry lifecycle
- **`MAX(0, current_load - 1)`** prevents negative load — nice defensive SQL
- **Clean separation of concerns** — db, queue, agents, sweep, protocol, interchange are well-factored

## Issues

### 1. [CRITICAL] Race condition in `claimTask` — stale read before transaction
**File:** `src/queue.js` lines 51-54
**Problem:** `getTask()` is called *outside* the transaction, then `task.status` and `task.depends_on` are used *inside* it. Between the read and the transaction start, another process could claim the task. The `WHERE status = 'pending'` in the UPDATE catches the claim race, but `task.depends_on` is read from stale data. If dependencies are modified between the read and the transaction, the check uses outdated info. More critically, if `task.status !== 'pending'` the function returns `null` *before* entering the transaction, skipping the atomic check entirely.
**Fix:** Move the initial `getTask()` + status check inside the transaction:
```js
const doClaim = db.transaction(() => {
  const task = getTask(db, taskId);
  if (!task || task.status !== 'pending') return null;
  // ... rest of claim logic
});
```

### 2. [HIGH] `completeTask` and `failTask` are not transactional
**File:** `src/queue.js` lines 93-113, 121-134
**Problem:** These functions run multiple statements (UPDATE tasks, INSERT results, INSERT handoff_log, UPDATE agents) without a transaction. If the process crashes mid-way, the DB ends up in an inconsistent state — e.g., task marked completed but agent load not decremented, or result not recorded.
**Fix:** Wrap each in `db.transaction()`.

### 3. [HIGH] `createTask` is not transactional
**File:** `src/queue.js` lines 18-26
**Problem:** INSERT into `tasks` and INSERT into `handoff_log` are separate statements. A crash between them loses the audit trail silently.
**Fix:** Wrap in `db.transaction()`.

### 4. [HIGH] `failTask` ignores the `reason` parameter entirely
**File:** `src/queue.js` line 121
**Problem:** The `reason` parameter is accepted but never stored anywhere — not in the task, not in handoff_log, not in results. Failure context is silently discarded.
**Fix:** Either add a `reason` column to `handoff_log` or store it in `results.summary`.

### 5. [HIGH] `refreshInterchange` async close race in CLI
**File:** `src/cli.js` lines 145-150
**Problem:** The `refresh` command calls `await refreshInterchange(db)` then `closeDb()`. But Commander's `.action()` doesn't await async handlers by default in older versions. If it doesn't await, `closeDb()` runs before `refreshInterchange` finishes, causing writes to a closed database.
**Fix:** Ensure Commander v12+ is used (which awaits async actions), or wrap with `.then(() => closeDb())`.

### 6. [MEDIUM] `claimTask` doesn't enforce `max_concurrent`
**File:** `src/queue.js` lines 51-82
**Problem:** An agent can claim unlimited tasks — `max_concurrent` is tracked via `current_load` but never checked. The field exists in the schema and is displayed, creating a false sense of enforcement.
**Fix:** Inside the claim transaction, check `current_load < max_concurrent` before allowing the claim.

### 7. [MEDIUM] `backup()` calls `db.backup()` which returns a Promise but is not awaited
**File:** `src/backup.js` line 17
**Problem:** `better-sqlite3`'s `.backup()` returns a Promise. The CLI command and function treat it synchronously, so the backup may not complete before the program exits or the "Backup saved" message prints.
**Fix:** Make `backup()` async and await `db.backup(dest)`. Update CLI accordingly.

### 8. [MEDIUM] `restore()` overwrites live database without closing connections
**File:** `src/backup.js` lines 24-29
**Problem:** `fs.copyFileSync` overwrites the DB file while a connection may be open (the module-level `_db` singleton). With WAL mode this can corrupt data. The CLI `restore` command doesn't call `initDb()` first, so _db is null, but nothing prevents misuse from code.
**Fix:** Ensure `closeDb()` is called before restore. Add a guard or accept the db instance to close.

### 9. [MEDIUM] No validation on `priority` or `status` values in application layer
**File:** `src/queue.js` line 19
**Problem:** Invalid priority values (e.g., `"urgent"`) will fail with a CHECK constraint error from SQLite, producing a raw unhelpful error message. Same for any direct status manipulation.
**Fix:** Validate `priority` against `['high', 'medium', 'low']` before the INSERT and throw a descriptive error.

### 10. [MEDIUM] Stale task files accumulate in interchange
**File:** `src/interchange.js` lines 120-130
**Problem:** `generateTaskFiles` creates a `.md` file per task but never cleans up old files. If a task is deleted from the DB (not currently possible but future-proofing), its interchange file persists forever.
**Fix:** Clear the `state/tasks/` directory before regenerating, or add a cleanup step.

### 11. [LOW] UUID collision risk with 8-char truncation
**File:** `src/queue.js` line 18, `src/agents.js` line 16
**Problem:** `uuidv4().slice(0, 8)` gives 32 bits of entropy (~4 billion values). Fine for small deployments but a birthday collision is probable around 65K tasks. Not urgent but worth noting.
**Fix:** Use 12 chars for more headroom, or use `nanoid`.

### 12. [LOW] `retryTask` doesn't verify agent existence
**File:** `src/queue.js` line 145
**Problem:** Minor — `retryTask` clears `assigned_agent` but doesn't decrement the original agent's `current_load`. If a task was manually failed (not via `failTask`), the load could drift.
**Fix:** Already handled in `failTask` which is the normal path. Just document that manual DB edits can desync load counts.


path: README.md
content: # @openclaw/orchestration

[![Tests](https://img.shields.io/badge/tests-13%20passing-brightgreen)]() [![Node](https://img.shields.io/badge/node-%3E%3D18-blue)]() [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)]()

> Multi-agent task queue with collision-safe claiming and dependency chains.

A SQLite-backed task orchestration system designed for multi-agent environments. Agents register capabilities, claim tasks atomically, and chain dependencies — all with automatic timeout detection, retry logic, and `.md` interchange output.

## Features

- **Task queue** — create, claim, complete, fail, and retry tasks
- **Collision-safe claiming** — atomic SQLite transactions prevent double-claims
- **Dependency chains** — tasks can depend on other tasks completing first
- **Timeout & retry** — configurable timeouts with automatic sweep and retry
- **Agent registry** — register agents with capabilities and concurrency limits
- **Priority levels** — high, medium, low task prioritization
- **Interchange output** — regenerate `.md` files for cross-skill visibility
- **Backup & restore** — full database backup and recovery

## Quick Start

```bash
cd skills/orchestration
npm install

# Register an agent
node src/cli.js agent register researcher --capabilities "search,summarize" --max-concurrent 3

# Create a task
node src/cli.js task create "Research competitor pricing" --priority high --timeout 30

# Claim and complete
node src/cli.js task claim <task-id> --agent researcher
node src/cli.js task complete <task-id> --summary "Found 5 competitors"

# Sweep stale tasks
node src/cli.js sweep
```

## CLI Reference

### Task Management

```bash
orch task create <title> [options]
  --desc <description>       Task description
  --priority <high|medium|low>  Priority (default: medium)
  --timeout <minutes>        Timeout in minutes (default: 60)
  --depends-on <ids>         Comma-separated dependency task IDs
  --created-by <agent>       Creating agent (default: cli)
  --max-retries <n>          Max retries (default: 3)

orch task list [options]
  --status <status>          Filter by status
  --agent <name>             Filter by assigned agent

orch task claim <task-id> --agent <name>
orch task complete <task-id> [--result-path <path>] [--summary <text>]
orch task fail <task-id> [--reason <text>]
orch task retry <task-id>
```

### Agent Management

```bash
orch agent register <name> [--capabilities <list>] [--max-concurrent <n>]
orch agent list
orch agent status <name>
```

### Utilities

```bash
orch sweep              # Timeout stale tasks, retry eligible ones
orch refresh            # Regenerate interchange .md files
orch backup [--output <path>]
orch restore <backup-file>
```

## Architecture

SQLite database (`data/`) stores tasks, agents, and state transitions. Tasks flow through `pending → in_progress → completed|failed` with atomic claiming via SQL transactions. The sweep command handles timeouts and auto-retries.

## .md Interchange

Running `orch refresh` generates `.md` status files that other skills and agents can read to understand current task state, agent load, and queue health — all via `@openclaw/interchange`.

## Testing

```bash
npm test
```

13 tests covering task lifecycle, collision-safe claiming, dependency resolution, timeout/retry logic, and agent management.

## Part of the OpenClaw Ecosystem

Orchestration coordinates work across agents. It uses `@openclaw/interchange` for `.md` output, and its task results can feed into `monitoring` for success rate tracking and cost analysis.

## License

MIT


path: migrations/001_initial.sql
content: -- UP
CREATE TABLE IF NOT EXISTS _migrations (
  version INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  applied_at TEXT NOT NULL DEFAULT (datetime('now')),
  checksum TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS tasks (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  description TEXT DEFAULT '',
  status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','claimed','in-progress','completed','failed')),
  assigned_agent TEXT,
  priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('high','medium','low')),
  depends_on TEXT NOT NULL DEFAULT '[]',
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  claimed_at TEXT,
  completed_at TEXT,
  timeout_minutes INTEGER NOT NULL DEFAULT 60,
  retry_count INTEGER NOT NULL DEFAULT 0,
  max_retries INTEGER NOT NULL DEFAULT 3,
  created_by TEXT NOT NULL DEFAULT 'unknown'
);

CREATE TABLE IF NOT EXISTS agents (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  capabilities TEXT NOT NULL DEFAULT '[]',
  max_concurrent INTEGER NOT NULL DEFAULT 1,
  current_load INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS results (
  task_id TEXT PRIMARY KEY REFERENCES tasks(id),
  output_path TEXT,
  summary TEXT,
  completed_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS handoff_log (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  task_id TEXT NOT NULL REFERENCES tasks(id),
  from_agent TEXT,
  to_agent TEXT,
  timestamp TEXT NOT NULL DEFAULT (datetime('now')),
  action TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
CREATE INDEX IF NOT EXISTS idx_handoff_task ON handoff_log(task_id);

-- DOWN
DROP TABLE IF EXISTS handoff_log;
DROP TABLE IF EXISTS results;
DROP TABLE IF EXISTS agents;
DROP TABLE IF EXISTS tasks;
DROP TABLE IF EXISTS _migrations;


path: package-lock.json
content: {
  "name": "@openclaw/orchestration",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@openclaw/orchestration",
      "version": "1.0.0",
      "dependencies": {
        "better-sqlite3": "^11.0.0",
        "commander": "^12.0.0",
        "uuid": "^9.0.0"
      },
      "bin": {
        "orch": "src/cli.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/better-sqlite3": {
      "version": "11.10.0",
      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "dependencies": {
        "bindings": "^1.5.0",
        "prebuild-install": "^7.1.1"
      }
    },
    "node_modules/bindings": {
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "file-uri-to-path": "1.0.0"
      }
    },
    "node_modules/bl": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "buffer": "^5.5.0",
        "inherits": "^2.0.4",
        "readable-stream": "^3.4.0"
      }
    },
    "node_modules/buffer": {
      "version": "5.7.1",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.1.13"
      }
    },
    "node_modules/chownr": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/commander": {
      "version": "12.1.0",
      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/decompress-response": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mimic-response": "^3.1.0"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=4.0.0"
      }
    },
    "node_modules/detect-libc": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/end-of-stream": {
      "version": "1.4.5",
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "once": "^1.4.0"
      }
    },
    "node_modules/expand-template": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "(MIT OR WTFPL)",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/file-uri-to-path": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fs-constants": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/github-from-package": {
      "version": "0.0.0",
      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/ini": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/mimic-response": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/minimist": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/mkdirp-classic": {
      "version": "0.5.3",
      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/napi-build-utils": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/node-abi": {
      "version": "3.87.0",
      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "semver": "^7.3.5"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/prebuild-install": {
      "version": "7.1.3",
      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "detect-libc": "^2.0.0",
        "expand-template": "^2.0.3",
        "github-from-package": "0.0.0",
        "minimist": "^1.2.3",
        "mkdirp-classic": "^0.5.3",
        "napi-build-utils": "^2.0.0",
        "node-abi": "^3.3.0",
        "pump": "^3.0.0",
        "rc": "^1.2.7",
        "simple-get": "^4.0.0",
        "tar-fs": "^2.0.0",
        "tunnel-agent": "^0.6.0"
      },
      "bin": {
        "prebuild-install": "bin.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/pump": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "end-of-stream": "^1.1.0",
        "once": "^1.3.1"
      }
    },
    "node_modules/rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
      "dependencies": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      },
      "bin": {
        "rc": "cli.js"
      }
    },
    "node_modules/readable-stream": {
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "inherits": "^2.0.3",
        "string_decoder": "^1.1.1",
        "util-deprecate": "^1.0.1"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/safe-buffer": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/semver": {
      "version": "7.7.4",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/simple-concat": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/simple-get": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "decompress-response": "^6.0.0",
        "once": "^1.3.1",
        "simple-concat": "^1.0.0"
      }
    },
    "node_modules/string_decoder": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "~5.2.0"
      }
    },
    "node_modules/strip-json-comments": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/tar-fs": {
      "version": "2.1.4",
      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "chownr": "^1.1.1",
        "mkdirp-classic": "^0.5.2",
        "pump": "^3.0.0",
        "tar-stream": "^2.1.4"
      }
    },
    "node_modules/tar-stream": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bl": "^4.0.3",
        "end-of-stream": "^1.4.1",
        "fs-constants": "^1.0.0",
        "inherits": "^2.0.3",
        "readable-stream": "^3.1.1"
      },
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/tunnel-agent": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "safe-buffer": "^5.0.1"
      },
      "engines": {
        "node": "*"
      }
    },
    "node_modules/util-deprecate": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/uuid": {
      "version": "9.0.1",
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        "https://github.com/sponsors/broofa",
        "https://github.com/sponsors/ctavan"
      ],
      "license": "MIT",
      "bin": {
        "uuid": "dist/bin/uuid"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    }
  }
}


path: package.json
content: {
  "name": "@openclaw/orchestration",
  "version": "1.0.0",
  "type": "module",
  "description": "Multi-agent task orchestration with shared .md context files",
  "bin": {
    "orch": "./src/cli.js"
  },
  "scripts": {
    "test": "node --test test/orchestration.test.js"
  },
  "engines": {
    "node": ">=18"
  },
  "dependencies": {
    "better-sqlite3": "^11.0.0",
    "commander": "^12.0.0",
    "uuid": "^9.0.0"
  }
}


path: scripts/refresh-interchange.sh
content: #!/bin/bash
set -euo pipefail
# Refresh interchange .md files
cd "$(dirname "$0")/.." && node src/cli.js refresh


path: scripts/sweep-stale.sh
content: #!/bin/bash
set -euo pipefail
# Sweep stale tasks
cd "$(dirname "$0")/.." && node src/cli.js sweep


path: src/agents.js
content: /**
 * @module agents
 * Agent registry logic.
 */
import { v4 as uuidv4 } from 'uuid';

/**
 * Register a new agent.
 * @param {import('better-sqlite3').Database} db
 * @param {object} opts
 * @param {string} opts.name
 * @param {string[]} [opts.capabilities]
 * @param {number} [opts.maxConcurrent]
 * @returns {object}
 */
export function registerAgent(db, opts) {
  const { name, capabilities = [], maxConcurrent = 1 } = opts;
  const id = `agent-${uuidv4().slice(0, 8)}`;

  try {
    db.prepare(`INSERT INTO agents (id, name, capabilities, max_concurrent) VALUES (?, ?, ?, ?)`)
      .run(id, name, JSON.stringify(capabilities), maxConcurrent);
  } catch (err) {
    if (err.message.includes('UNIQUE')) throw new Error(`Agent "${name}" already exists`);
    throw err;
  }

  return getAgent(db, name);
}

/**
 * Get an agent by name.
 * @param {import('better-sqlite3').Database} db
 * @param {string} name
 * @returns {object|null}
 */
export function getAgent(db, name) {
  const row = db.prepare('SELECT * FROM agents WHERE name = ?').get(name);
  if (row) row.capabilities = JSON.parse(row.capabilities);
  return row || null;
}

/**
 * List all agents.
 * @param {import('better-sqlite3').Database} db
 * @returns {object[]}
 */
export function listAgents(db) {
  return db.prepare('SELECT * FROM agents ORDER BY name').all()
    .map(r => ({ ...r, capabilities: JSON.parse(r.capabilities) }));
}

/**
 * Get agent status including current tasks.
 * @param {import('better-sqlite3').Database} db
 * @param {string} name
 * @returns {object}
 */
export function agentStatus(db, name) {
  const agent = getAgent(db, name);
  if (!agent) throw new Error(`Agent "${name}" not found`);
  const tasks = db.prepare("SELECT * FROM tasks WHERE assigned_agent = ? AND status IN ('claimed','in-progress')")
    .all(name).map(r => ({ ...r, depends_on: JSON.parse(r.depends_on) }));
  return { ...agent, activeTasks: tasks };
}


path: src/backup.js
content: /**
 * @module backup
 * Database backup and restore.
 */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DEFAULT_DB_PATH = path.join(__dirname, '..', 'data', 'orchestration.db');

/**
 * Backup the database using SQLite's backup API.
 * @param {import('better-sqlite3').Database} db
 * @param {string} [outputPath] - Output file path
 * @returns {string} Path to backup file
 */
export async function backup(db, outputPath) {
  const dest = outputPath || path.join(__dirname, '..', 'data', `backup-${Date.now()}.db`);
  fs.mkdirSync(path.dirname(dest), { recursive: true });
  await db.backup(dest);
  return dest;
}

/**
 * Restore database from a backup file.
 * @param {string} backupPath - Path to backup file
 * @param {string} [targetPath] - Target database path
 */
export function restore(backupPath, targetPath) {
  const dest = targetPath || DEFAULT_DB_PATH;
  if (!fs.existsSync(backupPath)) throw new Error(`Backup file not found: ${backupPath}`);
  // Warn if WAL/SHM files exist (active connections could cause corruption)
  const walPath = `${dest}-wal`;
  const shmPath = `${dest}-shm`;
  if (fs.existsSync(walPath) || fs.existsSync(shmPath)) {
    console.warn('⚠️  WAL/SHM files detected — close all DB connections before restoring to avoid corruption.');
  }
  fs.mkdirSync(path.dirname(dest), { recursive: true });
  fs.copyFileSync(backupPath, dest);
}


path: src/cli.js
content: #!/usr/bin/env node
/**
 * @module cli
 * Commander CLI entry point for the orchestration skill.
 */
import { Command } from 'commander';
import { initDb, closeDb } from './db.js';
import { createTask, listTasks, claimTask, completeTask, failTask, retryTask } from './queue.js';
import { registerAgent, listAgents, agentStatus } from './agents.js';
import { sweep } from './sweep.js';
import { refreshInterchange } from './interchange.js';
import { backup, restore } from './backup.js';

const program = new Command();
program.name('orch').description('Multi-agent task orchestration').version('1.0.0');

// Task commands
const task = program.command('task').description('Task management');

task.command('create <title>')
  .description('Create a new task')
  .option('--desc <description>', 'Task description', '')
  .option('--priority <priority>', 'Priority: high/medium/low', 'medium')
  .option('--timeout <minutes>', 'Timeout in minutes', '60')
  .option('--depends-on <ids>', 'Comma-separated dependency task IDs', '')
  .option('--created-by <agent>', 'Creating agent', 'cli')
  .option('--max-retries <n>', 'Max retries', '3')
  .action((title, opts) => {
    const db = initDb();
    const t = createTask(db, {
      title,
      description: opts.desc,
      priority: opts.priority,
      timeout: parseInt(opts.timeout, 10),
      dependsOn: opts.dependsOn ? opts.dependsOn.split(',').map(s => s.trim()) : [],
      createdBy: opts.createdBy,
      maxRetries: parseInt(opts.maxRetries, 10),
    });
    console.log(`Created task ${t.id}: ${t.title} [${t.priority}]`);
    closeDb();
  });

task.command('list')
  .description('List tasks')
  .option('--status <status>', 'Filter by status')
  .option('--agent <name>', 'Filter by agent')
  .action((opts) => {
    const db = initDb();
    const tasks = listTasks(db, { status: opts.status, agent: opts.agent });
    if (tasks.length === 0) { console.log('No tasks found.'); }
    else {
      for (const t of tasks) {
        console.log(`${t.id} | ${t.title} | ${t.status} | ${t.priority} | ${t.assigned_agent || '-'}`);
      }
    }
    closeDb();
  });

task.command('claim <task-id>')
  .description('Claim a task')
  .requiredOption('--agent <name>', 'Agent name')
  .action((taskId, opts) => {
    const db = initDb();
    try {
      const t = claimTask(db, taskId, opts.agent);
      if (t) console.log(`Task ${taskId} claimed by ${opts.agent}`);
      else console.error(`Failed to claim task ${taskId} (already taken or not pending)`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

task.command('complete <task-id>')
  .description('Complete a task')
  .option('--result-path <path>', 'Path to result file')
  .option('--summary <text>', 'Summary of result')
  .action((taskId, opts) => {
    const db = initDb();
    try {
      completeTask(db, taskId, { resultPath: opts.resultPath, summary: opts.summary });
      console.log(`Task ${taskId} completed.`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

task.command('fail <task-id>')
  .description('Fail a task')
  .option('--reason <text>', 'Reason for failure')
  .action((taskId, opts) => {
    const db = initDb();
    try {
      failTask(db, taskId, opts.reason);
      console.log(`Task ${taskId} marked as failed.`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

task.command('retry <task-id>')
  .description('Retry a failed task')
  .action((taskId) => {
    const db = initDb();
    try {
      retryTask(db, taskId);
      console.log(`Task ${taskId} re-queued for retry.`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

// Agent commands
const agent = program.command('agent').description('Agent management');

agent.command('register <name>')
  .description('Register a new agent')
  .option('--capabilities <list>', 'Comma-separated capabilities', '')
  .option('--max-concurrent <n>', 'Max concurrent tasks', '1')
  .action((name, opts) => {
    const db = initDb();
    try {
      const a = registerAgent(db, {
        name,
        capabilities: opts.capabilities ? opts.capabilities.split(',').map(s => s.trim()) : [],
        maxConcurrent: parseInt(opts.maxConcurrent, 10),
      });
      console.log(`Registered agent "${a.name}" with capabilities: ${a.capabilities.join(', ') || 'none'}`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

agent.command('list')
  .description('List all agents')
  .action(() => {
    const db = initDb();
    const agents = listAgents(db);
    if (agents.length === 0) { console.log('No agents registered.'); }
    else {
      for (const a of agents) {
        console.log(`${a.name} | capabilities: ${a.capabilities.join(', ')} | load: ${a.current_load}/${a.max_concurrent}`);
      }
    }
    closeDb();
  });

agent.command('status <name>')
  .description('Show agent status')
  .action((name) => {
    const db = initDb();
    try {
      const s = agentStatus(db, name);
      console.log(`Agent: ${s.name}`);
      console.log(`Capabilities: ${s.capabilities.join(', ')}`);
      console.log(`Load: ${s.current_load}/${s.max_concurrent}`);
      console.log(`Active tasks: ${s.activeTasks.length}`);
      for (const t of s.activeTasks) console.log(`  - ${t.id}: ${t.title}`);
    } catch (e) { console.error(e.message); }
    closeDb();
  });

// Top-level commands
program.command('sweep')
  .description('Timeout stale tasks and retry eligible ones')
  .action(() => {
    const db = initDb();
    const result = sweep(db);
    console.log(`Sweep complete: ${result.timedOut} timed out, ${result.retried} retried`);
    closeDb();
  });

program.command('refresh')
  .description('Regenerate all interchange .md files')
  .action(async () => {
    const db = initDb();
    await refreshInterchange(db);
    console.log('Interchange files refreshed.');
    closeDb();
  });

program.command('backup')
  .description('Backup the database')
  .option('--output <path>', 'Output file path')
  .action(async (opts) => {
    const db = initDb();
    try {
      const p = await backup(db, opts.output);
      console.log(`Backup saved to: ${p}`);
    } finally {
      closeDb();
    }
  });

program.command('restore <backup-file>')
  .description('Restore from a backup')
  .action((backupFile) => {
    restore(backupFile);
    console.log(`Database restored from: ${backupFile}`);
  });

program.parse();


path: src/db.js
content: /**
 * @module db
 * Database initialization, WAL mode, and migration runner.
 */
import Database from 'better-sqlite3';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DEFAULT_DB_PATH = path.join(__dirname, '..', 'data', 'orchestration.db');
const MIGRATIONS_DIR = path.join(__dirname, '..', 'migrations');

let _db = null;

/**
 * Get or create the database connection.
 * @param {string} [dbPath] - Optional path to database file
 * @returns {import('better-sqlite3').Database}
 */
export function getDb(dbPath) {
  if (_db) return _db;
  const p = dbPath || DEFAULT_DB_PATH;
  fs.mkdirSync(path.dirname(p), { recursive: true });
  _db = new Database(p);
  _db.pragma('journal_mode = WAL');
  _db.pragma('foreign_keys = ON');
  return _db;
}

/**
 * Close the database connection.
 */
export function closeDb() {
  if (_db) {
    _db.close();
    _db = null;
  }
}

/**
 * Reset the cached db reference (for testing).
 */
export function resetDb() {
  _db = null;
}

/**
 * Run pending migrations.
 * @param {import('better-sqlite3').Database} db
 */
export function runMigrations(db) {
  // Ensure _migrations table exists
  db.exec(`CREATE TABLE IF NOT EXISTS _migrations (
    version INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    applied_at TEXT NOT NULL DEFAULT (datetime('now')),
    checksum TEXT NOT NULL
  )`);

  const applied = new Set(
    db.prepare('SELECT version FROM _migrations').all().map(r => r.version)
  );

  const files = fs.readdirSync(MIGRATIONS_DIR)
    .filter(f => f.endsWith('.sql'))
    .sort();

  for (const file of files) {
    const version = parseInt(file.split('_')[0], 10);
    if (applied.has(version)) continue;

    const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf8');
    const upSection = sql.split('-- DOWN')[0].replace('-- UP', '').trim();
    const checksum = crypto.createHash('sha256').update(sql).digest('hex').slice(0, 16);

    const migrate = db.transaction(() => {
      db.exec(upSection);
      db.prepare('INSERT INTO _migrations (version, name, checksum) VALUES (?, ?, ?)')
        .run(version, file, checksum);
    });
    migrate();
  }
}

/**
 * Initialize database: open + migrate.
 * @param {string} [dbPath]
 * @returns {import('better-sqlite3').Database}
 */
export function initDb(dbPath) {
  const db = getDb(dbPath);
  runMigrations(db);
  return db;
}


path: src/protocol.js
content: /**
 * @module protocol
 * Handoff protocol: generate task .md content for interchange.
 */

/**
 * Generate frontmatter and content for a task .md file.
 * @param {object} task - Task row from DB
 * @param {object} [result] - Result row if completed
 * @returns {{ frontmatter: object, content: string }}
 */
export function taskToMd(task, result) {
  const frontmatter = {
    skill: 'orchestration',
    type: 'detail',
    layer: 'state',
    task_id: task.id,
    status: task.status,
    assigned_to: task.assigned_agent || null,
    created_by: task.created_by,
    priority: task.priority,
    timeout_minutes: task.timeout_minutes,
    depends_on: Array.isArray(task.depends_on) ? task.depends_on : JSON.parse(task.depends_on || '[]'),
    version: 1,
    generator: 'orchestration@1.0.0',
    tags: ['task'],
  };

  let content = `# Task: ${task.title}\n\n`;
  content += `## Description\n${task.description || 'No description provided.'}\n\n`;
  content += `## Constraints\n- Complete within ${task.timeout_minutes} minutes\n- Priority: ${task.priority}\n\n`;
  content += `## Result\n`;

  if (result) {
    content += `**Status:** Completed\n`;
    if (result.output_path) content += `**Output:** ${result.output_path}\n`;
    if (result.summary) content += `**Summary:** ${result.summary}\n`;
  } else {
    content += `<!-- Filled by assigned agent on completion -->\n`;
  }

  return { frontmatter, content };
}


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