pi-goal-list-loop-audit 0.34.14 → 0.34.15
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.
|
@@ -176,6 +176,8 @@ export interface Goal {
|
|
|
176
176
|
* At 3 the goal pauses loudly — a broken auditor model must not spin a
|
|
177
177
|
* silent retry-forever loop. Cleared on any real auditor run. */
|
|
178
178
|
auditInfraStreak?: number;
|
|
179
|
+
/** v0.34.15: persisted error-brake rung — survives /reload so the 6-brake park can engage. */
|
|
180
|
+
errorBrakeStreak?: number;
|
|
179
181
|
/** v0.28.26: the completion claim captured when an audit attempt is
|
|
180
182
|
* quota-blocked. The quota retry re-runs the AUDITOR directly with this
|
|
181
183
|
* stored claim instead of re-engaging the agent — re-engaging produced a
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -849,7 +849,6 @@ const COMPACTION_GRACE_MS = 3 * 60_000;
|
|
|
849
849
|
// budget now spans ~5.5m) and escalate the brake cooldown per consecutive
|
|
850
850
|
// brake (1m, 2m, 4m, 8m, 16m cap). A successful turn resets both.
|
|
851
851
|
const ERROR_RETRY_LADDER_MS = [5_000, 15_000, 45_000, 90_000, 180_000];
|
|
852
|
-
let errorBrakeStreak = 0;
|
|
853
852
|
const SEND_REARM_LEDGER_MILESTONES_MS = [2 * 60_000, 5 * 60_000, 10 * 60_000];
|
|
854
853
|
// v0.28.29: escalation is TIME-based and ACTIVITY-gated. A busy session is
|
|
855
854
|
// NORMAL — the user conversing, or one long subagent turn — and the old
|
|
@@ -1259,6 +1258,39 @@ function freshCtx(): ExtensionContext | null {
|
|
|
1259
1258
|
}
|
|
1260
1259
|
}
|
|
1261
1260
|
|
|
1261
|
+
// v0.34.15 (hegemon 2026-08-01): pi ACCEPTED the continuation — footer showed
|
|
1262
|
+
// "1 queued" — but the turn trigger was dead, so the message sat queued while
|
|
1263
|
+
// pi idled. The 0.34.11 watchdog gates on "pi reported NO pending" and the
|
|
1264
|
+
// stall ladder takes ~10 minutes; a send that lands queued-without-a-turn is
|
|
1265
|
+
// a CONFIRMED dead trigger (hegemon law), so probe once, ~45s after every
|
|
1266
|
+
// landed send, and go straight to auto-recovery. A consumed message (even an
|
|
1267
|
+
// instant-429 turn consumes it) or any real activity disarms the probe.
|
|
1268
|
+
function queueStuckProbeMs(): number {
|
|
1269
|
+
return Number(process.env.GLLA_QUEUE_STUCK_MS ?? 45_000);
|
|
1270
|
+
}
|
|
1271
|
+
let queueStuckProbe: ReturnType<typeof setTimeout> | null = null;
|
|
1272
|
+
function armQueueStuckProbe(ctx: ExtensionContext, sentAt: number): void {
|
|
1273
|
+
if (queueStuckProbe) clearTimeout(queueStuckProbe);
|
|
1274
|
+
queueStuckProbe = setTimeout(() => {
|
|
1275
|
+
queueStuckProbe = null;
|
|
1276
|
+
try {
|
|
1277
|
+
if (isForeignCtx(ctx)) return; // stale instance — the live one probes
|
|
1278
|
+
if (!isSupervising()) return; // paused/completed meanwhile
|
|
1279
|
+
if (lastContinuationSentAt !== sentAt) return; // a newer send armed its own probe
|
|
1280
|
+
if (lastRealActivityAt > sentAt) return; // the turn started and worked
|
|
1281
|
+
if (!ctx.isIdle()) return; // a turn is running — healthy
|
|
1282
|
+
if (!ctx.hasPendingMessages()) return; // consumed — even an instant 429 consumes
|
|
1283
|
+
appendLedger(ctx.cwd, "queue_stuck_detected", { waitedMs: Date.now() - sentAt });
|
|
1284
|
+
if (!attemptAutoRecovery(ctx, "queue-stuck continuation")) {
|
|
1285
|
+
const msg = `${goalNoun()}: the continuation is QUEUED but pi won't start a turn — the turn trigger is dead (re-sends only queue). Cure: /reload — the goal resumes itself after the rebuild (autoresume off? /glla resume).`;
|
|
1286
|
+
ctx.ui.notify(msg, "warning");
|
|
1287
|
+
notifyExternal(ctx, msg);
|
|
1288
|
+
}
|
|
1289
|
+
} catch { /* stale ctx — the live instance owns the probe now */ }
|
|
1290
|
+
}, queueStuckProbeMs());
|
|
1291
|
+
queueStuckProbe.unref?.();
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1262
1294
|
function scheduleContinuation(ctx: ExtensionContext, force = false, delayMs?: number): void {
|
|
1263
1295
|
abortedStandDown = false; // v0.29.5: any explicit schedule ends the stand-down
|
|
1264
1296
|
if (!isActionableGoal()) return;
|
|
@@ -1315,6 +1347,7 @@ function sendContinuation(goalId: string): void {
|
|
|
1315
1347
|
continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): a landed send clears the storm
|
|
1316
1348
|
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId });
|
|
1317
1349
|
lastContinuationSentAt = Date.now();
|
|
1350
|
+
armQueueStuckProbe(ctx, lastContinuationSentAt);
|
|
1318
1351
|
} catch (err) {
|
|
1319
1352
|
appendLedger(ctx.cwd, "goal_continuation_send_failed", { goalId, error: err instanceof Error ? err.message : String(err) });
|
|
1320
1353
|
// v0.26.7: stale runtime = terminal (sends can never land); anything
|
|
@@ -2997,6 +3030,7 @@ function sendLoopTurn(): void {
|
|
|
2997
3030
|
loopRearmStreak = 0; loopRearmSince = 0; // v0.28.5 (E3): a landed turn clears the storm
|
|
2998
3031
|
appendLedger(ctx.cwd, "loop_turn_sent", { iteration: loop.iteration });
|
|
2999
3032
|
lastContinuationSentAt = Date.now();
|
|
3033
|
+
armQueueStuckProbe(ctx, lastContinuationSentAt);
|
|
3000
3034
|
} catch (err) {
|
|
3001
3035
|
// stale API — next agent_end reschedules (but if none comes, the
|
|
3002
3036
|
// heartbeat's stall escalation stops the spin — v0.26.1).
|
|
@@ -6731,12 +6765,22 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6731
6765
|
// 60-second provider hiccup waiting on a manual /goal resume.
|
|
6732
6766
|
const detail = text.trim() ? ` (last: ${text.trim().replace(/\s+/g, " ").slice(0, 160)})` : "";
|
|
6733
6767
|
const reason = `5 consecutive errors${detail}`;
|
|
6768
|
+
// v0.34.15: the streak now lives ON THE GOAL so it survives the
|
|
6769
|
+
// auto-recovery /reloads that used to zero the module counter —
|
|
6770
|
+
// hegemon 2026-08-01: a hard-exhausted MiniMax plan churned 1-minute
|
|
6771
|
+
// probes for an hour because every reload reset the ladder to rung 1
|
|
6772
|
+
// and the 6-brake park (v0.29.9) could never engage.
|
|
6773
|
+
const brakeStreak = state.goal!.errorBrakeStreak ?? 0;
|
|
6774
|
+
// v0.34.15: a quota/rate-limit wall is NOT a flake — the card must
|
|
6775
|
+
// say "resuming won't help; switch /model or wait out the window"
|
|
6776
|
+
// (the raw 429 text was in `detail` but nobody parses JSON on a card).
|
|
6777
|
+
const quotaWall = /rate.?limit|usage limit|quota|insufficient|credits/i.test(detail);
|
|
6734
6778
|
// v0.29.1: brake-cycle CAP. The v0.28.25 ladder slows the thrash
|
|
6735
6779
|
// (1m→16m) but never STOPS it — junk-runner/hellhunter/pully each
|
|
6736
6780
|
// burned 4+ pause↔retry cycles against provider windows that last
|
|
6737
6781
|
// hours. After 6 consecutive brakes: park. v0.29.9: the park keeps
|
|
6738
6782
|
// probing at the top of each hour (clock-aligned window resets).
|
|
6739
|
-
if (
|
|
6783
|
+
if (brakeStreak >= 6) {
|
|
6740
6784
|
// v0.29.9: park — but keep probing at the top of each hour
|
|
6741
6785
|
// (user: "simply adding an hourly retry … just to pick up work
|
|
6742
6786
|
// faster assuming the retry expired"). Coding-plan rate-limit
|
|
@@ -6750,18 +6794,20 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6750
6794
|
status: "paused",
|
|
6751
6795
|
pauseKind: "error",
|
|
6752
6796
|
pauseReason: `${reason} — 6 error-brakes in a row; the provider has been erroring for an extended window`,
|
|
6753
|
-
pauseSuggestedAction:
|
|
6797
|
+
pauseSuggestedAction: quotaWall
|
|
6798
|
+
? "Provider quota/rate-limit wall — resuming won't help until the window resets. Hourly top-of-hour probes will pick work back up; switch /model to a different provider to continue immediately."
|
|
6799
|
+
: "Probing at the top of each hour — rate-limit windows typically expire on clock-hour boundaries. /goal resume retries now.",
|
|
6754
6800
|
}, ctx);
|
|
6755
|
-
ctx.ui.notify(`${goalNoun()} parked: ${reason} — 6 brakes in a row. Hourly top-of-hour probes will pick work back up when the window opens
|
|
6801
|
+
ctx.ui.notify(`${goalNoun()} parked: ${reason} — 6 brakes in a row. ${quotaWall ? "Quota/rate-limit wall — switching /model continues immediately; otherwise hourly" : "Hourly"} top-of-hour probes will pick work back up when the window opens.`, "warning");
|
|
6756
6802
|
notifyExternal(ctx, `${goalNoun()} parked: provider erroring across 6 error-brake cycles — hourly top-of-hour probes scheduled.`);
|
|
6757
|
-
appendLedger(ctx.cwd, "error_brake_capped", { streak:
|
|
6803
|
+
appendLedger(ctx.cwd, "error_brake_capped", { streak: brakeStreak, reason });
|
|
6758
6804
|
const probeMs = msUntilNextHourBoundary(Date.now());
|
|
6759
6805
|
scheduleQuotaRetry(ctx, probeMs / 1000, reason, () => {
|
|
6760
6806
|
// Re-check: only probe if STILL parked by the error-brake cap —
|
|
6761
6807
|
// a user pause/resume/cancel meanwhile is never stomped.
|
|
6762
6808
|
if (state.goal && state.goal.status === "paused" && state.goal.pauseKind === "error"
|
|
6763
6809
|
&& (state.goal.pauseReason ?? "").includes("error-brakes in a row")) {
|
|
6764
|
-
appendLedger(ctx.cwd, "hourly_rate_probe", { goalId: state.goal.id, streak: errorBrakeStreak });
|
|
6810
|
+
appendLedger(ctx.cwd, "hourly_rate_probe", { goalId: state.goal.id, streak: state.goal.errorBrakeStreak ?? 0 });
|
|
6765
6811
|
updateGoal({ status: "active" }, ctx);
|
|
6766
6812
|
appendLedger(ctx.cwd, "goal_resumed", { via: "hourly-rate-probe" });
|
|
6767
6813
|
ctx.ui.notify("Hourly probe: resuming (rate-limit windows typically expire at the top of the hour).", "info");
|
|
@@ -6772,17 +6818,19 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6772
6818
|
}
|
|
6773
6819
|
// v0.28.25: the cooldown escalates per CONSECUTIVE brake — a fleet-wide
|
|
6774
6820
|
// 403 window is not cleared by re-braking every 60 seconds.
|
|
6775
|
-
const cooldownMs = 60_000 * 2 ** Math.min(
|
|
6821
|
+
const cooldownMs = 60_000 * 2 ** Math.min(brakeStreak, 4);
|
|
6776
6822
|
const cooldownMin = Math.round(cooldownMs / 60_000);
|
|
6777
|
-
errorBrakeStreak++;
|
|
6778
6823
|
updateGoal({
|
|
6779
6824
|
status: "paused",
|
|
6780
6825
|
pauseKind: "wait",
|
|
6781
6826
|
pauseResumeAt: new Date(Date.now() + cooldownMs).toISOString(),
|
|
6782
6827
|
pauseReason: reason,
|
|
6783
|
-
|
|
6828
|
+
errorBrakeStreak: brakeStreak + 1,
|
|
6829
|
+
pauseSuggestedAction: quotaWall
|
|
6830
|
+
? `Provider quota/rate-limit wall — resuming won't help until the window resets. Switch /model to a different provider to continue now, or let the probe auto-resume in ${cooldownMin}m.`
|
|
6831
|
+
: `Transient provider flake? The goal auto-resumes once in ${cooldownMin}m if still paused for this reason — or /goal resume now.`,
|
|
6784
6832
|
}, ctx);
|
|
6785
|
-
ctx.ui.notify(`Goal paused: ${reason}
|
|
6833
|
+
ctx.ui.notify(`Goal paused: ${reason}.${quotaWall ? " Quota/rate-limit wall — resuming won't help until the window resets; switch /model to continue now." : ""}`, "warning");
|
|
6786
6834
|
notifyExternal(ctx, `Goal paused: ${reason}.`);
|
|
6787
6835
|
appendLedger(ctx.cwd, "goal_paused", { reason });
|
|
6788
6836
|
scheduleQuotaRetry(ctx, cooldownMs / 1000, reason, () => {
|
|
@@ -6833,7 +6881,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
6833
6881
|
} else {
|
|
6834
6882
|
consecutiveErrorIterations = 0;
|
|
6835
6883
|
consecutiveAbortIterations = 0;
|
|
6836
|
-
|
|
6884
|
+
// v0.28.25/v0.34.15: a healthy turn clears the (now persisted) brake streak
|
|
6885
|
+
if ((state.goal?.errorBrakeStreak ?? 0) > 0) updateGoal({ errorBrakeStreak: undefined }, ctx);
|
|
6837
6886
|
}
|
|
6838
6887
|
|
|
6839
6888
|
// No wall-clock cap by design: a goal ends via completion, explicit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.15",
|
|
4
4
|
"description": "Mission control for autonomous pi: interview-drafted goals, an audited task queue, and forever-loops (metric, spec, project-audit) that run for hours. An isolated extension-less auditor re-verifies every completion with raw evidence; confirmed drafts, decision pauses and consent gates keep you in charge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|
package/schemas/goal.schema.json
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"interruptedAt": { "type": "string" },
|
|
54
54
|
"interruptedReason": { "type": "string" },
|
|
55
55
|
"auditInfraStreak": { "type": "number" },
|
|
56
|
+
"errorBrakeStreak": { "type": "number" },
|
|
56
57
|
"pendingCompletion": { "type": "object" },
|
|
57
58
|
"createdVia": { "type": "string" },
|
|
58
59
|
"activePath": { "type": "string" },
|