claude-flow 3.32.34 → 3.32.36
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/hook-handler.cjs +134 -3
- package/.claude/helpers/intelligence.cjs +47 -4
- package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +9 -13
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/bin/cli.js +25 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +50 -2
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +159 -11
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +63 -4
- package/v3/@claude-flow/cli/dist/src/commands/mcp.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +4 -0
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +89 -5
- package/v3/@claude-flow/cli/dist/src/config-adapter.js +1 -0
- package/v3/@claude-flow/cli/dist/src/index.js +3 -2
- package/v3/@claude-flow/cli/dist/src/init/executor.js +16 -10
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +15 -4
- package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -5
- package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +57 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +10 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +51 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +120 -60
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +14 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.d.ts +16 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.js +172 -7
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +5 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +66 -19
- package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +7 -4
- package/v3/@claude-flow/cli/dist/src/services/flywheel-receipt.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/services/flywheel-receipt.js +1 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +19 -19
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +1 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-project-anchor.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-project-anchor.js +129 -0
- package/v3/@claude-flow/cli/dist/src/services/learned-routing.d.ts +34 -0
- package/v3/@claude-flow/cli/dist/src/services/learned-routing.js +85 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +81 -5
- package/v3/@claude-flow/cli/dist/src/services/pheromone-adaptive.d.ts +71 -0
- package/v3/@claude-flow/cli/dist/src/services/pheromone-adaptive.js +214 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +5 -1
- package/v3/@claude-flow/cli/package.json +1 -1
- package/v3/@claude-flow/shared/dist/core/config/loader.js +1 -1
- package/v3/@claude-flow/shared/dist/core/config/schema.d.ts +12 -12
- package/v3/@claude-flow/shared/dist/core/config/schema.js +1 -1
- package/v3/@claude-flow/shared/dist/core/interfaces/coordinator.interface.d.ts +1 -1
- package/v3/@claude-flow/shared/dist/types/swarm.types.js +18 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-local, hash-pinned flywheel anchors (#2840).
|
|
3
|
+
*
|
|
4
|
+
* Downstream repositories must be evaluated against their own labelled
|
|
5
|
+
* retrieval tasks. Silently using Ruflo's development-history benchmark makes
|
|
6
|
+
* the objective flat and indistinguishable from "already optimal", so foreign
|
|
7
|
+
* projects fail closed unless they supply a pinned anchor manifest.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, realpathSync, } from 'node:fs';
|
|
10
|
+
import { dirname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
11
|
+
import { FROZEN_HUMAN_EVAL_HASH, loadFrozenHumanEval, humanEvalHash, } from './harness-frozen-eval.js';
|
|
12
|
+
export const PROJECT_ANCHOR_SCHEMA = 'ruflo.flywheel-anchor/v1';
|
|
13
|
+
export const PROJECT_ANCHOR_MANIFEST_SCHEMA = 'ruflo.flywheel-anchor-manifest/v1';
|
|
14
|
+
export const DEFAULT_PROJECT_ANCHOR_MANIFEST = join('.claude', 'eval', 'flywheel-anchor.manifest.json');
|
|
15
|
+
function normalizeHash(value) {
|
|
16
|
+
const trimmed = value.trim().toLowerCase();
|
|
17
|
+
return trimmed.startsWith('sha256:') ? trimmed : `sha256:${trimmed}`;
|
|
18
|
+
}
|
|
19
|
+
function containedPath(projectRoot, requested) {
|
|
20
|
+
const root = realpathSync(resolve(projectRoot));
|
|
21
|
+
const absolute = isAbsolute(requested) ? resolve(requested) : resolve(root, requested);
|
|
22
|
+
const lexical = relative(root, absolute);
|
|
23
|
+
if (lexical === '..' || lexical.startsWith(`..${sep}`) || isAbsolute(lexical)) {
|
|
24
|
+
throw new Error('flywheel anchor path must stay inside project root');
|
|
25
|
+
}
|
|
26
|
+
const actual = realpathSync(absolute);
|
|
27
|
+
const physical = relative(root, actual);
|
|
28
|
+
if (physical === '..' || physical.startsWith(`..${sep}`) || isAbsolute(physical)) {
|
|
29
|
+
throw new Error('flywheel anchor symlink escapes project root');
|
|
30
|
+
}
|
|
31
|
+
return actual;
|
|
32
|
+
}
|
|
33
|
+
function parseTasks(path) {
|
|
34
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
35
|
+
if (parsed.schemaVersion && parsed.schemaVersion !== PROJECT_ANCHOR_SCHEMA) {
|
|
36
|
+
throw new Error(`unsupported flywheel anchor schema: ${parsed.schemaVersion}`);
|
|
37
|
+
}
|
|
38
|
+
if (!Array.isArray(parsed.tasks) || parsed.tasks.length < 4) {
|
|
39
|
+
throw new Error('project flywheel anchor requires at least 4 labelled tasks');
|
|
40
|
+
}
|
|
41
|
+
const ids = new Set();
|
|
42
|
+
for (const [index, task] of parsed.tasks.entries()) {
|
|
43
|
+
if (!task || typeof task.id !== 'string' || !/^[A-Za-z0-9._-]{1,128}$/.test(task.id)) {
|
|
44
|
+
throw new Error(`invalid anchor task id at index ${index}`);
|
|
45
|
+
}
|
|
46
|
+
if (ids.has(task.id))
|
|
47
|
+
throw new Error(`duplicate anchor task id: ${task.id}`);
|
|
48
|
+
ids.add(task.id);
|
|
49
|
+
if (typeof task.q !== 'string' || task.q.trim().length === 0) {
|
|
50
|
+
throw new Error(`anchor task ${task.id} has no query`);
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {
|
|
53
|
+
throw new Error(`anchor task ${task.id} requires non-empty string labels`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };
|
|
57
|
+
}
|
|
58
|
+
function toSelection(path, expectedHash) {
|
|
59
|
+
const parsed = parseTasks(path);
|
|
60
|
+
const actualHash = humanEvalHash(parsed.tasks);
|
|
61
|
+
if (actualHash !== normalizeHash(expectedHash)) {
|
|
62
|
+
throw new Error(`project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
version: parsed.version,
|
|
66
|
+
anchorRef: actualHash,
|
|
67
|
+
source: 'project',
|
|
68
|
+
path,
|
|
69
|
+
tasks: parsed.tasks.map((task) => ({
|
|
70
|
+
id: task.id,
|
|
71
|
+
input: { id: task.id, q: task.q },
|
|
72
|
+
expected: task.labels,
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function isRufloRepository(projectRoot) {
|
|
77
|
+
try {
|
|
78
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
|
|
79
|
+
const repository = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
|
|
80
|
+
return ['claude-flow', 'ruflo', '@claude-flow/cli'].includes(pkg.name ?? '')
|
|
81
|
+
&& /github\.com[/:]ruvnet\/(?:ruflo|claude-flow)(?:\.git)?$/i.test(repository ?? '');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function loadEffectiveFlywheelAnchor(projectRoot, options = {}) {
|
|
88
|
+
const root = resolve(projectRoot);
|
|
89
|
+
if (!!options.anchorPath !== !!options.anchorHash) {
|
|
90
|
+
throw new Error('anchorPath and anchorHash must be supplied together');
|
|
91
|
+
}
|
|
92
|
+
if (options.anchorPath && options.anchorHash) {
|
|
93
|
+
return toSelection(containedPath(root, options.anchorPath), options.anchorHash);
|
|
94
|
+
}
|
|
95
|
+
const manifestCandidate = options.manifestPath ?? DEFAULT_PROJECT_ANCHOR_MANIFEST;
|
|
96
|
+
const manifestPath = isAbsolute(manifestCandidate)
|
|
97
|
+
? manifestCandidate
|
|
98
|
+
: resolve(root, manifestCandidate);
|
|
99
|
+
if (existsSync(manifestPath)) {
|
|
100
|
+
const containedManifest = containedPath(root, manifestPath);
|
|
101
|
+
const manifest = JSON.parse(readFileSync(containedManifest, 'utf8'));
|
|
102
|
+
if (manifest.schemaVersion !== PROJECT_ANCHOR_MANIFEST_SCHEMA) {
|
|
103
|
+
throw new Error(`unsupported flywheel anchor manifest schema: ${manifest.schemaVersion}`);
|
|
104
|
+
}
|
|
105
|
+
if (typeof manifest.path !== 'string' || typeof manifest.sha256 !== 'string') {
|
|
106
|
+
throw new Error('flywheel anchor manifest requires path and sha256');
|
|
107
|
+
}
|
|
108
|
+
// Manifest-relative paths are easier to relocate while remaining
|
|
109
|
+
// repository-contained; project-relative paths remain supported.
|
|
110
|
+
const manifestRelative = resolve(dirname(containedManifest), manifest.path);
|
|
111
|
+
const requested = existsSync(manifestRelative) ? manifestRelative : resolve(root, manifest.path);
|
|
112
|
+
return toSelection(containedPath(root, requested), manifest.sha256);
|
|
113
|
+
}
|
|
114
|
+
if (isRufloRepository(root) || process.env.RUFLO_FLYWHEEL_ALLOW_BUILTIN_ANCHOR === '1') {
|
|
115
|
+
const frozen = loadFrozenHumanEval();
|
|
116
|
+
return {
|
|
117
|
+
version: frozen.version,
|
|
118
|
+
anchorRef: FROZEN_HUMAN_EVAL_HASH,
|
|
119
|
+
source: 'ruflo-built-in',
|
|
120
|
+
tasks: frozen.tasks.map((task) => ({
|
|
121
|
+
id: task.id,
|
|
122
|
+
input: { id: task.id, q: task.q },
|
|
123
|
+
expected: task.labels,
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`project-local flywheel anchor required; create ${DEFAULT_PROJECT_ANCHOR_MANIFEST} or pass anchorPath + anchorHash`);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=harness-project-anchor.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminative routing-pattern learner (#2839).
|
|
3
|
+
*
|
|
4
|
+
* Outcomes are labelled by the agent that actually completed the task. The
|
|
5
|
+
* previous implementation collapsed every successful task for an agent into
|
|
6
|
+
* one insertion-ordered Set and kept the first 50 words forever. This module
|
|
7
|
+
* ranks terms by within-agent support, cross-agent rarity, outcome quality,
|
|
8
|
+
* and discriminative share. Generic terms therefore lose to stable,
|
|
9
|
+
* agent-specific evidence, and later outcomes can replace earlier noise.
|
|
10
|
+
*/
|
|
11
|
+
export interface LearnedRoutingOutcome {
|
|
12
|
+
task: string;
|
|
13
|
+
agent: string;
|
|
14
|
+
success: boolean;
|
|
15
|
+
quality: number;
|
|
16
|
+
keywords: string[];
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LearnedRoutingPattern {
|
|
20
|
+
keywords: string[];
|
|
21
|
+
agents: string[];
|
|
22
|
+
source: 'learned';
|
|
23
|
+
support: number;
|
|
24
|
+
reliability: number;
|
|
25
|
+
}
|
|
26
|
+
export interface LearnedRoutingOptions {
|
|
27
|
+
maxKeywords?: number;
|
|
28
|
+
minAgentOutcomes?: number;
|
|
29
|
+
minKeywordSupport?: number;
|
|
30
|
+
minOutcomeQuality?: number;
|
|
31
|
+
minDiscriminativeShare?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildLearnedRoutingPatterns(outcomes: LearnedRoutingOutcome[], options?: LearnedRoutingOptions): Record<string, LearnedRoutingPattern>;
|
|
34
|
+
//# sourceMappingURL=learned-routing.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminative routing-pattern learner (#2839).
|
|
3
|
+
*
|
|
4
|
+
* Outcomes are labelled by the agent that actually completed the task. The
|
|
5
|
+
* previous implementation collapsed every successful task for an agent into
|
|
6
|
+
* one insertion-ordered Set and kept the first 50 words forever. This module
|
|
7
|
+
* ranks terms by within-agent support, cross-agent rarity, outcome quality,
|
|
8
|
+
* and discriminative share. Generic terms therefore lose to stable,
|
|
9
|
+
* agent-specific evidence, and later outcomes can replace earlier noise.
|
|
10
|
+
*/
|
|
11
|
+
const finiteUnit = (value) => Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
12
|
+
export function buildLearnedRoutingPatterns(outcomes, options = {}) {
|
|
13
|
+
const maxKeywords = Math.max(1, Math.min(options.maxKeywords ?? 50, 100));
|
|
14
|
+
const minAgentOutcomes = Math.max(1, options.minAgentOutcomes ?? 2);
|
|
15
|
+
const minKeywordSupport = Math.max(1, options.minKeywordSupport ?? 2);
|
|
16
|
+
const minOutcomeQuality = finiteUnit(options.minOutcomeQuality ?? 0.65);
|
|
17
|
+
const minDiscriminativeShare = finiteUnit(options.minDiscriminativeShare ?? 0.6);
|
|
18
|
+
const accepted = outcomes.filter((outcome) => outcome.success
|
|
19
|
+
&& typeof outcome.agent === 'string'
|
|
20
|
+
&& outcome.agent.length > 0
|
|
21
|
+
&& finiteUnit(outcome.quality) >= minOutcomeQuality
|
|
22
|
+
&& Array.isArray(outcome.keywords)
|
|
23
|
+
&& outcome.keywords.length > 0);
|
|
24
|
+
const agents = [...new Set(accepted.map((outcome) => outcome.agent))].sort();
|
|
25
|
+
if (agents.length === 0)
|
|
26
|
+
return {};
|
|
27
|
+
const agentOutcomes = new Map();
|
|
28
|
+
const evidence = new Map();
|
|
29
|
+
const globalSupport = new Map();
|
|
30
|
+
const agentDocumentFrequency = new Map();
|
|
31
|
+
for (const agent of agents) {
|
|
32
|
+
const rows = accepted.filter((outcome) => outcome.agent === agent);
|
|
33
|
+
agentOutcomes.set(agent, rows);
|
|
34
|
+
const perKeyword = new Map();
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
const unique = new Set(row.keywords.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean));
|
|
37
|
+
for (const keyword of unique) {
|
|
38
|
+
const current = perKeyword.get(keyword) ?? { support: 0, qualityTotal: 0 };
|
|
39
|
+
current.support++;
|
|
40
|
+
current.qualityTotal += finiteUnit(row.quality);
|
|
41
|
+
perKeyword.set(keyword, current);
|
|
42
|
+
globalSupport.set(keyword, (globalSupport.get(keyword) ?? 0) + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
evidence.set(agent, perKeyword);
|
|
46
|
+
for (const keyword of perKeyword.keys()) {
|
|
47
|
+
agentDocumentFrequency.set(keyword, (agentDocumentFrequency.get(keyword) ?? 0) + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const patterns = {};
|
|
51
|
+
for (const agent of agents) {
|
|
52
|
+
const rows = agentOutcomes.get(agent) ?? [];
|
|
53
|
+
if (rows.length < minAgentOutcomes)
|
|
54
|
+
continue;
|
|
55
|
+
const ranked = [...(evidence.get(agent) ?? new Map()).entries()]
|
|
56
|
+
.filter(([, item]) => item.support >= minKeywordSupport)
|
|
57
|
+
.map(([keyword, item]) => {
|
|
58
|
+
const totalSupport = globalSupport.get(keyword) ?? item.support;
|
|
59
|
+
const discriminativeShare = item.support / Math.max(1, totalSupport);
|
|
60
|
+
const documentFrequency = agentDocumentFrequency.get(keyword) ?? 1;
|
|
61
|
+
const idf = Math.log(1 + agents.length / documentFrequency);
|
|
62
|
+
const withinAgentSupport = item.support / rows.length;
|
|
63
|
+
const meanQuality = item.qualityTotal / item.support;
|
|
64
|
+
return {
|
|
65
|
+
keyword,
|
|
66
|
+
discriminativeShare,
|
|
67
|
+
score: withinAgentSupport * meanQuality * idf * discriminativeShare,
|
|
68
|
+
};
|
|
69
|
+
})
|
|
70
|
+
.filter((item) => item.discriminativeShare >= minDiscriminativeShare)
|
|
71
|
+
.sort((a, b) => b.score - a.score || a.keyword.localeCompare(b.keyword))
|
|
72
|
+
.slice(0, maxKeywords);
|
|
73
|
+
if (ranked.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
patterns[`learned-${agent}`] = {
|
|
76
|
+
keywords: ranked.map((item) => item.keyword),
|
|
77
|
+
agents: [agent],
|
|
78
|
+
source: 'learned',
|
|
79
|
+
support: rows.length,
|
|
80
|
+
reliability: rows.reduce((sum, row) => sum + finiteUnit(row.quality), 0) / rows.length,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return patterns;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=learned-routing.js.map
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Turns raw `memory_entries` (what ruflo has been RECORDING for thousands of
|
|
5
5
|
* commits) into the structured intelligence substrate it has never populated:
|
|
6
|
-
* `episodes`
|
|
6
|
+
* `episodes` (+ `episode_embeddings`) → `reasoning_patterns`
|
|
7
|
+
* (+ `pattern_embeddings`) → `causal_edges`.
|
|
7
8
|
* This is the DISTILL/CONSOLIDATE half of the RETRIEVE→JUDGE→DISTILL→CONSOLIDATE
|
|
8
9
|
* pipeline; RETRIEVE (embeddings) was already populated, the rest was empty
|
|
9
10
|
* because the daemon's `consolidate` worker was a stub (see ADR-174 root cause).
|
|
@@ -28,7 +29,8 @@ import * as path from 'path';
|
|
|
28
29
|
import { distillTrajectoryContent, serialiseDistilled } from '../memory/structured-distill.js';
|
|
29
30
|
function emptyReport(dryRun, extra = {}) {
|
|
30
31
|
return {
|
|
31
|
-
processed: 0, episodes: 0,
|
|
32
|
+
processed: 0, episodes: 0, episodeEmbeddings: 0,
|
|
33
|
+
patterns: 0, patternEmbeddings: 0, causalEdges: 0,
|
|
32
34
|
promoted: 0, byProvenance: {}, namespaces: [], dryRun, spendUsd: 0, ...extra,
|
|
33
35
|
};
|
|
34
36
|
}
|
|
@@ -74,6 +76,20 @@ function judgeFeedback(content) {
|
|
|
74
76
|
catch { /* not JSON — treat as neutral */ }
|
|
75
77
|
return { success: true, reward: 0.5 };
|
|
76
78
|
}
|
|
79
|
+
/** Preserve an execution-observed lesson for Reflexion without presenting
|
|
80
|
+
* proxy/structural summaries as genuine self-critique. */
|
|
81
|
+
function feedbackCritique(content) {
|
|
82
|
+
try {
|
|
83
|
+
const value = JSON.parse(content);
|
|
84
|
+
for (const key of ['critique', 'error', 'message', 'summary', 'stderr']) {
|
|
85
|
+
if (typeof value[key] === 'string' && value[key].trim()) {
|
|
86
|
+
return value[key].trim().slice(0, 2000);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch { /* retain the original execution feedback below */ }
|
|
91
|
+
return `Observed execution feedback: ${content}`.slice(0, 2000);
|
|
92
|
+
}
|
|
77
93
|
/**
|
|
78
94
|
* Distill accumulated memory into the structured intelligence tables.
|
|
79
95
|
* Incremental, $0, transactional, provenance-tagged.
|
|
@@ -115,6 +131,17 @@ export async function runDistillation(options) {
|
|
|
115
131
|
db.close();
|
|
116
132
|
return { ...report, corrupt: true, skipped: 'memory DB reports corruption — run recoverMemoryDatabase first' };
|
|
117
133
|
}
|
|
134
|
+
// #2677 — older AgentDB stores may have episodes but no embedding table.
|
|
135
|
+
// Create the stable AgentDB v1 table only after the corruption gate, and
|
|
136
|
+
// never create it during a dry-run.
|
|
137
|
+
if (!dryRun && !tableExists('episode_embeddings')) {
|
|
138
|
+
db.exec(`CREATE TABLE episode_embeddings (
|
|
139
|
+
episode_id INTEGER PRIMARY KEY,
|
|
140
|
+
embedding BLOB NOT NULL,
|
|
141
|
+
embedding_model TEXT DEFAULT 'all-MiniLM-L6-v2',
|
|
142
|
+
FOREIGN KEY(episode_id) REFERENCES episodes(id) ON DELETE CASCADE
|
|
143
|
+
)`);
|
|
144
|
+
}
|
|
118
145
|
// M0: incremental cursor table (per namespace, by monotonic rowid).
|
|
119
146
|
db.exec(`CREATE TABLE IF NOT EXISTS distill_state (
|
|
120
147
|
namespace TEXT PRIMARY KEY,
|
|
@@ -128,10 +155,54 @@ export async function runDistillation(options) {
|
|
|
128
155
|
const getCursor = db.prepare('SELECT last_rowid FROM distill_state WHERE namespace = ?');
|
|
129
156
|
const setCursor = db.prepare('INSERT INTO distill_state (namespace, last_rowid, last_run_at) VALUES (?, ?, ?) ' +
|
|
130
157
|
'ON CONFLICT(namespace) DO UPDATE SET last_rowid = excluded.last_rowid, last_run_at = excluded.last_run_at');
|
|
131
|
-
const insEpisode = db.prepare('INSERT INTO episodes (session_id, task, input, output, reward, success, tags, metadata) VALUES (
|
|
158
|
+
const insEpisode = db.prepare('INSERT INTO episodes (session_id, task, input, output, critique, reward, success, tags, metadata) VALUES (?,?,?,?,?,?,?,?,?)');
|
|
159
|
+
const insEpisodeEmbedding = dryRun ? null : db.prepare('INSERT OR REPLACE INTO episode_embeddings (episode_id, embedding) VALUES (?, ?)');
|
|
132
160
|
const insPattern = db.prepare('INSERT INTO reasoning_patterns (task_type, approach, success_rate, uses, avg_reward, tags, metadata) VALUES (?,?,?,?,?,?,?)');
|
|
133
161
|
const insPatEmb = db.prepare('INSERT OR REPLACE INTO pattern_embeddings (pattern_id, embedding) VALUES (?, ?)');
|
|
134
162
|
const insEdge = db.prepare('INSERT INTO causal_edges (from_memory_id, from_memory_type, to_memory_id, to_memory_type, similarity, confidence, mechanism, metadata) VALUES (?,?,?,?,?,?,?,?)');
|
|
163
|
+
// Repair episodes produced by older distillation releases. Their metadata
|
|
164
|
+
// records sourceIds; if that provenance is absent, exact input matching is
|
|
165
|
+
// a conservative fallback. Never invent a vector or call a model.
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
const missingEpisodes = db.prepare(`
|
|
168
|
+
SELECT e.id, e.input, e.metadata
|
|
169
|
+
FROM episodes e
|
|
170
|
+
LEFT JOIN episode_embeddings ee ON ee.episode_id = e.id
|
|
171
|
+
WHERE ee.episode_id IS NULL AND e.session_id LIKE 'distill:%'
|
|
172
|
+
`).all();
|
|
173
|
+
const embeddingById = db.prepare('SELECT embedding FROM memory_entries WHERE id = ? AND embedding IS NOT NULL');
|
|
174
|
+
const embeddingByContent = db.prepare('SELECT embedding FROM memory_entries WHERE content = ? AND embedding IS NOT NULL LIMIT 1');
|
|
175
|
+
const missingCritiques = db.prepare(`
|
|
176
|
+
SELECT id, input FROM episodes
|
|
177
|
+
WHERE session_id LIKE 'distill:feedback%'
|
|
178
|
+
AND length(trim(coalesce(critique,''))) = 0
|
|
179
|
+
`).all();
|
|
180
|
+
const updateCritique = db.prepare('UPDATE episodes SET critique = ? WHERE id = ?');
|
|
181
|
+
const backfill = db.transaction(() => {
|
|
182
|
+
for (const episode of missingEpisodes) {
|
|
183
|
+
let sourceId;
|
|
184
|
+
try {
|
|
185
|
+
const metadata = JSON.parse(episode.metadata ?? '{}');
|
|
186
|
+
if (Array.isArray(metadata.sourceIds) && typeof metadata.sourceIds[0] === 'string') {
|
|
187
|
+
sourceId = metadata.sourceIds[0];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch { /* use exact-content fallback */ }
|
|
191
|
+
let raw = sourceId ? embeddingById.get(sourceId)?.embedding : undefined;
|
|
192
|
+
if (!raw)
|
|
193
|
+
raw = embeddingByContent.get(episode.input ?? '')?.embedding;
|
|
194
|
+
const vector = parseEmbedding(raw);
|
|
195
|
+
if (!vector)
|
|
196
|
+
continue;
|
|
197
|
+
insEpisodeEmbedding.run(episode.id, Buffer.from(Float32Array.from(vector).buffer));
|
|
198
|
+
report.episodeEmbeddings++;
|
|
199
|
+
}
|
|
200
|
+
for (const episode of missingCritiques) {
|
|
201
|
+
updateCritique.run(feedbackCritique(episode.input ?? ''), episode.id);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
backfill();
|
|
205
|
+
}
|
|
135
206
|
let remaining = typeof maxEntries === 'number' ? maxEntries : Infinity;
|
|
136
207
|
for (const ns of nsList) {
|
|
137
208
|
if (remaining <= 0)
|
|
@@ -200,8 +271,14 @@ export async function runDistillation(options) {
|
|
|
200
271
|
sourceIds: cl.members.map(m => m.id).slice(0, 25),
|
|
201
272
|
paths: distilled.paths.slice(0, 10), namespace: ns, distilledBy: 'structural',
|
|
202
273
|
};
|
|
203
|
-
const epInfo = insEpisode.run(`distill:${ns}`, approach, cl.rep.content.slice(0, 2000), '', avgReward, promoted && cl.successCount > 0 ? 1 : 0, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
|
|
274
|
+
const epInfo = insEpisode.run(`distill:${ns}`, approach, cl.rep.content.slice(0, 2000), '', cl.provenance === 'oracle:test-exec' ? feedbackCritique(cl.rep.content) : null, avgReward, promoted && cl.successCount > 0 ? 1 : 0, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
|
|
204
275
|
report.episodes++;
|
|
276
|
+
const epId = Number(epInfo.lastInsertRowid);
|
|
277
|
+
// ReflexionMemory.retrieveRelevant() INNER JOINs this table.
|
|
278
|
+
// Reuse the source embedding so every distilled episode is
|
|
279
|
+
// immediately retrievable without an embedding-model call.
|
|
280
|
+
insEpisodeEmbedding.run(epId, Buffer.from(Float32Array.from(embVec).buffer));
|
|
281
|
+
report.episodeEmbeddings++;
|
|
205
282
|
const patInfo = insPattern.run(taskType, approach, successRate, uses, avgReward, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
|
|
206
283
|
const patternId = Number(patInfo.lastInsertRowid);
|
|
207
284
|
report.patterns++;
|
|
@@ -217,7 +294,6 @@ export async function runDistillation(options) {
|
|
|
217
294
|
// rowids). Explicitly typed co-occurrence / proxy-tier / non-
|
|
218
295
|
// promoted: may rank retrieval, must NOT justify autonomous action
|
|
219
296
|
// (ADR-174 edge contract).
|
|
220
|
-
const epId = Number(epInfo.lastInsertRowid);
|
|
221
297
|
if (prevEpId !== null) {
|
|
222
298
|
insEdge.run(prevEpId, 'episode', epId, 'episode', 0, 0.3, 'co-occurrence', JSON.stringify({
|
|
223
299
|
edge_type: 'cooccurrence',
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-330 — Adaptive Pheromone Swarm Consensus.
|
|
3
|
+
*
|
|
4
|
+
* This is a scheduling eligibility layer, not an agent terminator. It learns a
|
|
5
|
+
* bounded EMA signal per agent, compares agents against role-local baselines,
|
|
6
|
+
* and may suspend them from future dispatch. Coordinators and other protected
|
|
7
|
+
* roles remain eligible, quorum is never crossed, pruning per round is capped,
|
|
8
|
+
* and exploration periodically reactivates a suspended agent.
|
|
9
|
+
*/
|
|
10
|
+
export declare const APSC_SCHEMA = "ruflo.apsc-state/v1";
|
|
11
|
+
export interface ApscConfig {
|
|
12
|
+
alpha: number;
|
|
13
|
+
beta: number;
|
|
14
|
+
gamma: number;
|
|
15
|
+
emaDecay: number;
|
|
16
|
+
pruningFactor: number;
|
|
17
|
+
reactivationThreshold: number;
|
|
18
|
+
minActiveAgents: number;
|
|
19
|
+
minSamples: number;
|
|
20
|
+
maxSuspendFraction: number;
|
|
21
|
+
explorationRate: number;
|
|
22
|
+
dryRun: boolean;
|
|
23
|
+
protectedRoles: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface ApscAgentState {
|
|
26
|
+
agentId: string;
|
|
27
|
+
role: string;
|
|
28
|
+
status: 'active' | 'suspended';
|
|
29
|
+
samples: number;
|
|
30
|
+
rawScore: number;
|
|
31
|
+
normalizedScore: number;
|
|
32
|
+
emaScore: number;
|
|
33
|
+
lastUpdatedAt: string;
|
|
34
|
+
lastDecision?: 'keep' | 'would-suspend' | 'suspend' | 'reactivate' | 'explore';
|
|
35
|
+
}
|
|
36
|
+
export interface ApscState {
|
|
37
|
+
schemaVersion: typeof APSC_SCHEMA;
|
|
38
|
+
config: ApscConfig;
|
|
39
|
+
round: number;
|
|
40
|
+
threshold: number;
|
|
41
|
+
roleBaselines: Record<string, number>;
|
|
42
|
+
agents: Record<string, ApscAgentState>;
|
|
43
|
+
}
|
|
44
|
+
export interface ApscSignal {
|
|
45
|
+
agentId: string;
|
|
46
|
+
role: string;
|
|
47
|
+
taskSuccess: number;
|
|
48
|
+
normalizedLatency: number;
|
|
49
|
+
consensusAlignment: number;
|
|
50
|
+
}
|
|
51
|
+
export interface ApscDecision {
|
|
52
|
+
agentId: string;
|
|
53
|
+
score: number;
|
|
54
|
+
normalizedScore: number;
|
|
55
|
+
emaScore: number;
|
|
56
|
+
threshold: number;
|
|
57
|
+
action: 'keep' | 'would-suspend' | 'suspend' | 'reactivate' | 'explore';
|
|
58
|
+
applied: boolean;
|
|
59
|
+
reason: string;
|
|
60
|
+
activeAgents: number;
|
|
61
|
+
suspendedAgents: number;
|
|
62
|
+
}
|
|
63
|
+
export declare const DEFAULT_APSC_CONFIG: ApscConfig;
|
|
64
|
+
export declare function normalizeApscConfig(input?: Partial<ApscConfig>): ApscConfig;
|
|
65
|
+
export declare function createApscState(config?: Partial<ApscConfig>): ApscState;
|
|
66
|
+
export declare function recordApscSignal(current: ApscState, signal: ApscSignal, now?: number): {
|
|
67
|
+
state: ApscState;
|
|
68
|
+
decision: ApscDecision;
|
|
69
|
+
};
|
|
70
|
+
export declare function isApscAgentEligible(state: ApscState | undefined, agentId: string): boolean;
|
|
71
|
+
//# sourceMappingURL=pheromone-adaptive.d.ts.map
|