omp-conductor 0.19.2 → 0.19.4
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 +4 -3
- package/REFERENCE.md +1 -1
- package/package.json +1 -1
- package/schema/config.schema.json +13 -1
- package/src/arm-challenge.ts +92 -16
- package/src/board.ts +50 -9
- package/src/cli.ts +2 -0
- package/src/command-manifest.ts +10 -0
- package/src/commands/arm.ts +30 -2
- package/src/commands/companion.ts +103 -0
- package/src/commands/context.ts +1 -0
- package/src/commands/worker.ts +1 -0
- package/src/companion-view.ts +81 -0
- package/src/config-schema.ts +7 -1
- package/src/config.ts +6 -3
- package/src/daemon.ts +288 -15
- package/src/dashboard/controls.ts +1 -1
- package/src/doctor.ts +126 -7
- package/src/escalate.ts +47 -1
- package/src/failure-class.fixture.json +546 -0
- package/src/failure-class.ts +192 -0
- package/src/fleet.ts +286 -32
- package/src/orchestrator-tick.ts +115 -34
- package/src/settlement.ts +181 -0
- package/src/setup.ts +6 -4
- package/src/spend-telemetry.ts +74 -2
- package/src/status-render.ts +25 -6
- package/src/store.ts +39 -3
- package/src/types.ts +39 -2
- package/src/usage.ts +25 -0
package/src/failure-class.ts
CHANGED
|
@@ -170,6 +170,16 @@ const START_FAILURE_SIGNATURES = [
|
|
|
170
170
|
// symptom this replaced (the child dying on the peer import) already read as
|
|
171
171
|
// a start failure, so charging one here would be a regression dressed as a fix.
|
|
172
172
|
"worker identity unavailable",
|
|
173
|
+
// The 2026-08-2x host faults (#986), each recorded verbatim in `lastError`
|
|
174
|
+
// by the launcher or the worker identity gate — all before the session took
|
|
175
|
+
// a turn:
|
|
176
|
+
// - the child died before it could connect to the dispatcher;
|
|
177
|
+
// - node could not resolve a package out of the worker harness tree;
|
|
178
|
+
// - the worker's settings file was unreadable (the EACCES outage).
|
|
179
|
+
"exited 1 before connecting",
|
|
180
|
+
"cannot find package",
|
|
181
|
+
"enoent while resolving package",
|
|
182
|
+
"failed to read settings config",
|
|
173
183
|
] as const;
|
|
174
184
|
|
|
175
185
|
/**
|
|
@@ -395,6 +405,128 @@ export function adminRestartAttribution(lastError: string | undefined): string |
|
|
|
395
405
|
return firstLine.trim();
|
|
396
406
|
}
|
|
397
407
|
|
|
408
|
+
/** The launcher's own statement that the session child died mid-run, before
|
|
409
|
+
* the session ended. Written by `omp.ts` when the child exits while the
|
|
410
|
+
* dispatcher still owns it; the exit code is the child's own, so a signal
|
|
411
|
+
* death reads >=128 (143 = SIGTERM, 137 = SIGKILL) — an external kill,
|
|
412
|
+
* never anything the worker's work decided (#986). */
|
|
413
|
+
const CHILD_EXIT_MARKER = /session child exited (\d+) before the session ended/;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Evidence that this run's session child was terminated by a signal, or
|
|
417
|
+
* `undefined` when it was not.
|
|
418
|
+
*
|
|
419
|
+
* Read from the surfaces a run persists — `lastError` first, then `report`,
|
|
420
|
+
* because the prompt-throw path records the launcher's message as the report
|
|
421
|
+
* and leaves `lastError` empty. Deliberately narrowed to signal-range codes:
|
|
422
|
+
* a clean nonzero exit is some process's verdict nobody has named yet, and
|
|
423
|
+
* naming it `admin-kill` would waive an attempt that may have been spent.
|
|
424
|
+
*/
|
|
425
|
+
export function childSignalDeath(
|
|
426
|
+
run: Pick<RunRecord, "lastError" | "report">,
|
|
427
|
+
): string | undefined {
|
|
428
|
+
const text = run.lastError ?? run.report;
|
|
429
|
+
if (text === undefined) return undefined;
|
|
430
|
+
const match = CHILD_EXIT_MARKER.exec(text);
|
|
431
|
+
if (match === null) return undefined;
|
|
432
|
+
const code = Number(match[1]);
|
|
433
|
+
if (!Number.isFinite(code) || code < 128) return undefined;
|
|
434
|
+
return match[0];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** The reason the settle sweep writes beside a reviewer-closed PR
|
|
438
|
+
* (`settlementFor`), matched as recorded evidence so rows written before
|
|
439
|
+
* that sweep stamped a class read the same way (#986). */
|
|
440
|
+
const REVIEWER_CLOSED_MARKER = "closed without merging";
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Evidence that a reviewer closed this run's pushed work without merging it,
|
|
444
|
+
* or `undefined` when nothing says so. Two independent signals, either
|
|
445
|
+
* sufficient: the tracker fact (`facts.pr === "closed"`), or the settle
|
|
446
|
+
* sweep's own recorded reason in `lastError`. A review decision is not a
|
|
447
|
+
* worker failure, which is exactly what `returned-for-revision` means; the
|
|
448
|
+
* only new reading here is applying it to rows whose PR closed while the row
|
|
449
|
+
* itself had already gone terminal-failed.
|
|
450
|
+
*/
|
|
451
|
+
export function reviewerClosed(
|
|
452
|
+
run: Pick<RunRecord, "lastError" | "prUrl">,
|
|
453
|
+
facts: ClassifyFacts,
|
|
454
|
+
): string | undefined {
|
|
455
|
+
if (run.lastError !== undefined && run.lastError.includes(REVIEWER_CLOSED_MARKER)) {
|
|
456
|
+
return run.lastError.split("\n")[0]?.trim();
|
|
457
|
+
}
|
|
458
|
+
if (facts.pr === "closed" && run.prUrl !== undefined) {
|
|
459
|
+
return `${run.prUrl} closed without merging`;
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** The harness's own name for giving up on a provider that kept answering with
|
|
465
|
+
* empty assistant turns: it retries, then ends the session on the retry cap
|
|
466
|
+
* and says so in its maintenance routing (`route: "empty-stop-retry-cap"`,
|
|
467
|
+
* beside `Assistant returned empty stop after retry cap; try switching
|
|
468
|
+
* models`). A closed marker written by the harness, not vendor prose. */
|
|
469
|
+
const EMPTY_STOP_MARKER = "empty-stop-retry-cap";
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Evidence that the provider answered with empty turns until the harness's own
|
|
473
|
+
* retry cap ended the session, or `undefined` when nothing recorded says so
|
|
474
|
+
* (#986).
|
|
475
|
+
*
|
|
476
|
+
* Read from the terminal evidence the settlement sweep recorded, because this
|
|
477
|
+
* marker is written by the harness's session log rather than by the transcript
|
|
478
|
+
* or the row — the run itself has no error, no verdict and no idea it died,
|
|
479
|
+
* which is precisely why two of these read as `unknown` on 2026-08-23 at
|
|
480
|
+
* 100/180 and 54/180 turns while a third run on the same model finished green
|
|
481
|
+
* in the same window.
|
|
482
|
+
*
|
|
483
|
+
* Provider flake, not worker logic: the recovery continues from whatever the
|
|
484
|
+
* attempt pushed rather than charging an implementation attempt for it.
|
|
485
|
+
*/
|
|
486
|
+
export function modelEmptyStop(
|
|
487
|
+
run: Pick<RunRecord, "terminalEvidence">,
|
|
488
|
+
): string | undefined {
|
|
489
|
+
if (run.terminalEvidence === undefined) return undefined;
|
|
490
|
+
if (!run.terminalEvidence.includes(EMPTY_STOP_MARKER)) return undefined;
|
|
491
|
+
return run.terminalEvidence;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Evidence that this run ended without ever delivering a settlement verdict,
|
|
496
|
+
* or `undefined` when something on the row says otherwise (#986).
|
|
497
|
+
*
|
|
498
|
+
* The shape: state `failed`, turns taken, no error anywhere, and a final
|
|
499
|
+
* report whose words are mid-delivery narration — "Now I'll execute …", "Type
|
|
500
|
+
* check passes. Now the test suite:" — with no `state:`/`status:` line the
|
|
501
|
+
* result parser could read. The session reached its worker and died between
|
|
502
|
+
* turns without handing over; measured over two weeks of the reference fleet,
|
|
503
|
+
* this one shape was half of all `unknown`.
|
|
504
|
+
*
|
|
505
|
+
* Deliberately narrow at every edge: turn zero belongs to
|
|
506
|
+
* {@link neverStarted}, any error text keeps the row `unknown` (an
|
|
507
|
+
* unrecognised error says something happened), and an absent report leaves
|
|
508
|
+
* genuinely nothing to name. The class charges its attempt exactly as
|
|
509
|
+
* `unknown` did — only the name becomes specific.
|
|
510
|
+
*/
|
|
511
|
+
export function noVerdictExit(run: RunRecord): string | undefined {
|
|
512
|
+
if (run.state !== "failed") return undefined;
|
|
513
|
+
if (run.turns <= 0) return undefined;
|
|
514
|
+
// Blank counts as absent: a row whose `lastError` is an empty string carries
|
|
515
|
+
// no error information, and reading it as "something happened" is what kept
|
|
516
|
+
// four real rows unnamed. Any non-blank text, recognised or not, still wins —
|
|
517
|
+
// an unrecognised error says something happened.
|
|
518
|
+
if (run.lastError !== undefined && run.lastError.trim() !== "") return undefined;
|
|
519
|
+
if (run.report === undefined || run.report.trim() === "") return undefined;
|
|
520
|
+
// Mirror the two verdict spellings the result path reads (`worker.ts`
|
|
521
|
+
// STATE_LINE_PATTERN for prose settlements, `renderSettlement`'s leading
|
|
522
|
+
// `status:` line for structured yields). Either one present means a verdict
|
|
523
|
+
// was delivered and this function must stay silent.
|
|
524
|
+
if (/^state:\s*\S+\s*$/im.test(run.report)) return undefined;
|
|
525
|
+
if (/^status:\s*\S+\s*$/im.test(run.report)) return undefined;
|
|
526
|
+
const lastWords = run.report.split("\n")[0]?.trim() ?? "";
|
|
527
|
+
return `the session ended without delivering a settlement verdict; last words: "${lastWords.slice(0, 80)}"`;
|
|
528
|
+
}
|
|
529
|
+
|
|
398
530
|
export function classifyRun(
|
|
399
531
|
run: RunRecord,
|
|
400
532
|
facts: ClassifyFacts,
|
|
@@ -424,6 +556,23 @@ export function classifyRun(
|
|
|
424
556
|
};
|
|
425
557
|
}
|
|
426
558
|
|
|
559
|
+
// The launcher's own statement that the session child died on a signal
|
|
560
|
+
// mid-run: an external kill — a drain, a restart, the host — not anything
|
|
561
|
+
// the worker's work decided (#986). Ahead of the provider branches on
|
|
562
|
+
// purpose: a transcript can carry an older, long-recovered stream fault,
|
|
563
|
+
// and the launcher's terminal statement outranks whatever the session said
|
|
564
|
+
// earlier. Requeued free like every other administrative kill.
|
|
565
|
+
if (run.state === "failed") {
|
|
566
|
+
const marker = childSignalDeath(run);
|
|
567
|
+
if (marker !== undefined) {
|
|
568
|
+
return {
|
|
569
|
+
cls: "admin-kill",
|
|
570
|
+
recovery: "requeue",
|
|
571
|
+
evidence: `killed externally (${marker}) — the session child was terminated, not finished`,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
427
576
|
// A billing state, not an implementation failure. Its own class because it is
|
|
428
577
|
// the one outage an operator fixes with a card rather than a diagnosis, and
|
|
429
578
|
// because burying a self-describing provider error in `unknown` erodes what
|
|
@@ -507,6 +656,19 @@ export function classifyRun(
|
|
|
507
656
|
}
|
|
508
657
|
}
|
|
509
658
|
|
|
659
|
+
// A reviewer closed this run's pushed work without merging it: a review
|
|
660
|
+
// decision, not a worker failure (#986). Two rows reach here — the settle
|
|
661
|
+
// sweep's own recorded reason in `lastError` on a row that predates the
|
|
662
|
+
// class, and a live `closed` tracker fact on a row whose worker died after
|
|
663
|
+
// pushing but before claiming. Recovery `none` mirrors the settle sweep
|
|
664
|
+
// exactly: the queue label is the remedy, and no action is performed here.
|
|
665
|
+
if (run.state === "failed") {
|
|
666
|
+
const closed = reviewerClosed(run, facts);
|
|
667
|
+
if (closed !== undefined) {
|
|
668
|
+
return { cls: "returned-for-revision", recovery: "none", evidence: closed };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
510
672
|
if (run.state === "blocked") {
|
|
511
673
|
return {
|
|
512
674
|
cls: "question",
|
|
@@ -639,6 +801,36 @@ export function classifyRun(
|
|
|
639
801
|
}
|
|
640
802
|
}
|
|
641
803
|
|
|
804
|
+
// The provider answered with empty turns until the harness gave up (#986).
|
|
805
|
+
// Below every branch that reads a real error on purpose: a row carrying its
|
|
806
|
+
// own provider fault is that fault, and this marker only decides rows that
|
|
807
|
+
// would otherwise be `unknown`. Continues rather than requeues, because the
|
|
808
|
+
// attempt's pushed work is the honest place to resume from, and a provider
|
|
809
|
+
// flake must not charge an implementation attempt (#1001). `killed` rows
|
|
810
|
+
// never reach here — a cap kill is the operative fact about a run that hit
|
|
811
|
+
// its ceiling, whatever the provider did earlier in the session.
|
|
812
|
+
if (run.state === "failed") {
|
|
813
|
+
const emptyStop = modelEmptyStop(run);
|
|
814
|
+
if (emptyStop !== undefined) {
|
|
815
|
+
return {
|
|
816
|
+
cls: "model-empty-stop",
|
|
817
|
+
recovery: "continue",
|
|
818
|
+
evidence: `the provider returned empty turns until the harness's retry cap ended the session (${emptyStop}) — turns ${run.turns}/${run.maxTurns}`,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// The session reached its worker and died between turns without handing over
|
|
824
|
+
// a verdict (#986): the largest single shape inside `unknown`. Named rather
|
|
825
|
+
// than requeued blind — an operator reading `status` learns the run stopped
|
|
826
|
+
// mid-delivery, which is a different question from "nothing is known".
|
|
827
|
+
if (run.state === "failed") {
|
|
828
|
+
const narration = noVerdictExit(run);
|
|
829
|
+
if (narration !== undefined) {
|
|
830
|
+
return { cls: "no-verdict", recovery: "escalate", evidence: narration };
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
642
834
|
// Deliberately escalate rather than retry. An unrecognised shape is a gap in
|
|
643
835
|
// this table, and a silent requeue would spend a budget on a cause nobody has
|
|
644
836
|
// named — the exact behaviour #132 exists to end.
|