@openclawbrain/cli 0.4.15 → 0.4.16

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/cli.js CHANGED
@@ -17,7 +17,7 @@ import { inspectOpenClawBrainHookStatus, inspectOpenClawBrainPluginAllowlist } f
17
17
  import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds, normalizeOpenClawBrainPluginsConfig, pinInstalledOpenClawBrainPluginActivationRoot, resolveOpenClawBrainInstallTarget } from "./openclaw-plugin-install.js";
18
18
  import { buildOpenClawBrainConvergeRestartPlan, classifyOpenClawBrainConvergeVerification, describeOpenClawBrainConvergeChangeReasons, diffOpenClawBrainConvergeRuntimeFingerprint, finalizeOpenClawBrainConvergeResult, planOpenClawBrainConvergePluginAction } from "./install-converge.js";
19
19
  import { loadAttachmentPolicyDeclaration, resolveEffectiveAttachmentPolicyTruth, writeAttachmentPolicyDeclaration } from "./attachment-policy-truth.js";
20
- import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, writeScannedEventExportBundle } from "./index.js";
20
+ import { DEFAULT_WATCH_POLL_INTERVAL_SECONDS, buildNormalizedEventExportFromScannedEvents, bootstrapRuntimeAttach, buildOperatorSurfaceReport, clearOpenClawProfileRuntimeLoadProof, compileRuntimeContext, createAsyncTeacherLiveLoop, createOpenClawLocalSessionTail, createRuntimeEventExportScanner, describeCurrentProfileBrainStatus, formatOperatorRollbackReport, listOpenClawProfileRuntimeLoadProofs, loadRuntimeEventExportBundle, loadWatchTeacherSnapshotState, persistWatchTeacherSnapshot, rollbackRuntimeAttach, resolveAttachmentRuntimeLoadProofsPath, resolveOperatorTeacherSnapshotPath, resolveAsyncTeacherLiveLoopSnapshotPath, resolveWatchSessionTailCursorPath, resolveWatchStateRoot, resolveWatchTeacherSnapshotPath, scanLiveEventExport, scanRecordedSession, summarizeLearningPathFromMaterialization, summarizeNormalizedEventExportLabelFlow, summarizeTeacherNoArtifactCycle, writeScannedEventExportBundle } from "./index.js";
21
21
  import { appendLearningUpdateLogs } from "./learning-spine.js";
22
22
  import { buildPassiveLearningSessionExportFromOpenClawSessionStore } from "./local-session-passive-learning.js";
23
23
  import { reindexMaterializationCandidateWithEmbedder } from "./materialization-embedder.js";
@@ -682,14 +682,14 @@ const LEARNING_WARNING_MESSAGES = {
682
682
  passive_backfill_pending: "passive backfill remains queued",
683
683
  teacher_queue_full: "teacher queue is full",
684
684
  teacher_labels_stale: "teacher labels are stale",
685
- teacher_no_artifacts: "teacher produced no artifacts",
685
+ teacher_no_artifacts: "latest no-op cycle had teachable material but no new teacher artifact",
686
686
  teacher_snapshot_unavailable: "teacher snapshot is unavailable"
687
687
  };
688
688
  const TEACHER_NO_OP_MESSAGES = {
689
689
  none: "the latest processed export produced teacher artifacts",
690
690
  duplicate_export: "the latest cycle was a no-op because the export was already seen",
691
691
  queue_full: "the latest cycle was a no-op because the teacher queue was full",
692
- no_teacher_artifacts: "the latest cycle was a no-op because no teacher artifacts were produced",
692
+ no_teacher_artifacts: "the latest cycle produced no new teacher artifacts",
693
693
  empty_scan: "the latest cycle was a no-op because the scanner did not produce any events",
694
694
  unavailable: "the latest cycle is not visible from the current operator snapshot"
695
695
  };
