@claude-flow/cli 3.32.34 → 3.32.35
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.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/catalog-manifest.json +4 -4
- package/dist/src/commands/agent.js +49 -1
- package/dist/src/commands/hooks.js +24 -0
- package/dist/src/commands/metaharness.js +4 -0
- package/dist/src/commands/swarm.js +61 -5
- package/dist/src/config-adapter.js +1 -0
- package/dist/src/index.js +3 -2
- package/dist/src/mcp-tools/agent-tools.js +10 -0
- package/dist/src/mcp-tools/hooks-tools.js +86 -52
- package/dist/src/mcp-tools/memory-tools.js +14 -2
- package/dist/src/mcp-tools/metaharness-tools.js +7 -0
- package/dist/src/mcp-tools/swarm-tools.d.ts +16 -0
- package/dist/src/mcp-tools/swarm-tools.js +172 -7
- package/dist/src/memory/memory-initializer.d.ts +9 -1
- package/dist/src/memory/memory-initializer.js +66 -19
- package/dist/src/services/daemon-autostart.js +7 -4
- package/dist/src/services/flywheel-receipt.d.ts +3 -0
- package/dist/src/services/flywheel-receipt.js +1 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +6 -0
- package/dist/src/services/harness-flywheel-runtime.js +19 -19
- package/dist/src/services/harness-flywheel.d.ts +2 -0
- package/dist/src/services/harness-flywheel.js +1 -0
- package/dist/src/services/harness-project-anchor.d.ts +23 -0
- package/dist/src/services/harness-project-anchor.js +129 -0
- package/dist/src/services/learned-routing.d.ts +34 -0
- package/dist/src/services/learned-routing.js +85 -0
- package/dist/src/services/pheromone-adaptive.d.ts +71 -0
- package/dist/src/services/pheromone-adaptive.js +214 -0
- package/dist/src/services/worker-daemon.js +4 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.35",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "68be7e9a9eba7bf9c4e8a230db7bf61a243b965639f8504842799d6c6ca28762",
|
|
6
6
|
"hook-handler.cjs": "50ea92a72651bdc95634f7588d56a5870963168eef5226f66ed14af3b47c8d9a",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"statusline.cjs": "d2a0eac56d1267d8dbed2d70b09feb592299469196ec4b215909f2b052144882"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "7hAABpKaZablL78ug8UW506P+ppwYlDb2qws0D4BbCeugfGQTdDEOYsndbZMc4+Wzbx10oQbnubqh8jHZnHvBQ==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generation":
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"gitSha": "
|
|
3
|
+
"generation": 4,
|
|
4
|
+
"generatedAt": "2026-07-29T17:56:33.000Z",
|
|
5
|
+
"gitSha": "3d9bca6d",
|
|
6
6
|
"catalog": {
|
|
7
7
|
"agents": 164,
|
|
8
|
-
"tools":
|
|
8
|
+
"tools": 397,
|
|
9
9
|
"skills": 34
|
|
10
10
|
},
|
|
11
11
|
"benchmark": null
|
|
@@ -487,6 +487,39 @@ const metricsCommand = {
|
|
|
487
487
|
const avgSuccessRate = tasksCompleted > 0
|
|
488
488
|
? `${Math.round(Object.values(typeCounts).reduce((a, d) => a + d.success, 0) / tasksCompleted * 100)}%`
|
|
489
489
|
: 'N/A';
|
|
490
|
+
// ADR-330: expose the learned scheduling signal where operators already
|
|
491
|
+
// inspect agent metrics. Old/non-APSC installs simply omit this section.
|
|
492
|
+
let pheromone;
|
|
493
|
+
const swarmStatePath = join(process.cwd(), '.claude-flow', 'swarm', 'swarm-state.json');
|
|
494
|
+
if (existsSync(swarmStatePath)) {
|
|
495
|
+
try {
|
|
496
|
+
const store = JSON.parse(readFileSync(swarmStatePath, 'utf-8'));
|
|
497
|
+
const current = Object.values(store.swarms ?? {})
|
|
498
|
+
.filter((swarm) => swarm.status === 'running' && swarm.topology === 'pheromone-adaptive')
|
|
499
|
+
.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))[0];
|
|
500
|
+
const apsc = current?.config?.apscState;
|
|
501
|
+
if (apsc) {
|
|
502
|
+
const agents = Object.values(apsc.agents ?? {})
|
|
503
|
+
.filter((agent) => !agentId || agent.agentId === agentId)
|
|
504
|
+
.sort((a, b) => a.agentId.localeCompare(b.agentId))
|
|
505
|
+
.map((agent) => ({
|
|
506
|
+
agentId: agent.agentId,
|
|
507
|
+
role: agent.role,
|
|
508
|
+
pheromoneScore: agent.emaScore,
|
|
509
|
+
rawScore: agent.rawScore,
|
|
510
|
+
samples: agent.samples,
|
|
511
|
+
eligibility: agent.status === 'suspended' ? 'suspended' : 'eligible',
|
|
512
|
+
}));
|
|
513
|
+
pheromone = {
|
|
514
|
+
active: true,
|
|
515
|
+
dryRun: apsc.config?.dryRun !== false,
|
|
516
|
+
threshold: apsc.threshold ?? 0.5,
|
|
517
|
+
agents,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch { /* malformed/legacy swarm state — preserve existing metrics */ }
|
|
522
|
+
}
|
|
490
523
|
const metrics = {
|
|
491
524
|
period,
|
|
492
525
|
summary: {
|
|
@@ -501,7 +534,8 @@ const metricsCommand = {
|
|
|
501
534
|
performance: {
|
|
502
535
|
memoryVectors: `${vectorCount} vectors`,
|
|
503
536
|
searchBackend: vectorCount > 0 ? 'HNSW-indexed' : 'none'
|
|
504
|
-
}
|
|
537
|
+
},
|
|
538
|
+
pheromone,
|
|
505
539
|
};
|
|
506
540
|
if (ctx.flags.format === 'json') {
|
|
507
541
|
output.printJson(metrics);
|
|
@@ -544,6 +578,20 @@ const metricsCommand = {
|
|
|
544
578
|
`Vectors: ${output.success(metrics.performance.memoryVectors)}`,
|
|
545
579
|
`Backend: ${output.success(metrics.performance.searchBackend)}`
|
|
546
580
|
]);
|
|
581
|
+
if (metrics.pheromone) {
|
|
582
|
+
output.writeln();
|
|
583
|
+
output.writeln(output.bold(`Pheromone Scheduling (${metrics.pheromone.dryRun ? 'dry run' : 'live'})`));
|
|
584
|
+
output.printTable({
|
|
585
|
+
columns: [
|
|
586
|
+
{ key: 'agentId', header: 'Agent', width: 20 },
|
|
587
|
+
{ key: 'role', header: 'Role', width: 18 },
|
|
588
|
+
{ key: 'pheromoneScore', header: 'EMA Score', width: 12, align: 'right' },
|
|
589
|
+
{ key: 'samples', header: 'Samples', width: 9, align: 'right' },
|
|
590
|
+
{ key: 'eligibility', header: 'Eligibility', width: 12 },
|
|
591
|
+
],
|
|
592
|
+
data: metrics.pheromone.agents,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
547
595
|
return { success: true, data: metrics };
|
|
548
596
|
}
|
|
549
597
|
};
|
|
@@ -1672,6 +1672,26 @@ const postTaskCommand = {
|
|
|
1672
1672
|
description: 'Agent that executed the task',
|
|
1673
1673
|
type: 'string'
|
|
1674
1674
|
},
|
|
1675
|
+
{
|
|
1676
|
+
name: 'agent-role',
|
|
1677
|
+
description: 'Stable agent role used for role-aware pheromone comparison (for example coder, tester, coordinator)',
|
|
1678
|
+
type: 'string'
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
name: 'duration',
|
|
1682
|
+
description: 'Observed task duration in milliseconds for latency fitness',
|
|
1683
|
+
type: 'number'
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
name: 'latency-budget-ms',
|
|
1687
|
+
description: 'Expected task duration in milliseconds; paired with --duration to normalize latency fitness',
|
|
1688
|
+
type: 'number'
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
name: 'consensus-alignment',
|
|
1692
|
+
description: 'Consensus alignment score from 0 to 1',
|
|
1693
|
+
type: 'number'
|
|
1694
|
+
},
|
|
1675
1695
|
{
|
|
1676
1696
|
name: 'task',
|
|
1677
1697
|
short: 't',
|
|
@@ -1716,6 +1736,10 @@ const postTaskCommand = {
|
|
|
1716
1736
|
success,
|
|
1717
1737
|
quality: ctx.flags.quality,
|
|
1718
1738
|
agent: ctx.flags.agent,
|
|
1739
|
+
agentRole: ctx.flags.agentRole,
|
|
1740
|
+
duration: ctx.flags.duration,
|
|
1741
|
+
latencyBudgetMs: ctx.flags.latencyBudgetMs,
|
|
1742
|
+
consensusAlignment: ctx.flags.consensusAlignment,
|
|
1719
1743
|
// #2785: forward the task description so routing outcomes actually persist
|
|
1720
1744
|
// (hooks_post-task requires taskText + agent to write the outcome row that
|
|
1721
1745
|
// hooks_metrics reads via getIntelligenceStatsFromMemory)
|
|
@@ -102,12 +102,16 @@ async function dispatchFlywheel(operation, positional, flags) {
|
|
|
102
102
|
receiptPublicKeyPem: publicKeyPath ? readFileSync(resolve(publicKeyPath), 'utf8') : undefined,
|
|
103
103
|
maxConcurrency: Number(flywheelFlag(flags, 'maxConcurrency', 2)),
|
|
104
104
|
evaluationTimeoutMs: Number(flywheelFlag(flags, 'timeoutMs', 120_000)),
|
|
105
|
+
anchorPath: flywheelFlag(flags, 'anchorPath'),
|
|
106
|
+
anchorHash: flywheelFlag(flags, 'anchorHash'),
|
|
107
|
+
anchorManifestPath: flywheelFlag(flags, 'anchorManifest'),
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
else if (operation === 'receipts') {
|
|
109
112
|
data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
110
113
|
receiptId: receipt.payload.receiptId,
|
|
114
|
+
anchorRef: receipt.payload.anchorRef,
|
|
111
115
|
candidateId: receipt.payload.candidateId,
|
|
112
116
|
baselineRef: receipt.payload.baselineRef,
|
|
113
117
|
decision: receipt.payload.decision,
|
|
@@ -271,7 +271,8 @@ const TOPOLOGIES = [
|
|
|
271
271
|
{ value: 'ring', label: 'Ring', hint: 'Circular communication pattern' },
|
|
272
272
|
{ value: 'star', label: 'Star', hint: 'Central coordinator with spoke agents' },
|
|
273
273
|
{ value: 'hybrid', label: 'Hybrid', hint: 'Hierarchical mesh for maximum flexibility' },
|
|
274
|
-
{ value: 'hierarchical-mesh', label: 'Hierarchical Mesh', hint: 'V3 15-agent queen + peer communication (recommended)' }
|
|
274
|
+
{ value: 'hierarchical-mesh', label: 'Hierarchical Mesh', hint: 'V3 15-agent queen + peer communication (recommended)' },
|
|
275
|
+
{ value: 'pheromone-adaptive', label: 'Pheromone Adaptive', hint: 'Role-aware dynamic eligibility with quorum safety (ADR-330)' }
|
|
275
276
|
];
|
|
276
277
|
// Swarm strategies
|
|
277
278
|
const STRATEGIES = [
|
|
@@ -324,6 +325,19 @@ const initCommand = {
|
|
|
324
325
|
type: 'boolean',
|
|
325
326
|
default: false
|
|
326
327
|
},
|
|
328
|
+
{
|
|
329
|
+
name: 'apsc-live',
|
|
330
|
+
description: 'Apply APSC suspension decisions (default is calibration-only dry run)',
|
|
331
|
+
type: 'boolean',
|
|
332
|
+
default: false
|
|
333
|
+
},
|
|
334
|
+
{ name: 'apsc-alpha', description: 'Task-success score weight', type: 'number', default: 0.5 },
|
|
335
|
+
{ name: 'apsc-beta', description: 'Latency score weight', type: 'number', default: 0.2 },
|
|
336
|
+
{ name: 'apsc-gamma', description: 'Consensus-alignment score weight', type: 'number', default: 0.3 },
|
|
337
|
+
{ name: 'apsc-pruning-factor', description: 'Fraction of adaptive threshold below which agents are eligible for suspension', type: 'number', default: 0.6 },
|
|
338
|
+
{ name: 'apsc-reactivation-threshold', description: 'Fraction of adaptive threshold required for recovery', type: 'number', default: 0.75 },
|
|
339
|
+
{ name: 'apsc-min-active-agents', description: 'Hard quorum floor', type: 'number', default: 3 },
|
|
340
|
+
{ name: 'apsc-min-samples', description: 'Warm-up observations before pruning', type: 'number', default: 3 },
|
|
327
341
|
{
|
|
328
342
|
// #2768 — dream-cycle SubagentPermissionDelegate. Ships a per-role
|
|
329
343
|
// capability manifest to `.swarm/permissions.jsonl` + an append-only
|
|
@@ -366,6 +380,18 @@ const initCommand = {
|
|
|
366
380
|
failureHandling: 'retry',
|
|
367
381
|
loadBalancing: true,
|
|
368
382
|
autoScaling: ctx.flags.autoScale ?? true,
|
|
383
|
+
...(topology === 'pheromone-adaptive' ? {
|
|
384
|
+
apsc: {
|
|
385
|
+
alpha: Number(ctx.flags.apscAlpha ?? 0.5),
|
|
386
|
+
beta: Number(ctx.flags.apscBeta ?? 0.2),
|
|
387
|
+
gamma: Number(ctx.flags.apscGamma ?? 0.3),
|
|
388
|
+
pruningFactor: Number(ctx.flags.apscPruningFactor ?? 0.6),
|
|
389
|
+
reactivationThreshold: Number(ctx.flags.apscReactivationThreshold ?? 0.75),
|
|
390
|
+
minActiveAgents: Number(ctx.flags.apscMinActiveAgents ?? 3),
|
|
391
|
+
minSamples: Number(ctx.flags.apscMinSamples ?? 3),
|
|
392
|
+
dryRun: ctx.flags.apscLive !== true,
|
|
393
|
+
},
|
|
394
|
+
} : {}),
|
|
369
395
|
},
|
|
370
396
|
metadata: {
|
|
371
397
|
v3Mode,
|
|
@@ -393,7 +419,10 @@ const initCommand = {
|
|
|
393
419
|
{ property: 'Max Agents', value: result.config.maxAgents },
|
|
394
420
|
{ property: 'Auto Scale', value: result.config.autoScaling ? 'Enabled' : 'Disabled' },
|
|
395
421
|
{ property: 'Protocol', value: result.config.communicationProtocol || 'N/A' },
|
|
396
|
-
{ property: 'V3 Mode', value: v3Mode ? 'Enabled' : 'Disabled' }
|
|
422
|
+
{ property: 'V3 Mode', value: v3Mode ? 'Enabled' : 'Disabled' },
|
|
423
|
+
...(topology === 'pheromone-adaptive'
|
|
424
|
+
? [{ property: 'APSC Mode', value: ctx.flags.apscLive === true ? 'Live' : 'Dry run' }]
|
|
425
|
+
: [])
|
|
397
426
|
]
|
|
398
427
|
});
|
|
399
428
|
output.writeln();
|
|
@@ -924,15 +953,41 @@ const compressMessageCommand = {
|
|
|
924
953
|
return { success: true, data: result };
|
|
925
954
|
},
|
|
926
955
|
};
|
|
956
|
+
const pheromoneCommand = {
|
|
957
|
+
name: 'pheromone',
|
|
958
|
+
description: 'Inspect or update ADR-330 pheromone-adaptive scheduling state',
|
|
959
|
+
options: [
|
|
960
|
+
{ name: 'agent-id', description: 'Agent identifier; omit to show status', type: 'string' },
|
|
961
|
+
{ name: 'role', description: 'Agent role for role-local normalization', type: 'string' },
|
|
962
|
+
{ name: 'task-success', description: 'Task success in [0,1]', type: 'number' },
|
|
963
|
+
{ name: 'normalized-latency', description: 'Latency divided by budget in [0,1]', type: 'number' },
|
|
964
|
+
{ name: 'consensus-alignment', description: 'Consensus alignment in [0,1]', type: 'number' },
|
|
965
|
+
],
|
|
966
|
+
action: async (ctx) => {
|
|
967
|
+
const agentId = ctx.flags.agentId;
|
|
968
|
+
const data = agentId
|
|
969
|
+
? await callMCPTool('swarm_pheromone_update', {
|
|
970
|
+
agentId,
|
|
971
|
+
role: String(ctx.flags.role ?? 'worker'),
|
|
972
|
+
taskSuccess: Number(ctx.flags.taskSuccess ?? 1),
|
|
973
|
+
normalizedLatency: Number(ctx.flags.normalizedLatency ?? 0),
|
|
974
|
+
consensusAlignment: Number(ctx.flags.consensusAlignment ?? 1),
|
|
975
|
+
})
|
|
976
|
+
: await callMCPTool('swarm_pheromone_status', {});
|
|
977
|
+
output.writeln(JSON.stringify(data, null, 2));
|
|
978
|
+
return { success: data.active !== false, data };
|
|
979
|
+
},
|
|
980
|
+
};
|
|
927
981
|
export const swarmCommand = {
|
|
928
982
|
name: 'swarm',
|
|
929
983
|
description: 'Swarm coordination commands',
|
|
930
|
-
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand, compressMessageCommand],
|
|
984
|
+
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand, compressMessageCommand, pheromoneCommand],
|
|
931
985
|
options: [],
|
|
932
986
|
examples: [
|
|
933
987
|
{ command: 'claude-flow swarm init --v3-mode', description: 'Initialize V3 swarm' },
|
|
934
988
|
{ command: 'claude-flow swarm start -o "Build API" -s development', description: 'Start development swarm' },
|
|
935
|
-
{ command: 'claude-flow swarm coordinate --agents 15', description: 'V3 coordination' }
|
|
989
|
+
{ command: 'claude-flow swarm coordinate --agents 15', description: 'V3 coordination' },
|
|
990
|
+
{ command: 'claude-flow swarm pheromone', description: 'Inspect pheromone-adaptive scheduling state' }
|
|
936
991
|
],
|
|
937
992
|
action: async (ctx) => {
|
|
938
993
|
output.writeln();
|
|
@@ -947,7 +1002,8 @@ export const swarmCommand = {
|
|
|
947
1002
|
`${output.highlight('status')} - Show swarm status`,
|
|
948
1003
|
`${output.highlight('stop')} - Stop swarm execution`,
|
|
949
1004
|
`${output.highlight('scale')} - Scale swarm agent count`,
|
|
950
|
-
`${output.highlight('coordinate')} - V3 15-agent coordination
|
|
1005
|
+
`${output.highlight('coordinate')} - V3 15-agent coordination`,
|
|
1006
|
+
`${output.highlight('pheromone')} - Inspect/update adaptive pheromone state`
|
|
951
1007
|
]);
|
|
952
1008
|
return { success: true };
|
|
953
1009
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -196,8 +196,9 @@ export class CLI {
|
|
|
196
196
|
try {
|
|
197
197
|
const { ensureDaemonRunning } = await import('./services/daemon-autostart.js');
|
|
198
198
|
const d = ensureDaemonRunning(process.cwd());
|
|
199
|
-
if (d.started
|
|
200
|
-
this.output.
|
|
199
|
+
if (d.started) {
|
|
200
|
+
this.output.printInfo(`Started Ruflo background daemon for ${process.cwd()} (stop: ruflo daemon stop)`);
|
|
201
|
+
}
|
|
201
202
|
}
|
|
202
203
|
catch { /* silent */ }
|
|
203
204
|
}
|
|
@@ -9,6 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
10
|
import { validateIdentifier, validateText, validateAgentSpawn } from './validate-input.js';
|
|
11
11
|
import { executeAgentTask } from './agent-execute-core.js';
|
|
12
|
+
import { pheromoneAgentEligibility } from './swarm-tools.js';
|
|
12
13
|
// Storage paths
|
|
13
14
|
const STORAGE_DIR = '.claude-flow';
|
|
14
15
|
const AGENT_DIR = 'agents';
|
|
@@ -383,6 +384,15 @@ export const agentTools = [
|
|
|
383
384
|
const vP = validateText(input.prompt, 'prompt');
|
|
384
385
|
if (!vP.valid)
|
|
385
386
|
return { success: false, error: `Input validation failed: ${vP.error}` };
|
|
387
|
+
const pheromone = pheromoneAgentEligibility(input.agentId);
|
|
388
|
+
if (!pheromone.eligible) {
|
|
389
|
+
return {
|
|
390
|
+
success: false,
|
|
391
|
+
agentId: input.agentId,
|
|
392
|
+
suspended: true,
|
|
393
|
+
error: pheromone.reason,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
386
396
|
// Delegate to the shared core (also used by the workflow runtime).
|
|
387
397
|
return executeAgentTask({
|
|
388
398
|
agentId: input.agentId,
|
|
@@ -8,6 +8,7 @@ import { dirname, join, resolve } from 'path';
|
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
9
|
import { validateIdentifier, validateText, validatePath } from './validate-input.js';
|
|
10
10
|
import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
|
|
11
|
+
import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
|
|
11
12
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
12
13
|
let searchEntriesFn = null;
|
|
13
14
|
/**
|
|
@@ -185,6 +186,13 @@ function saveRoutingOutcomes(outcomes) {
|
|
|
185
186
|
// Cap at 500 entries to bound file size
|
|
186
187
|
const capped = outcomes.slice(-500);
|
|
187
188
|
writeFileSync(ROUTING_OUTCOMES_PATH, JSON.stringify({ outcomes: capped }, null, 2));
|
|
189
|
+
// A long-lived MCP process must observe the new label on the next route.
|
|
190
|
+
// The prior singleton cache made the learned store inert until restart.
|
|
191
|
+
semanticRouter = null;
|
|
192
|
+
nativeVectorDb = null;
|
|
193
|
+
semanticRouterInitialized = false;
|
|
194
|
+
routerBackend = 'none';
|
|
195
|
+
TASK_PATTERN_EMBEDDINGS.clear();
|
|
188
196
|
}
|
|
189
197
|
catch { /* non-critical */ }
|
|
190
198
|
}
|
|
@@ -194,24 +202,7 @@ function saveRoutingOutcomes(outcomes) {
|
|
|
194
202
|
* merged into both the native HNSW and pure-JS semantic routers.
|
|
195
203
|
*/
|
|
196
204
|
function loadLearnedPatterns() {
|
|
197
|
-
|
|
198
|
-
const byAgent = {};
|
|
199
|
-
for (const o of outcomes) {
|
|
200
|
-
if (!o.success || !o.agent || !o.keywords?.length)
|
|
201
|
-
continue;
|
|
202
|
-
if (!byAgent[o.agent])
|
|
203
|
-
byAgent[o.agent] = new Set();
|
|
204
|
-
for (const kw of o.keywords)
|
|
205
|
-
byAgent[o.agent].add(kw);
|
|
206
|
-
}
|
|
207
|
-
const patterns = {};
|
|
208
|
-
for (const [agent, kwSet] of Object.entries(byAgent)) {
|
|
209
|
-
patterns[`learned-${agent}`] = {
|
|
210
|
-
keywords: [...kwSet].slice(0, 50),
|
|
211
|
-
agents: [agent],
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
return patterns;
|
|
205
|
+
return buildLearnedRoutingPatterns(loadRoutingOutcomes());
|
|
215
206
|
}
|
|
216
207
|
/**
|
|
217
208
|
* Merge static TASK_PATTERNS with runtime-learned patterns.
|
|
@@ -290,46 +281,55 @@ async function getSemanticRouter() {
|
|
|
290
281
|
// STEP 1: Try native VectorDb from @ruvector/router (HNSW-backed)
|
|
291
282
|
// Note: Native VectorDb uses a persistent database file which can have lock issues
|
|
292
283
|
// in concurrent environments. We try it first but fall back gracefully to pure JS.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
284
|
+
// Deterministic/test and lock-constrained environments may force the
|
|
285
|
+
// pure-JS router; production continues to prefer the native backend.
|
|
286
|
+
if (process.env.CLAUDE_FLOW_DISABLE_NATIVE_ROUTER !== '1')
|
|
287
|
+
try {
|
|
288
|
+
// Use createRequire for ESM compatibility with native modules
|
|
289
|
+
const { createRequire } = await import('module');
|
|
290
|
+
const require = createRequire(import.meta.url);
|
|
291
|
+
const router = require('@ruvector/router');
|
|
292
|
+
if (router.VectorDb && router.DistanceMetric) {
|
|
293
|
+
// Try to create VectorDb - may fail with lock error in concurrent envs
|
|
294
|
+
const db = new router.VectorDb({
|
|
295
|
+
dimensions: 384,
|
|
296
|
+
distanceMetric: router.DistanceMetric.Cosine,
|
|
297
|
+
hnswM: 16,
|
|
298
|
+
hnswEfConstruction: 200,
|
|
299
|
+
hnswEfSearch: 100,
|
|
300
|
+
});
|
|
301
|
+
// Initialize with static + runtime-learned task patterns
|
|
302
|
+
for (const [patternName, { keywords }] of Object.entries(getMergedTaskPatterns())) {
|
|
303
|
+
for (const keyword of keywords) {
|
|
304
|
+
const embedding = generateSimpleEmbedding(keyword);
|
|
305
|
+
db.insert(`${patternName}:${keyword}`, embedding);
|
|
306
|
+
TASK_PATTERN_EMBEDDINGS.set(`${patternName}:${keyword}`, embedding);
|
|
307
|
+
}
|
|
313
308
|
}
|
|
309
|
+
nativeVectorDb = db;
|
|
310
|
+
routerBackend = 'native';
|
|
311
|
+
console.log('[hooks] Semantic router initialized: native VectorDb (HNSW, 16k+ routes/s)');
|
|
312
|
+
return { router: null, backend: routerBackend, native: nativeVectorDb };
|
|
314
313
|
}
|
|
315
|
-
nativeVectorDb = db;
|
|
316
|
-
routerBackend = 'native';
|
|
317
|
-
console.log('[hooks] Semantic router initialized: native VectorDb (HNSW, 16k+ routes/s)');
|
|
318
|
-
return { router: null, backend: routerBackend, native: nativeVectorDb };
|
|
319
314
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
// Native not available or database locked - fall back to pure JS
|
|
317
|
+
// Common errors: "Database already open. Cannot acquire lock." or "MODULE_NOT_FOUND"
|
|
318
|
+
// This is expected in concurrent environments or when binary isn't installed
|
|
319
|
+
}
|
|
326
320
|
// STEP 2: Fall back to pure JS SemanticRouter
|
|
327
321
|
try {
|
|
328
322
|
const { SemanticRouter } = await import('../ruvector/semantic-router.js');
|
|
329
323
|
semanticRouter = new SemanticRouter({ dimension: 384 });
|
|
330
|
-
for (const [patternName, { keywords, agents }] of Object.entries(getMergedTaskPatterns())) {
|
|
324
|
+
for (const [patternName, { keywords, agents, source, support, reliability }] of Object.entries(getMergedTaskPatterns())) {
|
|
331
325
|
const embeddings = keywords.map(kw => generateSimpleEmbedding(kw));
|
|
332
|
-
semanticRouter.addIntentWithEmbeddings(patternName, embeddings, {
|
|
326
|
+
semanticRouter.addIntentWithEmbeddings(patternName, embeddings, {
|
|
327
|
+
agents,
|
|
328
|
+
keywords,
|
|
329
|
+
source: source ?? 'static',
|
|
330
|
+
support,
|
|
331
|
+
reliability,
|
|
332
|
+
});
|
|
333
333
|
// Cache embeddings for keywords
|
|
334
334
|
keywords.forEach((kw, i) => {
|
|
335
335
|
TASK_PATTERN_EMBEDDINGS.set(kw, embeddings[i]);
|
|
@@ -946,6 +946,9 @@ export const hooksRoute = {
|
|
|
946
946
|
score: 1 - r.score, // Native uses distance (lower is better), convert to similarity
|
|
947
947
|
metadata: {
|
|
948
948
|
agents: pattern?.agents || (patternName.startsWith('learned-') ? [patternName.slice(8)] : ['coder']),
|
|
949
|
+
source: pattern?.source ?? (patternName.startsWith('learned-') ? 'learned' : 'static'),
|
|
950
|
+
support: pattern?.support,
|
|
951
|
+
reliability: pattern?.reliability,
|
|
949
952
|
},
|
|
950
953
|
};
|
|
951
954
|
});
|
|
@@ -966,8 +969,18 @@ export const hooksRoute = {
|
|
|
966
969
|
let agents;
|
|
967
970
|
let confidence;
|
|
968
971
|
let matchedPattern = '';
|
|
969
|
-
|
|
970
|
-
|
|
972
|
+
const eligibleSemantic = semanticResult.find((match) => {
|
|
973
|
+
if (match.score <= 0.4)
|
|
974
|
+
return false;
|
|
975
|
+
const learned = match.intent.startsWith('learned-') || match.metadata.source === 'learned';
|
|
976
|
+
if (!learned)
|
|
977
|
+
return true;
|
|
978
|
+
return match.score >= 0.65
|
|
979
|
+
&& Number(match.metadata.support ?? 0) >= 2
|
|
980
|
+
&& Number(match.metadata.reliability ?? 0) >= 0.75;
|
|
981
|
+
});
|
|
982
|
+
if (eligibleSemantic) {
|
|
983
|
+
const topMatch = eligibleSemantic;
|
|
971
984
|
agents = topMatch.metadata.agents || ['coder', 'researcher'];
|
|
972
985
|
confidence = topMatch.score;
|
|
973
986
|
matchedPattern = topMatch.intent;
|
|
@@ -1249,6 +1262,10 @@ export const hooksPostTask = {
|
|
|
1249
1262
|
agent: { type: 'string', description: 'Agent that completed the task' },
|
|
1250
1263
|
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1251
1264
|
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1265
|
+
duration: { type: 'number', description: 'Observed task duration in milliseconds (used by pheromone-adaptive topology)' },
|
|
1266
|
+
latencyBudgetMs: { type: 'number', description: 'Latency budget used to normalize duration (default 60000ms)' },
|
|
1267
|
+
consensusAlignment: { type: 'number', description: 'Agreement with accepted swarm consensus in [0,1] (default quality)' },
|
|
1268
|
+
agentRole: { type: 'string', description: 'Role for role-local pheromone normalization (default agent)' },
|
|
1252
1269
|
storeDecisions: { type: 'boolean', description: 'Also store routing decision in memory DB' },
|
|
1253
1270
|
// ADR-147 P2: nested-subagent spawn-tree capture
|
|
1254
1271
|
parentAgentId: { type: 'string', description: 'ID of the parent agent (from Claude Code\'s parent_agent_id OTel span tag / x-claude-code-parent-agent-id header). Omit for top-level work.' },
|
|
@@ -1405,6 +1422,22 @@ export const hooksPostTask = {
|
|
|
1405
1422
|
}
|
|
1406
1423
|
catch { /* non-critical */ }
|
|
1407
1424
|
}
|
|
1425
|
+
let pheromone;
|
|
1426
|
+
if (agent) {
|
|
1427
|
+
try {
|
|
1428
|
+
const { recordSwarmPheromoneSignal } = await import('./swarm-tools.js');
|
|
1429
|
+
const observedDuration = Math.max(0, Number(params.duration ?? (Date.now() - startTime)));
|
|
1430
|
+
const latencyBudgetMs = Math.max(1, Number(params.latencyBudgetMs ?? 60_000));
|
|
1431
|
+
pheromone = recordSwarmPheromoneSignal({
|
|
1432
|
+
agentId: agent,
|
|
1433
|
+
role: String(params.agentRole ?? agent),
|
|
1434
|
+
taskSuccess: success ? 1 : 0,
|
|
1435
|
+
normalizedLatency: Math.max(0, Math.min(1, observedDuration / latencyBudgetMs)),
|
|
1436
|
+
consensusAlignment: Math.max(0, Math.min(1, Number(params.consensusAlignment ?? quality))),
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
catch { /* no active APSC swarm or optional path unavailable */ }
|
|
1440
|
+
}
|
|
1408
1441
|
const duration = Date.now() - startTime;
|
|
1409
1442
|
// Persist to auto-memory-store for statusline visibility
|
|
1410
1443
|
try {
|
|
@@ -1444,6 +1477,7 @@ export const hooksPostTask = {
|
|
|
1444
1477
|
outcomePersisted,
|
|
1445
1478
|
},
|
|
1446
1479
|
quality,
|
|
1480
|
+
pheromone,
|
|
1447
1481
|
feedback: feedbackResult ? {
|
|
1448
1482
|
recorded: feedbackResult.success,
|
|
1449
1483
|
controller: feedbackResult.controller,
|
|
@@ -1175,6 +1175,7 @@ export const memoryTools = [
|
|
|
1175
1175
|
handler: async (input) => {
|
|
1176
1176
|
await ensureInitialized();
|
|
1177
1177
|
const { listEntries, deleteEntry } = await getMemoryFunctions();
|
|
1178
|
+
const startedAt = Date.now();
|
|
1178
1179
|
const dryRun = input.dryRun !== false; // default true
|
|
1179
1180
|
const namespace = input.namespace ? String(input.namespace) : undefined;
|
|
1180
1181
|
if (namespace) {
|
|
@@ -1193,6 +1194,7 @@ export const memoryTools = [
|
|
|
1193
1194
|
});
|
|
1194
1195
|
let freedBytes = 0;
|
|
1195
1196
|
let deleted = 0;
|
|
1197
|
+
let vectorsRemoved = 0;
|
|
1196
1198
|
if (!dryRun) {
|
|
1197
1199
|
for (const e of expired) {
|
|
1198
1200
|
try {
|
|
@@ -1206,11 +1208,21 @@ export const memoryTools = [
|
|
|
1206
1208
|
else {
|
|
1207
1209
|
freedBytes = expired.reduce((s, e) => s + (e.size || 0), 0);
|
|
1208
1210
|
}
|
|
1211
|
+
if (!dryRun) {
|
|
1212
|
+
const { reconcileHNSWIndex } = await import('../memory/memory-initializer.js');
|
|
1213
|
+
vectorsRemoved = await reconcileHNSWIndex();
|
|
1214
|
+
}
|
|
1215
|
+
const formatted = freedBytes < 1024
|
|
1216
|
+
? `${freedBytes} B`
|
|
1217
|
+
: freedBytes < 1024 * 1024
|
|
1218
|
+
? `${(freedBytes / 1024).toFixed(1)} KiB`
|
|
1219
|
+
: `${(freedBytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
1209
1220
|
return {
|
|
1210
1221
|
dryRun,
|
|
1211
1222
|
candidates: { expired: expired.length, stale: 0, lowQuality: 0, total: expired.length },
|
|
1212
|
-
deleted: { entries: dryRun ? 0 : deleted, vectors:
|
|
1213
|
-
freed: { bytes: freedBytes },
|
|
1223
|
+
deleted: { entries: dryRun ? 0 : deleted, vectors: vectorsRemoved, patterns: 0 },
|
|
1224
|
+
freed: { bytes: freedBytes, formatted },
|
|
1225
|
+
duration: Date.now() - startedAt,
|
|
1214
1226
|
note: dryRun ? 'dry run — re-run with dryRun:false to delete' : undefined,
|
|
1215
1227
|
};
|
|
1216
1228
|
},
|
|
@@ -700,6 +700,9 @@ export const metaharnessTools = [
|
|
|
700
700
|
confirm: { type: 'boolean', description: 'Required for promote; never inferred', default: false },
|
|
701
701
|
maxConcurrency: { type: 'number', description: 'ADR-324 hard local candidate-evaluation concurrency cap (1-8)', default: 2 },
|
|
702
702
|
timeoutMs: { type: 'number', description: 'Abort concurrent evaluation after this wall-clock limit', default: 120000 },
|
|
703
|
+
anchorPath: { type: 'string', description: 'Project-contained human-labelled anchor JSON; requires anchorHash' },
|
|
704
|
+
anchorHash: { type: 'string', description: 'Pinned sha256 of canonical anchor tasks; requires anchorPath' },
|
|
705
|
+
anchorManifestPath: { type: 'string', description: 'Project-contained anchor manifest path (default .claude/eval/flywheel-anchor.manifest.json)' },
|
|
703
706
|
approvalIds: { type: 'array', items: { type: 'string' }, description: 'Scoped ADR-324 approval IDs for privileged promotion' },
|
|
704
707
|
},
|
|
705
708
|
required: ['operation'],
|
|
@@ -721,6 +724,7 @@ export const metaharnessTools = [
|
|
|
721
724
|
if (operation === 'receipts') {
|
|
722
725
|
const data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
723
726
|
receiptId: receipt.payload.receiptId,
|
|
727
|
+
anchorRef: receipt.payload.anchorRef,
|
|
724
728
|
candidateId: receipt.payload.candidateId,
|
|
725
729
|
baselineRef: receipt.payload.baselineRef,
|
|
726
730
|
decision: receipt.payload.decision,
|
|
@@ -748,6 +752,9 @@ export const metaharnessTools = [
|
|
|
748
752
|
receiptPublicKeyPem: publicKeyPath ? readFileSync(publicKeyPath, 'utf8') : undefined,
|
|
749
753
|
maxConcurrency: Number(input.maxConcurrency ?? 2),
|
|
750
754
|
evaluationTimeoutMs: Number(input.timeoutMs ?? 120_000),
|
|
755
|
+
anchorPath: input.anchorPath ? String(input.anchorPath) : undefined,
|
|
756
|
+
anchorHash: input.anchorHash ? String(input.anchorHash) : undefined,
|
|
757
|
+
anchorManifestPath: input.anchorManifestPath ? String(input.anchorManifestPath) : undefined,
|
|
751
758
|
});
|
|
752
759
|
return { success: data.ran, data, degraded: false, exitCode: data.ran ? 0 : 1 };
|
|
753
760
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Replaces previous stub implementations with real state tracking.
|
|
6
6
|
*/
|
|
7
7
|
import { type MCPTool } from './types.js';
|
|
8
|
+
import { type ApscDecision, type ApscSignal } from '../services/pheromone-adaptive.js';
|
|
8
9
|
interface SwarmState {
|
|
9
10
|
swarmId: string;
|
|
10
11
|
topology: string;
|
|
@@ -32,6 +33,21 @@ interface SwarmStore {
|
|
|
32
33
|
}
|
|
33
34
|
export declare function loadSwarmStore(): SwarmStore;
|
|
34
35
|
export declare function saveSwarmStore(store: SwarmStore): void;
|
|
36
|
+
/** Shared by hooks_post-task so outcome labels immediately drive APSC. */
|
|
37
|
+
export declare function recordSwarmPheromoneSignal(signal: ApscSignal): {
|
|
38
|
+
active: false;
|
|
39
|
+
reason: string;
|
|
40
|
+
} | {
|
|
41
|
+
active: true;
|
|
42
|
+
swarmId: string;
|
|
43
|
+
decision: ApscDecision;
|
|
44
|
+
};
|
|
45
|
+
/** Scheduling gate used by agent_execute. Dry-run topologies remain eligible. */
|
|
46
|
+
export declare function pheromoneAgentEligibility(agentId: string): {
|
|
47
|
+
eligible: boolean;
|
|
48
|
+
active: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
};
|
|
35
51
|
export declare const swarmTools: MCPTool[];
|
|
36
52
|
export {};
|
|
37
53
|
//# sourceMappingURL=swarm-tools.d.ts.map
|