synergyspec-selfevolving 2.1.5 → 2.1.7
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/dist/commands/learn.js +80 -24
- package/dist/commands/self-evolution-dream.d.ts +15 -1
- package/dist/commands/self-evolution-dream.js +111 -6
- package/dist/commands/self-evolution-episode.d.ts +3 -0
- package/dist/commands/self-evolution-episode.js +157 -108
- package/dist/commands/workflow/status.js +4 -0
- package/dist/core/archive.js +17 -9
- package/dist/core/change-readiness.d.ts +16 -1
- package/dist/core/change-readiness.js +441 -15
- package/dist/core/fitness/loss.d.ts +3 -5
- package/dist/core/fitness/loss.js +2 -2
- package/dist/core/fitness/test-metrics.d.ts +1 -0
- package/dist/core/fitness/test-metrics.js +49 -0
- package/dist/core/learn.js +129 -11
- package/dist/core/migration.d.ts +6 -14
- package/dist/core/migration.js +63 -21
- package/dist/core/runner-evidence.d.ts +53 -0
- package/dist/core/runner-evidence.js +613 -0
- package/dist/core/self-evolution/candidates.js +0 -2
- package/dist/core/self-evolution/dream.d.ts +57 -3
- package/dist/core/self-evolution/dream.js +480 -9
- package/dist/core/self-evolution/episode-orchestrator.d.ts +2 -0
- package/dist/core/self-evolution/episode-orchestrator.js +17 -5
- package/dist/core/self-evolution/episode-store.d.ts +5 -0
- package/dist/core/self-evolution/episode-store.js +6 -2
- package/dist/core/self-evolution/evolving-agent.d.ts +33 -4
- package/dist/core/self-evolution/evolving-agent.js +138 -11
- package/dist/core/self-evolution/host-harness.d.ts +35 -12
- package/dist/core/self-evolution/host-harness.js +188 -49
- package/dist/core/self-evolution/reward-aggregator.js +2 -2
- package/dist/core/templates/workflows/archive-change.js +18 -18
- package/dist/core/templates/workflows/dream.js +57 -47
- package/dist/core/templates/workflows/learn.js +7 -5
- package/dist/core/templates/workflows/run-tests.js +48 -29
- package/dist/core/templates/workflows/self-evolving.js +11 -8
- package/dist/core/trajectory/facts.d.ts +1 -1
- package/dist/core/trajectory/registry.js +39 -8
- package/package.json +1 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { type CanonicalTargetKind } from './canonical-targets.js';
|
|
2
|
-
import { type CanonicalCandidate } from './candidates.js';
|
|
1
|
+
import { type CanonicalTarget, type CanonicalTargetKind } from './canonical-targets.js';
|
|
2
|
+
import { type CandidateEdit, type CanonicalCandidate } from './candidates.js';
|
|
3
|
+
import { type ResolvedTargetFile } from './local-targets.js';
|
|
4
|
+
import { type PromotionApplyResult } from './promote.js';
|
|
5
|
+
import { type StaticGateResult } from './candidate-gates.js';
|
|
6
|
+
import type { TargetEvolutionPolicy } from './target-evolution.js';
|
|
3
7
|
export declare const DREAM_RUN_SCHEMA_VERSION: 1;
|
|
4
8
|
export type DreamEvidenceKind = 'change' | 'trajectory' | 'episode-diagnosis' | 'policy-ledger' | 'reject-buffer' | 'candidate-verdict';
|
|
5
9
|
export interface DreamEvidenceItem {
|
|
@@ -39,6 +43,43 @@ export interface DreamTargetProposal {
|
|
|
39
43
|
riskLevel: CanonicalCandidate['riskLevel'];
|
|
40
44
|
changedFiles: string[];
|
|
41
45
|
}
|
|
46
|
+
export interface DreamOptimizerInput {
|
|
47
|
+
projectRoot: string;
|
|
48
|
+
proposal: DreamTargetProposal;
|
|
49
|
+
target: CanonicalTarget;
|
|
50
|
+
evidence: DreamEvidenceItem[];
|
|
51
|
+
files: ResolvedTargetFile[];
|
|
52
|
+
}
|
|
53
|
+
export interface DreamOptimizerOutput {
|
|
54
|
+
edits: CandidateEdit[];
|
|
55
|
+
rationale: string;
|
|
56
|
+
validationPlan?: string;
|
|
57
|
+
riskReport?: string;
|
|
58
|
+
}
|
|
59
|
+
export type DreamOptimizer = (input: DreamOptimizerInput) => Promise<DreamOptimizerOutput>;
|
|
60
|
+
export interface DreamPolicyEditSynthesizerInput {
|
|
61
|
+
candidateId: string;
|
|
62
|
+
targetId: string;
|
|
63
|
+
acceptedBy: string;
|
|
64
|
+
proposalMd: string;
|
|
65
|
+
rationaleMd: string;
|
|
66
|
+
evalPlanMd: string;
|
|
67
|
+
riskReportMd: string;
|
|
68
|
+
}
|
|
69
|
+
export type DreamPolicyEditSynthesizer = (input: DreamPolicyEditSynthesizerInput) => Promise<DreamOptimizerOutput>;
|
|
70
|
+
export type DreamPolicyUpdateOutcome = 'promoted' | 'refused-no-local-files' | 'refused-optimizer' | 'refused-static-gate' | 'error-runtime';
|
|
71
|
+
export interface DreamPolicyUpdateResult {
|
|
72
|
+
candidateId: string;
|
|
73
|
+
targetId: string;
|
|
74
|
+
outcome: DreamPolicyUpdateOutcome;
|
|
75
|
+
promoted: boolean;
|
|
76
|
+
gatePassed: boolean;
|
|
77
|
+
promotedFiles: string[];
|
|
78
|
+
policyVersion: number | null;
|
|
79
|
+
reason?: string;
|
|
80
|
+
gate?: StaticGateResult;
|
|
81
|
+
applied?: PromotionApplyResult;
|
|
82
|
+
}
|
|
42
83
|
export interface DreamPreview {
|
|
43
84
|
schemaVersion: typeof DREAM_RUN_SCHEMA_VERSION;
|
|
44
85
|
mode: 'preview';
|
|
@@ -47,7 +88,7 @@ export interface DreamPreview {
|
|
|
47
88
|
proposals: DreamTargetProposal[];
|
|
48
89
|
safety: {
|
|
49
90
|
createsNewSkills: false;
|
|
50
|
-
autoPromotes:
|
|
91
|
+
autoPromotes: boolean;
|
|
51
92
|
directlyWritesPolicy: false;
|
|
52
93
|
allowedTargetKinds: readonly CanonicalTargetKind[];
|
|
53
94
|
};
|
|
@@ -57,6 +98,7 @@ export interface DreamRunManifest extends Omit<DreamPreview, 'mode'> {
|
|
|
57
98
|
runId: string;
|
|
58
99
|
runDir: string;
|
|
59
100
|
candidateIds: string[];
|
|
101
|
+
policyUpdates?: DreamPolicyUpdateResult[];
|
|
60
102
|
}
|
|
61
103
|
export interface BuildDreamPreviewOptions {
|
|
62
104
|
projectRoot: string;
|
|
@@ -66,6 +108,17 @@ export interface BuildDreamPreviewOptions {
|
|
|
66
108
|
}
|
|
67
109
|
export interface WriteDreamRunOptions extends BuildDreamPreviewOptions {
|
|
68
110
|
preview?: DreamPreview;
|
|
111
|
+
applyPolicy?: boolean;
|
|
112
|
+
optimizer?: DreamOptimizer;
|
|
113
|
+
targetPolicy?: TargetEvolutionPolicy;
|
|
114
|
+
}
|
|
115
|
+
export interface RunAcceptedDreamPolicyUpdateOptions {
|
|
116
|
+
projectRoot: string;
|
|
117
|
+
candidateId: string;
|
|
118
|
+
acceptedBy: string;
|
|
119
|
+
synthesizeEdits?: DreamPolicyEditSynthesizer;
|
|
120
|
+
targetPolicy?: TargetEvolutionPolicy;
|
|
121
|
+
now?: () => Date;
|
|
69
122
|
}
|
|
70
123
|
export declare function resolveDreamRunsDir(projectRoot: string): string;
|
|
71
124
|
export declare function resolveDreamRunDir(projectRoot: string, runId: string): string;
|
|
@@ -73,6 +126,7 @@ export declare function collectDreamEvidence(projectRoot: string): Promise<Dream
|
|
|
73
126
|
export declare function buildDreamPreview(opts: BuildDreamPreviewOptions): Promise<DreamPreview>;
|
|
74
127
|
export declare function writeDreamRun(opts: WriteDreamRunOptions): Promise<DreamRunManifest>;
|
|
75
128
|
export declare function readDreamRun(projectRoot: string, runId: string): Promise<DreamRunManifest>;
|
|
129
|
+
export declare function runAcceptedDreamPolicyUpdate(opts: RunAcceptedDreamPolicyUpdateOptions): Promise<DreamPolicyUpdateResult>;
|
|
76
130
|
export declare function renderDreamPreview(preview: DreamPreview): string;
|
|
77
131
|
export declare function renderDreamRun(manifest: DreamRunManifest): string;
|
|
78
132
|
//# sourceMappingURL=dream.d.ts.map
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Supervised Learning Dream: an offline, batch proposal lane for existing
|
|
3
3
|
* self-evolution targets. Dream reads accumulated evidence and writes review
|
|
4
|
-
* candidates.
|
|
4
|
+
* candidates by default. The explicit apply mode turns a Dream proposal into a
|
|
5
|
+
* concrete bounded edit, runs the normal static gate, and promotes through the
|
|
6
|
+
* existing candidate promotion channel.
|
|
5
7
|
*/
|
|
6
8
|
import { promises as fs } from 'node:fs';
|
|
7
9
|
import * as path from 'node:path';
|
|
8
10
|
import * as crypto from 'node:crypto';
|
|
9
11
|
import { CANONICAL_TARGETS, lookupCanonicalTarget, } from './canonical-targets.js';
|
|
10
|
-
import { generateCandidateId, resolveCandidateRepo, writeCandidatePackage, } from './candidates.js';
|
|
12
|
+
import { EDITS_JSON_FILE, generateCandidateId, readCandidatePackage, resolveCandidateRepo, writeCandidatePackage, } from './candidates.js';
|
|
13
|
+
import { renderUnifiedDiff, validateCandidateEdits, } from './edits-contract.js';
|
|
14
|
+
import { resolveTargetLocalFiles, } from './local-targets.js';
|
|
15
|
+
import { applyCandidatePromotion, } from './promote.js';
|
|
16
|
+
import { runStaticCandidateGate, } from './candidate-gates.js';
|
|
17
|
+
import { currentPolicyVersion } from './policy/policy-store.js';
|
|
11
18
|
export const DREAM_RUN_SCHEMA_VERSION = 1;
|
|
12
19
|
const DREAM_SUBDIR = path.join('.synergyspec-selfevolving', 'self-evolution', 'dream-runs');
|
|
13
20
|
const DEFAULT_ALLOWED_TARGET_KINDS = Object.freeze([
|
|
@@ -64,6 +71,7 @@ export async function buildDreamPreview(opts) {
|
|
|
64
71
|
export async function writeDreamRun(opts) {
|
|
65
72
|
const now = opts.now ?? (() => new Date());
|
|
66
73
|
const preview = opts.preview ?? (await buildDreamPreview(opts));
|
|
74
|
+
const applyPolicy = opts.applyPolicy === true;
|
|
67
75
|
const createdAt = preview.createdAt || now().toISOString();
|
|
68
76
|
const entropy = [
|
|
69
77
|
createdAt,
|
|
@@ -79,21 +87,62 @@ export async function writeDreamRun(opts) {
|
|
|
79
87
|
await fs.mkdir(tmpDir, { recursive: true });
|
|
80
88
|
const candidateIds = [];
|
|
81
89
|
const createdCandidateDirs = [];
|
|
90
|
+
const policyUpdates = [];
|
|
82
91
|
const candidateRepo = resolveCandidateRepo(opts.projectRoot);
|
|
83
92
|
try {
|
|
93
|
+
const evidence = applyPolicy ? await collectDreamEvidence(opts.projectRoot) : [];
|
|
94
|
+
const evidenceById = new Map(evidence.map((item) => [item.id, item]));
|
|
84
95
|
for (const proposal of preview.proposals) {
|
|
85
96
|
const candidateId = generateCandidateId({
|
|
86
97
|
now: new Date(createdAt),
|
|
87
98
|
entropy: `${runId}:${proposal.targetId}`,
|
|
88
99
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
let pkg;
|
|
101
|
+
let update;
|
|
102
|
+
if (applyPolicy) {
|
|
103
|
+
const applied = await buildDreamApplyCandidatePackage({
|
|
104
|
+
projectRoot: opts.projectRoot,
|
|
105
|
+
candidateId,
|
|
106
|
+
createdAt,
|
|
107
|
+
proposal,
|
|
108
|
+
evidence: proposal.evidenceIds
|
|
109
|
+
.map((id) => evidenceById.get(id))
|
|
110
|
+
.filter((item) => item !== undefined),
|
|
111
|
+
optimizer: opts.optimizer ?? defaultDreamOptimizer,
|
|
112
|
+
});
|
|
113
|
+
pkg = applied.pkg;
|
|
114
|
+
update = {
|
|
115
|
+
candidateId,
|
|
116
|
+
targetId: proposal.targetId,
|
|
117
|
+
outcome: applied.initialOutcome,
|
|
118
|
+
promoted: false,
|
|
119
|
+
gatePassed: false,
|
|
120
|
+
promotedFiles: [],
|
|
121
|
+
policyVersion: null,
|
|
122
|
+
...(applied.reason ? { reason: applied.reason } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
pkg = buildDreamCandidatePackage({
|
|
127
|
+
candidateId,
|
|
128
|
+
createdAt,
|
|
129
|
+
proposal,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
94
132
|
const written = await writeCandidatePackage(candidateRepo, pkg);
|
|
95
133
|
createdCandidateDirs.push(written.candidateDir);
|
|
96
134
|
candidateIds.push(candidateId);
|
|
135
|
+
if (applyPolicy && update) {
|
|
136
|
+
if (pkg.edits && pkg.edits.length > 0) {
|
|
137
|
+
update = await runDreamPolicyUpdate({
|
|
138
|
+
repoRoot: opts.projectRoot,
|
|
139
|
+
candidateId,
|
|
140
|
+
targetId: proposal.targetId,
|
|
141
|
+
targetPolicy: opts.targetPolicy,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
policyUpdates.push(update);
|
|
145
|
+
}
|
|
97
146
|
}
|
|
98
147
|
const manifest = {
|
|
99
148
|
...preview,
|
|
@@ -101,6 +150,11 @@ export async function writeDreamRun(opts) {
|
|
|
101
150
|
runId,
|
|
102
151
|
runDir,
|
|
103
152
|
candidateIds,
|
|
153
|
+
safety: {
|
|
154
|
+
...preview.safety,
|
|
155
|
+
autoPromotes: applyPolicy,
|
|
156
|
+
},
|
|
157
|
+
...(applyPolicy ? { policyUpdates } : {}),
|
|
104
158
|
};
|
|
105
159
|
await fs.writeFile(path.join(tmpDir, 'dream-run.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
106
160
|
await fs.rename(tmpDir, runDir);
|
|
@@ -108,7 +162,9 @@ export async function writeDreamRun(opts) {
|
|
|
108
162
|
}
|
|
109
163
|
catch (err) {
|
|
110
164
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
111
|
-
|
|
165
|
+
if (!applyPolicy) {
|
|
166
|
+
await Promise.all(createdCandidateDirs.map((candidateDir) => fs.rm(candidateDir, { recursive: true, force: true })));
|
|
167
|
+
}
|
|
112
168
|
throw err;
|
|
113
169
|
}
|
|
114
170
|
}
|
|
@@ -117,6 +173,113 @@ export async function readDreamRun(projectRoot, runId) {
|
|
|
117
173
|
const raw = await fs.readFile(path.join(runDir, 'dream-run.json'), 'utf8');
|
|
118
174
|
return JSON.parse(raw);
|
|
119
175
|
}
|
|
176
|
+
export async function runAcceptedDreamPolicyUpdate(opts) {
|
|
177
|
+
const layout = resolveCandidateRepo(opts.projectRoot);
|
|
178
|
+
const pkg = await readCandidatePackage(layout, opts.candidateId);
|
|
179
|
+
const targetId = pkg.candidate.targetIds[0] ?? '(none)';
|
|
180
|
+
if (pkg.candidate.source !== 'dream-batch') {
|
|
181
|
+
return {
|
|
182
|
+
candidateId: opts.candidateId,
|
|
183
|
+
targetId,
|
|
184
|
+
outcome: 'error-runtime',
|
|
185
|
+
promoted: false,
|
|
186
|
+
gatePassed: false,
|
|
187
|
+
promotedFiles: [],
|
|
188
|
+
policyVersion: targetId === '(none)' ? null : await currentPolicyVersion(opts.projectRoot, targetId),
|
|
189
|
+
reason: `candidate source is ${pkg.candidate.source}, expected dream-batch`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (targetId === '(none)') {
|
|
193
|
+
return {
|
|
194
|
+
candidateId: opts.candidateId,
|
|
195
|
+
targetId,
|
|
196
|
+
outcome: 'error-runtime',
|
|
197
|
+
promoted: false,
|
|
198
|
+
gatePassed: false,
|
|
199
|
+
promotedFiles: [],
|
|
200
|
+
policyVersion: null,
|
|
201
|
+
reason: 'Dream candidate has no targetIds entry',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const target = lookupCanonicalTarget(targetId);
|
|
205
|
+
if (!target) {
|
|
206
|
+
return {
|
|
207
|
+
candidateId: opts.candidateId,
|
|
208
|
+
targetId,
|
|
209
|
+
outcome: 'error-runtime',
|
|
210
|
+
promoted: false,
|
|
211
|
+
gatePassed: false,
|
|
212
|
+
promotedFiles: [],
|
|
213
|
+
policyVersion: null,
|
|
214
|
+
reason: `Unknown canonical target: ${targetId}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const resolved = await resolveTargetLocalFiles(targetId, opts.projectRoot);
|
|
218
|
+
if (resolved.files.length === 0) {
|
|
219
|
+
return {
|
|
220
|
+
candidateId: opts.candidateId,
|
|
221
|
+
targetId,
|
|
222
|
+
outcome: 'refused-no-local-files',
|
|
223
|
+
promoted: false,
|
|
224
|
+
gatePassed: false,
|
|
225
|
+
promotedFiles: [],
|
|
226
|
+
policyVersion: await currentPolicyVersion(opts.projectRoot, targetId),
|
|
227
|
+
reason: `no local editable file for target ${targetId}`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const synthesizeEdits = opts.synthesizeEdits ?? defaultAcceptedDreamPolicyEditSynthesizer(resolved.files);
|
|
231
|
+
let output;
|
|
232
|
+
try {
|
|
233
|
+
output = await synthesizeEdits({
|
|
234
|
+
candidateId: opts.candidateId,
|
|
235
|
+
targetId,
|
|
236
|
+
acceptedBy: opts.acceptedBy,
|
|
237
|
+
proposalMd: pkg.proposalMd,
|
|
238
|
+
rationaleMd: pkg.rationaleMd,
|
|
239
|
+
evalPlanMd: pkg.evalPlanMd,
|
|
240
|
+
riskReportMd: pkg.riskReportMd,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
return {
|
|
245
|
+
candidateId: opts.candidateId,
|
|
246
|
+
targetId,
|
|
247
|
+
outcome: 'refused-optimizer',
|
|
248
|
+
promoted: false,
|
|
249
|
+
gatePassed: false,
|
|
250
|
+
promotedFiles: [],
|
|
251
|
+
policyVersion: await currentPolicyVersion(opts.projectRoot, targetId),
|
|
252
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const prepared = prepareDreamEditsForPackage({
|
|
256
|
+
pkg,
|
|
257
|
+
targetId,
|
|
258
|
+
files: resolved.files,
|
|
259
|
+
output,
|
|
260
|
+
acceptedBy: opts.acceptedBy,
|
|
261
|
+
now: opts.now ?? (() => new Date()),
|
|
262
|
+
});
|
|
263
|
+
if ('reason' in prepared) {
|
|
264
|
+
return {
|
|
265
|
+
candidateId: opts.candidateId,
|
|
266
|
+
targetId,
|
|
267
|
+
outcome: 'refused-optimizer',
|
|
268
|
+
promoted: false,
|
|
269
|
+
gatePassed: false,
|
|
270
|
+
promotedFiles: [],
|
|
271
|
+
policyVersion: await currentPolicyVersion(opts.projectRoot, targetId),
|
|
272
|
+
reason: prepared.reason,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
await rewriteDreamCandidatePackage(layout, prepared.pkg);
|
|
276
|
+
return runDreamPolicyUpdate({
|
|
277
|
+
repoRoot: opts.projectRoot,
|
|
278
|
+
candidateId: opts.candidateId,
|
|
279
|
+
targetId,
|
|
280
|
+
targetPolicy: opts.targetPolicy,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
120
283
|
export function renderDreamPreview(preview) {
|
|
121
284
|
const lines = [
|
|
122
285
|
'Supervised Learning Dream preview',
|
|
@@ -145,7 +308,16 @@ export function renderDreamRun(manifest) {
|
|
|
145
308
|
if (manifest.candidateIds.length === 0) {
|
|
146
309
|
lines.push('No candidates were written.');
|
|
147
310
|
}
|
|
148
|
-
|
|
311
|
+
if (manifest.policyUpdates && manifest.policyUpdates.length > 0) {
|
|
312
|
+
const promoted = manifest.policyUpdates.filter((update) => update.promoted);
|
|
313
|
+
lines.push(`Policy updates: ${promoted.length}/${manifest.policyUpdates.length} promoted`);
|
|
314
|
+
for (const update of manifest.policyUpdates) {
|
|
315
|
+
lines.push(`- ${update.candidateId}: ${update.outcome}${update.reason ? ` (${update.reason})` : ''}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
lines.push('Next: review the proposal, author concrete bounded edits through the existing review channel, then run gate/promote or reject.');
|
|
320
|
+
}
|
|
149
321
|
return lines.join('\n');
|
|
150
322
|
}
|
|
151
323
|
function buildDreamCandidatePackage(args) {
|
|
@@ -217,6 +389,305 @@ function buildDreamCandidatePackage(args) {
|
|
|
217
389
|
riskReportMd,
|
|
218
390
|
};
|
|
219
391
|
}
|
|
392
|
+
async function buildDreamApplyCandidatePackage(args) {
|
|
393
|
+
const target = lookupCanonicalTarget(args.proposal.targetId);
|
|
394
|
+
if (!target) {
|
|
395
|
+
return {
|
|
396
|
+
pkg: buildDreamCandidatePackage(args),
|
|
397
|
+
initialOutcome: 'error-runtime',
|
|
398
|
+
reason: `Unknown canonical target: ${args.proposal.targetId}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const resolved = await resolveTargetLocalFiles(target.id, args.projectRoot);
|
|
402
|
+
if (resolved.files.length === 0) {
|
|
403
|
+
return {
|
|
404
|
+
pkg: buildDreamCandidatePackage(args),
|
|
405
|
+
initialOutcome: 'refused-no-local-files',
|
|
406
|
+
reason: `no local editable file for target ${target.id}`,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
let optimized;
|
|
410
|
+
try {
|
|
411
|
+
optimized = await args.optimizer({
|
|
412
|
+
projectRoot: args.projectRoot,
|
|
413
|
+
proposal: args.proposal,
|
|
414
|
+
target,
|
|
415
|
+
evidence: args.evidence,
|
|
416
|
+
files: resolved.files,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
return {
|
|
421
|
+
pkg: buildDreamCandidatePackage(args),
|
|
422
|
+
initialOutcome: 'refused-optimizer',
|
|
423
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const basePkg = buildDreamCandidatePackage(args);
|
|
427
|
+
const prepared = prepareDreamEditsForPackage({
|
|
428
|
+
pkg: basePkg,
|
|
429
|
+
targetId: target.id,
|
|
430
|
+
files: resolved.files,
|
|
431
|
+
output: optimized,
|
|
432
|
+
acceptedBy: 'dream run --apply',
|
|
433
|
+
now: () => new Date(args.createdAt),
|
|
434
|
+
});
|
|
435
|
+
if ('reason' in prepared) {
|
|
436
|
+
return {
|
|
437
|
+
pkg: buildDreamCandidatePackage(args),
|
|
438
|
+
initialOutcome: 'refused-optimizer',
|
|
439
|
+
reason: prepared.reason,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
pkg: prepared.pkg,
|
|
444
|
+
initialOutcome: 'promoted',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async function runDreamPolicyUpdate(args) {
|
|
448
|
+
const layout = resolveCandidateRepo(args.repoRoot);
|
|
449
|
+
let gate;
|
|
450
|
+
try {
|
|
451
|
+
gate = await runStaticCandidateGate(layout, args.candidateId, {
|
|
452
|
+
applyTransition: true,
|
|
453
|
+
targetPolicy: args.targetPolicy,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
return {
|
|
458
|
+
candidateId: args.candidateId,
|
|
459
|
+
targetId: args.targetId,
|
|
460
|
+
outcome: 'error-runtime',
|
|
461
|
+
promoted: false,
|
|
462
|
+
gatePassed: false,
|
|
463
|
+
promotedFiles: [],
|
|
464
|
+
policyVersion: await currentPolicyVersion(args.repoRoot, args.targetId),
|
|
465
|
+
reason: `static gate error: ${err instanceof Error ? err.message : String(err)}`,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (!gate.passed) {
|
|
469
|
+
return {
|
|
470
|
+
candidateId: args.candidateId,
|
|
471
|
+
targetId: args.targetId,
|
|
472
|
+
outcome: 'refused-static-gate',
|
|
473
|
+
promoted: false,
|
|
474
|
+
gatePassed: false,
|
|
475
|
+
promotedFiles: [],
|
|
476
|
+
policyVersion: await currentPolicyVersion(args.repoRoot, args.targetId),
|
|
477
|
+
gate,
|
|
478
|
+
reason: gate.findings
|
|
479
|
+
.filter((finding) => finding.severity === 'error')
|
|
480
|
+
.map((finding) => finding.message)
|
|
481
|
+
.join('; ') || 'static gate failed',
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const applied = await applyCandidatePromotion(layout, args.candidateId, {
|
|
486
|
+
repoRoot: args.repoRoot,
|
|
487
|
+
policy: args.targetPolicy,
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
candidateId: args.candidateId,
|
|
491
|
+
targetId: args.targetId,
|
|
492
|
+
outcome: 'promoted',
|
|
493
|
+
promoted: true,
|
|
494
|
+
gatePassed: true,
|
|
495
|
+
promotedFiles: applied.appliedFiles.map((file) => file.file),
|
|
496
|
+
policyVersion: await currentPolicyVersion(args.repoRoot, args.targetId),
|
|
497
|
+
gate,
|
|
498
|
+
applied,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
return {
|
|
503
|
+
candidateId: args.candidateId,
|
|
504
|
+
targetId: args.targetId,
|
|
505
|
+
outcome: 'error-runtime',
|
|
506
|
+
promoted: false,
|
|
507
|
+
gatePassed: true,
|
|
508
|
+
promotedFiles: [],
|
|
509
|
+
policyVersion: await currentPolicyVersion(args.repoRoot, args.targetId),
|
|
510
|
+
gate,
|
|
511
|
+
reason: `promotion error: ${err instanceof Error ? err.message : String(err)}`,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function prepareDreamEditsForPackage(args) {
|
|
516
|
+
let edits;
|
|
517
|
+
try {
|
|
518
|
+
edits = validateCandidateEdits(args.output.edits, args.files.map((file) => file.relPath));
|
|
519
|
+
}
|
|
520
|
+
catch (err) {
|
|
521
|
+
return { reason: err instanceof Error ? err.message : String(err) };
|
|
522
|
+
}
|
|
523
|
+
const currentByRel = new Map(args.files.map((file) => [file.relPath, file.content]));
|
|
524
|
+
const changed = edits.filter((edit) => edit.content !== currentByRel.get(edit.relPath));
|
|
525
|
+
if (changed.length === 0) {
|
|
526
|
+
return { reason: 'optimizer produced no byte-changing edits' };
|
|
527
|
+
}
|
|
528
|
+
const diffPatch = changed
|
|
529
|
+
.map((edit) => renderUnifiedDiff(edit.relPath, currentByRel.get(edit.relPath) ?? '', edit.content))
|
|
530
|
+
.join('\n');
|
|
531
|
+
const rationaleMd = [
|
|
532
|
+
'# Rationale',
|
|
533
|
+
'',
|
|
534
|
+
`Because ${args.acceptedBy} accepted the Dream optimizer brief, this bounded edit materializes the supervised batch-learning proposal for ${args.targetId}.`,
|
|
535
|
+
'',
|
|
536
|
+
args.output.rationale,
|
|
537
|
+
'',
|
|
538
|
+
].join('\n');
|
|
539
|
+
return {
|
|
540
|
+
pkg: {
|
|
541
|
+
...args.pkg,
|
|
542
|
+
candidate: {
|
|
543
|
+
...args.pkg.candidate,
|
|
544
|
+
updatedAt: args.now().toISOString(),
|
|
545
|
+
changedFiles: changed.map((edit) => edit.relPath),
|
|
546
|
+
rollbackPlan: 'Use self-evolution promote --rollback for this candidate; promotion writes a rollback snapshot before changing policy files.',
|
|
547
|
+
},
|
|
548
|
+
diffPatch: `${diffPatch}\n`,
|
|
549
|
+
rationaleMd,
|
|
550
|
+
evalPlanMd: args.output.validationPlan ??
|
|
551
|
+
[
|
|
552
|
+
'# Evaluation Plan',
|
|
553
|
+
'',
|
|
554
|
+
args.pkg.evalPlanMd,
|
|
555
|
+
'',
|
|
556
|
+
'- Static candidate gate must pass before promotion.',
|
|
557
|
+
'- Held-out replay should cover the cited Dream evidence before relying on the update.',
|
|
558
|
+
'',
|
|
559
|
+
].join('\n'),
|
|
560
|
+
riskReportMd: args.output.riskReport ??
|
|
561
|
+
[
|
|
562
|
+
'# Risk Report',
|
|
563
|
+
'',
|
|
564
|
+
args.pkg.riskReportMd,
|
|
565
|
+
'',
|
|
566
|
+
'- Dream apply mode promotes only through the existing candidate promotion channel.',
|
|
567
|
+
'- Rollback metadata is written before any canonical policy file is changed.',
|
|
568
|
+
'- New skill creation remains out of scope.',
|
|
569
|
+
'',
|
|
570
|
+
].join('\n'),
|
|
571
|
+
edits: changed,
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async function rewriteDreamCandidatePackage(layout, pkg) {
|
|
576
|
+
const dir = path.join(layout.baseDir, pkg.candidate.id);
|
|
577
|
+
await Promise.all([
|
|
578
|
+
fs.writeFile(path.join(dir, 'candidate.json'), `${JSON.stringify(pkg.candidate, null, 2)}\n`, 'utf8'),
|
|
579
|
+
fs.writeFile(path.join(dir, 'proposal.md'), pkg.proposalMd, 'utf8'),
|
|
580
|
+
fs.writeFile(path.join(dir, 'diff.patch'), pkg.diffPatch, 'utf8'),
|
|
581
|
+
fs.writeFile(path.join(dir, 'rationale.md'), pkg.rationaleMd, 'utf8'),
|
|
582
|
+
fs.writeFile(path.join(dir, 'eval-plan.md'), pkg.evalPlanMd, 'utf8'),
|
|
583
|
+
fs.writeFile(path.join(dir, 'risk-report.md'), pkg.riskReportMd, 'utf8'),
|
|
584
|
+
]);
|
|
585
|
+
if (pkg.edits && pkg.edits.length > 0) {
|
|
586
|
+
await fs.writeFile(path.join(dir, EDITS_JSON_FILE), `${JSON.stringify(pkg.edits, null, 2)}\n`, 'utf8');
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
await fs.rm(path.join(dir, EDITS_JSON_FILE), { force: true });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function defaultAcceptedDreamPolicyEditSynthesizer(files) {
|
|
593
|
+
return async (input) => {
|
|
594
|
+
const file = files.find((candidate) => candidate.content.trim().length > 0) ?? files[0];
|
|
595
|
+
if (!file) {
|
|
596
|
+
return {
|
|
597
|
+
edits: [],
|
|
598
|
+
rationale: `Dream found no editable files for ${input.targetId}.`,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
const note = renderDreamPolicyNote({
|
|
602
|
+
targetId: input.targetId,
|
|
603
|
+
evidenceIds: [],
|
|
604
|
+
expectedBenefit: `Accepted by ${input.acceptedBy}`,
|
|
605
|
+
validationPlan: 'Static gate plus held-out replay before relying on the updated policy.',
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
edits: [{ relPath: file.relPath, content: applyDreamPolicyNote(file, note) }],
|
|
609
|
+
rationale: 'Accepted Dream optimizer brief; materialize it as one bounded policy-file update with rollback support.',
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async function defaultDreamOptimizer(input) {
|
|
614
|
+
const file = input.files.find((candidate) => candidate.content.trim().length > 0) ?? input.files[0];
|
|
615
|
+
if (!file) {
|
|
616
|
+
return {
|
|
617
|
+
edits: [],
|
|
618
|
+
rationale: `Dream found no editable files for ${input.target.id}.`,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const evidenceIds = input.proposal.evidenceIds.slice(0, 6);
|
|
622
|
+
const note = renderDreamPolicyNote({
|
|
623
|
+
targetId: input.target.id,
|
|
624
|
+
evidenceIds,
|
|
625
|
+
expectedBenefit: input.proposal.expectedBenefit,
|
|
626
|
+
validationPlan: input.proposal.validationPlan,
|
|
627
|
+
});
|
|
628
|
+
const content = applyDreamPolicyNote(file, note);
|
|
629
|
+
return {
|
|
630
|
+
edits: [{ relPath: file.relPath, content }],
|
|
631
|
+
rationale: `Because ${input.target.id} repeatedly appears in accumulated Dream evidence, ` +
|
|
632
|
+
'the policy now carries a supervised batch-learning note that makes validation, held-out replay, and rollback expectations explicit for similar future work.',
|
|
633
|
+
validationPlan: [
|
|
634
|
+
'# Evaluation Plan',
|
|
635
|
+
'',
|
|
636
|
+
input.proposal.validationPlan,
|
|
637
|
+
'',
|
|
638
|
+
'- Static candidate gate must pass before promotion.',
|
|
639
|
+
'- Validate with tests or replay for the cited evidence before treating this policy update as reliable.',
|
|
640
|
+
'',
|
|
641
|
+
].join('\n'),
|
|
642
|
+
riskReport: [
|
|
643
|
+
'# Risk Report',
|
|
644
|
+
'',
|
|
645
|
+
`Risk level: ${input.proposal.riskLevel}`,
|
|
646
|
+
'',
|
|
647
|
+
'- Uses existing policy target only; no new skill is created.',
|
|
648
|
+
'- Promotion writes rollback metadata before mutating local policy files.',
|
|
649
|
+
'- If held-out replay regresses, roll back this candidate promotion.',
|
|
650
|
+
'',
|
|
651
|
+
].join('\n'),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function renderDreamPolicyNote(args) {
|
|
655
|
+
return [
|
|
656
|
+
'<!-- SS_SUPERVISED_DREAM_OPTIMIZER_START -->',
|
|
657
|
+
'**Supervised Dream Optimizer Note**',
|
|
658
|
+
'',
|
|
659
|
+
`- Target: ${args.targetId}`,
|
|
660
|
+
`- Evidence batch: ${args.evidenceIds.length > 0 ? args.evidenceIds.join(', ') : 'none'}`,
|
|
661
|
+
`- Expected benefit: ${args.expectedBenefit}`,
|
|
662
|
+
`- Validation: ${args.validationPlan}`,
|
|
663
|
+
'- Behavior: when similar friction appears, make the expected verification, recovery, and rollback check explicit before finishing the workflow.',
|
|
664
|
+
'<!-- SS_SUPERVISED_DREAM_OPTIMIZER_END -->',
|
|
665
|
+
].join('\n');
|
|
666
|
+
}
|
|
667
|
+
function applyDreamPolicyNote(file, note) {
|
|
668
|
+
const current = file.content;
|
|
669
|
+
const replaceBlock = (text) => {
|
|
670
|
+
const blockRe = /<!-- SS_SUPERVISED_DREAM_OPTIMIZER_START -->[\s\S]*?<!-- SS_SUPERVISED_DREAM_OPTIMIZER_END -->/;
|
|
671
|
+
return blockRe.test(text)
|
|
672
|
+
? text.replace(blockRe, note)
|
|
673
|
+
: `${text.replace(/\s*$/, '')}\n\n${note}\n`;
|
|
674
|
+
};
|
|
675
|
+
if (/\.ts$/.test(file.relPath)) {
|
|
676
|
+
const marker = 'const INSTRUCTIONS_BODY = `';
|
|
677
|
+
const start = current.indexOf(marker);
|
|
678
|
+
if (start >= 0) {
|
|
679
|
+
const bodyStart = start + marker.length;
|
|
680
|
+
const bodyEnd = current.indexOf('`;', bodyStart);
|
|
681
|
+
if (bodyEnd > bodyStart) {
|
|
682
|
+
const before = current.slice(0, bodyStart);
|
|
683
|
+
const body = current.slice(bodyStart, bodyEnd);
|
|
684
|
+
const after = current.slice(bodyEnd);
|
|
685
|
+
return `${before}${replaceBlock(body)}${after}`;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return replaceBlock(current);
|
|
690
|
+
}
|
|
220
691
|
function buildDreamProposals(evidence, targets, limit) {
|
|
221
692
|
if (limit === 0 || evidence.length === 0)
|
|
222
693
|
return [];
|
|
@@ -196,6 +196,7 @@ export interface RunEpisodeOptions {
|
|
|
196
196
|
}
|
|
197
197
|
export interface RunEpisodeResult {
|
|
198
198
|
episodeId: string;
|
|
199
|
+
harness?: AgentHarness;
|
|
199
200
|
/** True when the CRITIC AGENT(基线智能体 baseline agent)arm was skipped. */
|
|
200
201
|
baselineSkipped: boolean;
|
|
201
202
|
/** advantage = reward(主臂) − reward(基线臂); null when skipped or abstained. */
|
|
@@ -236,6 +237,7 @@ export interface ResumeEpisodeOptions {
|
|
|
236
237
|
}
|
|
237
238
|
export interface ResumeEpisodeResult {
|
|
238
239
|
episodeId: string;
|
|
240
|
+
harness?: AgentHarness;
|
|
239
241
|
/** The stage the episode was at when resume was called. */
|
|
240
242
|
resumedFrom: EpisodeStage;
|
|
241
243
|
/** The stage it reached after the resume re-ran the remaining steps. */
|