@@ -1089,7 +1089,9 @@ function summarizeStatusTeacher(report, providerConfig, localLlm) {
1089
1089
  const healthy = report.teacherLoop.failureMode === "none" &&
1090
1090
  stale === false &&
1091
1091
  report.teacherLoop.watchState !== "not_visible";
1092
- const cycleDetail = TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
1092
+ const cycleDetail = report.teacherLoop.lastNoOpReason === "no_teacher_artifacts"
1093
+ ? summarizeTeacherNoArtifactCycle(report.teacherLoop.notes).detail
1094
+ : TEACHER_NO_OP_MESSAGES[report.teacherLoop.lastNoOpReason] ?? "the latest teacher cycle detail is unavailable";
1093
1095
  if (report.teacherLoop.failureMode !== "none" && report.teacherLoop.failureMode !== "unavailable") {
1094
1096
  return {
1095
1097
  model: providerConfig.teacher.model,
@@ -5,7 +5,7 @@ import { type AdvanceAlwaysOnLearningRuntimeInput, type AlwaysOnLearningCadenceV
5
5
  import { type ActivationInspection, type ActivationObservabilityReport, type GraphEvolutionLogV1, type LearningSpineServeRouteBreadcrumbsV1, type ActivationSlotInspection, type InitHandoffState, type LearningSpineServeRouteDecisionLogEntryV1 } from "@openclawbrain/pack-format";
6
6
  export { clearOpenClawProfileRuntimeLoadProof, listOpenClawProfileRuntimeLoadProofs, recordOpenClawProfileRuntimeLoadProof, resolveAttachmentRuntimeLoadProofsPath, type OpenClawProfileRuntimeLoadProofRecordV1, type OpenClawProfileRuntimeLoadProofSetV1, type OpenClawProfileRuntimeLoadProofsV1 } from "./attachment-truth.js";
7
7
  import { type AsyncTeacherLabelerConfigV1 } from "./teacher-labeler.js";
8
- export { createHttpOllamaTeacherLabelerClient, createOllamaTeacherLabeler, createTeacherLabeler, type AsyncTeacherLabelerConfigV1, type AsyncTeacherNoopLabelerConfigV1, type AsyncTeacherOllamaLabelerConfigV1, type OllamaTeacherLabelerClient, type TeacherLabeler, type TeacherLabelerResultV1, type TeacherLabelerRunInputV1 } from "./teacher-labeler.js";
8
+ export { createHttpOllamaTeacherLabelerClient, createOllamaTeacherLabeler, createTeacherLabeler, summarizeTeacherLabelerOpportunity, type AsyncTeacherLabelerConfigV1, type AsyncTeacherNoopLabelerConfigV1, type AsyncTeacherOllamaLabelerConfigV1, type OllamaTeacherLabelerClient, type TeacherLabeler, type TeacherLabelerOpportunityInputV1, type TeacherLabelerOpportunityV1, type TeacherLabelerResultV1, type TeacherLabelerRunInputV1 } from "./teacher-labeler.js";
9
9
  export declare const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
10
10
  declare const RECORDED_SESSION_TRACE_CONTRACT: "recorded_session_trace.v1";
11
11
  declare const RECORDED_SESSION_FIXTURE_CONTRACT: "recorded_session_replay_fixture.v1";
@@ -401,6 +401,10 @@ export interface AsyncTeacherLiveLoopInput extends Pick<AdvanceAlwaysOnLearningR
401
401
  persistUpdatedBaseline?: (state: BaselineStateV1) => void;
402
402
  teacherLabeler?: AsyncTeacherLabelerConfigV1 | null;
403
403
  }
404
+ export interface TeacherNoArtifactCycleSummaryV1 {
405
+ shouldWarn: boolean;
406
+ detail: string;
407
+ }
404
408
  export interface AsyncTeacherQueuedExportJobV1 {
405
409
  jobId: string;
406
410
  exportDigest: string;
@@ -681,6 +685,7 @@ export declare const WATCH_STATE_DIRNAME = "watch";
681
685
  export declare const WATCH_SESSION_TAIL_CURSOR_BASENAME = "session-tail-cursor.json";
682
686
  export declare const WATCH_TEACHER_SNAPSHOT_BASENAME = "teacher-snapshot.json";
683
687
  export declare const DEFAULT_WATCH_POLL_INTERVAL_SECONDS = 30;
688
+ export declare function summarizeTeacherNoArtifactCycle(notes: readonly string[] | null | undefined): TeacherNoArtifactCycleSummaryV1;
684
689
  export interface WatchTeacherSnapshotFailureV1 {
685
690
  mode: "materialization_failed" | "teacher_fail_open";
686
691
  detail: string;
package/dist/src/index.js CHANGED
@@ -12,8 +12,8 @@ import { inspectOpenClawBrainHookStatus, summarizeOpenClawBrainHookLoad } from "
12
12
  import { appendLearningUpdateLogs, appendServeTimeRouteDecisionLog } from "./learning-spine.js";
13
13
  import { buildFeedbackSemanticMetadata, buildInteractionSemanticMetadata } from "./semantic-metadata.js";
14
14
  export { clearOpenClawProfileRuntimeLoadProof, listOpenClawProfileRuntimeLoadProofs, recordOpenClawProfileRuntimeLoadProof, resolveAttachmentRuntimeLoadProofsPath } from "./attachment-truth.js";
15
- import { createTeacherLabeler } from "./teacher-labeler.js";
16
- export { createHttpOllamaTeacherLabelerClient, createOllamaTeacherLabeler, createTeacherLabeler } from "./teacher-labeler.js";
15
+ import { createTeacherLabeler, summarizeTeacherLabelerOpportunity } from "./teacher-labeler.js";
16
+ export { createHttpOllamaTeacherLabelerClient, createOllamaTeacherLabeler, createTeacherLabeler, summarizeTeacherLabelerOpportunity } from "./teacher-labeler.js";
17
17
  const DEFAULT_AGENT_ID = "openclaw-runtime";
18
18
  const FEEDBACK_KINDS = new Set(["correction", "teaching", "approval", "suppression"]);
19
19
  export const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
@@ -250,9 +250,45 @@ function buildAsyncTeacherLoopNotes(input) {
250
250
  `teacher_noop=${input.noOpReason}`,
251
251
  `teacher_labeler=${input.teacherLabeler?.status ?? "disabled"}`,
252
252
  `teacher_labeler_detail=${input.teacherLabeler?.detail ?? "disabled"}`,
253
+ `teacher_last_cycle_deterministic_artifacts=${input.lastCycle?.deterministicArtifactCount ?? "unknown"}`,
254
+ `teacher_last_cycle_new_deterministic_artifacts=${input.lastCycle?.newDeterministicArtifactCount ?? "unknown"}`,
255
+ `teacher_last_cycle_labeler_candidates=${input.lastCycle?.labelerCandidateCount ?? "unknown"}`,
256
+ `teacher_last_cycle_labeler_budgeted_candidates=${input.lastCycle?.labelerBudgetedCandidateCount ?? "unknown"}`,
257
+ `teacher_last_cycle_labeler_status=${input.lastCycle?.labelerStatus ?? "unknown"}`,
258
+ `teacher_last_cycle_labeler_detail=${input.lastCycle?.labelerDetail ?? "unknown"}`,
253
259
  input.materialization === null ? "teacher_materialization=noop" : `teacher_materialized_pack=${input.materialization.candidate.summary.packId}`
254
260
  ];
255
261
  }
262
+ function parseAsyncTeacherLastCycleNotes(notes) {
263
+ const values = new Map();
264
+ for (const note of notes) {
265
+ const separator = note.indexOf("=");
266
+ if (separator <= 0) {
267
+ continue;
268
+ }
269
+ values.set(note.slice(0, separator), note.slice(separator + 1));
270
+ }
271
+ const readNullableNumber = (key) => {
272
+ const raw = values.get(key);
273
+ if (raw === undefined || raw === "unknown") {
274
+ return null;
275
+ }
276
+ const parsed = Number.parseInt(raw, 10);
277
+ return Number.isFinite(parsed) ? parsed : null;
278
+ };
279
+ const readNullableString = (key) => {
280
+ const raw = values.get(key);
281
+ return raw === undefined || raw === "unknown" ? null : raw;
282
+ };
283
+ return {
284
+ deterministicArtifactCount: readNullableNumber("teacher_last_cycle_deterministic_artifacts"),
285
+ newDeterministicArtifactCount: readNullableNumber("teacher_last_cycle_new_deterministic_artifacts"),
286
+ labelerCandidateCount: readNullableNumber("teacher_last_cycle_labeler_candidates"),
287
+ labelerBudgetedCandidateCount: readNullableNumber("teacher_last_cycle_labeler_budgeted_candidates"),
288
+ labelerStatus: readNullableString("teacher_last_cycle_labeler_status"),
289
+ labelerDetail: readNullableString("teacher_last_cycle_labeler_detail")
290
+ };
291
+ }
256
292
  function cloneAlwaysOnLearningMaterializationJobOrNull(value) {
257
293
  return value === null ? null : structuredClone(value);
258
294
  }
@@ -565,6 +601,14 @@ export class AsyncTeacherLiveLoop {
565
601
  learnerState = createAlwaysOnLearningRuntimeState();
566
602
  lastMaterialization = null;
567
603
  lastTeacherLabelerResult = null;
604
+ lastCycle = {
605
+ deterministicArtifactCount: null,
606
+ newDeterministicArtifactCount: null,
607
+ labelerCandidateCount: null,
608
+ labelerBudgetedCandidateCount: null,
609
+ labelerStatus: null,
610
+ labelerDetail: null
611
+ };
568
612
  diagnostics = {
569
613
  acceptedExportCount: 0,
570
614
  processedExportCount: 0,
@@ -584,7 +628,8 @@ export class AsyncTeacherLiveLoop {
584
628
  sparseFeedback: this.learnerState.sparseFeedback,
585
629
  noOpReason: "none",
586
630
  materialization: null,
587
- teacherLabeler: null
631
+ teacherLabeler: null,
632
+ lastCycle: this.lastCycle
588
633
  })
589
634
  };
590
635
  constructor(input) {
@@ -612,6 +657,7 @@ export class AsyncTeacherLiveLoop {
612
657
  ...structuredClone(resumedSnapshot.diagnostics),
613
658
  notes: [...resumedSnapshot.diagnostics.notes]
614
659
  };
660
+ this.lastCycle = parseAsyncTeacherLastCycleNotes(this.diagnostics.notes);
615
661
  for (const exportDigest of resumedSnapshot.state?.seenExportDigests ?? []) {
616
662
  this.seenExportDigests.add(exportDigest);
617
663
  }
@@ -858,6 +904,19 @@ export class AsyncTeacherLiveLoop {
858
904
  feedbackEvents: this.feedbackEvents
859
905
  });
860
906
  const learnedRoutingState = this.input.resolveLearnedRoutingState?.() ?? {};
907
+ const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
908
+ const currentCycleBuiltArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
909
+ normalizedEventExport: job.normalizedEventExport,
910
+ observedAt: job.observedAt,
911
+ staleAfterMs: this.staleAfterMs,
912
+ ...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {})
913
+ });
914
+ const currentCycleOpportunity = summarizeTeacherLabelerOpportunity({
915
+ normalizedEventExport: job.normalizedEventExport,
916
+ ...(learnedRoutingState.serveTimeDecisions !== undefined
917
+ ? { serveTimeDecisions: learnedRoutingState.serveTimeDecisions }
918
+ : {})
919
+ }, this.input.teacherLabeler ?? null);
861
920
  const builtArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
862
921
  normalizedEventExport: mergedNormalizedEventExport,
863
922
  observedAt: job.observedAt,
@@ -887,10 +946,21 @@ export class AsyncTeacherLiveLoop {
887
946
  }
888
947
  }
