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 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.4 continue through the supervisor freeze (`/glla pause`),
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: resync + continuationPrompt(state.goal!),
1190
+ content,
1124
1191
  display: false,
1125
1192
  }, { triggerTurn: true, deliverAs: "followUp" });
1126
- lastContinuationSentPayload = { content: resync + continuationPrompt(state.goal!), display: false }; // v0.34.88: verbatim retry payload
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
- ctx.ui.notify("glla: output-token stop was context starvation (tiny output at a nearly full context) — yielding to pi auto-compaction instead of re-sending.", "info");
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
- return Date.now() - lastContextStarvedAt <= CONTEXT_STARVATION_RECENT_WINDOW_MS;
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 });
@@ -288,6 +288,7 @@ const continuationDeps: ContinuationDeps = {
288
288
  isForeignCtx,
289
289
  sessionManagerId,
290
290
  isActionableGoal,
291
+ isContextStarvedRefused,
291
292
  isSupervising,
292
293
  goalNoun,
293
294
  activeGoalSurfaceCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.4",
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",