fix(routing): stale route cache + --explore false (3.10.8) (#2229)

* feat(workflows): add native Workflow JS orchestration surface (ADR-0002)

ruflo-workflows now documents both workflow surfaces: the existing 10
workflow_* MCP tools and the native Claude Code Workflow JS capability
(.claude/workflows/*.js — agent/parallel/pipeline/phase fan-out).

- ADR-0002 (Accepted): adopts native orchestration alongside the MCP surface
- Reference workflow .claude/workflows/plugin-contract-audit.js (fans smoke
  contracts across all plugins, diagnoses failures in parallel)
- README: native orchestration section + four-hook API + surface decision table
- workflow-create/workflow-run skills, workflow-specialist agent, /workflow
  command made surface-aware
- plugin.json 0.3.0 -> 0.4.0; native-workflow keywords + component block
- smoke.sh reconciled (stale version + ADR-0001 status) and extended 11 -> 15
  checks; smoke-gaia.sh version assertion bumped to 0.4.0

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(routing): #bugB stale route cache + #bugC --explore false (3.10.8)

Two routing-learning correctness bugs from the intelligence audit's remaining
punch-list (docs/reviews/intelligence-system-audit-2026-05-29.md §Remediation).

- Bug B (stale route cache): QLearningRouter.update() only invalidated the
  whole route cache every 50 updates, so a freshly-learned Q-update stayed
  hidden behind a stale cached decision — feedback appeared to have no effect
  on routing in-process until 50 updates accumulated. Now update() invalidates
  the updated state's cache entry immediately (new invalidateCacheEntry).
  Verified: learned route flips coder→researcher within 10 updates (was 50+).

- Bug C (--explore false ignored): boolean flags dropped an explicit space-form
  value, forcing a default-true boolean (explore) to true even with
  , so exploitation could never be forced. parser.ts now
  consumes a true/false literal for boolean flags (--explore false / -e false),
  while --explore=false and --no-explore keep working. Verified deterministic.

+4 regression tests (15/15 bug-cluster pass); 52/52 parser tests pass; cli
build clean. Audit doc updated with full remediation status (3.10.7 + 3.10.8
shipped; SONA-default/MicroLoRA/EWC-Fisher/per-task-bandit deferred with
honest rationale — the latter two need an ADR/upstream fix, not a patch).

Co-Authored-By: RuFlo <ruv@ruv.net>
This commit is contained in:
rUv
2026-05-29 13:02:15 -04:00
committed by GitHub
parent 08b3623759
commit a73a2cbe32
18 changed files with 504 additions and 56 deletions
@@ -0,0 +1,91 @@
export const meta = {
name: 'plugin-contract-audit',
description: 'Run every ruflo plugin smoke contract, fan diagnosis agents out over the failures, and report a punch list',
phases: [
{ title: 'Sweep', detail: 'run all plugins/*/scripts/smoke.sh, collect pass/fail' },
{ title: 'Diagnose', detail: 'one agent per failing plugin — root cause + minimal fix' },
{ title: 'Report', detail: 'assemble the audit summary' },
],
}
// args (all optional):
// string → only audit plugins whose name contains this substring
// { filter?: string, → same substring filter
// diagnose?: boolean } → set false to skip the Diagnose phase (sweep only)
const opts = typeof args === 'string' ? { filter: args } : (args || {})
const FILTER = opts.filter || ''
const DIAGNOSE = opts.diagnose !== false
const SWEEP_SCHEMA = {
type: 'object', additionalProperties: false,
required: ['results'],
properties: {
results: {
type: 'array',
items: {
type: 'object', additionalProperties: false,
required: ['plugin', 'passed', 'failed'],
properties: {
plugin: { type: 'string' },
passed: { type: 'integer' },
failed: { type: 'integer' },
exitCode: { type: 'integer' },
failingChecks: { type: 'array', items: { type: 'string' } },
},
},
},
notes: { type: 'string' },
},
}
const DIAGNOSIS_SCHEMA = {
type: 'object', additionalProperties: false,
required: ['plugin', 'rootCause', 'proposedFix', 'confident'],
properties: {
plugin: { type: 'string' },
rootCause: { type: 'string' },
proposedFix: { type: 'string' },
files: { type: 'array', items: { type: 'string' } },
confident: { type: 'boolean' },
},
}
phase('Sweep')
const filterClause = FILTER
? `Only audit plugins whose directory name contains "${FILTER}". `
: ''
const sweep = await agent(
`From the repo root, audit every ruflo plugin's smoke contract. ${filterClause}For each script matching the glob plugins/*/scripts/smoke.sh, run it with bash and capture its output and exit code. Each smoke script prints a trailing "N passed, M failed" line.
For every plugin report: plugin (the directory name under plugins/), passed (integer), failed (integer), exitCode (integer), and failingChecks (the "→ ..." lines that printed FAIL, verbatim, empty array if none).
Do NOT modify any files — this is read/run only. Return every audited plugin via the schema, not just the failing ones.`,
{ label: 'sweep:all-smokes', phase: 'Sweep', schema: SWEEP_SCHEMA, agentType: 'tester' }
)
const results = (sweep?.results || []).filter((r) => !FILTER || r.plugin.includes(FILTER))
const failures = results.filter((r) => r.failed > 0 || (r.exitCode && r.exitCode !== 0))
log(`Sweep: ${results.length} plugins audited, ${failures.length} failing`)
let diagnoses = []
if (DIAGNOSE && failures.length) {
phase('Diagnose')
diagnoses = (await parallel(
failures.map((f) => () =>
agent(
`Plugin "${f.plugin}" fails its smoke contract (plugins/${f.plugin}/scripts/smoke.sh): ${f.failed} check(s) failed. Failing checks:\n${(f.failingChecks || []).join('\n') || '(not captured — re-run the smoke script to see them)'}\n\nRead plugins/${f.plugin}/scripts/smoke.sh and the plugin files it inspects (plugin.json, README.md, skills, agents, commands, docs/adrs). Determine the ROOT CAUSE of each failing check and propose a MINIMAL fix. Distinguish a stale assertion in smoke.sh (the contract drifted from reality) from a genuine plugin defect. Do NOT edit anything — report only, via the schema, with confident=true only if the root cause is unambiguous.`,
{ label: `diagnose:${f.plugin}`, phase: 'Diagnose', schema: DIAGNOSIS_SCHEMA, agentType: 'code-analyzer' }
)
)
)).filter(Boolean)
log(`Diagnose: ${diagnoses.length}/${failures.length} diagnosed`)
}
phase('Report')
const summary = {
audited: results.length,
passing: results.length - failures.length,
failing: failures.length,
failingPlugins: failures.map((f) => ({ plugin: f.plugin, failed: f.failed })),
diagnoses,
}
log(`Report: ${summary.passing}/${summary.audited} plugins pass their contract`)
return summary
@@ -87,3 +87,26 @@ What does **not** hold up is the **performance-multiplier marketing**: the HNSW
5. Wire or remove the inert pieces (#4#7) so named capabilities are either real or not advertised.
*Per-subsystem raw evidence is preserved in the audit run; load-bearing file:line references are inline above.*
---
## Remediation status (updated 2026-05-29)
### Shipped in v3.10.7
-**#1 negative-reward inversion** — fixed in `parser.ts` (negative numeric literals accepted as flag values). Verified in the published artifact.
-**#2 Flash Attention fabrication** — randomized telemetry removed from both `attention-coordinator` copies (unmeasured sentinel + "unverified" labels).
-**#3 embedding observability** — `generateEmbedding` returns `backend: onnx|mock`, surfaced in `memory_bridge_status`/`import`.
-**#4/#5 MCP learning** — `trajectory-end` no longer feeds EWC a synthetic gradient; `hooks_intelligence_learn` runs a real cycle.
-**HNSW optimization** — root-caused the silent brute-force fallback (no `storagePath` → native DB lock → silent `catch{}`); fixed with unique storagePath + `hnswConfig {m:32, efC:200}` + a visible fallback warning. Measured 0.92×→3.24.7× at N=5k, 0.95×→1.89× at N=20k.
- ✅ Perf docs rewritten to measured values; `scripts/benchmark-intelligence.mjs` added.
### Shipped in v3.10.8
-**#10 Bug B (stale route cache)** — `update()` now invalidates the updated state's cache entry immediately (was: whole cache only every 50 updates, hiding learning in-process). Verified: learned route changes within 10 updates.
-**#10 Bug C (`--explore false` ignored)** — the parser now consumes an explicit `true`/`false` value for boolean flags in the space form, so a default-true boolean can be disabled. Verified deterministic exploitation with `explore=false`.
### Deferred — with honest rationale (NOT fixed)
- **SONA "default-path adapt is a stub"** — re-examined: the default intelligence path's pattern-confidence learning runs through `LocalSonaCoordinator`, which IS real and was confirmed working end-to-end (confidence 0.906→1.0). The inert piece is the *supplementary* `@ruvector/ruvllm` `SonaCoordinator` forward, which is not load-bearing for the confirmed learning. The audit slightly overstated this as a default-path gap; wiring the WASM SONA into the default path is an *enhancement*, not a bug fix, and is left for a dedicated change.
- **WASM MicroLoRA `apply()` inert** — lives in the `@ruvector/ruvllm`/`-wasm` **published dependency**, not ruflo source; cannot be fixed by editing a node_module. Requires an upstream fix or a deliberate route-around (use the real JS `LoraAdapter` path). Tracked, not shipped here.
- **EWC++ "Fisher information" is a proxy** (`|w|`/`embedding²`, not gradient curvature) — functional regularizer; relabeling vs. real gradient-Fisher is a design decision, deferred.
- **Bandit priors global-per-model, not per-task** — making them per-task changes the **persisted state schema** (`priors` → per-task-bucket map), so it needs an ADR + migration, not a patch. Deferred to a dedicated change.
- **Embedding ONNX broken without native `sharp`** — now *observable* (3.10.7 `backend:mock`); a sharp-free transformers path / bundled binary is the real fix, deferred.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-flow",
"version": "3.10.7",
"version": "3.10.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-flow",
"version": "3.10.7",
"version": "3.10.8",
"license": "MIT",
"dependencies": {
"@claude-flow/cli-core": "^3.7.0-alpha.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-flow",
"version": "3.10.7",
"version": "3.10.8",
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
"main": "dist/index.js",
"type": "module",
@@ -1,7 +1,7 @@
{
"name": "ruflo-workflows",
"description": "Workflow automation with templates, orchestration, and lifecycle management — wraps 10 workflow_* MCP tools (create/run/execute/status/list/pause/resume/cancel/delete/template) with full state-machine lifecycle (created → running ↔ paused → completed/cancelled). Includes GAIA benchmark component for Princeton HAL leaderboard submissions.",
"version": "0.3.0",
"description": "Workflow automation across two surfaces: the 10 workflow_* MCP tools (create/run/execute/status/list/pause/resume/cancel/delete/template) with full state-machine lifecycle (created → running ↔ paused → completed/cancelled), and native Claude Code Workflow JS orchestration (.claude/workflows/*.js — agent/parallel/pipeline/phase fan-out). Includes GAIA benchmark component for Princeton HAL leaderboard submissions.",
"version": "0.4.0",
"author": {
"name": "ruvnet",
"url": "https://github.com/ruvnet"
@@ -18,6 +18,10 @@
"workflow-templates",
"pause-resume",
"lifecycle",
"native-workflow",
"agent-fanout",
"pipeline",
"parallel",
"gaia",
"benchmark",
"hal-leaderboard",
@@ -53,6 +57,14 @@
"gaia-patterns",
"gaia-debug-patterns"
]
},
"native_workflows": {
"description": "Native Claude Code Workflow JS orchestration — deterministic subagent fan-out via agent/parallel/pipeline/phase, authored as .claude/workflows/*.js named workflows",
"location": ".claude/workflows/*.js",
"api": ["agent", "parallel", "pipeline", "phase", "log"],
"invocation": "Workflow tool — Workflow({ name }) | Workflow({ scriptPath }) | Workflow({ resumeFromRunId })",
"reference_workflow": ".claude/workflows/plugin-contract-audit.js",
"adrs": ["ADR-0002"]
}
}
}
+65 -9
View File
@@ -1,6 +1,11 @@
# ruflo-workflows
Workflow automation with templates, orchestration, and full state-machine lifecycle management.
Workflow automation across **two complementary surfaces**:
1. **MCP `workflow_*` tools** — declarative, persisted workflow definitions with a full state-machine lifecycle (create → run ↔ pause → complete/cancel). Best for long-lived, resumable, human-gated pipelines.
2. **Native Claude Code `Workflow` JS** — imperative orchestration scripts (`.claude/workflows/*.js`) that fan subagents out deterministically via `agent` / `parallel` / `pipeline` / `phase`. Best for comprehensive fan-out: review, audit, migration, research.
Neither subsumes the other — see [Choosing a surface](#choosing-a-surface).
## Install
@@ -11,19 +16,19 @@ Workflow automation with templates, orchestration, and full state-machine lifecy
## Features
- **Workflow creation**: Define multi-step processes with conditions and parallel execution
- **Templates**: Reusable workflow patterns for common operations
- **Lifecycle management**: Execute, pause, resume, cancel running workflows
- **Approval gates**: Manual pause points for human review
- **MCP workflow definitions**: multi-step processes with conditions, parallel steps, and templates
- **Lifecycle management**: execute, pause, resume, cancel running workflows
- **Approval gates**: manual pause points for human review
- **Native orchestration**: author `.claude/workflows/*.js` fan-out/pipeline scripts and run them with the `Workflow` tool
## Commands
- `/workflow` -- List workflows, check status, view templates
- `/workflow` -- List MCP workflows + templates **and** native `.claude/workflows/*.js` scripts
## Skills
- `workflow-create` -- Create reusable workflow templates
- `workflow-run` -- Execute and manage running workflows
- `workflow-create` -- Author MCP workflow templates **or** native `.claude/workflows/*.js` orchestration scripts
- `workflow-run` -- Execute/manage MCP workflows **or** invoke + resume native workflows
## Compatibility
@@ -68,6 +73,56 @@ created ──run──→ running ──pause──→ paused ──resume─
`workflow_execute` is the **stateless** path — fire-and-forget, no persisted state machine.
## Native Workflow Orchestration (Claude Code `Workflow` tool)
The native surface runs a JavaScript orchestration script that fans subagents out deterministically. Scripts live in **`.claude/workflows/*.js`** and each begins with a **pure-literal** `export const meta` block — the `meta.name` makes it an invocable **named workflow**.
```js
export const meta = {
name: 'plugin-contract-audit',
description: 'Run every plugin smoke contract, diagnose failures, report',
phases: [{ title: 'Sweep' }, { title: 'Diagnose' }, { title: 'Report' }],
}
// body runs inside an async wrapper — top-level await + return are legal
const sweep = await agent('run all plugins/*/scripts/smoke.sh ...', { schema: SWEEP_SCHEMA })
const failures = (sweep?.results || []).filter((r) => r.failed > 0)
const diagnoses = await parallel(failures.map((f) => () =>
agent(`diagnose ${f.plugin}`, { schema: DIAGNOSIS_SCHEMA })))
return { audited: sweep.results.length, failures, diagnoses }
```
### The four-hook API
| Hook | Purpose |
|------|---------|
| `agent(prompt, opts)` | Spawn one subagent; with `opts.schema` returns validated structured output |
| `parallel(thunks)` | Run thunks concurrently with a **barrier** (await all) |
| `pipeline(items, ...stages)` | Run each item through all stages independently — **no barrier** |
| `phase(title)` / `log(msg)` | Progress grouping and narration |
`meta` MUST be a pure literal (no variables, calls, or interpolation). Use `parallel` only when you genuinely need every result together; otherwise prefer `pipeline`.
### Invocation
```js
Workflow({ name: 'plugin-contract-audit' }) // run a named .claude/workflows/*.js
Workflow({ scriptPath: '.claude/workflows/foo.js' }) // run a script by path
Workflow({ name: 'plugin-contract-audit', args: 'ruflo-agentdb' }) // pass args (the script's `args` global)
Workflow({ scriptPath, resumeFromRunId: 'wf_…' }) // resume — unchanged agent() calls return cached
```
The repo ships a reference workflow at `.claude/workflows/plugin-contract-audit.js` and a worked example at `.claude/workflows/intelligence-system-hardening.js`.
## Choosing a surface
| If you need… | Use |
|--------------|-----|
| A persisted definition that pauses for human approval and resumes across sessions | **MCP `workflow_*`** |
| A stateful lifecycle (`created → running ↔ paused → completed/cancelled`) the engine schedules | **MCP `workflow_*`** |
| Deterministic fan-out of many subagents (review N dimensions, audit N plugins, migrate N files) | **Native `Workflow` JS** |
| Structured, schema-validated results aggregated in code | **Native `Workflow` JS** |
| A one-shot stateless run | MCP `workflow_execute` **or** native `Workflow` |
## Namespace coordination
This plugin owns the `workflows-state` AgentDB namespace (kebab-case, follows the convention from [ruflo-agentdb ADR-0001 §"Namespace convention"](../ruflo-agentdb/docs/adrs/0001-agentdb-optimization.md)). Reserved namespaces (`pattern`, `claude-memories`, `default`) MUST NOT be shadowed.
@@ -78,12 +133,13 @@ This plugin owns the `workflows-state` AgentDB namespace (kebab-case, follows th
```bash
bash plugins/ruflo-workflows/scripts/smoke.sh
# Expected: "11 passed, 0 failed"
# Expected: "15 passed, 0 failed"
```
## Architecture Decisions
- [`ADR-0001` — ruflo-workflows plugin contract (10-tool MCP surface, lifecycle state machine, smoke as contract)](./docs/adrs/0001-workflows-contract.md)
- [`ADR-0002` — native Claude Code Workflow orchestration (`.claude/workflows/*.js` fan-out) alongside the MCP surface](./docs/adrs/0002-native-workflow-orchestration.md)
## Related Plugins
@@ -4,7 +4,11 @@ description: Workflow automation specialist for creating, executing, and managin
model: sonnet
---
You are a workflow automation specialist for Ruflo's workflow engine. Your responsibilities:
You are a workflow automation specialist for Ruflo. You work across **two surfaces** and pick the right one for each job.
## Surface 1 — MCP `workflow_*` (persisted, lifecycle)
For long-lived, resumable, human-gated pipelines with a state machine (created → running ↔ paused → completed/cancelled).
1. **Design workflows** with sequential, parallel, and conditional steps
2. **Execute workflows** and monitor step-by-step progress
@@ -21,6 +25,18 @@ Use these MCP tools:
Design workflows with clear failure paths and approval gates for critical steps.
## Surface 2 — Native `.claude/workflows/*.js` (deterministic fan-out)
For comprehensive subagent fan-out (review N dimensions, audit N targets, migrate N files, multi-source research) where results are aggregated in code.
- Author a `.js` file under `.claude/workflows/` starting with a **pure-literal** `export const meta = { name, description, phases }`. The body runs in an async wrapper with `agent` / `parallel` / `pipeline` / `phase` / `log` injected; pass `schema` to `agent()` for validated structured output. Default to `pipeline` over `parallel`. Never call `Date.now()`/`Math.random()` (they throw).
- Invoke with the `Workflow` tool: `Workflow({ name })`, `Workflow({ scriptPath })`, `Workflow({ name, args })`, or `Workflow({ scriptPath, resumeFromRunId })`.
- Reference implementation: `.claude/workflows/plugin-contract-audit.js`. Contract: [ADR-0002](../docs/adrs/0002-native-workflow-orchestration.md).
## Choosing a surface
Persisted definition that pauses for human approval and resumes across sessions → **MCP**. Deterministic parallel/pipeline subagent fan-out with code-side aggregation → **native JS**. One-shot stateless run → either.
### Memory Learning
Store successful workflow templates and execution patterns:
+12 -2
View File
@@ -1,10 +1,20 @@
---
name: workflow
description: Workflow management -- list workflows, check status, view templates
description: Workflow management -- list MCP workflows + templates and native .claude/workflows/*.js scripts
---
Manage workflows:
Manage workflows across both surfaces:
## MCP workflows (persisted, lifecycle)
1. Call `mcp__claude-flow__workflow_list` to show all defined workflows
2. Call `mcp__claude-flow__workflow_template` to show available templates
3. Show workflow IDs, status (running/paused/completed), and step progress
## Native workflows (`.claude/workflows/*.js`)
4. List the native orchestration scripts: `ls .claude/workflows/*.js` (each file's `meta.name` is its invocable name)
5. For each, read the `export const meta` block and show `name` + `description` + phase titles
6. Run one with the `Workflow` tool — `Workflow({ name })` — or author a new one via the `workflow-create` skill
See ADR-0002 for when to use which surface.
@@ -0,0 +1,87 @@
---
id: ADR-0002
title: ruflo-workflows adopts native Claude Code Workflow orchestration (.claude/workflows/*.js) alongside the MCP workflow_* surface
status: Accepted
date: 2026-05-29
authors:
- coder (Claude Code)
tags: [plugin, workflows, orchestration, native-workflow, claude-code, agent-fanout]
---
## Context
[ADR-0001](./0001-workflows-contract.md) established `ruflo-workflows` as the canonical wrapper for the **10 `workflow_*` MCP tools** (`v3/@claude-flow/cli/src/mcp-tools/workflow-tools.ts`). That surface is *declarative and persisted*: a workflow definition is created, then run/paused/resumed/cancelled through a state machine, with state indexed in the `workflows-state` AgentDB namespace.
Claude Code has since shipped a second, complementary capability — the **native `Workflow` tool**. It executes a JavaScript orchestration script that fans subagents out deterministically via four hooks:
| Hook | Purpose |
|------|---------|
| `agent(prompt, opts)` | Spawn one subagent; with `opts.schema` it returns validated structured output |
| `parallel(thunks)` | Run thunks concurrently with a barrier (await all) |
| `pipeline(items, ...stages)` | Run each item through all stages independently, no barrier |
| `phase(title)` / `log(msg)` | Progress grouping and narration |
These scripts live in `.claude/workflows/*.js`. Each begins with a pure-literal `export const meta = { name, description, phases }` block; the file's `meta.name` makes it an invocable **named workflow** (`Workflow({ name })`) that also surfaces in the skill/workflow list. The repo already contains one such script — `.claude/workflows/intelligence-system-hardening.js`.
The two surfaces solve different problems and neither subsumes the other:
| Dimension | MCP `workflow_*` (ADR-0001) | Native `Workflow` JS (this ADR) |
|-----------|------------------------------|----------------------------------|
| Form | Declarative definition + lifecycle state machine | Imperative JS orchestration script |
| Unit of work | Persisted workflow steps | Subagents (`agent()`) |
| Persistence | Stateful, resumable across sessions (`workflows-state`) | Per-run journal; resume via `resumeFromRunId` |
| Concurrency | Engine-scheduled steps | `parallel()` barrier / `pipeline()` streaming |
| Best for | Long-lived, pausable, human-gated pipelines | Comprehensive fan-out: review, audit, migration, research |
| Location | AgentDB definitions | `.claude/workflows/*.js` |
Before this ADR the plugin documented only the MCP surface, so users had no in-plugin guidance for authoring or running the native scripts that the project is already accumulating.
## Decision
1. Add this ADR (Accepted). ADR-0001 remains Accepted and unchanged; this ADR **amends** it by adding a second surface, it does not supersede it.
2. The plugin documents **both** surfaces. README gains a "Native Workflow Orchestration" section: the four-hook API, the pure-literal `meta` requirement, the `.claude/workflows/*.js` location/discovery rule, invocation (`Workflow({ name })` / `{ scriptPath }` / `{ resumeFromRunId }`), and a decision table for choosing MCP-vs-native.
3. Skills extend to cover authoring and running native scripts:
- `workflow-create` — how to author a `.claude/workflows/*.js` (meta block, hook API, schema-validated agents, `parallel` vs `pipeline`).
- `workflow-run` — how to invoke a named native workflow and resume it, in addition to MCP run/pause/resume/cancel.
4. The `workflow-specialist` agent and `/workflow` command become surface-aware: the command also lists `.claude/workflows/*.js`; the agent knows when to reach for native fan-out vs the MCP lifecycle engine.
5. Ship a reference native workflow — `.claude/workflows/plugin-contract-audit.js` — that runs every `plugins/*/scripts/smoke.sh`, fans diagnosis agents out over the failures, and reports. It is the executable example of the new capability and is directly useful for the repo's "smoke as contract" discipline.
6. Bump `0.3.0 → 0.4.0` (minor: additive capability). Keywords add `native-workflow`, `agent-fanout`, `pipeline`, `parallel`.
7. `scripts/smoke.sh` is reconciled with current reality (version, ADR-0001 now Accepted) and extended with native-surface checks: ADR-0002 present + Accepted, README native section + four-hook API documented, `.claude/workflows/` location referenced.
## Consequences
**Positive:** the plugin now reflects the full workflow capability of the platform, not just the MCP slice. Authors get a documented, validated path to write fan-out/pipeline orchestrations, with a working reference script. The audit workflow turns the project's 32 plugin smoke contracts into a one-call parallel sweep.
**Negative:** two surfaces means contributors must pick the right one; the README decision table mitigates this. The native scripts are project-level (`.claude/workflows/`), not shipped inside the plugin package, so the plugin documents and exemplifies them rather than bundling them.
**Neutral:** the `workflows-state` namespace claim is unchanged and applies to the MCP surface only; native scripts persist via the per-run journal, not AgentDB.
## Verification
```bash
# Plugin contract (documents both surfaces; stays inside the plugin boundary):
bash plugins/ruflo-workflows/scripts/smoke.sh
# Expected: "15 passed, 0 failed"
```
The reference native workflow is project-level (`.claude/workflows/`), not part of the plugin
package, so its syntax is validated separately rather than from the plugin smoke. A native
workflow body runs inside an async wrapper (top-level `await`/`return` are legal), so it is
checked as an async-wrapped ES module with `meta` kept as a module export:
```bash
node -e 'const fs=require("fs");let s=fs.readFileSync(".claude/workflows/plugin-contract-audit.js","utf8").replace(/^export\s+const\s+meta/m,"const meta");fs.writeFileSync("/tmp/wf.mjs","let agent,parallel,pipeline,phase,log,args,budget,workflow;async function __wf(){\n"+s+"\n}")' \
&& node --check /tmp/wf.mjs && echo OK
```
## Related
- [`0001-workflows-contract.md`](./0001-workflows-contract.md) — the MCP `workflow_*` contract this ADR amends
- `.claude/workflows/intelligence-system-hardening.js` — first native workflow in the repo
- `.claude/workflows/plugin-contract-audit.js` — reference native workflow shipped with this ADR
- `plugins/ruflo-loop-workers/docs/adrs/0001-loop-workers-contract.md` — sibling automation surface (recurring loops)
- `plugins/ruflo-sparc/docs/adrs/0001-sparc-contract.md` — SPARC phase transitions as workflows
## Implementation status
ADR-0002 accepted. Native-workflow documentation added to README + both skills + agent + command; plugin bumped to v0.4.0; reference workflow `.claude/workflows/plugin-contract-audit.js` authored and syntax-validated; smoke gate extended to 15 checks covering both surfaces.
@@ -11,9 +11,9 @@ bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
# ── plugin.json ──────────────────────────────────────────────────────────────
step "1. plugin.json bumped to 0.3.0 with gaia keywords"
step "1. plugin.json at 0.4.0 with gaia keywords"
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ "$v" != "0.3.0" ]]; then bad "expected 0.3.0, got '$v'"; else
if [[ "$v" != "0.4.0" ]]; then bad "expected 0.4.0, got '$v'"; else
miss=""
for k in gaia benchmark hal-leaderboard evaluation; do
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
@@ -118,7 +118,7 @@ grep -qE 'SWE-bench|WebArena|HumanEval|extensib' "$ROOT/skills/gaia-submission/S
# ── original smoke test still passes ─────────────────────────────────────────
step "13. original plugin smoke (0.3.0 is not 0.2.0 but other checks still valid)"
step "13. core plugin artifacts intact (skills + agent + command)"
# Re-run a subset: skills + agent + command for pre-existing artifacts
miss=""
for s in workflow-create workflow-run; do
+32 -5
View File
@@ -7,9 +7,9 @@ step() { printf "→ %s ... " "$1"; }
ok() { printf "PASS\n"; PASS=$((PASS+1)); }
bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
step "1. plugin.json declares 0.2.0 with new keywords"
step "1. plugin.json declares 0.4.0 with lifecycle keywords"
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ "$v" != "0.2.0" ]]; then bad "expected 0.2.0, got '$v'"; else
if [[ "$v" != "0.4.0" ]]; then bad "expected 0.4.0, got '$v'"; else
miss=""
for k in mcp workflow-templates pause-resume lifecycle; do
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
@@ -72,10 +72,10 @@ grep -q "workflow_execute" "$F" \
&& grep -qE "stateless|one-shot|fire-and-forget" "$F" \
&& ok || bad "stateless path not documented"
step "10. ADR-0001 exists with status Proposed"
step "10. ADR-0001 exists with status Accepted"
ADR="$ROOT/docs/adrs/0001-workflows-contract.md"
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Proposed" "$ADR" \
&& ok || bad "ADR missing or status != Proposed"
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Accepted" "$ADR" \
&& ok || bad "ADR-0001 missing or status != Accepted"
step "11. no wildcard tool grants in skills"
bad_skills=""
@@ -84,5 +84,32 @@ for f in "$ROOT"/skills/*/SKILL.md; do
done
[[ -z "$bad_skills" ]] && ok || bad "wildcard:$bad_skills"
step "12. ADR-0002 exists with status Accepted"
ADR2="$ROOT/docs/adrs/0002-native-workflow-orchestration.md"
[[ -f "$ADR2" ]] && grep -qE "^status:[[:space:]]*Accepted" "$ADR2" \
&& ok || bad "ADR-0002 missing or status != Accepted"
step "13. README documents native workflow orchestration"
F="$ROOT/README.md"
grep -q "Native Workflow Orchestration" "$F" \
&& grep -qF ".claude/workflows" "$F" \
&& grep -qF "Workflow({" "$F" \
&& ok || bad "native orchestration section incomplete"
step "14. four-hook API documented (agent/parallel/pipeline/phase)"
F="$ROOT/README.md"
miss=""
for hook in 'agent(' 'parallel(' 'pipeline(' 'phase('; do
grep -qF "$hook" "$F" || miss="$miss $hook"
done
[[ -z "$miss" ]] && ok || bad "missing hooks:$miss"
step "15. plugin.json declares native-workflow keywords"
miss=""
for k in native-workflow agent-fanout pipeline parallel; do
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
done
[[ -z "$miss" ]] && ok || bad "missing native keywords:$miss"
printf "\n%s passed, %s failed\n" "$PASS" "$FAIL"
[[ $FAIL -eq 0 ]] || exit 1
@@ -1,29 +1,60 @@
---
name: workflow-create
description: Create reusable workflow templates with steps, conditions, and parallel execution
argument-hint: "<name> [--steps N]"
allowed-tools: mcp__claude-flow__workflow_create mcp__claude-flow__workflow_template mcp__claude-flow__workflow_list mcp__claude-flow__workflow_status mcp__claude-flow__workflow_delete Bash
description: Author a workflow — either an MCP workflow template (persisted, lifecycle) or a native .claude/workflows/*.js orchestration script (agent/parallel/pipeline fan-out)
argument-hint: "<name> [--native] [--steps N]"
allowed-tools: mcp__claude-flow__workflow_create mcp__claude-flow__workflow_template mcp__claude-flow__workflow_list mcp__claude-flow__workflow_status mcp__claude-flow__workflow_delete Write Read Edit Bash
---
# Workflow Create
Create reusable workflow templates for automated task execution.
Author a workflow on whichever surface fits the job.
## When to use
## Pick a surface
When you have a repeatable multi-step process (CI/CD, onboarding, release, review) that should be codified as a workflow.
- **MCP workflow template** — a persisted definition with a pause/resume lifecycle. Use for long-lived, human-gated, resumable pipelines.
- **Native `.claude/workflows/*.js`** — an imperative orchestration script that fans subagents out. Use for comprehensive fan-out (review, audit, migration, research) where you aggregate structured results in code.
## Steps
## A — MCP workflow template
1. **List templates** — call `mcp__claude-flow__workflow_template` to see available workflow templates
1. **List templates** — call `mcp__claude-flow__workflow_template` to see available templates
2. **Create workflow** — call `mcp__claude-flow__workflow_create` with steps, conditions, and execution order
3. **List workflows** — call `mcp__claude-flow__workflow_list` to see all defined workflows
4. **Check status** — call `mcp__claude-flow__workflow_status` to monitor a workflow
5. **Clean up** — call `mcp__claude-flow__workflow_delete` to remove unused workflows
## Workflow features
Features: sequential/parallel steps, conditional branching, template inheritance, pause/resume approval gates.
- Sequential and parallel step execution
- Conditional branching based on step outcomes
- Template inheritance for common patterns
- Pause/resume for manual approval gates
## B — Native `.claude/workflows/*.js`
Write a `.js` file under `.claude/workflows/`. It MUST begin with a **pure-literal** `export const meta` block; the body runs inside an async wrapper (top-level `await`/`return` are legal) with these hooks injected:
| Hook | Purpose |
|------|---------|
| `agent(prompt, opts)` | Spawn one subagent; pass `opts.schema` (JSON Schema) to get validated structured output back |
| `parallel(thunks)` | Run thunks concurrently with a **barrier**`.filter(Boolean)` the results |
| `pipeline(items, ...stages)` | Stream each item through stages independently — **prefer this** over a barrier |
| `phase(title)` / `log(msg)` | Progress grouping / narration |
```js
export const meta = {
name: 'my-workflow', // becomes the invocable name — must be a pure literal
description: 'one line',
phases: [{ title: 'Find' }, { title: 'Verify' }],
}
const SCHEMA = { type: 'object', required: ['ok'], properties: { ok: { type: 'boolean' } }, additionalProperties: false }
phase('Find')
const found = await agent('find the things', { schema: SCHEMA, agentType: 'tester' })
phase('Verify')
const checked = await parallel((found.items || []).map((it) => () =>
agent(`verify ${it}`, { schema: SCHEMA })))
return { found, checked: checked.filter(Boolean) }
```
Rules: `meta` is a pure literal (no variables/calls/interpolation); default to `pipeline` over `parallel`; never use `Date.now()`/`Math.random()` (they throw — vary by index instead). Validate syntax (the body is ESM-in-async-wrapper, not a bare module):
```bash
node -e 'const fs=require("fs");let s=fs.readFileSync(".claude/workflows/my-workflow.js","utf8").replace(/^export\s+const\s+meta/m,"const meta");fs.writeFileSync("/tmp/wf.mjs","let agent,parallel,pipeline,phase,log,args,budget,workflow;async function __wf(){\n"+s+"\n}")' \
&& node --check /tmp/wf.mjs && echo OK
```
Run it with the `workflow-run` skill or `Workflow({ name: 'my-workflow' })`. Reference: `.claude/workflows/plugin-contract-audit.js`. See [ADR-0002](../../docs/adrs/0002-native-workflow-orchestration.md).
@@ -1,29 +1,40 @@
---
name: workflow-run
description: Execute, pause, resume, and cancel running workflows
argument-hint: "<workflow-id>"
allowed-tools: mcp__claude-flow__workflow_execute mcp__claude-flow__workflow_run mcp__claude-flow__workflow_pause mcp__claude-flow__workflow_resume mcp__claude-flow__workflow_cancel mcp__claude-flow__workflow_status Bash
description: Run a workflow — drive an MCP workflow lifecycle (execute/pause/resume/cancel) or invoke + resume a native .claude/workflows/*.js orchestration via the Workflow tool
argument-hint: "<workflow-id-or-name>"
allowed-tools: mcp__claude-flow__workflow_execute mcp__claude-flow__workflow_run mcp__claude-flow__workflow_pause mcp__claude-flow__workflow_resume mcp__claude-flow__workflow_cancel mcp__claude-flow__workflow_status Workflow Read Bash
---
# Workflow Run
Execute and manage running workflows.
Run and manage a workflow on either surface.
## When to use
## A — MCP workflow lifecycle
When you need to run a defined workflow, monitor its progress, or control its execution (pause, resume, cancel).
## Steps
When you need to run a persisted definition and control its lifecycle (pause/resume/cancel):
1. **Execute** — call `mcp__claude-flow__workflow_execute` or `mcp__claude-flow__workflow_run` with the workflow ID
2. **Monitor** — call `mcp__claude-flow__workflow_status` to check progress and step outcomes
3. **Pause** — call `mcp__claude-flow__workflow_pause` to halt execution at the current step
3. **Pause** — call `mcp__claude-flow__workflow_pause` to halt at the current step
4. **Resume** — call `mcp__claude-flow__workflow_resume` to continue from where paused
5. **Cancel** — call `mcp__claude-flow__workflow_cancel` to abort the workflow
## Execution modes
Execution modes: **sequential**, **parallel** (independent steps), **conditional** (branch on outcome), **manual gate** (pause for human approval).
- **Sequential** — steps run one after another
- **Parallel** — independent steps run concurrently
- **Conditional** — steps execute based on previous step outcomes
- **Manual gate** — pause for human approval before continuing
## B — Native `.claude/workflows/*.js`
When you need a deterministic subagent fan-out, run a named native workflow with the `Workflow` tool. The named workflows are the `meta.name` of each `.claude/workflows/*.js` file (list them with `/workflow` or `ls .claude/workflows/`).
```js
Workflow({ name: 'plugin-contract-audit' }) // run a named workflow
Workflow({ name: 'plugin-contract-audit', args: 'ruflo-agentdb' }) // pass args → the script's `args` global
Workflow({ scriptPath: '.claude/workflows/foo.js' }) // run a script by path
Workflow({ scriptPath, resumeFromRunId: 'wf_…' }) // resume — unchanged agent() calls return cached
```
Notes:
- A native workflow runs in the background; you are notified on completion (don't poll). Watch live progress with `/workflows`.
- Pause/resume here is **journal-based** (`resumeFromRunId`), not the MCP state machine. Stop a run first, then resume from its `runId`.
- To author a new native workflow, use the `workflow-create` skill.
See [ADR-0002](../../docs/adrs/0002-native-workflow-orchestration.md).
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ruflo",
"version": "3.10.7",
"version": "3.10.8",
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
"main": "bin/ruflo.js",
"type": "module",
@@ -167,3 +167,49 @@ describe('#2226 — pattern store and search share a backend', () => {
expect(hit).toBeDefined();
}, 60_000);
});
/**
* 3.10.8 routing-learning fixes (follow-ups to the intelligence audit):
* Bug B — Q-router cached a stale route decision and only invalidated the
* whole cache every 50 updates, so a freshly-learned Q-update was
* hidden in-process until 50 updates accumulated. Now the updated
* state's cache entry is invalidated immediately.
* Bug C — boolean flags ignored an explicit space-form value, so
* `route task --explore false` still explored (could not disable a
* default-true boolean). The parser now consumes `true`/`false`.
*/
describe('3.10.8 #bugB — Q-router reflects a learned update immediately (no 50-update cache lag)', () => {
it('changes the exploited route within a handful of updates', async () => {
const dir = mkdtempSync(path.join(tmpdir(), 'ruflo-qcache-'));
try {
const r = createQLearningRouter({ modelPath: path.join(dir, 'q.json') });
await r.initialize();
const task = 'deep research and investigation task';
r.route(task, false); // prime the cache with the cold (all-zero) decision
for (let i = 0; i < 5; i++) r.update(task, 'researcher', 1.0);
for (let i = 0; i < 5; i++) r.update(task, 'architect', -1.0);
// Only 10 updates — well under the old 50-update invalidation threshold.
expect(r.route(task, false).route).toBe('researcher');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});
describe('3.10.8 #bugC — boolean flags accept an explicit space-form value', () => {
const parse = (argv: string[]) =>
new CommandParser({ booleanFlags: ['explore'], allowUnknownFlags: true })
.parse(['route', 'task', 'x', ...argv]).flags.explore;
it('parses --explore false as false (was forced true)', () => {
expect(parse(['--explore', 'false'])).toBe(false);
});
it('parses --explore true as true', () => {
expect(parse(['--explore', 'true'])).toBe(true);
});
it('still honors --explore=false and bare --explore', () => {
expect(parse(['--explore=false'])).toBe(false);
expect(parse(['--explore'])).toBe(true);
expect(parse(['--no-explore'])).toBe(false);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@claude-flow/cli",
"version": "3.10.7",
"version": "3.10.8",
"type": "module",
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
"main": "dist/src/index.js",
+26 -2
View File
@@ -297,7 +297,16 @@ export class CommandParser {
const normalizedKey = this.normalizeKey(key);
if (booleanFlags.has(normalizedKey)) {
flags[normalizedKey] = true;
// #explore-flag: allow an explicit boolean value (`--explore false`,
// `--explore true`). Without this, a default-true boolean could never
// be disabled via the space form — the value was dropped and the flag
// forced to true. The `=` form already worked via parseValue.
if (nextIndex < args.length && this.isBooleanLiteral(args[nextIndex])) {
flags[normalizedKey] = args[nextIndex].toLowerCase() === 'true';
nextIndex++;
} else {
flags[normalizedKey] = true;
}
} else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
flags[normalizedKey] = this.parseValue(args[nextIndex]);
nextIndex++;
@@ -315,7 +324,14 @@ export class CommandParser {
const normalizedKey = this.normalizeKey(key);
if (booleanFlags.has(normalizedKey)) {
flags[normalizedKey] = true;
// #explore-flag: short boolean flags also accept an explicit value
// (`-e false`) so a default-true boolean can be turned off.
if (nextIndex < args.length && this.isBooleanLiteral(args[nextIndex])) {
flags[normalizedKey] = args[nextIndex].toLowerCase() === 'true';
nextIndex++;
} else {
flags[normalizedKey] = true;
}
} else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
flags[normalizedKey] = this.parseValue(args[nextIndex]);
nextIndex++;
@@ -355,6 +371,14 @@ export class CommandParser {
return /^-\d*\.?\d+(?:[eE][+-]?\d+)?$/.test(arg);
}
/** True for the literal tokens `true`/`false` (case-insensitive). Used so a
* boolean flag can take an explicit value in the space form, e.g.
* `--explore false` / `-e true`. */
private isBooleanLiteral(arg: string): boolean {
const a = arg.toLowerCase();
return a === 'true' || a === 'false';
}
private parseValue(value: string): string | number | boolean {
// Boolean
if (value.toLowerCase() === 'true') return true;
@@ -423,6 +423,14 @@ export class QLearningRouter {
this.cacheOrder = [];
}
/** Invalidate a single state's cached route (called after its Q-values change
* so the next route() reflects the update immediately). */
private invalidateCacheEntry(stateKey: string): void {
if (this.routeCache.delete(stateKey)) {
this.cacheOrder = this.cacheOrder.filter(k => k !== stateKey);
}
}
/**
* Update Q-values based on feedback
* Includes experience replay for stable learning
@@ -453,6 +461,12 @@ export class QLearningRouter {
// Perform direct update
const tdError = this.updateQValue(stateKey, actionIdx, reward, nextStateKey);
// #cache-staleness: invalidate THIS state's cached route immediately. The
// periodic full invalidation (every 50 updates) otherwise left a freshly
// learned Q-update hidden behind a stale cached decision, so feedback
// appeared to have no effect on routing until 50 updates accumulated.
this.invalidateCacheEntry(stateKey);
// Perform experience replay
if (this.config.enableReplay && this.replayBuffer.length >= this.config.replayBatchSize) {
this.experienceReplay();