@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/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 { compileRuntime } from "@openclawbrain/compiler";
6
- import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, validateNormalizedEventExport } from "@openclawbrain/contracts";
7
- import { inspectActivationState } from "@openclawbrain/pack-format";
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 fallbackCompileResult(error, activationRoot) {
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 compileResponse = compileRuntime(target.activePointer.packRootDir, {
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.activePointer.packId,
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 fallbackCompileResult(error, activationRoot);
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