omp-conductor 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,7 @@ import { formatDigestBacklog, formatOpenReports } from "./reports.ts";
26
26
  import { formatDownDuration, formatOrchestratorDown } from "./orchestrator-down.ts";
27
27
  import { planUsageLine } from "./usage.ts";
28
28
  import { HEALTH_TIMEOUT_MS, SYSTEMD_UNIT, type UnitOwnership } from "./lifecycle.ts";
29
- import type { WorkerPausePhase } from "./worker.ts";
29
+ import type { WorkerPauseView } from "./fleet.ts";
30
30
  import { formatRss, rssBytesFromHealthz } from "./host.ts";
31
31
  import {
32
32
  formatBaseHealth,
@@ -269,7 +269,8 @@ const GROOMING_IN_FLIGHT_REASON = "in-flight";
269
269
  * pass saw that no durable row covers: `routed − rows`, clamped at zero so
270
270
  * stale rows (candidates since removed from the queue) cannot push it
271
271
  * negative. `routed` is the same denominator the tick's grooming trigger
272
- * reads (`summary.routed >= groomBelow`), and the fleet's queue-digest
272
+ * reads (`summary.routed >= groomBelow`; under `groomBelow: "always"`,
273
+ * #988, the duty never clears on volume), and the fleet's queue-digest
273
274
  * arithmetic already treats durable rows as current-queue facts
274
275
  * (`claimable = routed − knownBlocked`), so this is the operator-facing
275
276
  * half of the same subtraction. Parent/epic exclusions and backlog
@@ -472,7 +473,7 @@ export function formatFleetStatus(
472
473
  brief: string | undefined = undefined,
473
474
  decisions: string | undefined = undefined,
474
475
  failureClasses: string | undefined = undefined,
475
- workerPhases: ReadonlyMap<number, WorkerPausePhase> = new Map(),
476
+ workerPhases: ReadonlyMap<number, WorkerPauseView> = new Map(),
476
477
  intake: string | undefined = undefined,
477
478
  lastStop: DaemonStop | undefined = undefined,
478
479
  siblings: { project: string; live: number }[] = [],
@@ -653,7 +654,7 @@ function formatSiblingLive(siblings: { project: string; live: number }[]): strin
653
654
 
654
655
  function formatProjectBody(
655
656
  s: StatusSnapshot,
656
- workerPhases: ReadonlyMap<number, WorkerPausePhase> = new Map(),
657
+ workerPhases: ReadonlyMap<number, WorkerPauseView> = new Map(),
657
658
  now = Date.now(),
658
659
  siblings: { project: string; live: number }[] = [],
659
660
  ): string {
@@ -702,6 +703,11 @@ function formatProjectBody(
702
703
  s.caps.dailySpendUsd === null
703
704
  ? ` spend today $${s.spendTodayUsd.toFixed(2)} estimated (no daily cap)`
704
705
  : ` spend today $${s.spendTodayUsd.toFixed(2)} estimated / $${s.caps.dailySpendUsd.toFixed(2)}` +
706
+ // A dollar ceiling over a subscription-billed fleet can never fire, so
707
+ // showing it bare asserts headroom that is not the constraint (#984).
708
+ // The cap stays visible because it IS configured — what changes is that
709
+ // the row stops implying it governs anything.
710
+ (s.spendTelemetry?.kind === "subscription" ? " (inert — subscription-billed, see below)" : "") +
705
711
  (s.reservedSpendUsd === undefined || s.reservedSpendUsd === 0
706
712
  ? ""
707
713
  : ` (+$${s.reservedSpendUsd.toFixed(2)} reserved by runs in flight)`),
@@ -857,15 +863,28 @@ function formatProjectBody(
857
863
  } else {
858
864
  lines.push(leases.length === 0 ? "mutation leases (none)" : "mutation leases");
859
865
  for (const r of leases) {
860
- const phase = workerPhases.get(r.issue);
866
+ const pauseView = workerPhases.get(r.issue);
867
+ const phase = pauseView?.phase;
861
868
  // A run in a live review round reads `review-revision N`, distinct from
862
869
  // a failure and from an ordinary continuation, with the round number
863
870
  // from the durable revision row (#692). The live pause overlay still
864
871
  // wins: an operator pause is the newer fact about the same session.
872
+ // The overlay names its source and age when the daemon recorded them
873
+ // (#997): `paused (board, 4m)` answers "who and since when" at the
874
+ // surface where the question actually gets asked.
865
875
  const paused = phase === "pausing" || phase === "paused";
866
876
  const round = s.reviewRounds?.[r.id];
877
+ const pauseDetail =
878
+ paused && (pauseView?.source !== undefined || pauseView?.pausedAtMs !== undefined)
879
+ ? ` (${[
880
+ ...(pauseView.source === undefined ? [] : [pauseView.source]),
881
+ ...(pauseView.pausedAtMs === undefined
882
+ ? []
883
+ : [formatDownDuration(Math.max(0, now - pauseView.pausedAtMs))]),
884
+ ].join(", ")})`
885
+ : "";
867
886
  const state = paused
868
- ? phase
887
+ ? `${phase}${pauseDetail}`
869
888
  : round !== undefined
870
889
  ? `review-revision ${round.round}`
871
890
  : r.state;
package/src/store.ts CHANGED
@@ -211,6 +211,7 @@ const UPDATABLE_COLUMNS: Record<string, true> = {
211
211
  graphTools: true,
212
212
  failureCharges: true,
213
213
  continuationCharges: true,
214
+ terminalEvidence: true,
214
215
  };
215
216
 
216
217
  /** Everything SQLite will accept from us. */
@@ -263,6 +264,7 @@ interface RunRow {
263
264
  lastError: string | null;
264
265
  settlementFlags: string | null;
265
266
  report: string | null;
267
+ terminalEvidence: string | null;
266
268
  failureClass: string | null;
267
269
  recoveryAction: string | null;
268
270
  recoveredAt: number | null;
@@ -746,6 +748,11 @@ CREATE TABLE IF NOT EXISTS runs (
746
748
  endedAt INTEGER,
747
749
  lastError TEXT,
748
750
  settlementFlags TEXT,
751
+ -- The terminal evidence the settlement sweep recorded for this attempt
752
+ -- (#986): exit path, first recoverable error line, audit flags. Nullable:
753
+ -- rows written before the column, or not yet looked at by the sweep, carry
754
+ -- NULL, which reads as "not recorded" — never "nothing was known".
755
+ terminalEvidence TEXT,
749
756
  report TEXT,
750
757
  failureClass TEXT,
751
758
  recoveryAction TEXT,
@@ -1494,6 +1501,7 @@ function toRecord(row: RunRow): RunRecord {
1494
1501
  if (flags !== undefined) record.settlementFlags = flags;
1495
1502
  }
1496
1503
  if (row.report !== null) record.report = row.report;
1504
+ if (row.terminalEvidence !== null) record.terminalEvidence = row.terminalEvidence;
1497
1505
  if (row.provider429Count !== null) record.provider429Count = row.provider429Count;
1498
1506
  if (row.resolvedModel !== null) record.resolvedModel = row.resolvedModel;
1499
1507
  if (row.resolvedProvider !== null) record.resolvedProvider = row.resolvedProvider;
@@ -1992,6 +2000,13 @@ export function openStore(dbPath: string): Store {
1992
2000
  if (!columns.some((column) => column.name === "report")) {
1993
2001
  db.exec("ALTER TABLE runs ADD COLUMN report TEXT");
1994
2002
  }
2003
+ // The terminal evidence the settlement sweep records for every non-clean
2004
+ // row (#986). Rows written before the column were never looked at by the
2005
+ // recorder, so NULL is the honest reading — no backfill, because the
2006
+ // recorder derives evidence from facts only the settling pass holds.
2007
+ if (!columns.some((column) => column.name === "terminalEvidence")) {
2008
+ db.exec("ALTER TABLE runs ADD COLUMN terminalEvidence TEXT");
2009
+ }
1995
2010
  // Every row written before #132 is unclassified, and NULL is the honest
1996
2011
  // reading of that: the budget counters below deliberately still count an
1997
2012
  // unclassified terminal row exactly as this release's predecessor did, so an
@@ -2516,7 +2531,7 @@ export function openStore(dbPath: string): Store {
2516
2531
  // the same predicate against the pre-claim row, so a preserved charge can
2517
2532
  // never disagree with what the counters would have counted.
2518
2533
  const CHARGEABLE_FAILURE_EXCLUSIONS =
2519
- "'ci-infra', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit', 'provider-transient', 'provider-capacity', 'returned-for-revision'";
2534
+ "'ci-infra', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit', 'provider-transient', 'provider-capacity', 'returned-for-revision', 'model-empty-stop'";
2520
2535
  const CHARGEABLE_CONTINUATION_EXCLUSIONS =
2521
2536
  "'admin-kill', 'settlement-stuck', 'env-start-failure', 'dispatch-infra', 'provider-credit', 'provider-transient', 'provider-capacity'";
2522
2537
 
@@ -2545,7 +2560,14 @@ export function openStore(dbPath: string): Store {
2545
2560
  AND (
2546
2561
  (state IN ('killed', 'orphaned', 'blocked')
2547
2562
  AND (failureClass IS NULL OR failureClass NOT IN (${CHARGEABLE_CONTINUATION_EXCLUSIONS})))
2548
- OR (state = 'failed' AND failureClass = 'returned-for-revision')
2563
+ -- Two failed-state classes charge a continuation instead of an
2564
+ -- attempt (#795, #1001): a reviewer's decision, and a provider
2565
+ -- that answered with empty turns until the harness gave up.
2566
+ -- Neither is the work failing, and both resume from the head the
2567
+ -- attempt already pushed - but each still consumes a bounded
2568
+ -- budget, so a provider stuck in that state escalates to a human
2569
+ -- rather than looping forever.
2570
+ OR (state = 'failed' AND failureClass IN ('returned-for-revision', 'model-empty-stop'))
2549
2571
  )) AS n`,
2550
2572
  );
2551
2573
  // How many times one issue reached a given class. Recovery uses it to bound a
@@ -2665,6 +2687,15 @@ export function openStore(dbPath: string): Store {
2665
2687
  GROUP BY failureClass
2666
2688
  ORDER BY n DESC, failureClass ASC`,
2667
2689
  );
2690
+ // The surviving `unknown` rows (#986): classified but never explained, each
2691
+ // carrying its recorded terminal evidence for `status` to print. Bounded —
2692
+ // a backlog must not turn one status render into an unbounded scan.
2693
+ const selectUnknownEvidence = db.query<RunRow, [string, number]>(
2694
+ `SELECT * FROM runs
2695
+ WHERE project = ? AND failureClass = 'unknown'
2696
+ ORDER BY startedAt DESC, rowid DESC
2697
+ LIMIT ?`,
2698
+ );
2668
2699
  const selectRecoveredSince = db.query<RunRow, [string, number]>(
2669
2700
  `SELECT * FROM runs
2670
2701
  WHERE project = ? AND recoveredAt IS NOT NULL AND recoveredAt >= ?
@@ -5082,6 +5113,11 @@ export function openStore(dbPath: string): Store {
5082
5113
  return entry;
5083
5114
  },
5084
5115
 
5116
+ /** The surviving `unknown` rows, newest first, with whatever terminal
5117
+ * evidence the sweep recorded on them (#986). Bounded per call. */
5118
+ unknownRuns(project: string, limit = 20): RunRecord[] {
5119
+ return selectUnknownEvidence.all(project, limit).map(toRecord);
5120
+ },
5085
5121
  verbLedger(
5086
5122
  project: string,
5087
5123
  opts: { runId?: string; issue?: number; limit?: number } = {},
package/src/types.ts CHANGED
@@ -1007,6 +1007,16 @@ export type ArmProof = (typeof ARM_PROOFS)[number];
1007
1007
  */
1008
1008
  export const DEFAULT_ARM_PROOF: ArmProof = "challenge";
1009
1009
 
1010
+ /**
1011
+ * The grooming trigger (#988): a numeric threshold keeps today's behaviour —
1012
+ * the tick grooms when the routable count is below it — and the literal
1013
+ * `"always"` grooms on demand every tick while any ungroomed, unrefused,
1014
+ * unparked candidate exists, judged from candidate state rather than queue
1015
+ * volume. Never spell "always" as a large number: a big queue must not
1016
+ * silence the duty.
1017
+ */
1018
+ export type GroomTrigger = number | "always";
1019
+
1010
1020
  /**
1011
1021
  * Everything the dispatcher needs to service one product: where work comes
1012
1022
  * from, where code goes, and what it may spend doing it. Config is per project
@@ -1020,9 +1030,10 @@ export interface ProjectConfig {
1020
1030
  /** The one label that means "a human has signed this off as agent-ready". */
1021
1031
  queueLabel: string;
1022
1032
  /** Routable-candidate count below which the tick prompt tells the orchestrator
1023
- * to groom the queue. Optional; defaults to {@link DEFAULT_GROOM_BELOW} in
1033
+ * to groom the queue, or {@link GroomTrigger} `"always"` to groom on demand
1034
+ * every tick (#988). Optional; defaults to {@link DEFAULT_GROOM_BELOW} in
1024
1035
  * orchestrator-tick.ts. */
1025
- groomBelow?: number;
1036
+ groomBelow?: GroomTrigger;
1026
1037
  /** Labels the dispatcher writes back so the tracker alone shows live state
1027
1038
  * to a human who never opens the daemon's logs. `backlog` is the
1028
1039
  * operator's own park gesture (#507) — not dispatcher-written, but read by
@@ -1683,6 +1694,19 @@ export const FAILURE_CLASSES = [
1683
1694
  /** A reviewer closed pushed-green or pushed-pending work without merging it:
1684
1695
  * a review decision, not a worker failure. */
1685
1696
  "returned-for-revision",
1697
+ /** The session ended without delivering any settlement verdict: no yield, no
1698
+ * green claim, no error — the row's last words are mid-delivery narration
1699
+ * (#986). Distinct from `unknown` on purpose: the evidence proves the run
1700
+ * reached its worker and died between turns, so the gap it names is "the
1701
+ * model stopped before handing over" rather than "nothing is known". It
1702
+ * charges the attempt exactly as `unknown` did; only the name is specific. */
1703
+ "no-verdict",
1704
+ /** The provider answered with empty assistant turns until the harness's own
1705
+ * retry cap gave up and ended the session (#986): the terminal line reads
1706
+ * `empty-stop-retry-cap`. Provider flake, not worker logic — both of the
1707
+ * 2026-08-23 failures were this, at 100/180 and 54/180 turns, while a third
1708
+ * run on the same model finished green in the same window. */
1709
+ "model-empty-stop",
1686
1710
  "unknown",
1687
1711
  ] as const;
1688
1712
 
@@ -2084,6 +2108,15 @@ export interface RunRecord {
2084
2108
  endedAt?: number;
2085
2109
  /** Last failure text, surfaced verbatim in escalations. */
2086
2110
  lastError?: string;
2111
+ /**
2112
+ * The terminal evidence the settlement sweep recorded for this attempt
2113
+ * (#986): the exit path the row took, the first line of whatever error was
2114
+ * recoverable from the row or its transcript, and any settlement-audit
2115
+ * flags — one bounded line, readable in `status` beside a surviving
2116
+ * `unknown`. Absent means the row predates the column, or the sweep has not
2117
+ * looked at it yet; it never means "nothing was known".
2118
+ */
2119
+ terminalEvidence?: string;
2087
2120
  /** Advisory settlement-audit findings for this attempt (#128). Deliberately
2088
2121
  * never consulted by anything that decides `state`: a flagged run settles
2089
2122
  * exactly as an unflagged one does, and the flags are evidence for whoever
@@ -3285,6 +3318,10 @@ export interface Store {
3285
3318
  * every actionable class behind a monotonically growing tally.
3286
3319
  */
3287
3320
  recordedOnlyClassCounts(project: string): { cls: FailureClass; n: number }[];
3321
+ /** The surviving `unknown` rows, newest first, each with whatever terminal
3322
+ * evidence the sweep recorded on it (#986). Bounded per call — a status
3323
+ * render must never walk a whole backlog. */
3324
+ unknownRuns(project: string, limit?: number): RunRecord[];
3288
3325
  /** Rows whose recovery ran at or after `since`, newest first — the tick's
3289
3326
  * "auto-recovered since last tick" line, and its count. */
3290
3327
  recoveredSince(project: string, since: number): RunRecord[];
package/src/usage.ts CHANGED
@@ -688,6 +688,31 @@ export async function readPlanUsage(
688
688
  * to fit on one row. Derived from the same status object as the long form so
689
689
  * the two cannot disagree about whether the guard is active.
690
690
  */
691
+ /**
692
+ * The allowance window nearest its ceiling, named for a reader (#984).
693
+ *
694
+ * A subscription has an allowance where an API key has a price, so this is what
695
+ * "the constraint that binds" means on a subscription-billed fleet — and the
696
+ * one nearest exhaustion is the one that will stop the fleet first, whatever
697
+ * the others say. Read from the provider's own reply, never derived: a fleet
698
+ * whose provider reports no window gets `undefined`, which is the honest answer
699
+ * and keeps a genuine telemetry fault a fault.
700
+ *
701
+ * Only windows that reported a comparable fraction are eligible, for the reason
702
+ * this module already documents at length: `used` is not comparable across
703
+ * providers, so a raw count can never stand in for one.
704
+ */
705
+ export function bindingAllowanceWindow(reading: UsageReading): string | undefined {
706
+ if (reading.kind !== "ok") return undefined;
707
+ const comparable = reading.windows.filter((w) => w.usedFraction !== undefined);
708
+ if (comparable.length === 0) return undefined;
709
+ let worst = comparable[0]!;
710
+ for (const window of comparable.slice(1)) {
711
+ if ((window.usedFraction ?? 0) > (worst.usedFraction ?? 0)) worst = window;
712
+ }
713
+ return worst.label === undefined ? worst.id : `${worst.id} (${worst.label})`;
714
+ }
715
+
691
716
  export function planUsageBadge(status: PlanUsageStatus | undefined): string {
692
717
  if (status === undefined) return "plan not read";
693
718
  let core: string;