From a73a2cbe32299d7635eee6b42ee32ce5bdb01fd7 Mon Sep 17 00:00:00 2001 From: rUv Date: Fri, 29 May 2026 13:02:15 -0400 Subject: [PATCH] fix(routing): stale route cache + --explore false (3.10.8) (#2229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 --- .claude/workflows/plugin-contract-audit.js | 91 +++++++++++++++++++ .../intelligence-system-audit-2026-05-29.md | 23 +++++ package-lock.json | 4 +- package.json | 2 +- .../.claude-plugin/plugin.json | 16 +++- plugins/ruflo-workflows/README.md | 74 +++++++++++++-- .../agents/workflow-specialist.md | 18 +++- plugins/ruflo-workflows/commands/workflow.md | 14 ++- .../0002-native-workflow-orchestration.md | 87 ++++++++++++++++++ plugins/ruflo-workflows/scripts/smoke-gaia.sh | 6 +- plugins/ruflo-workflows/scripts/smoke.sh | 37 +++++++- .../skills/workflow-create/SKILL.md | 57 +++++++++--- .../skills/workflow-run/SKILL.md | 39 +++++--- ruflo/package.json | 2 +- .../__tests__/bug-cluster-2219-2226.test.ts | 46 ++++++++++ v3/@claude-flow/cli/package.json | 2 +- v3/@claude-flow/cli/src/parser.ts | 28 +++++- .../cli/src/ruvector/q-learning-router.ts | 14 +++ 18 files changed, 504 insertions(+), 56 deletions(-) create mode 100644 .claude/workflows/plugin-contract-audit.js create mode 100644 plugins/ruflo-workflows/docs/adrs/0002-native-workflow-orchestration.md diff --git a/.claude/workflows/plugin-contract-audit.js b/.claude/workflows/plugin-contract-audit.js new file mode 100644 index 000000000..fd1ef5030 --- /dev/null +++ b/.claude/workflows/plugin-contract-audit.js @@ -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 diff --git a/docs/reviews/intelligence-system-audit-2026-05-29.md b/docs/reviews/intelligence-system-audit-2026-05-29.md index 08d7ded9a..9eab84316 100644 --- a/docs/reviews/intelligence-system-audit-2026-05-29.md +++ b/docs/reviews/intelligence-system-audit-2026-05-29.md @@ -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.2–4.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. diff --git a/package-lock.json b/package-lock.json index 77b586a16..3026bce16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index fa2f5f7af..c310d4c32 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugins/ruflo-workflows/.claude-plugin/plugin.json b/plugins/ruflo-workflows/.claude-plugin/plugin.json index d1ef13eb8..bb8645918 100644 --- a/plugins/ruflo-workflows/.claude-plugin/plugin.json +++ b/plugins/ruflo-workflows/.claude-plugin/plugin.json @@ -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"] } } } diff --git a/plugins/ruflo-workflows/README.md b/plugins/ruflo-workflows/README.md index 5278be844..9052d5208 100644 --- a/plugins/ruflo-workflows/README.md +++ b/plugins/ruflo-workflows/README.md @@ -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 diff --git a/plugins/ruflo-workflows/agents/workflow-specialist.md b/plugins/ruflo-workflows/agents/workflow-specialist.md index 9368a9d47..de908a4d0 100644 --- a/plugins/ruflo-workflows/agents/workflow-specialist.md +++ b/plugins/ruflo-workflows/agents/workflow-specialist.md @@ -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: diff --git a/plugins/ruflo-workflows/commands/workflow.md b/plugins/ruflo-workflows/commands/workflow.md index eda08288d..82ce0ae40 100644 --- a/plugins/ruflo-workflows/commands/workflow.md +++ b/plugins/ruflo-workflows/commands/workflow.md @@ -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. diff --git a/plugins/ruflo-workflows/docs/adrs/0002-native-workflow-orchestration.md b/plugins/ruflo-workflows/docs/adrs/0002-native-workflow-orchestration.md new file mode 100644 index 000000000..1b92990bf --- /dev/null +++ b/plugins/ruflo-workflows/docs/adrs/0002-native-workflow-orchestration.md @@ -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. diff --git a/plugins/ruflo-workflows/scripts/smoke-gaia.sh b/plugins/ruflo-workflows/scripts/smoke-gaia.sh index bb3350efe..58b547807 100755 --- a/plugins/ruflo-workflows/scripts/smoke-gaia.sh +++ b/plugins/ruflo-workflows/scripts/smoke-gaia.sh @@ -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 diff --git a/plugins/ruflo-workflows/scripts/smoke.sh b/plugins/ruflo-workflows/scripts/smoke.sh index 1e2f01bd0..988f892ef 100755 --- a/plugins/ruflo-workflows/scripts/smoke.sh +++ b/plugins/ruflo-workflows/scripts/smoke.sh @@ -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 diff --git a/plugins/ruflo-workflows/skills/workflow-create/SKILL.md b/plugins/ruflo-workflows/skills/workflow-create/SKILL.md index ed2c80b97..408105cd2 100644 --- a/plugins/ruflo-workflows/skills/workflow-create/SKILL.md +++ b/plugins/ruflo-workflows/skills/workflow-create/SKILL.md @@ -1,29 +1,60 @@ --- name: workflow-create -description: Create reusable workflow templates with steps, conditions, and parallel execution -argument-hint: " [--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: " [--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). diff --git a/plugins/ruflo-workflows/skills/workflow-run/SKILL.md b/plugins/ruflo-workflows/skills/workflow-run/SKILL.md index 66b7ebaf5..1da5f3470 100644 --- a/plugins/ruflo-workflows/skills/workflow-run/SKILL.md +++ b/plugins/ruflo-workflows/skills/workflow-run/SKILL.md @@ -1,29 +1,40 @@ --- name: workflow-run -description: Execute, pause, resume, and cancel running workflows -argument-hint: "" -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: "" +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). diff --git a/ruflo/package.json b/ruflo/package.json index 8b581195f..dbea299eb 100644 --- a/ruflo/package.json +++ b/ruflo/package.json @@ -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", diff --git a/v3/@claude-flow/cli/__tests__/bug-cluster-2219-2226.test.ts b/v3/@claude-flow/cli/__tests__/bug-cluster-2219-2226.test.ts index ad11b751b..4981879c7 100644 --- a/v3/@claude-flow/cli/__tests__/bug-cluster-2219-2226.test.ts +++ b/v3/@claude-flow/cli/__tests__/bug-cluster-2219-2226.test.ts @@ -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); + }); +}); diff --git a/v3/@claude-flow/cli/package.json b/v3/@claude-flow/cli/package.json index 8b41c8d7e..c408646f2 100644 --- a/v3/@claude-flow/cli/package.json +++ b/v3/@claude-flow/cli/package.json @@ -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", diff --git a/v3/@claude-flow/cli/src/parser.ts b/v3/@claude-flow/cli/src/parser.ts index f525b2be4..c23b35dd8 100644 --- a/v3/@claude-flow/cli/src/parser.ts +++ b/v3/@claude-flow/cli/src/parser.ts @@ -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; diff --git a/v3/@claude-flow/cli/src/ruvector/q-learning-router.ts b/v3/@claude-flow/cli/src/ruvector/q-learning-router.ts index 8502fa336..01883adf6 100644 --- a/v3/@claude-flow/cli/src/ruvector/q-learning-router.ts +++ b/v3/@claude-flow/cli/src/ruvector/q-learning-router.ts @@ -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();