superlab 0.1.42 → 0.1.43

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.
@@ -21,10 +21,12 @@ const {
21
21
  verifyStageContract,
22
22
  } = require("./auto_contracts.cjs");
23
23
  const {
24
+ parseAutoLedger,
24
25
  parseAutoMode,
25
26
  parseAutoStatus,
26
27
  readWorkflowLanguage,
27
28
  resolveRequiredArtifact,
29
+ writeAutoLedger,
28
30
  writeAutoOutcome,
29
31
  writeAutoStatus,
30
32
  } = require("./auto_state.cjs");
@@ -33,6 +35,10 @@ function normalizeTransition(value) {
33
35
  return (value || "").trim();
34
36
  }
35
37
 
38
+ function normalizeObservedState(value) {
39
+ return (value || "").trim().toLowerCase();
40
+ }
41
+
36
42
  function isSuccessTransition(value) {
37
43
  return ["success", "terminal-success", "campaign-success"].includes((value || "").trim().toLowerCase());
38
44
  }
@@ -41,6 +47,101 @@ function isStopTransition(value) {
41
47
  return ["stop", "campaign-stop", "terminal-stop"].includes((value || "").trim().toLowerCase());
42
48
  }
43
49
 
50
+ function isLocalProcessAlive(ownerId) {
51
+ const pid = parseInteger(ownerId, null);
52
+ if (!Number.isInteger(pid) || pid <= 0) {
53
+ return false;
54
+ }
55
+ try {
56
+ process.kill(pid, 0);
57
+ return true;
58
+ } catch (error) {
59
+ if (error && error.code === "EPERM") {
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+ }
65
+
66
+ function resolveResumePlan({ mode, evalProtocol, status, ledger, now }) {
67
+ const hasLedgerState = [
68
+ ledger.campaignId,
69
+ ledger.observedState,
70
+ ledger.activeRung,
71
+ ledger.nextTransition,
72
+ ledger.ownerId,
73
+ ].some((value) => isMeaningful(value));
74
+ if (!hasLedgerState) {
75
+ return { blockingIssue: "", resumePlan: null };
76
+ }
77
+
78
+ if ((ledger.ownerType || "").trim().toLowerCase() === "local-process" && isLocalProcessAlive(ledger.ownerId)) {
79
+ return {
80
+ blockingIssue: `auto campaign already has a live local owner: ${ledger.ownerId}`,
81
+ resumePlan: null,
82
+ };
83
+ }
84
+
85
+ const startedAt = isMeaningful(status.startedAt) ? status.startedAt : now.toISOString();
86
+ const campaignId = isMeaningful(ledger.campaignId)
87
+ ? ledger.campaignId
88
+ : `auto-${startedAt.replace(/[:.]/g, "-")}`;
89
+ const iterationCount = parseInteger(status.iterationCount, 0);
90
+ const observedState = normalizeObservedState(ledger.observedState);
91
+
92
+ if (evalProtocol.experimentRungs.length > 0) {
93
+ const rungMap = new Map(evalProtocol.experimentRungs.map((rung) => [rung.id, rung]));
94
+ const nextTransition = normalizeTransition(ledger.nextTransition || status.nextRung);
95
+ if (
96
+ isMeaningful(nextTransition) &&
97
+ !isSuccessTransition(nextTransition) &&
98
+ !isStopTransition(nextTransition) &&
99
+ rungMap.has(nextTransition)
100
+ ) {
101
+ const rung = rungMap.get(nextTransition);
102
+ return {
103
+ blockingIssue: "",
104
+ resumePlan: {
105
+ kind: "ladder",
106
+ rungId: nextTransition,
107
+ stage: rung.stage,
108
+ watchTarget: rung.watch,
109
+ campaignId,
110
+ startedAt,
111
+ iterationsCompleted: iterationCount,
112
+ lastCheckpoint: ledger.lastCheckpoint || status.lastCheckpoint || "",
113
+ reason: `resuming at next rung ${nextTransition}`,
114
+ },
115
+ };
116
+ }
117
+
118
+ const activeRung = normalizeTransition(ledger.activeRung || status.currentRung);
119
+ if (
120
+ ["running", "retrying"].includes(observedState) &&
121
+ isMeaningful(activeRung) &&
122
+ rungMap.has(activeRung)
123
+ ) {
124
+ const rung = rungMap.get(activeRung);
125
+ return {
126
+ blockingIssue: "",
127
+ resumePlan: {
128
+ kind: "ladder",
129
+ rungId: activeRung,
130
+ stage: rung.stage,
131
+ watchTarget: rung.watch,
132
+ campaignId,
133
+ startedAt,
134
+ iterationsCompleted: Math.max(0, iterationCount - 1),
135
+ lastCheckpoint: ledger.lastCheckpoint || status.lastCheckpoint || "",
136
+ reason: `restarting active rung ${activeRung} after owner exit`,
137
+ },
138
+ };
139
+ }
140
+ }
141
+
142
+ return { blockingIssue: "", resumePlan: null };
143
+ }
144
+
44
145
  async function runCommandWithPolling({
45
146
  targetDir,
46
147
  stage,
@@ -53,6 +154,8 @@ async function runCommandWithPolling({
53
154
  rungId = "",
54
155
  watchTarget = "",
55
156
  nextRung = "",
157
+ ownerInfo = null,
158
+ updateLedger = null,
56
159
  }) {
57
160
  const child = spawn(command, {
58
161
  cwd: targetDir,
@@ -106,6 +209,20 @@ async function runCommandWithPolling({
106
209
  },
107
210
  { lang }
108
211
  );
212
+ if (typeof updateLedger === "function") {
213
+ updateLedger({
214
+ ownerType: ownerInfo?.ownerType || "local-process",
215
+ ownerId: String(child.pid || ownerInfo?.ownerId || ""),
216
+ command,
217
+ watchTarget,
218
+ activeStage: stage,
219
+ activeRung: rungId,
220
+ startedAt,
221
+ lastObservedAt: new Date().toISOString(),
222
+ observedState: "running",
223
+ nextTransition: nextRung || "",
224
+ });
225
+ }
109
226
  await sleep(pollIntervalMs);
110
227
  }
111
228
 
@@ -217,6 +334,8 @@ async function evaluateTerminalGoal({ mode, iteration, targetDir, deadlineMs })
217
334
 
218
335
  async function startAutoMode({ targetDir, now = new Date() }) {
219
336
  const mode = parseAutoMode(targetDir);
337
+ const existingStatus = parseAutoStatus(targetDir);
338
+ const existingLedger = parseAutoLedger(targetDir);
220
339
  const evalProtocol = parseEvalProtocol(targetDir);
221
340
  const issues = validateAutoMode(mode, null, evalProtocol);
222
341
  if (issues.length > 0) {
@@ -229,20 +348,30 @@ async function startAutoMode({ targetDir, now = new Date() }) {
229
348
  if (mode.approvalStatus !== "approved") {
230
349
  throw new Error(`approval status must be approved before auto mode can start (current: ${mode.approvalStatus || "missing"})`);
231
350
  }
351
+ const { blockingIssue, resumePlan } = resolveResumePlan({
352
+ mode,
353
+ evalProtocol,
354
+ status: existingStatus,
355
+ ledger: existingLedger,
356
+ now,
357
+ });
358
+ if (blockingIssue) {
359
+ throw new Error(blockingIssue);
360
+ }
232
361
 
233
362
  const lang = readWorkflowLanguage(targetDir);
234
363
  const timestamp = now.toISOString();
235
364
  const status = {
236
365
  status: "running",
237
- currentStage: mode.allowedStages[0] || "run",
366
+ currentStage: resumePlan?.stage || mode.allowedStages[0] || "run",
238
367
  currentCommand: "",
239
368
  activeRunId: "",
240
- iterationCount: "0",
241
- startedAt: timestamp,
369
+ iterationCount: String(resumePlan?.iterationsCompleted || 0),
370
+ startedAt: resumePlan?.startedAt || timestamp,
242
371
  lastHeartbeat: timestamp,
243
- lastCheckpoint: "",
244
- lastSummary: "",
245
- decision: "armed for bounded auto orchestration",
372
+ lastCheckpoint: resumePlan?.lastCheckpoint || "",
373
+ lastSummary: resumePlan?.reason || "",
374
+ decision: resumePlan?.reason || "armed for bounded auto orchestration",
246
375
  };
247
376
  writeAutoStatus(targetDir, status, { lang });
248
377
 
@@ -257,13 +386,36 @@ async function startAutoMode({ targetDir, now = new Date() }) {
257
386
  const { loopStages, finalStages } = splitAutoStages(mode.allowedStages);
258
387
  const executedStages = [];
259
388
  let failureCount = 0;
260
- let iterationsCompleted = 0;
389
+ let iterationsCompleted = resumePlan?.iterationsCompleted || 0;
261
390
  let currentStatus = { ...status };
262
391
  let successReached = false;
263
392
  let stopMatched = false;
264
393
  let promotionApplied = false;
265
394
  let stopReason = "";
266
395
  let finalRung = "";
396
+ const campaignId = resumePlan?.campaignId || `auto-${startedAt.replace(/[:.]/g, "-")}`;
397
+ let currentLedger = {
398
+ campaignId,
399
+ objective: mode.objective,
400
+ activeStage: status.currentStage,
401
+ activeRung: resumePlan?.rungId || "",
402
+ ownerType: "",
403
+ ownerId: "",
404
+ command: "",
405
+ watchTarget: resumePlan?.watchTarget || "",
406
+ startedAt,
407
+ lastObservedAt: timestamp,
408
+ observedState: resumePlan ? "resuming" : "armed",
409
+ lastCheckpoint: resumePlan?.lastCheckpoint || "",
410
+ checkpointSummary: resumePlan?.reason || "auto loop armed and waiting for the first owned command",
411
+ nextTransition: resumePlan?.rungId || "",
412
+ continueBoundary: "Continue while the active owner is still running and no stop condition has matched.",
413
+ stopBoundary: mode.stopConditions,
414
+ escalationBoundary: mode.escalationConditions,
415
+ requiredReadSet: ".lab/context/eval-protocol.md, .lab/context/auto-mode.md, .lab/context/auto-status.md, .lab/context/auto-ledger.md, .lab/context/auto-outcome.md",
416
+ resumeCommand: "",
417
+ };
418
+ writeAutoLedger(targetDir, currentLedger, { lang });
267
419
  const outcomeProtocolFields = {
268
420
  primaryMetrics: evalProtocol.primaryMetrics,
269
421
  secondaryMetrics: evalProtocol.secondaryMetrics,
@@ -305,6 +457,22 @@ async function startAutoMode({ targetDir, now = new Date() }) {
305
457
  writeAutoStatus(targetDir, currentStatus, { lang });
306
458
  };
307
459
 
460
+ const writeLedger = (overrides = {}) => {
461
+ currentLedger = {
462
+ ...currentLedger,
463
+ activeStage: currentStatus.currentStage || currentLedger.activeStage,
464
+ activeRung: currentStatus.currentRung || currentLedger.activeRung,
465
+ watchTarget: currentStatus.watchTarget || currentLedger.watchTarget,
466
+ lastCheckpoint: currentStatus.lastCheckpoint || currentLedger.lastCheckpoint,
467
+ checkpointSummary: currentStatus.lastSummary || currentLedger.checkpointSummary,
468
+ lastObservedAt: new Date().toISOString(),
469
+ stopBoundary: mode.stopConditions,
470
+ escalationBoundary: mode.escalationConditions,
471
+ ...overrides,
472
+ };
473
+ writeAutoLedger(targetDir, currentLedger, { lang });
474
+ };
475
+
308
476
  const failAutoMode = (message) => {
309
477
  currentStatus = {
310
478
  ...currentStatus,
@@ -313,6 +481,13 @@ async function startAutoMode({ targetDir, now = new Date() }) {
313
481
  decision: message,
314
482
  };
315
483
  writeAutoStatus(targetDir, currentStatus, { lang });
484
+ writeLedger({
485
+ observedState: "failed",
486
+ ownerType: currentLedger.ownerType || "local-process",
487
+ checkpointSummary: message,
488
+ nextTransition: "terminal-failure",
489
+ resumeCommand: "",
490
+ });
316
491
  writeAutoOutcome(
317
492
  targetDir,
318
493
  {
@@ -366,6 +541,8 @@ async function startAutoMode({ targetDir, now = new Date() }) {
366
541
  rungId,
367
542
  watchTarget,
368
543
  nextRung,
544
+ ownerInfo: { ownerType: "local-process" },
545
+ updateLedger: writeLedger,
369
546
  });
370
547
  verifyStageContract({ stage, snapshot: contract.snapshot });
371
548
  executedStages.push(stage);
@@ -378,6 +555,18 @@ async function startAutoMode({ targetDir, now = new Date() }) {
378
555
  nextRung,
379
556
  decision: rungId ? `completed rung ${rungId}` : `completed stage ${stage}`,
380
557
  });
558
+ writeLedger({
559
+ ownerType: "local-process",
560
+ observedState: "checkpointed",
561
+ command,
562
+ watchTarget,
563
+ activeStage: stage,
564
+ activeRung: rungId || currentStatus.currentRung,
565
+ ownerId: currentLedger.ownerId,
566
+ checkpointSummary: rungId ? `completed rung ${rungId}` : `completed stage ${stage}`,
567
+ nextTransition: nextRung || "",
568
+ resumeCommand: command,
569
+ });
381
570
  const frozenCoreChanges = detectFrozenCoreChanges(frozenCoreSnapshot);
382
571
  if (frozenCoreChanges.length > 0) {
383
572
  failAutoMode(`frozen core changed: ${frozenCoreChanges.join(", ")}`);
@@ -412,6 +601,17 @@ async function startAutoMode({ targetDir, now = new Date() }) {
412
601
  nextRung,
413
602
  decision: `retrying ${rungId || stage} after failure ${failureCount}`,
414
603
  });
604
+ writeLedger({
605
+ ownerType: "local-process",
606
+ observedState: "retrying",
607
+ command,
608
+ watchTarget,
609
+ activeStage: stage,
610
+ activeRung: rungId || currentStatus.currentRung,
611
+ checkpointSummary: `retrying ${rungId || stage} after failure ${failureCount}`,
612
+ nextTransition: rungId || stage,
613
+ resumeCommand: command,
614
+ });
415
615
  }
416
616
  }
417
617
  };
@@ -451,6 +651,14 @@ async function startAutoMode({ targetDir, now = new Date() }) {
451
651
  currentCommand: mode.promotionCommand,
452
652
  decision: `promotion policy matched after ${label}`,
453
653
  });
654
+ writeLedger({
655
+ ownerType: "local-process",
656
+ command: mode.promotionCommand,
657
+ observedState: "checkpointed",
658
+ checkpointSummary: `promotion policy matched after ${label}`,
659
+ nextTransition: "post-promotion refresh",
660
+ resumeCommand: mode.promotionCommand,
661
+ });
454
662
  promotionApplied = true;
455
663
  const frozenCoreChangesAfterPromotion = detectFrozenCoreChanges(frozenCoreSnapshot);
456
664
  if (frozenCoreChangesAfterPromotion.length > 0) {
@@ -463,7 +671,12 @@ async function startAutoMode({ targetDir, now = new Date() }) {
463
671
 
464
672
  if (evalProtocol.experimentRungs.length > 0) {
465
673
  const rungMap = new Map(evalProtocol.experimentRungs.map((rung) => [rung.id, rung]));
466
- let currentRung = evalProtocol.experimentRungs[0];
674
+ let currentRung = resumePlan?.kind === "ladder"
675
+ ? rungMap.get(resumePlan.rungId)
676
+ : evalProtocol.experimentRungs[0];
677
+ if (!currentRung) {
678
+ failAutoMode(`resume rung is missing from the current experiment ladder: ${resumePlan?.rungId || ""}`);
679
+ }
467
680
 
468
681
  while (currentRung && iterationsCompleted < Math.max(1, maxIterations)) {
469
682
  if (!mode.allowedStages.includes(currentRung.stage)) {
@@ -618,6 +831,12 @@ async function startAutoMode({ targetDir, now = new Date() }) {
618
831
  decision: stopReason || "stopped by stop condition",
619
832
  };
620
833
  writeAutoStatus(targetDir, currentStatus, { lang });
834
+ writeLedger({
835
+ observedState: "stopped",
836
+ checkpointSummary: stopReason || "stopped by stop condition",
837
+ nextTransition: "terminal-stop",
838
+ resumeCommand: "",
839
+ });
621
840
  writeAutoOutcome(
622
841
  targetDir,
623
842
  {
@@ -670,6 +889,12 @@ async function startAutoMode({ targetDir, now = new Date() }) {
670
889
  decision: stopReason || "stopped by stop condition",
671
890
  };
672
891
  writeAutoStatus(targetDir, currentStatus, { lang });
892
+ writeLedger({
893
+ observedState: "stopped",
894
+ checkpointSummary: stopReason || "stopped by stop condition",
895
+ nextTransition: "terminal-stop",
896
+ resumeCommand: "",
897
+ });
673
898
  writeAutoOutcome(
674
899
  targetDir,
675
900
  {
@@ -724,6 +949,12 @@ async function startAutoMode({ targetDir, now = new Date() }) {
724
949
  decision: successReached ? "completed configured auto goal" : "completed configured stages",
725
950
  };
726
951
  writeAutoStatus(targetDir, currentStatus, { lang });
952
+ writeLedger({
953
+ observedState: "completed",
954
+ checkpointSummary: successReached ? "completed configured auto goal" : "completed configured stages",
955
+ nextTransition: "terminal-success",
956
+ resumeCommand: "",
957
+ });
727
958
  writeAutoOutcome(
728
959
  targetDir,
729
960
  {
@@ -805,6 +1036,31 @@ function stopAutoMode({ targetDir, now = new Date() }) {
805
1036
  decision: "stopped by operator",
806
1037
  };
807
1038
  writeAutoStatus(targetDir, status, { lang });
1039
+ writeAutoLedger(
1040
+ targetDir,
1041
+ {
1042
+ campaignId: existing.startedAt ? `auto-${existing.startedAt.replace(/[:.]/g, "-")}` : `auto-${now.toISOString().replace(/[:.]/g, "-")}`,
1043
+ objective: mode.objective,
1044
+ activeStage: existing.currentStage || "",
1045
+ activeRung: existing.currentRung || "",
1046
+ ownerType: "local-process",
1047
+ ownerId: "",
1048
+ command: existing.currentCommand || "",
1049
+ watchTarget: existing.watchTarget || "",
1050
+ startedAt: existing.startedAt || now.toISOString(),
1051
+ lastObservedAt: now.toISOString(),
1052
+ observedState: "stopped",
1053
+ lastCheckpoint: existing.lastCheckpoint || "",
1054
+ checkpointSummary: "stopped by operator",
1055
+ nextTransition: "terminal-stop",
1056
+ continueBoundary: "No further automatic progress is allowed until a new approved auto run starts.",
1057
+ stopBoundary: mode.stopConditions,
1058
+ escalationBoundary: mode.escalationConditions,
1059
+ requiredReadSet: ".lab/context/eval-protocol.md, .lab/context/auto-mode.md, .lab/context/auto-status.md, .lab/context/auto-ledger.md, .lab/context/auto-outcome.md",
1060
+ resumeCommand: "",
1061
+ },
1062
+ { lang }
1063
+ );
808
1064
  writeAutoOutcome(
809
1065
  targetDir,
810
1066
  {
@@ -68,6 +68,33 @@ function parseAutoStatus(targetDir) {
68
68
  };
69
69
  }
70
70
 
71
+ function parseAutoLedger(targetDir) {
72
+ const text = readFileIfExists(contextFile(targetDir, "auto-ledger.md"));
73
+ return {
74
+ path: contextFile(targetDir, "auto-ledger.md"),
75
+ text,
76
+ campaignId: extractValue(text, ["Campaign id", "Campaign ID", "活动 id"]),
77
+ objective: extractValue(text, ["Objective", "目标"]),
78
+ activeStage: extractValue(text, ["Active stage", "当前阶段"]),
79
+ activeRung: extractValue(text, ["Active rung", "当前 rung"]),
80
+ ownerType: extractValue(text, ["Owner type", "Owner 类型"]),
81
+ ownerId: extractValue(text, ["Owner id", "Owner ID"]),
82
+ command: extractValue(text, ["Command", "命令"]),
83
+ watchTarget: extractValue(text, ["Watch target", "监视目标"]),
84
+ startedAt: extractValue(text, ["Started at", "开始时间"]),
85
+ lastObservedAt: extractValue(text, ["Last observed at", "最近观察时间"]),
86
+ observedState: extractValue(text, ["Observed state", "观察状态"]),
87
+ lastCheckpoint: extractValue(text, ["Last checkpoint", "最近 checkpoint"]),
88
+ checkpointSummary: extractValue(text, ["Checkpoint summary", "Checkpoint 摘要"]),
89
+ nextTransition: extractValue(text, ["Next transition", "下一转换"]),
90
+ continueBoundary: extractValue(text, ["Continue boundary", "继续边界"]),
91
+ stopBoundary: extractValue(text, ["Stop boundary", "停止边界"]),
92
+ escalationBoundary: extractValue(text, ["Escalation boundary", "升级边界"]),
93
+ requiredReadSet: extractValue(text, ["Required read set", "必要读取集合"]),
94
+ resumeCommand: extractValue(text, ["Resume command", "恢复命令"]),
95
+ };
96
+ }
97
+
71
98
  function renderAutoStatus(status, { lang = "en" } = {}) {
72
99
  if (lang === "zh") {
73
100
  return `# 自动模式状态
@@ -240,12 +267,96 @@ function renderAutoOutcome(outcome, { lang = "en" } = {}) {
240
267
  `;
241
268
  }
242
269
 
270
+ function renderAutoLedger(ledger, { lang = "en" } = {}) {
271
+ if (lang === "zh") {
272
+ return `# 自动运行账本
273
+
274
+ ## Campaign
275
+
276
+ - Campaign id: ${ledger.campaignId || ""}
277
+ - Objective: ${ledger.objective || ""}
278
+ - Active stage: ${ledger.activeStage || ""}
279
+ - Active rung: ${ledger.activeRung || ""}
280
+
281
+ ## Owner
282
+
283
+ - Owner type: ${ledger.ownerType || ""}
284
+ - Owner id: ${ledger.ownerId || ""}
285
+ - Command: ${ledger.command || ""}
286
+ - Watch target: ${ledger.watchTarget || ""}
287
+ - Started at: ${ledger.startedAt || ""}
288
+ - Last observed at: ${ledger.lastObservedAt || ""}
289
+ - Observed state: ${ledger.observedState || ""}
290
+
291
+ ## Checkpoints
292
+
293
+ - Last checkpoint: ${ledger.lastCheckpoint || ""}
294
+ - Checkpoint summary: ${ledger.checkpointSummary || ""}
295
+ - Next transition: ${ledger.nextTransition || ""}
296
+
297
+ ## Boundaries
298
+
299
+ - Continue boundary: ${ledger.continueBoundary || ""}
300
+ - Stop boundary: ${ledger.stopBoundary || ""}
301
+ - Escalation boundary: ${ledger.escalationBoundary || ""}
302
+
303
+ ## Resume
304
+
305
+ - Required read set: ${ledger.requiredReadSet || ""}
306
+ - Resume command: ${ledger.resumeCommand || ""}
307
+ `;
308
+ }
309
+
310
+ return `# Auto Runtime Ledger
311
+
312
+ ## Campaign
313
+
314
+ - Campaign id: ${ledger.campaignId || ""}
315
+ - Objective: ${ledger.objective || ""}
316
+ - Active stage: ${ledger.activeStage || ""}
317
+ - Active rung: ${ledger.activeRung || ""}
318
+
319
+ ## Owner
320
+
321
+ - Owner type: ${ledger.ownerType || ""}
322
+ - Owner id: ${ledger.ownerId || ""}
323
+ - Command: ${ledger.command || ""}
324
+ - Watch target: ${ledger.watchTarget || ""}
325
+ - Started at: ${ledger.startedAt || ""}
326
+ - Last observed at: ${ledger.lastObservedAt || ""}
327
+ - Observed state: ${ledger.observedState || ""}
328
+
329
+ ## Checkpoints
330
+
331
+ - Last checkpoint: ${ledger.lastCheckpoint || ""}
332
+ - Checkpoint summary: ${ledger.checkpointSummary || ""}
333
+ - Next transition: ${ledger.nextTransition || ""}
334
+
335
+ ## Boundaries
336
+
337
+ - Continue boundary: ${ledger.continueBoundary || ""}
338
+ - Stop boundary: ${ledger.stopBoundary || ""}
339
+ - Escalation boundary: ${ledger.escalationBoundary || ""}
340
+
341
+ ## Resume
342
+
343
+ - Required read set: ${ledger.requiredReadSet || ""}
344
+ - Resume command: ${ledger.resumeCommand || ""}
345
+ `;
346
+ }
347
+
243
348
  function writeAutoOutcome(targetDir, outcome, { lang = "en" } = {}) {
244
349
  const filePath = contextFile(targetDir, "auto-outcome.md");
245
350
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
246
351
  fs.writeFileSync(filePath, renderAutoOutcome(outcome, { lang }).trimEnd() + "\n");
247
352
  }
248
353
 
354
+ function writeAutoLedger(targetDir, ledger, { lang = "en" } = {}) {
355
+ const filePath = contextFile(targetDir, "auto-ledger.md");
356
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
357
+ fs.writeFileSync(filePath, renderAutoLedger(ledger, { lang }).trimEnd() + "\n");
358
+ }
359
+
249
360
  function resolveRequiredArtifact(targetDir, configuredPath) {
250
361
  if (!isMeaningful(configuredPath)) {
251
362
  return { relativePath: "", absolutePath: "" };
@@ -258,12 +369,15 @@ function resolveRequiredArtifact(targetDir, configuredPath) {
258
369
  }
259
370
 
260
371
  module.exports = {
372
+ parseAutoLedger,
261
373
  parseAutoMode,
262
374
  parseAutoStatus,
263
375
  readWorkflowLanguage,
376
+ renderAutoLedger,
264
377
  renderAutoOutcome,
265
378
  renderAutoStatus,
266
379
  resolveRequiredArtifact,
380
+ writeAutoLedger,
267
381
  writeAutoOutcome,
268
382
  writeAutoStatus,
269
383
  };
package/lib/i18n.cjs CHANGED
@@ -1563,6 +1563,7 @@ const ZH_SKILL_FILES = {
1563
1563
  `# 自动模式契约
1564
1564
 
1565
1565
  用这个文件定义 \`/lab:auto\` 的有边界自治执行范围。
1566
+ 把 \`.lab/context/auto-ledger.md\` 当成运行时账本,记录 owner、checkpoint、resume 和 stop 边界。
1566
1567
 
1567
1568
  ## 目标
1568
1569
 
@@ -1602,6 +1603,7 @@ const ZH_SKILL_FILES = {
1602
1603
  - Rung 的 \`Command\` 应该绑定真实的长任务命令,由它产出最终实验结果。
1603
1604
  - 短 watcher 只用于查看进度;当真实实验还在运行时,不要把短 watcher 当成 stage 或 rung 的主命令。
1604
1605
  - 当真实实验进程还活着时,只记录进度更新并继续等待。
1606
+ - 当 loop 处于运行态时,把当前 owner、命令和 watch target 写进 \`.lab/context/auto-ledger.md\`。
1605
1607
  - Run command:
1606
1608
  - Iterate command:
1607
1609
  - Review command:
@@ -1634,6 +1636,43 @@ const ZH_SKILL_FILES = {
1634
1636
  - Stop conditions:
1635
1637
  - Escalation conditions:
1636
1638
  - Canonical promotion writeback: update \`.lab/context/data-decisions.md\`、\`.lab/context/decisions.md\` 和 \`.lab/context/workflow-state.md\`,然后刷新 \`state.md\` 等派生视图。
1639
+ `,
1640
+ [path.join(".lab", "context", "auto-ledger.md")]:
1641
+ `# 自动运行账本
1642
+
1643
+ ## Campaign
1644
+
1645
+ - Campaign id:
1646
+ - Objective:
1647
+ - Active stage:
1648
+ - Active rung:
1649
+
1650
+ ## Owner
1651
+
1652
+ - Owner type:
1653
+ - Owner id:
1654
+ - Command:
1655
+ - Watch target:
1656
+ - Started at:
1657
+ - Last observed at:
1658
+ - Observed state:
1659
+
1660
+ ## Checkpoints
1661
+
1662
+ - Last checkpoint:
1663
+ - Checkpoint summary:
1664
+ - Next transition:
1665
+
1666
+ ## Boundaries
1667
+
1668
+ - Continue boundary:
1669
+ - Stop boundary:
1670
+ - Escalation boundary:
1671
+
1672
+ ## Resume
1673
+
1674
+ - Required read set:
1675
+ - Resume command:
1637
1676
  `,
1638
1677
  [path.join(".lab", "context", "auto-outcome.md")]:
1639
1678
  `# 自动结果
@@ -2141,7 +2180,7 @@ ZH_CONTENT[path.join(".codex", "prompts", "lab-data.md")] = codexPrompt(
2141
2180
  ZH_CONTENT[path.join(".codex", "prompts", "lab-auto.md")] = codexPrompt(
2142
2181
  "在已批准边界内编排自动实验循环",
2143
2182
  "auto mode objective",
2144
- "使用已安装的 `lab` 技能:`.codex/skills/lab/SKILL.md`。\n\n立刻针对用户当前给出的参数执行 `/lab:auto`,不要只推荐别的 `/lab` 阶段。只有在缺少阻塞性前提时,才明确指出缺什么,并且一次最多追问一个问题。\n\n本命令运行 `/lab:auto` 阶段。它必须读取 `.lab/context/eval-protocol.md`、`.lab/context/auto-mode.md`、`.lab/context/auto-status.md` 与 `.lab/context/auto-outcome.md`,先确认 autonomy level、approval status、terminal goal schema,以及 primary gate、secondary guard、promotion condition、stop reason、escalation reason,再把 eval-protocol 里的指标释义、主表计划、来源约束与结构化实验阶梯当作执行依据,在不修改 mission、framing 和核心 claims 的前提下编排已批准的 `run`、`iterate`、`review`、`report`,轮询长任务完成情况;如果声明了 rung,就保持会话活着并按 rung 转移继续推进。\n首个可见输出块必须是 `Auto preflight`。这个块必须列出已读取文件,并回显 `Autonomy level`、`Approval status`、`Allowed stages`、`Terminal goal`、`Primary gate` 和 `Secondary guard`,然后才能进入执行摘要或动作计划。\n如果 preflight 所需字段缺失、过期或彼此冲突,就必须在执行前停下,并明确指出到底是哪一个字段阻止了 loop 启动。\n如果仓库的 workflow language 是中文,摘要、清单条目、任务标签和进度更新都必须使用中文,除非某个文件路径、代码标识符或字面指标名必须保持原样。\n把 `Layer 3`、`Phase 1`、`Table 2` 这类表达视为论文范围目标;只有显式写成 `Autonomy level L3` 或 `自治级别 L3` 时,才把它当成执行权限级别。\n不要用 `sleep 30`、单次 `pgrep` 或一次性的 `metrics.json` 探针来代替真实长任务命令;当真实实验进程还活着时,只允许发进度更新并继续等待。"
2183
+ "使用已安装的 `lab` 技能:`.codex/skills/lab/SKILL.md`。\n\n立刻针对用户当前给出的参数执行 `/lab:auto`,不要只推荐别的 `/lab` 阶段。只有在缺少阻塞性前提时,才明确指出缺什么,并且一次最多追问一个问题。\n\n本命令运行 `/lab:auto` 阶段。它必须读取 `.lab/context/eval-protocol.md`、`.lab/context/auto-mode.md`、`.lab/context/auto-status.md`、`.lab/context/auto-ledger.md` 与 `.lab/context/auto-outcome.md`,先确认 autonomy level、approval status、terminal goal schema,以及 primary gate、secondary guard、promotion condition、stop reason、escalation reason,再把 eval-protocol 里的指标释义、主表计划、来源约束与结构化实验阶梯当作执行依据,在不修改 mission、framing 和核心 claims 的前提下编排已批准的 `run`、`iterate`、`review`、`report`,轮询长任务完成情况;如果声明了 rung,就保持会话活着并按 rung 转移继续推进。\n首个可见输出块必须是 `Auto preflight`。这个块必须列出已读取文件,并回显 `Autonomy level`、`Approval status`、`Allowed stages`、`Terminal goal`、`Primary gate` 和 `Secondary guard`,然后才能进入执行摘要或动作计划。\n如果 preflight 所需字段缺失、过期或彼此冲突,就必须在执行前停下,并明确指出到底是哪一个字段阻止了 loop 启动。\n当 loop 活着时,必须把当前 owner、观察状态、checkpoint 摘要、继续边界、停止边界和恢复读取集合写进 `.lab/context/auto-ledger.md`。\n如果仓库的 workflow language 是中文,摘要、清单条目、任务标签和进度更新都必须使用中文,除非某个文件路径、代码标识符或字面指标名必须保持原样。\n把 `Layer 3`、`Phase 1`、`Table 2` 这类表达视为论文范围目标;只有显式写成 `Autonomy level L3` 或 `自治级别 L3` 时,才把它当成执行权限级别。\n不要用 `sleep 30`、单次 `pgrep` 或一次性的 `metrics.json` 探针来代替真实长任务命令;当真实实验进程还活着时,只允许发进度更新并继续等待。"
2145
2184
  );
2146
2185
 
2147
2186
  ZH_CONTENT[path.join(".claude", "commands", "lab.md")] = claudeCommand(
@@ -2162,7 +2201,7 @@ ZH_CONTENT[path.join(".claude", "commands", "lab-auto.md")] = claudeCommand(
2162
2201
  "lab-auto",
2163
2202
  "在已批准边界内编排自动实验循环",
2164
2203
  "auto mode objective",
2165
- "使用已安装的 `lab` 技能:`.claude/skills/lab/SKILL.md`。\n\n立刻针对用户当前给出的参数执行 `auto` 阶段,不要只推荐别的 lab 阶段。只有在缺少阻塞性前提时,才明确指出缺什么,并且一次最多追问一个问题。\n\n本命令运行 lab workflow 的 `auto` 阶段。它必须读取 `.lab/context/eval-protocol.md`、`.lab/context/auto-mode.md`、`.lab/context/auto-status.md` 与 `.lab/context/auto-outcome.md`,先确认 autonomy level、approval status、terminal goal schema,以及 primary gate、secondary guard、promotion condition、stop reason、escalation reason,再把 eval-protocol 里的指标释义、主表计划、来源约束与结构化实验阶梯当作执行依据,在不修改 mission、framing 和核心 claims 的前提下编排已批准的 `run`、`iterate`、`review`、`report`,轮询长任务完成情况;如果声明了 rung,就保持会话活着并按 rung 转移继续推进。\n首个可见输出块必须是 `Auto preflight`。这个块必须列出已读取文件,并回显 `Autonomy level`、`Approval status`、`Allowed stages`、`Terminal goal`、`Primary gate` 和 `Secondary guard`,然后才能进入执行摘要或动作计划。\n如果 preflight 所需字段缺失、过期或彼此冲突,就必须在执行前停下,并明确指出到底是哪一个字段阻止了 loop 启动。\n如果仓库的 workflow language 是中文,摘要、清单条目、任务标签和进度更新都必须使用中文,除非某个文件路径、代码标识符或字面指标名必须保持原样。\n把 `Layer 3`、`Phase 1`、`Table 2` 这类表达视为论文范围目标;只有显式写成 `Autonomy level L3` 或 `自治级别 L3` 时,才把它当成执行权限级别。\n不要用 `sleep 30`、单次 `pgrep` 或一次性的 `metrics.json` 探针来代替真实长任务命令;当真实实验进程还活着时,只允许发进度更新并继续等待。"
2204
+ "使用已安装的 `lab` 技能:`.claude/skills/lab/SKILL.md`。\n\n立刻针对用户当前给出的参数执行 `auto` 阶段,不要只推荐别的 lab 阶段。只有在缺少阻塞性前提时,才明确指出缺什么,并且一次最多追问一个问题。\n\n本命令运行 lab workflow 的 `auto` 阶段。它必须读取 `.lab/context/eval-protocol.md`、`.lab/context/auto-mode.md`、`.lab/context/auto-status.md`、`.lab/context/auto-ledger.md` 与 `.lab/context/auto-outcome.md`,先确认 autonomy level、approval status、terminal goal schema,以及 primary gate、secondary guard、promotion condition、stop reason、escalation reason,再把 eval-protocol 里的指标释义、主表计划、来源约束与结构化实验阶梯当作执行依据,在不修改 mission、framing 和核心 claims 的前提下编排已批准的 `run`、`iterate`、`review`、`report`,轮询长任务完成情况;如果声明了 rung,就保持会话活着并按 rung 转移继续推进。\n首个可见输出块必须是 `Auto preflight`。这个块必须列出已读取文件,并回显 `Autonomy level`、`Approval status`、`Allowed stages`、`Terminal goal`、`Primary gate` 和 `Secondary guard`,然后才能进入执行摘要或动作计划。\n如果 preflight 所需字段缺失、过期或彼此冲突,就必须在执行前停下,并明确指出到底是哪一个字段阻止了 loop 启动。\n当 loop 活着时,必须把当前 owner、观察状态、checkpoint 摘要、继续边界、停止边界和恢复读取集合写进 `.lab/context/auto-ledger.md`。\n如果仓库的 workflow language 是中文,摘要、清单条目、任务标签和进度更新都必须使用中文,除非某个文件路径、代码标识符或字面指标名必须保持原样。\n把 `Layer 3`、`Phase 1`、`Table 2` 这类表达视为论文范围目标;只有显式写成 `Autonomy level L3` 或 `自治级别 L3` 时,才把它当成执行权限级别。\n不要用 `sleep 30`、单次 `pgrep` 或一次性的 `metrics.json` 探针来代替真实长任务命令;当真实实验进程还活着时,只允许发进度更新并继续等待。"
2166
2205
  );
2167
2206
 
2168
2207
  const zhRecipeQuickPathLine =
package/lib/install.cjs CHANGED
@@ -44,6 +44,7 @@ const PROJECT_OWNED_LOCALIZED_PATHS = [
44
44
  path.join(".lab", "context", "eval-protocol.md"),
45
45
  path.join(".lab", "context", "auto-mode.md"),
46
46
  path.join(".lab", "context", "auto-status.md"),
47
+ path.join(".lab", "context", "auto-ledger.md"),
47
48
  path.join(".lab", "context", "auto-outcome.md"),
48
49
  path.join(".lab", "context", "terminology-lock.md"),
49
50
  path.join(".lab", "context", "summary.md"),
@@ -7,7 +7,7 @@ argument-hint: autonomous campaign target
7
7
  Use the installed `lab` skill at `.claude/skills/lab/SKILL.md`.
8
8
 
9
9
  Execute the requested `/lab-auto` command against the user's argument now. Do not only recommend another lab stage. If a blocking prerequisite is missing, say exactly what is missing and ask at most one clarifying question.
10
- This command runs the `auto` stage of the lab workflow. It must read `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, and `.lab/context/auto-outcome.md`, enforce the declared terminal goal schema, make the primary gate, secondary guard, promotion condition, stop reason, and escalation reason explicit, orchestrate approved run, iterate, review, and report stages inside that contract, poll long-running work until completion or stop conditions, and write progress plus the final outcome back into `.lab/context/auto-status.md` and `.lab/context/auto-outcome.md`.
10
+ This command runs the `auto` stage of the lab workflow. It must read `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`, enforce the declared terminal goal schema, make the primary gate, secondary guard, promotion condition, stop reason, and escalation reason explicit, orchestrate approved run, iterate, review, and report stages inside that contract, poll long-running work until completion or stop conditions, and write live owner state plus progress and the final outcome back into `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`.
11
11
  The first visible block must be `Auto preflight`. That first visible block must list the files read and echo `Autonomy level`, `Approval status`, `Allowed stages`, `Terminal goal`, `Primary gate`, and `Secondary guard` before any execution summary or action plan.
12
12
  If the preflight block cannot be completed because any required field is missing, stale, or inconsistent, stop before execution and say exactly which field blocked arming the loop.
13
13
  When the repository workflow language is Chinese, summaries, checklist items, task labels, and progress updates should be written in Chinese unless a literal identifier must stay unchanged.
@@ -72,7 +72,8 @@ Use the same repository artifacts and stage boundaries every time.
72
72
  - If the request omits the level or mixes it with a paper layer, phase, or table target, `/lab auto` should stop and ask for an explicit autonomy level before arming the loop.
73
73
  - The first visible output of a real `/lab auto` run must be `Auto preflight`.
74
74
  - That first visible output must show files read plus `Autonomy level`, `Allowed stages`, `Terminal goal`, `Primary gate`, and `Secondary guard`.
75
- - If the preflight block cannot be completed from `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, and `.lab/context/auto-outcome.md`, `/lab auto` should stop instead of acting like the loop is armed.
75
+ - If the preflight block cannot be completed from `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`, `/lab auto` should stop instead of acting like the loop is armed.
76
+ - While the loop is alive, `/lab auto` should keep `.lab/context/auto-ledger.md` updated with the active owner, observed state, and resume boundary.
76
77
 
77
78
  - Treat `Autonomy level L1/L2/L3` as the execution privilege level, not as a paper layer, phase, or table number.
78
79
  - Treat `paper layer`, `phase`, and `table` as experiment targets. For example, `paper layer 3` or `Phase 1` should not be interpreted as `Autonomy level L3`.
@@ -6,7 +6,7 @@ argument-hint: autonomous campaign target
6
6
  Use the installed `lab` skill at `.codex/skills/lab/SKILL.md`.
7
7
 
8
8
  Execute the requested `/lab:auto` stage against the user's argument now. Do not only recommend another lab stage. If a blocking prerequisite is missing, say exactly what is missing and ask at most one clarifying question.
9
- This command runs the `/lab:auto` stage. It must read `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, and `.lab/context/auto-outcome.md`, enforce the declared terminal goal schema, make the primary gate, secondary guard, promotion condition, stop reason, and escalation reason explicit, orchestrate approved run, iterate, review, and report stages inside that contract, poll long-running work until completion or stop conditions, and write progress plus the final outcome back into `.lab/context/auto-status.md` and `.lab/context/auto-outcome.md`.
9
+ This command runs the `/lab:auto` stage. It must read `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`, enforce the declared terminal goal schema, make the primary gate, secondary guard, promotion condition, stop reason, and escalation reason explicit, orchestrate approved run, iterate, review, and report stages inside that contract, poll long-running work until completion or stop conditions, and write live owner state plus progress and the final outcome back into `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`.
10
10
  The first visible block must be `Auto preflight`. That first visible block must list the files read and echo `Autonomy level`, `Approval status`, `Allowed stages`, `Terminal goal`, `Primary gate`, and `Secondary guard` before any execution summary or action plan.
11
11
  If the preflight block cannot be completed because any required field is missing, stale, or inconsistent, stop before execution and say exactly which field blocked arming the loop.
12
12
  When the repository workflow language is Chinese, summaries, checklist items, task labels, and progress updates should be written in Chinese unless a literal identifier must stay unchanged.
@@ -66,7 +66,8 @@ argument-hint: workflow question or stage choice
66
66
  - If the request omits the level or mixes it with a paper layer, phase, or table target, `/lab:auto` should stop and ask for an explicit autonomy level before arming the loop.
67
67
  - The first visible output of a real `/lab:auto` run must be `Auto preflight`.
68
68
  - That first visible output must show files read plus `Autonomy level`, `Allowed stages`, `Terminal goal`, `Primary gate`, and `Secondary guard`.
69
- - If the preflight block cannot be completed from `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, and `.lab/context/auto-outcome.md`, `/lab:auto` should stop instead of acting like the loop is armed.
69
+ - If the preflight block cannot be completed from `.lab/context/eval-protocol.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, `.lab/context/auto-ledger.md`, and `.lab/context/auto-outcome.md`, `/lab:auto` should stop instead of acting like the loop is armed.
70
+ - While the loop is alive, `/lab:auto` should keep `.lab/context/auto-ledger.md` updated with the active owner, observed state, and resume boundary.
70
71
 
71
72
  - Treat `Autonomy level L1/L2/L3` as the execution privilege level, not as a paper layer, phase, or table number.
72
73
  - Treat `paper layer`, `phase`, and `table` as experiment targets. For example, `paper layer 3` or `Phase 1` should not be interpreted as `Autonomy level L3`.
@@ -0,0 +1,35 @@
1
+ # Auto Runtime Ledger
2
+
3
+ ## Campaign
4
+
5
+ - Campaign id:
6
+ - Objective:
7
+ - Active stage:
8
+ - Active rung:
9
+
10
+ ## Owner
11
+
12
+ - Owner type:
13
+ - Owner id:
14
+ - Command:
15
+ - Watch target:
16
+ - Started at:
17
+ - Last observed at:
18
+ - Observed state:
19
+
20
+ ## Checkpoints
21
+
22
+ - Last checkpoint:
23
+ - Checkpoint summary:
24
+ - Next transition:
25
+
26
+ ## Boundaries
27
+
28
+ - Continue boundary:
29
+ - Stop boundary:
30
+ - Escalation boundary:
31
+
32
+ ## Resume
33
+
34
+ - Required read set:
35
+ - Resume command:
@@ -3,6 +3,7 @@
3
3
  Use this file to define the bounded autonomous execution envelope for `/lab:auto`.
4
4
  Pair it with `.lab/context/eval-protocol.md`, which defines the paper-facing metrics, tables, gates, and benchmark ladder that auto mode should optimize against.
5
5
  If `eval-protocol.md` declares structured rung entries, auto mode follows those rung transitions first and uses the stage commands here as per-stage fallbacks.
6
+ Use `.lab/context/auto-ledger.md` as the live runtime ledger for ownership, checkpoints, resume, and stop boundaries.
6
7
 
7
8
  ## Objective
8
9
 
@@ -42,6 +43,7 @@ If `eval-protocol.md` declares structured rung entries, auto mode follows those
42
43
  - Rung `Command` should be the real long-running command that owns the experiment result.
43
44
  - A short watcher is only a progress probe. Do not use a short watcher as the stage or rung command when the real experiment is still running.
44
45
  - While the real experiment process is still alive, only record a progress update and keep waiting.
46
+ - Record the active owner, command, and watch target in `.lab/context/auto-ledger.md` while the loop is alive.
45
47
  - Run command:
46
48
  - Iterate command:
47
49
  - Review command:
@@ -108,12 +108,13 @@ Use this skill when the user invokes `/lab:*` or asks for the structured researc
108
108
  ### `/lab:auto`
109
109
 
110
110
  - Use this stage to orchestrate approved execution stages with bounded autonomy.
111
- - Read `.lab/config/workflow.json`, `.lab/context/mission.md`, `.lab/context/state.md`, `.lab/context/workflow-state.md`, `.lab/context/decisions.md`, `.lab/context/data-decisions.md`, `.lab/context/evidence-index.md`, `.lab/context/terminology-lock.md`, `.lab/context/auto-mode.md`, and `.lab/context/auto-status.md` before acting.
112
- - Treat `.lab/context/auto-mode.md` as the control contract and `.lab/context/auto-status.md` as the live state file.
111
+ - Read `.lab/config/workflow.json`, `.lab/context/mission.md`, `.lab/context/state.md`, `.lab/context/workflow-state.md`, `.lab/context/decisions.md`, `.lab/context/data-decisions.md`, `.lab/context/evidence-index.md`, `.lab/context/terminology-lock.md`, `.lab/context/auto-mode.md`, `.lab/context/auto-status.md`, and `.lab/context/auto-ledger.md` before acting.
112
+ - Treat `.lab/context/auto-mode.md` as the control contract, `.lab/context/auto-status.md` as the live summary, and `.lab/context/auto-ledger.md` as the runtime ledger.
113
113
  - Require `.lab/context/auto-mode.md` to expose `Primary gate`, `Secondary guard`, `Promotion condition`, `Stop reason`, and `Escalation reason` before execution.
114
114
  - Require `Autonomy level` and `Approval status` in `.lab/context/auto-mode.md` before execution.
115
115
  - Start every `/lab:auto` run with a visible `Auto preflight` summary that reports files read plus `Autonomy level`, `Approval status`, `Allowed stages`, `Terminal goal`, `Primary gate`, and `Secondary guard`.
116
116
  - If any required preflight field is missing or inconsistent, stop before any loop action. Do not present a fake auto summary as if the loop were armed.
117
+ - Keep `.lab/context/auto-ledger.md` updated with the active owner, observed state, and resume boundary while the loop is live.
117
118
  - Treat `L1` as safe-run validation, `L2` as bounded iteration, and `L3` as aggressive campaign mode.
118
119
  - Surface the level guide every time `/lab:auto` starts, and make the detailed guide mandatory when the user omits the level or mixes it with a paper layer, phase, or table target.
119
120
  - Reuse `/lab:run`, `/lab:iterate`, `/lab:review`, `/lab:report`, and optional `/lab:write` instead of inventing a second workflow.
@@ -22,6 +22,7 @@
22
22
  - `.lab/context/terminology-lock.md`
23
23
  - `.lab/context/auto-mode.md`
24
24
  - `.lab/context/auto-status.md`
25
+ - `.lab/context/auto-ledger.md`
25
26
  - `.lab/context/auto-outcome.md`
26
27
 
27
28
  ## Context Write Set
@@ -36,6 +37,7 @@
36
37
  - `.lab/context/summary.md`
37
38
  - `.lab/context/session-brief.md`
38
39
  - `.lab/context/auto-status.md`
40
+ - `.lab/context/auto-ledger.md`
39
41
  - `.lab/context/auto-outcome.md`
40
42
 
41
43
  ## Boundary Rules
@@ -48,6 +50,7 @@
48
50
  - Treat `Sanity and Alternative-Explanation Checks` as the anomaly gate for automation. When a rung yields all-null outputs, suspiciously identical runs, no-op deltas, or impl/result mismatches, pause promotion logic until implementation reality checks, alternative explanations, and at least one cross-check are recorded.
49
51
  - Treat paper-template selection as an explicit write-time gate, not as a silent fallback, when the loop is about to create `.tex` deliverables for the first time.
50
52
  - Treat `.lab/context/auto-mode.md` as a visible control plane. The contract should make the primary gate, secondary guard, promotion condition, stop reason, and escalation reason explicit before execution starts.
53
+ - Treat `.lab/context/auto-ledger.md` as the live runtime ledger for owner identity, observed state, checkpoint progress, continue boundary, stop boundary, escalation boundary, and resume read set.
51
54
  - The contract must declare `Autonomy level` and `Approval status`, and execution starts only when approval is explicitly set to `approved`.
52
55
  - The contract must also declare a concrete terminal goal:
53
56
  - `rounds`
@@ -72,6 +75,11 @@
72
75
  - Keep a poll-based waiting loop instead of sleeping blindly.
73
76
  - Do not treat a short watcher such as `sleep 30`, a one-shot `pgrep`, or a single `metrics.json` probe as the rung command when the real experiment is still running.
74
77
  - Bind each rung to the real long-running command or process that owns the experiment result.
78
+ - Record the active owner as one of:
79
+ - `local-process`
80
+ - `local-runner`
81
+ - `remote-runner`
82
+ - Every nonterminal `/lab:auto` state must remain resumable from `.lab/context/auto-ledger.md` plus `.lab/context/auto-status.md`.
75
83
  - Start every real `/lab:auto` run with a visible `Auto preflight` block before any execution summary or action plan. That first visible output should list:
76
84
  - files read
77
85
  - `Autonomy level`
@@ -82,6 +90,7 @@
82
90
  - `Secondary guard`
83
91
  - If any of those preflight fields are missing, stale, or inconsistent, stop before execution and report the blocking field directly.
84
92
  - Always write a canonical `.lab/context/auto-outcome.md` when the run completes, stops, or fails.
93
+ - Always keep `.lab/context/auto-ledger.md` in sync with the current active owner while the loop is live.
85
94
  - Keep handoff wording stable across auto outcomes and downstream report or write handoffs: record completed work, frozen scope, allowed next action, required read set for the next owner, and the accept or revise or reject boundary.
86
95
  - When the evaluation protocol declares structured ladder rungs, execute them as a foreground rung state machine:
87
96
  - each rung must declare `Stage`, `Goal`, `Command`, `Watch`, `Gate`, `On pass`, `On fail`, and `On stop`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlab",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "description": "Strict /lab research workflow installer for Codex and Claude",
5
5
  "keywords": [
6
6
  "codex",