889
948
  const nextBuiltArtifacts = mergeTeacherArtifacts([], [...builtArtifacts, ...generatedTeacherArtifacts]);
890
- const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
891
949
  const nextTeacherArtifacts = mergeTeacherArtifacts(this.teacherArtifacts, nextBuiltArtifacts);
892
950
  const emittedArtifactCount = nextBuiltArtifacts.filter((artifact) => !currentDedupIds.has(artifact.dedupId)).length;
893
951
  const dedupedArtifactCount = nextBuiltArtifacts.length - emittedArtifactCount;
952
+ this.lastCycle = {
953
+ deterministicArtifactCount: currentCycleBuiltArtifacts.length,
954
+ newDeterministicArtifactCount: currentCycleBuiltArtifacts.filter((artifact) => !currentDedupIds.has(artifact.dedupId)).length,
955
+ labelerCandidateCount: currentCycleOpportunity.candidateCount,
956
+ labelerBudgetedCandidateCount: currentCycleOpportunity.budgetedCandidateCount,
957
+ labelerStatus: currentCycleOpportunity.candidateCount === 0
958
+ ? currentCycleOpportunity.status
959
+ : this.lastTeacherLabelerResult?.status ?? (currentCycleOpportunity.enabled ? "unknown" : "disabled"),
960
+ labelerDetail: currentCycleOpportunity.candidateCount === 0
961
+ ? currentCycleOpportunity.detail
962
+ : this.lastTeacherLabelerResult?.detail ?? currentCycleOpportunity.detail
963
+ };
894
964
  this.teacherArtifacts = nextTeacherArtifacts;
