From 6e8eb57ad426e1beacc01ff7567563aae9ee619d Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Wed, 9 Sep 2026 23:17:12 +0800 Subject: [PATCH] Skip small-context Ready compaction --- .../solo-recovery/herdr-uat-2026-09-09.md | 37 +++++++++++++++++++ src/index.ts | 6 ++- src/supervisor-session.ts | 3 +- test/goals-flow.test.ts | 16 +++++++- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 slop/reviews/solo-recovery/herdr-uat-2026-09-09.md diff --git a/slop/reviews/solo-recovery/herdr-uat-2026-09-09.md b/slop/reviews/solo-recovery/herdr-uat-2026-09-09.md new file mode 100644 index 0000000..24bc3d0 --- /dev/null +++ b/slop/reviews/solo-recovery/herdr-uat-2026-09-09.md @@ -0,0 +1,37 @@ +# Real Herdr acceptance — 2026-09-09 + +- revision tested: `f49be72a307bbc4d22fd3468d090255e72389bc9` +- isolated repo: `/tmp/pi-goals-herdr-uat` (separate test pane `w8:p5V`; supervisor `w8:p5W`) +- worker model: `gpt-5.6-terra`; supervisor model: `gpt-5.6-terra` +- intervention: selected **Ready** in the worker's rendered plan menu; no implementation or sign-off was performed by the test driver. + +## Observations + +- The worker rendered a bounded plan and selected Ready opened a visible `goals-supervisor-...` pane. +- The worker produced and committed `hello.txt` and `verification.txt`; the supervisor inspected both files and reran the byte check. +- The supervisor recorded approval and sent the worker the exact `CompleteGoal` instruction. The worker invoked `CompleteGoal`; the plan was mechanically ticked `[x]`. +- Direct inspection after the cycle: + +> `xxd -g1 hello.txt` printed `68 65 6c 6c 6f 0a`. +> +> `verification.txt` says `status: exact match`. +> +> The plan log says `mechanically signed off "Create hello.txt and record its exact byte check in verification.txt" after matching supervisor approval`. + +## Failure observed + +The Ready compaction attempt visibly printed: + +> `Error: Compaction failed: Nothing to compact (session too small)` + +The extension caught that condition and continued: the supervisor opened and the full approval cycle succeeded. Functional acceptance still found an unresolved user-visible error for short plans. Skip the worker pre-fork `ctx.compact()` call when context use is below the existing 100k supervisor threshold, then run this same UAT again. + +## Post-fix rerun attempt + +- revision: `050f85c8d8346e64b0b260f454c57fd08d1e1fa0` +- isolated repo: `/tmp/pi-goals-herdr-uat-smallcontext`; created test pane: `w8:p5X` with `--no-focus` +- pre-run status: clean; existing dependencies installed locally with `npm ci`. + +The real interactive rerun could not start because the normal Pi profile loaded its separately installed `pi-goals` extension first. Its installed `pi-intercom` reported no extension channel, then the test extension could not register the duplicate `pi-goals` namespace. This is an environment/profile dependency conflict, not a result from the changed small-context path; no Ready action, supervisor pane, or worker work was performed. The test pane was closed after capture. The focused mocked flow test covers the changed 57k-token path; a clean normal-profile Pi environment is still required to repeat the functional UAT. + +-- PI[gpt-5.6-terra] diff --git a/src/index.ts b/src/index.ts index 314c0b9..3531222 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ import { GoalIntercom } from "./intercom.js"; import { FOLD_LINE, foldPlan, GOAL_LINE } from "./plan.js"; import { completeGoalDescription, completeGoalParamDescription, planDrafting, planningState, resync, supervisorPlanReview, workerCompaction } from "./prompts.js"; import { RoleModels } from "./role-models.js"; -import { isVisibleSupervisor, registerVisibleSupervisor, restoredSupervisor } from "./supervisor-session.js"; +import { COMPACT_AT_TOKENS, isVisibleSupervisor, registerVisibleSupervisor, restoredSupervisor } from "./supervisor-session.js"; import { workerView } from "./worker-view.js"; export { foldPlan } from "./plan.js"; @@ -257,6 +257,8 @@ export function registerWorker(pi: ExtensionAPI): void { /** Compact the agreed planning conversation once, before the supervisor forks it. */ function compactApprovedWorker(ctx: ExtensionContext): Promise { + const tokens = ctx.getContextUsage()?.tokens; + if (typeof tokens === "number" && tokens < COMPACT_AT_TOKENS) return Promise.resolve(); return new Promise((resolve, reject) => { ctx.compact({ customInstructions: workerCompaction(planPath(ctx)), @@ -680,7 +682,7 @@ export function registerWorker(pi: ExtensionAPI): void { pi.on("context", async (event, ctx) => { const messages = state.phase === "planning" ? event.messages : event.messages.filter((message) => (message as { customType?: string }).customType !== PLANNING_CONTEXT); const removedPlanningContext = messages.length !== event.messages.length; - // Ready compacts the worker before changing phase. Its custom instructions already preserve + // Ready may compact the worker before changing phase. Its custom instructions already preserve // the approved plan, so never append an extension message to that compaction transaction. if (state.phase === "planning" && planningContextPending && !readyAttempt) { planningContextPending = false; diff --git a/src/supervisor-session.ts b/src/supervisor-session.ts index 84e3e38..5cfea25 100644 --- a/src/supervisor-session.ts +++ b/src/supervisor-session.ts @@ -11,7 +11,8 @@ import { approveGoalDescription, approveGoalParameters, goalApprovalRecorded, st import { RoleModels } from "./role-models.js"; const BOOTSTRAPPED = "pi-goals-visible-supervisor-v2"; -const COMPACT_AT_TOKENS = 100_000; +/** Context size at which paired sessions compact before or during supervision. */ +export const COMPACT_AT_TOKENS = 100_000; const ROLE_STATE = "pi-goals-supervisor-binding"; diff --git a/test/goals-flow.test.ts b/test/goals-flow.test.ts index a55d91b..e9b689c 100644 --- a/test/goals-flow.test.ts +++ b/test/goals-flow.test.ts @@ -215,6 +215,19 @@ describe("/goals flow", () => { } }); + it("skips small-context worker compaction and still starts the visible supervisor", async () => { + const flow = setup(["Ready"]); + try { + flow.ctx.getContextUsage = () => ({ percent: 25, tokens: 57_000 }); + await flow.commands.get("goals").handler("make the file", flow.ctx); + approvedPlan(flow.cwd); + await flow.hooks.get("agent_settled")({}, flow.ctx); + expect(flow.ctx.compact).not.toHaveBeenCalled(); + expect(openSupervisorPane).toHaveBeenCalledOnce(); + expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", supervisorPaneId: "pane-2" }); + } finally { rmSync(flow.cwd, { recursive: true, force: true }); } + }); + it("forks a visible supervisor on Ready and keeps the main session as worker", async () => { const flow = setup(["Ready"]); try { @@ -528,9 +541,10 @@ it.each(["launch", "model"])("rejects plan content changes during Ready %s witho } finally { rmSync(flow.cwd, { recursive: true, force: true }); } }); -it("compacts the approved worker before forking the supervisor without injecting planning context", async () => { +it("compacts the approved worker at the shared threshold before forking without injecting planning context", async () => { const flow = setup(["Ready"]); try { + flow.ctx.getContextUsage = () => ({ percent: 25, tokens: 100_000 }); let complete: (() => void) | undefined; flow.ctx.compact.mockImplementationOnce((options: any) => { complete = options.onComplete; }); await flow.commands.get("goals").handler("make the file", flow.ctx);