Skip small-context Ready compaction

This commit is contained in:
wassname
2026-09-09 23:19:19 +08:00
parent f49be72a30
commit 6e8eb57ad4
4 changed files with 58 additions and 4 deletions
@@ -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]
+4 -2
View File
@@ -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<void> {
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;
+2 -1
View File
@@ -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";
+15 -1
View File
@@ -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);