@openclawbrain/cli 0.4.10 → 0.4.12
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/README.md +18 -17
- package/dist/src/cli.js +53 -10
- package/dist/src/daemon.d.ts +6 -1
- package/dist/src/daemon.js +229 -41
- package/dist/src/index.js +5 -2
- package/dist/src/local-learner.d.ts +4 -0
- package/dist/src/local-learner.js +212 -5
- package/dist/src/proof-command.js +654 -0
- package/dist/src/status-learning-path.js +28 -0
- package/package.json +1 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
4
|
import { buildRouteArtifactReference, CONTRACT_IDS, PACK_GRAPH_SCHEMAS, ROUTER_PG_PROFILE_V1, ROUTER_PG_PROFILE_V2, checksumJsonPayload, computeRouterCollectedLabelCounts, computeRouterFreshnessChecksum, computeRouterObjectiveChecksum, computeRouterQueryChecksum, computeRouterWeightsChecksum, sortNormalizedEvents, validateTeacherSupervisionArtifact } from "@openclawbrain/contracts";
|
|
4
5
|
import { buildNormalizedEventDedupId, buildNormalizedEventExport, buildNormalizedEventExportBridge, createDefaultLearningSurface, createEventExportCursor, createExplicitEventRange, validateNormalizedEventExport, validateNormalizedEventExportBridge, validateNormalizedEventExportSlice } from "@openclawbrain/event-export";
|
|
5
6
|
import { computePayloadChecksum, loadPack, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
|
|
@@ -891,7 +892,8 @@ function buildAlwaysOnLearningMaterializationJob(input, current, selectedSlices,
|
|
|
891
892
|
principalBacklog,
|
|
892
893
|
...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
|
|
893
894
|
...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
|
|
894
|
-
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
|
|
895
|
+
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
|
|
896
|
+
...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
|
|
895
897
|
};
|
|
896
898
|
const candidate = buildCandidatePackFromNormalizedEventExport(candidateInput);
|
|
897
899
|
const selectedSliceIds = selectedSlices.map((slice) => slice.sliceId);
|
|
@@ -4040,6 +4042,201 @@ function createRouterArtifact(packId, builtAt, graph, vectors, eventExport, spar
|
|
|
4040
4042
|
* Join serve-time decisions with feedback events to assign outcome rewards.
|
|
4041
4043
|
* Returns decisionRecordId → outcome (z_T).
|
|
4042
4044
|
*/
|
|
4045
|
+
function clampTeacherObservationOutcome(value) {
|
|
4046
|
+
if (!Number.isFinite(value)) {
|
|
4047
|
+
return 0;
|
|
4048
|
+
}
|
|
4049
|
+
return Math.max(-1, Math.min(1, value));
|
|
4050
|
+
}
|
|
4051
|
+
function normalizeOutcomeMatchText(value) {
|
|
4052
|
+
return typeof value === "string" && value.trim().length > 0
|
|
4053
|
+
? value.replace(/\s+/g, " ").trim().toLowerCase()
|
|
4054
|
+
: null;
|
|
4055
|
+
}
|
|
4056
|
+
function parseObservationRouteMetadata(value) {
|
|
4057
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4058
|
+
return {
|
|
4059
|
+
selectedNodeIds: [],
|
|
4060
|
+
selectedPathNodeIds: []
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
try {
|
|
4064
|
+
const parsed = JSON.parse(value);
|
|
4065
|
+
const selectedNodeIds = Array.isArray(parsed?.selectedNodeIds)
|
|
4066
|
+
? parsed.selectedNodeIds.filter((entry) => typeof entry === "string")
|
|
4067
|
+
: [];
|
|
4068
|
+
const selectedPathNodeIds = Array.isArray(parsed?.selectedPathNodeIds)
|
|
4069
|
+
? parsed.selectedPathNodeIds.filter((entry) => typeof entry === "string")
|
|
4070
|
+
: [];
|
|
4071
|
+
return {
|
|
4072
|
+
selectedNodeIds,
|
|
4073
|
+
selectedPathNodeIds
|
|
4074
|
+
};
|
|
4075
|
+
}
|
|
4076
|
+
catch {
|
|
4077
|
+
return {
|
|
4078
|
+
selectedNodeIds: [],
|
|
4079
|
+
selectedPathNodeIds: []
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
4084
|
+
const resolvedActivationRoot = typeof activationRoot === "string" && activationRoot.trim().length > 0
|
|
4085
|
+
? path.resolve(activationRoot)
|
|
4086
|
+
: null;
|
|
4087
|
+
if (resolvedActivationRoot === null) {
|
|
4088
|
+
return [];
|
|
4089
|
+
}
|
|
4090
|
+
const dbPath = path.join(resolvedActivationRoot, "state.db");
|
|
4091
|
+
if (!existsSync(dbPath)) {
|
|
4092
|
+
return [];
|
|
4093
|
+
}
|
|
4094
|
+
let db = null;
|
|
4095
|
+
try {
|
|
4096
|
+
db = new DatabaseSync(dbPath);
|
|
4097
|
+
const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'brain_observations' LIMIT 1").get();
|
|
4098
|
+
if (table === undefined) {
|
|
4099
|
+
return [];
|
|
4100
|
+
}
|
|
4101
|
+
const rows = db.prepare(`
|
|
4102
|
+
SELECT id, query_text, route_metadata_json, final_score, confidence, created_at, updated_at, evaluated_at
|
|
4103
|
+
FROM brain_observations
|
|
4104
|
+
WHERE final_score IS NOT NULL
|
|
4105
|
+
ORDER BY created_at DESC
|
|
4106
|
+
`).all();
|
|
4107
|
+
return rows
|
|
4108
|
+
.map((row) => {
|
|
4109
|
+
const routeMetadata = parseObservationRouteMetadata(row.route_metadata_json);
|
|
4110
|
+
return {
|
|
4111
|
+
observationId: typeof row.id === "string" ? row.id : null,
|
|
4112
|
+
queryText: typeof row.query_text === "string" ? row.query_text : "",
|
|
4113
|
+
finalScore: clampTeacherObservationOutcome(Number(row.final_score)),
|
|
4114
|
+
confidence: Number.isFinite(Number(row.confidence)) ? Number(row.confidence) : null,
|
|
4115
|
+
createdAt: Number.isFinite(Number(row.created_at)) ? Number(row.created_at) : null,
|
|
4116
|
+
updatedAt: Number.isFinite(Number(row.updated_at)) ? Number(row.updated_at) : null,
|
|
4117
|
+
evaluatedAt: Number.isFinite(Number(row.evaluated_at)) ? Number(row.evaluated_at) : null,
|
|
4118
|
+
selectedNodeIds: routeMetadata.selectedNodeIds,
|
|
4119
|
+
selectedPathNodeIds: routeMetadata.selectedPathNodeIds
|
|
4120
|
+
};
|
|
4121
|
+
})
|
|
4122
|
+
.filter((row) => row.createdAt !== null && row.finalScore !== 0);
|
|
4123
|
+
}
|
|
4124
|
+
catch {
|
|
4125
|
+
return [];
|
|
4126
|
+
}
|
|
4127
|
+
finally {
|
|
4128
|
+
try {
|
|
4129
|
+
db?.close();
|
|
4130
|
+
}
|
|
4131
|
+
catch {
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
function decisionTimestampsForObservationMatch(decision) {
|
|
4136
|
+
const timestamps = [];
|
|
4137
|
+
for (const value of [decision.turnCreatedAt, decision.recordedAt]) {
|
|
4138
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4139
|
+
continue;
|
|
4140
|
+
}
|
|
4141
|
+
const parsed = Date.parse(value);
|
|
4142
|
+
if (Number.isFinite(parsed) && !timestamps.includes(parsed)) {
|
|
4143
|
+
timestamps.push(parsed);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
return timestamps;
|
|
4147
|
+
}
|
|
4148
|
+
function decisionObservationMatchKey(decision, observation) {
|
|
4149
|
+
const normalizedDecisionText = normalizeOutcomeMatchText(decision.userMessage);
|
|
4150
|
+
const normalizedObservationText = normalizeOutcomeMatchText(observation.queryText);
|
|
4151
|
+
const decisionSelected = new Set([
|
|
4152
|
+
...(Array.isArray(decision.selectedBrainContextIds) ? decision.selectedBrainContextIds : []),
|
|
4153
|
+
...(Array.isArray(decision.chosenContextIds) ? decision.chosenContextIds : [])
|
|
4154
|
+
].filter((entry) => typeof entry === "string"));
|
|
4155
|
+
const observationSelected = new Set([
|
|
4156
|
+
...observation.selectedNodeIds,
|
|
4157
|
+
...observation.selectedPathNodeIds
|
|
4158
|
+
]);
|
|
4159
|
+
let overlapCount = 0;
|
|
4160
|
+
for (const blockId of observationSelected) {
|
|
4161
|
+
if (decisionSelected.has(blockId)) {
|
|
4162
|
+
overlapCount += 1;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
const timestamps = decisionTimestampsForObservationMatch(decision);
|
|
4166
|
+
const observationCreatedAt = observation.createdAt;
|
|
4167
|
+
const bestDelta = observationCreatedAt === null || timestamps.length === 0
|
|
4168
|
+
? Number.POSITIVE_INFINITY
|
|
4169
|
+
: Math.min(...timestamps.map((timestamp) => Math.abs(timestamp - observationCreatedAt)));
|
|
4170
|
+
const sameQuery = normalizedDecisionText !== null && normalizedDecisionText === normalizedObservationText;
|
|
4171
|
+
if (!sameQuery && overlapCount === 0) {
|
|
4172
|
+
return null;
|
|
4173
|
+
}
|
|
4174
|
+
if (!Number.isFinite(bestDelta) || bestDelta > 300_000) {
|
|
4175
|
+
return null;
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
sameQuery,
|
|
4179
|
+
overlapCount,
|
|
4180
|
+
deltaMs: bestDelta,
|
|
4181
|
+
confidence: observation.confidence ?? 0
|
|
4182
|
+
};
|
|
4183
|
+
}
|
|
4184
|
+
function compareDecisionObservationMatch(left, right) {
|
|
4185
|
+
if (left.sameQuery !== right.sameQuery) {
|
|
4186
|
+
return left.sameQuery ? -1 : 1;
|
|
4187
|
+
}
|
|
4188
|
+
if (left.overlapCount !== right.overlapCount) {
|
|
4189
|
+
return right.overlapCount - left.overlapCount;
|
|
4190
|
+
}
|
|
4191
|
+
if (left.deltaMs !== right.deltaMs) {
|
|
4192
|
+
return left.deltaMs - right.deltaMs;
|
|
4193
|
+
}
|
|
4194
|
+
if (left.confidence !== right.confidence) {
|
|
4195
|
+
return right.confidence - left.confidence;
|
|
4196
|
+
}
|
|
4197
|
+
return 0;
|
|
4198
|
+
}
|
|
4199
|
+
function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutcomes) {
|
|
4200
|
+
const outcomes = new Map();
|
|
4201
|
+
for (const decision of decisions) {
|
|
4202
|
+
outcomes.set(decision.recordId, 0);
|
|
4203
|
+
}
|
|
4204
|
+
if (!Array.isArray(observationOutcomes) || observationOutcomes.length === 0) {
|
|
4205
|
+
return outcomes;
|
|
4206
|
+
}
|
|
4207
|
+
for (const observation of observationOutcomes) {
|
|
4208
|
+
const matches = decisions
|
|
4209
|
+
.map((decision) => {
|
|
4210
|
+
const key = decisionObservationMatchKey(decision, observation);
|
|
4211
|
+
return key === null
|
|
4212
|
+
? null
|
|
4213
|
+
: {
|
|
4214
|
+
decision,
|
|
4215
|
+
key
|
|
4216
|
+
};
|
|
4217
|
+
})
|
|
4218
|
+
.filter((entry) => entry !== null)
|
|
4219
|
+
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4220
|
+
const best = matches[0] ?? null;
|
|
4221
|
+
const runnerUp = matches[1] ?? null;
|
|
4222
|
+
if (best === null) {
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
if (runnerUp !== null && compareDecisionObservationMatch(best.key, runnerUp.key) === 0) {
|
|
4226
|
+
continue;
|
|
4227
|
+
}
|
|
4228
|
+
const reward = clampTeacherObservationOutcome(observation.finalScore);
|
|
4229
|
+
if (reward === 0) {
|
|
4230
|
+
continue;
|
|
4231
|
+
}
|
|
4232
|
+
const current = outcomes.get(best.decision.recordId) ?? 0;
|
|
4233
|
+
if (current === 0 || Math.abs(reward) > Math.abs(current)) {
|
|
4234
|
+
outcomes.set(best.decision.recordId, reward);
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
return outcomes;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4043
4240
|
export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 300_000) {
|
|
4044
4241
|
const outcomes = new Map();
|
|
4045
4242
|
for (const decision of decisions) {
|
|
@@ -4421,15 +4618,21 @@ function computeTrajectoryPolicyGradientV2(trajectory, adjacency, graph, vectors
|
|
|
4421
4618
|
* 6. Aggregates into RouterPolicyUpdateV1[] format
|
|
4422
4619
|
* 7. Builds the router artifact with ROUTER_PG_PROFILE_V2
|
|
4423
4620
|
*/
|
|
4424
|
-
function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog) {
|
|
4621
|
+
function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog, teacherObservationOutcomes = []) {
|
|
4425
4622
|
const tau = DEFAULT_TAU;
|
|
4426
4623
|
const pgScale = 8;
|
|
4427
4624
|
// 1. Build adjacency map from graph
|
|
4428
4625
|
const adjacency = buildAdjacencyMap(graph);
|
|
4429
4626
|
const resolveBlockId = buildGraphBlockIdResolver(graph);
|
|
4430
4627
|
const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
|
|
4431
|
-
// 2. Join serve-time decisions with feedback
|
|
4628
|
+
// 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
|
|
4432
4629
|
const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
|
|
4630
|
+
const teacherObservationOutcomeMap = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
|
|
4631
|
+
for (const [decisionId, reward] of teacherObservationOutcomeMap.entries()) {
|
|
4632
|
+
if (reward !== 0) {
|
|
4633
|
+
outcomeMap.set(decisionId, reward);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4433
4636
|
// 3 & 4. Reconstruct trajectories and update baseline
|
|
4434
4637
|
let currentBaseline = { ...baselineState };
|
|
4435
4638
|
const trajectories = [];
|
|
@@ -4719,9 +4922,12 @@ export function buildCandidatePack(input) {
|
|
|
4719
4922
|
const vectors = createVectorsPayload(graph);
|
|
4720
4923
|
let router = null;
|
|
4721
4924
|
let updatedBaseline = null;
|
|
4925
|
+
const teacherObservationOutcomes = input.activationRoot === undefined
|
|
4926
|
+
? []
|
|
4927
|
+
: loadTeacherObservationOutcomesFromActivation(input.activationRoot);
|
|
4722
4928
|
if (input.learnedRouting) {
|
|
4723
4929
|
if (useV2) {
|
|
4724
|
-
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog);
|
|
4930
|
+
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
|
|
4725
4931
|
router = v2Result.artifact;
|
|
4726
4932
|
updatedBaseline = v2Result.updatedBaseline;
|
|
4727
4933
|
}
|
|
@@ -4883,7 +5089,8 @@ export function buildCandidatePackFromNormalizedEventExport(input) {
|
|
|
4883
5089
|
...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
|
|
4884
5090
|
...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
|
|
4885
5091
|
...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
|
|
4886
|
-
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
|
|
5092
|
+
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
|
|
5093
|
+
...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
|
|
4887
5094
|
};
|
|
4888
5095
|
return buildCandidatePack(candidateInput);
|
|
4889
5096
|
}
|