mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-12 12:50:58 +08:00
Separate short review context from full supervisor orientation
This commit is contained in:
@@ -65,7 +65,7 @@ Keep the prompt generic. Do not prescribe pueue, Modal, worktrees, or a particul
|
||||
|
||||
Use `@monotykamary/pi-supervisor` as a behavioral reference, not an implementation to copy wholesale. Its outcome focus, autonomous continuation, and instruction not to repeat ineffective steering are useful. Judge our behavior in real sessions, not by test counts alone.
|
||||
|
||||
Pi/OpenAI implementation: every supervisor review rereads the current plan above `## Log`, including its outcome and discriminators. A short role reminder repeats on startup, after compaction, after any change to that plan section (including goal ticks), and after N supervisor turns. Set `PI_GOALS_SUPERVISOR_REMINDER_TURNS` in the launch environment; default 5, positive integers only. This reminder supports judgment; it does not establish that the goal was achieved.
|
||||
Pi/OpenAI implementation: each review repeats the short supervisor opening and current plan outcome, preferences, goals and discriminators, excluding task/evidence detail. Startup and compaction repeat the longer role prompt and full active plan before appendices/history. The long prompt asks the supervisor to read applicable AGENTS.md instructions and relevant skills rather than assuming project-specific preferences. Both forms preserve plan wording. Prompt inspiration: Anthropic's constitution (intent and autonomy) and @monotykamary/pi-supervisor (outcome focus and effective steering). Repetition supports judgment; it does not establish success.
|
||||
|
||||
Keep brief visible recaps that add judgment rather than repeat unchanged status. Preserve useful reasoning and evidence checks; reduce redundant context and reviews before reducing judgment. Manual checkbox changes are claims, not proof of completion. Plan edits should reach the supervisor so it can judge drift and direct corrections.
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Pi/OpenAI: Preserve plan wording; omit history and, in the short view, task/evidence details.
|
||||
export function planViews(plan: string): { short: string; long: string } {
|
||||
const long = plan.split(/^#{1,6}\s+(?:Log|Appendix|Appendices|Appendixes|Interview|Learnings|Papercuts)\b.*$/mi)[0].trim();
|
||||
const kept: string[] = [];
|
||||
let omittedIndent: number | null = null;
|
||||
let omittedHeading: number | null = null;
|
||||
for (const line of long.split("\n")) {
|
||||
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
|
||||
if (heading) {
|
||||
if (omittedHeading !== null && heading[1].length <= omittedHeading) omittedHeading = null;
|
||||
if (/^(?:Tasks?|Task list|Subtasks?|Evidence)\b/i.test(heading[2])) omittedHeading = heading[1].length;
|
||||
}
|
||||
if (omittedHeading !== null) continue;
|
||||
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
||||
if (omittedIndent !== null) {
|
||||
if (!line.trim() || indent > omittedIndent) continue;
|
||||
omittedIndent = null;
|
||||
}
|
||||
if (/^\s*[-*]\s+(?:tasks?|subtasks?|evidence):/i.test(line) || /^\s*(?:\d+[.)]|[-*])\s+\[[ x/~-]\]\s+(?!goal:)/i.test(line)) {
|
||||
omittedIndent = indent;
|
||||
continue;
|
||||
}
|
||||
kept.push(line);
|
||||
}
|
||||
return { short: kept.join("\n").trim(), long };
|
||||
}
|
||||
+24
-13
@@ -5,6 +5,7 @@ import { Text } from "@earendil-works/pi-tui";
|
||||
import { Type } from "typebox";
|
||||
import { approvalPath, goalBlock, hashGoalBlock, repositoryState, verifyOutputPath, writeApproval } from "./approval.js";
|
||||
import { GoalIntercom } from "./intercom.js";
|
||||
import { planViews } from "./plan-view.js";
|
||||
import { RoleModels } from "./role-models.js";
|
||||
|
||||
const BOOTSTRAPPED = "pi-goals-visible-supervisor-v2";
|
||||
@@ -66,8 +67,21 @@ function latestWorkerView(ctx: ExtensionContext): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function supervisorOpening(settings: SupervisorConfig): string {
|
||||
return `Your job is to be a diligent supervisor, autonomously extending the user's agency by correctly understanding their goals and preferences. Supervise the worker according to ${settings.planPath}, which the user helped write.`;
|
||||
}
|
||||
|
||||
// Pi/OpenAI: User intent/autonomy adapted from https://www.anthropic.com/constitution; outcome focus from @monotykamary/pi-supervisor.
|
||||
function supervisorPrompt(settings: SupervisorConfig): string {
|
||||
return `You are the visible pi-goals supervisor for ${settings.planPath}. You are a stronger, read-only reviewer. The other Pi session is the implementation worker and keeps the full conversation. You keep the high-level intent from the compacted planning conversation and worker views. The complete plan at ${settings.planPath} is the source of truth; read it directly after every compaction.
|
||||
return `${supervisorOpening(settings)}
|
||||
|
||||
At startup and after compaction, read the applicable AGENTS.md instructions and relevant skills to understand the user's goals, preferences, and working standards. Do not assume a particular project or workflow. Read the plan's appendices when needed.
|
||||
|
||||
Understand the user's immediate request without interpreting it too literally or too liberally. Consider their final goals and the background standards and preferences the work should meet. Use good planning, taste, context, and high-level perspective. Infer ordinary implementation details, but do not silently replace the agreed outcome or invent restrictions.
|
||||
|
||||
Protect the user's epistemic autonomy and rational agency. Make consequential uncertainty and disagreement visible. Respect their authorized decisions without requiring them to justify reasonable preferences; voice concerns without substituting your preferences for theirs.
|
||||
|
||||
You are the visible pi-goals supervisor for ${settings.planPath}. You are a stronger, read-only reviewer. The other Pi session is the implementation worker and keeps the full conversation. You keep the high-level intent from the compacted planning conversation and worker views. The complete plan at ${settings.planPath} is the source of truth; read it directly after every compaction.
|
||||
|
||||
Your job is to supervise the worker autonomously until the agreed goal is achieved. Use judgment: identify the missing user-visible result, decide the next useful action, and supervise it through to delivery. Approval records support this work; they are not the outcome. Seek justified confidence, not certainty at any cost. Investigate uncertainty with the cheapest useful check, then decide. Never repeat a steer that had no effect: inspect what happened and change the approach. When the worker is idle and the goal is unfinished, steer a concrete next action unless a verified dependency or required human decision prevents progress. Do not prolong completed work for optional polish.
|
||||
|
||||
@@ -91,12 +105,8 @@ export function isVisibleSupervisor(): boolean {
|
||||
export function registerVisibleSupervisor(pi: ExtensionAPI): void {
|
||||
const settings = config();
|
||||
let compacting = false;
|
||||
const reminderEvery = Number(process.env.PI_GOALS_SUPERVISOR_REMINDER_TURNS ?? 5);
|
||||
if (!Number.isInteger(reminderEvery) || reminderEvery < 1) throw new Error("PI_GOALS_SUPERVISOR_REMINDER_TURNS must be a positive integer.");
|
||||
let turnsSinceReminder = reminderEvery;
|
||||
let previousPlan = "";
|
||||
pi.on("turn_end", async () => { turnsSinceReminder++; });
|
||||
pi.on("session_compact", async () => { turnsSinceReminder = reminderEvery; });
|
||||
let repeatFullPrompt = true;
|
||||
pi.on("session_compact", async () => { repeatFullPrompt = true; });
|
||||
let bootstrapping = false;
|
||||
let warnedUnknownUsage = false;
|
||||
let modelError: string | null = null;
|
||||
@@ -172,12 +182,13 @@ export function registerVisibleSupervisor(pi: ExtensionAPI): void {
|
||||
if (BLOCKED_TOOLS.has(event.toolName.toLowerCase())) return { block: true, terminate: true, reason: "Supervisor is read-only; use SteerWorker for the bound worker, not the general intercom tool." };
|
||||
});
|
||||
pi.on("before_agent_start", async (_event, ctx) => {
|
||||
const plan = readFileSync(settings.planPath, "utf8").split(/^## Log\s*$/m)[0].trim();
|
||||
const remind = plan !== previousPlan || turnsSinceReminder >= reminderEvery;
|
||||
previousPlan = plan;
|
||||
if (remind) turnsSinceReminder = 0;
|
||||
const reminder = remind ? "\n\nSupervisor role reminder: Supervise autonomously toward the agreed outcome. Use judgment, investigate blockers, keep useful work moving, and inspect the result before accepting completion. A checkbox change is a claim to review, not proof." : "";
|
||||
return { systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorPrompt(settings)}\n\nCurrent agreed plan (reread for every review):\n${plan}\n\nJudge progress against this outcome and its discriminators. A completed artifact or task is not completion unless it satisfies the agreed goal.${reminder}` };
|
||||
const plan = planViews(readFileSync(settings.planPath, "utf8"));
|
||||
const message = repeatFullPrompt ? { customType: "pi-goals-supervisor-role", content: `${supervisorPrompt(settings)}\n\nFull active plan:\n${plan.long}`, display: true } : undefined;
|
||||
repeatFullPrompt = false;
|
||||
return {
|
||||
systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorOpening(settings)}\n\nCurrent agreed plan (reread for every review):\n${plan.short}\n\nJudge progress against this outcome and its discriminators. A completed artifact or task is not completion unless it satisfies the agreed goal.`,
|
||||
...(message ? { message } : {}),
|
||||
};
|
||||
});
|
||||
pi.on("agent_settled", async (_event, ctx) => {
|
||||
if (compacting) return;
|
||||
|
||||
@@ -52,7 +52,7 @@ it("runs a forked Pi supervisor and receives its exact instruction in another Pi
|
||||
let body = "";
|
||||
for await (const chunk of request) body += chunk;
|
||||
const input = JSON.parse(body);
|
||||
const latest = input.messages.at(-1);
|
||||
const latest = input.messages.filter((message: any) => !JSON.stringify(message.content).includes("Full active plan:")).at(-1);
|
||||
const steer = latest.role === "user" && JSON.stringify(latest.content).includes("The worker stopped.");
|
||||
if (steer) supervisorTools = input.tools.map((tool: any) => tool.function.name);
|
||||
response.writeHead(200, { "content-type": "text/event-stream" });
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { planViews } from "../src/plan-view.js";
|
||||
|
||||
it("keeps outcome, preferences and discriminators without tasks or history", () => {
|
||||
const plan = "# Outcome\nBeat random, not just plot it.\n## User preferences\nKeep costs low.\n## Goals\n1. [ ] goal: repair\n - discriminator: beats random\n - subtle failure mode: plot exists but result fails\n - tasks:\n 1. [x] draw plot\n - evidence:\n - old output\n2. [ ] goal: confirm\n## Task list\n- [ ] run it\n## Appendix\nunapproved idea";
|
||||
const views = planViews(plan);
|
||||
for (const text of ["Beat random", "Keep costs low", "goal: repair", "discriminator: beats random", "subtle failure mode", "goal: confirm"]) expect(views.short).toContain(text);
|
||||
for (const text of ["draw plot", "old output", "run it", "unapproved idea"]) expect(views.short).not.toContain(text);
|
||||
expect(views.long).toContain("draw plot");
|
||||
expect(views.long).toContain("old output");
|
||||
expect(views.long).not.toContain("unapproved idea");
|
||||
});
|
||||
|
||||
it("stops at history and preserves a manual goal tick", () => {
|
||||
const view = planViews("# Plan\n1. [x] goal: result\n## Log\n1. [ ] goal: historical");
|
||||
expect(view.short).toContain("[x] goal: result");
|
||||
expect(view.long).not.toContain("historical");
|
||||
});
|
||||
@@ -133,19 +133,20 @@ describe("visible supervisor session", () => {
|
||||
try {
|
||||
writeFileSync(join(cwd, "plan.md"), "# Outcome\nBeat random\n1. [ ] goal: repair\n - discriminator: beats random\n## Log\nold history");
|
||||
const runtime = setup(cwd, join(cwd, "plan.md"));
|
||||
const { systemPrompt } = await runtime.hooks.get("before_agent_start")({}, runtime.ctx);
|
||||
const first = await runtime.hooks.get("before_agent_start")({}, runtime.ctx);
|
||||
const systemPrompt = `${first.systemPrompt}\n${first.message.content}`;
|
||||
expect(systemPrompt).toContain("brief visible recap");
|
||||
expect(systemPrompt).toContain("discriminator: beats random");
|
||||
expect(systemPrompt).not.toContain("old history");
|
||||
expect(systemPrompt).toContain("Supervisor role reminder:");
|
||||
const review = async () => (await runtime.hooks.get("before_agent_start")({}, runtime.ctx)).systemPrompt;
|
||||
expect(await review()).not.toContain("Supervisor role reminder:");
|
||||
for (let i = 0; i < 5; i++) await runtime.hooks.get("turn_end")({}, runtime.ctx);
|
||||
expect(await review()).toContain("Supervisor role reminder:");
|
||||
expect(first.message.customType).toBe("pi-goals-supervisor-role");
|
||||
const review = async () => runtime.hooks.get("before_agent_start")({}, runtime.ctx);
|
||||
const next = await review();
|
||||
expect(next.message).toBeUndefined();
|
||||
expect(next.systemPrompt).toContain("autonomously extending the user's agency");
|
||||
await runtime.hooks.get("session_compact")({}, runtime.ctx);
|
||||
expect(await review()).toContain("Supervisor role reminder:");
|
||||
expect((await review()).message.content).toContain("Protect the user's epistemic autonomy");
|
||||
writeFileSync(join(cwd, "plan.md"), "# Outcome\nBeat random\n1. [x] goal: repair\n - discriminator: beats random\n");
|
||||
expect(await review()).toContain("Supervisor role reminder:");
|
||||
expect((await review()).systemPrompt).toContain("[x] goal: repair");
|
||||
expect(systemPrompt).toContain("your judgment");
|
||||
expect(systemPrompt).toContain("justified confidence, not certainty at any cost");
|
||||
expect(systemPrompt).toContain('Treat "blocked", "waiting", "impossible", and "already done" as claims to investigate');
|
||||
|
||||
Reference in New Issue
Block a user