@openclawbrain/cli 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/dist/src/cli.js +8 -2
- package/dist/src/index.js +5 -2
- package/dist/src/local-learner.d.ts +4 -0
- package/dist/src/local-learner.js +212 -5
- package/dist/src/status-learning-path.js +28 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
# @openclawbrain/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Operator CLI for OpenClawBrain. Use it with `@openclawbrain/openclaw`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The public install story is three commands to install or update, then one command to verify.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
openclaw plugins install @openclawbrain/openclaw
|
|
9
|
-
npx @openclawbrain/cli
|
|
8
|
+
openclaw plugins install @openclawbrain/openclaw
|
|
9
|
+
npx @openclawbrain/cli install --openclaw-home ~/.openclaw
|
|
10
10
|
openclaw gateway restart
|
|
11
|
-
npx @openclawbrain/cli
|
|
11
|
+
npx @openclawbrain/cli status --openclaw-home ~/.openclaw --detailed
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
The first three commands install or update OpenClawBrain. The last command verifies the selected OpenClaw home.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
This package carries the `openclawbrain` CLI, daemon controls, import/export helpers, and install/status/operator management code. `@openclawbrain/openclaw` is the plugin/runtime payload.
|
|
19
|
-
|
|
20
|
-
## Commands
|
|
16
|
+
## Common commands
|
|
21
17
|
|
|
22
18
|
```bash
|
|
23
|
-
npx @openclawbrain/cli
|
|
24
|
-
npx @openclawbrain/cli
|
|
25
|
-
npx @openclawbrain/cli
|
|
26
|
-
npx @openclawbrain/cli
|
|
19
|
+
npx @openclawbrain/cli rollback --openclaw-home ~/.openclaw --dry-run
|
|
20
|
+
npx @openclawbrain/cli detach --openclaw-home ~/.openclaw
|
|
21
|
+
npx @openclawbrain/cli uninstall --openclaw-home ~/.openclaw --keep-data
|
|
22
|
+
npx @openclawbrain/cli learn --openclaw-home ~/.openclaw --json
|
|
23
|
+
npx @openclawbrain/cli daemon status --activation-root ~/.openclawbrain/activation
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
## Docs
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
- [Repo README](../../README.md)
|
|
29
|
+
- [Quick start](../../docs/getting-started/quick-start.md)
|
|
30
|
+
- [Lifecycle](../../docs/lifecycle.md)
|
|
31
|
+
- [Troubleshooting](../../docs/operating/troubleshooting.md)
|
package/dist/src/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ import { summarizePackVectorEmbeddingState } from "./embedding-status.js";
|
|
|
23
23
|
import { buildTracedLearningBridgePayloadFromRuntime, buildTracedLearningStatusSurface, persistTracedLearningBridgeState } from "./traced-learning-bridge.js";
|
|
24
24
|
import { discoverOpenClawSessionStores, loadOpenClawSessionIndex, readOpenClawSessionFile } from "./session-store.js";
|
|
25
25
|
import { readOpenClawBrainProviderDefaults, readOpenClawBrainProviderConfig, readOpenClawBrainProviderConfigFromSources, resolveOpenClawBrainProviderDefaultsPath } from "./provider-config.js";
|
|
26
|
+
import { formatOperatorLearningPathSummary } from "./status-learning-path.js";
|
|
26
27
|
const OPENCLAWBRAIN_EMBEDDER_BASE_URL_ENV = "OPENCLAWBRAIN_EMBEDDER_BASE_URL";
|
|
27
28
|
const OPENCLAWBRAIN_EMBEDDER_PROVIDER_ENV = "OPENCLAWBRAIN_EMBEDDER_PROVIDER";
|
|
28
29
|
const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
|
|
@@ -1396,7 +1397,11 @@ function formatCurrentProfileStatusSummary(status, report, targetInspection, opt
|
|
|
1396
1397
|
`scanner flowing=${yesNo(report.supervision.flowing)} scan=${report.supervision.scanPolicy ?? "none"} surfaces=${formatScannerSurfaces(report)} labels=${report.supervision.humanLabelCount ?? "none"}/${report.supervision.selfLabelCount ?? "none"} attributable=${report.supervision.attributedEventCount ?? "none"}/${report.supervision.totalEventCount ?? "none"} digests=${report.supervision.selectionDigestCount ?? "none"}`,
|
|
1397
1398
|
`labels ${formatLabelFlowSummary(report.labelFlow)}`,
|
|
1398
1399
|
`graph source=${report.graph.runtimePlasticitySource ?? "none"} blocks=${report.graph.blockCount ?? "none"} strongest=${report.graph.strongestBlockId ?? "none"} ops=${formatStructuralOps(report)} latest=${report.graph.latestMaterialization.packId ?? "none"} latestChanged=${yesNo(report.graph.latestMaterialization.changed)} connect=${formatGraphConnectDiagnostics(report.graph.latestMaterialization.connectDiagnostics ?? report.graph.connectDiagnostics)} summary=${formatGraphSummary(report)}`,
|
|
1399
|
-
`path ${
|
|
1400
|
+
`path ${formatOperatorLearningPathSummary({
|
|
1401
|
+
status,
|
|
1402
|
+
learningPath: report.learningPath,
|
|
1403
|
+
tracedLearning
|
|
1404
|
+
})}`,
|
|
1400
1405
|
`learning state=${report.learning.backlogState} bootstrapped=${yesNo(report.learning.bootstrapped)} mode=${report.learning.mode} next=${report.learning.nextPriorityLane} priority=${report.learning.nextPriorityBucket} pending=${report.learning.pendingLive ?? "none"}/${report.learning.pendingBackfill ?? "none"} buckets=${formatLearningBuckets(report)} warn=${formatLearningWarnings(report)} lastPack=${report.learning.lastMaterializedPackId ?? "none"} detail=${report.learning.detail}`,
|
|
1401
1406
|
`traced ${formatTracedLearningSurface(tracedLearning)}`,
|
|
1402
1407
|
`teacherProof ${formatTeacherLoopSummary(report)}`,
|
|
@@ -4238,7 +4243,8 @@ function runLearnCommand(parsed) {
|
|
|
4238
4243
|
maxCycles: 16,
|
|
4239
4244
|
pgVersion: serveTimeLearning.pgVersion,
|
|
4240
4245
|
...(serveTimeLearning.decisionLogCount > 0 ? { serveTimeDecisions: serveTimeLearning.serveTimeDecisions } : {}),
|
|
4241
|
-
...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {})
|
|
4246
|
+
...(serveTimeLearning.baselineState !== undefined ? { baselineState: serveTimeLearning.baselineState } : {}),
|
|
4247
|
+
activationRoot
|
|
4242
4248
|
});
|
|
4243
4249
|
const lastMaterialization = learnerResult.materializations.at(-1) ?? null;
|
|
4244
4250
|
const plan = describeAlwaysOnLearningRuntimeState(learnerResult.state, lastMaterialization);
|
package/dist/src/index.js
CHANGED
|
@@ -887,7 +887,8 @@ export class AsyncTeacherLiveLoop {
|
|
|
887
887
|
...(this.input.cadence !== undefined ? { cadence: this.input.cadence } : {}),
|
|
888
888
|
...(learnedRoutingState.pgVersion !== undefined ? { pgVersion: learnedRoutingState.pgVersion } : {}),
|
|
889
889
|
...(learnedRoutingState.serveTimeDecisions !== undefined ? { serveTimeDecisions: learnedRoutingState.serveTimeDecisions } : {}),
|
|
890
|
-
...(learnedRoutingState.baselineState !== undefined ? { baselineState: learnedRoutingState.baselineState } : {})
|
|
890
|
+
...(learnedRoutingState.baselineState !== undefined ? { baselineState: learnedRoutingState.baselineState } : {}),
|
|
891
|
+
...(this.input.activationRoot !== undefined ? { activationRoot: this.input.activationRoot } : {})
|
|
891
892
|
});
|
|
892
893
|
this.learnerState = structuredClone(learnerResult.state);
|
|
893
894
|
this.lastMaterialization = cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization);
|
|
@@ -996,6 +997,7 @@ export function scanLiveEventExport(input) {
|
|
|
996
997
|
learnedRouting: input.learnedRouting ?? true,
|
|
997
998
|
state: createAlwaysOnLearningRuntimeState(),
|
|
998
999
|
builtAt: normalizeIsoTimestamp(input.builtAt, "builtAt", observedAt),
|
|
1000
|
+
...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {}),
|
|
999
1001
|
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
1000
1002
|
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {})
|
|
1001
1003
|
});
|
|
@@ -4037,7 +4039,8 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
4037
4039
|
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
|
|
4038
4040
|
...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
|
|
4039
4041
|
...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
|
|
4040
|
-
...(input.cadence !== undefined ? { cadence: input.cadence } : {})
|
|
4042
|
+
...(input.cadence !== undefined ? { cadence: input.cadence } : {}),
|
|
4043
|
+
activationRoot
|
|
4041
4044
|
});
|
|
4042
4045
|
currentState.learner = structuredClone(learnerResult.state);
|
|
4043
4046
|
currentState.runtimePlasticity = learnerResult.state.runtimePlasticity === null ? null : structuredClone(learnerResult.state.runtimePlasticity);
|
|
@@ -29,6 +29,7 @@ export interface CandidatePackBuildInput {
|
|
|
29
29
|
serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
|
|
30
30
|
/** Baseline state for V2 variance reduction. */
|
|
31
31
|
baselineState?: BaselineStateV1;
|
|
32
|
+
activationRoot?: string;
|
|
32
33
|
}
|
|
33
34
|
export interface CandidatePackFromNormalizedEventExportInput {
|
|
34
35
|
packLabel: string;
|
|
@@ -45,6 +46,7 @@ export interface CandidatePackFromNormalizedEventExportInput {
|
|
|
45
46
|
pgVersion?: "v1" | "v2";
|
|
46
47
|
serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
|
|
47
48
|
baselineState?: BaselineStateV1;
|
|
49
|
+
activationRoot?: string;
|
|
48
50
|
}
|
|
49
51
|
export interface BuildTeacherSupervisionArtifactsInput {
|
|
50
52
|
normalizedEventExport: NormalizedEventExportV1;
|
|
@@ -66,6 +68,7 @@ interface CandidatePackBridgeInputBase {
|
|
|
66
68
|
pgVersion?: "v1" | "v2";
|
|
67
69
|
serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
|
|
68
70
|
baselineState?: BaselineStateV1;
|
|
71
|
+
activationRoot?: string;
|
|
69
72
|
}
|
|
70
73
|
export interface CandidatePackFromNormalizedEventExportSliceInput extends CandidatePackBridgeInputBase {
|
|
71
74
|
normalizedEventExportSlice: NormalizedEventExportSliceV1;
|
|
@@ -325,6 +328,7 @@ export interface AdvanceAlwaysOnLearningRuntimeInput {
|
|
|
325
328
|
pgVersion?: "v1" | "v2";
|
|
326
329
|
serveTimeDecisions?: LearningSpineServeRouteDecisionLogEntryV1[];
|
|
327
330
|
baselineState?: BaselineStateV1;
|
|
331
|
+
activationRoot?: string;
|
|
328
332
|
}
|
|
329
333
|
export interface AlwaysOnLearningMaterializationJobV1 {
|
|
330
334
|
jobId: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
4
|
import { buildRouteArtifactReference, CONTRACT_IDS, PACK_GRAPH_SCHEMAS, ROUTER_PG_PROFILE_V1, ROUTER_PG_PROFILE_V2, checksumJsonPayload, computeRouterCollectedLabelCounts, computeRouterFreshnessChecksum, computeRouterObjectiveChecksum, computeRouterQueryChecksum, computeRouterWeightsChecksum, sortNormalizedEvents, validateTeacherSupervisionArtifact } from "@openclawbrain/contracts";
|
|
4
5
|
import { buildNormalizedEventDedupId, buildNormalizedEventExport, buildNormalizedEventExportBridge, createDefaultLearningSurface, createEventExportCursor, createExplicitEventRange, validateNormalizedEventExport, validateNormalizedEventExportBridge, validateNormalizedEventExportSlice } from "@openclawbrain/event-export";
|
|
5
6
|
import { computePayloadChecksum, loadPack, PACK_LAYOUT, summarizeStructuralGraphEvolution, writePackFile } from "@openclawbrain/pack-format";
|
|
@@ -891,7 +892,8 @@ function buildAlwaysOnLearningMaterializationJob(input, current, selectedSlices,
|
|
|
891
892
|
principalBacklog,
|
|
892
893
|
...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
|
|
893
894
|
...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
|
|
894
|
-
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
|
|
895
|
+
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
|
|
896
|
+
...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
|
|
895
897
|
};
|
|
896
898
|
const candidate = buildCandidatePackFromNormalizedEventExport(candidateInput);
|
|
897
899
|
const selectedSliceIds = selectedSlices.map((slice) => slice.sliceId);
|
|
@@ -4040,6 +4042,201 @@ function createRouterArtifact(packId, builtAt, graph, vectors, eventExport, spar
|
|
|
4040
4042
|
* Join serve-time decisions with feedback events to assign outcome rewards.
|
|
4041
4043
|
* Returns decisionRecordId → outcome (z_T).
|
|
4042
4044
|
*/
|
|
4045
|
+
function clampTeacherObservationOutcome(value) {
|
|
4046
|
+
if (!Number.isFinite(value)) {
|
|
4047
|
+
return 0;
|
|
4048
|
+
}
|
|
4049
|
+
return Math.max(-1, Math.min(1, value));
|
|
4050
|
+
}
|
|
4051
|
+
function normalizeOutcomeMatchText(value) {
|
|
4052
|
+
return typeof value === "string" && value.trim().length > 0
|
|
4053
|
+
? value.replace(/\s+/g, " ").trim().toLowerCase()
|
|
4054
|
+
: null;
|
|
4055
|
+
}
|
|
4056
|
+
function parseObservationRouteMetadata(value) {
|
|
4057
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4058
|
+
return {
|
|
4059
|
+
selectedNodeIds: [],
|
|
4060
|
+
selectedPathNodeIds: []
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
try {
|
|
4064
|
+
const parsed = JSON.parse(value);
|
|
4065
|
+
const selectedNodeIds = Array.isArray(parsed?.selectedNodeIds)
|
|
4066
|
+
? parsed.selectedNodeIds.filter((entry) => typeof entry === "string")
|
|
4067
|
+
: [];
|
|
4068
|
+
const selectedPathNodeIds = Array.isArray(parsed?.selectedPathNodeIds)
|
|
4069
|
+
? parsed.selectedPathNodeIds.filter((entry) => typeof entry === "string")
|
|
4070
|
+
: [];
|
|
4071
|
+
return {
|
|
4072
|
+
selectedNodeIds,
|
|
4073
|
+
selectedPathNodeIds
|
|
4074
|
+
};
|
|
4075
|
+
}
|
|
4076
|
+
catch {
|
|
4077
|
+
return {
|
|
4078
|
+
selectedNodeIds: [],
|
|
4079
|
+
selectedPathNodeIds: []
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
4084
|
+
const resolvedActivationRoot = typeof activationRoot === "string" && activationRoot.trim().length > 0
|
|
4085
|
+
? path.resolve(activationRoot)
|
|
4086
|
+
: null;
|
|
4087
|
+
if (resolvedActivationRoot === null) {
|
|
4088
|
+
return [];
|
|
4089
|
+
}
|
|
4090
|
+
const dbPath = path.join(resolvedActivationRoot, "state.db");
|
|
4091
|
+
if (!existsSync(dbPath)) {
|
|
4092
|
+
return [];
|
|
4093
|
+
}
|
|
4094
|
+
let db = null;
|
|
4095
|
+
try {
|
|
4096
|
+
db = new DatabaseSync(dbPath);
|
|
4097
|
+
const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'brain_observations' LIMIT 1").get();
|
|
4098
|
+
if (table === undefined) {
|
|
4099
|
+
return [];
|
|
4100
|
+
}
|
|
4101
|
+
const rows = db.prepare(`
|
|
4102
|
+
SELECT id, query_text, route_metadata_json, final_score, confidence, created_at, updated_at, evaluated_at
|
|
4103
|
+
FROM brain_observations
|
|
4104
|
+
WHERE final_score IS NOT NULL
|
|
4105
|
+
ORDER BY created_at DESC
|
|
4106
|
+
`).all();
|
|
4107
|
+
return rows
|
|
4108
|
+
.map((row) => {
|
|
4109
|
+
const routeMetadata = parseObservationRouteMetadata(row.route_metadata_json);
|
|
4110
|
+
return {
|
|
4111
|
+
observationId: typeof row.id === "string" ? row.id : null,
|
|
4112
|
+
queryText: typeof row.query_text === "string" ? row.query_text : "",
|
|
4113
|
+
finalScore: clampTeacherObservationOutcome(Number(row.final_score)),
|
|
4114
|
+
confidence: Number.isFinite(Number(row.confidence)) ? Number(row.confidence) : null,
|
|
4115
|
+
createdAt: Number.isFinite(Number(row.created_at)) ? Number(row.created_at) : null,
|
|
4116
|
+
updatedAt: Number.isFinite(Number(row.updated_at)) ? Number(row.updated_at) : null,
|
|
4117
|
+
evaluatedAt: Number.isFinite(Number(row.evaluated_at)) ? Number(row.evaluated_at) : null,
|
|
4118
|
+
selectedNodeIds: routeMetadata.selectedNodeIds,
|
|
4119
|
+
selectedPathNodeIds: routeMetadata.selectedPathNodeIds
|
|
4120
|
+
};
|
|
4121
|
+
})
|
|
4122
|
+
.filter((row) => row.createdAt !== null && row.finalScore !== 0);
|
|
4123
|
+
}
|
|
4124
|
+
catch {
|
|
4125
|
+
return [];
|
|
4126
|
+
}
|
|
4127
|
+
finally {
|
|
4128
|
+
try {
|
|
4129
|
+
db?.close();
|
|
4130
|
+
}
|
|
4131
|
+
catch {
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
function decisionTimestampsForObservationMatch(decision) {
|
|
4136
|
+
const timestamps = [];
|
|
4137
|
+
for (const value of [decision.turnCreatedAt, decision.recordedAt]) {
|
|
4138
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4139
|
+
continue;
|
|
4140
|
+
}
|
|
4141
|
+
const parsed = Date.parse(value);
|
|
4142
|
+
if (Number.isFinite(parsed) && !timestamps.includes(parsed)) {
|
|
4143
|
+
timestamps.push(parsed);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
return timestamps;
|
|
4147
|
+
}
|
|
4148
|
+
function decisionObservationMatchKey(decision, observation) {
|
|
4149
|
+
const normalizedDecisionText = normalizeOutcomeMatchText(decision.userMessage);
|
|
4150
|
+
const normalizedObservationText = normalizeOutcomeMatchText(observation.queryText);
|
|
4151
|
+
const decisionSelected = new Set([
|
|
4152
|
+
...(Array.isArray(decision.selectedBrainContextIds) ? decision.selectedBrainContextIds : []),
|
|
4153
|
+
...(Array.isArray(decision.chosenContextIds) ? decision.chosenContextIds : [])
|
|
4154
|
+
].filter((entry) => typeof entry === "string"));
|
|
4155
|
+
const observationSelected = new Set([
|
|
4156
|
+
...observation.selectedNodeIds,
|
|
4157
|
+
...observation.selectedPathNodeIds
|
|
4158
|
+
]);
|
|
4159
|
+
let overlapCount = 0;
|
|
4160
|
+
for (const blockId of observationSelected) {
|
|
4161
|
+
if (decisionSelected.has(blockId)) {
|
|
4162
|
+
overlapCount += 1;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
const timestamps = decisionTimestampsForObservationMatch(decision);
|
|
4166
|
+
const observationCreatedAt = observation.createdAt;
|
|
4167
|
+
const bestDelta = observationCreatedAt === null || timestamps.length === 0
|
|
4168
|
+
? Number.POSITIVE_INFINITY
|
|
4169
|
+
: Math.min(...timestamps.map((timestamp) => Math.abs(timestamp - observationCreatedAt)));
|
|
4170
|
+
const sameQuery = normalizedDecisionText !== null && normalizedDecisionText === normalizedObservationText;
|
|
4171
|
+
if (!sameQuery && overlapCount === 0) {
|
|
4172
|
+
return null;
|
|
4173
|
+
}
|
|
4174
|
+
if (!Number.isFinite(bestDelta) || bestDelta > 300_000) {
|
|
4175
|
+
return null;
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
sameQuery,
|
|
4179
|
+
overlapCount,
|
|
4180
|
+
deltaMs: bestDelta,
|
|
4181
|
+
confidence: observation.confidence ?? 0
|
|
4182
|
+
};
|
|
4183
|
+
}
|
|
4184
|
+
function compareDecisionObservationMatch(left, right) {
|
|
4185
|
+
if (left.sameQuery !== right.sameQuery) {
|
|
4186
|
+
return left.sameQuery ? -1 : 1;
|
|
4187
|
+
}
|
|
4188
|
+
if (left.overlapCount !== right.overlapCount) {
|
|
4189
|
+
return right.overlapCount - left.overlapCount;
|
|
4190
|
+
}
|
|
4191
|
+
if (left.deltaMs !== right.deltaMs) {
|
|
4192
|
+
return left.deltaMs - right.deltaMs;
|
|
4193
|
+
}
|
|
4194
|
+
if (left.confidence !== right.confidence) {
|
|
4195
|
+
return right.confidence - left.confidence;
|
|
4196
|
+
}
|
|
4197
|
+
return 0;
|
|
4198
|
+
}
|
|
4199
|
+
function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutcomes) {
|
|
4200
|
+
const outcomes = new Map();
|
|
4201
|
+
for (const decision of decisions) {
|
|
4202
|
+
outcomes.set(decision.recordId, 0);
|
|
4203
|
+
}
|
|
4204
|
+
if (!Array.isArray(observationOutcomes) || observationOutcomes.length === 0) {
|
|
4205
|
+
return outcomes;
|
|
4206
|
+
}
|
|
4207
|
+
for (const observation of observationOutcomes) {
|
|
4208
|
+
const matches = decisions
|
|
4209
|
+
.map((decision) => {
|
|
4210
|
+
const key = decisionObservationMatchKey(decision, observation);
|
|
4211
|
+
return key === null
|
|
4212
|
+
? null
|
|
4213
|
+
: {
|
|
4214
|
+
decision,
|
|
4215
|
+
key
|
|
4216
|
+
};
|
|
4217
|
+
})
|
|
4218
|
+
.filter((entry) => entry !== null)
|
|
4219
|
+
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4220
|
+
const best = matches[0] ?? null;
|
|
4221
|
+
const runnerUp = matches[1] ?? null;
|
|
4222
|
+
if (best === null) {
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
if (runnerUp !== null && compareDecisionObservationMatch(best.key, runnerUp.key) === 0) {
|
|
4226
|
+
continue;
|
|
4227
|
+
}
|
|
4228
|
+
const reward = clampTeacherObservationOutcome(observation.finalScore);
|
|
4229
|
+
if (reward === 0) {
|
|
4230
|
+
continue;
|
|
4231
|
+
}
|
|
4232
|
+
const current = outcomes.get(best.decision.recordId) ?? 0;
|
|
4233
|
+
if (current === 0 || Math.abs(reward) > Math.abs(current)) {
|
|
4234
|
+
outcomes.set(best.decision.recordId, reward);
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
return outcomes;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4043
4240
|
export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 300_000) {
|
|
4044
4241
|
const outcomes = new Map();
|
|
4045
4242
|
for (const decision of decisions) {
|
|
@@ -4421,15 +4618,21 @@ function computeTrajectoryPolicyGradientV2(trajectory, adjacency, graph, vectors
|
|
|
4421
4618
|
* 6. Aggregates into RouterPolicyUpdateV1[] format
|
|
4422
4619
|
* 7. Builds the router artifact with ROUTER_PG_PROFILE_V2
|
|
4423
4620
|
*/
|
|
4424
|
-
function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog) {
|
|
4621
|
+
function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, serveTimeDecisions, baselineState, sparseFeedback, principalBacklog, teacherObservationOutcomes = []) {
|
|
4425
4622
|
const tau = DEFAULT_TAU;
|
|
4426
4623
|
const pgScale = 8;
|
|
4427
4624
|
// 1. Build adjacency map from graph
|
|
4428
4625
|
const adjacency = buildAdjacencyMap(graph);
|
|
4429
4626
|
const resolveBlockId = buildGraphBlockIdResolver(graph);
|
|
4430
4627
|
const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
|
|
4431
|
-
// 2. Join serve-time decisions with feedback
|
|
4628
|
+
// 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
|
|
4432
4629
|
const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
|
|
4630
|
+
const teacherObservationOutcomeMap = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
|
|
4631
|
+
for (const [decisionId, reward] of teacherObservationOutcomeMap.entries()) {
|
|
4632
|
+
if (reward !== 0) {
|
|
4633
|
+
outcomeMap.set(decisionId, reward);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4433
4636
|
// 3 & 4. Reconstruct trajectories and update baseline
|
|
4434
4637
|
let currentBaseline = { ...baselineState };
|
|
4435
4638
|
const trajectories = [];
|
|
@@ -4719,9 +4922,12 @@ export function buildCandidatePack(input) {
|
|
|
4719
4922
|
const vectors = createVectorsPayload(graph);
|
|
4720
4923
|
let router = null;
|
|
4721
4924
|
let updatedBaseline = null;
|
|
4925
|
+
const teacherObservationOutcomes = input.activationRoot === undefined
|
|
4926
|
+
? []
|
|
4927
|
+
: loadTeacherObservationOutcomesFromActivation(input.activationRoot);
|
|
4722
4928
|
if (input.learnedRouting) {
|
|
4723
4929
|
if (useV2) {
|
|
4724
|
-
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog);
|
|
4930
|
+
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
|
|
4725
4931
|
router = v2Result.artifact;
|
|
4726
4932
|
updatedBaseline = v2Result.updatedBaseline;
|
|
4727
4933
|
}
|
|
@@ -4883,7 +5089,8 @@ export function buildCandidatePackFromNormalizedEventExport(input) {
|
|
|
4883
5089
|
...(input.principalBacklog !== undefined ? { principalBacklog: input.principalBacklog } : {}),
|
|
4884
5090
|
...(input.pgVersion !== undefined ? { pgVersion: input.pgVersion } : {}),
|
|
4885
5091
|
...(input.serveTimeDecisions !== undefined ? { serveTimeDecisions: [...input.serveTimeDecisions] } : {}),
|
|
4886
|
-
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {})
|
|
5092
|
+
...(input.baselineState !== undefined ? { baselineState: { ...input.baselineState } } : {}),
|
|
5093
|
+
...(input.activationRoot !== undefined ? { activationRoot: input.activationRoot } : {})
|
|
4887
5094
|
};
|
|
4888
5095
|
return buildCandidatePack(candidateInput);
|
|
4889
5096
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function formatRawLearningPathSummary(learningPath) {
|
|
2
|
+
return `source=${learningPath.source} pg=${learningPath.policyGradientVersion} method=${learningPath.policyGradientMethod ?? "none"} target=${learningPath.targetConstruction ?? "none"} connect=${learningPath.connectOpsFired ?? "none"} trajectories=${learningPath.reconstructedTrajectoryCount ?? "none"}`;
|
|
3
|
+
}
|
|
4
|
+
function isSeedAwaitingFirstPromotion(status) {
|
|
5
|
+
return status?.brain?.state === "seed_state_authoritative" && status?.brainStatus?.awaitingFirstExport === true;
|
|
6
|
+
}
|
|
7
|
+
function normalizeOptionalString(value) {
|
|
8
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
9
|
+
}
|
|
10
|
+
export function formatOperatorLearningPathSummary({ status, learningPath, tracedLearning }) {
|
|
11
|
+
if (!isSeedAwaitingFirstPromotion(status)) {
|
|
12
|
+
return formatRawLearningPathSummary(learningPath);
|
|
13
|
+
}
|
|
14
|
+
const detailParts = [
|
|
15
|
+
"detail=seed_state_awaiting_first_promotion",
|
|
16
|
+
`tracedPg=${normalizeOptionalString(tracedLearning?.pgVersionUsed) ?? "none"}`,
|
|
17
|
+
`tracedPack=${normalizeOptionalString(tracedLearning?.materializedPackId) ?? "none"}`
|
|
18
|
+
];
|
|
19
|
+
return [
|
|
20
|
+
"source=seed_state",
|
|
21
|
+
"pg=seed",
|
|
22
|
+
"method=not_yet_promoted",
|
|
23
|
+
"target=not_yet_promoted",
|
|
24
|
+
"connect=none",
|
|
25
|
+
"trajectories=none",
|
|
26
|
+
...detailParts
|
|
27
|
+
].join(" ");
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclawbrain/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
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",
|
|
@@ -62,4 +62,3 @@
|
|
|
62
62
|
"test": "node --test dist/test/*.test.js"
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|