@openclawbrain/cli 0.4.13 → 0.4.15
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 +17 -11
- package/dist/extension/index.js +29 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.d.ts +8 -0
- package/dist/extension/runtime-guard.js +100 -12
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +32 -22
- package/dist/src/attachment-truth.js +338 -186
- package/dist/src/cli.d.ts +13 -1
- package/dist/src/cli.js +595 -113
- package/dist/src/index.d.ts +242 -3
- package/dist/src/index.js +1029 -38
- package/dist/src/install-converge.js +217 -0
- package/dist/src/learning-spine.d.ts +2 -1
- package/dist/src/learning-spine.js +49 -19
- package/dist/src/local-learner.d.ts +30 -0
- package/dist/src/local-learner.js +298 -179
- package/dist/src/local-session-passive-learning.js +28 -2
- package/dist/src/materialization-embedder.js +11 -0
- package/dist/src/openclaw-hook-truth.d.ts +6 -0
- package/dist/src/openclaw-hook-truth.js +27 -0
- package/dist/src/proof-command.js +301 -42
- package/dist/src/runtime-core.js +658 -0
- package/dist/src/status-learning-path.js +32 -2
- package/dist/src/teacher-decision-match.js +277 -0
- package/dist/src/teacher-labeler.js +4 -30
- package/dist/src/traced-learning-bridge.js +17 -1
- package/extension/index.ts +35 -4
- package/extension/runtime-guard.ts +92 -14
- package/package.json +4 -3
|
@@ -3,9 +3,10 @@ import path from "node:path";
|
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
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";
|
|
5
5
|
import { buildNormalizedEventDedupId, buildNormalizedEventExport, buildNormalizedEventExportBridge, createDefaultLearningSurface, createEventExportCursor, createExplicitEventRange, validateNormalizedEventExport, validateNormalizedEventExportBridge, validateNormalizedEventExportSlice } from "@openclawbrain/event-export";
|
|
6
|
-
import { computePayloadChecksum, loadPack, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
|
|
6
|
+
import { computePayloadChecksum, loadPack, loadPackFromActivation, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
|
|
7
7
|
import { buildArtifactProvenance } from "@openclawbrain/provenance";
|
|
8
8
|
import { createWorkspaceMetadata } from "@openclawbrain/workspace-metadata";
|
|
9
|
+
import { createServeTimeDecisionMatcher } from "./teacher-decision-match.js";
|
|
9
10
|
export const DEFAULT_ALWAYS_ON_LEARNING_LIVE_SLICES_PER_CYCLE = 1;
|
|
10
11
|
export const DEFAULT_ALWAYS_ON_LEARNING_BACKFILL_SLICES_PER_CYCLE = 1;
|
|
11
12
|
export const DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS = 5 * 60 * 1_000;
|
|
@@ -26,6 +27,7 @@ export const ALWAYS_ON_STRUCTURAL_PLASTICITY_OP_CEILING = {
|
|
|
26
27
|
export const ALWAYS_ON_STRUCTURAL_PLASTICITY_MIN_INTERACTIONS = 2;
|
|
27
28
|
export const ALWAYS_ON_STRUCTURAL_PLASTICITY_MIN_FEEDBACK = 1;
|
|
28
29
|
const CONNECT_PAIR_SCORE_THRESHOLD = 2;
|
|
30
|
+
const MAX_CARRY_FORWARD_SEED_BLOCKS = 12;
|
|
29
31
|
export const DEFAULT_SPARSE_FEEDBACK_POLICY = {
|
|
30
32
|
teacherBudget: 32,
|
|
31
33
|
teacherDelayMs: 0,
|
|
@@ -4054,15 +4056,36 @@ function normalizeOutcomeMatchText(value) {
|
|
|
4054
4056
|
? value.replace(/\s+/g, " ").trim().toLowerCase()
|
|
4055
4057
|
: null;
|
|
4056
4058
|
}
|
|
4059
|
+
function normalizeObservationOptionalString(value) {
|
|
4060
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
4061
|
+
}
|
|
4062
|
+
function buildObservationSelectionDigestKey(selectionDigest, activePackGraphChecksum) {
|
|
4063
|
+
const normalizedSelectionDigest = normalizeObservationOptionalString(selectionDigest);
|
|
4064
|
+
const normalizedGraphChecksum = normalizeObservationOptionalString(activePackGraphChecksum);
|
|
4065
|
+
if (normalizedSelectionDigest === null || normalizedGraphChecksum === null) {
|
|
4066
|
+
return null;
|
|
4067
|
+
}
|
|
4068
|
+
return `${normalizedGraphChecksum}|${normalizedSelectionDigest}`;
|
|
4069
|
+
}
|
|
4057
4070
|
function parseObservationRouteMetadata(value) {
|
|
4058
4071
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4059
4072
|
return {
|
|
4073
|
+
serveDecisionRecordId: null,
|
|
4074
|
+
selectionDigest: null,
|
|
4075
|
+
turnCompileEventId: null,
|
|
4076
|
+
activePackId: null,
|
|
4077
|
+
activePackGraphChecksum: null,
|
|
4060
4078
|
selectedNodeIds: [],
|
|
4061
4079
|
selectedPathNodeIds: []
|
|
4062
4080
|
};
|
|
4063
4081
|
}
|
|
4064
4082
|
try {
|
|
4065
4083
|
const parsed = JSON.parse(value);
|
|
4084
|
+
const serveDecisionRecordId = normalizeObservationOptionalString(parsed?.serveDecisionRecordId);
|
|
4085
|
+
const selectionDigest = normalizeObservationOptionalString(parsed?.selectionDigest);
|
|
4086
|
+
const turnCompileEventId = normalizeObservationOptionalString(parsed?.turnCompileEventId);
|
|
4087
|
+
const activePackId = normalizeObservationOptionalString(parsed?.activePackId);
|
|
4088
|
+
const activePackGraphChecksum = normalizeObservationOptionalString(parsed?.activePackGraphChecksum);
|
|
4066
4089
|
const selectedNodeIds = Array.isArray(parsed?.selectedNodeIds)
|
|
4067
4090
|
? parsed.selectedNodeIds.filter((entry) => typeof entry === "string")
|
|
4068
4091
|
: [];
|
|
@@ -4070,12 +4093,22 @@ function parseObservationRouteMetadata(value) {
|
|
|
4070
4093
|
? parsed.selectedPathNodeIds.filter((entry) => typeof entry === "string")
|
|
4071
4094
|
: [];
|
|
4072
4095
|
return {
|
|
4096
|
+
serveDecisionRecordId,
|
|
4097
|
+
selectionDigest,
|
|
4098
|
+
turnCompileEventId,
|
|
4099
|
+
activePackId,
|
|
4100
|
+
activePackGraphChecksum,
|
|
4073
4101
|
selectedNodeIds,
|
|
4074
4102
|
selectedPathNodeIds
|
|
4075
4103
|
};
|
|
4076
4104
|
}
|
|
4077
4105
|
catch {
|
|
4078
4106
|
return {
|
|
4107
|
+
serveDecisionRecordId: null,
|
|
4108
|
+
selectionDigest: null,
|
|
4109
|
+
turnCompileEventId: null,
|
|
4110
|
+
activePackId: null,
|
|
4111
|
+
activePackGraphChecksum: null,
|
|
4079
4112
|
selectedNodeIds: [],
|
|
4080
4113
|
selectedPathNodeIds: []
|
|
4081
4114
|
};
|
|
@@ -4099,8 +4132,24 @@ function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
|
4099
4132
|
if (table === undefined) {
|
|
4100
4133
|
return [];
|
|
4101
4134
|
}
|
|
4135
|
+
const columns = new Set(db.prepare("PRAGMA table_info(brain_observations)").all().map((column) => String(column.name ?? "")));
|
|
4136
|
+
const selectableColumns = [
|
|
4137
|
+
"id",
|
|
4138
|
+
"query_text",
|
|
4139
|
+
"route_metadata_json",
|
|
4140
|
+
"final_score",
|
|
4141
|
+
"confidence",
|
|
4142
|
+
"created_at",
|
|
4143
|
+
"updated_at",
|
|
4144
|
+
"evaluated_at",
|
|
4145
|
+
columns.has("serve_decision_record_id") ? "serve_decision_record_id" : "NULL AS serve_decision_record_id",
|
|
4146
|
+
columns.has("selection_digest") ? "selection_digest" : "NULL AS selection_digest",
|
|
4147
|
+
columns.has("turn_compile_event_id") ? "turn_compile_event_id" : "NULL AS turn_compile_event_id",
|
|
4148
|
+
columns.has("active_pack_id") ? "active_pack_id" : "NULL AS active_pack_id",
|
|
4149
|
+
columns.has("active_pack_graph_checksum") ? "active_pack_graph_checksum" : "NULL AS active_pack_graph_checksum"
|
|
4150
|
+
];
|
|
4102
4151
|
const rows = db.prepare(`
|
|
4103
|
-
SELECT
|
|
4152
|
+
SELECT ${selectableColumns.join(", ")}
|
|
4104
4153
|
FROM brain_observations
|
|
4105
4154
|
WHERE final_score IS NOT NULL
|
|
4106
4155
|
ORDER BY created_at DESC
|
|
@@ -4116,6 +4165,11 @@ function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
|
4116
4165
|
createdAt: Number.isFinite(Number(row.created_at)) ? Number(row.created_at) : null,
|
|
4117
4166
|
updatedAt: Number.isFinite(Number(row.updated_at)) ? Number(row.updated_at) : null,
|
|
4118
4167
|
evaluatedAt: Number.isFinite(Number(row.evaluated_at)) ? Number(row.evaluated_at) : null,
|
|
4168
|
+
serveDecisionRecordId: normalizeObservationOptionalString(row.serve_decision_record_id) ?? routeMetadata.serveDecisionRecordId,
|
|
4169
|
+
selectionDigest: normalizeObservationOptionalString(row.selection_digest) ?? routeMetadata.selectionDigest,
|
|
4170
|
+
turnCompileEventId: normalizeObservationOptionalString(row.turn_compile_event_id) ?? routeMetadata.turnCompileEventId,
|
|
4171
|
+
activePackId: normalizeObservationOptionalString(row.active_pack_id) ?? routeMetadata.activePackId,
|
|
4172
|
+
activePackGraphChecksum: normalizeObservationOptionalString(row.active_pack_graph_checksum) ?? routeMetadata.activePackGraphChecksum,
|
|
4119
4173
|
selectedNodeIds: routeMetadata.selectedNodeIds,
|
|
4120
4174
|
selectedPathNodeIds: routeMetadata.selectedPathNodeIds
|
|
4121
4175
|
};
|
|
@@ -4197,45 +4251,186 @@ function compareDecisionObservationMatch(left, right) {
|
|
|
4197
4251
|
}
|
|
4198
4252
|
return 0;
|
|
4199
4253
|
}
|
|
4254
|
+
function emptyTeacherObservationBindingStats() {
|
|
4255
|
+
return {
|
|
4256
|
+
totalObservationCount: 0,
|
|
4257
|
+
nonZeroObservationCount: 0,
|
|
4258
|
+
skippedZeroRewardCount: 0,
|
|
4259
|
+
accounting: {
|
|
4260
|
+
exact: 0,
|
|
4261
|
+
heuristic: 0,
|
|
4262
|
+
unmatched: 0,
|
|
4263
|
+
ambiguous: 0
|
|
4264
|
+
},
|
|
4265
|
+
matched: {
|
|
4266
|
+
exactDecisionId: 0,
|
|
4267
|
+
exactSelectionDigest: 0,
|
|
4268
|
+
turnCompileEventId: 0,
|
|
4269
|
+
legacyHeuristic: 0
|
|
4270
|
+
},
|
|
4271
|
+
unmatched: {
|
|
4272
|
+
exactDecisionId: 0,
|
|
4273
|
+
exactSelectionDigest: 0,
|
|
4274
|
+
turnCompileEventId: 0,
|
|
4275
|
+
legacyHeuristic: 0
|
|
4276
|
+
},
|
|
4277
|
+
ambiguous: {
|
|
4278
|
+
exactDecisionId: 0,
|
|
4279
|
+
exactSelectionDigest: 0,
|
|
4280
|
+
turnCompileEventId: 0,
|
|
4281
|
+
legacyHeuristic: 0
|
|
4282
|
+
}
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
function summarizeTeacherObservationBindingStats(stats) {
|
|
4286
|
+
return [
|
|
4287
|
+
`accounting(exact=${stats.accounting.exact},heuristic=${stats.accounting.heuristic},unmatched=${stats.accounting.unmatched},ambiguous=${stats.accounting.ambiguous})`,
|
|
4288
|
+
`matched(exact_decision_id=${stats.matched.exactDecisionId},exact_selection_digest=${stats.matched.exactSelectionDigest},turn_compile_event_id=${stats.matched.turnCompileEventId},legacy_heuristic=${stats.matched.legacyHeuristic})`,
|
|
4289
|
+
`unmatched(exact_decision_id=${stats.unmatched.exactDecisionId},exact_selection_digest=${stats.unmatched.exactSelectionDigest},turn_compile_event_id=${stats.unmatched.turnCompileEventId},legacy_heuristic=${stats.unmatched.legacyHeuristic})`,
|
|
4290
|
+
`ambiguous(exact_decision_id=${stats.ambiguous.exactDecisionId},exact_selection_digest=${stats.ambiguous.exactSelectionDigest},turn_compile_event_id=${stats.ambiguous.turnCompileEventId},legacy_heuristic=${stats.ambiguous.legacyHeuristic})`,
|
|
4291
|
+
`non_zero=${stats.nonZeroObservationCount}`,
|
|
4292
|
+
`skipped_zero=${stats.skippedZeroRewardCount}`
|
|
4293
|
+
].join(" ");
|
|
4294
|
+
}
|
|
4295
|
+
function buildTeacherObservationDecisionIndexes(decisions) {
|
|
4296
|
+
const byRecordId = new Map();
|
|
4297
|
+
const bySelectionDigest = new Map();
|
|
4298
|
+
const ambiguousSelectionDigests = new Set();
|
|
4299
|
+
const byTurnCompileEventId = new Map();
|
|
4300
|
+
const ambiguousTurnCompileEventIds = new Set();
|
|
4301
|
+
for (const decision of decisions) {
|
|
4302
|
+
byRecordId.set(decision.recordId, decision);
|
|
4303
|
+
const selectionDigestKey = buildObservationSelectionDigestKey(decision.selectionDigest, decision.activePackGraphChecksum);
|
|
4304
|
+
if (selectionDigestKey !== null) {
|
|
4305
|
+
if (bySelectionDigest.has(selectionDigestKey)) {
|
|
4306
|
+
bySelectionDigest.delete(selectionDigestKey);
|
|
4307
|
+
ambiguousSelectionDigests.add(selectionDigestKey);
|
|
4308
|
+
}
|
|
4309
|
+
else if (!ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
4310
|
+
bySelectionDigest.set(selectionDigestKey, decision);
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
const turnCompileEventId = normalizeObservationOptionalString(decision.turnCompileEventId);
|
|
4314
|
+
if (turnCompileEventId !== null) {
|
|
4315
|
+
if (byTurnCompileEventId.has(turnCompileEventId)) {
|
|
4316
|
+
byTurnCompileEventId.delete(turnCompileEventId);
|
|
4317
|
+
ambiguousTurnCompileEventIds.add(turnCompileEventId);
|
|
4318
|
+
}
|
|
4319
|
+
else if (!ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
4320
|
+
byTurnCompileEventId.set(turnCompileEventId, decision);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
return {
|
|
4325
|
+
byRecordId,
|
|
4326
|
+
bySelectionDigest,
|
|
4327
|
+
ambiguousSelectionDigests,
|
|
4328
|
+
byTurnCompileEventId,
|
|
4329
|
+
ambiguousTurnCompileEventIds
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
function resolveLegacyTeacherObservationMatch(decisions, observation) {
|
|
4333
|
+
const matches = decisions
|
|
4334
|
+
.map((decision) => {
|
|
4335
|
+
const key = decisionObservationMatchKey(decision, observation);
|
|
4336
|
+
return key === null
|
|
4337
|
+
? null
|
|
4338
|
+
: {
|
|
4339
|
+
decision,
|
|
4340
|
+
key
|
|
4341
|
+
};
|
|
4342
|
+
})
|
|
4343
|
+
.filter((entry) => entry !== null)
|
|
4344
|
+
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4345
|
+
const best = matches[0] ?? null;
|
|
4346
|
+
const runnerUp = matches[1] ?? null;
|
|
4347
|
+
if (best === null) {
|
|
4348
|
+
return { kind: "unmatched", mode: "legacyHeuristic" };
|
|
4349
|
+
}
|
|
4350
|
+
if (runnerUp !== null && compareDecisionObservationMatch(best.key, runnerUp.key) === 0) {
|
|
4351
|
+
return { kind: "ambiguous", mode: "legacyHeuristic" };
|
|
4352
|
+
}
|
|
4353
|
+
return {
|
|
4354
|
+
kind: "matched",
|
|
4355
|
+
mode: "legacyHeuristic",
|
|
4356
|
+
decision: best.decision
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
function resolveTeacherObservationDecisionMatch(decisions, observation, indexes) {
|
|
4360
|
+
const serveDecisionRecordId = normalizeObservationOptionalString(observation.serveDecisionRecordId);
|
|
4361
|
+
if (serveDecisionRecordId !== null) {
|
|
4362
|
+
const decision = indexes.byRecordId.get(serveDecisionRecordId) ?? null;
|
|
4363
|
+
return decision === null
|
|
4364
|
+
? { kind: "unmatched", mode: "exactDecisionId" }
|
|
4365
|
+
: { kind: "matched", mode: "exactDecisionId", decision };
|
|
4366
|
+
}
|
|
4367
|
+
const selectionDigestKey = buildObservationSelectionDigestKey(observation.selectionDigest, observation.activePackGraphChecksum);
|
|
4368
|
+
if (selectionDigestKey !== null) {
|
|
4369
|
+
if (indexes.ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
4370
|
+
return { kind: "ambiguous", mode: "exactSelectionDigest" };
|
|
4371
|
+
}
|
|
4372
|
+
const decision = indexes.bySelectionDigest.get(selectionDigestKey) ?? null;
|
|
4373
|
+
return decision === null
|
|
4374
|
+
? { kind: "unmatched", mode: "exactSelectionDigest" }
|
|
4375
|
+
: { kind: "matched", mode: "exactSelectionDigest", decision };
|
|
4376
|
+
}
|
|
4377
|
+
if (normalizeObservationOptionalString(observation.selectionDigest) !== null) {
|
|
4378
|
+
return { kind: "unmatched", mode: "exactSelectionDigest" };
|
|
4379
|
+
}
|
|
4380
|
+
const turnCompileEventId = normalizeObservationOptionalString(observation.turnCompileEventId);
|
|
4381
|
+
if (turnCompileEventId !== null) {
|
|
4382
|
+
if (indexes.ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
4383
|
+
return { kind: "ambiguous", mode: "turnCompileEventId" };
|
|
4384
|
+
}
|
|
4385
|
+
const decision = indexes.byTurnCompileEventId.get(turnCompileEventId) ?? null;
|
|
4386
|
+
return decision === null
|
|
4387
|
+
? { kind: "unmatched", mode: "turnCompileEventId" }
|
|
4388
|
+
: { kind: "matched", mode: "turnCompileEventId", decision };
|
|
4389
|
+
}
|
|
4390
|
+
return resolveLegacyTeacherObservationMatch(decisions, observation);
|
|
4391
|
+
}
|
|
4200
4392
|
function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutcomes) {
|
|
4201
4393
|
const outcomes = new Map();
|
|
4394
|
+
const stats = emptyTeacherObservationBindingStats();
|
|
4202
4395
|
for (const decision of decisions) {
|
|
4203
4396
|
outcomes.set(decision.recordId, 0);
|
|
4204
4397
|
}
|
|
4205
4398
|
if (!Array.isArray(observationOutcomes) || observationOutcomes.length === 0) {
|
|
4206
|
-
return outcomes;
|
|
4399
|
+
return { outcomes, stats };
|
|
4207
4400
|
}
|
|
4401
|
+
const indexes = buildTeacherObservationDecisionIndexes(decisions);
|
|
4402
|
+
stats.totalObservationCount = observationOutcomes.length;
|
|
4208
4403
|
for (const observation of observationOutcomes) {
|
|
4209
|
-
const
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
return key === null
|
|
4213
|
-
? null
|
|
4214
|
-
: {
|
|
4215
|
-
decision,
|
|
4216
|
-
key
|
|
4217
|
-
};
|
|
4218
|
-
})
|
|
4219
|
-
.filter((entry) => entry !== null)
|
|
4220
|
-
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4221
|
-
const best = matches[0] ?? null;
|
|
4222
|
-
const runnerUp = matches[1] ?? null;
|
|
4223
|
-
if (best === null) {
|
|
4404
|
+
const reward = clampTeacherObservationOutcome(observation.finalScore);
|
|
4405
|
+
if (reward === 0) {
|
|
4406
|
+
stats.skippedZeroRewardCount += 1;
|
|
4224
4407
|
continue;
|
|
4225
4408
|
}
|
|
4226
|
-
|
|
4409
|
+
stats.nonZeroObservationCount += 1;
|
|
4410
|
+
const match = resolveTeacherObservationDecisionMatch(decisions, observation, indexes);
|
|
4411
|
+
if (match.kind === "unmatched") {
|
|
4412
|
+
stats.accounting.unmatched += 1;
|
|
4413
|
+
stats.unmatched[match.mode] += 1;
|
|
4227
4414
|
continue;
|
|
4228
4415
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4416
|
+
if (match.kind === "ambiguous") {
|
|
4417
|
+
stats.accounting.ambiguous += 1;
|
|
4418
|
+
stats.ambiguous[match.mode] += 1;
|
|
4231
4419
|
continue;
|
|
4232
4420
|
}
|
|
4233
|
-
|
|
4421
|
+
if (match.mode === "legacyHeuristic") {
|
|
4422
|
+
stats.accounting.heuristic += 1;
|
|
4423
|
+
}
|
|
4424
|
+
else {
|
|
4425
|
+
stats.accounting.exact += 1;
|
|
4426
|
+
}
|
|
4427
|
+
stats.matched[match.mode] += 1;
|
|
4428
|
+
const current = outcomes.get(match.decision.recordId) ?? 0;
|
|
4234
4429
|
if (current === 0 || Math.abs(reward) > Math.abs(current)) {
|
|
4235
|
-
outcomes.set(
|
|
4430
|
+
outcomes.set(match.decision.recordId, reward);
|
|
4236
4431
|
}
|
|
4237
4432
|
}
|
|
4238
|
-
return outcomes;
|
|
4433
|
+
return { outcomes, stats };
|
|
4239
4434
|
}
|
|
4240
4435
|
|
|
4241
4436
|
export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 300_000) {
|
|
@@ -4247,149 +4442,7 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
|
|
|
4247
4442
|
return outcomes;
|
|
4248
4443
|
}
|
|
4249
4444
|
const normalizeOptionalString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
4250
|
-
const
|
|
4251
|
-
const normalized = normalizeOptionalString(value);
|
|
4252
|
-
if (normalized === undefined) {
|
|
4253
|
-
return null;
|
|
4254
|
-
}
|
|
4255
|
-
const parsed = Date.parse(normalized);
|
|
4256
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
4257
|
-
};
|
|
4258
|
-
const buildSessionChannelKey = (sessionId, channel) => {
|
|
4259
|
-
const normalizedSessionId = normalizeOptionalString(sessionId);
|
|
4260
|
-
const normalizedChannel = normalizeOptionalString(channel);
|
|
4261
|
-
if (normalizedSessionId === undefined || normalizedChannel === undefined) {
|
|
4262
|
-
return null;
|
|
4263
|
-
}
|
|
4264
|
-
return `${normalizedSessionId}|${normalizedChannel}`;
|
|
4265
|
-
};
|
|
4266
|
-
const buildCandidateKey = (sessionId, channel, createdAt) => {
|
|
4267
|
-
const sessionChannelKey = buildSessionChannelKey(sessionId, channel);
|
|
4268
|
-
const normalizedCreatedAt = normalizeOptionalString(createdAt);
|
|
4269
|
-
if (sessionChannelKey === null || normalizedCreatedAt === undefined) {
|
|
4270
|
-
return null;
|
|
4271
|
-
}
|
|
4272
|
-
return `${sessionChannelKey}|${normalizedCreatedAt}`;
|
|
4273
|
-
};
|
|
4274
|
-
const buildDecisionTimestamps = (decision) => {
|
|
4275
|
-
const timestamps = [];
|
|
4276
|
-
const turnCreatedAt = toTimestamp(decision.turnCreatedAt);
|
|
4277
|
-
const recordedAt = toTimestamp(decision.recordedAt);
|
|
4278
|
-
if (turnCreatedAt !== null) {
|
|
4279
|
-
timestamps.push(turnCreatedAt);
|
|
4280
|
-
}
|
|
4281
|
-
if (recordedAt !== null && !timestamps.includes(recordedAt)) {
|
|
4282
|
-
timestamps.push(recordedAt);
|
|
4283
|
-
}
|
|
4284
|
-
return timestamps;
|
|
4285
|
-
};
|
|
4286
|
-
const operationalPatterns = [
|
|
4287
|
-
/^NO_REPLY$/i,
|
|
4288
|
-
/^HEARTBEAT_OK$/i,
|
|
4289
|
-
/^read heartbeat\.md if it exists/i,
|
|
4290
|
-
/^a new session was started via \/new or \/reset\./i,
|
|
4291
|
-
/\[cron:[a-f0-9-]+\s/i,
|
|
4292
|
-
/\[system message\]\s*\[sessionid:/i,
|
|
4293
|
-
];
|
|
4294
|
-
const isOperationalDecision = (decision) => {
|
|
4295
|
-
const userMessage = normalizeOptionalString(decision.userMessage);
|
|
4296
|
-
if (userMessage === undefined) {
|
|
4297
|
-
return true;
|
|
4298
|
-
}
|
|
4299
|
-
return operationalPatterns.some((pattern) => pattern.test(userMessage));
|
|
4300
|
-
};
|
|
4301
|
-
const selectNearestDecision = (entries, interactionAt) => {
|
|
4302
|
-
const candidates = entries
|
|
4303
|
-
.map((entry) => {
|
|
4304
|
-
const deltas = entry.timestamps.map((timestamp) => Math.abs(timestamp - interactionAt));
|
|
4305
|
-
const bestDelta = deltas.length === 0 ? null : Math.min(...deltas);
|
|
4306
|
-
return bestDelta === null || bestDelta > maxDelayMs
|
|
4307
|
-
? null
|
|
4308
|
-
: {
|
|
4309
|
-
decision: entry.decision,
|
|
4310
|
-
deltaMs: bestDelta,
|
|
4311
|
-
recordedAt: toTimestamp(entry.decision.recordedAt) ?? 0,
|
|
4312
|
-
};
|
|
4313
|
-
})
|
|
4314
|
-
.filter((entry) => entry !== null)
|
|
4315
|
-
.sort((left, right) => {
|
|
4316
|
-
if (left.deltaMs !== right.deltaMs) {
|
|
4317
|
-
return left.deltaMs - right.deltaMs;
|
|
4318
|
-
}
|
|
4319
|
-
return right.recordedAt - left.recordedAt;
|
|
4320
|
-
});
|
|
4321
|
-
const best = candidates[0] ?? null;
|
|
4322
|
-
const runnerUp = candidates[1] ?? null;
|
|
4323
|
-
if (best === null) {
|
|
4324
|
-
return null;
|
|
4325
|
-
}
|
|
4326
|
-
if (runnerUp !== null && runnerUp.deltaMs === best.deltaMs && runnerUp.decision !== best.decision) {
|
|
4327
|
-
return null;
|
|
4328
|
-
}
|
|
4329
|
-
return best.decision;
|
|
4330
|
-
};
|
|
4331
|
-
const exactDecisions = new Map();
|
|
4332
|
-
const fallbackDecisions = new Map();
|
|
4333
|
-
const decisionsBySessionChannel = new Map();
|
|
4334
|
-
const globalFallbackDecisions = [];
|
|
4335
|
-
for (const decision of [...decisions].sort((left, right) => Date.parse(right.recordedAt) - Date.parse(left.recordedAt))) {
|
|
4336
|
-
const userMessage = normalizeOptionalString(decision.userMessage);
|
|
4337
|
-
if (userMessage === undefined) {
|
|
4338
|
-
continue;
|
|
4339
|
-
}
|
|
4340
|
-
const turnCompileEventId = normalizeOptionalString(decision.turnCompileEventId);
|
|
4341
|
-
if (turnCompileEventId !== undefined && !exactDecisions.has(turnCompileEventId)) {
|
|
4342
|
-
exactDecisions.set(turnCompileEventId, decision);
|
|
4343
|
-
}
|
|
4344
|
-
for (const candidateKey of [
|
|
4345
|
-
buildCandidateKey(decision.sessionId, decision.channel, decision.turnCreatedAt),
|
|
4346
|
-
buildCandidateKey(decision.sessionId, decision.channel, decision.recordedAt),
|
|
4347
|
-
]) {
|
|
4348
|
-
if (candidateKey !== null && !fallbackDecisions.has(candidateKey)) {
|
|
4349
|
-
fallbackDecisions.set(candidateKey, decision);
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
const sessionChannelKey = buildSessionChannelKey(decision.sessionId, decision.channel);
|
|
4353
|
-
const indexedEntry = {
|
|
4354
|
-
decision,
|
|
4355
|
-
timestamps: buildDecisionTimestamps(decision),
|
|
4356
|
-
operational: isOperationalDecision(decision),
|
|
4357
|
-
};
|
|
4358
|
-
if (sessionChannelKey !== null) {
|
|
4359
|
-
const indexed = decisionsBySessionChannel.get(sessionChannelKey) ?? [];
|
|
4360
|
-
indexed.push(indexedEntry);
|
|
4361
|
-
decisionsBySessionChannel.set(sessionChannelKey, indexed);
|
|
4362
|
-
}
|
|
4363
|
-
if (!indexedEntry.operational) {
|
|
4364
|
-
globalFallbackDecisions.push(indexedEntry);
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
const matchInteractionToDecision = (interaction) => {
|
|
4368
|
-
const exact = exactDecisions.get(interaction.eventId);
|
|
4369
|
-
if (exact !== undefined) {
|
|
4370
|
-
return exact;
|
|
4371
|
-
}
|
|
4372
|
-
const exactFallbackKey = buildCandidateKey(interaction.sessionId, interaction.channel, interaction.createdAt);
|
|
4373
|
-
if (exactFallbackKey !== null) {
|
|
4374
|
-
const fallback = fallbackDecisions.get(exactFallbackKey);
|
|
4375
|
-
if (fallback !== undefined) {
|
|
4376
|
-
return fallback;
|
|
4377
|
-
}
|
|
4378
|
-
}
|
|
4379
|
-
const interactionAt = toTimestamp(interaction.createdAt);
|
|
4380
|
-
const sessionChannelKey = buildSessionChannelKey(interaction.sessionId, interaction.channel);
|
|
4381
|
-
if (interactionAt === null) {
|
|
4382
|
-
return null;
|
|
4383
|
-
}
|
|
4384
|
-
if (sessionChannelKey !== null) {
|
|
4385
|
-
const sessionEntries = (decisionsBySessionChannel.get(sessionChannelKey) ?? []).filter((entry) => entry.operational !== true);
|
|
4386
|
-
const sessionMatch = selectNearestDecision(sessionEntries, interactionAt);
|
|
4387
|
-
if (sessionMatch !== null) {
|
|
4388
|
-
return sessionMatch;
|
|
4389
|
-
}
|
|
4390
|
-
}
|
|
4391
|
-
return selectNearestDecision(globalFallbackDecisions, interactionAt);
|
|
4392
|
-
};
|
|
4445
|
+
const matchInteractionToDecision = createServeTimeDecisionMatcher(decisions, { maxTimeDeltaMs: maxDelayMs });
|
|
4393
4446
|
const interactionById = new Map();
|
|
4394
4447
|
for (const interaction of eventExport.interactionEvents ?? []) {
|
|
4395
4448
|
const interactionId = normalizeOptionalString(interaction.eventId);
|
|
@@ -4397,7 +4450,7 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
|
|
|
4397
4450
|
interactionById.set(interactionId, interaction);
|
|
4398
4451
|
}
|
|
4399
4452
|
}
|
|
4400
|
-
const
|
|
4453
|
+
const consumedFeedbackIds = new Set();
|
|
4401
4454
|
for (const event of eventExport.feedbackEvents ?? []) {
|
|
4402
4455
|
const relatedInteractionId = normalizeOptionalString(event.relatedInteractionId);
|
|
4403
4456
|
if (relatedInteractionId === undefined) {
|
|
@@ -4407,6 +4460,8 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
|
|
|
4407
4460
|
if (interaction === undefined) {
|
|
4408
4461
|
continue;
|
|
4409
4462
|
}
|
|
4463
|
+
const feedbackId = normalizeOptionalString(event.eventId) ?? `${relatedInteractionId}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
|
|
4464
|
+
consumedFeedbackIds.add(feedbackId);
|
|
4410
4465
|
const matchedDecision = matchInteractionToDecision(interaction);
|
|
4411
4466
|
if (matchedDecision === null) {
|
|
4412
4467
|
continue;
|
|
@@ -4418,13 +4473,11 @@ export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 3
|
|
|
4418
4473
|
outcomes.set(matchedDecision.recordId, reward);
|
|
4419
4474
|
}
|
|
4420
4475
|
}
|
|
4421
|
-
const feedbackId = normalizeOptionalString(event.eventId) ?? `${relatedInteractionId}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
|
|
4422
|
-
matchedFeedbackIds.add(feedbackId);
|
|
4423
4476
|
}
|
|
4424
4477
|
const feedbackBySession = new Map();
|
|
4425
4478
|
for (const event of eventExport.feedbackEvents ?? []) {
|
|
4426
4479
|
const feedbackId = normalizeOptionalString(event.eventId) ?? `${normalizeOptionalString(event.relatedInteractionId) ?? '__none__'}|${normalizeOptionalString(event.createdAt) ?? 'unknown'}`;
|
|
4427
|
-
if (
|
|
4480
|
+
if (consumedFeedbackIds.has(feedbackId)) {
|
|
4428
4481
|
continue;
|
|
4429
4482
|
}
|
|
4430
4483
|
const sessionId = event.sessionId ?? "__none__";
|
|
@@ -4627,8 +4680,8 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4627
4680
|
const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
|
|
4628
4681
|
// 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
|
|
4629
4682
|
const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
|
|
4630
|
-
const
|
|
4631
|
-
for (const [decisionId, reward] of
|
|
4683
|
+
const teacherObservationBindings = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
|
|
4684
|
+
for (const [decisionId, reward] of teacherObservationBindings.outcomes.entries()) {
|
|
4632
4685
|
if (reward !== 0) {
|
|
4633
4686
|
outcomeMap.set(decisionId, reward);
|
|
4634
4687
|
}
|
|
@@ -4695,7 +4748,8 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4695
4748
|
if (fallback.policyUpdates.length > 0) {
|
|
4696
4749
|
return {
|
|
4697
4750
|
artifact: fallback,
|
|
4698
|
-
updatedBaseline: currentBaseline
|
|
4751
|
+
updatedBaseline: currentBaseline,
|
|
4752
|
+
observationBindingStats: teacherObservationBindings.stats
|
|
4699
4753
|
};
|
|
4700
4754
|
}
|
|
4701
4755
|
}
|
|
@@ -4717,7 +4771,7 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4717
4771
|
: remappedServeTimeDecisions.length === 0
|
|
4718
4772
|
? "no serve-time decisions supplied for V2 learned routing refresh"
|
|
4719
4773
|
: supervisedTrajectoryCount === 0
|
|
4720
|
-
?
|
|
4774
|
+
? `no outcomes found for serve-time decisions (${summarizeTeacherObservationBindingStats(teacherObservationBindings.stats)})`
|
|
4721
4775
|
: "trajectory updates produced no learned routing delta";
|
|
4722
4776
|
const artifact = {
|
|
4723
4777
|
routerIdentity: `${packId}:route_fn`,
|
|
@@ -4765,7 +4819,11 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4765
4819
|
traces,
|
|
4766
4820
|
policyUpdates
|
|
4767
4821
|
};
|
|
4768
|
-
return {
|
|
4822
|
+
return {
|
|
4823
|
+
artifact,
|
|
4824
|
+
updatedBaseline: currentBaseline,
|
|
4825
|
+
observationBindingStats: teacherObservationBindings.stats
|
|
4826
|
+
};
|
|
4769
4827
|
}
|
|
4770
4828
|
function resolveEventExport(input) {
|
|
4771
4829
|
if (input.eventExports === undefined) {
|
|
@@ -4788,6 +4846,55 @@ function defaultLearningSurface(workspace, offlineArtifacts, workspaceInit) {
|
|
|
4788
4846
|
...offlineArtifacts.map((artifact) => `offline:${artifact}`)
|
|
4789
4847
|
]));
|
|
4790
4848
|
}
|
|
4849
|
+
function isCarryForwardSeedBlock(block) {
|
|
4850
|
+
const role = block.learning?.role ?? "";
|
|
4851
|
+
if (role !== "interaction" && role !== "feedback" && role !== "teacher_supervision" && role !== "label_surface") {
|
|
4852
|
+
return false;
|
|
4853
|
+
}
|
|
4854
|
+
if (typeof block.text !== "string" || block.text.trim().length === 0) {
|
|
4855
|
+
return false;
|
|
4856
|
+
}
|
|
4857
|
+
return block.initSeed !== undefined ||
|
|
4858
|
+
block.semantic?.sourceKind === "recorded_session_seed" ||
|
|
4859
|
+
block.source.includes("/seed:");
|
|
4860
|
+
}
|
|
4861
|
+
function carryForwardSeedBlockScore(block) {
|
|
4862
|
+
return (block.initSeed?.score ?? 0) +
|
|
4863
|
+
(block.state?.strength ?? 0) +
|
|
4864
|
+
(block.learning?.humanLabels ?? 0) * 2 +
|
|
4865
|
+
(block.learning?.selfLabels ?? 0);
|
|
4866
|
+
}
|
|
4867
|
+
function loadCarryForwardSeedBlocksFromActivation(activationRoot, currentGraphBlockIds) {
|
|
4868
|
+
try {
|
|
4869
|
+
const activePack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
|
|
4870
|
+
if (activePack === null) {
|
|
4871
|
+
return [];
|
|
4872
|
+
}
|
|
4873
|
+
const candidates = activePack.graph.blocks
|
|
4874
|
+
.filter((block) => isCarryForwardSeedBlock(block) && !currentGraphBlockIds.has(block.id));
|
|
4875
|
+
const preferred = candidates.filter((block) => block.learning?.role !== "interaction");
|
|
4876
|
+
const selected = (preferred.length > 0 ? preferred : candidates)
|
|
4877
|
+
.sort((left, right) => carryForwardSeedBlockScore(right) - carryForwardSeedBlockScore(left) ||
|
|
4878
|
+
right.priority - left.priority ||
|
|
4879
|
+
left.id.localeCompare(right.id))
|
|
4880
|
+
.slice(0, MAX_CARRY_FORWARD_SEED_BLOCKS);
|
|
4881
|
+
if (selected.length === 0) {
|
|
4882
|
+
return [];
|
|
4883
|
+
}
|
|
4884
|
+
const selectedIds = new Set(selected.map((block) => block.id));
|
|
4885
|
+
return selected.map((block) => ({
|
|
4886
|
+
...structuredClone(block),
|
|
4887
|
+
...(block.edges === undefined
|
|
4888
|
+
? {}
|
|
4889
|
+
: {
|
|
4890
|
+
edges: block.edges.filter((edge) => selectedIds.has(edge.targetBlockId))
|
|
4891
|
+
})
|
|
4892
|
+
}));
|
|
4893
|
+
}
|
|
4894
|
+
catch {
|
|
4895
|
+
return [];
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4791
4898
|
function cloneRuntimeGraphForPack(packId, runtimeGraph, builtAt) {
|
|
4792
4899
|
const cloned = structuredClone(runtimeGraph);
|
|
4793
4900
|
cloned.packId = packId;
|
|
@@ -4861,9 +4968,18 @@ function buildRuntimeGraphSnapshot(input) {
|
|
|
4861
4968
|
rootDir: workspace.rootDir,
|
|
4862
4969
|
observedAt: builtAt
|
|
4863
4970
|
}));
|
|
4971
|
+
const carryForwardSeedBlocks = input.activationRoot === undefined
|
|
4972
|
+
? []
|
|
4973
|
+
: loadCarryForwardSeedBlocksFromActivation(input.activationRoot, new Set(graph.blocks.map((block) => block.id)));
|
|
4974
|
+
const graphWithCarryForwardSeed = carryForwardSeedBlocks.length === 0
|
|
4975
|
+
? graph
|
|
4976
|
+
: {
|
|
4977
|
+
...graph,
|
|
4978
|
+
blocks: [...graph.blocks, ...carryForwardSeedBlocks]
|
|
4979
|
+
};
|
|
4864
4980
|
return {
|
|
4865
|
-
graph,
|
|
4866
|
-
plasticity: summarizeRuntimeGraphPlasticity("live_loop",
|
|
4981
|
+
graph: graphWithCarryForwardSeed,
|
|
4982
|
+
plasticity: summarizeRuntimeGraphPlasticity("live_loop", graphWithCarryForwardSeed, builtAt, null, normalizedEventExport)
|
|
4867
4983
|
};
|
|
4868
4984
|
}
|
|
4869
4985
|
export function buildCandidatePack(input) {
|
|
@@ -4922,6 +5038,7 @@ export function buildCandidatePack(input) {
|
|
|
4922
5038
|
const vectors = createVectorsPayload(graph);
|
|
4923
5039
|
let router = null;
|
|
4924
5040
|
let updatedBaseline = null;
|
|
5041
|
+
let observationBindingStats = null;
|
|
4925
5042
|
const teacherObservationOutcomes = input.activationRoot === undefined
|
|
4926
5043
|
? []
|
|
4927
5044
|
: loadTeacherObservationOutcomesFromActivation(input.activationRoot);
|
|
@@ -4930,6 +5047,7 @@ export function buildCandidatePack(input) {
|
|
|
4930
5047
|
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
|
|
4931
5048
|
router = v2Result.artifact;
|
|
4932
5049
|
updatedBaseline = v2Result.updatedBaseline;
|
|
5050
|
+
observationBindingStats = v2Result.observationBindingStats;
|
|
4933
5051
|
}
|
|
4934
5052
|
else {
|
|
4935
5053
|
router = createRouterArtifact(packId, builtAt, graph, vectors, eventExport, input.sparseFeedback, input.principalBacklog);
|
|
@@ -5045,7 +5163,8 @@ export function buildCandidatePack(input) {
|
|
|
5045
5163
|
pgVersionUsed: input.learnedRouting ? (useV2 ? "v2" : "v1") : null,
|
|
5046
5164
|
decisionLogCount,
|
|
5047
5165
|
fallbackReason,
|
|
5048
|
-
updatedBaseline
|
|
5166
|
+
updatedBaseline,
|
|
5167
|
+
observationBindingStats
|
|
5049
5168
|
},
|
|
5050
5169
|
summary: {
|
|
5051
5170
|
packId,
|