claude-flow 3.20.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
  3. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
  4. package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
  5. package/v3/@claude-flow/cli/dist/src/commands/neural.js +309 -1
  6. package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
  7. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
  8. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
  9. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
  10. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  16. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
  17. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
  18. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
  19. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
  20. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
  21. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
  22. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
  23. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
  24. package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
  25. package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
  26. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
  27. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
  28. package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
  29. package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
  30. package/v3/@claude-flow/cli/package.json +5 -4
@@ -3983,11 +3983,319 @@ const routerCommand = {
3983
3983
  return { success: true };
3984
3984
  },
3985
3985
  };
3986
+ // ============================================================================
3987
+ // ADR-150 weight-eft slice — `neural distill export | plan | eval | train`
3988
+ //
3989
+ // Turns ruflo's captured run transcripts into AUDITED TRAINING DATA + a
3990
+ // COST-PARETO measurement + a GPU TRAINING PLAN via the optional
3991
+ // `@metaharness/weight-eft` dependency. HARD honesty rule: this ships training
3992
+ // DATA + a cost audit + a GPU plan — it does NOT train a model and does NOT
3993
+ // "reduce escalation". weight-eft's own `train` never spawns; `resolved` in the
3994
+ // captured archive is a PROXY (no SWE-bench gold oracle). Every path degrades
3995
+ // gracefully ({degraded:true}) when the optional dep is absent (ADR-150).
3996
+ // ============================================================================
3997
+ const distillExportCommand = {
3998
+ name: 'export',
3999
+ description: 'Export captured run transcripts → SFT (OpenAI chat) + DPO (TRL preference) JSONL + a guard report (contamination / reward-hack / long-context). $0, offline. Does NOT train.',
4000
+ options: [
4001
+ { name: 'archive', short: 'a', type: 'string', description: 'Run-transcript JSONL to read (default: $CLAUDE_FLOW_RUN_TRANSCRIPTS_PATH or .swarm/run-transcripts.jsonl)' },
4002
+ { name: 'out-dir', short: 'o', type: 'string', description: 'Output dir for sft.jsonl / dpo.jsonl / export-report.json', default: '.claude-flow/neural/weft-export' },
4003
+ { name: 'eval-holdout', type: 'string', description: 'Comma-separated instance_ids reserved for eval (contamination guard). Excluded + asserted-disjoint.' },
4004
+ { name: 'max-tokens', type: 'number', description: 'Per-trajectory token budget (default weight-eft 28000)' },
4005
+ { name: 'truncate', type: 'boolean', description: 'Truncate over-length trajectories instead of dropping', default: 'false' },
4006
+ { name: 'keep-reward-hacked', type: 'boolean', description: 'Disable the reward-hacking filter (debug only; NOT recommended)', default: 'false' },
4007
+ { name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
4008
+ ],
4009
+ examples: [
4010
+ { command: 'claude-flow neural distill export', description: 'Export from the default captured .swarm/run-transcripts.jsonl' },
4011
+ { command: 'claude-flow neural distill export -a runs.jsonl -o ./out --eval-holdout astropy__astropy-1', description: 'Export a specific archive, holding out one instance' },
4012
+ ],
4013
+ action: async (ctx) => {
4014
+ const fs = await import('node:fs');
4015
+ const path = await import('node:path');
4016
+ const { readRunTranscripts } = await import('../ruvector/run-transcript-recorder.js');
4017
+ const { buildArchiveFromRecords, runExport } = await import('../services/weight-eft.js');
4018
+ const fmt = ctx.flags.format || 'table';
4019
+ const archivePath = ctx.flags.archive
4020
+ ?? process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS_PATH
4021
+ ?? path.resolve(process.cwd(), '.swarm', 'run-transcripts.jsonl');
4022
+ const { records, malformed } = readRunTranscripts(archivePath);
4023
+ if (records.length === 0) {
4024
+ const msg = `No run transcripts at ${archivePath}. Enable capture with CLAUDE_FLOW_RUN_TRANSCRIPTS=1, or pass --archive <file>.`;
4025
+ if (fmt === 'json')
4026
+ output.writeln(JSON.stringify({ ok: false, archivePath, records: 0, malformed, error: msg }, null, 2));
4027
+ else
4028
+ output.printError(msg);
4029
+ return { success: false, exitCode: 1, data: { archivePath, records: 0 } };
4030
+ }
4031
+ const { trajectories, stats, proxyNote } = buildArchiveFromRecords(records);
4032
+ const holdout = (ctx.flags['eval-holdout'] ?? ctx.flags.evalHoldout)?.split(',').map((s) => s.trim()).filter(Boolean) ?? [];
4033
+ const maxTokens = (ctx.flags['max-tokens'] ?? ctx.flags.maxTokens) != null ? parseInt(String((ctx.flags['max-tokens'] ?? ctx.flags.maxTokens)), 10) : undefined;
4034
+ const res = await runExport({
4035
+ archive: trajectories,
4036
+ evalHoldout: holdout,
4037
+ maxTokens,
4038
+ truncateOverLength: ctx.flags.truncate === true,
4039
+ dropRewardHacked: (ctx.flags['keep-reward-hacked'] ?? ctx.flags.keepRewardHacked) === true ? false : undefined,
4040
+ });
4041
+ if (res.degraded) {
4042
+ // ADR-150 graceful degradation: dep absent → not a runtime failure.
4043
+ const payload = { degraded: true, reason: res.reason, archiveTrajectories: trajectories.length };
4044
+ if (fmt === 'json')
4045
+ output.writeln(JSON.stringify(payload, null, 2));
4046
+ else {
4047
+ output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
4048
+ output.writeln(output.dim('Install the optional dep: npm i @metaharness/weight-eft. Archive was built (' + trajectories.length + ' trajectories) but not exported.'));
4049
+ }
4050
+ return { success: true, exitCode: 0, data: payload };
4051
+ }
4052
+ const outDir = path.resolve(process.cwd(), (ctx.flags['out-dir'] ?? ctx.flags.outDir) || '.claude-flow/neural/weft-export');
4053
+ fs.mkdirSync(outDir, { recursive: true });
4054
+ const sftPath = path.join(outDir, 'sft.jsonl');
4055
+ const dpoPath = path.join(outDir, 'dpo.jsonl');
4056
+ const reportPath = path.join(outDir, 'export-report.json');
4057
+ fs.writeFileSync(sftPath, res.sftJsonl);
4058
+ fs.writeFileSync(dpoPath, res.dpoJsonl);
4059
+ fs.writeFileSync(reportPath, JSON.stringify({ report: res.report, archiveStats: stats, proxyNote }, null, 2));
4060
+ const payload = {
4061
+ ok: true, archivePath, outDir, sftPath, dpoPath, reportPath,
4062
+ sftRows: res.sftRows, dpoRows: res.dpoRows, malformed,
4063
+ archiveStats: stats, report: res.report, proxyNote,
4064
+ };
4065
+ if (fmt === 'json') {
4066
+ output.writeln(JSON.stringify(payload, null, 2));
4067
+ return { success: true, data: payload };
4068
+ }
4069
+ output.writeln();
4070
+ output.writeln(output.bold('weight-eft export — audited training data ($0, no model trained)'));
4071
+ output.writeln(` archive: ${archivePath} (${records.length} records, ${malformed} malformed skipped)`);
4072
+ output.writeln(` trajectories: ${stats.total} (cheap ${stats.byTier.cheap} / frontier ${stats.byTier.frontier}), resolved ${stats.resolved}`);
4073
+ output.writeln(` SFT rows: ${res.sftRows} → ${sftPath}`);
4074
+ output.writeln(` DPO rows: ${res.dpoRows} → ${dpoPath}`);
4075
+ output.writeln(` guards: holdout=${res.report.excludedByHoldout} reward-hacked=${res.report.droppedRewardHacked} over-length=${res.report.droppedOverLength} truncated=${res.report.truncatedOverLength}`);
4076
+ output.writeln(` report: ${reportPath}`);
4077
+ output.writeln();
4078
+ output.writeln(output.warning('resolved provenance: ' + JSON.stringify(stats.byResolvedSource)));
4079
+ output.writeln(output.dim(proxyNote));
4080
+ return { success: true, data: payload };
4081
+ },
4082
+ };
4083
+ const distillPlanCommand = {
4084
+ name: 'plan',
4085
+ description: 'Print the two-stage (SFT → on-policy DPO) GPU training plan + the exact `ruvllm microlora` commands a GPU host would run. $0 dry-run — NEVER spawns a tune.',
4086
+ options: [
4087
+ { name: 'sft', type: 'string', description: 'Path to sft.jsonl (default: .claude-flow/neural/weft-export/sft.jsonl)' },
4088
+ { name: 'dpo', type: 'string', description: 'Path to dpo.jsonl (default: .claude-flow/neural/weft-export/dpo.jsonl)' },
4089
+ { name: 'base', short: 'b', type: 'string', description: 'Base model id to tune (7-14B band). Default Qwen2.5-Coder-7B-Instruct' },
4090
+ { name: 'params-b', type: 'number', description: 'Base model param count in billions (gate [1,14]). Default 7' },
4091
+ { name: 'adapter-prefix', type: 'string', description: 'Adapter output prefix', default: 'ruflo-weft' },
4092
+ { name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
4093
+ ],
4094
+ examples: [
4095
+ { command: 'claude-flow neural distill plan', description: 'Print the GPU plan for the last export ($0 dry-run)' },
4096
+ { command: 'claude-flow neural distill plan --base Qwen/Qwen2.5-Coder-7B-Instruct --params-b 7', description: 'Plan for a specific base model' },
4097
+ ],
4098
+ action: async (ctx) => {
4099
+ const path = await import('node:path');
4100
+ const { runPlan, DEFAULT_BASE_MODEL } = await import('../services/weight-eft.js');
4101
+ const fmt = ctx.flags.format || 'table';
4102
+ const sftPath = ctx.flags.sft || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/sft.jsonl');
4103
+ const dpoPath = ctx.flags.dpo || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/dpo.jsonl');
4104
+ const base = ctx.flags.base
4105
+ ? { id: String(ctx.flags.base), paramsB: (ctx.flags['params-b'] ?? ctx.flags.paramsB) != null ? parseInt(String((ctx.flags['params-b'] ?? ctx.flags.paramsB)), 10) : 7 }
4106
+ : DEFAULT_BASE_MODEL;
4107
+ const res = await runPlan({ base, sftPath, dpoPath, adapterPrefix: String((ctx.flags['adapter-prefix'] ?? ctx.flags.adapterPrefix) || 'ruflo-weft') });
4108
+ if (res.degraded) {
4109
+ const payload = { degraded: true, reason: res.reason };
4110
+ if (fmt === 'json')
4111
+ output.writeln(JSON.stringify(payload, null, 2));
4112
+ else {
4113
+ output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
4114
+ output.writeln(output.dim('Install: npm i @metaharness/weight-eft'));
4115
+ }
4116
+ return { success: true, exitCode: 0, data: payload };
4117
+ }
4118
+ const payload = { ok: true, base: res.base, sft: res.sft, dpo: res.dpo, dryRun: true };
4119
+ if (fmt === 'json') {
4120
+ output.writeln(JSON.stringify(payload, null, 2));
4121
+ return { success: true, data: payload };
4122
+ }
4123
+ output.writeln();
4124
+ output.writeln(output.bold(`weight-eft GPU training plan ($0 dry-run — no tune runs from ruflo)`));
4125
+ output.writeln(` base model: ${res.base.id} (${res.base.paramsB}B)`);
4126
+ output.writeln(output.dim(' SFT stage:'));
4127
+ output.writeln(` ${res.sft.summary}`);
4128
+ output.writeln(` $ ${res.sft.command}`);
4129
+ output.writeln(output.dim(' DPO stage (on-policy, init from SFT adapter):'));
4130
+ output.writeln(` ${res.dpo.summary}`);
4131
+ output.writeln(` $ ${res.dpo.command}`);
4132
+ output.writeln();
4133
+ output.writeln(output.dim('These commands run on a GPU host; ruflo does not execute them. See `neural distill train --remote` for a spend-gated remote path.'));
4134
+ return { success: true, data: payload };
4135
+ },
4136
+ };
4137
+ const distillEvalCommand = {
4138
+ name: 'eval',
4139
+ description: 'Fold two CascadeOutcome[] JSON files (base vs adapter) into the cost-Pareto delta — escalation-rate reduction + $/resolved. $0. Measures cost, does NOT claim a tune ran.',
4140
+ options: [
4141
+ { name: 'base-outcomes', type: 'string', description: 'JSON file: CascadeOutcome[] for the BASE cascade run', required: true },
4142
+ { name: 'adapter-outcomes', type: 'string', description: 'JSON file: CascadeOutcome[] for the ADAPTER cascade run', required: true },
4143
+ { name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
4144
+ ],
4145
+ examples: [
4146
+ { command: 'claude-flow neural distill eval --base-outcomes base.json --adapter-outcomes adapter.json', description: 'Cost-Pareto delta between base and adapter cascade runs' },
4147
+ ],
4148
+ action: async (ctx) => {
4149
+ const fs = await import('node:fs');
4150
+ const { runEval } = await import('../services/weight-eft.js');
4151
+ const fmt = ctx.flags.format || 'table';
4152
+ const basePath = (ctx.flags['base-outcomes'] ?? ctx.flags.baseOutcomes);
4153
+ const adapterPath = (ctx.flags['adapter-outcomes'] ?? ctx.flags.adapterOutcomes);
4154
+ if (!basePath || !adapterPath) {
4155
+ output.printError('Both --base-outcomes and --adapter-outcomes are required.');
4156
+ return { success: false, exitCode: 2 };
4157
+ }
4158
+ let baseOutcomes;
4159
+ let adapterOutcomes;
4160
+ try {
4161
+ baseOutcomes = JSON.parse(fs.readFileSync(basePath, 'utf8'));
4162
+ adapterOutcomes = JSON.parse(fs.readFileSync(adapterPath, 'utf8'));
4163
+ }
4164
+ catch (e) {
4165
+ output.printError(`Failed to read outcome files: ${e.message}`);
4166
+ return { success: false, exitCode: 1 };
4167
+ }
4168
+ if (!Array.isArray(baseOutcomes) || !Array.isArray(adapterOutcomes)) {
4169
+ output.printError('Both files must contain a JSON array of CascadeOutcome objects.');
4170
+ return { success: false, exitCode: 1 };
4171
+ }
4172
+ const res = await runEval({ baseOutcomes: baseOutcomes, adapterOutcomes: adapterOutcomes });
4173
+ if (res.degraded) {
4174
+ const payload = { degraded: true, reason: res.reason };
4175
+ if (fmt === 'json')
4176
+ output.writeln(JSON.stringify(payload, null, 2));
4177
+ else {
4178
+ output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
4179
+ output.writeln(output.dim('Install: npm i @metaharness/weight-eft'));
4180
+ }
4181
+ return { success: true, exitCode: 0, data: payload };
4182
+ }
4183
+ if (fmt === 'json') {
4184
+ output.writeln(JSON.stringify({ ok: true, delta: res.delta }, null, 2));
4185
+ return { success: true, data: res.delta };
4186
+ }
4187
+ output.writeln();
4188
+ output.writeln(output.bold('weight-eft cost-Pareto delta (measurement only)'));
4189
+ output.writeln(` cheap-resolve lift: ${res.delta.cheapResolveLift.toFixed(4)}`);
4190
+ output.writeln(` escalation-rate reduction: ${res.delta.escalationRateReduction.toFixed(4)}`);
4191
+ output.writeln(` $/resolved reduction: ${res.delta.costPerResolvedReduction.toFixed(6)}`);
4192
+ output.writeln(` resolve-rate delta: ${res.delta.resolveRateDelta.toFixed(4)} (expected ≈ 0 — ceiling unmoved)`);
4193
+ output.writeln(` verdict: ${res.delta.verdict}`);
4194
+ return { success: true, data: res.delta };
4195
+ },
4196
+ };
4197
+ const distillTrainCommand = {
4198
+ name: 'train',
4199
+ description: 'Remote-GPU LoRA tune over SSH — DRY-RUN by default (prints ssh/rsync/ruvllm commands + read-only preflight). Real compute ONLY with --execute --yes (spends GPU time on YOUR host). Not a $0/local tune.',
4200
+ options: [
4201
+ { name: 'remote', short: 'r', type: 'string', description: 'SSH host or tailscale name (default: $RUFLO_DISTILL_REMOTE). Never hard-coded.' },
4202
+ { name: 'base', short: 'b', type: 'string', description: 'Base model id to tune. Default Qwen2.5-Coder-7B-Instruct' },
4203
+ { name: 'sft', type: 'string', description: 'Local sft.jsonl (default: .claude-flow/neural/weft-export/sft.jsonl)' },
4204
+ { name: 'dpo', type: 'string', description: 'Local dpo.jsonl (default: .claude-flow/neural/weft-export/dpo.jsonl)' },
4205
+ { name: 'adapter-dir', type: 'string', description: 'Local dir to fetch the trained adapter into', default: '.claude-flow/neural' },
4206
+ { name: 'ssh-user', type: 'string', description: 'SSH user (default: current user)' },
4207
+ { name: 'ssh-port', type: 'number', description: 'SSH port', default: '22' },
4208
+ { name: 'remote-workdir', type: 'string', description: 'Remote working dir (default: ~/.ruflo-weft/<runId>)' },
4209
+ { name: 'execute', type: 'boolean', description: 'Opt in to REAL GPU compute on the remote host (still needs --yes)', default: 'false' },
4210
+ { name: 'yes', type: 'boolean', description: 'Second confirmation gate; required with --execute to actually spend', default: 'false' },
4211
+ { name: 'preflight', type: 'boolean', description: 'Opt in to read-only reachability/GPU probes against the host (bare dry-run is fully offline and contacts nothing)', default: 'false' },
4212
+ { name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
4213
+ ],
4214
+ examples: [
4215
+ { command: 'claude-flow neural distill train --remote gpu-box', description: 'OFFLINE DRY-RUN: print the ssh/rsync/ruvllm commands only (no host contact)' },
4216
+ { command: 'claude-flow neural distill train --remote gpu-box --preflight', description: 'DRY-RUN + read-only reachability/GPU probes against the host' },
4217
+ { command: 'RUFLO_DISTILL_REMOTE=gpu-box claude-flow neural distill train --execute --yes', description: 'Run the real remote tune (spends GPU time on your host)' },
4218
+ ],
4219
+ action: async (ctx) => {
4220
+ const path = await import('node:path');
4221
+ const { runRemoteTrain } = await import('../services/weight-eft.js');
4222
+ const fmt = ctx.flags.format || 'table';
4223
+ const host = ctx.flags.remote || process.env.RUFLO_DISTILL_REMOTE;
4224
+ if (!host) {
4225
+ output.printError('No remote host. Pass --remote <host> or set RUFLO_DISTILL_REMOTE.');
4226
+ return { success: false, exitCode: 2 };
4227
+ }
4228
+ const res = await runRemoteTrain({
4229
+ host,
4230
+ base: ctx.flags.base ? String(ctx.flags.base) : undefined,
4231
+ sftPath: ctx.flags.sft || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/sft.jsonl'),
4232
+ dpoPath: ctx.flags.dpo || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/dpo.jsonl'),
4233
+ adapterDir: (ctx.flags['adapter-dir'] ?? ctx.flags.adapterDir) || '.claude-flow/neural',
4234
+ sshUser: (ctx.flags['ssh-user'] ?? ctx.flags.sshUser) ? String((ctx.flags['ssh-user'] ?? ctx.flags.sshUser)) : undefined,
4235
+ sshPort: (ctx.flags['ssh-port'] ?? ctx.flags.sshPort) != null ? parseInt(String((ctx.flags['ssh-port'] ?? ctx.flags.sshPort)), 10) : undefined,
4236
+ remoteWorkdir: (ctx.flags['remote-workdir'] ?? ctx.flags.remoteWorkdir) ? String((ctx.flags['remote-workdir'] ?? ctx.flags.remoteWorkdir)) : undefined,
4237
+ execute: ctx.flags.execute === true,
4238
+ yes: ctx.flags.yes === true,
4239
+ preflight: ctx.flags.preflight === true,
4240
+ });
4241
+ if ('degraded' in res && res.degraded) {
4242
+ const payload = { degraded: true, reason: res.reason };
4243
+ if (fmt === 'json')
4244
+ output.writeln(JSON.stringify(payload, null, 2));
4245
+ else
4246
+ output.writeln(output.warning(`remote-train unavailable (${res.reason}).`));
4247
+ return { success: true, exitCode: 0, data: payload };
4248
+ }
4249
+ if (fmt === 'json') {
4250
+ output.writeln(JSON.stringify(res, null, 2));
4251
+ return { success: res.mode !== 'preflight-failed', data: res };
4252
+ }
4253
+ output.writeln();
4254
+ output.writeln(output.bold(`weight-eft remote-GPU tune [${res.mode}] on ${res.plan.host}`));
4255
+ if (res.mode === 'dry-run')
4256
+ output.writeln(output.dim('DRY-RUN — no data transferred, no training. Re-run with --execute --yes to spend GPU time on your host.'));
4257
+ if (res.reason)
4258
+ output.writeln(output.warning(res.reason));
4259
+ output.writeln(` base: ${res.plan.base} remote workdir: ${res.plan.remoteWorkdir} adapter → ${res.plan.adapterDir}/${res.plan.dpoAdapter}`);
4260
+ if (res.preflight) {
4261
+ output.writeln(output.dim(' preflight (read-only probes):'));
4262
+ for (const p of res.preflight)
4263
+ output.writeln(` [${p.ok ? 'ok ' : 'FAIL'}] ${p.label}: ${p.detail}`);
4264
+ }
4265
+ output.writeln(output.dim(' commands that ' + (res.mode === 'executed' ? 'ran' : 'WOULD run') + ':'));
4266
+ for (const c of res.plan.humanCommands)
4267
+ output.writeln(` $ ${c}`);
4268
+ if (res.steps) {
4269
+ output.writeln(output.dim(' execution:'));
4270
+ for (const s of res.steps)
4271
+ output.writeln(` [${s.ok ? 'ok ' : 'FAIL'}] ${s.label}: ${s.detail}`);
4272
+ }
4273
+ output.writeln();
4274
+ output.writeln(output.dim('Honesty: ruflo does not train locally or at $0. This is an explicit, user-triggered remote-GPU spend. resolved-gold in the SFT data is still a proxy.'));
4275
+ return { success: res.mode !== 'preflight-failed', data: res };
4276
+ },
4277
+ };
4278
+ const distillCommand = {
4279
+ name: 'distill',
4280
+ description: 'weight-eft training-data + cost-audit slice (ADR-150): export | plan | eval | train. Ships audited SFT/DPO data + a cost-Pareto measurement + a GPU plan. Does NOT train a model or reduce escalation.',
4281
+ subcommands: [distillExportCommand, distillPlanCommand, distillEvalCommand, distillTrainCommand],
4282
+ examples: [
4283
+ { command: 'claude-flow neural distill export', description: 'Captured transcripts → audited SFT/DPO JSONL + guard report ($0)' },
4284
+ { command: 'claude-flow neural distill plan', description: 'Print the GPU training plan + ruvllm commands ($0 dry-run)' },
4285
+ { command: 'claude-flow neural distill eval --base-outcomes b.json --adapter-outcomes a.json', description: 'Cost-Pareto delta ($0)' },
4286
+ { command: 'claude-flow neural distill train --remote gpu-box', description: 'Remote-GPU tune DRY-RUN (spend-gated behind --execute --yes)' },
4287
+ ],
4288
+ action: async () => {
4289
+ output.writeln('Use a subcommand: export | plan | eval | train');
4290
+ output.writeln(output.dim('Ships audited training DATA + a cost audit + a GPU plan. It does NOT train a model or reduce escalation (weight-eft train never spawns; resolved is a proxy).'));
4291
+ return { success: true };
4292
+ },
4293
+ };
3986
4294
  // Main neural command
3987
4295
  export const neuralCommand = {
3988
4296
  name: 'neural',
3989
4297
  description: 'Neural pattern training, MoE, Flash Attention, pattern learning',
3990
- subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand],
4298
+ subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand, distillCommand],
3991
4299
  examples: [
3992
4300
  { command: 'claude-flow neural status', description: 'Check neural system status' },
3993
4301
  { command: 'claude-flow neural train -p coordination', description: 'Train coordination patterns' },
@@ -50,6 +50,9 @@ import { metaharnessTools } from './mcp-tools/metaharness-tools.js';
50
50
  // agenticow@~0.2.3 — Copy-On-Write memory branching tools (162-byte branches);
51
51
  // optional runtime dep, every handler returns `{degraded: true}` when missing.
52
52
  import { agenticowTools } from './mcp-tools/agenticow-tools.js';
53
+ // agenticow step 4 — speculative branch-and-promote (A/B memory exploration).
54
+ // Optional runtime dep, degrades to `{degraded: true}` when agenticow is missing.
55
+ import { agenticowSpeculateTools } from './mcp-tools/agenticow-speculate-tools.js';
53
56
  // ADR-164 — AgentBBS federated business-domain BBS rooms (Phase 1).
54
57
  // Optional runtime dep, every handler returns `{degraded: true}` when missing.
55
58
  import { agentbbsTools } from './mcp-tools/agentbbs-tools.js';
@@ -139,8 +142,10 @@ registerTools([
139
142
  ...coverageRouterTools,
140
143
  // ADR-150 — MetaHarness static-analysis tools (5)
141
144
  ...metaharnessTools,
142
- // agenticow@~0.2.3 — COW memory branching (4 tools, graceful-degraded when missing)
145
+ // agenticow@~0.2.4 — COW memory branching (9 tools: branch/checkpoint/rollback/promote + ingest/query/diff/lineage/status, graceful-degraded when missing)
143
146
  ...agenticowTools,
147
+ // agenticow step 4 — speculative branch-and-promote (1 tool, graceful-degraded when missing)
148
+ ...agenticowSpeculateTools,
144
149
  // ADR-164 — AgentBBS federated business-domain BBS rooms (4 tools, Phase 1, graceful-degraded)
145
150
  ...agentbbsTools,
146
151
  // ADR-164 Phase 2 + Phase 3 — business_pod_validate + business_pod_route_backend
@@ -512,6 +512,39 @@ export async function executeAgentTask(input) {
512
512
  }
513
513
  catch { /* never break execution */ }
514
514
  }
515
+ // ADR-150 weight-eft capture seam — the ONLY place a run's full outcome is
516
+ // known here: prompt (issue), assistant transcript, model, tier, and a
517
+ // resolved PROXY. Gated behind CLAUDE_FLOW_RUN_TRANSCRIPTS=1 (off by
518
+ // default; PII/retention surface, mirrors the router trajectory recorder).
519
+ // HONESTY: `resolved` here is the WEAKEST proxy — 'api-success' means only
520
+ // that the model returned without an API error, NOT that the output is
521
+ // correct (ruflo has no SWE-bench gold oracle). model_patch is '' because
522
+ // this single-shot execute path produces no unified diff. Both facts are
523
+ // stamped so no downstream weight-eft export mistakes this for gold data.
524
+ if (process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS === '1') {
525
+ try {
526
+ const { recordRunTranscript, tierForModel } = await import('../ruvector/run-transcript-recorder.js');
527
+ const modelId = agent.modelId ?? String(agent.model ?? 'unknown');
528
+ const messages = [];
529
+ if (systemPrompt)
530
+ messages.push({ role: 'system', content: systemPrompt });
531
+ messages.push({ role: 'user', content: input.prompt });
532
+ if (result.success && typeof result.output === 'string') {
533
+ messages.push({ role: 'assistant', content: result.output });
534
+ }
535
+ recordRunTranscript({
536
+ task: input.prompt,
537
+ model: modelId,
538
+ tier: tierForModel(modelId),
539
+ resolved: outcome === 'success',
540
+ resolvedSource: 'api-success',
541
+ messages,
542
+ source: 'agent-execute',
543
+ tokens: result.usage ? { input: result.usage.inputTokens, output: result.usage.outputTokens } : undefined,
544
+ });
545
+ }
546
+ catch { /* never break execution */ }
547
+ }
515
548
  }
516
549
  catch {
517
550
  // Silent — bandit feedback must never block routing.
@@ -224,6 +224,8 @@ export const agentTools = [
224
224
  description: 'Claude model alias (haiku=fast/cheap, sonnet=balanced, opus=current Opus 4.8, opus-4.7=prior Opus pin)'
225
225
  },
226
226
  task: { type: 'string', description: 'Task description for intelligent model routing' },
227
+ memoryBase: { type: 'string', description: 'Opt-in: base .rvf memory file to fork a per-agent Copy-On-Write branch from (agenticow). When set, the agent gets an isolated ~162-byte COW branch instead of a full copy — promote on success, discard on terminate. Requires the optional `agenticow` dep; degrades to a no-op when absent or when CLAUDE_FLOW_NO_COW_MEMORY=1.' },
228
+ memoryDimension: { type: 'integer', description: 'Vector dimension for the COW base (required only when memoryBase does not exist yet)' },
227
229
  },
228
230
  required: ['agentType'],
229
231
  },
@@ -295,6 +297,31 @@ export const agentTools = [
295
297
  await addNode({ id: agentId, type: 'agent', name: agentType });
296
298
  }
297
299
  catch { /* graph-node not available */ }
300
+ // ACOW — opt-in per-agent COW memory branch. Only when the caller
301
+ // supplies `memoryBase` does the agent get an isolated 162-byte branch
302
+ // (vs a full .rvf copy). Lazy-imported so agenticow stays off the
303
+ // startup path. Non-fatal: a branch failure never blocks the spawn —
304
+ // the agent is already registered above.
305
+ let memoryBranch;
306
+ if (input.memoryBase) {
307
+ try {
308
+ const { SwarmMemoryBranches } = await import('../services/swarm-memory-branches.js');
309
+ const svc = new SwarmMemoryBranches();
310
+ const br = await svc.branchForAgent(String(input.memoryBase), agentId, {
311
+ dimension: typeof input.memoryDimension === 'number' ? input.memoryDimension : undefined,
312
+ });
313
+ if (br.branchPath) {
314
+ memoryBranch = br.branchPath;
315
+ agent.memoryBranch = br.branchPath;
316
+ agent.memoryBase = br.basePath;
317
+ store.agents[agentId] = agent;
318
+ saveAgentStore(store);
319
+ }
320
+ // else: degraded (agenticow missing / kill-switched) — agent stands
321
+ // without an isolated branch; callers see no memoryBranch field.
322
+ }
323
+ catch { /* COW branch is best-effort; agent already registered */ }
324
+ }
298
325
  // Include deterministic codemod routing info if applicable
299
326
  const response = {
300
327
  success: true,
@@ -307,6 +334,7 @@ export const agentTools = [
307
334
  ...(routingResult.openrouterModel ? { openrouterModel: routingResult.openrouterModel } : {}),
308
335
  status: 'registered',
309
336
  createdAt: agent.createdAt,
337
+ ...(memoryBranch ? { memoryBranch, memoryBase: agent.memoryBase } : {}),
310
338
  note: 'Agent registered for coordination. Three execution paths: ' +
311
339
  '(1) call agent_execute(agentId, prompt) — direct LLM call via Anthropic Messages API (requires ANTHROPIC_API_KEY); ' +
312
340
  '(2) Claude Code Task tool — spawns a real subagent; ' +
@@ -375,6 +403,7 @@ export const agentTools = [
375
403
  properties: {
376
404
  agentId: { type: 'string', description: 'ID of agent to terminate' },
377
405
  force: { type: 'boolean', description: 'Force immediate termination' },
406
+ promoteMemory: { type: 'boolean', description: 'When the agent has a per-agent COW memory branch (spawned with memoryBase), promote (merge) its edits into the shared base on terminate. Default false → discard the branch (throw its edits away). No-op when the agent has no branch.' },
378
407
  },
379
408
  required: ['agentId'],
380
409
  },
@@ -385,13 +414,30 @@ export const agentTools = [
385
414
  const store = loadAgentStore();
386
415
  const agentId = input.agentId;
387
416
  if (store.agents[agentId]) {
388
- store.agents[agentId].status = 'terminated';
417
+ const rec = store.agents[agentId];
418
+ rec.status = 'terminated';
389
419
  saveAgentStore(store);
420
+ // ACOW — resolve the agent's COW memory branch on teardown: promote
421
+ // (merge into base) when asked, else discard. Non-fatal — a failure
422
+ // here never blocks termination. Discard needs no agenticow (pure fs),
423
+ // so cleanup still works in the degraded path.
424
+ let memory;
425
+ if (rec.memoryBranch) {
426
+ try {
427
+ const { SwarmMemoryBranches } = await import('../services/swarm-memory-branches.js');
428
+ const svc = new SwarmMemoryBranches();
429
+ memory = input.promoteMemory
430
+ ? (await svc.promoteAgent(agentId))
431
+ : (await svc.discardAgent(agentId));
432
+ }
433
+ catch { /* best-effort teardown */ }
434
+ }
390
435
  return {
391
436
  success: true,
392
437
  agentId,
393
438
  terminated: true,
394
439
  terminatedAt: new Date().toISOString(),
440
+ ...(memory ? { memory } : {}),
395
441
  };
396
442
  }
397
443
  return {
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Agenticow shared loader + helpers.
3
+ *
4
+ * Extracted from `agenticow-tools.ts` so both the MCP tool surface AND the
5
+ * `SwarmMemoryBranches` service (src/services/swarm-memory-branches.ts) share
6
+ * one loader, one degradation contract, and one set of path/lineage helpers —
7
+ * rather than duplicating the optional-dep dance in two places.
8
+ *
9
+ * Architectural constraint (ADR-150, mirrors metaharness-tools.ts):
10
+ * - `agenticow` lives in `optionalDependencies` — never a hard runtime dep.
11
+ * - When the package is missing, callers get `null` from `loadAgenticow()`
12
+ * (or a `{degraded:true}` result via `degradedResult`) and MUST fall back
13
+ * gracefully — never throw a MODULE_NOT_FOUND.
14
+ *
15
+ * @module @claude-flow/cli/mcp-tools/agenticow-loader
16
+ */
17
+ export declare const PACKAGE_NAME = "agenticow";
18
+ export interface AgenticowApi {
19
+ open: (file: string, opts?: {
20
+ dimension?: number;
21
+ metric?: string;
22
+ }) => Promise<any>;
23
+ openBase?: (file: string, opts?: any) => Promise<any>;
24
+ AgenticMemory: any;
25
+ }
26
+ /**
27
+ * Lazily import `agenticow`. Returns the module on success, or `null` when the
28
+ * package is not installed (the optional-dep degraded path). Any OTHER load
29
+ * error (e.g. a corrupt install) is re-thrown so it is not silently masked.
30
+ */
31
+ export declare function loadAgenticow(): Promise<AgenticowApi | null>;
32
+ /** Reset the module-level load cache. Test-only seam. */
33
+ export declare function __resetAgenticowCache(): void;
34
+ export declare function degradedResult(reason: string): {
35
+ success: true;
36
+ degraded: true;
37
+ reason: string;
38
+ };
39
+ /**
40
+ * Resolve a user-supplied memory path against the project cwd, rejecting path
41
+ * traversal and NUL bytes (D-2 style hardening — same rule the MCP verbs use).
42
+ */
43
+ export declare function resolveMemoryPath(path: string): string;
44
+ /**
45
+ * Lineage manifest companion path. agenticow persists the COW chain
46
+ * (working → checkpoints → base) into `<file>.agenticow.json` next to the
47
+ * `.rvf` data file. Without it, forks/checkpoints are in-memory only and
48
+ * disappear when the AgenticMemory handle closes.
49
+ */
50
+ export declare function manifestFor(file: string): string;
51
+ /** Validate a COW branch/checkpoint label (alnum + a small safe symbol set). */
52
+ export declare function validateLabel(label: string): string;
53
+ /**
54
+ * Open (or create) a memory file, restoring its COW chain from the lineage
55
+ * manifest when one exists. When neither the `.rvf` nor the manifest exists,
56
+ * `dimension` is required to create a fresh base.
57
+ */
58
+ export declare function openWithLineage(api: AgenticowApi, file: string, dimension?: number): Promise<any>;
59
+ //# sourceMappingURL=agenticow-loader.d.ts.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Agenticow shared loader + helpers.
3
+ *
4
+ * Extracted from `agenticow-tools.ts` so both the MCP tool surface AND the
5
+ * `SwarmMemoryBranches` service (src/services/swarm-memory-branches.ts) share
6
+ * one loader, one degradation contract, and one set of path/lineage helpers —
7
+ * rather than duplicating the optional-dep dance in two places.
8
+ *
9
+ * Architectural constraint (ADR-150, mirrors metaharness-tools.ts):
10
+ * - `agenticow` lives in `optionalDependencies` — never a hard runtime dep.
11
+ * - When the package is missing, callers get `null` from `loadAgenticow()`
12
+ * (or a `{degraded:true}` result via `degradedResult`) and MUST fall back
13
+ * gracefully — never throw a MODULE_NOT_FOUND.
14
+ *
15
+ * @module @claude-flow/cli/mcp-tools/agenticow-loader
16
+ */
17
+ import { existsSync } from 'node:fs';
18
+ import { resolve, isAbsolute } from 'node:path';
19
+ import { getProjectCwd } from './types.js';
20
+ export const PACKAGE_NAME = 'agenticow';
21
+ // Cache: module load is expensive enough to amortize across handler calls.
22
+ // null = not yet attempted; false = attempted and unavailable; module = loaded.
23
+ let _agenticowMod = null;
24
+ let _loadAttempted = false;
25
+ /**
26
+ * Lazily import `agenticow`. Returns the module on success, or `null` when the
27
+ * package is not installed (the optional-dep degraded path). Any OTHER load
28
+ * error (e.g. a corrupt install) is re-thrown so it is not silently masked.
29
+ */
30
+ export async function loadAgenticow() {
31
+ if (_loadAttempted)
32
+ return _agenticowMod || null;
33
+ _loadAttempted = true;
34
+ try {
35
+ _agenticowMod = await import(PACKAGE_NAME);
36
+ return _agenticowMod;
37
+ }
38
+ catch (err) {
39
+ if (err && (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND' ||
40
+ /Cannot find (module|package)/i.test(String(err.message)))) {
41
+ _agenticowMod = false;
42
+ return null;
43
+ }
44
+ throw err;
45
+ }
46
+ }
47
+ /** Reset the module-level load cache. Test-only seam. */
48
+ export function __resetAgenticowCache() {
49
+ _agenticowMod = null;
50
+ _loadAttempted = false;
51
+ }
52
+ export function degradedResult(reason) {
53
+ return { success: true, degraded: true, reason };
54
+ }
55
+ /**
56
+ * Resolve a user-supplied memory path against the project cwd, rejecting path
57
+ * traversal and NUL bytes (D-2 style hardening — same rule the MCP verbs use).
58
+ */
59
+ export function resolveMemoryPath(path) {
60
+ if (!path || typeof path !== 'string')
61
+ throw new Error('memory path is required');
62
+ if (/\.\.[\\/]|\0/.test(path))
63
+ throw new Error('memory path contains disallowed characters');
64
+ return isAbsolute(path) ? path : resolve(getProjectCwd(), path);
65
+ }
66
+ /**
67
+ * Lineage manifest companion path. agenticow persists the COW chain
68
+ * (working → checkpoints → base) into `<file>.agenticow.json` next to the
69
+ * `.rvf` data file. Without it, forks/checkpoints are in-memory only and
70
+ * disappear when the AgenticMemory handle closes.
71
+ */
72
+ export function manifestFor(file) {
73
+ return `${file}.agenticow.json`;
74
+ }
75
+ /** Validate a COW branch/checkpoint label (alnum + a small safe symbol set). */
76
+ export function validateLabel(label) {
77
+ if (!label || typeof label !== 'string')
78
+ throw new Error('label is required');
79
+ if (label.length > 256)
80
+ throw new Error('label exceeds 256 chars');
81
+ if (!/^[A-Za-z0-9_.\-:/@]+$/.test(label)) {
82
+ throw new Error('label may only contain [A-Za-z0-9_.\\-:/@]');
83
+ }
84
+ return label;
85
+ }
86
+ /**
87
+ * Open (or create) a memory file, restoring its COW chain from the lineage
88
+ * manifest when one exists. When neither the `.rvf` nor the manifest exists,
89
+ * `dimension` is required to create a fresh base.
90
+ */
91
+ export async function openWithLineage(api, file, dimension) {
92
+ const manifest = manifestFor(file);
93
+ if (existsSync(manifest)) {
94
+ return api.AgenticMemory.load(manifest);
95
+ }
96
+ const opts = {};
97
+ if (typeof dimension === 'number' && Number.isInteger(dimension) && dimension > 0) {
98
+ opts.dimension = dimension;
99
+ }
100
+ else if (!existsSync(file)) {
101
+ throw new Error('dimension is required when creating a new memory file');
102
+ }
103
+ return api.open(file, opts);
104
+ }
105
+ //# sourceMappingURL=agenticow-loader.js.map