pi-goal-list-loop-audit 0.38.4 → 0.38.6
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/CHANGELOG.md +13 -0
- package/docs/INDEX.md +1 -1
- package/extensions/goal-continuation.ts +70 -3
- package/extensions/loops/goal-activation.ts +19 -1
- package/extensions/loops/goal-runtime-globals.ts +15 -0
- package/extensions/loops/goal-ui.ts +48 -1
- package/extensions/loops/goal.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.6 — over-cap starvation ladder: compact first, then 5 ordered recoveries (2026-09-03)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
Compact-first nudge: at 85% context (below the 90% starvation line) a one-shot-per-episode notify to run `/compact` while summarization still fits (episode resets below 80% or on compaction; ledger `context_compact_first_nudge`). The agent_end starvation yield notify now carries the full over-cap ladder — `/compact` retry after GLLA's deterministic trim, larger-context model (GLLA already auto-rotates its fallback chain after a failed compact-and-retry, v0.34.116), `/new` + `/goal resume` from durable disk state with no summarization needed — and states that automatic turns stay parked.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
Over-cap spin: `sendContinuation` now refuses while `isContextStarvedRefused()` — the single choke point every automatic path funnels through — so a 119–243% session stops queueing truncating turns (ledger `continuation_send_refused_context_starved`, silent; heartbeat one-shot + yield ladder own messaging). The refuse stays sticky while the last-known percent is ≥90% instead of lapsing 90s after the last yield. Heartbeat refuse branch/ledger/text unchanged (pinned). `tests/starvation-ladder.test.ts` (6 tests) pins band, ladder text, nudge episodes, sticky matrix, wiring, and a behavioral starved boot.
|
|
10
|
+
|
|
11
|
+
## 0.38.5 — delta-only goal continuation: marker-only steady-state (2026-09-03)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
Ongoing-conversation resend: steady-state goal turns now send the 45-char `[GOAL CHECKPOINT goalId=…]` marker only instead of the full ~23k continuation prompt every turn (history already holds T0 objective/contract/tasks + `complete_task` deltas). Post-compact sends `resync + marker` (~250 chars); full sends only on first-send per process or dynamic deltas (`repairTarget` / `autoResumedAt` / auditor TODOs / audit report / stale-approval mismatch / designer / full-audit). Marker still carries the dispatch marker so `before_agent_start` start-proof keeps matching; `agent_start`/`turn_start` fallback needs no prompt. Ledger `goal_continuation_sent` gains `kind` (`full`/`full+resync`/`resync`/`marker`) + `payloadChars`. Loop turns unchanged (per-iteration prompts vary). `extensions/goal-continuation.ts` (`buildMarkerContent` / `needsFullContinuation` / `buildContinuationContent`); `tests/delta-only-continuation.test.ts` pins the matrix.
|
|
15
|
+
|
|
3
16
|
## 0.38.4 — human-input zombie stand-down (PR #36 slice) + AVO close (2026-09-02)
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/docs/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ For shipped docs, the relevant entry points are:
|
|
|
18
18
|
failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
|
|
19
19
|
handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
|
|
20
20
|
shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
|
|
21
|
-
v0.35.14–v0.38.
|
|
21
|
+
v0.35.14–v0.38.6 continue through the supervisor freeze (`/glla pause`),
|
|
22
22
|
load hold, auditor picker parity, Windows launch fix, zombie-watchdog
|
|
23
23
|
subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
|
|
24
24
|
durable state-root selection, blank-until-resume auditor context, frozen
|
|
@@ -146,6 +146,7 @@ export interface ContinuationDeps {
|
|
|
146
146
|
isForeignCtx(ctx: ExtensionContext): boolean;
|
|
147
147
|
sessionManagerId(ctx: ExtensionContext): string;
|
|
148
148
|
isActionableGoal(): boolean;
|
|
149
|
+
isContextStarvedRefused(): boolean;
|
|
149
150
|
isSupervising(): boolean;
|
|
150
151
|
goalNoun(): string;
|
|
151
152
|
activeGoalSurfaceCommand(command: string): string;
|
|
@@ -170,6 +171,7 @@ let goStaleTerminal: ContinuationDeps["goStaleTerminal"];
|
|
|
170
171
|
let isForeignCtx: ContinuationDeps["isForeignCtx"];
|
|
171
172
|
let sessionManagerId: ContinuationDeps["sessionManagerId"];
|
|
172
173
|
let isActionableGoal: ContinuationDeps["isActionableGoal"];
|
|
174
|
+
let isContextStarvedRefused: ContinuationDeps["isContextStarvedRefused"];
|
|
173
175
|
let isSupervising: ContinuationDeps["isSupervising"];
|
|
174
176
|
let goalNoun: ContinuationDeps["goalNoun"];
|
|
175
177
|
let activeGoalSurfaceCommand: ContinuationDeps["activeGoalSurfaceCommand"];
|
|
@@ -194,6 +196,7 @@ export function createGoalContinuation(flagsArg: ContinuationFlags, d: Continuat
|
|
|
194
196
|
isForeignCtx = d.isForeignCtx;
|
|
195
197
|
sessionManagerId = d.sessionManagerId;
|
|
196
198
|
isActionableGoal = d.isActionableGoal;
|
|
199
|
+
isContextStarvedRefused = d.isContextStarvedRefused;
|
|
197
200
|
isSupervising = d.isSupervising;
|
|
198
201
|
goalNoun = d.goalNoun;
|
|
199
202
|
activeGoalSurfaceCommand = d.activeGoalSurfaceCommand;
|
|
@@ -1054,6 +1057,52 @@ export function scheduleContinuation(ctx: ExtensionContext, force = false, delay
|
|
|
1054
1057
|
continuationTimer = scheduleSessionTimeout(() => sendContinuation(goalId), delay);
|
|
1055
1058
|
}
|
|
1056
1059
|
|
|
1060
|
+
export function buildMarkerContent(goalId: string): string {
|
|
1061
|
+
return `[GOAL CHECKPOINT goalId=${goalId}]`;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** v0.38.5 (delta-only): steady-state live turns need only the wake-up marker —
|
|
1065
|
+
* history already holds T0 objective/contract/tasks + complete_task deltas.
|
|
1066
|
+
* Full is required only for dynamic deltas the model has not seen: repair,
|
|
1067
|
+
* recovery, auditor TODOs/report, stale-approval mismatch, plus the rare
|
|
1068
|
+
* designer / full-audit paths (preserved verbatim in v1). Stable guidance
|
|
1069
|
+
* (vision, discipline) stays clean — it was in the first full send.
|
|
1070
|
+
*/
|
|
1071
|
+
export function needsFullContinuation(goal: Goal): boolean {
|
|
1072
|
+
if (goal.repairTarget) return true;
|
|
1073
|
+
if (goal.autoResumedAt) return true;
|
|
1074
|
+
if (!auditorSurfaceSuppressed() && goal.pendingTasks && goal.pendingTasks.length > 0) return true;
|
|
1075
|
+
const lastAudit = goal.auditHistory?.[goal.auditHistory.length - 1];
|
|
1076
|
+
if (lastAudit && lastAudit.report && !auditorSurfaceSuppressed()) return true;
|
|
1077
|
+
if (
|
|
1078
|
+
!auditorSurfaceSuppressed() &&
|
|
1079
|
+
goal.status === "active" &&
|
|
1080
|
+
!goal.pendingCompletion &&
|
|
1081
|
+
lastAudit?.approved &&
|
|
1082
|
+
!lastAudit.impossible &&
|
|
1083
|
+
lastAudit.regressionShieldPassed !== false &&
|
|
1084
|
+
typeof lastAudit.revision === "number" &&
|
|
1085
|
+
lastAudit.revision !== (goal.revision ?? 0)
|
|
1086
|
+
) return true;
|
|
1087
|
+
const next = findNextPendingTask(goal.taskList?.tasks ?? []);
|
|
1088
|
+
if (goal.agentRole === "designer" || next?.agentRole === "designer") return true;
|
|
1089
|
+
try {
|
|
1090
|
+
const settingsCwd = freshCtx?.()?.cwd ?? process.cwd();
|
|
1091
|
+
const eff = resolveEffectiveAggressiveSettings(loadSettings(settingsCwd));
|
|
1092
|
+
if (eff.aggressiveMode && isFullAuditObjective(goal.objective)) return true;
|
|
1093
|
+
} catch { /* settings read failure must not force full */ }
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export function buildContinuationContent(goal: Goal, opts: { resync?: string; firstSend?: boolean } = {}): { content: string; kind: string } {
|
|
1098
|
+
const resync = opts.resync ?? "";
|
|
1099
|
+
const marker = buildMarkerContent(goal.id);
|
|
1100
|
+
const needsFull = (opts.firstSend ?? false) || needsFullContinuation(goal);
|
|
1101
|
+
if (needsFull) return { content: resync + continuationPrompt(goal), kind: resync ? "full+resync" : "full" };
|
|
1102
|
+
if (resync) return { content: resync + marker, kind: "resync" };
|
|
1103
|
+
return { content: marker, kind: "marker" };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1057
1106
|
export function sendContinuation(goalId: string): void {
|
|
1058
1107
|
// v0.35.15: `/glla pause` — a continuation timer armed BEFORE the pause
|
|
1059
1108
|
// must not fire into the frozen window. scheduleContinuation already
|
|
@@ -1094,6 +1143,15 @@ export function sendContinuation(goalId: string): void {
|
|
|
1094
1143
|
continuationTimer = scheduleSessionTimeout(() => sendContinuation(goalId), BACKOFF_IDLE_RETRY_MS);
|
|
1095
1144
|
return;
|
|
1096
1145
|
}
|
|
1146
|
+
// v0.38.6: the starvation choke point — EVERY automatic send path funnels
|
|
1147
|
+
// through here (agent_end, heartbeat rearm, loop tick, recovery). While
|
|
1148
|
+
// the session is starved with no compaction, no turn can land, so refuse
|
|
1149
|
+
// SILENTLY (ledger only): the heartbeat one-shot + agent_end yield ladder
|
|
1150
|
+
// own user messaging, and a refused send must not rearm a timer spin.
|
|
1151
|
+
if (isContextStarvedRefused()) {
|
|
1152
|
+
appendLedger(ctx.cwd, "continuation_send_refused_context_starved", { goalId, generation: flags.sessionGeneration });
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1097
1155
|
if (!guardGoalBeforeContinuation(ctx, "dispatch", goalId)) return;
|
|
1098
1156
|
if (!isActionableGoal()) return;
|
|
1099
1157
|
if (!ctx.isIdle() || ctx.hasPendingMessages()) {
|
|
@@ -1118,15 +1176,24 @@ export function sendContinuation(goalId: string): void {
|
|
|
1118
1176
|
resync: Boolean(resync),
|
|
1119
1177
|
});
|
|
1120
1178
|
if (!attempt) return;
|
|
1179
|
+
// v0.38.5 (delta-only, ongoing conversation): clean live turns send the
|
|
1180
|
+
// 45-char marker only — history already holds objective/contract/tasks.
|
|
1181
|
+
// Resync (post-compact) sends resync+marker (~250 chars). Full 23k sends
|
|
1182
|
+
// only on first-send per process or dynamic deltas (repair/recovery/
|
|
1183
|
+
// audit). Marker still carries the dispatch marker so before_agent_start
|
|
1184
|
+
// start-proof matching keeps working; fallback agent_start/turn_start
|
|
1185
|
+
// needs no prompt at all.
|
|
1186
|
+
const goalForSend = state.goal!;
|
|
1187
|
+
const { content, kind } = buildContinuationContent(goalForSend, { resync, firstSend: lastContinuationSentAt === 0 });
|
|
1121
1188
|
flags.extensionApi.sendMessage({
|
|
1122
1189
|
customType: GOAL_EVENT_ENTRY,
|
|
1123
|
-
content
|
|
1190
|
+
content,
|
|
1124
1191
|
display: false,
|
|
1125
1192
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
1126
|
-
lastContinuationSentPayload = { content
|
|
1193
|
+
lastContinuationSentPayload = { content, display: false }; // v0.34.88: verbatim retry payload
|
|
1127
1194
|
if (!dispatchAccepted(ctx, attempt)) return;
|
|
1128
1195
|
continuationRearmStreak = 0; continuationRearmSince = 0; // v0.28.5 (E3): an accepted dispatch clears the storm
|
|
1129
|
-
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId, attemptId: attempt.id, generation: attempt.generation });
|
|
1196
|
+
appendLedger(ctx.cwd, "goal_continuation_sent", { goalId, attemptId: attempt.id, generation: attempt.generation, kind, payloadChars: content.length });
|
|
1130
1197
|
// v0.35.37 (audit finding): the welcome-back recovery notice must fire
|
|
1131
1198
|
// EXACTLY ONCE per auto-resume. The payload above was built with the
|
|
1132
1199
|
// notice in it; once the dispatch is ACCEPTED the message has landed in
|
|
@@ -1980,7 +1980,11 @@ export function registerGoalRuntime(pi: ExtensionAPI): void {
|
|
|
1980
1980
|
return;
|
|
1981
1981
|
}
|
|
1982
1982
|
}
|
|
1983
|
-
|
|
1983
|
+
// v0.38.6: the yield now carries the full over-cap ladder (retry
|
|
1984
|
+
// /compact after GLLA's deterministic trim, larger-context model,
|
|
1985
|
+
// /new + resume from durable state) instead of only the auto-compact
|
|
1986
|
+
// pointer — field 2026-09-03: summarization itself was failing.
|
|
1987
|
+
ctx.ui.notify(buildStarvationLadderMessage({ percent: typeof contextUsage?.percent === "number" ? contextUsage.percent : null, streak: starved.streak }), "info");
|
|
1984
1988
|
return;
|
|
1985
1989
|
}
|
|
1986
1990
|
if (lc.giveUpNow) {
|
|
@@ -2187,6 +2191,20 @@ export function registerGoalRuntime(pi: ExtensionAPI): void {
|
|
|
2187
2191
|
if (!state.goal) return;
|
|
2188
2192
|
if (state.goal.status !== "active") return;
|
|
2189
2193
|
clearContinuationTimer();
|
|
2194
|
+
// v0.38.6: feed the sticky refuse gate + compact-first nudge with the
|
|
2195
|
+
// live percent. Placed AFTER the goal gate: the length-continue order
|
|
2196
|
+
// pin requires the length path first, and starved sessions already
|
|
2197
|
+
// returned via the yield ladder above. Recording null clears a stale
|
|
2198
|
+
// reading (host estimate gone) so the refuse can lapse instead of
|
|
2199
|
+
// sticking forever.
|
|
2200
|
+
noteContextPercent(typeof contextUsage?.percent === "number" ? contextUsage.percent : null);
|
|
2201
|
+
if (shouldCompactFirstNudge(contextUsage?.percent)) {
|
|
2202
|
+
appendLedger(ctx.cwd, "context_compact_first_nudge", {
|
|
2203
|
+
goalId: state.goal?.id ?? null,
|
|
2204
|
+
contextPercent: contextUsage?.percent ?? null,
|
|
2205
|
+
});
|
|
2206
|
+
ctx.ui.notify(`glla: context at ${contextUsage!.percent!.toFixed(1)}% — run /compact now while summarization still fits. Below 90% the compact succeeds; past it you get the over-cap ladder (retry /compact, larger model, or /new + resume).`, "info");
|
|
2207
|
+
}
|
|
2190
2208
|
|
|
2191
2209
|
const last = [...(event.messages as any[])].reverse().find((m) => m.role === "assistant");
|
|
2192
2210
|
const text = last && Array.isArray(last.content) ? last.content.filter((p: any) => p.type === "text").map((p: any) => p.text).join("\n") : "";
|
|
@@ -150,6 +150,11 @@ export const GOAL_RUNTIME_GLOBAL_NAMES = [
|
|
|
150
150
|
"contextStarvedStreak",
|
|
151
151
|
"lastContextStarvedAt",
|
|
152
152
|
"noteContextStarvedYield",
|
|
153
|
+
"noteContextPercent",
|
|
154
|
+
"shouldCompactFirstNudge",
|
|
155
|
+
"buildStarvationLadderMessage",
|
|
156
|
+
"COMPACT_FIRST_NUDGE_PERCENT",
|
|
157
|
+
"lastContextPercent",
|
|
153
158
|
"clearContextStarvedStreak",
|
|
154
159
|
"onCompactionLanded",
|
|
155
160
|
"isContextStarvedRefused",
|
|
@@ -310,6 +315,11 @@ interface GoalRuntimeDataTypes {
|
|
|
310
315
|
CONTEXT_STARVATION_RECENT_WINDOW_MS: number;
|
|
311
316
|
contextStarvedStreak: number;
|
|
312
317
|
lastContextStarvedAt: number;
|
|
318
|
+
noteContextPercent: (pct: number | null | undefined) => void;
|
|
319
|
+
shouldCompactFirstNudge: (percent: number | null | undefined) => boolean;
|
|
320
|
+
buildStarvationLadderMessage: (input?: { percent?: number | null; streak?: number }) => string;
|
|
321
|
+
COMPACT_FIRST_NUDGE_PERCENT: number;
|
|
322
|
+
lastContextPercent: number | null;
|
|
313
323
|
postCompactResumeOwed: boolean;
|
|
314
324
|
postCompactResyncPending: boolean;
|
|
315
325
|
COMPACTION_GRACE_MS: number;
|
|
@@ -487,6 +497,11 @@ declare global {
|
|
|
487
497
|
var contextStarvedStreak: GoalRuntimeGlobals["contextStarvedStreak"];
|
|
488
498
|
var lastContextStarvedAt: GoalRuntimeGlobals["lastContextStarvedAt"];
|
|
489
499
|
var noteContextStarvedYield: GoalRuntimeGlobals["noteContextStarvedYield"];
|
|
500
|
+
var noteContextPercent: GoalRuntimeGlobals["noteContextPercent"];
|
|
501
|
+
var shouldCompactFirstNudge: GoalRuntimeGlobals["shouldCompactFirstNudge"];
|
|
502
|
+
var buildStarvationLadderMessage: GoalRuntimeGlobals["buildStarvationLadderMessage"];
|
|
503
|
+
var COMPACT_FIRST_NUDGE_PERCENT: GoalRuntimeGlobals["COMPACT_FIRST_NUDGE_PERCENT"];
|
|
504
|
+
var lastContextPercent: GoalRuntimeGlobals["lastContextPercent"];
|
|
490
505
|
var clearContextStarvedStreak: GoalRuntimeGlobals["clearContextStarvedStreak"];
|
|
491
506
|
var onCompactionLanded: GoalRuntimeGlobals["onCompactionLanded"];
|
|
492
507
|
var isContextStarvedRefused: GoalRuntimeGlobals["isContextStarvedRefused"];
|
|
@@ -171,6 +171,7 @@ import {
|
|
|
171
171
|
} from "../goal-continuation.js";
|
|
172
172
|
export { __testOnlySetContinuationStartTimeout, __testOnlySetContinuationRetryBackoff } from "../goal-continuation.js";
|
|
173
173
|
import {
|
|
174
|
+
LENGTH_CONTINUE_CONTEXT_STARVED_PERCENT,
|
|
174
175
|
LENGTH_CONTINUE_MAX,
|
|
175
176
|
LENGTH_CONTINUE_TEXT,
|
|
176
177
|
isContextStarvedLengthStop,
|
|
@@ -963,8 +964,45 @@ let lastCompactionAt = 0;
|
|
|
963
964
|
// immediately clears the streak.
|
|
964
965
|
const CONTEXT_STARVATION_REFUSE_THRESHOLD = 2;
|
|
965
966
|
const CONTEXT_STARVATION_RECENT_WINDOW_MS = 90_000;
|
|
967
|
+
// v0.38.6: compact-first band — heavy but still compactable. One proactive
|
|
968
|
+
// nudge per episode here avoids the over-cap ladder entirely (summarization
|
|
969
|
+
// still fits below the 90% starvation line).
|
|
970
|
+
export const COMPACT_FIRST_NUDGE_PERCENT = 85;
|
|
971
|
+
export const COMPACT_FIRST_RESET_PERCENT = 80;
|
|
966
972
|
let contextStarvedStreak = 0;
|
|
967
973
|
let lastContextStarvedAt = 0;
|
|
974
|
+
// v0.38.6: last-known context percent (sampled at agent_end). Keeps the
|
|
975
|
+
// refuse sticky while the session is physically over cap even after the 90s
|
|
976
|
+
// recency window lapses — a slow-truncating 120%+ session must not re-arm
|
|
977
|
+
// the spin (field 2026-09-03: 119–243% truncation loops).
|
|
978
|
+
let lastContextPercent: number | null = null;
|
|
979
|
+
let compactFirstNudged = false;
|
|
980
|
+
export function noteContextPercent(pct: number | null | undefined): void {
|
|
981
|
+
lastContextPercent = typeof pct === "number" && Number.isFinite(pct) ? pct : null;
|
|
982
|
+
}
|
|
983
|
+
/** v0.38.6: compact-first decision — pure. True exactly once per episode
|
|
984
|
+
* while percent sits at/above the nudge band; resets below 80% or on
|
|
985
|
+
* compaction. Returns false for missing/non-finite input. */
|
|
986
|
+
export function shouldCompactFirstNudge(percent: number | null | undefined): boolean {
|
|
987
|
+
if (typeof percent !== "number" || !Number.isFinite(percent)) return false;
|
|
988
|
+
if (percent < COMPACT_FIRST_RESET_PERCENT) { compactFirstNudged = false; return false; }
|
|
989
|
+
if (percent < COMPACT_FIRST_NUDGE_PERCENT || compactFirstNudged) return false;
|
|
990
|
+
compactFirstNudged = true;
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
/** v0.38.6: the over-cap ladder, user-facing. Three recoveries in order
|
|
994
|
+
* (retry /compact after GLLA's deterministic trim, larger-context model,
|
|
995
|
+
* /new + resume from durable state) plus the no-LLM backstop: the
|
|
996
|
+
* post-compact resync re-anchors a fresh session with no summarization. */
|
|
997
|
+
export function buildStarvationLadderMessage(input: { percent?: number | null; streak?: number } = {}): string {
|
|
998
|
+
const at = typeof input.percent === "number" && Number.isFinite(input.percent)
|
|
999
|
+
? `${input.percent.toFixed(1)}%` : "nearly full";
|
|
1000
|
+
return `glla: output-token stop was context starvation (tiny output at ${at} context) — yielding to pi auto-compaction instead of re-sending. ` +
|
|
1001
|
+
`If compaction fails or context is already over cap, in order: (1) run /compact again — GLLA already trimmed repeat payloads to a checkpoint, so the retry may now fit; ` +
|
|
1002
|
+
`(2) switch to a larger-context model, then /compact (GLLA auto-rotates its fallback chain after a failed compact-and-retry); ` +
|
|
1003
|
+
`(3) /new, then /goal resume — the goal, tasks, ledger and audits are durable on disk, and the post-compact resync re-anchors the fresh session with no summarization needed. ` +
|
|
1004
|
+
`Automatic turns stay parked until a real compaction lands.`;
|
|
1005
|
+
}
|
|
968
1006
|
function noteContextStarvedYield(): { streak: number; shouldRefuse: boolean } {
|
|
969
1007
|
const now = Date.now();
|
|
970
1008
|
if (now - lastContextStarvedAt > CONTEXT_STARVATION_RECENT_WINDOW_MS) {
|
|
@@ -981,6 +1019,7 @@ function clearContextStarvedStreak(): void {
|
|
|
981
1019
|
/** Public: a session_compact landed — clear the starvation refuse gate. */
|
|
982
1020
|
function onCompactionLanded(): void {
|
|
983
1021
|
clearContextStarvedStreak();
|
|
1022
|
+
compactFirstNudged = false; // v0.38.6: fresh episode after real room
|
|
984
1023
|
}
|
|
985
1024
|
/** Public: has the yield path declared compaction absent? The heartbeat
|
|
986
1025
|
* consults this to refuse to schedule a new continuation while the
|
|
@@ -988,7 +1027,10 @@ function onCompactionLanded(): void {
|
|
|
988
1027
|
function isContextStarvedRefused(): boolean {
|
|
989
1028
|
if (contextStarvedStreak < CONTEXT_STARVATION_REFUSE_THRESHOLD) return false;
|
|
990
1029
|
if (lastCompactionAt > lastContextStarvedAt) return false; // a compact happened after the streak — fresh slate
|
|
991
|
-
|
|
1030
|
+
if (Date.now() - lastContextStarvedAt <= CONTEXT_STARVATION_RECENT_WINDOW_MS) return true;
|
|
1031
|
+
// v0.38.6: sticky while physically over cap — recency lapse must not
|
|
1032
|
+
// re-arm the spin against a session that never got room (field: 243%).
|
|
1033
|
+
return lastContextPercent !== null && lastContextPercent >= LENGTH_CONTINUE_CONTEXT_STARVED_PERCENT;
|
|
992
1034
|
}
|
|
993
1035
|
/** Test-only: simulate a compaction event at a controlled time without firing
|
|
994
1036
|
* the full session_compact plumbing. Pass null to clear. */
|
|
@@ -1132,6 +1174,11 @@ defineGoalRuntimeGlobal("CONTEXT_STARVATION_RECENT_WINDOW_MS", { get: () => CONT
|
|
|
1132
1174
|
defineGoalRuntimeGlobal("contextStarvedStreak", { get: () => contextStarvedStreak, set: (v) => { contextStarvedStreak = v as any; } });
|
|
1133
1175
|
defineGoalRuntimeGlobal("lastContextStarvedAt", { get: () => lastContextStarvedAt, set: (v) => { lastContextStarvedAt = v as any; } });
|
|
1134
1176
|
defineGoalRuntimeGlobal("noteContextStarvedYield", { get: () => noteContextStarvedYield });
|
|
1177
|
+
defineGoalRuntimeGlobal("noteContextPercent", { get: () => noteContextPercent });
|
|
1178
|
+
defineGoalRuntimeGlobal("shouldCompactFirstNudge", { get: () => shouldCompactFirstNudge });
|
|
1179
|
+
defineGoalRuntimeGlobal("buildStarvationLadderMessage", { get: () => buildStarvationLadderMessage });
|
|
1180
|
+
defineGoalRuntimeGlobal("COMPACT_FIRST_NUDGE_PERCENT", { get: () => COMPACT_FIRST_NUDGE_PERCENT });
|
|
1181
|
+
defineGoalRuntimeGlobal("lastContextPercent", { get: () => lastContextPercent, set: (v) => { lastContextPercent = v as any; } });
|
|
1135
1182
|
defineGoalRuntimeGlobal("clearContextStarvedStreak", { get: () => clearContextStarvedStreak });
|
|
1136
1183
|
defineGoalRuntimeGlobal("onCompactionLanded", { get: () => onCompactionLanded });
|
|
1137
1184
|
defineGoalRuntimeGlobal("isContextStarvedRefused", { get: () => isContextStarvedRefused });
|
package/extensions/loops/goal.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.6",
|
|
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. A detached extension-less auditor process re-verifies every completion with raw evidence without holding the main pi turn; confirmed drafts, decision pauses and consent gates keep you in charge.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "dracon",
|