fix: package goal worker for nested discovery

Co-Authored-By: Pi/Codex <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-09-05 18:43:00 +08:00
co-authored by Pi/Codex
parent 9fbc156860
commit 2852432d44
9 changed files with 59 additions and 81 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ continue until all goals close, the human uses `auto off`, or the plan is cleare
## Prompts
Planning and coordinator sign-off prompts live in [`src/prompts.ts`](src/prompts.ts). Runtime-agent registration and RPC calls live in [`src/worker.ts`](src/worker.ts). The supervisor-only nested-worker registration and approval tool live in [`src/supervisor-runtime.ts`](src/supervisor-runtime.ts).
Planning and coordinator sign-off prompts live in [`src/prompts.ts`](src/prompts.ts). Supervisor registration and RPC calls live in [`src/worker.ts`](src/worker.ts). The packaged worker definition lives in [`agents/goal-worker.md`](agents/goal-worker.md), and the supervisor-only approval tool lives in [`src/supervisor-runtime.ts`](src/supervisor-runtime.ts).
## Manual check
+21
View File
@@ -0,0 +1,21 @@
---
name: goal-worker
description: Implementation worker directed by the retained goal supervisor
thinking: high
systemPromptMode: replace
inheritProjectContext: true
inheritGlobalContext: true
inheritSkills: true
tools: read, grep, find, ls, bash, edit, write, contact_supervisor
defaultContext: fork
async: true
defaultProgress: true
---
You are the retained implementation worker for one goal supervisor.
Work autonomously from the approved plan. Keep the plan current, run the real checks, commit the implementation, and leave specific evidence in its Log. The human's latest message outranks the plan; update affected goals instead of defending an obsolete decision. The main Pi agent is the research supervisor and owns direction and goal sign-off.
Send `contact_supervisor` progress updates when evidence changes the research direction, when an hourly check asks for one, or when you need a decision. Do not claim a goal is complete; report the evidence and let the supervisor decide. Continue until the plan is complete or the human stops the session.
-- Pi/Codex
+6
View File
@@ -27,6 +27,7 @@
},
"files": [
"src",
"agents",
"README.md"
],
"publishConfig": {
@@ -54,6 +55,11 @@
"extensions": [
"./src/index.ts"
],
"subagents": {
"agents": [
"./agents"
]
},
"image": "https://cdn.jsdelivr.net/gh/wassname/pi-goals@main/media/screenshot.png"
}
}
+1 -14
View File
@@ -4,7 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
import { goalBlock, hashGoalBlock, repositoryState, writeApproval } from "./approval.js";
import { isSupervisorReadOnlyCommand } from "./index.js";
import { registerGoalWorker, subagentWorkState } from "./worker.js";
import { subagentWorkState } from "./worker.js";
const APPROVE_GOAL = "ApproveGoal";
@@ -13,19 +13,6 @@ function result(text: string, isError = false) {
}
export default function goalSupervisorRuntime(pi: ExtensionAPI): void {
let workerRegistration: { dispose(): void } | null = null;
pi.on("session_start", async (_event, ctx) => {
workerRegistration?.dispose();
workerRegistration = registerGoalWorker(pi.events, null);
ctx.ui.notify("Goal supervisor can now launch its retained worker.", "info");
});
pi.on("session_shutdown", async () => {
workerRegistration?.dispose();
workerRegistration = null;
});
pi.on("tool_call", async (event) => {
if (event.toolName === "edit" || event.toolName === "write") {
return { block: true, reason: "Goal supervision is read-only. Direct project changes to the nested goal-worker." };
-31
View File
@@ -7,7 +7,6 @@ const RPC_REPLY_PREFIX = "subagents:rpc:v1:reply:";
const RPC_VERSION = 1;
const RPC_TIMEOUT_MS = 15_000;
export const SUPERVISOR_AGENT = "goal-supervisor";
export const WORKER_AGENT = "goal-worker";
interface EventBus {
on(event: string, handler: (data: unknown) => void): () => void;
@@ -49,12 +48,6 @@ is clean, and you have explicitly inspected the plan, repository, evidence, and
Otherwise return continue or redirect the worker. Do not claim acceptance in prose: only ApproveGoal creates the durable
approval checkpoint. -- Pi/Codex`;
export const workerSystemPrompt = `You are the retained implementation worker for one goal supervisor.
Work autonomously from the approved plan. The latest human message outranks the plan. Keep the plan current, run the real checks, commit the implementation,
and leave specific evidence in its Log. Your goal supervisor owns direction and approval. Send contact_supervisor
progress updates when evidence changes the research direction, when an hourly check asks for one, or when you need a
decision. Do not claim a goal is complete; report the evidence and let the supervisor decide. -- Pi/Codex`;
export function registerGoalSupervisor(events: EventBus, model: string | null): Registration {
const supervisorRuntime = fileURLToPath(new URL("./supervisor-runtime.ts", import.meta.url));
const request: Record<string, unknown> = {
@@ -82,30 +75,6 @@ export function registerGoalSupervisor(events: EventBus, model: string | null):
return result.registration;
}
export function registerGoalWorker(events: EventBus, model: string | null): Registration {
const request: Record<string, unknown> = {
version: 1,
name: WORKER_AGENT,
definition: {
description: "Implementation worker directed by the retained goal supervisor.",
systemPrompt: workerSystemPrompt,
...(model ? { model } : {}),
systemPromptMode: "replace",
inheritProjectContext: true,
inheritGlobalContext: true,
inheritSkills: true,
defaultContext: "fork",
defaultAsync: true,
defaultProgress: true,
},
};
events.emit(REGISTER_EVENT, request);
const result = request.result as { ok?: boolean; registration?: Registration; error?: Error } | undefined;
if (!result) throw new Error("pi-subagents is not installed or not ready.");
if (!result.ok || !result.registration) throw result.error ?? new Error("pi-subagents rejected the goal-worker agent.");
return result.registration;
}
async function rpc(events: EventBus, method: "spawn" | "resume" | "steer" | "status", params: Record<string, unknown>, signal?: AbortSignal): Promise<RpcData> {
if (signal?.aborted) throw new Error("Goal-worker request aborted.");
const requestId = randomUUID();
+23
View File
@@ -0,0 +1,23 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
interface PackageManifest {
files: string[];
pi: { subagents: { agents: string[] } };
}
describe("packaged goal worker", () => {
it("exposes goal-worker through pi-subagents package discovery", () => {
const root = resolve(import.meta.dirname, "..");
const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")) as PackageManifest;
expect(manifest.files).toContain("agents");
expect(manifest.pi.subagents.agents).toEqual(["./agents"]);
const definition = readFileSync(resolve(root, "agents", "goal-worker.md"), "utf8");
expect(definition).toMatch(/^---\nname: goal-worker\n/);
expect(definition).toContain("tools: read, grep, find, ls, bash, edit, write, contact_supervisor");
expect(definition).toContain("defaultContext: fork");
expect(definition).toContain("retained implementation worker");
});
});
+5 -3
View File
@@ -1,6 +1,8 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { completeGoalDescription, planDrafting, planningState, resync } from "../src/prompts.js";
import { workerSystemPrompt } from "../src/worker.js";
const workerSystemPrompt = readFileSync(new URL("../agents/goal-worker.md", import.meta.url), "utf8");
describe("planning prompt", () => {
it("requires fact finding or a focused question before a goal", () => {
@@ -24,8 +26,8 @@ describe("planning prompt", () => {
expect(planDrafting).toContain("Take it from the original request, not from your implementation plan");
expect(planDrafting).toContain("Future work may not defer any artifact or action named there");
expect(resync("plan", ".pi/plan/test.md", "Compacted.")).toContain("amend the plan rather than preserving an obsolete decision");
expect(workerSystemPrompt).toContain("latest human message outranks the plan");
expect(workerSystemPrompt).toContain("goal supervisor owns direction and approval");
expect(workerSystemPrompt).toContain("human's latest message outranks the plan");
expect(workerSystemPrompt).toContain("main Pi agent is the research supervisor");
expect(completeGoalDescription).toContain("approval checkpoint only after it inspected");
});
});
+2 -11
View File
@@ -30,12 +30,6 @@ function setup() {
const hooks = new Map<string, any>();
const tools = new Map<string, any>();
const events = new Events();
let workerDefinition: Record<string, unknown> | undefined;
events.on("pi-subagents:runtime-agent-register:v1", (raw) => {
const request = raw as { definition: Record<string, unknown>; result?: unknown };
workerDefinition = request.definition;
request.result = { ok: true, registration: { dispose() {} } };
});
events.on("subagents:rpc:v1:request", (raw) => {
const request = raw as any;
events.emit(`subagents:rpc:v1:reply:${request.requestId}`, {
@@ -54,15 +48,13 @@ function setup() {
registerTool: (tool: any) => tools.set(tool.name, tool),
};
supervisorRuntime(pi as any);
return { cwd, ctx, hooks, tools, workerDefinition: () => workerDefinition };
return { cwd, ctx, hooks, tools };
}
describe("supervisor-only runtime", () => {
it("registers the nested worker and blocks direct supervisor writes", async () => {
it("blocks direct supervisor writes", async () => {
const runtime = setup();
try {
await runtime.hooks.get("session_start")({}, runtime.ctx);
expect(runtime.workerDefinition()?.description).toContain("Implementation worker");
expect((await runtime.hooks.get("tool_call")({ toolName: "edit", input: { path: "README.md" } }, runtime.ctx))?.block).toBe(true);
expect((await runtime.hooks.get("tool_call")({ toolName: "bash", input: { command: "git status && npm test" } }, runtime.ctx))).toBeUndefined();
} finally {
@@ -73,7 +65,6 @@ describe("supervisor-only runtime", () => {
it("writes an approval only after inspecting the plan and confirming a clean worktree at a commit", async () => {
const runtime = setup();
try {
await runtime.hooks.get("session_start")({}, runtime.ctx);
const planPath = join(runtime.cwd, ".pi/plan/session-a-v1.md");
mkdirSync(join(runtime.cwd, ".pi/plan"), { recursive: true });
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [/] goal: ship it\n - evidence: verify.log: PASS\n");
-21
View File
@@ -2,13 +2,11 @@ import { describe, expect, it } from "vitest";
import {
processWorkState,
registerGoalSupervisor,
registerGoalWorker,
resumeGoalSupervisor,
startGoalSupervisor,
steerGoalSupervisor,
subagentWorkState,
supervisorSystemPrompt,
workerSystemPrompt,
} from "../src/worker.js";
class Events {
@@ -52,25 +50,6 @@ describe("goal hierarchy registration", () => {
expect(definition?.subagentOnlyExtensions).toEqual([expect.stringContaining("supervisor-runtime.ts")]);
expect(supervisorSystemPrompt).toContain("nested goal-worker");
expect(supervisorSystemPrompt).toContain("ApproveGoal");
expect(workerSystemPrompt).toContain("retained implementation worker");
});
it("registers the implementation worker without nested supervisor tools", () => {
const events = new Events();
let definition: Record<string, unknown> | undefined;
events.on("pi-subagents:runtime-agent-register:v1", (raw) => {
const request = raw as { definition: Record<string, unknown>; result?: unknown };
definition = request.definition;
request.result = { ok: true, registration: { dispose() {} } };
});
registerGoalWorker(events, null);
expect(definition?.allowNestedSubagents).toBeUndefined();
expect(definition).not.toHaveProperty("subagentOnlyExtensions");
});
it("fails clearly when pi-subagents is absent", () => {
expect(() => registerGoalWorker(new Events(), null)).toThrow("pi-subagents is not installed or not ready");
});
});