@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.3
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/algo/candidateAssembly.js +9 -6
- package/dist/algo/cycleEngine.d.ts +11 -0
- package/dist/algo/cycleEngine.js +12 -6
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- package/dist/algo/geneSelection.d.ts +12 -1
- package/dist/algo/geneSelection.js +24 -8
- package/dist/algo/index.d.ts +1 -0
- package/dist/algo/index.js +1 -0
- package/dist/algo/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +3 -0
- package/dist/algo/orchestrator.js +14 -2
- package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
- package/dist/assetstore/assetSidecarRecords.js +142 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +75 -0
- package/dist/assetstore/assetStoreHealth.js +277 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +318 -0
- package/dist/assetstore/assetSyncLedger.d.ts +5 -1
- package/dist/assetstore/assetSyncLedger.js +44 -64
- package/dist/assetstore/index.d.ts +2 -0
- package/dist/assetstore/index.js +2 -0
- package/dist/assetstore/localJsonl.d.ts +1 -0
- package/dist/assetstore/localJsonl.js +36 -32
- package/dist/assetstore/provenance.d.ts +13 -0
- package/dist/assetstore/provenance.js +60 -84
- package/dist/assetstore/provider.d.ts +2 -0
- package/dist/assetstore/reviewFilter.js +3 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/events/ingest.d.ts +1 -1
- package/dist/events/ingest.js +2 -0
- package/dist/events/paths.d.ts +1 -1
- package/dist/events/paths.js +2 -2
- package/dist/exec/autoExec.d.ts +6 -1
- package/dist/exec/autoExec.js +31 -0
- package/dist/exec/autonomousCycle.d.ts +2 -0
- package/dist/exec/autonomousCycle.js +5 -0
- package/dist/exec/claudeBridge.d.ts +7 -2
- package/dist/exec/claudeBridge.js +92 -14
- package/dist/exec/prompt.js +9 -0
- package/dist/exec/runnerRegistry.d.ts +56 -12
- package/dist/exec/runnerRegistry.js +272 -22
- package/dist/hub/bindings.js +12 -2
- package/dist/ops/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +10 -1
- package/dist/ops/selfUpdate.js +64 -15
- package/dist/util/fileLock.d.ts +19 -2
- package/dist/util/fileLock.js +166 -31
- package/package.json +5 -1
|
@@ -18,14 +18,15 @@ function candidateIds(geneId, assetId) {
|
|
|
18
18
|
function isBannedCandidate(banned, geneId, assetId) {
|
|
19
19
|
return candidateIds(geneId, assetId).some((id) => banned.has(id));
|
|
20
20
|
}
|
|
21
|
-
function passesInjectedCandidateGates(candidate, opts, provenance) {
|
|
21
|
+
function passesInjectedCandidateGates(candidate, opts, provenance, review) {
|
|
22
22
|
const assetId = candidate.assetId;
|
|
23
23
|
if (opts.provenance && !opts.includeUntrusted) {
|
|
24
24
|
if (!assetId || provenance?.get(assetId)?.trusted !== true)
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
27
27
|
if (opts.review) {
|
|
28
|
-
|
|
28
|
+
const reviewRecord = assetId ? review?.get(assetId) : undefined;
|
|
29
|
+
if (!assetId || (reviewRecord !== undefined && reviewRecord.state !== 'approved'))
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
32
|
return true;
|
|
@@ -80,6 +81,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
80
81
|
const banned = await computeBans(store, signals, limit);
|
|
81
82
|
const sigSet = new Set(signals);
|
|
82
83
|
const provenance = opts.provenance?.snapshot();
|
|
84
|
+
const review = opts.review?.snapshot();
|
|
83
85
|
const out = [];
|
|
84
86
|
const distilledFallback = [];
|
|
85
87
|
const antiWarnings = [];
|
|
@@ -92,8 +94,9 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
92
94
|
// out too. No record → eligible (cycle/migrate genes). Symmetric to the provenance trust-first filter above.
|
|
93
95
|
// Probation (#306): includeProbation lets a quarantined draft be TRIED so it can earn evidence; a rejected
|
|
94
96
|
// draft is never tried. Off (default) keeps quarantined drafts out until approval.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
const reviewRecord = review?.get(String(g.asset_id));
|
|
98
|
+
if (opts.review && reviewRecord !== undefined && reviewRecord.state !== 'approved'
|
|
99
|
+
&& (!opts.includeProbation || reviewRecord.state === 'rejected'))
|
|
97
100
|
continue;
|
|
98
101
|
const geneId = typeof g['id'] === 'string' ? String(g['id']) : String(g.asset_id);
|
|
99
102
|
const assetId = String(g.asset_id);
|
|
@@ -148,7 +151,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
148
151
|
if (opts.hubCandidates && opts.hubCandidates.length > 0) {
|
|
149
152
|
const localIds = new Set(out.map((c) => c.geneId));
|
|
150
153
|
for (const h of opts.hubCandidates) {
|
|
151
|
-
if (!passesInjectedCandidateGates(h, opts, provenance))
|
|
154
|
+
if (!passesInjectedCandidateGates(h, opts, provenance, review))
|
|
152
155
|
continue;
|
|
153
156
|
if (localIds.has(h.geneId))
|
|
154
157
|
continue; // a trusted local gene already covers this id
|
|
@@ -166,7 +169,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
166
169
|
// AntiGene is negative memory that changes an autonomous prompt. Unlike legacy/cycle-authored Genes, it must
|
|
167
170
|
// never inherit ReviewLedger's backward-compatible "no record = approved" default: no ledger, no record,
|
|
168
171
|
// quarantined, and rejected all fail closed. Only an explicit human approval can enable warning injection.
|
|
169
|
-
if (
|
|
172
|
+
if (review?.get(String(a.asset_id))?.state !== 'approved')
|
|
170
173
|
continue;
|
|
171
174
|
const trigger = asStrings(a['trigger']);
|
|
172
175
|
const avoid = asStrings(a['avoid']);
|
|
@@ -10,11 +10,13 @@ import type { PersonalityStore } from '../personality/store.js';
|
|
|
10
10
|
import { type ResolutionStatus } from './solidify.js';
|
|
11
11
|
import type { ProofOfWork } from '../schema/proofOfWork.js';
|
|
12
12
|
import { type ClassifyRecentEvent } from './cycleFailureClassifier.js';
|
|
13
|
+
import type { MemoryGraphGeneEvidence } from './memoryGraph.js';
|
|
13
14
|
export interface TriggerEval {
|
|
14
15
|
trigger: boolean;
|
|
15
16
|
reasons: string[];
|
|
16
17
|
valueScore: number;
|
|
17
18
|
}
|
|
19
|
+
export type ExecutionFailureKind = 'spawn_failed' | 'timeout' | 'cancelled' | 'permission_denied' | 'non_zero_exit' | 'invalid_output' | 'runtime_error';
|
|
18
20
|
export interface ExecutionResult {
|
|
19
21
|
outcome: {
|
|
20
22
|
status: 'success' | 'failed';
|
|
@@ -23,6 +25,9 @@ export interface ExecutionResult {
|
|
|
23
25
|
};
|
|
24
26
|
proofOfWork?: ProofOfWork;
|
|
25
27
|
strongEvidence?: boolean;
|
|
28
|
+
/** Structured runner failure metadata. Safe to persist; unlike sessionLog it contains no transcript text. */
|
|
29
|
+
failureKind?: ExecutionFailureKind;
|
|
30
|
+
exitCode?: number | null;
|
|
26
31
|
/**
|
|
27
32
|
* The agent run's transcript (stdout, plus stderr when present), attached by the execution layer on a FAILED
|
|
28
33
|
* outcome. It is the host-side context classifyCycleFailure needs to reach the host_no_transcript /
|
|
@@ -102,6 +107,8 @@ export interface CycleInput {
|
|
|
102
107
|
distilledFallback?: readonly GeneCandidateInput[];
|
|
103
108
|
/** Advisory-only AntiGene warnings matched upstream for this cycle's base signals. */
|
|
104
109
|
antiWarnings?: readonly AntiWarning[];
|
|
110
|
+
/** Bounded structured outcome evidence for explainability and prompt enrichment. */
|
|
111
|
+
memoryEvidence?: readonly MemoryGraphGeneEvidence[];
|
|
105
112
|
/**
|
|
106
113
|
* Optional triage context for the cycle.failed event (PORT v1 #279 issue-reporter half). When the caller can
|
|
107
114
|
* supply a session transcript or a list of recent failed cycles, classifyCycleFailure runs and the resulting
|
|
@@ -125,11 +132,15 @@ export interface CycleResult {
|
|
|
125
132
|
cycleId: string;
|
|
126
133
|
triggered: boolean;
|
|
127
134
|
finalStage: CycleStage;
|
|
135
|
+
/** Real CycleEngine returns this on every path; optional keeps injected legacy engines source-compatible. */
|
|
136
|
+
producedValue?: boolean;
|
|
128
137
|
decision?: GeneDecision;
|
|
129
138
|
mutation?: Mutation;
|
|
130
139
|
capsule?: Capsule;
|
|
131
140
|
event?: EvolutionEvent;
|
|
132
141
|
resolutionStatus?: ResolutionStatus;
|
|
142
|
+
failureKind?: ExecutionFailureKind;
|
|
143
|
+
exitCode?: number | null;
|
|
133
144
|
reasons: string[];
|
|
134
145
|
}
|
|
135
146
|
/**
|
package/dist/algo/cycleEngine.js
CHANGED
|
@@ -200,6 +200,10 @@ export class CycleEngine {
|
|
|
200
200
|
});
|
|
201
201
|
return r.reason ? { failure_class: r.failureClass, failure_class_reason: r.reason } : { failure_class: r.failureClass };
|
|
202
202
|
};
|
|
203
|
+
const executionMetadata = (exec) => ({
|
|
204
|
+
...(exec.failureKind !== undefined ? { failureKind: exec.failureKind } : {}),
|
|
205
|
+
...(exec.exitCode !== undefined ? { exitCode: exec.exitCode } : {}),
|
|
206
|
+
});
|
|
203
207
|
const strategy = resolveStrategy({ name: input.strategyName, signals: cycleSignals, cycleCount: historyCycleCount(ingestor, 1000, input.cycleId) });
|
|
204
208
|
const cycleCategory = categoryForStrategy(input.category, strategy);
|
|
205
209
|
let stage = 'none';
|
|
@@ -226,7 +230,7 @@ export class CycleEngine {
|
|
|
226
230
|
if (!trig.trigger) {
|
|
227
231
|
await ingestor.ingest({ type: 'cycle.aborted', human: { title: `cycle ${input.cycleId} 抑制` }, payload: { cycleId: input.cycleId, reasons: trig.reasons } });
|
|
228
232
|
advance('aborted');
|
|
229
|
-
return { cycleId: input.cycleId, triggered: false, finalStage: stage, reasons: trig.reasons };
|
|
233
|
+
return { cycleId: input.cycleId, triggered: false, finalStage: stage, producedValue: false, reasons: trig.reasons };
|
|
230
234
|
}
|
|
231
235
|
// 选 gene(可解释决策). Exploration: when recent cycles plateau, enable drift to escape local optima.
|
|
232
236
|
const recentOutcomes = recentCycleOutcomes(ingestor, 100);
|
|
@@ -297,11 +301,11 @@ export class CycleEngine {
|
|
|
297
301
|
const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
|
|
298
302
|
return epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
|
|
299
303
|
});
|
|
300
|
-
const decision = (await this.deps.selection.run({ signals: baseSignals, candidates, floor: input.selectionFloor, ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
|
|
304
|
+
const decision = (await this.deps.selection.run({ signals: baseSignals, candidates, floor: input.selectionFloor, ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}), ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: input.memoryEvidence } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
|
|
301
305
|
await ingestor.ingest({
|
|
302
306
|
type: 'decision.gene_selected',
|
|
303
307
|
human: { title: `选 gene ${decision.selectedGeneId ?? '(innovate)'}`, why: decision.candidates.map((c) => `${c.geneId}:${c.score.toFixed(3)}`).join(', ') || '无候选→innovate' },
|
|
304
|
-
payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), candidates: decision.candidates, ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), weightsVersion: decision.weightsVersion, strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
|
|
308
|
+
payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), ...(decision.selectedReason ? { selectedReason: decision.selectedReason } : {}), candidates: decision.candidates, ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), ...(decision.memoryEvidence && decision.memoryEvidence.length > 0 ? { memoryEvidence: decision.memoryEvidence } : {}), weightsVersion: decision.weightsVersion, strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
|
|
305
309
|
});
|
|
306
310
|
advance('gene_selected');
|
|
307
311
|
const geneId = decision.selectedGeneId ?? 'ad-hoc';
|
|
@@ -331,7 +335,7 @@ export class CycleEngine {
|
|
|
331
335
|
await ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } });
|
|
332
336
|
advance('failed');
|
|
333
337
|
await recordPersonalityOutcome('failed', null);
|
|
334
|
-
return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
|
|
338
|
+
return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: false, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
|
|
335
339
|
}
|
|
336
340
|
if (input.solidifyPermit) {
|
|
337
341
|
let permit;
|
|
@@ -360,12 +364,13 @@ export class CycleEngine {
|
|
|
360
364
|
reason: permit.reason,
|
|
361
365
|
gene: geneId,
|
|
362
366
|
env: envKey,
|
|
367
|
+
...executionMetadata(exec),
|
|
363
368
|
...failureClassPayload({ geneId }),
|
|
364
369
|
},
|
|
365
370
|
});
|
|
366
371
|
advance('failed');
|
|
367
372
|
await recordPersonalityOutcome('failed', null);
|
|
368
|
-
return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, reasons: [...reasons, reason] };
|
|
373
|
+
return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: false, decision, mutation, ...executionMetadata(exec), reasons: [...reasons, reason] };
|
|
369
374
|
}
|
|
370
375
|
}
|
|
371
376
|
// solidify → Capsule
|
|
@@ -394,6 +399,7 @@ export class CycleEngine {
|
|
|
394
399
|
outcome: exec.outcome,
|
|
395
400
|
gene: geneId,
|
|
396
401
|
env: envKey,
|
|
402
|
+
...executionMetadata(exec),
|
|
397
403
|
// Only forward blast radius as a no-op signal when it was measured from a git_diff proof. solidify.ts
|
|
398
404
|
// forces blast to {0,0} for non-git_diff proofs (artifact_hash/external_receipt/tool_call_trace),
|
|
399
405
|
// where {0,0} means "blast unknown", NOT "no change" — forwarding it would mis-classify a productive
|
|
@@ -414,6 +420,6 @@ export class CycleEngine {
|
|
|
414
420
|
}
|
|
415
421
|
// 人格统计回写(用途②): 用本轮真实 outcome/score 回写当轮人格桶, 供下一轮自然选择靠拢. 未注入 ⇒ no-op.
|
|
416
422
|
await recordPersonalityOutcome(exec.outcome.status, exec.outcome.score);
|
|
417
|
-
return { cycleId: input.cycleId, triggered: true, finalStage: stage, decision, mutation, capsule: sol.capsule, event, resolutionStatus: sol.resolutionStatus, reasons };
|
|
423
|
+
return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: sol.producedValue, decision, mutation, capsule: sol.capsule, event, resolutionStatus: sol.resolutionStatus, ...executionMetadata(exec), reasons };
|
|
418
424
|
}
|
|
419
425
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Bucket the failure falls into. unclassified = default-open (treat as a real evolver bug worth filing).
|
|
2
|
-
* Bucket string matches V1
|
|
2
|
+
* Bucket string matches V1 byte-for-byte so wire / UI
|
|
3
3
|
* consumers can branch on the same value regardless of producer. */
|
|
4
4
|
export type CycleFailureClass = 'host_no_transcript' | 'host_provider_error' | 'local_gene_no_blast' | 'unclassified';
|
|
5
5
|
/** Shape of a recent cycle event the classifier reads. Loose so callers can pass partial DTOs. */
|
|
@@ -5,6 +5,7 @@ import type { GeneLearningView } from '../assetstore/learningHistory.js';
|
|
|
5
5
|
import type { AssetRecord } from '../assetstore/provider.js';
|
|
6
6
|
import { type ExplorationInput } from './exploration.js';
|
|
7
7
|
import type { GenerationSource } from '../wire/index.js';
|
|
8
|
+
import type { MemoryGraphGeneEvidence } from './memoryGraph.js';
|
|
8
9
|
/** 一个候选 gene 的选择期素材. */
|
|
9
10
|
export interface GeneCandidateInput {
|
|
10
11
|
geneId: string;
|
|
@@ -35,6 +36,8 @@ export interface GeneCandidateInput {
|
|
|
35
36
|
* set the hard gates already produced, so a high confidence can never resurrect a gene that was excluded.
|
|
36
37
|
*/
|
|
37
38
|
confidence?: number;
|
|
39
|
+
/** Scoped local MemoryGraph outcome signal in [-1,1]. Data-only and never executable prompt content. */
|
|
40
|
+
memoryBoost?: number;
|
|
38
41
|
/**
|
|
39
42
|
* Cross-runtime reuse sentiment in [-1, 1] for this gene (#268 phase 1): the net of self-reported reuse
|
|
40
43
|
* SUCCESSes vs negatives (failed/mismatched/stale/unsafe), computed upstream from reuse-outcome events
|
|
@@ -78,6 +81,8 @@ export interface SelectionInput {
|
|
|
78
81
|
* for prompt rendering only; they never enter scoring, fallback, or forced selection.
|
|
79
82
|
*/
|
|
80
83
|
antiWarnings?: readonly AntiWarning[];
|
|
84
|
+
/** Structured, scoped outcome evidence selected upstream. Contains no raw memory text or instructions. */
|
|
85
|
+
memoryEvidence?: readonly MemoryGraphGeneEvidence[];
|
|
81
86
|
}
|
|
82
87
|
export interface ScoredCandidate {
|
|
83
88
|
geneId: string;
|
|
@@ -105,6 +110,10 @@ export interface GeneDecision {
|
|
|
105
110
|
antiWarnings?: AntiWarning[];
|
|
106
111
|
weightsVersion: string;
|
|
107
112
|
strategyName: string;
|
|
113
|
+
/** Human-readable explanation for the winning candidate. */
|
|
114
|
+
selectedReason?: string;
|
|
115
|
+
/** Bounded structured outcome evidence for prompt enrichment. */
|
|
116
|
+
memoryEvidence?: MemoryGraphGeneEvidence[];
|
|
108
117
|
}
|
|
109
118
|
/**
|
|
110
119
|
* Weight of the preferred-gene confidence factor (fourth factor, positive cross-cycle learning). Kept small so
|
|
@@ -118,12 +127,14 @@ export declare const CONFIDENCE_WEIGHT = 0.15;
|
|
|
118
127
|
* ±REUSE_WEIGHT and can never dominate health/signal-match.
|
|
119
128
|
*/
|
|
120
129
|
export declare const REUSE_WEIGHT = 0.1;
|
|
130
|
+
/** Weight of scoped local MemoryGraph outcome evidence. */
|
|
131
|
+
export declare const MEMORY_GRAPH_WEIGHT = 0.12;
|
|
121
132
|
/**
|
|
122
133
|
* Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
|
|
123
134
|
* + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
|
|
124
135
|
* weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
|
|
125
136
|
*/
|
|
126
|
-
export declare const SELECTION_WEIGHTS_VERSION = "sel-
|
|
137
|
+
export declare const SELECTION_WEIGHTS_VERSION = "sel-4(gh-1,conf=0.15,memory=0.12,reuse=0.1)";
|
|
127
138
|
/** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
|
|
128
139
|
export declare const engineHealthSelection: Strategy<SelectionInput, GeneDecision>;
|
|
129
140
|
/** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
|
|
@@ -4,9 +4,17 @@ import { tagOverlapScore, bagCosine } from '../signals/expand.js';
|
|
|
4
4
|
import { driftSelect } from './exploration.js';
|
|
5
5
|
import { isDistilledGeneId } from './geneIntake.js';
|
|
6
6
|
function decisionWithWarnings(input, decision) {
|
|
7
|
+
const selected = decision.selectedGeneId
|
|
8
|
+
? decision.candidates.find((candidate) => candidate.geneId === decision.selectedGeneId || candidate.assetId === decision.selectedAssetId)
|
|
9
|
+
: undefined;
|
|
10
|
+
const enriched = {
|
|
11
|
+
...decision,
|
|
12
|
+
...(selected ? { selectedReason: selected.reasons.join('; ') } : {}),
|
|
13
|
+
...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: [...input.memoryEvidence] } : {}),
|
|
14
|
+
};
|
|
7
15
|
return input.antiWarnings && input.antiWarnings.length > 0
|
|
8
|
-
? { ...
|
|
9
|
-
:
|
|
16
|
+
? { ...enriched, antiWarnings: [...input.antiWarnings] }
|
|
17
|
+
: enriched;
|
|
10
18
|
}
|
|
11
19
|
function signalMatchScore(signals, match) {
|
|
12
20
|
if (signals.length === 0 || match.length === 0)
|
|
@@ -29,12 +37,14 @@ export const CONFIDENCE_WEIGHT = 0.15;
|
|
|
29
37
|
* ±REUSE_WEIGHT and can never dominate health/signal-match.
|
|
30
38
|
*/
|
|
31
39
|
export const REUSE_WEIGHT = 0.1;
|
|
40
|
+
/** Weight of scoped local MemoryGraph outcome evidence. */
|
|
41
|
+
export const MEMORY_GRAPH_WEIGHT = 0.12;
|
|
32
42
|
/**
|
|
33
43
|
* Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
|
|
34
44
|
* + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
|
|
35
45
|
* weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
|
|
36
46
|
*/
|
|
37
|
-
export const SELECTION_WEIGHTS_VERSION = `sel-
|
|
47
|
+
export const SELECTION_WEIGHTS_VERSION = `sel-4(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT})`;
|
|
38
48
|
/**
|
|
39
49
|
* Match score with semantic signal expansion (ported from v1): literal coverage first, then fill the
|
|
40
50
|
* recall gap with semantic tag overlap, then with bag-of-words cosine similarity. A perfect literal match
|
|
@@ -65,13 +75,16 @@ function scoreCandidate(signals, c) {
|
|
|
65
75
|
// Cross-runtime reuse sentiment (#268 phase 1): clamped to [-1,1] so an upstream bug cannot turn the soft nudge
|
|
66
76
|
// into a dominant (or unbounded) term. Absent → 0 → no effect (default-off until a caller injects it).
|
|
67
77
|
const reuse = Math.max(-1, Math.min(1, c.reuseAdjust ?? 0));
|
|
68
|
-
const
|
|
78
|
+
const memory = Math.max(-1, Math.min(1, c.memoryBoost ?? 0));
|
|
79
|
+
const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf + MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse;
|
|
69
80
|
const breakdown = m.tag > 0 || m.cos > 0 ? `(literal=${m.literal.toFixed(2)}+tag=${m.tag.toFixed(2)}+cos=${m.cos.toFixed(2)})` : '';
|
|
70
81
|
const reasons = [`health=${health.score.toFixed(3)}(succ=${health.successRate.toFixed(2)},reuse=${health.reuseCount})`, `信号匹配=${m.score.toFixed(2)}${breakdown}`];
|
|
71
82
|
if (epi > 0)
|
|
72
83
|
reasons.push(`epigenetic 环境抑制 -${epi.toFixed(2)}`);
|
|
73
84
|
if (conf > 0)
|
|
74
85
|
reasons.push(`preferred-gene confidence +${(CONFIDENCE_WEIGHT * conf).toFixed(3)} (conf=${conf.toFixed(2)})`);
|
|
86
|
+
if (memory !== 0)
|
|
87
|
+
reasons.push(`scoped memory-graph outcome ${memory >= 0 ? '+' : ''}${(MEMORY_GRAPH_WEIGHT * memory).toFixed(3)} (boost=${memory.toFixed(2)})`);
|
|
75
88
|
if (reuse !== 0)
|
|
76
89
|
reasons.push(`cross-runtime reuse ${reuse >= 0 ? '+' : ''}${(REUSE_WEIGHT * reuse).toFixed(3)} (sentiment=${reuse.toFixed(2)})`);
|
|
77
90
|
return { geneId: c.geneId, ...(c.assetId ? { assetId: c.assetId } : {}), score, health, reasons };
|
|
@@ -164,11 +177,14 @@ export const engineHealthSelection = {
|
|
|
164
177
|
// (0 < score <= floor) would be preempted by an unrelated zero-evidence distilled gene; V1 (which has no floor)
|
|
165
178
|
// keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
|
|
166
179
|
const noSignalMatch = !forced.forceRejected && forced.scoredForChoice.every((s) => s.score <= 0);
|
|
167
|
-
const
|
|
168
|
-
? (input.distilledFallback ?? [])
|
|
180
|
+
const fallback = noSignalMatch
|
|
181
|
+
? (input.distilledFallback ?? [])
|
|
182
|
+
.filter((candidate) => isReusableFallbackCandidate(candidate) && (candidate.epigeneticPenalty ?? 0) === 0)
|
|
183
|
+
.map((candidate) => ({ candidate, scored: scoreCandidate(input.signals, candidate) }))
|
|
184
|
+
.sort((left, right) => right.scored.score - left.scored.score || left.candidate.geneId.localeCompare(right.candidate.geneId))[0]
|
|
169
185
|
: undefined;
|
|
170
|
-
if (
|
|
171
|
-
const fbScored =
|
|
186
|
+
if (fallback) {
|
|
187
|
+
const { candidate: fb, scored: fbScored } = fallback;
|
|
172
188
|
fbScored.reasons.push('distilled_fallback(#97): 无信号匹配, 低置信度复用蒸馏 gene');
|
|
173
189
|
scored.push(fbScored);
|
|
174
190
|
selectedGeneId = fb.geneId;
|
package/dist/algo/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './geneHealth.js';
|
|
|
2
2
|
export * from './exploration.js';
|
|
3
3
|
export * from './epigenetics.js';
|
|
4
4
|
export * from './confidence.js';
|
|
5
|
+
export * from './memoryGraph.js';
|
|
5
6
|
export * from './capabilityCandidates.js';
|
|
6
7
|
export * from './conversationSniffer.js';
|
|
7
8
|
export * from './geneIntake.js';
|
package/dist/algo/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './geneHealth.js';
|
|
|
2
2
|
export * from './exploration.js';
|
|
3
3
|
export * from './epigenetics.js';
|
|
4
4
|
export * from './confidence.js';
|
|
5
|
+
export * from './memoryGraph.js';
|
|
5
6
|
export * from './capabilityCandidates.js';
|
|
6
7
|
export * from './conversationSniffer.js';
|
|
7
8
|
export * from './geneIntake.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type MemoryGraphOutcomeStatus = 'success' | 'failed';
|
|
2
|
+
export interface MemoryGraphOutcomeRecord {
|
|
3
|
+
version: 2;
|
|
4
|
+
kind: 'outcome';
|
|
5
|
+
provenance: 'v2_local' | 'v1_import';
|
|
6
|
+
workspaceScope: string;
|
|
7
|
+
userScope: string;
|
|
8
|
+
signalFingerprint: string;
|
|
9
|
+
signals: readonly string[];
|
|
10
|
+
geneId: string;
|
|
11
|
+
status: MemoryGraphOutcomeStatus;
|
|
12
|
+
score: number;
|
|
13
|
+
at: string;
|
|
14
|
+
/** Stable local-source identity used to make one-time imports crash-retry safe. */
|
|
15
|
+
sourceFingerprint?: string;
|
|
16
|
+
successCount?: number;
|
|
17
|
+
failCount?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface MemoryGraphGeneEvidence {
|
|
20
|
+
geneId: string;
|
|
21
|
+
boost: number;
|
|
22
|
+
expectedSuccess: number;
|
|
23
|
+
successCount: number;
|
|
24
|
+
failCount: number;
|
|
25
|
+
attempts: number;
|
|
26
|
+
similarity: number;
|
|
27
|
+
lastAt: string;
|
|
28
|
+
}
|
|
29
|
+
export interface MemoryGraphDiagnostics {
|
|
30
|
+
bytesRead: number;
|
|
31
|
+
recordsRead: number;
|
|
32
|
+
corruptLines: number;
|
|
33
|
+
oversizedLines: number;
|
|
34
|
+
scopeRejected: number;
|
|
35
|
+
provenanceRejected: number;
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
recovery: 'healthy' | 'degraded' | 'recovered' | 'empty';
|
|
38
|
+
/** The graph was healthy enough to address, but another process held its maintenance lock. */
|
|
39
|
+
busy?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface MemoryGraphAdvice {
|
|
42
|
+
genes: readonly MemoryGraphGeneEvidence[];
|
|
43
|
+
diagnostics: MemoryGraphDiagnostics;
|
|
44
|
+
}
|
|
45
|
+
export interface MemoryGraphQueryInput {
|
|
46
|
+
workspace: string;
|
|
47
|
+
signals: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
export interface MemoryGraphRecordInput extends MemoryGraphQueryInput {
|
|
50
|
+
geneId: string;
|
|
51
|
+
status: MemoryGraphOutcomeStatus;
|
|
52
|
+
score: number;
|
|
53
|
+
at: string;
|
|
54
|
+
}
|
|
55
|
+
export interface MemoryGraphProvider {
|
|
56
|
+
query(input: MemoryGraphQueryInput): Promise<MemoryGraphAdvice> | MemoryGraphAdvice;
|
|
57
|
+
recordOutcome(input: MemoryGraphRecordInput): Promise<void> | void;
|
|
58
|
+
}
|
|
59
|
+
export declare function normalizeMemorySignals(signals: readonly string[]): string[];
|
|
60
|
+
export declare function memorySignalFingerprint(signals: readonly string[]): string;
|
|
61
|
+
export declare function safeMemoryGeneId(value: string): string;
|
|
62
|
+
export declare function deriveMemoryGraphAdvice(records: readonly MemoryGraphOutcomeRecord[], signals: readonly string[], nowMs: number, diagnostics: MemoryGraphDiagnostics): MemoryGraphAdvice;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const MAX_SIGNALS = 32;
|
|
2
|
+
const MAX_SIGNAL_CHARS = 120;
|
|
3
|
+
const MAX_GENE_CHARS = 240;
|
|
4
|
+
const MIN_SIMILARITY = 0.34;
|
|
5
|
+
const HALF_LIFE_DAYS = 30;
|
|
6
|
+
export function normalizeMemorySignals(signals) {
|
|
7
|
+
return [...new Set(signals
|
|
8
|
+
.filter((signal) => typeof signal === 'string')
|
|
9
|
+
.map((signal) => signal.trim().toLowerCase().slice(0, MAX_SIGNAL_CHARS))
|
|
10
|
+
.filter(Boolean))]
|
|
11
|
+
.sort()
|
|
12
|
+
.slice(0, MAX_SIGNALS);
|
|
13
|
+
}
|
|
14
|
+
export function memorySignalFingerprint(signals) {
|
|
15
|
+
return normalizeMemorySignals(signals).join('|') || '(none)';
|
|
16
|
+
}
|
|
17
|
+
export function safeMemoryGeneId(value) {
|
|
18
|
+
return value.trim().slice(0, MAX_GENE_CHARS);
|
|
19
|
+
}
|
|
20
|
+
export function deriveMemoryGraphAdvice(records, signals, nowMs, diagnostics) {
|
|
21
|
+
const currentSignals = normalizeMemorySignals(signals);
|
|
22
|
+
const aggregates = new Map();
|
|
23
|
+
for (const record of records) {
|
|
24
|
+
const similarity = jaccard(currentSignals, record.signals);
|
|
25
|
+
if (similarity < MIN_SIMILARITY)
|
|
26
|
+
continue;
|
|
27
|
+
const successCount = boundedCount(record.successCount ?? (record.status === 'success' ? 1 : 0));
|
|
28
|
+
const failCount = boundedCount(record.failCount ?? (record.status === 'failed' ? 1 : 0));
|
|
29
|
+
const total = successCount + failCount;
|
|
30
|
+
if (total === 0)
|
|
31
|
+
continue;
|
|
32
|
+
const decay = decayWeight(record.at, nowMs);
|
|
33
|
+
const weight = similarity * decay;
|
|
34
|
+
const current = aggregates.get(record.geneId) ?? { success: 0, fail: 0, weightedSimilarity: 0, weight: 0, lastAt: record.at };
|
|
35
|
+
current.success += successCount * weight;
|
|
36
|
+
current.fail += failCount * weight;
|
|
37
|
+
current.weightedSimilarity += similarity * total;
|
|
38
|
+
current.weight += total;
|
|
39
|
+
if (Date.parse(record.at) > Date.parse(current.lastAt))
|
|
40
|
+
current.lastAt = record.at;
|
|
41
|
+
aggregates.set(record.geneId, current);
|
|
42
|
+
}
|
|
43
|
+
const genes = [];
|
|
44
|
+
for (const [geneId, aggregate] of aggregates) {
|
|
45
|
+
const successCount = Math.round(aggregate.success);
|
|
46
|
+
const failCount = Math.round(aggregate.fail);
|
|
47
|
+
const attempts = successCount + failCount;
|
|
48
|
+
if (attempts === 0)
|
|
49
|
+
continue;
|
|
50
|
+
const expectedSuccess = (aggregate.success + 1) / (aggregate.success + aggregate.fail + 2);
|
|
51
|
+
const similarity = aggregate.weight > 0 ? Math.min(1, aggregate.weightedSimilarity / aggregate.weight) : 0;
|
|
52
|
+
const confidence = Math.min(1, Math.log2(attempts + 1) / 3);
|
|
53
|
+
const boost = clamp((expectedSuccess - 0.5) * 2 * similarity * confidence, -1, 1);
|
|
54
|
+
genes.push({ geneId, boost, expectedSuccess, successCount, failCount, attempts, similarity, lastAt: aggregate.lastAt });
|
|
55
|
+
}
|
|
56
|
+
genes.sort((left, right) => Math.abs(right.boost) - Math.abs(left.boost) || right.attempts - left.attempts || left.geneId.localeCompare(right.geneId));
|
|
57
|
+
return { genes: genes.slice(0, 64), diagnostics };
|
|
58
|
+
}
|
|
59
|
+
function boundedCount(value) {
|
|
60
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
61
|
+
return 0;
|
|
62
|
+
return Math.min(1_000_000, Math.floor(value));
|
|
63
|
+
}
|
|
64
|
+
function jaccard(left, right) {
|
|
65
|
+
const a = new Set(normalizeMemorySignals(left));
|
|
66
|
+
const b = new Set(normalizeMemorySignals(right));
|
|
67
|
+
if (a.size === 0 && b.size === 0)
|
|
68
|
+
return 1;
|
|
69
|
+
if (a.size === 0 || b.size === 0)
|
|
70
|
+
return 0;
|
|
71
|
+
let intersection = 0;
|
|
72
|
+
for (const value of a)
|
|
73
|
+
if (b.has(value))
|
|
74
|
+
intersection += 1;
|
|
75
|
+
return intersection / (a.size + b.size - intersection);
|
|
76
|
+
}
|
|
77
|
+
function decayWeight(at, nowMs) {
|
|
78
|
+
const timestamp = Date.parse(at);
|
|
79
|
+
if (!Number.isFinite(timestamp))
|
|
80
|
+
return 0;
|
|
81
|
+
const ageDays = Math.max(0, nowMs - timestamp) / 86_400_000;
|
|
82
|
+
return Math.pow(0.5, ageDays / HALF_LIFE_DAYS);
|
|
83
|
+
}
|
|
84
|
+
function clamp(value, min, max) {
|
|
85
|
+
return Math.max(min, Math.min(max, value));
|
|
86
|
+
}
|
|
@@ -7,6 +7,7 @@ import type { GeneCandidateInput } from './geneSelection.js';
|
|
|
7
7
|
import { CycleEngine, type CycleInput, type CycleResult, type SolidifyPermitGate } from './cycleEngine.js';
|
|
8
8
|
import { type PendingSignalsContext } from '../assetstore/pendingSignals.js';
|
|
9
9
|
import { type ReuseOutcomeSummary, type ReuseOutcomeEvent } from '../ops/reuseOutcomes.js';
|
|
10
|
+
import type { MemoryGraphAdvice } from './memoryGraph.js';
|
|
10
11
|
export interface RunCycleOptions {
|
|
11
12
|
cycleId: string;
|
|
12
13
|
problem: ProblemPattern;
|
|
@@ -60,6 +61,8 @@ export interface RunCycleOptions {
|
|
|
60
61
|
* agent self-report) gains teeth on selection. Omit → no recall contribution. Never feeds quarantine.
|
|
61
62
|
*/
|
|
62
63
|
recallEvents?: readonly ReuseOutcomeEvent[];
|
|
64
|
+
/** Scoped local MemoryGraph query result. Omit to keep selection unchanged. */
|
|
65
|
+
memoryGraphAdvice?: MemoryGraphAdvice;
|
|
63
66
|
}
|
|
64
67
|
/** Drive one full evolution cycle end-to-end: assemble candidates from the store, then run the cycle. */
|
|
65
68
|
export declare function runEvolutionCycle(engine: CycleEngine, store: AssetStoreProvider, opts: RunCycleOptions): Promise<CycleResult>;
|
|
@@ -38,7 +38,18 @@ export async function runEvolutionCycle(engine, store, opts) {
|
|
|
38
38
|
...(opts.hubCandidates && opts.hubCandidates.length > 0 ? { hubCandidates: opts.hubCandidates } : {}),
|
|
39
39
|
...(reuseCounts.size > 0 ? { reuseCounts } : {}),
|
|
40
40
|
};
|
|
41
|
-
const { candidates, distilledFallback, antiWarnings } = await assembleSelectionPool(store, signals, asmOpts);
|
|
41
|
+
const { candidates: assembledCandidates, distilledFallback, antiWarnings } = await assembleSelectionPool(store, signals, asmOpts);
|
|
42
|
+
const memoryByGene = new Map((opts.memoryGraphAdvice?.genes ?? []).map((evidence) => [evidence.geneId, evidence]));
|
|
43
|
+
const candidates = assembledCandidates.map((candidate) => {
|
|
44
|
+
const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
|
|
45
|
+
return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
|
|
46
|
+
});
|
|
47
|
+
const memoryDistilledFallback = distilledFallback.map((candidate) => {
|
|
48
|
+
const evidence = memoryByGene.get(candidate.geneId) ?? (candidate.assetId ? memoryByGene.get(candidate.assetId) : undefined);
|
|
49
|
+
return evidence ? { ...candidate, memoryBoost: evidence.boost } : candidate;
|
|
50
|
+
});
|
|
51
|
+
const memoryEligible = [...candidates, ...memoryDistilledFallback];
|
|
52
|
+
const memoryEvidence = opts.memoryGraphAdvice?.genes.filter((evidence) => memoryEligible.some((candidate) => candidate.geneId === evidence.geneId || candidate.assetId === evidence.geneId)).slice(0, 3) ?? [];
|
|
42
53
|
return engine.runCycle({
|
|
43
54
|
cycleId: opts.cycleId,
|
|
44
55
|
problem: opts.problem,
|
|
@@ -57,7 +68,8 @@ export async function runEvolutionCycle(engine, store, opts) {
|
|
|
57
68
|
...(opts.solidifyPermit ? { solidifyPermit: opts.solidifyPermit } : {}),
|
|
58
69
|
// #97: forward the distilled-gene fallback pool so a no-signal-match cycle reuses a distilled strategy
|
|
59
70
|
// (instead of a blind innovate) when nothing clears the floor.
|
|
60
|
-
...(
|
|
71
|
+
...(memoryDistilledFallback.length > 0 ? { distilledFallback: memoryDistilledFallback } : {}),
|
|
61
72
|
...(antiWarnings.length > 0 ? { antiWarnings } : {}),
|
|
73
|
+
...(memoryEvidence.length > 0 ? { memoryEvidence } : {}),
|
|
62
74
|
});
|
|
63
75
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AssetSyncRecord } from './assetSyncLedger.js';
|
|
2
|
+
import type { ProvenanceRecord } from './provenance.js';
|
|
3
|
+
import type { ReviewRecord } from './reviewLedger.js';
|
|
4
|
+
export type AssetSidecarKind = 'provenance' | 'review' | 'asset-sync';
|
|
5
|
+
export type AssetSidecarCorruptionReason = 'invalid_row' | 'unterminated';
|
|
6
|
+
export interface ParsedSidecarJsonl<T> {
|
|
7
|
+
records: T[];
|
|
8
|
+
rows: number;
|
|
9
|
+
validRows: number;
|
|
10
|
+
corruptRows: number;
|
|
11
|
+
unterminated: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class CorruptAssetSidecarError extends Error {
|
|
14
|
+
readonly sidecar: AssetSidecarKind;
|
|
15
|
+
readonly reason: AssetSidecarCorruptionReason;
|
|
16
|
+
readonly code = "CORRUPT_ASSET_SIDECAR";
|
|
17
|
+
constructor(sidecar: AssetSidecarKind, reason: AssetSidecarCorruptionReason);
|
|
18
|
+
}
|
|
19
|
+
export declare function parseSidecarJsonl<T>(raw: string, parseRecord: (value: unknown) => T | null): ParsedSidecarJsonl<T>;
|
|
20
|
+
export declare function assertTrustSidecarHealthy<T>(sidecar: Extract<AssetSidecarKind, 'provenance' | 'review'>, parsed: ParsedSidecarJsonl<T>): void;
|
|
21
|
+
export declare function parseProvenanceRecord(value: unknown): ProvenanceRecord | null;
|
|
22
|
+
export declare function parseReviewRecord(value: unknown): ReviewRecord | null;
|
|
23
|
+
export declare function parseAssetSyncRecord(value: unknown): AssetSyncRecord | null;
|