895
965
  const learnerResult = advanceAlwaysOnLearningRuntime({
896
966
  packLabel: this.input.packLabel,
@@ -952,7 +1022,8 @@ export class AsyncTeacherLiveLoop {
952
1022
  sparseFeedback: this.learnerState.sparseFeedback,
953
1023
  noOpReason: this.diagnostics.lastNoOpReason,
954
1024
  materialization: this.lastMaterialization,
955
- teacherLabeler: this.lastTeacherLabelerResult
1025
+ teacherLabeler: this.lastTeacherLabelerResult,
1026
+ lastCycle: this.lastCycle
956
1027
  });
957
1028
  }
958
1029
  }
@@ -6981,11 +7052,104 @@ function summarizeLearningWarningStates(input) {
6981
7052
  if (input.teacherSnapshot.diagnostics.latestFreshness === "stale" && input.teacherSnapshot.diagnostics.lastNoOpReason !== "no_teacher_artifacts") {
6982
7053
  warnings.add("teacher_labels_stale");
6983
7054
  }
6984
- if (input.teacherSnapshot.diagnostics.lastNoOpReason === "no_teacher_artifacts") {
7055
+ if (input.teacherSnapshot.diagnostics.lastNoOpReason === "no_teacher_artifacts" &&
7056
+ summarizeTeacherNoArtifactCycle(input.teacherSnapshot.diagnostics.notes).shouldWarn) {
6985
7057
  warnings.add("teacher_no_artifacts");
6986
7058
  }
6987
7059
  return [...warnings];
6988
7060
  }
7061
+ export function summarizeTeacherNoArtifactCycle(notes) {
7062
+ const values = new Map();
7063
+ for (const note of notes ?? []) {
7064
+ const separator = note.indexOf("=");
7065
+ if (separator <= 0) {
7066
+ continue;
7067
+ }
7068
+ values.set(note.slice(0, separator), note.slice(separator + 1));
7069
+ }
7070
+ const readNullableNumber = (key) => {
7071
+ const raw = values.get(key);
7072
+ if (raw === undefined || raw === "unknown") {
7073
+ return null;
7074
+ }
7075
+ const parsed = Number.parseInt(raw, 10);
7076
+ return Number.isFinite(parsed) ? parsed : null;
7077
+ };
7078
+ const readNullableString = (key) => {
7079
+ const raw = values.get(key);
7080
+ return raw === undefined || raw === "unknown" ? null : raw;
7081
+ };
7082
+ const deterministicArtifactCount = readNullableNumber("teacher_last_cycle_deterministic_artifacts");
7083
+ const newDeterministicArtifactCount = readNullableNumber("teacher_last_cycle_new_deterministic_artifacts");
7084
+ const labelerCandidateCount = readNullableNumber("teacher_last_cycle_labeler_candidates");
7085
+ const labelerBudgetedCandidateCount = readNullableNumber("teacher_last_cycle_labeler_budgeted_candidates");
7086
+ const labelerStatus = readNullableString("teacher_last_cycle_labeler_status");
7087
+ const labelerDetail = readNullableString("teacher_last_cycle_labeler_detail");
7088
+ if (deterministicArtifactCount === null && labelerCandidateCount === null) {
7089
+ return {
7090
+ shouldWarn: true,
7091
+ detail: "the latest cycle produced no new teacher artifacts, and the snapshot did not say whether any teachable material was present"
7092
+ };
7093
+ }
7094
+ if ((deterministicArtifactCount ?? 0) === 0 && (labelerCandidateCount ?? 0) === 0) {
7095
+ return {
7096
+ shouldWarn: false,
7097
+ detail: "the latest cycle produced no new teacher artifacts because the current export had no eligible feedback, operator overrides, or matched interaction text"
7098
+ };
7099
+ }
7100
+ if ((deterministicArtifactCount ?? 0) > 0 &&
7101
+ (newDeterministicArtifactCount ?? 0) === 0 &&
7102
+ (labelerCandidateCount ?? 0) === 0) {
7103
+ return {
7104
+ shouldWarn: false,
7105
+ detail: "the latest cycle produced no new teacher artifacts because the current export only repeated supervision that was already captured"
7106
+ };
7107
+ }
7108
+ if ((labelerCandidateCount ?? 0) > 0) {
7109
+ if ((labelerBudgetedCandidateCount ?? labelerCandidateCount) === 0) {
7110
+ return {
7111
+ shouldWarn: true,
7112
+ detail: "the latest cycle produced no new teacher artifacts because candidate interactions exceeded the teacher labeler prompt budget"
7113
+ };
7114
+ }
7115
+ if (labelerStatus === "disabled") {
7116
+ return {
7117
+ shouldWarn: false,
7118
+ detail: "the latest cycle produced no new teacher artifacts because candidate interactions existed, but the background teacher labeler is disabled"
7119
+ };
7120
+ }
7121
+ if (labelerStatus === "ok") {
7122
+ return {
7123
+ shouldWarn: false,
7124
+ detail: "the latest cycle produced no new teacher artifacts because candidate interactions were evaluated but did not add a new reusable label"
7125
+ };
7126
+ }
7127
+ if (labelerStatus === "fail_open") {
7128
+ return {
7129
+ shouldWarn: true,
7130
+ detail: labelerDetail === null
7131
+ ? "the latest cycle produced no new teacher artifacts because the teacher labeler failed open while candidate interactions were present"
7132
+ : `the latest cycle produced no new teacher artifacts because the teacher labeler failed open while candidate interactions were present: ${labelerDetail}`
7133
+ };
7134
+ }
7135
+ if (labelerDetail === "no_labels_emitted") {
7136
+ return {
7137
+ shouldWarn: true,
7138
+ detail: "the latest cycle produced no new teacher artifacts even though candidate interactions were present; the teacher labeler emitted no reusable labels"
7139
+ };
7140
+ }
7141
+ return {
7142
+ shouldWarn: true,
7143
+ detail: labelerDetail === null
7144
+ ? "the latest cycle produced no new teacher artifacts even though candidate interactions were present"
7145
+ : `the latest cycle produced no new teacher artifacts even though candidate interactions were present: ${labelerDetail}`
7146
+ };
7147
+ }
7148
+ return {
7149
+ shouldWarn: true,
7150
+ detail: "the latest cycle produced no new teacher artifacts even though teachable material was present"
7151
+ };
7152
+ }
6989
7153
  function summarizeAlwaysOnLearning(input, active) {
6990
7154
  const unavailableLag = {
6991
7155
  activeEventRangeEnd: active?.eventRange.end ?? null,
@@ -142,9 +142,10 @@ export function classifyOpenClawBrainConvergeVerification(input) {
142
142
  if (displayedStatus === "fail") {
143
143
  blockingReasons.push("status still reports fail");
144
144
  }
145
- const runtimeTruthAlreadyProven = displayedStatus === "ok"
146
- && runtimeLoad === "proven"
147
- && loadProof === "status_probe_ready";
145
+ const runtimeTruthAlreadyProven = runtimeLoad === "proven"
146
+ && loadProof === "status_probe_ready"
147
+ && installState === "installed"
148
+ && loadability === "loadable";
148
149
  if (input.restartRequired === true && input.restartPerformed !== true && !runtimeTruthAlreadyProven) {
149
150
  blockingReasons.push("restart is still required before runtime load can be trusted");
150
151
  }
@@ -4854,8 +4854,10 @@ function isCarryForwardSeedBlock(block) {
4854
4854
  if (typeof block.text !== "string" || block.text.trim().length === 0) {
4855
4855
  return false;
4856
4856
  }
4857
- return block.initSeed !== undefined ||
4858
- block.semantic?.sourceKind === "recorded_session_seed" ||
4857
+ // Only true seed-session evidence should survive prefix-changing promotions.
4858
+ // Ordinary runtime-turn feedback is re-materialized from learnedEventExport;
4859
+ // carrying it forward under the old runtime-graph id duplicates evidence.
4860
+ return block.semantic?.sourceKind === "recorded_session_seed" ||
4859
4861
  block.source.includes("/seed:");
4860
4862
  }
4861
4863
  function carryForwardSeedBlockScore(block) {
@@ -398,8 +398,10 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
398
398
  const runtimeProofMatched = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
399
399
  && runtimeLoadProofSnapshot.value.profiles.some((profile) => canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome));
400
400
  const runtimeTruthGaps = [];
401
- if (!statusSignals.statusOk)
402
- runtimeTruthGaps.push("status_ok");
401
+ const strongRuntimeTruth = statusSignals.loadProofReady
402
+ && statusSignals.runtimeProven
403
+ && statusSignals.serveActivePack
404
+ && statusSignals.routeFnAvailable;
403
405
  if (!statusSignals.loadProofReady)
404
406
  runtimeTruthGaps.push("load_proof");
405
407
  if (!statusSignals.runtimeProven)
@@ -410,6 +412,15 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
410
412
  runtimeTruthGaps.push("route_fn");
411
413
  const warningCodes = [];
412
414
  const warnings = [];
415
+ if (!statusSignals.statusOk) {
416
+ if (strongRuntimeTruth) {
417
+ warningCodes.push("status_warn");
418
+ warnings.push("detailed status did not return STATUS ok, but loadProof/runtime/serve/routeFn proofs stayed healthy");
419
+ }
420
+ else {
421
+ runtimeTruthGaps.push("status_ok");
422
+ }
423
+ }
413
424
  if (!gatewayHealthy) {
414
425
  warningCodes.push("gateway_health");
415
426
  warnings.push("gateway status did not confirm runtime running and RPC probe ok");
@@ -597,6 +608,17 @@ function buildGatewayArgs(action, profileName) {
597
608
  : ["gateway", action, "--profile", profileName];
598
609
  }
599
610
 
611
+ function buildGatewayStatusArgs(profileName, gatewayUrl, gatewayToken) {
612
+ const args = buildGatewayArgs("status", profileName);
613
+ if (gatewayUrl !== null) {
614
+ args.push("--url", gatewayUrl);
615
+ }
616
+ if (gatewayToken !== null) {
617
+ args.push("--token", gatewayToken);
618
+ }
619
+ return args;
620
+ }
621
+
600
622
  export function buildProofCommandForOpenClawHome(openclawHome) {
601
623
  return `openclawbrain proof --openclaw-home ${quoteShellArg(path.resolve(openclawHome))}`;
602
624
  }
@@ -609,6 +631,8 @@ export function buildProofCommandHelpSection() {
609
631
  " --skip-install Capture proof without rerunning install first (proof only).",
610
632
  " --skip-restart Capture proof without restarting OpenClaw first (proof only).",
611
633
  ` --plugin-id <id> Plugin id for \`openclaw plugins inspect\` (proof only; default: ${DEFAULT_OPERATOR_PROOF_PLUGIN_ID}).`,
634
+ " --gateway-url <url> Override the gateway-status probe target for proof capture (proof only).",
635
+ " --gateway-token <token> Gateway token to use with --gateway-url or other non-default proof probes.",
612
636
  ` --timeout-ms <ms> Per-step timeout in ms for proof capture (proof only; default: ${DEFAULT_OPERATOR_PROOF_TIMEOUT_MS}).`,
613
637
  ],
614
638
  lifecycle: " 5. proof openclawbrain proof --openclaw-home <path> - capture one durable operator proof bundle after install/restart/status",
@@ -624,6 +648,8 @@ export function parseProofCliArgs(argv, options = {}) {
624
648
  let skipInstall = false;
625
649
  let skipRestart = false;
626
650
  let pluginId = DEFAULT_OPERATOR_PROOF_PLUGIN_ID;
651
+ let gatewayUrl = null;
652
+ let gatewayToken = null;
627
653
  let timeoutMs = DEFAULT_OPERATOR_PROOF_TIMEOUT_MS;
628
654
  let json = false;
629
655
  let help = false;
@@ -681,6 +707,24 @@ export function parseProofCliArgs(argv, options = {}) {
681
707
  index += 1;
682
708
  continue;
683
709
  }
710
+ if (arg === "--gateway-url") {
711
+ const next = argv[index + 1];
712
+ if (next === undefined) {
713
+ throw new Error("--gateway-url requires a value");
714
+ }
715
+ gatewayUrl = next;
716
+ index += 1;
717
+ continue;
718
+ }
719
+ if (arg === "--gateway-token") {
720
+ const next = argv[index + 1];
721
+ if (next === undefined) {
722
+ throw new Error("--gateway-token requires a value");
723
+ }
724
+ gatewayToken = next;
725
+ index += 1;
726
+ continue;
727
+ }
684
728
  if (arg === "--timeout-ms") {
685
729
  const next = argv[index + 1];
686
730
  if (next === undefined) {
@@ -705,6 +749,8 @@ export function parseProofCliArgs(argv, options = {}) {
705
749
  skipInstall,
706
750
  skipRestart,
707
751
  pluginId,
752
+ gatewayUrl,
753
+ gatewayToken,
708
754
  timeoutMs,
709
755
  json,
710
756
  help
@@ -725,6 +771,8 @@ export function parseProofCliArgs(argv, options = {}) {
725
771
  skipInstall,
726
772
  skipRestart,
727
773
  pluginId,
774
+ gatewayUrl,
775
+ gatewayToken,
728
776
  timeoutMs,
729
777
  json,
730
778
  help
@@ -783,7 +831,11 @@ export function captureOperatorProofBundle(options) {
783
831
  }
784
832
  addStep("01-install", "install", cliInvocation.command, [...cliInvocation.args, "install", "--openclaw-home", options.openclawHome], { skipped: options.skipInstall === true });
785
833
  addStep("02-restart", "gateway restart", "openclaw", buildGatewayArgs("restart", gatewayProfile), { skipped: options.skipRestart === true });
786
- const gatewayStatusCapture = addStep("03-gateway-status", "gateway status", "openclaw", buildGatewayArgs("status", gatewayProfile));
834
+ const gatewayStatusCapture = addStep("03-gateway-status", "gateway status", "openclaw", buildGatewayStatusArgs(
835
+ gatewayProfile,
836
+ normalizeOptionalCliString(options.gatewayUrl ?? null),
837
+ normalizeOptionalCliString(options.gatewayToken ?? null),
838
+ ));
787
839
  const pluginInspectCapture = addStep("04-plugin-inspect", "plugin inspect", "openclaw", ["plugins", "inspect", options.pluginId]);
788
840
  const statusCapture = addStep("05-detailed-status", "detailed status", cliInvocation.command, [...cliInvocation.args, "status", "--openclaw-home", options.openclawHome, "--detailed"]);
789
841
  const gatewayLogPath = extractGatewayLogPath(gatewayStatusCapture.stdout);
@@ -15,6 +15,17 @@ export interface TeacherLabelerResultV1 {
15
15
  export interface TeacherLabeler {
16
16
  label(input: TeacherLabelerRunInputV1): Promise<TeacherLabelerResultV1>;
17
17
  }
18
+ export interface TeacherLabelerOpportunityInputV1 {
19
+ normalizedEventExport: NormalizedEventExportV1;
20
+ serveTimeDecisions?: readonly LearningSpineServeRouteDecisionLogEntryV1[];
21
+ }
22
+ export interface TeacherLabelerOpportunityV1 {
23
+ enabled: boolean;
24
+ candidateCount: number;
25
+ budgetedCandidateCount: number;
26
+ status: "disabled" | "ready" | "skipped";
27
+ detail: string;
28
+ }
18
29
  export interface OllamaTeacherLabelerGenerateInputV1 {
19
30
  model: string;
20
31
  prompt: string;
@@ -47,4 +58,5 @@ export interface AsyncTeacherNoopLabelerConfigV1 {
47
58
  export type AsyncTeacherLabelerConfigV1 = AsyncTeacherNoopLabelerConfigV1 | AsyncTeacherOllamaLabelerConfigV1;
48
59
  export declare function createHttpOllamaTeacherLabelerClient(baseUrl?: string): OllamaTeacherLabelerClient;
49
60
  export declare function createOllamaTeacherLabeler(config: AsyncTeacherOllamaLabelerConfigV1): TeacherLabeler;
61
+ export declare function summarizeTeacherLabelerOpportunity(input: TeacherLabelerOpportunityInputV1, config?: AsyncTeacherLabelerConfigV1 | null): TeacherLabelerOpportunityV1;
50
62
  export declare function createTeacherLabeler(config: AsyncTeacherLabelerConfigV1 | null | undefined): TeacherLabeler | null;
@@ -246,6 +246,48 @@ function normalizeOllamaTeacherLabelerConfig(config) {
246
246
  client: config.client ?? createHttpOllamaTeacherLabelerClient(normalizeBaseUrl(config.baseUrl))
247
247
  };
248
248
  }
249
+ export function summarizeTeacherLabelerOpportunity(input, config) {
250
+ const normalized = config === undefined || config === null || config.provider === "none"
251
+ ? {
252
+ enabled: false,
253
+ maxPromptChars: DEFAULT_OLLAMA_MAX_PROMPT_CHARS,
254
+ maxArtifactsPerExport: DEFAULT_OLLAMA_MAX_ARTIFACTS_PER_EXPORT,
255
+ maxInteractionsPerExport: DEFAULT_OLLAMA_MAX_INTERACTIONS,
256
+ maxUserMessageChars: DEFAULT_OLLAMA_MAX_USER_MESSAGE_CHARS,
257
+ maxContextIdsPerDecision: DEFAULT_OLLAMA_MAX_CONTEXT_IDS
258
+ }
259
+ : {
260
+ enabled: true,
261
+ ...normalizeOllamaTeacherLabelerConfig(config)
262
+ };
263
+ const candidates = collectCandidates(input, normalized);
264
+ if (candidates.length === 0) {
265
+ return {
266
+ enabled: normalized.enabled,
267
+ candidateCount: 0,
268
+ budgetedCandidateCount: 0,
269
+ status: normalized.enabled ? "skipped" : "disabled",
270
+ detail: "no_matching_interaction_text"
271
+ };
272
+ }
273
+ const budgetedCandidates = fitCandidatesToPromptBudget(candidates, normalized);
274
+ if (budgetedCandidates.length === 0) {
275
+ return {
276
+ enabled: normalized.enabled,
277
+ candidateCount: candidates.length,
278
+ budgetedCandidateCount: 0,
279
+ status: normalized.enabled ? "skipped" : "disabled",
280
+ detail: "prompt_budget_exhausted"
281
+ };
282
+ }
283
+ return {
284
+ enabled: normalized.enabled,
285
+ candidateCount: candidates.length,
286
+ budgetedCandidateCount: budgetedCandidates.length,
287
+ status: normalized.enabled ? "ready" : "disabled",
288
+ detail: `candidates=${budgetedCandidates.length}`
289
+ };
290
+ }
249
291
  class HttpOllamaTeacherLabelerClient {
250
292
  baseUrl;
251
293
  constructor(baseUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclawbrain/cli",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "OpenClawBrain operator CLI package with install/status helpers, daemon controls, and import/export tooling.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",