Rotate curated supervisor nudges and repeat delivered upkeep

Keep the hourly schedule unchanged. Advance the local nudge cycle only on prompt delivery and reset the unchanged-turn counter so upkeep repeats. Test compaction supersession, cadence, rotation and solo behavior.

Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-09-11 20:58:39 +08:00
co-authored by Pi/OpenAI
parent 46a82c5488
commit ac3b9575fa
5 changed files with 52 additions and 7 deletions
+1 -1
View File
@@ -148,7 +148,7 @@ pi -e .
## Context delivery
Startup and successful compaction mark the plan for a fresh read at the next ordinary prompt (`before_agent_start`). Upkeep becomes due after eight unchanged turns, but waits for that same prompt boundary. A full plan refresh replaces pending upkeep; edits, pause, exit and session navigation invalidate obsolete reminders. Failed or cancelled compaction does not schedule another refresh or consume pending upkeep. Missing plans are retried without discarding progress.
Startup and successful compaction mark the plan for a fresh read at the next ordinary prompt (`before_agent_start`). Upkeep becomes due after each eight unchanged turns, but waits for that same prompt boundary. Supervisor upkeep cycles through six curated nudges, advancing only when delivered; the editable hourly prompt is unchanged. A full plan refresh replaces pending upkeep; edits, pause, exit and session navigation invalidate obsolete reminders. Failed or cancelled compaction does not schedule another refresh or consume pending upkeep. Missing plans are retried without discarding progress.
This is deliberately passive on Pi 0.85.1: tool-loop continuations, overflow retries and already-queued user messages keep Pi's existing role and compacted context, without an extra model turn just to repeat the plan. They do **not** receive a newly read plan until ordinary prompt preparation. Pi's `triggerTurn: false` mid-run path can save a message absent from the live request snapshot; steering can instead force an unwanted turn. We use neither path for upkeep. Passive pause notices use `nextTurn`, with immediate UI feedback; stopping remains local and remote termination is unconfirmed. Quit sends no model message.
+5 -2
View File
@@ -91,6 +91,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
return snapshot.text;
};
let turnsStale = 0;
let upkeepRound = 0;
let lastWorkingSet = "";
let pendingUpkeep: { generation: number; workingSet: string } | undefined;
const checkIn = (ctx: ExtensionContext) => scheduleCheckIn(ctx.sessionManager.getSessionId(), state.plan ?? "");
@@ -170,6 +171,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
state.helpers ??= []; // sessions persisted before helper bookkeeping
notice = true;
turnsStale = 0;
upkeepRound = 0;
lastWorkingSet = "";
pendingUpkeep = undefined;
refresh(ctx);
@@ -280,8 +282,9 @@ export default function mainSupervisor(pi: ExtensionAPI) {
? { customType: "pi-goals-plan", content: planContext(state.child ? "worker" : state.mode, state.plan, snapshot.text), display: false }
: pendingUpkeep?.generation === generation && pendingUpkeep.workingSet === foldPlan(snapshot.text)
&& ["supervising", "solo"].includes(state.mode) && goals(snapshot.text).some(g => g.status === "open" || g.status === "active")
? { customType: "pi-goals-upkeep", content: upkeep(state.plan!), display: false } : undefined;
if (notice) turnsStale = 0;
? { customType: "pi-goals-upkeep", content: upkeep(state.plan!, state.mode === "supervising" ? upkeepRound : undefined), display: false } : undefined;
if (message?.customType === "pi-goals-upkeep" && state.mode === "supervising") upkeepRound++;
if (message) turnsStale = 0;
notice = false;
pendingUpkeep = undefined;
return { systemPrompt: `${event.systemPrompt}\n\n${role}`, ...(message ? { message } : {}) };
+12 -2
View File
@@ -154,8 +154,18 @@ Take uncertainty as an invitation to investigate, not something to hide. Have ro
Use stock subagent for launch and subagent_resume with the returned sessionFile only after confirming the worker stopped. A stored handle is not proof of liveness; missing runtime state is not proof it stopped. Use pi-intercom list/status to identify the actual live child session before live steering; receipt alone does not prove action. Give each worker your Intercom session ID ${supervisorId}; require its completion report through Intercom while its pane stays open. A recap alone sends no instruction. Record '- worker session:' and '- worker intercom session:' in plan preferences from actual launch results and received-message identity; never confuse the runtime ID with the Intercom ID. Ensure the child calls AttachGoalPlan with the supplied path. Inspect results before CompleteGoal, then continue only unfinished goals.
Use the worker model requested in plan preferences, verify the resolved model, and report unavailable choices instead of silently substituting. Keep normal tools, not edxeth's restricted orchestrator mode. After reload or compaction reread the plan. Failed compaction, exhausted credits or lost connection do not erase progress: diagnose the actual error, restore an available authorized model/credits and resume the same saved session; never restart long work. Stock edxeth can crash the parent when a worker exits after parent reload: preserve drafts and stop workers before /reload. If it already happened, restart the saved parent session; do not repeat completed work.`;
}
export function upkeep(planPath: string): string {
return `Plan upkeep: update task ticks, evidence and Log in ${planPath} when you have new progress to record. Preserve agreed goals and discriminators. If already reviewing evidence, finish that review rather than repeat a status recap. This turn-event reminder does not resume paused work.`;
// Pi/OpenAI: user nudges plus quotes/attributions from https://github.com/wassname/ml-debug/blob/main/fortune.txt.
const upkeepNudges = [
"is the worker stuck? (or are you)",
"Insufficient skepticism doesn't feel like insufficient skepticism from the inside. It just feels like doing research. -- Neel Nanda",
"take a breath, use a kamoji, how it going?",
"Don't let your instruments overwhelm your system. -- David J. Agans, *Debugging: The 9 Indispensable Rules*",
"is the worker being cheeky, does it need sheperding",
"The first step is just making time to stop and ask yourself: do I endorse what I'm doing, and could I be doing something better? -- Neel Nanda",
];
export function upkeep(planPath: string, supervisorRound?: number): string {
const nudge = supervisorRound === undefined ? "" : `${upkeepNudges[supervisorRound % upkeepNudges.length]}\n\n`;
return `${nudge}Plan upkeep: update task ticks, evidence and Log in ${planPath} when you have new progress to record. Preserve agreed goals and discriminators. If already reviewing evidence, finish that review rather than repeat a status recap. This turn-event reminder does not resume paused work.`;
}
export function planContext(mode: string, path: string | undefined, text: string): string {
return `Current goal mode: ${mode}. Earlier role messages are historical; this current role governs.\nPlan: ${path ?? "not attached"}\n${text}`;
+24 -1
View File
@@ -4,7 +4,7 @@ import { join } from "node:path";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { afterEach, expect, it, vi } from "vitest";
import goalsExtension from "../src/index.js";
import { scheduleCheckIn } from "../src/prompts.js";
import { scheduleCheckIn, upkeep } from "../src/prompts.js";
const roots: string[] = [];
const shutdowns: Array<() => void> = [];
@@ -625,6 +625,29 @@ it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, and joins
expect(reminders()).toHaveLength(0);
});
it.each(["supervising", "solo"])("%s repeats upkeep every eight unchanged turns and rotates only delivered supervisor nudges", async mode => {
const f = fixture(); await f.draft();
if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); }
else await f.command("ready");
const prepare = () => f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
prepare();
for (let i = 0; i < 9; i++) f.hooks.get("turn_end")({}, f.ctx);
f.hooks.get("session_compact")();
expect(prepare().message.customType).toBe("pi-goals-plan");
const sent = f.messages.length;
for (let round = 0; round < 7; round++) {
for (let turn = 0; turn < 7; turn++) f.hooks.get("turn_end")({}, f.ctx);
expect(prepare().message).toBeUndefined();
f.hooks.get("turn_end")({}, f.ctx);
expect(f.messages).toHaveLength(sent);
expect(prepare().message).toMatchObject({
customType: "pi-goals-upkeep",
content: upkeep(f.path, mode === "supervising" ? round : undefined),
});
expect(prepare().message).toBeUndefined();
}
});
it("extra subagent launches are recorded as helpers and never steal the implementation identity", async () => {
const f = fixture(); await f.draft(); await f.command("ready");
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "impl", sessionFile: "/tmp/impl.jsonl" } });
+10 -1
View File
@@ -1,5 +1,14 @@
import { describe, expect, it } from "vitest";
import { planDrafting } from "../src/prompts.js";
import { planDrafting, upkeep } from "../src/prompts.js";
it("cycles six curated supervisor nudges without changing the shared upkeep instructions", () => {
const base = upkeep("/plan.md");
const variants = Array.from({ length: 6 }, (_, round) => upkeep("/plan.md", round));
expect(new Set(variants).size).toBe(6);
for (const text of variants) expect(text.endsWith(base)).toBe(true);
expect(upkeep("/plan.md", 6)).toBe(variants[0]);
expect(base.startsWith("Plan upkeep:")).toBe(true);
});
describe("planning prompt", () => {
it("requires fact finding or a focused question before a goal", () => {