mirror of
https://github.com/wassname/pi-plan.git
synced 2026-10-01 12:20:58 +08:00
Compact the supervisor fork before work
Co-Authored-By: PI[gpt-5.6-sol] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
1 parent
3eaaec9f5a
commit
6cfeaf44ee
8 files changed
+117
-27
No files matched your search
+5
-5
@@ -187,7 +187,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function startOrResumeWorker(ctx: ExtensionContext, task: string, signal?: AbortSignal): Promise<string> {
|
||||
async function startOrResumeWorker(ctx: ExtensionContext, task: string, compactPlanning: boolean, signal?: AbortSignal): Promise<string> {
|
||||
if (workerLaunchPending) throw new Error("A goal-worker launch is already in progress.");
|
||||
if (!workerRegistration) setupWorker(ctx);
|
||||
if (!workerRegistration) throw new Error(`Goal worker unavailable: ${workerRegistrationError ?? "pi-subagents is not ready"}.`);
|
||||
@@ -196,7 +196,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
try {
|
||||
const runId = state.workerRunId
|
||||
? await resumeGoalSupervisor(pi.events, state.workerRunId, task, signal)
|
||||
: await startGoalSupervisor(pi.events, ctx.cwd, task, signal);
|
||||
: await startGoalSupervisor(pi.events, ctx.cwd, task, compactPlanning, signal);
|
||||
rememberWorkerRun(runId);
|
||||
return runId;
|
||||
} finally {
|
||||
@@ -238,7 +238,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
return `${instruction}\n\nYou are the retained goal-supervisor. Here is the complete current plan; inspect its exact goal blocks and cited evidence before directing or approving work.\nPlan path: ${planPath(ctx)}\nApproval ID: ${state.approvalId}\nNested worker model: ${state.workerModel ?? "pi-subagents default"}\nPass the exact approval ID to ApproveGoal. Keep checkpoint paths and the approval ID from the nested worker.\nPrivate approval checkpoints, one per current goal:\n${checkpoints || "(no open goals)"}\n\n${plan}`;
|
||||
}
|
||||
|
||||
async function directSupervisor(ctx: ExtensionContext, instruction: string, signal?: AbortSignal): Promise<string> {
|
||||
async function directSupervisor(ctx: ExtensionContext, instruction: string, signal?: AbortSignal, compactPlanning = false): Promise<string> {
|
||||
beginReview(ctx);
|
||||
const task = supervisorTask(ctx, instruction);
|
||||
if (state.workerPending && state.workerRunId) {
|
||||
@@ -251,7 +251,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
persist();
|
||||
}
|
||||
}
|
||||
return startOrResumeWorker(ctx, task, signal);
|
||||
return startOrResumeWorker(ctx, task, compactPlanning, signal);
|
||||
}
|
||||
|
||||
function wakeSupervisor(ctx: ExtensionContext, reason: string): void {
|
||||
@@ -518,7 +518,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
persist();
|
||||
updateWidget(ctx);
|
||||
try {
|
||||
await directSupervisor(ctx, "Start by launching or resuming the nested goal-worker. Then supervise the current plan.");
|
||||
await directSupervisor(ctx, "Start by launching or resuming the nested goal-worker. Then supervise the current plan.", undefined, choice === "Ready (compact)");
|
||||
scheduleSupervisorCheck(ctx);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isSupervisorReadOnlyCommand } from "./index.js";
|
||||
import { processWorkState } from "./worker.js";
|
||||
|
||||
const NESTED_STATE = "pi-goals-nested-worker";
|
||||
const COMPACTED_STATE = "pi-goals-supervisor-compacted";
|
||||
|
||||
interface NestedState {
|
||||
runId: string | null;
|
||||
@@ -22,8 +23,17 @@ function targetRun(input: Record<string, unknown>): string | null {
|
||||
return typeof value === "string" && value ? value : null;
|
||||
}
|
||||
|
||||
function compactPlanningRequested(): boolean {
|
||||
const raw = process.env.PI_SUBAGENT_EXTENSION_BINDINGS;
|
||||
if (!raw) return false;
|
||||
const bindings = JSON.parse(raw) as { "pi-goals/1"?: { compactPlanning?: unknown } };
|
||||
return bindings["pi-goals/1"]?.compactPlanning === true;
|
||||
}
|
||||
|
||||
export default function goalSupervisorRuntime(pi: ExtensionAPI): void {
|
||||
let nested: NestedState = { runId: null, pending: false };
|
||||
let compacting = false;
|
||||
let compactionDone = Promise.resolve();
|
||||
const persist = () => pi.appendEntry<NestedState>(NESTED_STATE, nested);
|
||||
|
||||
pi.events.on("subagent:async-started", (raw) => {
|
||||
@@ -41,11 +51,46 @@ export default function goalSupervisorRuntime(pi: ExtensionAPI): void {
|
||||
pi.events.on("subagent:async-complete", completeNested);
|
||||
pi.events.on("subagent:process-terminal", completeNested);
|
||||
|
||||
pi.on("session_before_compact", async (event) => {
|
||||
if (!compacting) return;
|
||||
const branchEntries = event.branchEntries as Array<{ id?: string; type?: string; message?: { role?: string } }>;
|
||||
const latestMessage = [...branchEntries].reverse().find((entry) => entry.type === "message" && ["user", "assistant"].includes(entry.message?.role ?? ""));
|
||||
return {
|
||||
compaction: {
|
||||
summary: "Planning is complete. The latest retained goal-supervisor task contains the current plan and approval paths; use it as the source of truth. -- PI[gpt-5.6-sol]",
|
||||
firstKeptEntryId: latestMessage?.id ?? event.preparation.firstKeptEntryId,
|
||||
tokensBefore: event.preparation.tokensBefore,
|
||||
details: { source: "pi-goals-plan-handoff" },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
const last = ctx.sessionManager.getEntries()
|
||||
const entries = ctx.sessionManager.getEntries();
|
||||
const last = entries
|
||||
.filter((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === NESTED_STATE)
|
||||
.pop() as { data?: NestedState } | undefined;
|
||||
nested = last?.data ?? nested;
|
||||
if (!compactPlanningRequested()) return;
|
||||
if (entries.some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === COMPACTED_STATE)) return;
|
||||
compacting = true;
|
||||
compactionDone = new Promise<void>((resolvePromise, reject) => {
|
||||
ctx.compact({
|
||||
onComplete: () => {
|
||||
compacting = false;
|
||||
pi.appendEntry(COMPACTED_STATE, { version: 1 });
|
||||
resolvePromise();
|
||||
},
|
||||
onError: (error) => {
|
||||
compacting = false;
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pi.on("before_agent_start", async () => {
|
||||
await compactionDone;
|
||||
});
|
||||
|
||||
pi.on("tool_call", async (event) => {
|
||||
@@ -68,6 +113,17 @@ export default function goalSupervisorRuntime(pi: ExtensionAPI): void {
|
||||
return { block: true, reason: "The supervisor may inspect or control only its retained goal-worker." };
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "CheckWorkerState",
|
||||
label: "Check retained worker",
|
||||
description: "Return concise retained-worker state after a needs-attention notice or scheduled review. This does not return transcript text.",
|
||||
parameters: Type.Object({}),
|
||||
async execute() {
|
||||
const state = nested.runId ? (nested.pending ? "active" : "terminal") : "not-started";
|
||||
return result(`retained-worker=${state}${nested.runId ? `; run=${nested.runId}` : ""}`);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "ApproveGoal",
|
||||
label: "Approve goal",
|
||||
|
||||
+8
-6
@@ -39,8 +39,9 @@ interface AsyncSnapshot {
|
||||
export type WorkState = "active" | "idle" | "unknown";
|
||||
|
||||
export const supervisorSystemPrompt = `You are the retained goal supervisor. The main Pi session only coordinates with the human.
|
||||
Launch one goal-worker, then rely on native progress and completion updates. Do not poll status, wait, or repeatedly
|
||||
steer an active worker. Read the current plan, repository, cited evidence, and saved verification output yourself after
|
||||
Your forked planning history is compacted before your first turn. Launch one goal-worker, then rely on native progress
|
||||
and completion updates. Do not poll status, wait, or repeatedly steer an active worker. Use CheckWorkerState once only
|
||||
after a needs-attention notice or a scheduled review. Read the current plan, repository, cited evidence, and saved verification output yourself after
|
||||
the worker finishes. Do not edit project files. Use read/search and standard verification commands only. The worker must
|
||||
commit its changes before approval. When no nested work is active, HEAD is committed, the worktree is clean, and the
|
||||
evidence proves the discriminator, call ApproveGoal with the current approval ID. Otherwise give the retained worker
|
||||
@@ -54,7 +55,7 @@ export function registerGoalSupervisor(events: EventBus, model: string | null):
|
||||
definition: {
|
||||
description: "Read-only supervisor that owns a nested retained implementation worker.",
|
||||
systemPrompt: supervisorSystemPrompt,
|
||||
tools: ["read", "grep", "find", "ls", "bash", "subagent", "ApproveGoal"],
|
||||
tools: ["read", "grep", "find", "ls", "bash", "subagent", "CheckWorkerState", "ApproveGoal"],
|
||||
allowNestedSubagents: true,
|
||||
subagentOnlyExtensions: [supervisorRuntime],
|
||||
...(model ? { model } : {}),
|
||||
@@ -63,7 +64,7 @@ export function registerGoalSupervisor(events: EventBus, model: string | null):
|
||||
inheritProjectContext: false,
|
||||
inheritGlobalContext: false,
|
||||
inheritSkills: false,
|
||||
defaultContext: "fresh",
|
||||
defaultContext: "fork",
|
||||
defaultAsync: true,
|
||||
defaultProgress: true,
|
||||
},
|
||||
@@ -112,14 +113,15 @@ function asyncRunId(data: RpcData): string {
|
||||
return runId;
|
||||
}
|
||||
|
||||
export async function startGoalSupervisor(events: EventBus, cwd: string, task: string, signal?: AbortSignal): Promise<string> {
|
||||
export async function startGoalSupervisor(events: EventBus, cwd: string, task: string, compactPlanning: boolean, signal?: AbortSignal): Promise<string> {
|
||||
const data = await rpc(events, "spawn", {
|
||||
agent: SUPERVISOR_AGENT,
|
||||
task,
|
||||
cwd,
|
||||
context: "fresh",
|
||||
context: "fork",
|
||||
async: true,
|
||||
mission: false,
|
||||
extensionBindings: { "pi-goals/1": { compactPlanning } },
|
||||
}, signal);
|
||||
return asyncRunId(data);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user