@openclawbrain/openclaw 0.1.1 → 0.1.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/README.md +136 -31
- package/dist/src/cli.d.ts +12 -0
- package/dist/src/cli.js +188 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/index.d.ts +749 -4
- package/dist/src/index.js +2622 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/learning-spine.d.ts +46 -0
- package/dist/src/learning-spine.js +445 -0
- package/dist/src/learning-spine.js.map +1 -0
- package/package.json +12 -7
package/dist/src/index.js
CHANGED
|
@@ -1,20 +1,599 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
import {
|
|
6
|
-
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
|
-
import {
|
|
5
|
+
import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
|
|
6
|
+
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
|
+
import { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
8
|
+
import { DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS, advanceAlwaysOnLearningRuntime, buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, materializeAlwaysOnLearningCandidatePack, materializeCandidatePackFromNormalizedEventExport } from "@openclawbrain/learner";
|
|
9
|
+
import { LEARNING_SPINE_LOG_LAYOUT, activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
10
|
+
import { appendLearningUpdateLogs, appendServeTimeRouteDecisionLog } from "./learning-spine.js";
|
|
8
11
|
const DEFAULT_AGENT_ID = "openclaw-runtime";
|
|
9
12
|
const FEEDBACK_KINDS = new Set(["correction", "teaching", "approval", "suppression"]);
|
|
13
|
+
export const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
|
|
14
|
+
const RECORDED_SESSION_TRACE_CONTRACT = "recorded_session_trace.v1";
|
|
15
|
+
const RECORDED_SESSION_FIXTURE_CONTRACT = "recorded_session_replay_fixture.v1";
|
|
16
|
+
const RECORDED_SESSION_BUNDLE_CONTRACT = "recorded_session_replay_bundle.v1";
|
|
10
17
|
const RUNTIME_EVENT_EXPORT_BUNDLE_CONTRACT = "normalized_event_export_bundle.v1";
|
|
18
|
+
const DEFAULT_ATTACH_STATUS_MESSAGE = "openclaw attach status probe";
|
|
19
|
+
const DEFAULT_ATTACH_STATUS_RUNTIME_HINTS = ["attach", "status", "probe"];
|
|
11
20
|
export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
|
|
12
21
|
manifest: "manifest.json",
|
|
13
22
|
payload: "normalized-event-export.json"
|
|
14
23
|
};
|
|
24
|
+
function normalizeBrainAttachmentPolicy(value) {
|
|
25
|
+
if (value === "dedicated" || value === "shared") {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return "undeclared";
|
|
29
|
+
}
|
|
30
|
+
function buildCurrentProfileAttachmentPolicy(policyMode) {
|
|
31
|
+
if (policyMode === "dedicated") {
|
|
32
|
+
return {
|
|
33
|
+
mode: "dedicated",
|
|
34
|
+
readScope: "current_profile_only",
|
|
35
|
+
writeScope: "current_profile_only",
|
|
36
|
+
currentProfileExclusive: true,
|
|
37
|
+
requiresProfileAttribution: true,
|
|
38
|
+
detail: "dedicated brains are exclusive to the current profile and must keep profile attribution explicit on every served turn"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (policyMode === "shared") {
|
|
42
|
+
return {
|
|
43
|
+
mode: "shared",
|
|
44
|
+
readScope: "attached_profiles",
|
|
45
|
+
writeScope: "attached_profiles",
|
|
46
|
+
currentProfileExclusive: false,
|
|
47
|
+
requiresProfileAttribution: true,
|
|
48
|
+
detail: "shared brains may serve multiple attached profiles, so status and per-turn attribution must stay profile-explicit"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const OPENCLAW_LANDING_BOUNDARIES_V1 = {
|
|
54
|
+
compileBoundary: {
|
|
55
|
+
contract: CONTRACT_IDS.runtimeCompile,
|
|
56
|
+
activationSlot: "active",
|
|
57
|
+
entrypoint: "compileRuntimeContext",
|
|
58
|
+
servedFromCandidateBeforePromotion: false,
|
|
59
|
+
learnedRouteEvidenceRequiredWhenManifestRequiresIt: true
|
|
60
|
+
},
|
|
61
|
+
eventExportBoundary: {
|
|
62
|
+
emittedContracts: [CONTRACT_IDS.interactionEvents, CONTRACT_IDS.feedbackEvents],
|
|
63
|
+
entrypoint: "runRuntimeTurn",
|
|
64
|
+
bundleWriteOptional: true,
|
|
65
|
+
writeFailuresEraseSuccessfulCompile: false,
|
|
66
|
+
learningHandoffStaysOffHotPath: true
|
|
67
|
+
},
|
|
68
|
+
activePackBoundary: {
|
|
69
|
+
servingSlot: "active",
|
|
70
|
+
inspectableSlots: ["active", "candidate", "previous"],
|
|
71
|
+
candidateServedBeforePromotion: false,
|
|
72
|
+
previousSlotUsedForRollback: true
|
|
73
|
+
},
|
|
74
|
+
promotionBoundary: {
|
|
75
|
+
candidateSlot: "candidate",
|
|
76
|
+
activeSlot: "active",
|
|
77
|
+
previousSlot: "previous",
|
|
78
|
+
requiresActivationReadyCandidate: true,
|
|
79
|
+
compileSeesCandidateOnlyAfterPromotion: true,
|
|
80
|
+
promotionHappensOffHotPath: true
|
|
81
|
+
},
|
|
82
|
+
failOpenSemantics: {
|
|
83
|
+
missingActivePackFallsBackToStaticContext: true,
|
|
84
|
+
learnedRequiredRouteArtifactDriftHardFails: true,
|
|
85
|
+
hardFailuresDisableStaticFallback: true,
|
|
86
|
+
eventExportWriteFailurePreservesCompile: true
|
|
87
|
+
},
|
|
88
|
+
runtimeResponsibilities: [
|
|
89
|
+
"runtime orchestration and session flow",
|
|
90
|
+
"prompt assembly and response delivery",
|
|
91
|
+
"guarded serve-path fail-open decisions"
|
|
92
|
+
],
|
|
93
|
+
brainResponsibilities: [
|
|
94
|
+
"normalized event emission and export handoff",
|
|
95
|
+
"candidate pack materialization and learned route refresh",
|
|
96
|
+
"activation staging promotion and rollback",
|
|
97
|
+
"promoted-pack compilation diagnostics"
|
|
98
|
+
]
|
|
99
|
+
};
|
|
15
100
|
function toErrorMessage(error) {
|
|
16
101
|
return error instanceof Error ? error.message : String(error);
|
|
17
102
|
}
|
|
103
|
+
function buildAsyncTeacherLoopNotes(input) {
|
|
104
|
+
return [
|
|
105
|
+
`teacher_queue_depth=${input.queueDepth}`,
|
|
106
|
+
`teacher_freshness=${input.latestFreshness}`,
|
|
107
|
+
`teacher_artifacts_total=${input.artifactCount}`,
|
|
108
|
+
`teacher_artifacts_emitted=${input.emittedArtifactCount}`,
|
|
109
|
+
`teacher_artifacts_deduped=${input.dedupedArtifactCount}`,
|
|
110
|
+
`teacher_budget=${input.sparseFeedback.teacherBudget}`,
|
|
111
|
+
`teacher_delay_ms=${input.sparseFeedback.teacherDelayMs}`,
|
|
112
|
+
`teacher_feedback_mask=correction:${input.sparseFeedback.feedbackMask.correction},teaching:${input.sparseFeedback.feedbackMask.teaching},approval:${input.sparseFeedback.feedbackMask.approval},suppression:${input.sparseFeedback.feedbackMask.suppression}`,
|
|
113
|
+
`teacher_feedback_eligible=${input.sparseFeedback.eligibleFeedbackCount}`,
|
|
114
|
+
`teacher_feedback_masked=${input.sparseFeedback.maskedFeedbackCount}`,
|
|
115
|
+
`teacher_feedback_delayed=${input.sparseFeedback.delayedFeedbackCount}`,
|
|
116
|
+
`teacher_feedback_budgeted_out=${input.sparseFeedback.budgetedOutFeedbackCount}`,
|
|
117
|
+
`teacher_background_amplified=${input.sparseFeedback.amplifiedBackgroundLabelCount}`,
|
|
118
|
+
`teacher_noop=${input.noOpReason}`,
|
|
119
|
+
input.materialization === null ? "teacher_materialization=noop" : `teacher_materialized_pack=${input.materialization.candidate.summary.packId}`
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
function cloneAlwaysOnLearningMaterializationJobOrNull(value) {
|
|
123
|
+
return value === null ? null : structuredClone(value);
|
|
124
|
+
}
|
|
125
|
+
function cloneTeacherSupervisionArtifacts(value) {
|
|
126
|
+
return [...structuredClone(value)];
|
|
127
|
+
}
|
|
128
|
+
function cloneCanonicalSupervision(value) {
|
|
129
|
+
return structuredClone(value);
|
|
130
|
+
}
|
|
131
|
+
function cloneContinuousProductLoopPackVersion(value) {
|
|
132
|
+
return structuredClone(value);
|
|
133
|
+
}
|
|
134
|
+
function cloneContinuousProductLoopState(value) {
|
|
135
|
+
return structuredClone(value);
|
|
136
|
+
}
|
|
137
|
+
function buildNormalizedEventDedupId(event) {
|
|
138
|
+
return checksumJsonPayload({
|
|
139
|
+
contract: event.contract,
|
|
140
|
+
eventId: event.eventId,
|
|
141
|
+
agentId: event.agentId,
|
|
142
|
+
sessionId: event.sessionId,
|
|
143
|
+
channel: event.channel,
|
|
144
|
+
sequence: event.sequence,
|
|
145
|
+
kind: event.kind,
|
|
146
|
+
createdAt: event.createdAt,
|
|
147
|
+
source: event.source,
|
|
148
|
+
packId: "packId" in event ? event.packId ?? null : null,
|
|
149
|
+
content: "content" in event ? event.content : null,
|
|
150
|
+
messageId: event.messageId ?? null,
|
|
151
|
+
relatedInteractionId: "relatedInteractionId" in event ? event.relatedInteractionId ?? null : null
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function mergeRuntimeEventHistory(current, incoming) {
|
|
155
|
+
const merged = sortNormalizedEvents([
|
|
156
|
+
...current.interactionEvents,
|
|
157
|
+
...current.feedbackEvents,
|
|
158
|
+
...incoming.interactionEvents,
|
|
159
|
+
...incoming.feedbackEvents
|
|
160
|
+
]);
|
|
161
|
+
const deduped = [];
|
|
162
|
+
const seen = new Set();
|
|
163
|
+
for (const event of merged) {
|
|
164
|
+
const dedupId = buildNormalizedEventDedupId(event);
|
|
165
|
+
if (seen.has(dedupId)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
seen.add(dedupId);
|
|
169
|
+
deduped.push(event);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
interactionEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.interactionEvents),
|
|
173
|
+
feedbackEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.feedbackEvents)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function buildContinuousTurnExport(turn, loopRoot) {
|
|
177
|
+
const exportSeed = checksumJsonPayload({
|
|
178
|
+
sessionId: turn.sessionId,
|
|
179
|
+
channel: turn.channel,
|
|
180
|
+
sourceStream: turn.sourceStream ?? null,
|
|
181
|
+
userMessage: turn.userMessage,
|
|
182
|
+
createdAt: turn.createdAt ?? null,
|
|
183
|
+
sequenceStart: turn.sequenceStart ?? null,
|
|
184
|
+
compileCreatedAt: turn.compile?.createdAt ?? null,
|
|
185
|
+
delivery: turn.delivery === false
|
|
186
|
+
? false
|
|
187
|
+
: turn.delivery === undefined || turn.delivery === null
|
|
188
|
+
? null
|
|
189
|
+
: turn.delivery === true
|
|
190
|
+
? true
|
|
191
|
+
: {
|
|
192
|
+
createdAt: turn.delivery.createdAt ?? null,
|
|
193
|
+
messageId: turn.delivery.messageId ?? null,
|
|
194
|
+
sequence: turn.delivery.sequence ?? null
|
|
195
|
+
},
|
|
196
|
+
feedback: (turn.feedback ?? [])
|
|
197
|
+
.filter((item) => item !== null)
|
|
198
|
+
.map((item) => ({
|
|
199
|
+
content: item.content,
|
|
200
|
+
createdAt: item.createdAt ?? null,
|
|
201
|
+
sequence: item.sequence ?? null,
|
|
202
|
+
kind: item.kind ?? null,
|
|
203
|
+
messageId: item.messageId ?? null,
|
|
204
|
+
relatedInteractionId: item.relatedInteractionId ?? null
|
|
205
|
+
}))
|
|
206
|
+
})
|
|
207
|
+
.replace(/^sha256-/u, "")
|
|
208
|
+
.slice(0, 12);
|
|
209
|
+
const exportName = `${turn.sessionId}-${exportSeed}`;
|
|
210
|
+
return {
|
|
211
|
+
rootDir: path.join(loopRoot, "event-exports", exportName),
|
|
212
|
+
exportName
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function withContinuousTurnExport(turn, loopRoot) {
|
|
216
|
+
if (turn.export !== undefined && turn.export !== null) {
|
|
217
|
+
return {
|
|
218
|
+
...turn,
|
|
219
|
+
export: {
|
|
220
|
+
...turn.export
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...turn,
|
|
226
|
+
export: buildContinuousTurnExport(turn, loopRoot)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function buildPackVersion(version, target) {
|
|
230
|
+
return {
|
|
231
|
+
version,
|
|
232
|
+
packId: target.packId,
|
|
233
|
+
routePolicy: target.routePolicy,
|
|
234
|
+
routerIdentity: target.routerIdentity,
|
|
235
|
+
workspaceSnapshot: target.workspaceSnapshot,
|
|
236
|
+
workspaceRevision: target.workspaceRevision,
|
|
237
|
+
eventRange: {
|
|
238
|
+
start: target.eventRange.start,
|
|
239
|
+
end: target.eventRange.end,
|
|
240
|
+
count: target.eventRange.count
|
|
241
|
+
},
|
|
242
|
+
eventExportDigest: target.eventExportDigest,
|
|
243
|
+
builtAt: target.builtAt
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function buildLearningCandidateTarget(candidate) {
|
|
247
|
+
return {
|
|
248
|
+
packId: candidate.summary.packId,
|
|
249
|
+
routePolicy: candidate.summary.routePolicy,
|
|
250
|
+
routerIdentity: candidate.payloads.router?.routerIdentity ?? null,
|
|
251
|
+
workspaceSnapshot: candidate.summary.workspaceSnapshot,
|
|
252
|
+
workspaceRevision: candidate.manifest.provenance.workspace.revision,
|
|
253
|
+
eventRange: {
|
|
254
|
+
start: candidate.summary.eventRange.start,
|
|
255
|
+
end: candidate.summary.eventRange.end,
|
|
256
|
+
count: candidate.summary.eventRange.count
|
|
257
|
+
},
|
|
258
|
+
eventExportDigest: candidate.summary.eventExportDigest,
|
|
259
|
+
builtAt: candidate.manifest.provenance.builtAt
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function registerPackVersion(state, target) {
|
|
263
|
+
const existing = state.packLineage.find((entry) => entry.packId === target.packId);
|
|
264
|
+
if (existing !== undefined) {
|
|
265
|
+
return cloneContinuousProductLoopPackVersion(existing);
|
|
266
|
+
}
|
|
267
|
+
const created = buildPackVersion(state.nextPackVersion, target);
|
|
268
|
+
state.packLineage.push(cloneContinuousProductLoopPackVersion(created));
|
|
269
|
+
state.nextPackVersion += 1;
|
|
270
|
+
return created;
|
|
271
|
+
}
|
|
272
|
+
function tryReadActivePackTarget(rootDir) {
|
|
273
|
+
try {
|
|
274
|
+
return describeActivationTarget(rootDir, "active", { requireActivationReady: true });
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function syncContinuousActivePack(state) {
|
|
281
|
+
const activeTarget = tryReadActivePackTarget(state.activationRoot);
|
|
282
|
+
if (activeTarget === null) {
|
|
283
|
+
state.currentActivePack = null;
|
|
284
|
+
state.activePackVersion = 0;
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const activePack = registerPackVersion(state, activeTarget);
|
|
288
|
+
state.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
|
|
289
|
+
state.activePackVersion = activePack.version;
|
|
290
|
+
return activePack;
|
|
291
|
+
}
|
|
292
|
+
function buildContinuousPackRoot(loopRoot, packVersion) {
|
|
293
|
+
return path.join(loopRoot, "packs", `v${String(packVersion.version).padStart(4, "0")}-${packVersion.packId}`);
|
|
294
|
+
}
|
|
295
|
+
export function buildCanonicalSupervision(normalizedEventExport) {
|
|
296
|
+
const feedback = normalizedEventExport.feedbackEvents.map((event) => ({
|
|
297
|
+
eventId: event.eventId,
|
|
298
|
+
kind: event.kind,
|
|
299
|
+
sequence: event.sequence,
|
|
300
|
+
createdAt: event.createdAt,
|
|
301
|
+
content: event.content,
|
|
302
|
+
relatedInteractionId: event.relatedInteractionId ?? null
|
|
303
|
+
}));
|
|
304
|
+
const compilePackIds = [
|
|
305
|
+
...new Set(normalizedEventExport.interactionEvents.flatMap((event) => event.kind === "memory_compiled" && event.packId ? [event.packId] : []))
|
|
306
|
+
];
|
|
307
|
+
const relatedInteractionIds = [...new Set(feedback.flatMap((event) => (event.relatedInteractionId ? [event.relatedInteractionId] : [])))];
|
|
308
|
+
const feedbackCounts = {
|
|
309
|
+
corrections: feedback.filter((event) => event.kind === "correction").length,
|
|
310
|
+
teachings: feedback.filter((event) => event.kind === "teaching").length,
|
|
311
|
+
approvals: feedback.filter((event) => event.kind === "approval").length,
|
|
312
|
+
suppressions: feedback.filter((event) => event.kind === "suppression").length
|
|
313
|
+
};
|
|
314
|
+
const supervisionDigest = checksumJsonPayload({
|
|
315
|
+
exportDigest: normalizedEventExport.provenance.exportDigest,
|
|
316
|
+
eventRange: {
|
|
317
|
+
start: normalizedEventExport.range.start,
|
|
318
|
+
end: normalizedEventExport.range.end,
|
|
319
|
+
count: normalizedEventExport.range.count
|
|
320
|
+
},
|
|
321
|
+
sourceStreams: normalizedEventExport.provenance.sourceStreams,
|
|
322
|
+
humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
|
|
323
|
+
selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
|
|
324
|
+
feedback,
|
|
325
|
+
compilePackIds,
|
|
326
|
+
relatedInteractionIds
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
runtimeOwner: "openclaw",
|
|
330
|
+
exportDigest: normalizedEventExport.provenance.exportDigest,
|
|
331
|
+
supervisionDigest,
|
|
332
|
+
sessionId: normalizedEventExport.provenance.sessionId,
|
|
333
|
+
channel: normalizedEventExport.provenance.channel,
|
|
334
|
+
eventRange: {
|
|
335
|
+
start: normalizedEventExport.range.start,
|
|
336
|
+
end: normalizedEventExport.range.end,
|
|
337
|
+
count: normalizedEventExport.range.count
|
|
338
|
+
},
|
|
339
|
+
sourceStreams: [...normalizedEventExport.provenance.sourceStreams],
|
|
340
|
+
humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
|
|
341
|
+
selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
|
|
342
|
+
feedbackCounts,
|
|
343
|
+
compilePackIds,
|
|
344
|
+
relatedInteractionIds,
|
|
345
|
+
feedback
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
export function createContinuousProductLoopState(input) {
|
|
349
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
350
|
+
const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
|
|
351
|
+
const activeTarget = tryReadActivePackTarget(activationRoot);
|
|
352
|
+
const activePack = activeTarget === null ? null : buildPackVersion(1, activeTarget);
|
|
353
|
+
return {
|
|
354
|
+
runtimeOwner: "openclaw",
|
|
355
|
+
activationRoot,
|
|
356
|
+
loopRoot,
|
|
357
|
+
interactionEvents: [],
|
|
358
|
+
feedbackEvents: [],
|
|
359
|
+
learner: createAlwaysOnLearningRuntimeState(),
|
|
360
|
+
runtimePlasticity: null,
|
|
361
|
+
activePackVersion: activePack?.version ?? 0,
|
|
362
|
+
currentActivePack: activePack === null ? null : cloneContinuousProductLoopPackVersion(activePack),
|
|
363
|
+
candidatePack: null,
|
|
364
|
+
packLineage: activePack === null ? [] : [cloneContinuousProductLoopPackVersion(activePack)],
|
|
365
|
+
nextPackVersion: activePack === null ? 1 : 2,
|
|
366
|
+
promotionCount: 0,
|
|
367
|
+
lastSupervision: null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function mergeUniqueEvents(current, additions) {
|
|
371
|
+
const merged = new Map();
|
|
372
|
+
for (const event of [...current, ...additions]) {
|
|
373
|
+
merged.set(buildNormalizedEventDedupId(event), structuredClone(event));
|
|
374
|
+
}
|
|
375
|
+
return [...merged.values()].sort((left, right) => left.sequence - right.sequence || left.createdAt.localeCompare(right.createdAt));
|
|
376
|
+
}
|
|
377
|
+
function mergeTeacherArtifacts(current, additions) {
|
|
378
|
+
const merged = new Map();
|
|
379
|
+
for (const artifact of [...current, ...additions]) {
|
|
380
|
+
const existing = merged.get(artifact.dedupId);
|
|
381
|
+
if (existing === undefined ||
|
|
382
|
+
Date.parse(artifact.freshness.observedAt) > Date.parse(existing.freshness.observedAt) ||
|
|
383
|
+
(artifact.freshness.observedAt === existing.freshness.observedAt && artifact.artifactId.localeCompare(existing.artifactId) < 0)) {
|
|
384
|
+
merged.set(artifact.dedupId, structuredClone(artifact));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return [...merged.values()].sort((left, right) => {
|
|
388
|
+
if (left.freshness.status !== right.freshness.status) {
|
|
389
|
+
return left.freshness.status === "fresh" ? -1 : 1;
|
|
390
|
+
}
|
|
391
|
+
if (left.createdAt !== right.createdAt) {
|
|
392
|
+
return Date.parse(right.createdAt) - Date.parse(left.createdAt);
|
|
393
|
+
}
|
|
394
|
+
return left.artifactId.localeCompare(right.artifactId);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function latestTeacherFreshness(artifacts) {
|
|
398
|
+
return artifacts[0]?.freshness.status ?? "none";
|
|
399
|
+
}
|
|
400
|
+
export class AsyncTeacherLiveLoop {
|
|
401
|
+
input;
|
|
402
|
+
queueCapacity;
|
|
403
|
+
staleAfterMs;
|
|
404
|
+
queuedExportDigests = new Set();
|
|
405
|
+
seenExportDigests = new Set();
|
|
406
|
+
queue = [];
|
|
407
|
+
drainPromise = null;
|
|
408
|
+
interactionEvents = [];
|
|
409
|
+
feedbackEvents = [];
|
|
410
|
+
teacherArtifacts = [];
|
|
411
|
+
learnerState = createAlwaysOnLearningRuntimeState();
|
|
412
|
+
lastMaterialization = null;
|
|
413
|
+
diagnostics = {
|
|
414
|
+
acceptedExportCount: 0,
|
|
415
|
+
processedExportCount: 0,
|
|
416
|
+
duplicateExportCount: 0,
|
|
417
|
+
droppedExportCount: 0,
|
|
418
|
+
emittedArtifactCount: 0,
|
|
419
|
+
dedupedArtifactCount: 0,
|
|
420
|
+
lastProcessedAt: null,
|
|
421
|
+
latestFreshness: "none",
|
|
422
|
+
lastNoOpReason: "none",
|
|
423
|
+
notes: buildAsyncTeacherLoopNotes({
|
|
424
|
+
queueDepth: 0,
|
|
425
|
+
latestFreshness: "none",
|
|
426
|
+
artifactCount: 0,
|
|
427
|
+
emittedArtifactCount: 0,
|
|
428
|
+
dedupedArtifactCount: 0,
|
|
429
|
+
sparseFeedback: this.learnerState.sparseFeedback,
|
|
430
|
+
noOpReason: "none",
|
|
431
|
+
materialization: null
|
|
432
|
+
})
|
|
433
|
+
};
|
|
434
|
+
constructor(input) {
|
|
435
|
+
this.input = input;
|
|
436
|
+
this.queueCapacity = input.maxQueuedExports ?? DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY;
|
|
437
|
+
this.staleAfterMs = input.staleAfterMs ?? DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS;
|
|
438
|
+
if (!Number.isInteger(this.queueCapacity) || this.queueCapacity <= 0) {
|
|
439
|
+
throw new Error("maxQueuedExports must be a positive integer");
|
|
440
|
+
}
|
|
441
|
+
if (!Number.isInteger(this.staleAfterMs) || this.staleAfterMs <= 0) {
|
|
442
|
+
throw new Error("staleAfterMs must be a positive integer");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
enqueueNormalizedEventExport(normalizedEventExport, options = {}) {
|
|
446
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
447
|
+
if (validationErrors.length > 0) {
|
|
448
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
449
|
+
}
|
|
450
|
+
const exportDigest = normalizedEventExport.provenance.exportDigest;
|
|
451
|
+
if (this.seenExportDigests.has(exportDigest) || this.queuedExportDigests.has(exportDigest)) {
|
|
452
|
+
this.diagnostics.duplicateExportCount += 1;
|
|
453
|
+
this.diagnostics.lastNoOpReason = "duplicate_export";
|
|
454
|
+
this.refreshNotes();
|
|
455
|
+
return {
|
|
456
|
+
accepted: false,
|
|
457
|
+
exportDigest,
|
|
458
|
+
queueDepth: this.queue.length,
|
|
459
|
+
notes: [...this.diagnostics.notes],
|
|
460
|
+
reason: "duplicate_export"
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
if (this.queue.length >= this.queueCapacity) {
|
|
464
|
+
this.diagnostics.droppedExportCount += 1;
|
|
465
|
+
this.diagnostics.lastNoOpReason = "queue_full";
|
|
466
|
+
this.refreshNotes();
|
|
467
|
+
return {
|
|
468
|
+
accepted: false,
|
|
469
|
+
exportDigest,
|
|
470
|
+
queueDepth: this.queue.length,
|
|
471
|
+
notes: [...this.diagnostics.notes],
|
|
472
|
+
reason: "queue_full"
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const observedAt = options.observedAt ?? normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString();
|
|
476
|
+
this.queue.push({
|
|
477
|
+
jobId: `teacher-loop-${createHash("sha256").update(`${exportDigest}:${observedAt}`).digest("hex")}`,
|
|
478
|
+
exportDigest,
|
|
479
|
+
observedAt,
|
|
480
|
+
normalizedEventExport: structuredClone(normalizedEventExport)
|
|
481
|
+
});
|
|
482
|
+
this.queuedExportDigests.add(exportDigest);
|
|
483
|
+
this.diagnostics.acceptedExportCount += 1;
|
|
484
|
+
this.refreshNotes();
|
|
485
|
+
void this.ensureDrain();
|
|
486
|
+
return {
|
|
487
|
+
accepted: true,
|
|
488
|
+
exportDigest,
|
|
489
|
+
queueDepth: this.queue.length,
|
|
490
|
+
notes: [...this.diagnostics.notes],
|
|
491
|
+
reason: null
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
async flush() {
|
|
495
|
+
await this.ensureDrain();
|
|
496
|
+
return this.snapshot();
|
|
497
|
+
}
|
|
498
|
+
snapshot() {
|
|
499
|
+
return {
|
|
500
|
+
runtimeOwner: "openclaw",
|
|
501
|
+
queue: {
|
|
502
|
+
capacity: this.queueCapacity,
|
|
503
|
+
depth: this.queue.length,
|
|
504
|
+
running: this.drainPromise !== null
|
|
505
|
+
},
|
|
506
|
+
teacher: {
|
|
507
|
+
artifactCount: this.teacherArtifacts.length,
|
|
508
|
+
artifacts: cloneTeacherSupervisionArtifacts(this.teacherArtifacts),
|
|
509
|
+
latestFreshness: this.diagnostics.latestFreshness
|
|
510
|
+
},
|
|
511
|
+
learner: {
|
|
512
|
+
state: structuredClone(this.learnerState),
|
|
513
|
+
lastMaterialization: cloneAlwaysOnLearningMaterializationJobOrNull(this.lastMaterialization)
|
|
514
|
+
},
|
|
515
|
+
diagnostics: {
|
|
516
|
+
...this.diagnostics,
|
|
517
|
+
notes: [...this.diagnostics.notes]
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
async ensureDrain() {
|
|
522
|
+
if (this.drainPromise === null) {
|
|
523
|
+
this.drainPromise = this.drain().finally(() => {
|
|
524
|
+
this.drainPromise = null;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
await this.drainPromise;
|
|
528
|
+
if (this.queue.length > 0) {
|
|
529
|
+
await this.ensureDrain();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async drain() {
|
|
533
|
+
while (this.queue.length > 0) {
|
|
534
|
+
const job = this.queue.shift();
|
|
535
|
+
this.queuedExportDigests.delete(job.exportDigest);
|
|
536
|
+
this.seenExportDigests.add(job.exportDigest);
|
|
537
|
+
this.interactionEvents = mergeUniqueEvents(this.interactionEvents, job.normalizedEventExport.interactionEvents);
|
|
538
|
+
this.feedbackEvents = mergeUniqueEvents(this.feedbackEvents, job.normalizedEventExport.feedbackEvents);
|
|
539
|
+
const mergedNormalizedEventExport = buildNormalizedEventExport({
|
|
540
|
+
interactionEvents: this.interactionEvents,
|
|
541
|
+
feedbackEvents: this.feedbackEvents
|
|
542
|
+
});
|
|
543
|
+
const builtArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
|
|
544
|
+
normalizedEventExport: mergedNormalizedEventExport,
|
|
545
|
+
observedAt: job.observedAt,
|
|
546
|
+
staleAfterMs: this.staleAfterMs,
|
|
547
|
+
...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {})
|
|
548
|
+
});
|
|
549
|
+
const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
|
|
550
|
+
const nextTeacherArtifacts = mergeTeacherArtifacts(this.teacherArtifacts, builtArtifacts);
|
|
551
|
+
const emittedArtifactCount = builtArtifacts.filter((artifact) => !currentDedupIds.has(artifact.dedupId)).length;
|
|
552
|
+
const dedupedArtifactCount = builtArtifacts.length - emittedArtifactCount;
|
|
553
|
+
this.teacherArtifacts = nextTeacherArtifacts;
|
|
554
|
+
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
555
|
+
packLabel: this.input.packLabel,
|
|
556
|
+
workspace: this.input.workspace,
|
|
557
|
+
interactionEvents: this.interactionEvents,
|
|
558
|
+
feedbackEvents: this.feedbackEvents,
|
|
559
|
+
teacherSupervisionArtifacts: this.teacherArtifacts,
|
|
560
|
+
learnedRouting: this.input.learnedRouting,
|
|
561
|
+
state: this.learnerState,
|
|
562
|
+
builtAt: this.input.builtAt ?? job.observedAt,
|
|
563
|
+
...(this.input.offlineArtifacts !== undefined ? { offlineArtifacts: this.input.offlineArtifacts } : {}),
|
|
564
|
+
...(this.input.structuralOps !== undefined ? { structuralOps: this.input.structuralOps } : {}),
|
|
565
|
+
...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {}),
|
|
566
|
+
...(this.input.liveSliceSize !== undefined ? { liveSliceSize: this.input.liveSliceSize } : {}),
|
|
567
|
+
...(this.input.backfillSliceSize !== undefined ? { backfillSliceSize: this.input.backfillSliceSize } : {}),
|
|
568
|
+
...(this.input.cadence !== undefined ? { cadence: this.input.cadence } : {})
|
|
569
|
+
});
|
|
570
|
+
this.learnerState = structuredClone(learnerResult.state);
|
|
571
|
+
this.lastMaterialization = cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization);
|
|
572
|
+
this.diagnostics.processedExportCount += 1;
|
|
573
|
+
this.diagnostics.emittedArtifactCount += emittedArtifactCount;
|
|
574
|
+
this.diagnostics.dedupedArtifactCount += dedupedArtifactCount;
|
|
575
|
+
this.diagnostics.lastProcessedAt = job.observedAt;
|
|
576
|
+
this.diagnostics.latestFreshness = latestTeacherFreshness(this.teacherArtifacts);
|
|
577
|
+
this.diagnostics.lastNoOpReason = emittedArtifactCount === 0 ? "no_teacher_artifacts" : "none";
|
|
578
|
+
this.refreshNotes();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
refreshNotes() {
|
|
582
|
+
this.diagnostics.notes = buildAsyncTeacherLoopNotes({
|
|
583
|
+
queueDepth: this.queue.length,
|
|
584
|
+
latestFreshness: this.diagnostics.latestFreshness,
|
|
585
|
+
artifactCount: this.teacherArtifacts.length,
|
|
586
|
+
emittedArtifactCount: this.diagnostics.emittedArtifactCount,
|
|
587
|
+
dedupedArtifactCount: this.diagnostics.dedupedArtifactCount,
|
|
588
|
+
sparseFeedback: this.learnerState.sparseFeedback,
|
|
589
|
+
noOpReason: this.diagnostics.lastNoOpReason,
|
|
590
|
+
materialization: this.lastMaterialization
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
export function createAsyncTeacherLiveLoop(input) {
|
|
595
|
+
return new AsyncTeacherLiveLoop(input);
|
|
596
|
+
}
|
|
18
597
|
function readJsonFile(filePath) {
|
|
19
598
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
20
599
|
}
|
|
@@ -209,15 +788,38 @@ export function formatPromptContext(compileResponse) {
|
|
|
209
788
|
lines.push("[/BRAIN_CONTEXT]");
|
|
210
789
|
return `${lines.join("\n")}\n`;
|
|
211
790
|
}
|
|
212
|
-
function
|
|
791
|
+
function failOpenCompileResult(error, activationRoot) {
|
|
213
792
|
return {
|
|
214
793
|
ok: false,
|
|
215
794
|
fallbackToStaticContext: true,
|
|
795
|
+
hardRequirementViolated: false,
|
|
216
796
|
activationRoot: path.resolve(activationRoot),
|
|
217
797
|
error: toErrorMessage(error),
|
|
218
798
|
brainContext: ""
|
|
219
799
|
};
|
|
220
800
|
}
|
|
801
|
+
function classifyCompileFailure(error, activationRoot) {
|
|
802
|
+
const resolvedActivationRoot = path.resolve(activationRoot);
|
|
803
|
+
try {
|
|
804
|
+
const inspection = inspectActivationState(resolvedActivationRoot);
|
|
805
|
+
const active = inspection.active;
|
|
806
|
+
if (active !== null && active.routePolicy === "requires_learned_routing") {
|
|
807
|
+
const failureReason = active.findings.length > 0 ? active.findings.join("; ") : toErrorMessage(error);
|
|
808
|
+
return {
|
|
809
|
+
ok: false,
|
|
810
|
+
fallbackToStaticContext: false,
|
|
811
|
+
hardRequirementViolated: true,
|
|
812
|
+
activationRoot: resolvedActivationRoot,
|
|
813
|
+
error: `Learned-routing hotpath hard requirement violated for active pack ${active.packId} (routerIdentity=${active.routerIdentity ?? "null"}): ${failureReason}`,
|
|
814
|
+
brainContext: ""
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
return failOpenCompileResult(error, resolvedActivationRoot);
|
|
820
|
+
}
|
|
821
|
+
return failOpenCompileResult(error, resolvedActivationRoot);
|
|
822
|
+
}
|
|
221
823
|
export function resolveActivePackForCompile(activationRoot) {
|
|
222
824
|
const resolvedActivationRoot = path.resolve(normalizeNonEmptyString(activationRoot, "activationRoot"));
|
|
223
825
|
const inspection = inspectActivationState(resolvedActivationRoot);
|
|
@@ -240,28 +842,435 @@ export function compileRuntimeContext(input) {
|
|
|
240
842
|
const runtimeHints = normalizeRuntimeHints(input.runtimeHints);
|
|
241
843
|
try {
|
|
242
844
|
const target = resolveActivePackForCompile(activationRoot);
|
|
243
|
-
const
|
|
845
|
+
const compile = compileRuntimeFromActivation(activationRoot, {
|
|
244
846
|
contract: CONTRACT_IDS.runtimeCompile,
|
|
245
847
|
agentId,
|
|
246
848
|
userMessage: normalizeNonEmptyString(input.message, "message"),
|
|
247
849
|
maxContextBlocks: normalizeNonNegativeInteger(input.maxContextBlocks, "maxContextBlocks", 4),
|
|
850
|
+
...(input.maxContextChars !== undefined
|
|
851
|
+
? { maxContextChars: normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars) }
|
|
852
|
+
: {}),
|
|
248
853
|
modeRequested: normalizeMode(input.mode),
|
|
249
854
|
activePackId: target.activePointer.packId,
|
|
855
|
+
...(input.compactionMode !== undefined ? { compactionMode: input.compactionMode } : {}),
|
|
250
856
|
...(runtimeHints.length > 0 ? { runtimeHints } : {})
|
|
251
857
|
});
|
|
858
|
+
const compileResponse = {
|
|
859
|
+
...compile.response,
|
|
860
|
+
diagnostics: {
|
|
861
|
+
...compile.response.diagnostics,
|
|
862
|
+
notes: [...compile.response.diagnostics.notes, "OpenClaw remains the runtime owner"]
|
|
863
|
+
}
|
|
864
|
+
};
|
|
252
865
|
return {
|
|
253
866
|
ok: true,
|
|
254
867
|
fallbackToStaticContext: false,
|
|
868
|
+
hardRequirementViolated: false,
|
|
255
869
|
activationRoot,
|
|
256
|
-
activePackId: target.
|
|
870
|
+
activePackId: compile.target.packId,
|
|
257
871
|
packRootDir: path.resolve(target.activePointer.packRootDir),
|
|
258
872
|
compileResponse,
|
|
259
873
|
brainContext: formatPromptContext(compileResponse)
|
|
260
874
|
};
|
|
261
875
|
}
|
|
262
876
|
catch (error) {
|
|
263
|
-
return
|
|
877
|
+
return classifyCompileFailure(error, activationRoot);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function readDiagnosticNoteValue(notes, prefix) {
|
|
881
|
+
const note = notes.find((entry) => entry.startsWith(prefix));
|
|
882
|
+
return note === undefined ? null : note.slice(prefix.length);
|
|
883
|
+
}
|
|
884
|
+
function readDiagnosticNoteList(notes, prefix) {
|
|
885
|
+
const value = readDiagnosticNoteValue(notes, prefix);
|
|
886
|
+
if (value === null || value.length === 0) {
|
|
887
|
+
return [];
|
|
888
|
+
}
|
|
889
|
+
return value
|
|
890
|
+
.split("|")
|
|
891
|
+
.map((entry) => entry.trim())
|
|
892
|
+
.filter((entry) => entry.length > 0);
|
|
893
|
+
}
|
|
894
|
+
function sortedUniqueStrings(values) {
|
|
895
|
+
return [...new Set(values.filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
896
|
+
}
|
|
897
|
+
function isStableKernelContextBlock(block) {
|
|
898
|
+
if (block.id.includes(":event:") || block.id.includes(":teacher:")) {
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
if (block.source.startsWith("split:") || block.source.startsWith("merge:")) {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
function buildContextAttributionSummary(input) {
|
|
907
|
+
const selectionTiers = readDiagnosticNoteValue(input.notes ?? [], "selection_tiers=");
|
|
908
|
+
const selectedContext = [...(input.selectedContext ?? [])];
|
|
909
|
+
const stableKernelBlocks = selectedContext.filter((block) => isStableKernelContextBlock(block));
|
|
910
|
+
const brainCompiledBlocks = selectedContext.filter((block) => !isStableKernelContextBlock(block));
|
|
911
|
+
if (input.unprobed) {
|
|
912
|
+
return {
|
|
913
|
+
selectedContextCount: 0,
|
|
914
|
+
stableKernelBlockCount: 0,
|
|
915
|
+
brainCompiledBlockCount: 0,
|
|
916
|
+
stableKernelSources: [],
|
|
917
|
+
brainCompiledSources: [],
|
|
918
|
+
selectionTiers,
|
|
919
|
+
evidence: "unprobed",
|
|
920
|
+
detail: "compile probe was not run, so kernel-vs-brain attribution is unknown"
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
if (input.hardRequirementViolated) {
|
|
924
|
+
return {
|
|
925
|
+
selectedContextCount: 0,
|
|
926
|
+
stableKernelBlockCount: 0,
|
|
927
|
+
brainCompiledBlockCount: 0,
|
|
928
|
+
stableKernelSources: [],
|
|
929
|
+
brainCompiledSources: [],
|
|
930
|
+
selectionTiers,
|
|
931
|
+
evidence: "hard_fail",
|
|
932
|
+
detail: "learned-routing hard requirement failed before any compiled brain context could be selected"
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
if (input.fallbackToStaticContext) {
|
|
936
|
+
return {
|
|
937
|
+
selectedContextCount: 0,
|
|
938
|
+
stableKernelBlockCount: 0,
|
|
939
|
+
brainCompiledBlockCount: 0,
|
|
940
|
+
stableKernelSources: [],
|
|
941
|
+
brainCompiledSources: [],
|
|
942
|
+
selectionTiers,
|
|
943
|
+
evidence: "fail_open_static_context",
|
|
944
|
+
detail: "serve probe fell back to static context, so no compiled brain context was selected"
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
const evidence = brainCompiledBlocks.length > 0
|
|
948
|
+
? input.usedLearnedRouteFn === true
|
|
949
|
+
? "route_fn_and_brain_context"
|
|
950
|
+
: "brain_context_only"
|
|
951
|
+
: input.usedLearnedRouteFn === true
|
|
952
|
+
? "route_fn_only"
|
|
953
|
+
: "stable_kernel_only";
|
|
954
|
+
const detailPrefix = `selected=${selectedContext.length}; tiers=${selectionTiers ?? "unknown"}; kernel=${stableKernelBlocks.length}; brain=${brainCompiledBlocks.length}`;
|
|
955
|
+
const detail = evidence === "route_fn_and_brain_context"
|
|
956
|
+
? `${detailPrefix}; learned route ran and non-seed brain-compiled context was selected`
|
|
957
|
+
: evidence === "brain_context_only"
|
|
958
|
+
? `${detailPrefix}; non-seed brain-compiled context was selected without learned-route evidence`
|
|
959
|
+
: evidence === "route_fn_only"
|
|
960
|
+
? `${detailPrefix}; learned route ran but selected context stayed inside the stable kernel`
|
|
961
|
+
: `${detailPrefix}; selected context stayed inside the stable kernel`;
|
|
962
|
+
return {
|
|
963
|
+
selectedContextCount: selectedContext.length,
|
|
964
|
+
stableKernelBlockCount: stableKernelBlocks.length,
|
|
965
|
+
brainCompiledBlockCount: brainCompiledBlocks.length,
|
|
966
|
+
stableKernelSources: sortedUniqueStrings(stableKernelBlocks.map((block) => block.source)),
|
|
967
|
+
brainCompiledSources: sortedUniqueStrings(brainCompiledBlocks.map((block) => block.source)),
|
|
968
|
+
selectionTiers,
|
|
969
|
+
evidence,
|
|
970
|
+
detail
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function buildAttachCompileStatus(result, observability, activePackId) {
|
|
974
|
+
if (!result.ok) {
|
|
975
|
+
return {
|
|
976
|
+
ok: false,
|
|
977
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
978
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
979
|
+
activePackId,
|
|
980
|
+
usedLearnedRouteFn: null,
|
|
981
|
+
routerIdentity: observability?.learnedRouteFn.routerIdentity ?? null,
|
|
982
|
+
initMode: observability?.initHandoff.initMode ?? null,
|
|
983
|
+
handoffState: observability?.initHandoff.handoffState ?? null,
|
|
984
|
+
seedSources: observability?.initHandoff.seedSources ?? [],
|
|
985
|
+
contextAttribution: buildContextAttributionSummary({
|
|
986
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
987
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
988
|
+
usedLearnedRouteFn: null
|
|
989
|
+
}),
|
|
990
|
+
notes: [],
|
|
991
|
+
error: result.error
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const notes = [...result.compileResponse.diagnostics.notes];
|
|
995
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
996
|
+
fallbackToStaticContext: false,
|
|
997
|
+
hardRequirementViolated: false,
|
|
998
|
+
usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
999
|
+
selectedContext: result.compileResponse.selectedContext,
|
|
1000
|
+
notes
|
|
1001
|
+
});
|
|
1002
|
+
return {
|
|
1003
|
+
ok: true,
|
|
1004
|
+
fallbackToStaticContext: false,
|
|
1005
|
+
hardRequirementViolated: false,
|
|
1006
|
+
activePackId: result.activePackId,
|
|
1007
|
+
usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
1008
|
+
routerIdentity: result.compileResponse.diagnostics.routerIdentity,
|
|
1009
|
+
initMode: readDiagnosticNoteValue(notes, "init_mode=") ?? observability?.initHandoff.initMode ?? null,
|
|
1010
|
+
handoffState: readDiagnosticNoteValue(notes, "handoff_state=") ?? observability?.initHandoff.handoffState ?? null,
|
|
1011
|
+
seedSources: readDiagnosticNoteList(notes, "seed_sources=").length > 0
|
|
1012
|
+
? readDiagnosticNoteList(notes, "seed_sources=")
|
|
1013
|
+
: observability?.initHandoff.seedSources ?? [],
|
|
1014
|
+
contextAttribution,
|
|
1015
|
+
notes,
|
|
1016
|
+
error: null
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
function buildAttachSuccessSignals(input) {
|
|
1020
|
+
const signals = [];
|
|
1021
|
+
const activeTarget = input.observability?.target ?? null;
|
|
1022
|
+
if (input.inspection.active?.activationReady) {
|
|
1023
|
+
signals.push(`active_pack_ready:${input.inspection.active.packId}`);
|
|
1024
|
+
}
|
|
1025
|
+
if (activeTarget !== null) {
|
|
1026
|
+
signals.push(`active_workspace_snapshot:${activeTarget.workspaceSnapshot}`);
|
|
1027
|
+
if (activeTarget.eventRange.count === 0) {
|
|
1028
|
+
signals.push("awaiting_first_export");
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (input.compile?.ok) {
|
|
1032
|
+
signals.push(`compile_ok:${input.compile.activePackId ?? "unknown"}`);
|
|
1033
|
+
}
|
|
1034
|
+
if (input.compile !== null) {
|
|
1035
|
+
signals.push(`context:${input.compile.contextAttribution.evidence}`);
|
|
1036
|
+
signals.push(`context_blocks:kernel=${input.compile.contextAttribution.stableKernelBlockCount},brain=${input.compile.contextAttribution.brainCompiledBlockCount}`);
|
|
1037
|
+
}
|
|
1038
|
+
if (input.compile?.handoffState !== null && input.compile?.handoffState !== undefined) {
|
|
1039
|
+
signals.push(`handoff:${input.compile.handoffState}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (input.inspection.active?.routePolicy === "requires_learned_routing") {
|
|
1042
|
+
if (input.compile?.ok && input.compile.usedLearnedRouteFn === true && input.compile.routerIdentity !== null) {
|
|
1043
|
+
signals.push(`learned_route_compile_verified:${input.compile.routerIdentity}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
else if (input.compile?.ok) {
|
|
1047
|
+
signals.push("heuristic_compile_verified");
|
|
1048
|
+
}
|
|
1049
|
+
return signals;
|
|
1050
|
+
}
|
|
1051
|
+
function buildAttachStatusCompileInput(activationRoot, compile) {
|
|
1052
|
+
if (compile === false) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
activationRoot,
|
|
1057
|
+
agentId: normalizeOptionalString(compile?.agentId) ?? `${DEFAULT_AGENT_ID}-attach-status`,
|
|
1058
|
+
message: normalizeOptionalString(compile?.message) ?? DEFAULT_ATTACH_STATUS_MESSAGE,
|
|
1059
|
+
...(compile?.maxContextBlocks !== undefined ? { maxContextBlocks: compile.maxContextBlocks } : {}),
|
|
1060
|
+
...(compile?.maxContextChars !== undefined ? { maxContextChars: compile.maxContextChars } : {}),
|
|
1061
|
+
...(compile?.mode !== undefined ? { mode: compile.mode } : {}),
|
|
1062
|
+
...(compile?.compactionMode !== undefined ? { compactionMode: compile.compactionMode } : {}),
|
|
1063
|
+
runtimeHints: compile?.runtimeHints ?? [...DEFAULT_ATTACH_STATUS_RUNTIME_HINTS]
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
export function describeAttachStatus(input) {
|
|
1067
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1068
|
+
const inspection = inspectActivationState(activationRoot);
|
|
1069
|
+
const activeObservability = inspection.active === null ? null : describeActivationObservability(activationRoot, "active");
|
|
1070
|
+
const compileInput = buildAttachStatusCompileInput(activationRoot, input.compile);
|
|
1071
|
+
const compile = compileInput === null
|
|
1072
|
+
? null
|
|
1073
|
+
: buildAttachCompileStatus(compileRuntimeContext(compileInput), activeObservability, inspection.active?.packId ?? null);
|
|
1074
|
+
return {
|
|
1075
|
+
runtimeOwner: "openclaw",
|
|
1076
|
+
activationRoot,
|
|
1077
|
+
inspection,
|
|
1078
|
+
activeObservability,
|
|
1079
|
+
compile,
|
|
1080
|
+
landingBoundaries: structuredClone(OPENCLAW_LANDING_BOUNDARIES_V1),
|
|
1081
|
+
successSignals: buildAttachSuccessSignals({
|
|
1082
|
+
inspection,
|
|
1083
|
+
observability: activeObservability,
|
|
1084
|
+
compile
|
|
1085
|
+
})
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
export function rollbackRuntimeAttach(input) {
|
|
1089
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1090
|
+
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
1091
|
+
const dryRun = input.dryRun === true;
|
|
1092
|
+
const before = inspectActivationState(activationRoot, updatedAt);
|
|
1093
|
+
const findings = [...before.rollback.findings];
|
|
1094
|
+
const allowed = before.rollback.allowed;
|
|
1095
|
+
if (!allowed) {
|
|
1096
|
+
return {
|
|
1097
|
+
runtimeOwner: "openclaw",
|
|
1098
|
+
activationRoot,
|
|
1099
|
+
updatedAt,
|
|
1100
|
+
dryRun,
|
|
1101
|
+
allowed,
|
|
1102
|
+
findings,
|
|
1103
|
+
before: {
|
|
1104
|
+
activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
|
|
1105
|
+
candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
|
|
1106
|
+
previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
|
|
1107
|
+
},
|
|
1108
|
+
after: null,
|
|
1109
|
+
restoredPackId: null,
|
|
1110
|
+
parkedCandidatePackId: null
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
const after = dryRun
|
|
1114
|
+
? before.rollback.nextPointers
|
|
1115
|
+
: rollbackActivePack(activationRoot, {
|
|
1116
|
+
updatedAt,
|
|
1117
|
+
reason: "runtime_attach_rollback"
|
|
1118
|
+
}).pointers;
|
|
1119
|
+
return {
|
|
1120
|
+
runtimeOwner: "openclaw",
|
|
1121
|
+
activationRoot,
|
|
1122
|
+
updatedAt,
|
|
1123
|
+
dryRun,
|
|
1124
|
+
allowed,
|
|
1125
|
+
findings,
|
|
1126
|
+
before: {
|
|
1127
|
+
activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
|
|
1128
|
+
candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
|
|
1129
|
+
previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
|
|
1130
|
+
},
|
|
1131
|
+
after: after === null ? null : {
|
|
1132
|
+
activePackId: after.active?.packId ?? null,
|
|
1133
|
+
candidatePackId: after.candidate?.packId ?? null,
|
|
1134
|
+
previousPackId: after.previous?.packId ?? null
|
|
1135
|
+
},
|
|
1136
|
+
restoredPackId: after?.active?.packId ?? null,
|
|
1137
|
+
parkedCandidatePackId: after?.candidate?.packId ?? null
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function resolveBootstrapNormalizedEventExport(input) {
|
|
1141
|
+
const interactionEvents = [...(input.interactionEvents ?? [])];
|
|
1142
|
+
const feedbackEvents = [...(input.feedbackEvents ?? [])];
|
|
1143
|
+
if (input.normalizedEventExport !== undefined && (interactionEvents.length > 0 || feedbackEvents.length > 0)) {
|
|
1144
|
+
throw new Error("Provide normalizedEventExport or interactionEvents/feedbackEvents, not both");
|
|
1145
|
+
}
|
|
1146
|
+
const normalizedEventExport = input.normalizedEventExport !== undefined
|
|
1147
|
+
? canonicalizeBootstrapNormalizedEventExport(input.normalizedEventExport)
|
|
1148
|
+
: buildNormalizedEventExport({
|
|
1149
|
+
interactionEvents,
|
|
1150
|
+
feedbackEvents
|
|
1151
|
+
});
|
|
1152
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
1153
|
+
if (validationErrors.length > 0) {
|
|
1154
|
+
throw new Error(formatBootstrapNormalizedEventExportValidationError(normalizedEventExport, validationErrors));
|
|
1155
|
+
}
|
|
1156
|
+
return normalizedEventExport;
|
|
1157
|
+
}
|
|
1158
|
+
function canonicalizeBootstrapNormalizedEventExport(normalizedEventExport) {
|
|
1159
|
+
const interactionEvents = cloneBootstrapNormalizedEventArray(normalizedEventExport.interactionEvents, "interactionEvents");
|
|
1160
|
+
const feedbackEvents = cloneBootstrapNormalizedEventArray(normalizedEventExport.feedbackEvents, "feedbackEvents");
|
|
1161
|
+
try {
|
|
1162
|
+
return buildNormalizedEventExport({
|
|
1163
|
+
interactionEvents,
|
|
1164
|
+
feedbackEvents
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1169
|
+
throw new Error("bootstrapRuntimeAttach could not reconstruct a safe normalized event export from the provided event arrays. " +
|
|
1170
|
+
"Repair the event payload or, for a first attach with no live events yet, pass empty arrays so bootstrap can self-boot. " +
|
|
1171
|
+
`Details: ${detail}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function cloneBootstrapNormalizedEventArray(value, fieldName) {
|
|
1175
|
+
if (!Array.isArray(value)) {
|
|
1176
|
+
throw new Error(`bootstrapRuntimeAttach expected normalizedEventExport.${fieldName} to be an array. ` +
|
|
1177
|
+
"For a first attach with no live events yet, pass empty arrays or omit normalizedEventExport and use interactionEvents: [] / feedbackEvents: [].");
|
|
1178
|
+
}
|
|
1179
|
+
return structuredClone(value);
|
|
1180
|
+
}
|
|
1181
|
+
function formatBootstrapNormalizedEventExportValidationError(normalizedEventExport, validationErrors) {
|
|
1182
|
+
const details = validationErrors.join("; ");
|
|
1183
|
+
const zeroEventBootstrap = normalizedEventExport.interactionEvents.length === 0 &&
|
|
1184
|
+
normalizedEventExport.feedbackEvents.length === 0 &&
|
|
1185
|
+
normalizedEventExport.range.count === 0;
|
|
1186
|
+
if (zeroEventBootstrap) {
|
|
1187
|
+
return ("bootstrapRuntimeAttach could not derive a safe zero-event bootstrap export: " +
|
|
1188
|
+
`${details}. ` +
|
|
1189
|
+
"For a first attach with no live events yet, pass empty interaction/feedback arrays or an empty normalized export payload and bootstrap will self-boot.");
|
|
1190
|
+
}
|
|
1191
|
+
return ("bootstrapRuntimeAttach could not use the provided normalized event export: " +
|
|
1192
|
+
`${details}. ` +
|
|
1193
|
+
"Repair the event payload or pass raw interactionEvents/feedbackEvents so bootstrap can derive range and provenance itself.");
|
|
1194
|
+
}
|
|
1195
|
+
export function bootstrapRuntimeAttach(input) {
|
|
1196
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1197
|
+
const packRoot = path.resolve(normalizeNonEmptyString(input.packRoot, "packRoot"));
|
|
1198
|
+
const normalizedEventExport = resolveBootstrapNormalizedEventExport(input);
|
|
1199
|
+
const builtAt = normalizeIsoTimestamp(input.builtAt, "builtAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString());
|
|
1200
|
+
const activatedAt = normalizeIsoTimestamp(input.activatedAt, "activatedAt", builtAt);
|
|
1201
|
+
const teacherSupervisionArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
|
|
1202
|
+
normalizedEventExport,
|
|
1203
|
+
observedAt: activatedAt,
|
|
1204
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
|
|
1205
|
+
});
|
|
1206
|
+
const descriptor = materializeCandidatePackFromNormalizedEventExport(packRoot, {
|
|
1207
|
+
packLabel: normalizeNonEmptyString(input.packLabel, "packLabel"),
|
|
1208
|
+
workspace: input.workspace,
|
|
1209
|
+
normalizedEventExport,
|
|
1210
|
+
teacherSupervisionArtifacts,
|
|
1211
|
+
learnedRouting: input.learnedRouting ?? true,
|
|
1212
|
+
builtAt,
|
|
1213
|
+
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1214
|
+
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
1215
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
|
|
1216
|
+
});
|
|
1217
|
+
activatePack(activationRoot, packRoot, {
|
|
1218
|
+
updatedAt: activatedAt,
|
|
1219
|
+
reason: "attach_bootstrap"
|
|
1220
|
+
});
|
|
1221
|
+
return {
|
|
1222
|
+
runtimeOwner: "openclaw",
|
|
1223
|
+
activationRoot,
|
|
1224
|
+
packRoot,
|
|
1225
|
+
packId: descriptor.manifest.packId,
|
|
1226
|
+
normalizedEventExport,
|
|
1227
|
+
status: describeAttachStatus({
|
|
1228
|
+
activationRoot,
|
|
1229
|
+
...(input.compile !== undefined ? { compile: input.compile } : {})
|
|
1230
|
+
})
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function buildRuntimeTurnAttribution(input) {
|
|
1234
|
+
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.turn.brainAttachmentPolicy);
|
|
1235
|
+
if (input.compileResult.ok) {
|
|
1236
|
+
const notes = input.compileResult.compileResponse.diagnostics.notes;
|
|
1237
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
1238
|
+
fallbackToStaticContext: false,
|
|
1239
|
+
hardRequirementViolated: false,
|
|
1240
|
+
usedLearnedRouteFn: input.compileResult.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
1241
|
+
selectedContext: input.compileResult.compileResponse.selectedContext,
|
|
1242
|
+
notes
|
|
1243
|
+
});
|
|
1244
|
+
return {
|
|
1245
|
+
hostRuntimeOwner: "openclaw",
|
|
1246
|
+
profileSelector: "current_profile",
|
|
1247
|
+
brainAttachmentPolicy,
|
|
1248
|
+
brainStatus: "serving_active_pack",
|
|
1249
|
+
activePackId: input.compileResult.activePackId,
|
|
1250
|
+
usedLearnedRouteFn: input.compileResult.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
1251
|
+
routerIdentity: input.compileResult.compileResponse.diagnostics.routerIdentity,
|
|
1252
|
+
selectionDigest: input.compileResult.compileResponse.diagnostics.selectionDigest,
|
|
1253
|
+
selectionTiers: readDiagnosticNoteValue(notes, "selection_tiers="),
|
|
1254
|
+
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1255
|
+
};
|
|
264
1256
|
}
|
|
1257
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
1258
|
+
fallbackToStaticContext: input.compileResult.fallbackToStaticContext,
|
|
1259
|
+
hardRequirementViolated: input.compileResult.hardRequirementViolated,
|
|
1260
|
+
usedLearnedRouteFn: null
|
|
1261
|
+
});
|
|
1262
|
+
return {
|
|
1263
|
+
hostRuntimeOwner: "openclaw",
|
|
1264
|
+
profileSelector: "current_profile",
|
|
1265
|
+
brainAttachmentPolicy,
|
|
1266
|
+
brainStatus: input.compileResult.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
1267
|
+
activePackId: null,
|
|
1268
|
+
usedLearnedRouteFn: null,
|
|
1269
|
+
routerIdentity: null,
|
|
1270
|
+
selectionDigest: null,
|
|
1271
|
+
selectionTiers: null,
|
|
1272
|
+
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1273
|
+
};
|
|
265
1274
|
}
|
|
266
1275
|
function buildCompileInteractionEvent(input) {
|
|
267
1276
|
if (!input.compileResult.ok) {
|
|
@@ -277,6 +1286,10 @@ function buildCompileInteractionEvent(input) {
|
|
|
277
1286
|
sessionId: input.turn.sessionId,
|
|
278
1287
|
source: input.sourceStream
|
|
279
1288
|
});
|
|
1289
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1290
|
+
turn: input.turn,
|
|
1291
|
+
compileResult: input.compileResult
|
|
1292
|
+
});
|
|
280
1293
|
return createInteractionEvent({
|
|
281
1294
|
eventId,
|
|
282
1295
|
agentId: input.agentId,
|
|
@@ -289,7 +1302,8 @@ function buildCompileInteractionEvent(input) {
|
|
|
289
1302
|
runtimeOwner: "openclaw",
|
|
290
1303
|
stream: input.sourceStream
|
|
291
1304
|
},
|
|
292
|
-
packId: input.compileResult.compileResponse.packId
|
|
1305
|
+
packId: input.compileResult.compileResponse.packId,
|
|
1306
|
+
attribution
|
|
293
1307
|
});
|
|
294
1308
|
}
|
|
295
1309
|
function buildDeliveryInteractionEvent(input) {
|
|
@@ -310,6 +1324,10 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
310
1324
|
sessionId: input.turn.sessionId,
|
|
311
1325
|
source: input.sourceStream
|
|
312
1326
|
});
|
|
1327
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1328
|
+
turn: input.turn,
|
|
1329
|
+
compileResult: input.compileResult
|
|
1330
|
+
});
|
|
313
1331
|
return createInteractionEvent({
|
|
314
1332
|
eventId,
|
|
315
1333
|
agentId: input.agentId,
|
|
@@ -322,12 +1340,17 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
322
1340
|
runtimeOwner: "openclaw",
|
|
323
1341
|
stream: input.sourceStream
|
|
324
1342
|
},
|
|
1343
|
+
attribution,
|
|
325
1344
|
...(input.compileResult.ok ? { packId: input.compileResult.compileResponse.packId } : {}),
|
|
326
1345
|
...(messageId !== undefined ? { messageId } : {})
|
|
327
1346
|
});
|
|
328
1347
|
}
|
|
329
1348
|
function buildFeedbackEvents(input) {
|
|
330
1349
|
const feedbackItems = input.turn.feedback ?? [];
|
|
1350
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1351
|
+
turn: input.turn,
|
|
1352
|
+
compileResult: input.compileResult
|
|
1353
|
+
});
|
|
331
1354
|
return feedbackItems.map((item, index) => {
|
|
332
1355
|
if (item === null) {
|
|
333
1356
|
throw new Error(`feedback[${index}] must be an object`);
|
|
@@ -364,6 +1387,7 @@ function buildFeedbackEvents(input) {
|
|
|
364
1387
|
stream: input.sourceStream
|
|
365
1388
|
},
|
|
366
1389
|
content,
|
|
1390
|
+
attribution,
|
|
367
1391
|
...(messageId !== undefined ? { messageId } : {}),
|
|
368
1392
|
...(relatedInteractionId !== undefined ? { relatedInteractionId } : {})
|
|
369
1393
|
});
|
|
@@ -396,6 +1420,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
396
1420
|
nextSequence,
|
|
397
1421
|
defaultCreatedAt: compileCreatedAt,
|
|
398
1422
|
compileInteraction,
|
|
1423
|
+
compileResult,
|
|
399
1424
|
agentId
|
|
400
1425
|
});
|
|
401
1426
|
const deliveryInteraction = buildDeliveryInteractionEvent({
|
|
@@ -467,8 +1492,35 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
467
1492
|
};
|
|
468
1493
|
const compileResult = compileRuntimeContext(compileInput);
|
|
469
1494
|
const warnings = [];
|
|
1495
|
+
const serveLoggedAt = turn.compile?.createdAt ?? turn.createdAt ?? new Date().toISOString();
|
|
1496
|
+
if (!compileResult.ok && compileResult.hardRequirementViolated) {
|
|
1497
|
+
try {
|
|
1498
|
+
appendServeTimeRouteDecisionLog({
|
|
1499
|
+
activationRoot: compileInput.activationRoot,
|
|
1500
|
+
turn,
|
|
1501
|
+
compileResult,
|
|
1502
|
+
recordedAt: serveLoggedAt
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
}
|
|
1507
|
+
throw new Error(compileResult.error);
|
|
1508
|
+
}
|
|
470
1509
|
try {
|
|
471
1510
|
const normalizedEventExport = buildNormalizedRuntimeEventExport(turn, compileResult);
|
|
1511
|
+
try {
|
|
1512
|
+
const compileEvent = normalizedEventExport.interactionEvents.find((event) => event.kind === "memory_compiled");
|
|
1513
|
+
appendServeTimeRouteDecisionLog({
|
|
1514
|
+
activationRoot: compileInput.activationRoot,
|
|
1515
|
+
turn,
|
|
1516
|
+
compileResult,
|
|
1517
|
+
normalizedEventExport,
|
|
1518
|
+
recordedAt: compileEvent?.createdAt ?? serveLoggedAt
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
catch (error) {
|
|
1522
|
+
warnings.push(`learning spine serve log failed: ${toErrorMessage(error)}`);
|
|
1523
|
+
}
|
|
472
1524
|
const eventExport = writeRuntimeEventExportBundle(turn, normalizedEventExport);
|
|
473
1525
|
return {
|
|
474
1526
|
...compileResult,
|
|
@@ -492,4 +1544,1565 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
492
1544
|
};
|
|
493
1545
|
}
|
|
494
1546
|
}
|
|
1547
|
+
export function runContinuousProductLoopTurn(input) {
|
|
1548
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1549
|
+
const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
|
|
1550
|
+
const failOpen = input.failOpen !== false;
|
|
1551
|
+
const currentState = cloneContinuousProductLoopState(input.state ??
|
|
1552
|
+
createContinuousProductLoopState({
|
|
1553
|
+
activationRoot,
|
|
1554
|
+
loopRoot
|
|
1555
|
+
}));
|
|
1556
|
+
currentState.activationRoot = activationRoot;
|
|
1557
|
+
currentState.loopRoot = loopRoot;
|
|
1558
|
+
const activeBeforeTurn = syncContinuousActivePack(currentState);
|
|
1559
|
+
const compileActiveVersion = activeBeforeTurn?.version ?? 0;
|
|
1560
|
+
const compileActivePackId = activeBeforeTurn?.packId ?? null;
|
|
1561
|
+
const turn = withContinuousTurnExport(input.turn, loopRoot);
|
|
1562
|
+
const turnResult = runRuntimeTurn(turn, {
|
|
1563
|
+
activationRoot,
|
|
1564
|
+
failOpen
|
|
1565
|
+
});
|
|
1566
|
+
const learningWarnings = [];
|
|
1567
|
+
let supervision = null;
|
|
1568
|
+
const learning = {
|
|
1569
|
+
warnings: learningWarnings,
|
|
1570
|
+
supervisionDigest: null,
|
|
1571
|
+
bridgeDigest: null,
|
|
1572
|
+
selectedSliceIds: [],
|
|
1573
|
+
materializationJobId: null,
|
|
1574
|
+
materializationReason: null,
|
|
1575
|
+
materializationLane: null,
|
|
1576
|
+
candidateRootDir: null,
|
|
1577
|
+
candidatePack: currentState.candidatePack === null ? null : cloneContinuousProductLoopPackVersion(currentState.candidatePack),
|
|
1578
|
+
runtimePlasticity: currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity),
|
|
1579
|
+
promotionAllowed: false,
|
|
1580
|
+
promotionFindings: [],
|
|
1581
|
+
promoted: false
|
|
1582
|
+
};
|
|
1583
|
+
if (!turnResult.eventExport.ok) {
|
|
1584
|
+
learningWarnings.push(`continuous learner skipped: ${turnResult.eventExport.error}`);
|
|
1585
|
+
return {
|
|
1586
|
+
runtimeOwner: "openclaw",
|
|
1587
|
+
compileActiveVersion,
|
|
1588
|
+
compileActivePackId,
|
|
1589
|
+
turn: turnResult,
|
|
1590
|
+
supervision,
|
|
1591
|
+
learning,
|
|
1592
|
+
state: cloneContinuousProductLoopState(currentState)
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
const normalizedEventExport = turnResult.eventExport.normalizedEventExport;
|
|
1596
|
+
supervision = buildCanonicalSupervision(normalizedEventExport);
|
|
1597
|
+
learning.supervisionDigest = supervision.supervisionDigest;
|
|
1598
|
+
currentState.lastSupervision = cloneCanonicalSupervision(supervision);
|
|
1599
|
+
const mergedHistory = mergeRuntimeEventHistory(currentState, normalizedEventExport);
|
|
1600
|
+
currentState.interactionEvents = mergedHistory.interactionEvents;
|
|
1601
|
+
currentState.feedbackEvents = mergedHistory.feedbackEvents;
|
|
1602
|
+
try {
|
|
1603
|
+
let activeBeforePack = null;
|
|
1604
|
+
try {
|
|
1605
|
+
activeBeforePack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
|
|
1606
|
+
}
|
|
1607
|
+
catch {
|
|
1608
|
+
activeBeforePack = null;
|
|
1609
|
+
}
|
|
1610
|
+
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
1611
|
+
packLabel: input.packLabel,
|
|
1612
|
+
workspace: input.workspace,
|
|
1613
|
+
interactionEvents: currentState.interactionEvents,
|
|
1614
|
+
feedbackEvents: currentState.feedbackEvents,
|
|
1615
|
+
learnedRouting: input.learnedRouting ?? true,
|
|
1616
|
+
state: currentState.learner,
|
|
1617
|
+
builtAt: normalizeIsoTimestamp(input.candidateBuiltAt, "candidateBuiltAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt),
|
|
1618
|
+
...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
|
|
1619
|
+
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
1620
|
+
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
|
|
1621
|
+
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
1622
|
+
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
|
|
1623
|
+
...(input.cadence !== undefined ? { cadence: input.cadence } : {})
|
|
1624
|
+
});
|
|
1625
|
+
currentState.learner = structuredClone(learnerResult.state);
|
|
1626
|
+
currentState.runtimePlasticity = learnerResult.state.runtimePlasticity === null ? null : structuredClone(learnerResult.state.runtimePlasticity);
|
|
1627
|
+
learning.runtimePlasticity = currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity);
|
|
1628
|
+
learning.bridgeDigest = learnerResult.bridge.bridgeDigest;
|
|
1629
|
+
learning.selectedSliceIds = learnerResult.selectedSlices.map((slice) => slice.sliceId);
|
|
1630
|
+
learning.materializationJobId = learnerResult.materialization?.jobId ?? null;
|
|
1631
|
+
learning.materializationReason = learnerResult.materialization?.reason ?? null;
|
|
1632
|
+
learning.materializationLane = learnerResult.materialization?.lane ?? null;
|
|
1633
|
+
if (learnerResult.materialization !== null) {
|
|
1634
|
+
const candidatePack = registerPackVersion(currentState, buildLearningCandidateTarget(learnerResult.materialization.candidate));
|
|
1635
|
+
const candidateRootDir = buildContinuousPackRoot(loopRoot, candidatePack);
|
|
1636
|
+
const descriptor = materializeAlwaysOnLearningCandidatePack(candidateRootDir, learnerResult.materialization);
|
|
1637
|
+
const candidateTarget = describePackCompileTarget(descriptor);
|
|
1638
|
+
learning.candidateRootDir = candidateRootDir;
|
|
1639
|
+
learning.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1640
|
+
currentState.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1641
|
+
const stagedAt = normalizeIsoTimestamp(input.stageUpdatedAt, "stageUpdatedAt", descriptor.manifest.provenance.builtAt);
|
|
1642
|
+
try {
|
|
1643
|
+
appendLearningUpdateLogs({
|
|
1644
|
+
activationRoot,
|
|
1645
|
+
materialization: learnerResult.materialization,
|
|
1646
|
+
activeBeforePack,
|
|
1647
|
+
candidateDescriptor: descriptor
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
catch (error) {
|
|
1651
|
+
learningWarnings.push(`learning spine update logs failed: ${toErrorMessage(error)}`);
|
|
1652
|
+
}
|
|
1653
|
+
stageCandidatePack(activationRoot, candidateRootDir, {
|
|
1654
|
+
updatedAt: stagedAt,
|
|
1655
|
+
reason: `stage_candidate:${learnerResult.materialization.reason}:${learnerResult.materialization.lane}`
|
|
1656
|
+
});
|
|
1657
|
+
const stagedInspection = inspectActivationState(activationRoot, stagedAt);
|
|
1658
|
+
learning.promotionAllowed = stagedInspection.promotion.allowed;
|
|
1659
|
+
learning.promotionFindings = [...stagedInspection.promotion.findings];
|
|
1660
|
+
if ((input.autoPromote ?? true) && stagedInspection.promotion.allowed) {
|
|
1661
|
+
const promotedAt = normalizeIsoTimestamp(input.promoteUpdatedAt, "promoteUpdatedAt", stagedAt);
|
|
1662
|
+
promoteCandidatePack(activationRoot, {
|
|
1663
|
+
updatedAt: promotedAt,
|
|
1664
|
+
reason: `auto_promote:${learnerResult.materialization.reason}:${learnerResult.materialization.lane}`
|
|
1665
|
+
});
|
|
1666
|
+
currentState.promotionCount += 1;
|
|
1667
|
+
currentState.candidatePack = null;
|
|
1668
|
+
learning.promoted = true;
|
|
1669
|
+
const activePack = registerPackVersion(currentState, candidateTarget);
|
|
1670
|
+
currentState.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
|
|
1671
|
+
currentState.activePackVersion = activePack.version;
|
|
1672
|
+
syncContinuousActivePack(currentState);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
catch (error) {
|
|
1677
|
+
if (!failOpen) {
|
|
1678
|
+
throw error;
|
|
1679
|
+
}
|
|
1680
|
+
learningWarnings.push(`continuous learner failed open: ${toErrorMessage(error)}`);
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
runtimeOwner: "openclaw",
|
|
1684
|
+
compileActiveVersion,
|
|
1685
|
+
compileActivePackId,
|
|
1686
|
+
turn: turnResult,
|
|
1687
|
+
supervision,
|
|
1688
|
+
learning,
|
|
1689
|
+
state: cloneContinuousProductLoopState(currentState)
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
function ensureRecordedSessionTrace(trace) {
|
|
1693
|
+
if (trace.contract !== RECORDED_SESSION_TRACE_CONTRACT) {
|
|
1694
|
+
throw new Error(`${RECORDED_SESSION_TRACE_CONTRACT} contract is required`);
|
|
1695
|
+
}
|
|
1696
|
+
normalizeNonEmptyString(trace.traceId, "traceId");
|
|
1697
|
+
normalizeIsoTimestamp(trace.recordedAt, "recordedAt");
|
|
1698
|
+
normalizeIsoTimestamp(trace.bundleBuiltAt, "bundleBuiltAt");
|
|
1699
|
+
normalizeNonEmptyString(trace.sessionId, "sessionId");
|
|
1700
|
+
normalizeNonEmptyString(trace.channel, "channel");
|
|
1701
|
+
normalizeNonEmptyString(trace.sourceStream, "sourceStream");
|
|
1702
|
+
normalizeIsoTimestamp(trace.seedBuiltAt, "seedBuiltAt");
|
|
1703
|
+
normalizeIsoTimestamp(trace.seedActivatedAt, "seedActivatedAt");
|
|
1704
|
+
if (trace.privacy.sanitized !== true) {
|
|
1705
|
+
throw new Error("recorded session trace must be explicitly sanitized");
|
|
1706
|
+
}
|
|
1707
|
+
if (trace.seedCues.length === 0) {
|
|
1708
|
+
throw new Error("recorded session trace requires at least one seed cue");
|
|
1709
|
+
}
|
|
1710
|
+
if (trace.turns.length === 0) {
|
|
1711
|
+
throw new Error("recorded session trace requires at least one turn");
|
|
1712
|
+
}
|
|
1713
|
+
for (const [index, cue] of trace.seedCues.entries()) {
|
|
1714
|
+
normalizeNonEmptyString(cue.cueId, `seedCues[${index}].cueId`);
|
|
1715
|
+
normalizeIsoTimestamp(cue.createdAt, `seedCues[${index}].createdAt`);
|
|
1716
|
+
normalizeNonEmptyString(cue.content, `seedCues[${index}].content`);
|
|
1717
|
+
}
|
|
1718
|
+
for (const [index, turn] of trace.turns.entries()) {
|
|
1719
|
+
normalizeIsoTimestamp(turn.createdAt, `turns[${index}].createdAt`);
|
|
1720
|
+
normalizeNonEmptyString(turn.userMessage, `turns[${index}].userMessage`);
|
|
1721
|
+
if ((turn.expectedContextPhrases ?? []).length === 0) {
|
|
1722
|
+
throw new Error(`turns[${index}].expectedContextPhrases must include at least one phrase`);
|
|
1723
|
+
}
|
|
1724
|
+
for (const [feedbackIndex, feedback] of (turn.feedback ?? []).entries()) {
|
|
1725
|
+
normalizeIsoTimestamp(feedback.createdAt, `turns[${index}].feedback[${feedbackIndex}].createdAt`);
|
|
1726
|
+
normalizeNonEmptyString(feedback.content, `turns[${index}].feedback[${feedbackIndex}].content`);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function padReplayNumber(value) {
|
|
1731
|
+
return String(value).padStart(2, "0");
|
|
1732
|
+
}
|
|
1733
|
+
function replayTurnId(traceId, index, explicitValue) {
|
|
1734
|
+
return normalizeOptionalString(explicitValue) ?? `${traceId}-turn-${padReplayNumber(index + 1)}`;
|
|
1735
|
+
}
|
|
1736
|
+
function replaySequenceStart(index) {
|
|
1737
|
+
return 1_000 + index * 10;
|
|
1738
|
+
}
|
|
1739
|
+
function replayMessageId(turnId) {
|
|
1740
|
+
return `msg-${turnId}`;
|
|
1741
|
+
}
|
|
1742
|
+
function addMinutes(value, minutes) {
|
|
1743
|
+
return new Date(Date.parse(value) + minutes * 60_000).toISOString();
|
|
1744
|
+
}
|
|
1745
|
+
function normalizeReplayPhrase(value) {
|
|
1746
|
+
return value.toLowerCase().replace(/\s+/gu, " ").trim();
|
|
1747
|
+
}
|
|
1748
|
+
function hasReplayPhrase(texts, phrase) {
|
|
1749
|
+
const normalizedPhrase = normalizeReplayPhrase(phrase);
|
|
1750
|
+
return texts.some((text) => normalizeReplayPhrase(text).includes(normalizedPhrase));
|
|
1751
|
+
}
|
|
1752
|
+
function uniqueStringsInOrder(values) {
|
|
1753
|
+
const seen = new Set();
|
|
1754
|
+
const unique = [];
|
|
1755
|
+
for (const value of values) {
|
|
1756
|
+
if (seen.has(value)) {
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
seen.add(value);
|
|
1760
|
+
unique.push(value);
|
|
1761
|
+
}
|
|
1762
|
+
return unique;
|
|
1763
|
+
}
|
|
1764
|
+
function buildRecordedSessionSeedExport(trace) {
|
|
1765
|
+
const agentId = normalizeOptionalString(trace.agentId) ?? DEFAULT_AGENT_ID;
|
|
1766
|
+
const seedSessionId = `${trace.sessionId}-seed`;
|
|
1767
|
+
const sourceStream = `${trace.sourceStream}/seed`;
|
|
1768
|
+
const interactionEvents = [];
|
|
1769
|
+
const feedbackEvents = [];
|
|
1770
|
+
let sequence = 1;
|
|
1771
|
+
for (const cue of trace.seedCues) {
|
|
1772
|
+
const interactionEventId = `${seedSessionId}:${cue.cueId}:interaction`;
|
|
1773
|
+
const feedbackEventId = `${seedSessionId}:${cue.cueId}:feedback`;
|
|
1774
|
+
const interaction = createInteractionEvent({
|
|
1775
|
+
eventId: interactionEventId,
|
|
1776
|
+
agentId,
|
|
1777
|
+
sessionId: seedSessionId,
|
|
1778
|
+
channel: trace.channel,
|
|
1779
|
+
sequence,
|
|
1780
|
+
kind: "operator_override",
|
|
1781
|
+
createdAt: cue.createdAt,
|
|
1782
|
+
source: {
|
|
1783
|
+
runtimeOwner: "openclaw",
|
|
1784
|
+
stream: sourceStream
|
|
1785
|
+
},
|
|
1786
|
+
messageId: `${cue.cueId}-seed-message`
|
|
1787
|
+
});
|
|
1788
|
+
sequence += 1;
|
|
1789
|
+
const feedback = createFeedbackEvent({
|
|
1790
|
+
eventId: feedbackEventId,
|
|
1791
|
+
agentId,
|
|
1792
|
+
sessionId: seedSessionId,
|
|
1793
|
+
channel: trace.channel,
|
|
1794
|
+
sequence,
|
|
1795
|
+
kind: cue.kind ?? "teaching",
|
|
1796
|
+
createdAt: addMinutes(cue.createdAt, 1),
|
|
1797
|
+
source: {
|
|
1798
|
+
runtimeOwner: "openclaw",
|
|
1799
|
+
stream: sourceStream
|
|
1800
|
+
},
|
|
1801
|
+
content: cue.content,
|
|
1802
|
+
relatedInteractionId: interaction.eventId
|
|
1803
|
+
});
|
|
1804
|
+
sequence += 1;
|
|
1805
|
+
interactionEvents.push(interaction);
|
|
1806
|
+
feedbackEvents.push(feedback);
|
|
1807
|
+
}
|
|
1808
|
+
return buildNormalizedEventExport({
|
|
1809
|
+
interactionEvents,
|
|
1810
|
+
feedbackEvents
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
function recordedSessionFixtureBase(trace) {
|
|
1814
|
+
const traceHash = checksumJsonPayload(trace);
|
|
1815
|
+
const turns = trace.turns.map((turn, index) => {
|
|
1816
|
+
const turnId = replayTurnId(trace.traceId, index, turn.turnId);
|
|
1817
|
+
const sequenceStart = replaySequenceStart(index);
|
|
1818
|
+
return {
|
|
1819
|
+
turnId,
|
|
1820
|
+
turn: {
|
|
1821
|
+
...(trace.agentId !== undefined && trace.agentId !== null ? { agentId: trace.agentId } : {}),
|
|
1822
|
+
sessionId: trace.sessionId,
|
|
1823
|
+
channel: trace.channel,
|
|
1824
|
+
sourceStream: trace.sourceStream,
|
|
1825
|
+
userMessage: turn.userMessage,
|
|
1826
|
+
createdAt: turn.createdAt,
|
|
1827
|
+
sequenceStart,
|
|
1828
|
+
maxContextBlocks: 3,
|
|
1829
|
+
mode: "heuristic",
|
|
1830
|
+
...(turn.runtimeHints !== undefined ? { runtimeHints: [...turn.runtimeHints] } : {}),
|
|
1831
|
+
compile: {
|
|
1832
|
+
createdAt: turn.createdAt,
|
|
1833
|
+
sequence: sequenceStart
|
|
1834
|
+
},
|
|
1835
|
+
delivery: turn.deliveredAt === undefined || turn.deliveredAt === null
|
|
1836
|
+
? false
|
|
1837
|
+
: {
|
|
1838
|
+
createdAt: turn.deliveredAt,
|
|
1839
|
+
sequence: sequenceStart + 1,
|
|
1840
|
+
messageId: replayMessageId(turnId)
|
|
1841
|
+
},
|
|
1842
|
+
feedback: (turn.feedback ?? []).map((feedback, feedbackIndex) => ({
|
|
1843
|
+
createdAt: feedback.createdAt,
|
|
1844
|
+
content: feedback.content,
|
|
1845
|
+
sequence: sequenceStart + 2 + feedbackIndex,
|
|
1846
|
+
kind: feedback.kind ?? null
|
|
1847
|
+
}))
|
|
1848
|
+
},
|
|
1849
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
1850
|
+
minimumPhraseHits: Math.max(1, turn.minimumPhraseHits ?? turn.expectedContextPhrases.length)
|
|
1851
|
+
};
|
|
1852
|
+
});
|
|
1853
|
+
return {
|
|
1854
|
+
contract: RECORDED_SESSION_FIXTURE_CONTRACT,
|
|
1855
|
+
traceId: trace.traceId,
|
|
1856
|
+
source: trace.source,
|
|
1857
|
+
recordedAt: trace.recordedAt,
|
|
1858
|
+
bundleBuiltAt: trace.bundleBuiltAt,
|
|
1859
|
+
traceHash,
|
|
1860
|
+
privacy: {
|
|
1861
|
+
sanitized: true,
|
|
1862
|
+
notes: [...trace.privacy.notes]
|
|
1863
|
+
},
|
|
1864
|
+
workspace: {
|
|
1865
|
+
workspaceId: trace.workspace.workspaceId,
|
|
1866
|
+
snapshotId: trace.workspace.snapshotId,
|
|
1867
|
+
capturedAt: trace.workspace.capturedAt,
|
|
1868
|
+
rootDir: trace.workspace.rootDir,
|
|
1869
|
+
...(trace.workspace.branch !== undefined ? { branch: trace.workspace.branch } : {}),
|
|
1870
|
+
revision: trace.workspace.revision,
|
|
1871
|
+
...(trace.workspace.labels !== undefined ? { labels: [...trace.workspace.labels] } : {})
|
|
1872
|
+
},
|
|
1873
|
+
seedBuiltAt: trace.seedBuiltAt,
|
|
1874
|
+
seedActivatedAt: trace.seedActivatedAt,
|
|
1875
|
+
seedExport: buildRecordedSessionSeedExport(trace),
|
|
1876
|
+
turns
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
export function buildRecordedSessionReplayFixture(trace) {
|
|
1880
|
+
ensureRecordedSessionTrace(trace);
|
|
1881
|
+
const base = recordedSessionFixtureBase(trace);
|
|
1882
|
+
return {
|
|
1883
|
+
...base,
|
|
1884
|
+
fixtureHash: checksumJsonPayload(base)
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
function recordedSessionReplayFixtureBase(fixture) {
|
|
1888
|
+
return {
|
|
1889
|
+
contract: RECORDED_SESSION_FIXTURE_CONTRACT,
|
|
1890
|
+
traceId: fixture.traceId,
|
|
1891
|
+
source: fixture.source,
|
|
1892
|
+
recordedAt: fixture.recordedAt,
|
|
1893
|
+
bundleBuiltAt: fixture.bundleBuiltAt,
|
|
1894
|
+
traceHash: fixture.traceHash,
|
|
1895
|
+
privacy: {
|
|
1896
|
+
sanitized: true,
|
|
1897
|
+
notes: [...fixture.privacy.notes]
|
|
1898
|
+
},
|
|
1899
|
+
workspace: {
|
|
1900
|
+
workspaceId: fixture.workspace.workspaceId,
|
|
1901
|
+
snapshotId: fixture.workspace.snapshotId,
|
|
1902
|
+
capturedAt: fixture.workspace.capturedAt,
|
|
1903
|
+
rootDir: fixture.workspace.rootDir,
|
|
1904
|
+
...(fixture.workspace.branch !== undefined ? { branch: fixture.workspace.branch } : {}),
|
|
1905
|
+
revision: fixture.workspace.revision,
|
|
1906
|
+
...(fixture.workspace.labels !== undefined ? { labels: [...fixture.workspace.labels] } : {})
|
|
1907
|
+
},
|
|
1908
|
+
seedBuiltAt: fixture.seedBuiltAt,
|
|
1909
|
+
seedActivatedAt: fixture.seedActivatedAt,
|
|
1910
|
+
seedExport: fixture.seedExport,
|
|
1911
|
+
turns: fixture.turns.map((turn) => ({
|
|
1912
|
+
turnId: turn.turnId,
|
|
1913
|
+
turn: structuredClone(turn.turn),
|
|
1914
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
1915
|
+
minimumPhraseHits: turn.minimumPhraseHits
|
|
1916
|
+
}))
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
function buildReplayTurnScore(input) {
|
|
1920
|
+
const phraseHits = input.expectedContextPhrases.filter((phrase) => hasReplayPhrase(input.texts, phrase));
|
|
1921
|
+
const missedPhrases = input.expectedContextPhrases.filter((phrase) => !phraseHits.includes(phrase));
|
|
1922
|
+
const compileScore = input.compileOk ? 40 : 0;
|
|
1923
|
+
const phraseScore = input.expectedContextPhrases.length === 0 ? 60 : Math.round((phraseHits.length / input.expectedContextPhrases.length) * 60);
|
|
1924
|
+
return {
|
|
1925
|
+
phraseHits,
|
|
1926
|
+
missedPhrases,
|
|
1927
|
+
qualityScore: Math.min(100, compileScore + phraseScore)
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
function buildRecordedSessionTurnReport(turnFixture, result, options) {
|
|
1931
|
+
const compileOk = result.ok;
|
|
1932
|
+
const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
|
|
1933
|
+
const selectedContextIds = compileOk ? result.compileResponse.selectedContext.map((block) => block.id) : [];
|
|
1934
|
+
const scoring = buildReplayTurnScore({
|
|
1935
|
+
compileOk,
|
|
1936
|
+
texts: selectedContextTexts,
|
|
1937
|
+
expectedContextPhrases: turnFixture.expectedContextPhrases
|
|
1938
|
+
});
|
|
1939
|
+
const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
|
|
1940
|
+
return {
|
|
1941
|
+
turnId: turnFixture.turnId,
|
|
1942
|
+
compileOk,
|
|
1943
|
+
fallbackToStaticContext: result.fallbackToStaticContext,
|
|
1944
|
+
hardRequirementViolated: result.hardRequirementViolated,
|
|
1945
|
+
activePackId: result.ok ? result.activePackId : null,
|
|
1946
|
+
usedLearnedRouteFn: result.ok ? result.compileResponse.diagnostics.usedLearnedRouteFn : false,
|
|
1947
|
+
routerIdentity: result.ok ? result.compileResponse.diagnostics.routerIdentity : null,
|
|
1948
|
+
selectionDigest: result.ok ? result.compileResponse.diagnostics.selectionDigest : null,
|
|
1949
|
+
selectedContextIds,
|
|
1950
|
+
selectedContextTexts,
|
|
1951
|
+
eventExportDigest,
|
|
1952
|
+
expectedContextPhrases: [...turnFixture.expectedContextPhrases],
|
|
1953
|
+
minimumPhraseHits: turnFixture.minimumPhraseHits,
|
|
1954
|
+
phraseHits: scoring.phraseHits,
|
|
1955
|
+
missedPhrases: scoring.missedPhrases,
|
|
1956
|
+
qualityScore: scoring.qualityScore,
|
|
1957
|
+
compileActiveVersion: options.compileActiveVersion,
|
|
1958
|
+
promoted: options.promoted,
|
|
1959
|
+
warnings: [...result.warnings]
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
function buildRecordedSessionReplayModeSummary(mode, turns) {
|
|
1963
|
+
const compileOkCount = turns.filter((turn) => turn.compileOk).length;
|
|
1964
|
+
const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
|
|
1965
|
+
const phraseCount = turns.reduce((sum, turn) => sum + turn.expectedContextPhrases.length, 0);
|
|
1966
|
+
const usedLearnedRouteTurnCount = turns.filter((turn) => turn.usedLearnedRouteFn).length;
|
|
1967
|
+
const promotionCount = turns.filter((turn) => turn.promoted).length;
|
|
1968
|
+
const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
|
|
1969
|
+
const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
|
|
1970
|
+
const base = {
|
|
1971
|
+
mode,
|
|
1972
|
+
qualityScore,
|
|
1973
|
+
compileOkCount,
|
|
1974
|
+
phraseHitCount,
|
|
1975
|
+
phraseCount,
|
|
1976
|
+
usedLearnedRouteTurnCount,
|
|
1977
|
+
promotionCount,
|
|
1978
|
+
packIds
|
|
1979
|
+
};
|
|
1980
|
+
return {
|
|
1981
|
+
...base,
|
|
1982
|
+
scoreHash: checksumJsonPayload({
|
|
1983
|
+
summary: base,
|
|
1984
|
+
turns: turns.map((turn) => ({
|
|
1985
|
+
turnId: turn.turnId,
|
|
1986
|
+
qualityScore: turn.qualityScore,
|
|
1987
|
+
phraseHits: turn.phraseHits,
|
|
1988
|
+
missedPhrases: turn.missedPhrases,
|
|
1989
|
+
compileOk: turn.compileOk,
|
|
1990
|
+
usedLearnedRouteFn: turn.usedLearnedRouteFn,
|
|
1991
|
+
activePackId: turn.activePackId,
|
|
1992
|
+
selectionDigest: turn.selectionDigest,
|
|
1993
|
+
promoted: turn.promoted,
|
|
1994
|
+
compileActiveVersion: turn.compileActiveVersion
|
|
1995
|
+
}))
|
|
1996
|
+
})
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function buildRecordedSessionReplayModeReport(mode, turns) {
|
|
2000
|
+
return {
|
|
2001
|
+
mode,
|
|
2002
|
+
summary: buildRecordedSessionReplayModeSummary(mode, turns),
|
|
2003
|
+
turns: [...turns]
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
function buildRecordedSessionReplayScoreHash(modes) {
|
|
2007
|
+
return checksumJsonPayload(modes.map((mode) => ({
|
|
2008
|
+
mode: mode.mode,
|
|
2009
|
+
qualityScore: mode.summary.qualityScore,
|
|
2010
|
+
compileOkCount: mode.summary.compileOkCount,
|
|
2011
|
+
phraseHitCount: mode.summary.phraseHitCount,
|
|
2012
|
+
phraseCount: mode.summary.phraseCount,
|
|
2013
|
+
usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
|
|
2014
|
+
promotionCount: mode.summary.promotionCount,
|
|
2015
|
+
packIds: mode.summary.packIds,
|
|
2016
|
+
scoreHash: mode.summary.scoreHash
|
|
2017
|
+
})));
|
|
2018
|
+
}
|
|
2019
|
+
function recordedSessionReplayBundleBase(bundle) {
|
|
2020
|
+
return {
|
|
2021
|
+
contract: RECORDED_SESSION_BUNDLE_CONTRACT,
|
|
2022
|
+
traceId: bundle.traceId,
|
|
2023
|
+
source: bundle.source,
|
|
2024
|
+
recordedAt: bundle.recordedAt,
|
|
2025
|
+
generatedAt: bundle.generatedAt,
|
|
2026
|
+
traceHash: bundle.traceHash,
|
|
2027
|
+
fixtureHash: bundle.fixtureHash,
|
|
2028
|
+
scoreHash: bundle.scoreHash,
|
|
2029
|
+
privacy: {
|
|
2030
|
+
sanitized: true,
|
|
2031
|
+
notes: [...bundle.privacy.notes]
|
|
2032
|
+
},
|
|
2033
|
+
modes: bundle.modes.map((mode) => ({
|
|
2034
|
+
mode: mode.mode,
|
|
2035
|
+
summary: {
|
|
2036
|
+
...mode.summary,
|
|
2037
|
+
packIds: [...mode.summary.packIds]
|
|
2038
|
+
},
|
|
2039
|
+
turns: mode.turns.map((turn) => ({
|
|
2040
|
+
...turn,
|
|
2041
|
+
selectedContextIds: [...turn.selectedContextIds],
|
|
2042
|
+
selectedContextTexts: [...turn.selectedContextTexts],
|
|
2043
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
2044
|
+
phraseHits: [...turn.phraseHits],
|
|
2045
|
+
missedPhrases: [...turn.missedPhrases],
|
|
2046
|
+
warnings: [...turn.warnings]
|
|
2047
|
+
}))
|
|
2048
|
+
})),
|
|
2049
|
+
summary: {
|
|
2050
|
+
winnerMode: bundle.summary.winnerMode,
|
|
2051
|
+
ranking: bundle.summary.ranking.map((entry) => ({ ...entry }))
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
function buildRecordedSessionTurnExportRoot(modeRoot, turnId) {
|
|
2056
|
+
return {
|
|
2057
|
+
rootDir: path.join(modeRoot, "exports", turnId),
|
|
2058
|
+
exportName: turnId
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function prepareReplayModeRoot(rootDir, mode) {
|
|
2062
|
+
const modeRoot = path.resolve(path.join(rootDir, mode));
|
|
2063
|
+
rmSync(modeRoot, { recursive: true, force: true });
|
|
2064
|
+
mkdirSync(modeRoot, { recursive: true });
|
|
2065
|
+
return modeRoot;
|
|
2066
|
+
}
|
|
2067
|
+
function prepareSeedActivation(rootDir, fixture) {
|
|
2068
|
+
const activationRoot = path.join(rootDir, "activation");
|
|
2069
|
+
const seedPackRoot = path.join(rootDir, "seed-pack");
|
|
2070
|
+
const seedPack = materializeCandidatePackFromNormalizedEventExport(seedPackRoot, {
|
|
2071
|
+
packLabel: `${fixture.traceId}-seed`,
|
|
2072
|
+
workspace: fixture.workspace,
|
|
2073
|
+
normalizedEventExport: fixture.seedExport,
|
|
2074
|
+
learnedRouting: false,
|
|
2075
|
+
builtAt: fixture.seedBuiltAt,
|
|
2076
|
+
offlineArtifacts: ["recorded-session-replay-seed"],
|
|
2077
|
+
structuralOps: {
|
|
2078
|
+
connect: 1
|
|
2079
|
+
}
|
|
2080
|
+
});
|
|
2081
|
+
activatePack(activationRoot, seedPackRoot, {
|
|
2082
|
+
updatedAt: fixture.seedActivatedAt,
|
|
2083
|
+
reason: "recorded_session_seed_activate"
|
|
2084
|
+
});
|
|
2085
|
+
return {
|
|
2086
|
+
activationRoot,
|
|
2087
|
+
seedPackId: seedPack.manifest.packId
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
function runRecordedSessionNoBrainMode(rootDir, fixture) {
|
|
2091
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "no_brain");
|
|
2092
|
+
const activationRoot = path.join(modeRoot, "activation");
|
|
2093
|
+
const turns = fixture.turns.map((turnFixture) => {
|
|
2094
|
+
const result = runRuntimeTurn({
|
|
2095
|
+
...turnFixture.turn,
|
|
2096
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
2097
|
+
}, {
|
|
2098
|
+
activationRoot,
|
|
2099
|
+
failOpen: true
|
|
2100
|
+
});
|
|
2101
|
+
return buildRecordedSessionTurnReport(turnFixture, result, {
|
|
2102
|
+
compileActiveVersion: null,
|
|
2103
|
+
promoted: false
|
|
2104
|
+
});
|
|
2105
|
+
});
|
|
2106
|
+
return buildRecordedSessionReplayModeReport("no_brain", turns);
|
|
2107
|
+
}
|
|
2108
|
+
function runRecordedSessionSeedPackMode(rootDir, fixture) {
|
|
2109
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "seed_pack");
|
|
2110
|
+
const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
|
|
2111
|
+
const turns = fixture.turns.map((turnFixture) => {
|
|
2112
|
+
const result = runRuntimeTurn({
|
|
2113
|
+
...turnFixture.turn,
|
|
2114
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
2115
|
+
}, {
|
|
2116
|
+
activationRoot,
|
|
2117
|
+
failOpen: false
|
|
2118
|
+
});
|
|
2119
|
+
return buildRecordedSessionTurnReport(turnFixture, result, {
|
|
2120
|
+
compileActiveVersion: 1,
|
|
2121
|
+
promoted: false
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
return buildRecordedSessionReplayModeReport("seed_pack", turns);
|
|
2125
|
+
}
|
|
2126
|
+
function runRecordedSessionLearnedReplayMode(rootDir, fixture) {
|
|
2127
|
+
const modeRoot = prepareReplayModeRoot(rootDir, "learned_replay");
|
|
2128
|
+
const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
|
|
2129
|
+
const loopRoot = path.join(modeRoot, "loop");
|
|
2130
|
+
let state;
|
|
2131
|
+
const turns = [];
|
|
2132
|
+
for (const turnFixture of fixture.turns) {
|
|
2133
|
+
const compileCreatedAt = normalizeIsoTimestamp(turnFixture.turn.compile?.createdAt, "turn.compile.createdAt", turnFixture.turn.createdAt);
|
|
2134
|
+
const result = runContinuousProductLoopTurn({
|
|
2135
|
+
activationRoot,
|
|
2136
|
+
loopRoot,
|
|
2137
|
+
packLabel: `${fixture.traceId}-learned`,
|
|
2138
|
+
workspace: fixture.workspace,
|
|
2139
|
+
...(state !== undefined ? { state } : {}),
|
|
2140
|
+
learnedRouting: true,
|
|
2141
|
+
failOpen: false,
|
|
2142
|
+
turn: {
|
|
2143
|
+
...turnFixture.turn,
|
|
2144
|
+
export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
|
|
2145
|
+
},
|
|
2146
|
+
candidateBuiltAt: addMinutes(compileCreatedAt, 2),
|
|
2147
|
+
stageUpdatedAt: addMinutes(compileCreatedAt, 3),
|
|
2148
|
+
promoteUpdatedAt: addMinutes(compileCreatedAt, 4)
|
|
2149
|
+
});
|
|
2150
|
+
state = result.state;
|
|
2151
|
+
turns.push(buildRecordedSessionTurnReport(turnFixture, result.turn, {
|
|
2152
|
+
compileActiveVersion: result.compileActiveVersion,
|
|
2153
|
+
promoted: result.learning.promoted
|
|
2154
|
+
}));
|
|
2155
|
+
}
|
|
2156
|
+
return buildRecordedSessionReplayModeReport("learned_replay", turns);
|
|
2157
|
+
}
|
|
2158
|
+
export function runRecordedSessionReplay(rootDir, fixture) {
|
|
2159
|
+
const resolvedRoot = path.resolve(normalizeNonEmptyString(rootDir, "rootDir"));
|
|
2160
|
+
const seedExportErrors = validateNormalizedEventExport(fixture.seedExport);
|
|
2161
|
+
if (seedExportErrors.length > 0) {
|
|
2162
|
+
throw new Error(`recorded session replay seed export is invalid: ${seedExportErrors.join("; ")}`);
|
|
2163
|
+
}
|
|
2164
|
+
const expectedFixtureHash = checksumJsonPayload(recordedSessionReplayFixtureBase(fixture));
|
|
2165
|
+
if (fixture.fixtureHash !== expectedFixtureHash) {
|
|
2166
|
+
throw new Error(`recorded session replay fixtureHash mismatch: expected ${expectedFixtureHash}, received ${fixture.fixtureHash}`);
|
|
2167
|
+
}
|
|
2168
|
+
const modes = [
|
|
2169
|
+
runRecordedSessionNoBrainMode(resolvedRoot, fixture),
|
|
2170
|
+
runRecordedSessionSeedPackMode(resolvedRoot, fixture),
|
|
2171
|
+
runRecordedSessionLearnedReplayMode(resolvedRoot, fixture)
|
|
2172
|
+
];
|
|
2173
|
+
const ranking = modes
|
|
2174
|
+
.map((mode) => ({
|
|
2175
|
+
mode: mode.mode,
|
|
2176
|
+
qualityScore: mode.summary.qualityScore
|
|
2177
|
+
}))
|
|
2178
|
+
.sort((left, right) => right.qualityScore - left.qualityScore || left.mode.localeCompare(right.mode));
|
|
2179
|
+
const scoreHash = buildRecordedSessionReplayScoreHash(modes);
|
|
2180
|
+
const base = {
|
|
2181
|
+
contract: RECORDED_SESSION_BUNDLE_CONTRACT,
|
|
2182
|
+
traceId: fixture.traceId,
|
|
2183
|
+
source: fixture.source,
|
|
2184
|
+
recordedAt: fixture.recordedAt,
|
|
2185
|
+
generatedAt: fixture.bundleBuiltAt,
|
|
2186
|
+
traceHash: fixture.traceHash,
|
|
2187
|
+
fixtureHash: fixture.fixtureHash,
|
|
2188
|
+
scoreHash,
|
|
2189
|
+
privacy: {
|
|
2190
|
+
sanitized: true,
|
|
2191
|
+
notes: [...fixture.privacy.notes]
|
|
2192
|
+
},
|
|
2193
|
+
modes,
|
|
2194
|
+
summary: {
|
|
2195
|
+
winnerMode: ranking[0]?.mode ?? null,
|
|
2196
|
+
ranking
|
|
2197
|
+
}
|
|
2198
|
+
};
|
|
2199
|
+
return {
|
|
2200
|
+
...base,
|
|
2201
|
+
bundleHash: checksumJsonPayload(base)
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
function rescoreRecordedSessionReplayTurn(turn) {
|
|
2205
|
+
const scoring = buildReplayTurnScore({
|
|
2206
|
+
compileOk: turn.compileOk,
|
|
2207
|
+
texts: turn.selectedContextTexts,
|
|
2208
|
+
expectedContextPhrases: turn.expectedContextPhrases
|
|
2209
|
+
});
|
|
2210
|
+
return {
|
|
2211
|
+
...turn,
|
|
2212
|
+
phraseHits: scoring.phraseHits,
|
|
2213
|
+
missedPhrases: scoring.missedPhrases,
|
|
2214
|
+
qualityScore: scoring.qualityScore,
|
|
2215
|
+
selectedContextIds: [...turn.selectedContextIds],
|
|
2216
|
+
selectedContextTexts: [...turn.selectedContextTexts],
|
|
2217
|
+
expectedContextPhrases: [...turn.expectedContextPhrases],
|
|
2218
|
+
warnings: [...turn.warnings]
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function rescoreRecordedSessionReplayMode(mode) {
|
|
2222
|
+
const turns = mode.turns.map((turn) => rescoreRecordedSessionReplayTurn(turn));
|
|
2223
|
+
return buildRecordedSessionReplayModeReport(mode.mode, turns);
|
|
2224
|
+
}
|
|
2225
|
+
export function rescoreRecordedSessionReplayBundle(bundle) {
|
|
2226
|
+
const modes = bundle.modes.map((mode) => rescoreRecordedSessionReplayMode(mode));
|
|
2227
|
+
return {
|
|
2228
|
+
scoreHash: buildRecordedSessionReplayScoreHash(modes),
|
|
2229
|
+
modes: modes.map((mode) => ({
|
|
2230
|
+
mode: mode.mode,
|
|
2231
|
+
qualityScore: mode.summary.qualityScore,
|
|
2232
|
+
scoreHash: mode.summary.scoreHash
|
|
2233
|
+
}))
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
export function verifyRecordedSessionReplayBundleHashes(bundle) {
|
|
2237
|
+
const rescored = rescoreRecordedSessionReplayBundle(bundle);
|
|
2238
|
+
const rebuiltBundleHash = checksumJsonPayload(recordedSessionReplayBundleBase(bundle));
|
|
2239
|
+
return {
|
|
2240
|
+
bundleHashMatches: rebuiltBundleHash === bundle.bundleHash,
|
|
2241
|
+
scoreHashMatches: rescored.scoreHash === bundle.scoreHash
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
export const OPERATOR_API_CONTRACT_ID = "openclaw_operator_api.v1";
|
|
2245
|
+
export const SUPPORTED_OPERATOR_API_FAMILIES = [
|
|
2246
|
+
"bootstrap_attach",
|
|
2247
|
+
"status",
|
|
2248
|
+
"export",
|
|
2249
|
+
"refresh",
|
|
2250
|
+
"promote",
|
|
2251
|
+
"rollback",
|
|
2252
|
+
"proof_observability"
|
|
2253
|
+
];
|
|
2254
|
+
export const OPERATOR_API_CONTRACT_V1 = {
|
|
2255
|
+
contract: OPERATOR_API_CONTRACT_ID,
|
|
2256
|
+
runtimeOwner: "openclaw",
|
|
2257
|
+
scope: "narrow_supported_operator_surface",
|
|
2258
|
+
families: SUPPORTED_OPERATOR_API_FAMILIES,
|
|
2259
|
+
routes: [
|
|
2260
|
+
{
|
|
2261
|
+
family: "bootstrap_attach",
|
|
2262
|
+
scope: "programmatic",
|
|
2263
|
+
packageName: "@openclawbrain/openclaw",
|
|
2264
|
+
entrypoints: ["bootstrapRuntimeAttach", "describeAttachStatus"],
|
|
2265
|
+
summary: "Bootstrap the first attach pack and prove the initial handoff state without pretending live learning has already run.",
|
|
2266
|
+
notes: [
|
|
2267
|
+
"Zero-event bootstrap is supported and stays explicit through awaiting_first_export.",
|
|
2268
|
+
"Attach serves only from activation's active slot after bootstrap completes."
|
|
2269
|
+
]
|
|
2270
|
+
},
|
|
2271
|
+
{
|
|
2272
|
+
family: "status",
|
|
2273
|
+
scope: "cli",
|
|
2274
|
+
packageName: "@openclawbrain/openclaw",
|
|
2275
|
+
entrypoints: ["openclawbrain-ops status", "describeCurrentProfileBrainStatus"],
|
|
2276
|
+
summary: "Read the canonical current-profile brain-status object for the active Host/Profile/Brain/Attachment boundary.",
|
|
2277
|
+
notes: [
|
|
2278
|
+
"Status is the first operator read path.",
|
|
2279
|
+
"describeCurrentProfileBrainStatus() freezes the supported Host/Profile/Brain/Attachment answer shape for the current profile.",
|
|
2280
|
+
"Use activation and export observability proof helpers when you need candidate/previous or export-freshness detail."
|
|
2281
|
+
]
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
family: "export",
|
|
2285
|
+
scope: "programmatic",
|
|
2286
|
+
packageName: "@openclawbrain/openclaw",
|
|
2287
|
+
entrypoints: ["buildNormalizedRuntimeEventExport", "writeRuntimeEventExportBundle", "loadRuntimeEventExportBundle"],
|
|
2288
|
+
summary: "Emit the deterministic learner handoff artifact explicitly instead of folding export into a larger implicit runtime loop.",
|
|
2289
|
+
notes: [
|
|
2290
|
+
"Export is an off-hot-path operator handoff artifact, not proof of immediate active-pack mutation.",
|
|
2291
|
+
"Bundle roots and normalized payloads are both accepted downstream by observability surfaces."
|
|
2292
|
+
]
|
|
2293
|
+
},
|
|
2294
|
+
{
|
|
2295
|
+
family: "refresh",
|
|
2296
|
+
scope: "programmatic",
|
|
2297
|
+
packageName: "@openclawbrain/learner",
|
|
2298
|
+
entrypoints: [
|
|
2299
|
+
"createAlwaysOnLearningRuntimeState",
|
|
2300
|
+
"advanceAlwaysOnLearningRuntime",
|
|
2301
|
+
"materializeAlwaysOnLearningCandidatePack"
|
|
2302
|
+
],
|
|
2303
|
+
summary: "Refresh candidate learning state explicitly through the learner boundary before any activation-pointer move happens.",
|
|
2304
|
+
notes: [
|
|
2305
|
+
"Refresh is PG-only candidate-pack materialization in this repo.",
|
|
2306
|
+
"Refresh does not mutate the currently served active pack in place."
|
|
2307
|
+
]
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
family: "promote",
|
|
2311
|
+
scope: "programmatic",
|
|
2312
|
+
packageName: "@openclawbrain/pack-format",
|
|
2313
|
+
entrypoints: ["stageCandidatePack", "promoteCandidatePack"],
|
|
2314
|
+
summary: "Stage and promote activation-ready candidate packs through explicit pointer changes.",
|
|
2315
|
+
notes: [
|
|
2316
|
+
"Promotion is the only path that changes which pack is served.",
|
|
2317
|
+
"Candidate and previous remain inspectable around the pointer move."
|
|
2318
|
+
]
|
|
2319
|
+
},
|
|
2320
|
+
{
|
|
2321
|
+
family: "rollback",
|
|
2322
|
+
scope: "cli",
|
|
2323
|
+
packageName: "@openclawbrain/openclaw",
|
|
2324
|
+
entrypoints: ["openclawbrain-ops rollback", "rollbackRuntimeAttach", "formatOperatorRollbackReport"],
|
|
2325
|
+
summary: "Preview and apply the explicit active<-previous / active->candidate rollback move.",
|
|
2326
|
+
notes: [
|
|
2327
|
+
"Rollback is blocked when the previous pointer is unavailable.",
|
|
2328
|
+
"Dry-run is the required first read path for safe operator rollback."
|
|
2329
|
+
]
|
|
2330
|
+
},
|
|
2331
|
+
{
|
|
2332
|
+
family: "proof_observability",
|
|
2333
|
+
scope: "programmatic",
|
|
2334
|
+
packageName: "@openclawbrain/openclaw",
|
|
2335
|
+
entrypoints: ["describeAttachStatus", "describeKernelBrainBoundary"],
|
|
2336
|
+
summary: "Prove the local attach and kernel-vs-brain boundary from the shipped bridge surface.",
|
|
2337
|
+
notes: [
|
|
2338
|
+
"Use these for repo-local or installed-package operator proof reads.",
|
|
2339
|
+
"These surfaces report the promoted artifact boundary, not full live runtime plasticity."
|
|
2340
|
+
]
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
family: "proof_observability",
|
|
2344
|
+
scope: "programmatic",
|
|
2345
|
+
packageName: "@openclawbrain/pack-format",
|
|
2346
|
+
entrypoints: ["describeActivationObservability"],
|
|
2347
|
+
summary: "Inspect activation health, freshness, route artifacts, rollback lineage, and slot readiness.",
|
|
2348
|
+
notes: ["Activation observability is the ground truth for active/candidate/previous slot inspection."]
|
|
2349
|
+
},
|
|
2350
|
+
{
|
|
2351
|
+
family: "proof_observability",
|
|
2352
|
+
scope: "programmatic",
|
|
2353
|
+
packageName: "@openclawbrain/event-export",
|
|
2354
|
+
entrypoints: ["describeNormalizedEventExportObservability"],
|
|
2355
|
+
summary: "Inspect supervision freshness and teacher freshness from the exported learner handoff artifact.",
|
|
2356
|
+
notes: ["Export observability is local-to-export proof only."]
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
family: "proof_observability",
|
|
2360
|
+
scope: "proof_lane",
|
|
2361
|
+
packageName: "workspace",
|
|
2362
|
+
entrypoints: ["pnpm current-profile-lifecycle:smoke", "pnpm observability:smoke"],
|
|
2363
|
+
summary: "Run the repo-local proof lanes that derive operator truth from the canonical current-profile status object plus activation observability.",
|
|
2364
|
+
notes: ["These lanes are proof machinery, not a second semver-stable API."]
|
|
2365
|
+
}
|
|
2366
|
+
],
|
|
2367
|
+
quarantinedSurface: [
|
|
2368
|
+
"openclawbrain-ops doctor was deleted; use the canonical current-profile status object plus proof helpers instead of a parallel troubleshooting surface.",
|
|
2369
|
+
"buildOperatorSurfaceReport / formatOperatorStatusReport / formatOperatorDoctorReport were historical parallel status surfaces and are not the supported operator API.",
|
|
2370
|
+
"runContinuousProductLoopTurn collapses export/refresh/promote into one proof helper and is not the supported operator API.",
|
|
2371
|
+
"runRecordedSessionReplay and recorded-session fixtures are proof helpers, not operator API.",
|
|
2372
|
+
"release scripts, root smoke plumbing, and workspace layout are proof-and-build machinery, not operator API.",
|
|
2373
|
+
"runRuntimeTurn is a runtime convenience wrapper and not the narrow operator export contract.",
|
|
2374
|
+
"createAsyncTeacherLiveLoop is supporting internals for refresh/teacher snapshots, not the narrow operator contract."
|
|
2375
|
+
]
|
|
2376
|
+
};
|
|
2377
|
+
export const OPENCLAW_OPERATOR_NOUNS_V1 = ["Host", "Profile", "Brain", "Attachment"];
|
|
2378
|
+
export const CURRENT_PROFILE_BRAIN_STATUS_CONTRACT = CONTRACT_IDS.currentProfileBrainStatus;
|
|
2379
|
+
export const BRAIN_ATTACHMENT_POLICY_SEMANTICS_V1 = {
|
|
2380
|
+
undeclared: "The Host has not declared whether the current Profile's Brain attachment policy is shared or dedicated; do not infer profile exclusivity from activation state alone.",
|
|
2381
|
+
dedicated: "The Host declares a dedicated Brain attachment policy: one Profile is intentionally attached to one Brain activation root, and operators may treat the served Brain state as profile-specific until the attachment changes.",
|
|
2382
|
+
shared: "The Host declares a shared Brain attachment policy: multiple Profiles may intentionally attach to the same Brain activation root, attribution must stay current-profile explicit, and operators must not treat later served context as profile-exclusive."
|
|
2383
|
+
};
|
|
2384
|
+
function summarizeOperatorSlot(slot, updatedAt) {
|
|
2385
|
+
if (slot === null) {
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
return {
|
|
2389
|
+
slot: slot.slot,
|
|
2390
|
+
packId: slot.packId,
|
|
2391
|
+
activationReady: slot.activationReady,
|
|
2392
|
+
routePolicy: slot.routePolicy,
|
|
2393
|
+
routerIdentity: slot.routerIdentity,
|
|
2394
|
+
workspaceSnapshot: slot.workspaceSnapshot,
|
|
2395
|
+
workspaceRevision: slot.workspaceRevision,
|
|
2396
|
+
eventRange: { ...slot.eventRange },
|
|
2397
|
+
eventExportDigest: slot.eventExportDigest,
|
|
2398
|
+
builtAt: slot.builtAt,
|
|
2399
|
+
updatedAt,
|
|
2400
|
+
findings: [...slot.findings]
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
function summarizeLastPromotion(inspection) {
|
|
2404
|
+
if (inspection.active === null) {
|
|
2405
|
+
return {
|
|
2406
|
+
known: false,
|
|
2407
|
+
at: null,
|
|
2408
|
+
confidence: "no_active_pack",
|
|
2409
|
+
note: "active slot is empty, so no promotion can be proven"
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
if (inspection.previous !== null) {
|
|
2413
|
+
return {
|
|
2414
|
+
known: inspection.pointers.active?.updatedAt !== null,
|
|
2415
|
+
at: inspection.pointers.active?.updatedAt ?? null,
|
|
2416
|
+
confidence: "proven_from_previous_pointer",
|
|
2417
|
+
note: "previous pointer is retained, so the current active pack is the last promoted pack"
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
return {
|
|
2421
|
+
known: false,
|
|
2422
|
+
at: null,
|
|
2423
|
+
confidence: "unknown_from_local_pointers",
|
|
2424
|
+
note: "no previous pointer is retained, so local activation pointers cannot prove the last promotion time"
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
function summarizeCandidateAheadBy(candidateAheadBy) {
|
|
2428
|
+
if (candidateAheadBy === null) {
|
|
2429
|
+
return [];
|
|
2430
|
+
}
|
|
2431
|
+
return Object.entries(candidateAheadBy)
|
|
2432
|
+
.filter(([, changed]) => changed === true)
|
|
2433
|
+
.map(([field]) => field)
|
|
2434
|
+
.sort();
|
|
2435
|
+
}
|
|
2436
|
+
function isAwaitingFirstExportSlot(slot) {
|
|
2437
|
+
return slot !== null && slot.eventRange.count === 0;
|
|
2438
|
+
}
|
|
2439
|
+
function summarizeBrainState(active, observability) {
|
|
2440
|
+
if (active === null) {
|
|
2441
|
+
return {
|
|
2442
|
+
state: "no_active_pack",
|
|
2443
|
+
initMode: null,
|
|
2444
|
+
runtimePlasticitySource: null,
|
|
2445
|
+
seedStateVisible: false,
|
|
2446
|
+
seedBlockCount: 0,
|
|
2447
|
+
activePackId: null,
|
|
2448
|
+
activeWorkspaceSnapshot: null,
|
|
2449
|
+
activeEventExportDigest: null,
|
|
2450
|
+
detail: "no active pack is pinned, so the serve path can only fail open or hard fail"
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
const state = observability.initHandoff.handoffState;
|
|
2454
|
+
const detail = state === "pg_promoted_pack_authoritative"
|
|
2455
|
+
? "serving is pinned to a PG-promoted pack rather than seed-state authority"
|
|
2456
|
+
: state === "seed_state_authoritative"
|
|
2457
|
+
? "serving is still pinned to the current seed-state authority"
|
|
2458
|
+
: "init/handoff metadata is missing for the active pack";
|
|
2459
|
+
return {
|
|
2460
|
+
state,
|
|
2461
|
+
initMode: observability.initHandoff.initMode,
|
|
2462
|
+
runtimePlasticitySource: observability.graphDynamics.runtimePlasticitySource,
|
|
2463
|
+
seedStateVisible: observability.initHandoff.seedStateVisible,
|
|
2464
|
+
seedBlockCount: observability.initHandoff.seedBlockCount,
|
|
2465
|
+
activePackId: active.packId,
|
|
2466
|
+
activeWorkspaceSnapshot: active.workspaceSnapshot,
|
|
2467
|
+
activeEventExportDigest: active.eventExportDigest,
|
|
2468
|
+
detail
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
function summarizeServePath(compile) {
|
|
2472
|
+
if (compile === null) {
|
|
2473
|
+
return {
|
|
2474
|
+
state: "unprobed",
|
|
2475
|
+
fallbackToStaticContext: false,
|
|
2476
|
+
hardRequirementViolated: false,
|
|
2477
|
+
activePackId: null,
|
|
2478
|
+
usedLearnedRouteFn: null,
|
|
2479
|
+
routerIdentity: null,
|
|
2480
|
+
selectionMode: null,
|
|
2481
|
+
refreshStatus: null,
|
|
2482
|
+
freshnessChecksum: null,
|
|
2483
|
+
contextAttribution: buildContextAttributionSummary({
|
|
2484
|
+
fallbackToStaticContext: false,
|
|
2485
|
+
hardRequirementViolated: false,
|
|
2486
|
+
usedLearnedRouteFn: null,
|
|
2487
|
+
unprobed: true
|
|
2488
|
+
}),
|
|
2489
|
+
error: null
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
if (!compile.ok) {
|
|
2493
|
+
return {
|
|
2494
|
+
state: compile.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
2495
|
+
fallbackToStaticContext: compile.fallbackToStaticContext,
|
|
2496
|
+
hardRequirementViolated: compile.hardRequirementViolated,
|
|
2497
|
+
activePackId: compile.activePackId,
|
|
2498
|
+
usedLearnedRouteFn: compile.usedLearnedRouteFn,
|
|
2499
|
+
routerIdentity: compile.routerIdentity,
|
|
2500
|
+
selectionMode: null,
|
|
2501
|
+
refreshStatus: null,
|
|
2502
|
+
freshnessChecksum: null,
|
|
2503
|
+
contextAttribution: compile.contextAttribution,
|
|
2504
|
+
error: compile.error
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
return {
|
|
2508
|
+
state: "serving_active_pack",
|
|
2509
|
+
fallbackToStaticContext: compile.fallbackToStaticContext,
|
|
2510
|
+
hardRequirementViolated: compile.hardRequirementViolated,
|
|
2511
|
+
activePackId: compile.activePackId,
|
|
2512
|
+
usedLearnedRouteFn: compile.usedLearnedRouteFn,
|
|
2513
|
+
routerIdentity: compile.routerIdentity,
|
|
2514
|
+
selectionMode: readDiagnosticNoteValue(compile.notes, "selection_mode="),
|
|
2515
|
+
refreshStatus: readDiagnosticNoteValue(compile.notes, "router_refresh_status="),
|
|
2516
|
+
freshnessChecksum: readDiagnosticNoteValue(compile.notes, "router_freshness_checksum="),
|
|
2517
|
+
contextAttribution: compile.contextAttribution,
|
|
2518
|
+
error: compile.error
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
function loadOperatorEventExport(input) {
|
|
2522
|
+
const eventExportPath = normalizeOptionalString(input.eventExportPath);
|
|
2523
|
+
if (eventExportPath === undefined) {
|
|
2524
|
+
return null;
|
|
2525
|
+
}
|
|
2526
|
+
const resolvedPath = path.resolve(eventExportPath);
|
|
2527
|
+
const stats = statSync(resolvedPath);
|
|
2528
|
+
if (stats.isDirectory()) {
|
|
2529
|
+
const bundle = loadRuntimeEventExportBundle(resolvedPath);
|
|
2530
|
+
return {
|
|
2531
|
+
sourcePath: resolvedPath,
|
|
2532
|
+
sourceKind: "bundle_root",
|
|
2533
|
+
normalizedEventExport: bundle.normalizedEventExport,
|
|
2534
|
+
exportedAt: bundle.manifest.exportedAt
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
const normalizedEventExport = readJsonFile(resolvedPath);
|
|
2538
|
+
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
2539
|
+
if (validationErrors.length > 0) {
|
|
2540
|
+
throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
sourcePath: resolvedPath,
|
|
2544
|
+
sourceKind: "payload",
|
|
2545
|
+
normalizedEventExport,
|
|
2546
|
+
exportedAt: null
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function summarizeSupervision(input) {
|
|
2550
|
+
const loaded = loadOperatorEventExport(input);
|
|
2551
|
+
if (loaded === null) {
|
|
2552
|
+
return {
|
|
2553
|
+
available: false,
|
|
2554
|
+
sourcePath: null,
|
|
2555
|
+
sourceKind: "missing",
|
|
2556
|
+
exportDigest: null,
|
|
2557
|
+
exportedAt: null,
|
|
2558
|
+
flowing: null,
|
|
2559
|
+
sourceCount: 0,
|
|
2560
|
+
freshestSourceStream: null,
|
|
2561
|
+
freshestCreatedAt: null,
|
|
2562
|
+
freshestKind: null,
|
|
2563
|
+
humanLabelCount: null,
|
|
2564
|
+
sources: [],
|
|
2565
|
+
detail: "no event export path supplied"
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
const observability = describeNormalizedEventExportObservability(loaded.normalizedEventExport);
|
|
2569
|
+
const freshestSource = observability.supervisionFreshnessBySource[0] ?? null;
|
|
2570
|
+
const flowing = observability.teacherFreshness.freshestCreatedAt !== null && observability.teacherFreshness.humanLabelCount > 0;
|
|
2571
|
+
return {
|
|
2572
|
+
available: true,
|
|
2573
|
+
sourcePath: loaded.sourcePath,
|
|
2574
|
+
sourceKind: loaded.sourceKind,
|
|
2575
|
+
exportDigest: observability.exportDigest,
|
|
2576
|
+
exportedAt: loaded.exportedAt,
|
|
2577
|
+
flowing,
|
|
2578
|
+
sourceCount: observability.supervisionFreshnessBySource.length,
|
|
2579
|
+
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
2580
|
+
freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null,
|
|
2581
|
+
freshestKind: observability.teacherFreshness.freshestKind ?? freshestSource?.freshestKind ?? null,
|
|
2582
|
+
humanLabelCount: observability.teacherFreshness.humanLabelCount,
|
|
2583
|
+
sources: [...observability.teacherFreshness.sources],
|
|
2584
|
+
detail: flowing
|
|
2585
|
+
? "human supervision is visible in the supplied export"
|
|
2586
|
+
: "the supplied export does not yet show human supervision"
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function loadTeacherSnapshot(input) {
|
|
2590
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2591
|
+
if (teacherSnapshotPath === undefined) {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
const snapshot = readJsonFile(path.resolve(teacherSnapshotPath));
|
|
2595
|
+
if (snapshot.runtimeOwner !== "openclaw") {
|
|
2596
|
+
throw new Error("teacher snapshot runtimeOwner must be openclaw");
|
|
2597
|
+
}
|
|
2598
|
+
return snapshot;
|
|
2599
|
+
}
|
|
2600
|
+
function summarizeTeacherLoop(input) {
|
|
2601
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2602
|
+
if (teacherSnapshotPath === undefined) {
|
|
2603
|
+
return {
|
|
2604
|
+
available: false,
|
|
2605
|
+
sourcePath: null,
|
|
2606
|
+
lastNoOpReason: "unavailable",
|
|
2607
|
+
latestFreshness: "unavailable",
|
|
2608
|
+
lastProcessedAt: null,
|
|
2609
|
+
queueDepth: null,
|
|
2610
|
+
queueCapacity: null,
|
|
2611
|
+
running: null,
|
|
2612
|
+
lastMaterializedPackId: null,
|
|
2613
|
+
notes: [],
|
|
2614
|
+
detail: "no teacher snapshot path supplied"
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
const snapshot = loadTeacherSnapshot(input);
|
|
2618
|
+
if (snapshot === null) {
|
|
2619
|
+
return {
|
|
2620
|
+
available: false,
|
|
2621
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2622
|
+
lastNoOpReason: "unavailable",
|
|
2623
|
+
latestFreshness: "unavailable",
|
|
2624
|
+
lastProcessedAt: null,
|
|
2625
|
+
queueDepth: null,
|
|
2626
|
+
queueCapacity: null,
|
|
2627
|
+
running: null,
|
|
2628
|
+
lastMaterializedPackId: null,
|
|
2629
|
+
notes: [],
|
|
2630
|
+
detail: "teacher snapshot could not be loaded"
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
return {
|
|
2634
|
+
available: true,
|
|
2635
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2636
|
+
lastNoOpReason: snapshot.diagnostics.lastNoOpReason,
|
|
2637
|
+
latestFreshness: snapshot.diagnostics.latestFreshness,
|
|
2638
|
+
lastProcessedAt: snapshot.diagnostics.lastProcessedAt,
|
|
2639
|
+
queueDepth: snapshot.queue.depth,
|
|
2640
|
+
queueCapacity: snapshot.queue.capacity,
|
|
2641
|
+
running: snapshot.queue.running,
|
|
2642
|
+
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
2643
|
+
notes: [...snapshot.diagnostics.notes],
|
|
2644
|
+
detail: "async teacher diagnostics loaded"
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
function summarizeAlwaysOnLearning(input) {
|
|
2648
|
+
const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
|
|
2649
|
+
if (teacherSnapshotPath === undefined) {
|
|
2650
|
+
return {
|
|
2651
|
+
available: false,
|
|
2652
|
+
sourcePath: null,
|
|
2653
|
+
bootstrapped: null,
|
|
2654
|
+
mode: "unavailable",
|
|
2655
|
+
nextPriorityLane: "unavailable",
|
|
2656
|
+
pendingLive: null,
|
|
2657
|
+
pendingBackfill: null,
|
|
2658
|
+
pendingTotal: null,
|
|
2659
|
+
freshLivePriority: null,
|
|
2660
|
+
learnedRange: null,
|
|
2661
|
+
materializationCount: null,
|
|
2662
|
+
lastMaterializedAt: null,
|
|
2663
|
+
lastMaterializationReason: null,
|
|
2664
|
+
lastMaterializationLane: null,
|
|
2665
|
+
lastMaterializationPriority: null,
|
|
2666
|
+
lastMaterializedPackId: null,
|
|
2667
|
+
detail: "no teacher snapshot path supplied"
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
const snapshot = loadTeacherSnapshot(input);
|
|
2671
|
+
if (snapshot === null) {
|
|
2672
|
+
return {
|
|
2673
|
+
available: false,
|
|
2674
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2675
|
+
bootstrapped: null,
|
|
2676
|
+
mode: "unavailable",
|
|
2677
|
+
nextPriorityLane: "unavailable",
|
|
2678
|
+
pendingLive: null,
|
|
2679
|
+
pendingBackfill: null,
|
|
2680
|
+
pendingTotal: null,
|
|
2681
|
+
freshLivePriority: null,
|
|
2682
|
+
learnedRange: null,
|
|
2683
|
+
materializationCount: null,
|
|
2684
|
+
lastMaterializedAt: null,
|
|
2685
|
+
lastMaterializationReason: null,
|
|
2686
|
+
lastMaterializationLane: null,
|
|
2687
|
+
lastMaterializationPriority: null,
|
|
2688
|
+
lastMaterializedPackId: null,
|
|
2689
|
+
detail: "teacher snapshot could not be loaded"
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
const plan = describeAlwaysOnLearningRuntimeState(snapshot.learner.state, snapshot.learner.lastMaterialization);
|
|
2693
|
+
return {
|
|
2694
|
+
available: true,
|
|
2695
|
+
sourcePath: path.resolve(teacherSnapshotPath),
|
|
2696
|
+
bootstrapped: plan.bootstrapped,
|
|
2697
|
+
mode: plan.mode,
|
|
2698
|
+
nextPriorityLane: plan.nextPriorityLane,
|
|
2699
|
+
pendingLive: plan.pending.live,
|
|
2700
|
+
pendingBackfill: plan.pending.backfill,
|
|
2701
|
+
pendingTotal: plan.pending.total,
|
|
2702
|
+
freshLivePriority: plan.pending.freshLivePriority,
|
|
2703
|
+
learnedRange: plan.learnedRange === null ? null : { ...plan.learnedRange },
|
|
2704
|
+
materializationCount: plan.materialization.count,
|
|
2705
|
+
lastMaterializedAt: plan.materialization.lastMaterializedAt,
|
|
2706
|
+
lastMaterializationReason: plan.materialization.lastReason,
|
|
2707
|
+
lastMaterializationLane: plan.materialization.lastLane,
|
|
2708
|
+
lastMaterializationPriority: plan.materialization.lastPriority,
|
|
2709
|
+
lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
|
|
2710
|
+
detail: plan.pending.freshLivePriority
|
|
2711
|
+
? "fresh live slices remain ahead of passive catch-up"
|
|
2712
|
+
: plan.pending.backfill > 0
|
|
2713
|
+
? "passive backfill remains queued behind the current live state"
|
|
2714
|
+
: plan.bootstrapped
|
|
2715
|
+
? "fast-init has handed off to the current learned export without queued backlog"
|
|
2716
|
+
: "learner is waiting for the first export"
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
function buildOperatorFindings(report) {
|
|
2720
|
+
const findings = [];
|
|
2721
|
+
const push = (severity, code, summary, detail) => {
|
|
2722
|
+
findings.push({ severity, code, summary, detail });
|
|
2723
|
+
};
|
|
2724
|
+
if (report.active === null) {
|
|
2725
|
+
push("fail", "active_missing", "active slot is empty", "no active pack found; this is the pre-bootstrap state — call `bootstrapRuntimeAttach()` to activate an initial pack before compiling or serving");
|
|
2726
|
+
}
|
|
2727
|
+
else if (!report.active.activationReady) {
|
|
2728
|
+
push("fail", "active_unhealthy", `active slot is not activation-ready: ${report.active.packId}`, report.active.findings.join("; ") || "inspect the active pack payloads and activation pointers");
|
|
2729
|
+
}
|
|
2730
|
+
else {
|
|
2731
|
+
push("pass", "active_ready", `active slot is ready: ${report.active.packId}`, "serving can inspect the active pack without activation drift");
|
|
2732
|
+
if (isAwaitingFirstExportSlot(report.active)) {
|
|
2733
|
+
push("warn", "bootstrap_waiting_for_first_export", "active pack bootstrapped without live exports yet", "serving is up from seed-state defaults; this is expected on first install — learning activates after the first turn is captured via `runRuntimeTurn()`");
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (report.learnedRouting.required) {
|
|
2737
|
+
if (report.learnedRouting.available) {
|
|
2738
|
+
push("pass", "learned_route_ready", `learned routing is active: ${report.learnedRouting.routerIdentity ?? "unknown-router"}`, `updateCount=${report.learnedRouting.updateCount ?? 0}; labels=${report.learnedRouting.collectedLabelsTotal ?? 0}; handoff=${report.learnedRouting.handoffState}`);
|
|
2739
|
+
}
|
|
2740
|
+
else {
|
|
2741
|
+
push("fail", "learned_route_missing", "active pack requires learned routing but the learned route artifact is unavailable", "repair the router artifact or promote a healthy learned-routing candidate before serving");
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
else {
|
|
2745
|
+
push("pass", "learned_route_optional", "active pack does not require learned routing", `handoff=${report.learnedRouting.handoffState}`);
|
|
2746
|
+
}
|
|
2747
|
+
if (report.servePath.state === "serving_active_pack") {
|
|
2748
|
+
push("pass", "serve_path_verified", `serve path compiles from active pack ${report.servePath.activePackId ?? "unknown-pack"}`, `selection=${report.servePath.selectionMode ?? "unknown"}; tiers=${report.servePath.contextAttribution.selectionTiers ?? "unknown"}; router=${report.servePath.routerIdentity ?? "none"}; routeFreshness=${report.servePath.freshnessChecksum ?? "unknown"}`);
|
|
2749
|
+
}
|
|
2750
|
+
else if (report.servePath.state === "fail_open_static_context") {
|
|
2751
|
+
push("warn", "serve_path_fail_open", "serve path would fail open to static context", report.servePath.error ?? "compile probe fell back to static context");
|
|
2752
|
+
}
|
|
2753
|
+
else if (report.servePath.state === "hard_fail") {
|
|
2754
|
+
push("fail", "serve_path_hard_fail", "serve path would hard fail on the learned-routing requirement", report.servePath.error ?? "compile probe violated a learned-routing hard requirement");
|
|
2755
|
+
}
|
|
2756
|
+
else {
|
|
2757
|
+
push("warn", "serve_path_unprobed", "serve path was not probed", "operator surface could not verify fail-open versus hard-fail behavior");
|
|
2758
|
+
}
|
|
2759
|
+
if (report.learnedRouting.required && report.servePath.state === "serving_active_pack" && report.servePath.usedLearnedRouteFn !== true) {
|
|
2760
|
+
push("fail", "serve_path_route_evidence_missing", "serve path compiled without learned-route evidence on a learned-routing pack", `router=${report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? "none"}; selection=${report.servePath.selectionMode ?? "unknown"}`);
|
|
2761
|
+
}
|
|
2762
|
+
if (report.servePath.state === "serving_active_pack") {
|
|
2763
|
+
if (report.servePath.contextAttribution.brainCompiledBlockCount > 0) {
|
|
2764
|
+
push("pass", "brain_context_visible", "serve probe selected brain-compiled context", `brainSources=${formatList(report.servePath.contextAttribution.brainCompiledSources)}; kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}`);
|
|
2765
|
+
}
|
|
2766
|
+
else {
|
|
2767
|
+
push("warn", "brain_context_kernel_only", "serve probe stayed inside the stable kernel", `kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}; ${report.servePath.contextAttribution.detail}`);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (report.candidate === null) {
|
|
2771
|
+
push("pass", "candidate_missing", "no candidate pack is currently staged", "steady state can legitimately run without a staged candidate until the next refresh lands");
|
|
2772
|
+
}
|
|
2773
|
+
else if (!report.candidate.activationReady) {
|
|
2774
|
+
push("warn", "candidate_unhealthy", `candidate slot is not activation-ready: ${report.candidate.packId}`, report.candidate.findings.join("; ") || "fix candidate pack payloads before promotion");
|
|
2775
|
+
}
|
|
2776
|
+
else if (report.promotion.allowed) {
|
|
2777
|
+
push("pass", "promotion_ready", `candidate is promotion-ready: ${report.candidate.packId}`, report.freshness.candidateAheadBy.length === 0
|
|
2778
|
+
? "candidate is staged and promotion is allowed"
|
|
2779
|
+
: `candidate is ahead on ${report.freshness.candidateAheadBy.join(", ")}`);
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
push("warn", "promotion_blocked", `candidate is staged but promotion is blocked: ${report.candidate.packId}`, report.promotion.findings.join("; ") || "inspect freshness and activation findings before promotion");
|
|
2783
|
+
}
|
|
2784
|
+
if (report.promotion.lastPromotion.known) {
|
|
2785
|
+
push("pass", "last_promotion_known", `last promotion is proven at ${report.promotion.lastPromotion.at}`, report.promotion.lastPromotion.note);
|
|
2786
|
+
}
|
|
2787
|
+
else {
|
|
2788
|
+
push("warn", "last_promotion_unknown", "last promotion is not provable from local activation pointers", report.promotion.lastPromotion.note);
|
|
2789
|
+
}
|
|
2790
|
+
if (report.rollback.allowed) {
|
|
2791
|
+
push("pass", "rollback_ready", `rollback is ready to restore ${report.rollback.previousPackId ?? "the previous pack"}`, report.previous === null ? "previous pack is pointer-visible even if slot inspection is unavailable" : `previous pack=${report.previous.packId}`);
|
|
2792
|
+
}
|
|
2793
|
+
else {
|
|
2794
|
+
push("warn", "rollback_blocked", "rollback is not ready", report.rollback.findings.join("; ") || "previous pointer is missing or no rollback target is retained");
|
|
2795
|
+
}
|
|
2796
|
+
if (!report.supervision.available) {
|
|
2797
|
+
push("warn", "supervision_unavailable", "supervision flow is not inspectable yet", "pass `--event-export <bundle-root-or-payload>` to inspect local supervision freshness");
|
|
2798
|
+
}
|
|
2799
|
+
else if (report.supervision.flowing) {
|
|
2800
|
+
push("pass", "supervision_visible", `supervision is flowing through ${report.supervision.freshestSourceStream ?? "unknown-source"}`, `freshest=${report.supervision.freshestCreatedAt ?? "unknown"}; humanLabels=${report.supervision.humanLabelCount ?? 0}`);
|
|
2801
|
+
}
|
|
2802
|
+
else {
|
|
2803
|
+
push("warn", "supervision_not_flowing", "the supplied export does not yet show human supervision", `sourcePath=${report.supervision.sourcePath ?? "unknown"}; exportDigest=${report.supervision.exportDigest ?? "unknown"}`);
|
|
2804
|
+
}
|
|
2805
|
+
if (!report.teacherLoop.available) {
|
|
2806
|
+
push("warn", "teacher_snapshot_unavailable", "last async no-op reason is not inspectable yet", "pass `--teacher-snapshot <snapshot.json>` to inspect duplicate/no-op handling");
|
|
2807
|
+
}
|
|
2808
|
+
else {
|
|
2809
|
+
push("pass", "teacher_snapshot_loaded", `last async no-op reason is ${report.teacherLoop.lastNoOpReason}`, `latestFreshness=${report.teacherLoop.latestFreshness}; queue=${report.teacherLoop.queueDepth}/${report.teacherLoop.queueCapacity}`);
|
|
2810
|
+
}
|
|
2811
|
+
return findings;
|
|
2812
|
+
}
|
|
2813
|
+
function summarizeOperatorStatus(findings) {
|
|
2814
|
+
if (findings.some((finding) => finding.severity === "fail")) {
|
|
2815
|
+
return "fail";
|
|
2816
|
+
}
|
|
2817
|
+
if (findings.some((finding) => finding.severity === "warn")) {
|
|
2818
|
+
return "warn";
|
|
2819
|
+
}
|
|
2820
|
+
return "ok";
|
|
2821
|
+
}
|
|
2822
|
+
function yesNo(value) {
|
|
2823
|
+
if (value === null) {
|
|
2824
|
+
return "unknown";
|
|
2825
|
+
}
|
|
2826
|
+
return value ? "yes" : "no";
|
|
2827
|
+
}
|
|
2828
|
+
function formatList(values, empty = "none") {
|
|
2829
|
+
return values.length === 0 ? empty : values.join(",");
|
|
2830
|
+
}
|
|
2831
|
+
function formatCompactList(values, empty = "none", maxItems = 2, maxLength = 20) {
|
|
2832
|
+
if (values.length === 0) {
|
|
2833
|
+
return empty;
|
|
2834
|
+
}
|
|
2835
|
+
const visible = values.slice(0, maxItems).map((value) => formatCompactValue(value, empty, maxLength));
|
|
2836
|
+
return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
|
|
2837
|
+
}
|
|
2838
|
+
function formatCompactValue(value, empty = "none", maxLength = 24) {
|
|
2839
|
+
if (value === null || value === undefined || value.length === 0) {
|
|
2840
|
+
return empty;
|
|
2841
|
+
}
|
|
2842
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength)}…`;
|
|
2843
|
+
}
|
|
2844
|
+
function summarizeCurrentProfileLogRoot(activationRoot) {
|
|
2845
|
+
const logRoot = path.join(path.resolve(activationRoot), LEARNING_SPINE_LOG_LAYOUT.dir);
|
|
2846
|
+
return existsSync(logRoot) ? logRoot : null;
|
|
2847
|
+
}
|
|
2848
|
+
function summarizeCurrentProfileLastLearningUpdateAt(activationRoot, learning) {
|
|
2849
|
+
const updates = readLearningSpineLogEntries(activationRoot, "pgRouteUpdates");
|
|
2850
|
+
return updates.at(-1)?.recordedAt ?? learning.lastMaterializedAt ?? null;
|
|
2851
|
+
}
|
|
2852
|
+
function summarizeCurrentProfileBrainSummary(input) {
|
|
2853
|
+
if (!input.attached) {
|
|
2854
|
+
return "Brain is not attached to the current Profile.";
|
|
2855
|
+
}
|
|
2856
|
+
if (input.serveState === "fail_open_static_context") {
|
|
2857
|
+
return "Brain is attached but would fail open to static context.";
|
|
2858
|
+
}
|
|
2859
|
+
if (input.serveState === "hard_fail") {
|
|
2860
|
+
return "Brain is attached but currently hard-fails learned routing.";
|
|
2861
|
+
}
|
|
2862
|
+
if (input.awaitingFirstExport) {
|
|
2863
|
+
return "Brain is serving the seed-state pack and awaiting the first export.";
|
|
2864
|
+
}
|
|
2865
|
+
if (input.brainState === "pg_promoted_pack_authoritative") {
|
|
2866
|
+
return "Brain is serving the promoted pack.";
|
|
2867
|
+
}
|
|
2868
|
+
if (input.serveState === "serving_active_pack") {
|
|
2869
|
+
return "Brain is serving the active pack with learned routing.";
|
|
2870
|
+
}
|
|
2871
|
+
return "Brain is attached but has not been compile-probed yet.";
|
|
2872
|
+
}
|
|
2873
|
+
function summarizeCurrentProfileBrainStatusLevel(input) {
|
|
2874
|
+
if (!input.attached) {
|
|
2875
|
+
return "fail";
|
|
2876
|
+
}
|
|
2877
|
+
if (input.serveState === "fail_open_static_context" || input.serveState === "hard_fail") {
|
|
2878
|
+
return "fail";
|
|
2879
|
+
}
|
|
2880
|
+
if (input.awaitingFirstExport || input.serveState === "unprobed") {
|
|
2881
|
+
return "warn";
|
|
2882
|
+
}
|
|
2883
|
+
return input.routeFreshness === "updated" ? "ok" : "warn";
|
|
2884
|
+
}
|
|
2885
|
+
function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
2886
|
+
const attached = report.active !== null;
|
|
2887
|
+
const awaitingFirstExport = isAwaitingFirstExportSlot(report.active);
|
|
2888
|
+
const routerIdentity = report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? report.active?.routerIdentity ?? null;
|
|
2889
|
+
const routeFreshness = report.servePath.refreshStatus === "updated" || report.servePath.refreshStatus === "no_supervision"
|
|
2890
|
+
? report.servePath.refreshStatus
|
|
2891
|
+
: "unknown";
|
|
2892
|
+
const activePackId = report.brain.activePackId ?? report.servePath.activePackId ?? report.active?.packId ?? null;
|
|
2893
|
+
const status = summarizeCurrentProfileBrainStatusLevel({
|
|
2894
|
+
attached,
|
|
2895
|
+
serveState: report.servePath.state,
|
|
2896
|
+
routeFreshness,
|
|
2897
|
+
awaitingFirstExport
|
|
2898
|
+
});
|
|
2899
|
+
return {
|
|
2900
|
+
contract: CURRENT_PROFILE_BRAIN_STATUS_CONTRACT,
|
|
2901
|
+
generatedAt: report.generatedAt,
|
|
2902
|
+
host: {
|
|
2903
|
+
noun: "Host",
|
|
2904
|
+
runtimeOwner: "openclaw",
|
|
2905
|
+
activationRoot: report.activationRoot
|
|
2906
|
+
},
|
|
2907
|
+
profile: {
|
|
2908
|
+
noun: "Profile",
|
|
2909
|
+
selector: "current_profile",
|
|
2910
|
+
detail: attached
|
|
2911
|
+
? "The Host resolves the current Profile through the active Attachment boundary only."
|
|
2912
|
+
: "The current Profile has no active Brain attachment visible at the Host boundary."
|
|
2913
|
+
},
|
|
2914
|
+
brain: {
|
|
2915
|
+
noun: "Brain",
|
|
2916
|
+
activationRoot: attached ? report.activationRoot : null,
|
|
2917
|
+
logRoot: summarizeCurrentProfileLogRoot(report.activationRoot),
|
|
2918
|
+
activePackId,
|
|
2919
|
+
initMode: report.learnedRouting.initMode,
|
|
2920
|
+
state: report.brain.state,
|
|
2921
|
+
routeFreshness,
|
|
2922
|
+
routerIdentity,
|
|
2923
|
+
routerChecksum: report.learnedRouting.routerChecksum,
|
|
2924
|
+
lastExportAt: report.supervision.exportedAt,
|
|
2925
|
+
lastLearningUpdateAt: summarizeCurrentProfileLastLearningUpdateAt(report.activationRoot, report.learning),
|
|
2926
|
+
lastPromotionAt: report.promotion.lastPromotion.at,
|
|
2927
|
+
summary: summarizeCurrentProfileBrainSummary({
|
|
2928
|
+
attached,
|
|
2929
|
+
serveState: report.servePath.state,
|
|
2930
|
+
brainState: report.brain.state,
|
|
2931
|
+
awaitingFirstExport
|
|
2932
|
+
}),
|
|
2933
|
+
detail: report.brain.detail
|
|
2934
|
+
},
|
|
2935
|
+
attachment: attached
|
|
2936
|
+
? {
|
|
2937
|
+
noun: "Attachment",
|
|
2938
|
+
state: "attached",
|
|
2939
|
+
activationRoot: report.activationRoot,
|
|
2940
|
+
servingSlot: "active",
|
|
2941
|
+
policyMode,
|
|
2942
|
+
policy: buildCurrentProfileAttachmentPolicy(policyMode),
|
|
2943
|
+
detail: policyMode === "shared"
|
|
2944
|
+
? "current profile is attached to a shared OpenClawBrain activation boundary"
|
|
2945
|
+
: policyMode === "dedicated"
|
|
2946
|
+
? "current profile is attached to a dedicated OpenClawBrain activation boundary"
|
|
2947
|
+
: "current profile is attached to an OpenClawBrain activation boundary, but shared-vs-dedicated policy has not been declared"
|
|
2948
|
+
}
|
|
2949
|
+
: {
|
|
2950
|
+
noun: "Attachment",
|
|
2951
|
+
state: "not_attached",
|
|
2952
|
+
activationRoot: null,
|
|
2953
|
+
servingSlot: "none",
|
|
2954
|
+
policyMode,
|
|
2955
|
+
policy: buildCurrentProfileAttachmentPolicy(policyMode),
|
|
2956
|
+
detail: "current profile is not attached to an OpenClawBrain activation boundary"
|
|
2957
|
+
},
|
|
2958
|
+
brainStatus: {
|
|
2959
|
+
status,
|
|
2960
|
+
brainState: report.brain.state,
|
|
2961
|
+
serveState: report.servePath.state,
|
|
2962
|
+
usedLearnedRouteFn: report.servePath.usedLearnedRouteFn,
|
|
2963
|
+
failOpen: report.servePath.fallbackToStaticContext,
|
|
2964
|
+
awaitingFirstExport,
|
|
2965
|
+
detail: report.servePath.state === "serving_active_pack"
|
|
2966
|
+
? "current profile is serving compiled context from the active promoted pack"
|
|
2967
|
+
: report.servePath.state === "fail_open_static_context"
|
|
2968
|
+
? "current profile would fail open to static context because no serving pack is available"
|
|
2969
|
+
: report.servePath.state === "hard_fail"
|
|
2970
|
+
? "current profile cannot serve because the learned-route or activation requirement hard-failed"
|
|
2971
|
+
: "current profile serve state has not been compile-probed yet"
|
|
2972
|
+
},
|
|
2973
|
+
currentTurnAttribution: null
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
function buildOperatorSurfaceReport(input) {
|
|
2977
|
+
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
2978
|
+
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
2979
|
+
const inspection = inspectActivationState(activationRoot, updatedAt);
|
|
2980
|
+
const observability = describeActivationObservability(activationRoot, "active", {
|
|
2981
|
+
updatedAt
|
|
2982
|
+
});
|
|
2983
|
+
const attachStatus = describeAttachStatus({ activationRoot });
|
|
2984
|
+
const active = summarizeOperatorSlot(inspection.active, inspection.pointers.active?.updatedAt ?? null);
|
|
2985
|
+
const reportBase = {
|
|
2986
|
+
generatedAt: updatedAt,
|
|
2987
|
+
activationRoot,
|
|
2988
|
+
active,
|
|
2989
|
+
candidate: summarizeOperatorSlot(inspection.candidate, inspection.pointers.candidate?.updatedAt ?? null),
|
|
2990
|
+
previous: summarizeOperatorSlot(inspection.previous, inspection.pointers.previous?.updatedAt ?? null),
|
|
2991
|
+
freshness: {
|
|
2992
|
+
activeBehindPromotionReadyCandidate: observability.promotionFreshness.activeBehindPromotionReadyCandidate,
|
|
2993
|
+
candidateAheadBy: summarizeCandidateAheadBy(observability.promotionFreshness.candidateAheadBy)
|
|
2994
|
+
},
|
|
2995
|
+
brain: summarizeBrainState(active, observability),
|
|
2996
|
+
learnedRouting: {
|
|
2997
|
+
required: observability.learnedRouteFn.required,
|
|
2998
|
+
available: observability.learnedRouteFn.available,
|
|
2999
|
+
routerIdentity: observability.learnedRouteFn.routerIdentity,
|
|
3000
|
+
routeFnVersion: observability.learnedRouteFn.routeFnVersion,
|
|
3001
|
+
trainingMethod: observability.learnedRouteFn.trainingMethod,
|
|
3002
|
+
routerTrainedAt: observability.learnedRouteFn.routerTrainedAt,
|
|
3003
|
+
objective: observability.learnedRouteFn.objective,
|
|
3004
|
+
pgProfile: observability.learnedRouteFn.pgProfile,
|
|
3005
|
+
routerChecksum: observability.learnedRouteFn.routerChecksum,
|
|
3006
|
+
objectiveChecksum: observability.learnedRouteFn.objectiveChecksum,
|
|
3007
|
+
updateMechanism: observability.learnedRouteFn.updateMechanism,
|
|
3008
|
+
updateVersion: observability.learnedRouteFn.updateVersion,
|
|
3009
|
+
updateCount: observability.learnedRouteFn.updateCount,
|
|
3010
|
+
supervisionCount: observability.learnedRouteFn.supervisionCount,
|
|
3011
|
+
collectedLabelsTotal: observability.learnedRouteFn.collectedLabels?.total ?? null,
|
|
3012
|
+
freshnessChecksum: observability.learnedRouteFn.freshnessChecksum,
|
|
3013
|
+
handoffState: observability.initHandoff.handoffState,
|
|
3014
|
+
initMode: observability.initHandoff.initMode,
|
|
3015
|
+
seedStateVisible: observability.initHandoff.seedStateVisible
|
|
3016
|
+
},
|
|
3017
|
+
servePath: summarizeServePath(attachStatus.compile),
|
|
3018
|
+
promotion: {
|
|
3019
|
+
allowed: inspection.promotion.allowed,
|
|
3020
|
+
findings: [...inspection.promotion.findings],
|
|
3021
|
+
lastPromotion: summarizeLastPromotion(inspection),
|
|
3022
|
+
activeUpdatedAt: inspection.pointers.active?.updatedAt ?? null,
|
|
3023
|
+
candidateUpdatedAt: inspection.pointers.candidate?.updatedAt ?? null,
|
|
3024
|
+
previousUpdatedAt: inspection.pointers.previous?.updatedAt ?? null
|
|
3025
|
+
},
|
|
3026
|
+
rollback: {
|
|
3027
|
+
allowed: inspection.rollback.allowed,
|
|
3028
|
+
findings: [...inspection.rollback.findings],
|
|
3029
|
+
previousPackId: inspection.previous?.packId ?? inspection.pointers.previous?.packId ?? null,
|
|
3030
|
+
state: inspection.rollback.allowed ? "ready" : inspection.active === null ? "unknown" : "blocked"
|
|
3031
|
+
},
|
|
3032
|
+
supervision: summarizeSupervision(input),
|
|
3033
|
+
learning: summarizeAlwaysOnLearning(input),
|
|
3034
|
+
teacherLoop: summarizeTeacherLoop(input)
|
|
3035
|
+
};
|
|
3036
|
+
const findings = buildOperatorFindings(reportBase);
|
|
3037
|
+
return {
|
|
3038
|
+
...reportBase,
|
|
3039
|
+
status: summarizeOperatorStatus(findings),
|
|
3040
|
+
findings
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
export function describeCurrentProfileBrainStatus(input) {
|
|
3044
|
+
const report = buildOperatorSurfaceReport(input);
|
|
3045
|
+
return buildCurrentProfileBrainStatusFromReport(report, normalizeBrainAttachmentPolicy(input.brainAttachmentPolicy));
|
|
3046
|
+
}
|
|
3047
|
+
export function formatOperatorRollbackReport(result) {
|
|
3048
|
+
const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
|
|
3049
|
+
return [
|
|
3050
|
+
header,
|
|
3051
|
+
`preview ${yesNo(result.dryRun)} activation=${result.activationRoot} updatedAt=${result.updatedAt}`,
|
|
3052
|
+
`before active=${result.before.activePackId ?? "none"} candidate=${result.before.candidatePackId ?? "none"} previous=${result.before.previousPackId ?? "none"}`,
|
|
3053
|
+
`after active=${result.after?.activePackId ?? "none"} candidate=${result.after?.candidatePackId ?? "none"} previous=${result.after?.previousPackId ?? "none"}`,
|
|
3054
|
+
`result restored=${result.restoredPackId ?? "none"} parkedCandidate=${result.parkedCandidatePackId ?? "none"}`,
|
|
3055
|
+
`findings ${formatList(result.findings)}`
|
|
3056
|
+
].join("\n");
|
|
3057
|
+
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Describes the kernel/brain boundary for a single compile response.
|
|
3060
|
+
*
|
|
3061
|
+
* Combines:
|
|
3062
|
+
* - Brain context summary (from the compile response diagnostics)
|
|
3063
|
+
* - Kernel surface validation (if a surface descriptor is supplied)
|
|
3064
|
+
* - A coverage advisory based on routing signals
|
|
3065
|
+
*
|
|
3066
|
+
* See `docs/kernel-brain-boundary.md` for the full decision framework.
|
|
3067
|
+
*/
|
|
3068
|
+
export function describeKernelBrainBoundary(compileResponse, surface) {
|
|
3069
|
+
const diag = compileResponse.diagnostics;
|
|
3070
|
+
// Collect the roles of selected blocks.
|
|
3071
|
+
const selectedRoles = [
|
|
3072
|
+
...new Set(compileResponse.selectedContext
|
|
3073
|
+
.map((b) => b.source)
|
|
3074
|
+
.filter((s) => typeof s === "string" && s.length > 0))
|
|
3075
|
+
];
|
|
3076
|
+
// Detect whether any block was compacted (compactedFrom set on the block).
|
|
3077
|
+
const compactionApplied = compileResponse.selectedContext.some((b) => Array.isArray(b.compactedFrom) && (b.compactedFrom?.length ?? 0) > 0);
|
|
3078
|
+
// Coverage advisory.
|
|
3079
|
+
// Token match evidence lives in diagnostics.notes as "selection_mode=token_match(...)"
|
|
3080
|
+
// or "selection_tiers=token_match_only" / "selection_tiers=token_match+priority_fallback".
|
|
3081
|
+
const notesStr = diag.notes.join(" ");
|
|
3082
|
+
const hasTokenMatches = notesStr.includes("selection_mode=token_match") ||
|
|
3083
|
+
notesStr.includes("selection_tiers=token_match");
|
|
3084
|
+
let brainCoverageAdvisory;
|
|
3085
|
+
if (diag.usedLearnedRouteFn && hasTokenMatches) {
|
|
3086
|
+
brainCoverageAdvisory = "likely_covered";
|
|
3087
|
+
}
|
|
3088
|
+
else if (hasTokenMatches || diag.usedLearnedRouteFn) {
|
|
3089
|
+
brainCoverageAdvisory = "partial";
|
|
3090
|
+
}
|
|
3091
|
+
else {
|
|
3092
|
+
brainCoverageAdvisory = "likely_gap";
|
|
3093
|
+
}
|
|
3094
|
+
const kernelValidation = surface !== undefined ? validateKernelSurface(surface) : null;
|
|
3095
|
+
return {
|
|
3096
|
+
brain: {
|
|
3097
|
+
packId: compileResponse.packId,
|
|
3098
|
+
mode: diag.modeEffective,
|
|
3099
|
+
selectedBlockCount: compileResponse.selectedContext.length,
|
|
3100
|
+
selectedRoles,
|
|
3101
|
+
usedLearnedRouteFn: diag.usedLearnedRouteFn,
|
|
3102
|
+
compactionApplied
|
|
3103
|
+
},
|
|
3104
|
+
kernelValidation,
|
|
3105
|
+
brainCoverageAdvisory
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
495
3108
|
//# sourceMappingURL=index.js.map
|