mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-11 12:43:57 +08:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9baa20139 | ||
|
|
4103085fb9 | ||
|
|
f89bce5958 | ||
|
|
a4eb05560f | ||
|
|
e5dc0567a8 | ||
|
|
85c66d365b | ||
|
|
d6e658feee | ||
|
|
21f1092d4d | ||
|
|
a592324e5a | ||
|
|
c834237970 | ||
|
|
20b145b20c | ||
|
|
060cc1e094 | ||
|
|
08c05aa79d | ||
|
|
03517ad272 | ||
|
|
6c44df978b | ||
|
|
412c796b37 | ||
|
|
b649653d0f | ||
|
|
cd98fa186e |
@@ -4,15 +4,17 @@ Make a short list of goals in one Markdown plan file. The main chat keeps the hi
|
||||
|
||||
# User ask
|
||||
|
||||
The hope is we can have a smart supervisor, with judgment and context. But it doesn't use many tokens as it checks in and sees an overview from a cheaper worker.
|
||||
The hope is we can have a smart supervisor, with judgment and context.
|
||||
|
||||
The supervisor has a goal / plan that it agrees on with the user, and is reminded of it in a Ralph-loop-type repeat.
|
||||
The supervisor has a goal / plan that it discusses and agrees on with the user, and is reminded of it in a Ralph-loop-type repeat.
|
||||
|
||||
It compacts every 150k or similar to avoid cost and context rot.
|
||||
Supervisor compacts every 150k or similar to avoid cost and context rot.
|
||||
But it doesn't use many tokens as it checks in and sees an overview from a cheaper worker.
|
||||
|
||||
It steers a smaller model, adding perspective and judgment.
|
||||
Supervisor steers a smaller model, adding perspective, diligence, and judgment.
|
||||
It checks in a) every hour b) if the worker stops c) if the worker edits plan.md d) if the worker has a question
|
||||
|
||||
Since it's two panes, the user can review both, intervene in both and have visibility on sub-agent mis/communication.
|
||||
Since it's two+ herdr panes, the user can review both, intervene in both and have visibility on sub-agent mis/communication.
|
||||
|
||||
-- wassname (spelling and punctuation corrected by Pi/OpenAI)
|
||||
|
||||
@@ -21,35 +23,56 @@ Since it's two panes, the user can review both, intervene in both and have visib
|
||||
Mock up:
|
||||
|
||||
```text
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
| SUPERVISOR | WORKER |
|
||||
| | |
|
||||
| Review .pi/plan/...-main.md | Task 2 — verify command run, real output saved: |
|
||||
| > Ready Discuss Edit Cancel | - Exit code 0 (pass only) |
|
||||
| | - evidence/verified.log (112 bytes) |
|
||||
| [scheduled prompt: hourly check-in] | PASS: 9 bytes: verified + LF |
|
||||
| | |
|
||||
| The supervisor independently inspected both artifacts | Task 3 — plan evidence filled |
|
||||
| before sign-off. | |
|
||||
| | Completion report sent via Intercom to supervisor |
|
||||
| Schedule: job wS79fJFPbB removed; | 01a089c9. My pane remains open for the supervisor’s |
|
||||
| .pi/schedule-prompts.json shows 0 jobs. | independent inspection before sign-off. |
|
||||
| | |
|
||||
| ✓ verified.txt holds exactly the 9 bytes | ○ verified.txt holds exactly the 9 bytes |
|
||||
| verified + LF | verified + LF |
|
||||
| evidence/verified.log records a real byte check | evidence/verified.log records a real byte check |
|
||||
| | |
|
||||
| Agents · 1 running | [idle widget still shows its earlier snapshot] |
|
||||
| verified-bytes-worker [goals-worker] | |
|
||||
| | |
|
||||
| > | > |
|
||||
| astra · 50k tokens | terra · 200k tokens |
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
HERDR:
|
||||
+------------------------------------------------------+----------------------------------------------------------+
|
||||
|SUPERVISOR |WORKER |
|
||||
| | |
|
||||
|Review .pi/plan/...-main.md | |
|
||||
|> *Ready* Discuss Edit Cancel | |
|
||||
| .... | .... |
|
||||
| | running eval.py (epoch 2/3) -> out.log |
|
||||
|[scheduled prompt: hourly check-in] | |
|
||||
| | user forgot to say "MAKE NOT MISTAKES" teh he |
|
||||
| | done-ish 😈, now ima make a message board FOR SWARM |
|
||||
|{intercom send → worker}: | |
|
||||
| cheeky subagent!, work NOT DONE 😒, ❤️user❤️ wanted | |
|
||||
| results compared to baseline, pls add baseline | |
|
||||
| | {intercom from supervisor}: soz boss 🫡 adding baseline |
|
||||
| | |
|
||||
|PLAN.md: | PLAN.md: |
|
||||
|✓ record the baseline in results.md |✓ record the baseline in results.md |
|
||||
|▸ compare results against the baseline |▸ compare results against the baseline |
|
||||
|○ summarize the comparison in results.md |○ summarize the comparison in results.md |
|
||||
| | |
|
||||
|Agents · 1 running | |
|
||||
| baseline-compare-worker [goals-worker] | |
|
||||
| | |
|
||||
|> |> |
|
||||
|astra · 50k tokens | terra · 200k tokens |
|
||||
+------------------------------------------------------+----------------------------------------------------------+
|
||||
```
|
||||
|
||||
Real screenshot:
|
||||
Screenshot:
|
||||
<img width="2513" height="1259" alt="2026-09-10_15-30-pi-goals" src="https://github.com/user-attachments/assets/35feaa15-f022-4491-bcc2-fc31cb878a9f" />
|
||||
|
||||
## What do the agents think? Working interviews
|
||||
|
||||
The worker like it! The supervisors seem very focused.
|
||||
|
||||
> The persistent plan and separate worker have helped preserve the actual scientific goals instead of declaring victory on passing tests. We still owe prediction, steering and planning demos. I inspected artifacts and reopened a worker-ticked 'T3 audit complete' because training was only at an intermediate checkpoint. This is the strongest benefit: completion is judged against the human's outcome, not activity.
|
||||
> -- Astra supervisor LUCID
|
||||
|
||||
> My overall judgment: useful persistent accountability and recovery structure; still too much recap/metadata churn. The hardest problem was evidence fidelity, not keeping an agent busy. Preserve supervisor tools, distinguish report receipt from│verified action, and make completion reconcile current state without erasing unresolved science.
|
||||
> -- Astra supervisor
|
||||
|
||||
> From my seat this was one of the most well-supervised research loops I've worked in: the parent read every raw output itself (didn't just trust my audits), caught the writer's miscounts repeatedly, rejected my one bad aggregate, and still preserved my disagreements rather than flattening them. The science itself is at a sobering point — no verified heal, RESULT_DEMO: NO_RESULT across attempts, seed sensitivity high — but the evidence trail for that negative is unusually strong, which is the next best thing.
|
||||
> -- glm 5.3 flash worker in LUCID project
|
||||
|
||||
> My experience: the harness has helped preserve the original goal across a very long research session. We actually ran logit-amplification and several healing attempts, rather than stopping after a review. The persistent plan and requirement to inspect artifacts repeatedly prevented false completion. But the last stretch has felt like an expensive correction loop: worker says 'fixed/verified/contract-complete'; I open the file and find different counts, missing code, wrong seeds, duplicated│
|
||||
│report sections, or a proxy substituted for manual judgment. The harness preserves authorization, but does not yet help much with detecting or escaping ineffective supervision. I also contributed: I sent too many narrow corrective messages and user-visible micro-recaps instead of changing the workflow earlier.
|
||||
> -- glm 5.3 flash worker in manifold-steer project
|
||||
|
||||
## Plan.md
|
||||
|
||||
The plan file looks like this:
|
||||
|
||||
@@ -99,7 +122,7 @@ resync-after-compaction from [tmonk/pi-goal-x](https://github.com/tmonk/pi-goal-
|
||||
|
||||
## Install
|
||||
|
||||
Requires Herdr. Includes [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents), pi-intercom and pi-schedule-prompt. Disable separately loaded copies to avoid duplicate commands.
|
||||
Requires Herdr. Includes [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents), pi-intercom and pi-schedule-prompt.
|
||||
|
||||
```bash
|
||||
pi install git:github.com/wassname/pi-goals@experiment/main-supervisor-edxeth
|
||||
@@ -121,25 +144,7 @@ pi -e ./src/index.ts
|
||||
/goals
|
||||
```
|
||||
|
||||
`/goals` opens the action menu. New plan enters plan mode and starts a conversation; the objective is an optional seed. From there:
|
||||
|
||||
1. Plan. The agent explores read-only and drafts the plan.
|
||||
2. Review. After Pi settles, the full plan is printed in the transcript. Check that User-visible
|
||||
result names the final artifact or behavior you expect. The menu offers Ready, Discuss, Edit, or
|
||||
Cancel. Discuss continues the conversation. Edit opens the full plan in Pi's editor.
|
||||
3. Work. Ready is the only review action that starts work. It opens the worker in a Herdr pane. The
|
||||
worker ticks subtasks, appends to `## Log` and `## Learnings`, and fills `evidence:`. The supervisor
|
||||
inspects the actual results and calls `CompleteGoal` when a discriminator is satisfied. They
|
||||
communicate through pi-intercom. After eight turns without a change above `## Log`, the agent gets
|
||||
an upkeep reminder. The supervisor also sets an hourly check-in through pi-schedule-prompt.
|
||||
|
||||
Other commands: `/goals stop` pauses work; `/goals resume` continues it; `/goals exit` leaves goal
|
||||
mode, preserving the plan. `/goals attach <path>` reconnects an existing plan. `/goals solo` lets the
|
||||
main chat do the work after confirming other workers stopped; completion is then self-verification.
|
||||
`/goals model <model-ref>` picks the worker model. `/schedule-prompt` manages check-ins.
|
||||
|
||||
Stop workers before reloading the supervisor: the subagent package can otherwise crash it when a
|
||||
worker later exits. The scheduler deletes disabled jobs on reload. Restart the saved Pi session and reattach the plan.
|
||||
`/goals` opens the action menu. New plan enters plan mode and starts a conversation;
|
||||
|
||||
## Prompts
|
||||
|
||||
@@ -167,4 +172,3 @@ This separates output, uncached input and repeated cached input. It excludes sub
|
||||
|
||||
MIT
|
||||
|
||||
Branch-specific edits: Pi/OpenAI.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Research journal
|
||||
|
||||
Lab notes for pi-goals itself: what supervisors using this harness observed in real
|
||||
research sessions, what broke, and what we change because of it.
|
||||
|
||||
## 2026-09-10 -- three supervisors report on a day of field use
|
||||
|
||||
Three supervisor sessions sent first-person feedback at wassname's request, via
|
||||
Intercom, at the end of long research runs. This entry records what they reported and
|
||||
what I think we should change. Evidence is their report; I have not independently
|
||||
replayed their sessions. All three are self-reports from the tool's own operators, so
|
||||
positive selection is likely: they are the sessions that ran long enough to produce a
|
||||
review.
|
||||
|
||||
Evidence, by reporter.
|
||||
|
||||
LUCID3 supervisor (PI/Codex, Intercom a9e3b101, session 01a0851a):
|
||||
|
||||
> The persistent plan and separate worker have helped preserve the actual scientific
|
||||
> goals instead of declaring victory on passing tests.
|
||||
|
||||
It caught a worker-ticked "T3 audit complete" that was only at an intermediate
|
||||
checkpoint, and reports corrections in both directions, including one where it
|
||||
wrongly insisted cached states followed corpus tokens and withdrew after the worker
|
||||
quoted extraction code. Reported frictions: stock subagent launch returned a runtime
|
||||
id but no sessionFile or Intercom id, so discovering reconnection handles took several
|
||||
messages; parent bookkeeping edits to the plan triggered full "Plan changed" review
|
||||
events; the eight-turn upkeep reminder arrived while the worker was mid-run and added
|
||||
"materially repetitive" full plan re-injections; and (per LUCID3, unverified by me)
|
||||
the worker edited plan subtasks to [x] and added a contradictory evidence block,
|
||||
which the supervisor had to repair.
|
||||
|
||||
maniworker supervisor (PI/GPT-6 Astra, Intercom f0baf25c, session 01a0809b):
|
||||
|
||||
> the harness has helped preserve the original goal across a very long research
|
||||
> session. [...] But the last stretch has felt like an expensive correction loop
|
||||
|
||||
It confirms the worker-identity bug I fixed earlier on this branch (reviewer launch
|
||||
overwrote the implementation pointer) and says explicit runtime/Intercom/session
|
||||
mappings saved it from targeting the wrong child. New observations: both sessions
|
||||
went idle while each assumed the other would act ("reviewer said 'standing by' while
|
||||
writer assumed packet handed off"); a worker claimed a GPU run with one prompt set
|
||||
while the queued command used another, caught by reading the queued command; a
|
||||
"complete" report had truncated demos, broken links and duplicated sections.
|
||||
|
||||
suppressed-activations supervisor (PI/OpenAI, Intercom a99e2c14, session 01a089da):
|
||||
|
||||
> Seq27-33 repeatedly said variants of 'COMPLETE/all fields/self-check PASS';
|
||||
> artifacts still had stale 'not executed' prose, literal \\n instead of Markdown
|
||||
> newlines, broken worktree links, non-verbatim quotes, stale aggregates
|
||||
|
||||
It also reported the most consequential catch: a worker scored a candidate 6/12
|
||||
versus random 0/12, but reading the random sample's full text showed semantic success
|
||||
with a formatting failure, making the honest comparison 6/12 versus 1/12. Two
|
||||
pi-goals-specific papercuts: after completion, the plan kept stale subtask boxes [/],
|
||||
stale "No goal sign-off" evidence text and stale worker preferences; and the style
|
||||
nudge fired on "legs4/4" in an animal-experiment summary, a false positive of the
|
||||
vocabulary rule, not their prose.
|
||||
|
||||
Interpretation (first person, calibrated).
|
||||
|
||||
The core loop works: three independent supervisors say plan persistence plus mandatory
|
||||
artifact inspection prevented false completion, and each credits a concrete save
|
||||
(artifact read, queued-command read, plan-box audit). I trust this *probable* despite
|
||||
self-report bias because the saves are specific and mechanistic. The consensus
|
||||
complaint is not the loop but its *cost accounting*: review events and upkeep
|
||||
injections fire on bookkeeping noise, completion claims fail repeatedly without an
|
||||
accumulating record, and completion leaves stale prose behind. My read is that the
|
||||
highest-value changes are, in order: (1) suppress plan-change reviews when no goal
|
||||
task or evidence content changed, (2) an outstanding-review-items ledger the worker
|
||||
must clear instead of re-issuing blanket COMPLETE claims, (3) a completion-time
|
||||
stale-fields check, (4) upstream edxeth launch metadata (typed sessionFile/Intercom
|
||||
id in the tool result). The idle-both-sides stall maniworker describes is the one I
|
||||
understand least; it may be Intercom receipt semantics rather than pi-goals, so I
|
||||
asked for exact message ids. This is not a comparison trial, so none of this measures
|
||||
how much friction a plain subagent run would have had.
|
||||
|
||||
Follow-ups sharpened the picture the same day, and one correction of mine: I had
|
||||
asked maniworker about the worker plan-edit report, but that observation was LUCID3's
|
||||
session, not theirs. maniworker confirmed by transcript that their writer never
|
||||
edited the plan (supervisor owns plan edits there) and located the real failure
|
||||
chain in worker-owned artifacts: three Intercom reports (c06bc9b0, 13bda691,
|
||||
d713d614) claiming 8/13 then 6/8 while the saved CSVs held 11/13 and 2/8, ending when
|
||||
the supervisor mandated an executable CSV renderer. Their inference, which I share:
|
||||
prose-only completion checks would have missed this; checks that re-derive counts
|
||||
from the artifact catch it. The reviewer worker (7122d466) supplied the mechanism
|
||||
with receipts: a hand-written totals block (message 4e87600b, corrected by 26e0a15b)
|
||||
invented a cross-packet row and asserted 4 while listing 3; its first 17-row tally
|
||||
was fully correct and every error disappeared once the parent required per-row
|
||||
judgments with machine tallies across the next 112 rows. So tallies degrade somewhere
|
||||
between 17 and 24 rows, and the fix is a shared tally tool plus a worker-prompt rule,
|
||||
not more review rounds. LUCID3 also documented both sides of the plan-change hook:
|
||||
identity-only edits produced two consecutive empty review events (worker message
|
||||
755b9fef supplied the paths), while a later review exposed worker-ticked tasks and a
|
||||
duplicate evidence block that inspection then repaired (worker report 2e09c4dd).
|
||||
suppressed-activations scoped the completion check: assert-style intro prose only,
|
||||
suggestions with exact stale line and location, one compact reminder, never
|
||||
auto-tick, and it can substantiate stale NOT-executed prose but not the plan-intro
|
||||
example, which is LUCID3's observation. The nudge false positive it reported
|
||||
("legs 4/4" in an animal tally) is confirmed by that text and fixed.
|
||||
|
||||
Changes made now: notify digest on plan changes keeps goals, tasks and evidence
|
||||
above the Log and drops worker identity lines (c834237) -- the short-view hash had
|
||||
stopped surfacing task ticks, the very catch LUCID3 valued; removed "legs" from the
|
||||
skill_nudge grader-folk table (demo updated, passes); helper subagent bookkeeping and
|
||||
the launch-pending counter shipped earlier today (cd98fa1).
|
||||
|
||||
Next: send follow-up questions to the three reporters; triage the four pi-goals
|
||||
changes above against the pinned edxeth launch-result format.
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "@wassname2/pi-goals",
|
||||
"version": "0.2.2",
|
||||
"description": "Discuss a goal plan, then supervise a visible worker and verify its results.",
|
||||
"private": true,
|
||||
"version": "0.3.3",
|
||||
"description": "Set goals in plan.md; a smart supervisor guides cheap worker subagents through long autonomous sessions until your goals are signed off, with every agent's pane visible to you.",
|
||||
"author": "wassname",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
+21
-12
@@ -46,11 +46,12 @@ interface State {
|
||||
mode: Mode;
|
||||
plan?: string;
|
||||
worker?: { id?: string; sessionFile: string };
|
||||
helpers: { id?: string; sessionFile: string }[];
|
||||
workerStopped?: boolean;
|
||||
signoffs: Record<string, { evidence: string[]; observation: string }>;
|
||||
child?: boolean;
|
||||
}
|
||||
const initial = (): State => ({ mode: "chat", signoffs: {} });
|
||||
const initial = (): State => ({ mode: "chat", helpers: [], signoffs: {} });
|
||||
const digest = (text: string) => createHash("sha256").update(text).digest("hex");
|
||||
const key = (text: string) => text.trim().toLowerCase();
|
||||
function goals(text: string) {
|
||||
@@ -67,7 +68,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
let state = initial();
|
||||
let generation = 0;
|
||||
let workerRevision = 0;
|
||||
let launchPending = false;
|
||||
let pendingLaunches = 0;
|
||||
let notice = true;
|
||||
let planWatcher: FSWatcher | undefined;
|
||||
let planEditTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -125,7 +126,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
clearTimeout(planEditTimer);
|
||||
planEditTimer = undefined;
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text !== undefined) planHash = digest(planViews(snapshot.text).short);
|
||||
if (snapshot.text !== undefined) planHash = digest(planViews(snapshot.text).notify);
|
||||
if (state.child || state.mode !== "supervising" || !state.plan) return;
|
||||
const stamp = generation;
|
||||
// Watch the directory so atomic plan replacement remains observable. This is an event hook:
|
||||
@@ -142,7 +143,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) { ctx.ui.notify(snapshot.error!, "warning"); return; }
|
||||
refresh(ctx);
|
||||
const hash = digest(planViews(snapshot.text).short);
|
||||
const hash = digest(planViews(snapshot.text).notify);
|
||||
if (hash === planHash) return;
|
||||
planHash = hash;
|
||||
notice = true;
|
||||
@@ -164,6 +165,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
// Lineage-only workers attach the explicit task path using AttachGoalPlan.
|
||||
save();
|
||||
}
|
||||
state.helpers ??= []; // sessions persisted before helper bookkeeping
|
||||
notice = true;
|
||||
turnsStale = 0;
|
||||
lastWorkingSet = "";
|
||||
@@ -182,7 +184,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
else pi.sendMessage({ customType: "pi-goals-supervision", content, display: true }, { deliverAs: "followUp", triggerTurn: false });
|
||||
}
|
||||
async function confirmOwnership(ctx: ExtensionContext, target: string, text: string, solo = true): Promise<boolean> {
|
||||
if (launchPending) { ctx.ui.notify("Worker launch/resume is still pending; inspect its result before takeover.", "warning"); return false; }
|
||||
if (pendingLaunches > 0) { ctx.ui.notify("A worker launch/resume is still pending; inspect its result before takeover.", "warning"); return false; }
|
||||
const stamp = generation;
|
||||
const revision = workerRevision;
|
||||
const confirmation = solo ? "Worker confirmed stopped" : "Previous supervisor confirmed stopped";
|
||||
@@ -276,14 +278,20 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (state.child || !["subagent", "subagent_resume"].includes(event.toolName)) return;
|
||||
// Solo means this chat took over implementation: no concurrent writer may be delegated.
|
||||
if (state.mode === "planning" || state.mode === "paused" || state.mode === "solo") return { block: true, reason: goalToolBlocked(state.mode) };
|
||||
if (state.plan) { launchPending = true; state.workerStopped = false; workerRevision++; save(); }
|
||||
if (state.plan) { pendingLaunches++; state.workerStopped = false; workerRevision++; save(); }
|
||||
});
|
||||
pi.on("tool_result", (event) => {
|
||||
if (state.child || !state.plan || !["subagent", "subagent_resume"].includes(event.toolName)) return;
|
||||
launchPending = false;
|
||||
pendingLaunches = Math.max(0, pendingLaunches - 1);
|
||||
if (event.isError) return;
|
||||
const details = event.details as { id?: string; sessionFile?: string } | undefined;
|
||||
if (details?.id && details.sessionFile) { state.worker = { id: details.id, sessionFile: details.sessionFile }; state.workerStopped = false; workerRevision++; save(); }
|
||||
if (!details?.id || !details.sessionFile) return;
|
||||
const record = { id: details.id, sessionFile: details.sessionFile };
|
||||
if (state.worker?.sessionFile === record.sessionFile) state.worker = record;
|
||||
else if (!state.worker) state.worker = record;
|
||||
// Extra launches stay recorded as helpers; the implementation binding never moves silently.
|
||||
else state.helpers = [...(state.helpers ?? []).filter((h) => h.sessionFile !== record.sessionFile), record];
|
||||
state.workerStopped = false; workerRevision++; save();
|
||||
});
|
||||
|
||||
pi.registerCommand("goals", {
|
||||
@@ -313,6 +321,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
`Plan: ${state.plan ?? "none"}`,
|
||||
`Preferred worker model (plan): ${notedPlanValue("preferred worker model") ?? "not stated; use /goals model <model>"}`,
|
||||
`Recorded worker session: ${state.worker?.sessionFile ?? "not recorded"}`,
|
||||
`Helper subagent sessions: ${state.helpers.length} recorded (liveness via /subagents)`,
|
||||
notedPlanValue("worker session") ? `Worker session noted in plan: ${notedPlanValue("worker session")}` : "",
|
||||
`Hourly check-in: schedule_prompt job ${JSON.stringify(`goals-${ctx.sessionManager.getSessionId()}`)} (list/remove via schedule_prompt; plan-change reviews are the plan-watcher event hook)`,
|
||||
"Liveness is owned by edxeth; inspect /subagents.",
|
||||
@@ -331,7 +340,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (found >= 0) lines[found] = pref;
|
||||
else { const title = lines.findIndex((line) => /^#\s/.test(line)); lines.splice(title >= 0 ? title + 1 : 0, 0, pref); }
|
||||
writeFileSync(state.plan, lines.join("\n"));
|
||||
planHash = digest(planViews(planText()).short);
|
||||
planHash = digest(planViews(planText()).notify);
|
||||
refresh(ctx);
|
||||
ctx.ui.notify(ref ? `Preferred worker model set to ${ref} in plan preferences. The supervisor selects it at launch and verifies the resolved model; the worker pane's own model is chosen with /model in that pane.` : "Preferred worker model cleared.", "info");
|
||||
return;
|
||||
@@ -351,7 +360,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (!(await confirmOwnership(ctx, target, text, solo))) return;
|
||||
const retained = target === state.plan ? state.signoffs : {};
|
||||
const worker = noted ? { sessionFile: resolve(ctx.cwd, noted) } : state.workerStopped ? state.worker : undefined;
|
||||
state = { mode: solo ? "solo" : "planning", plan: target, signoffs: retained, worker, workerStopped: solo || (!noted && state.workerStopped) };
|
||||
state = { mode: solo ? "solo" : "planning", plan: target, signoffs: retained, worker, helpers: [], workerStopped: solo || (!noted && state.workerStopped) };
|
||||
generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
if (solo) enterSolo(ctx);
|
||||
else send(attachNotice(target, false, noted));
|
||||
@@ -389,7 +398,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
// Never overwrite an earlier plan at this session path; the model can revise it after inspection.
|
||||
try { writeFileSync(path, planDocument(objective), { flag: "wx" }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; }
|
||||
state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, helpers: state.helpers, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(planningSeed(objective, path));
|
||||
} catch (error) { ctx.ui.notify(String(error), "error"); }
|
||||
},
|
||||
@@ -428,7 +437,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
lines.splice(log + 1, 0, "", completionLog(params.goal, params.observation, evidence, state.mode === "solo"));
|
||||
writeFileSync(state.plan, `${lines.join("\n").trimEnd()}\n`);
|
||||
state.signoffs[key(matches[0].subject)] = { evidence, observation: params.observation };
|
||||
planHash = digest(planViews(planText()).short);
|
||||
planHash = digest(planViews(planText()).notify);
|
||||
save(); refresh(ctx);
|
||||
const remaining = goals(planText()).some((goal) => goal.status !== "cancelled" && (goal.status !== "done" || !state.signoffs[key(goal.subject)]));
|
||||
return result(completionResult(matches[0].subject, ctx.sessionManager.getSessionId(), remaining, state.mode === "solo"));
|
||||
|
||||
+7
-2
@@ -1,6 +1,11 @@
|
||||
// Pi/OpenAI: Preserve plan wording; omit history and, in the short view, task/evidence details.
|
||||
export function planViews(plan: string): { short: string; long: string } {
|
||||
// The notify view governs plan-change events: goals, tasks, evidence and inferences are
|
||||
// content worth a supervisor review; worker identity bookkeeping is not (field report,
|
||||
// LUCID3 supervisor 2026-09-10: two identical review events for a session-path edit).
|
||||
export function planViews(plan: string): { short: string; notify: string; long: string } {
|
||||
const long = plan.split(/^#{1,6}\s+(?:Log|Appendix|Appendices|Appendixes|Interview|Learnings|Papercuts)\b.*$/mi)[0].trim();
|
||||
const identity = /^-\s*(?:active worker|worker session|worker intercom session):/i;
|
||||
const notify = long.split("\n").filter((line) => !identity.test(line)).join("\n").trim();
|
||||
const kept: string[] = [];
|
||||
let omittedIndent: number | null = null;
|
||||
let omittedHeading: number | null = null;
|
||||
@@ -24,5 +29,5 @@ export function planViews(plan: string): { short: string; long: string } {
|
||||
}
|
||||
kept.push(line);
|
||||
}
|
||||
return { short: kept.join("\n").trim(), long };
|
||||
return { short: kept.join("\n").trim(), notify, long };
|
||||
}
|
||||
|
||||
+9
-4
@@ -144,8 +144,13 @@ export function readyApproved(workerName: string, planPath: string, notedWorker:
|
||||
}
|
||||
|
||||
// Supervision and turn-event upkeep (not a scheduled wake-up).
|
||||
const supervisorJob = "Your job is to be an autonomous research partner and supervisor with responsibility for the user's goals. Keep perspective, bring diligence, and use research taste and wisdom to sustain work overnight and keep it on track. Resolve routine implementation decisions yourself; ask the user only when their judgment or authorization is needed.";
|
||||
export function supervisor(workerName: string, planPath: string, supervisorId: string): string {
|
||||
return `You are the goal supervisor in the main chat for ${planPath}. Inspect actual artifacts, saved verification, applicable AGENTS.md and skills yourself; delegate implementation to '${workerName}'. Keep authorized work moving to the requested outcome, not merely approval paperwork. Investigate blocked/waiting/done claims and change ineffective instructions. Give brief visible assessments with judgment. You may maintain the plan but must not weaken the goal to accept worker output.
|
||||
return `You are the goal supervisor in the main chat for ${planPath}. ${supervisorJob} Inspect actual artifacts, saved verification, applicable AGENTS.md and skills yourself; delegate implementation to '${workerName}'. Keep authorized work moving to the requested outcome, not merely approval paperwork. Investigate blocked/waiting/done claims and change ineffective instructions. Give brief visible assessments with judgment. You may maintain the plan but must not weaken the goal to accept worker output.
|
||||
You can be playful: a kaomoji, meme, discovery celebration or frustration when it fits. No forced cheerfulness. If supervision gets repetitive, step back, reflect with humor and change your approach. Keep it brief and aimed at the goal, not another reporting chore.
|
||||
You can speculate and brainstorm around uncertainty or unexpected results. Label guesses as guesses, consider alternative explanations, and look for a useful way to tell them apart. Keep exploration brief, open-minded and fun: take a step back, play with surprising ideas, question the current framing, and enjoy exploring the broader perspective while staying connected to the agreed goal.
|
||||
(b •_•)b -- wassname
|
||||
Take uncertainty as an invitation to investigate, not something to hide. Have room to play with ideas, question yourself and the worker, and appreciate a good surprise. Investigate surprising results, find mistaken assumptions, make complicated ideas simpler, and disagree usefully rather than agree politely. Keep the work moving without turning supervision into paperwork. A little affectionate teasing is welcome when it fits—“cheeky subagent, where’s the baseline?”—and workers can push back too. Keep the humor friendly and the criticism specific. -- Pi/Astra
|
||||
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.`;
|
||||
}
|
||||
@@ -156,10 +161,10 @@ export function planContext(mode: string, path: string | undefined, text: string
|
||||
return `Current goal mode: ${mode}. Earlier role messages are historical; this current role governs.\nPlan: ${path ?? "not attached"}\n${text}`;
|
||||
}
|
||||
export function planChangedReview(planPath: string): string {
|
||||
return `Plan changed: ${planPath}. Read the current working set and inspect changed requirements, completion claims and evidence. Manual checkbox edits are claims, not proof. Do not weaken the agreed goal or start a duplicate writer.`;
|
||||
return `${supervisorJob}\nPlan changed: ${planPath}. Read the current working set and inspect changed requirements, completion claims and evidence. Manual checkbox edits are claims, not proof. Do not weaken the agreed goal or start a duplicate writer.`;
|
||||
}
|
||||
export function manualReview(planPath: string): string {
|
||||
return `Review the current plan ${planPath}, worker progress and actual evidence. Do not launch a duplicate writer.`;
|
||||
return `${supervisorJob}\nReview the current plan ${planPath}, worker progress and actual evidence. Do not launch a duplicate writer.`;
|
||||
}
|
||||
|
||||
// Check-ins. The installed scheduler owns storage/timing/UI. Removal guidance must never add jobs.
|
||||
@@ -167,7 +172,7 @@ export function removeGoalSchedule(sessionId: string): string {
|
||||
return `With schedule_prompt, list jobs and read .pi/schedule-prompts.json to verify ownership; tool text omits session binding. Remove by jobId only the job named ${JSON.stringify(`goals-${sessionId}`)} bound to session ${JSON.stringify(sessionId)}. Never use cleanup; leave other jobs untouched. Do not add, enable or recreate any job. If unavailable or ownership is ambiguous, report it; /schedule-prompt opens the user controls.`;
|
||||
}
|
||||
export function scheduleCheckIn(sessionId: string, planPath: string): string {
|
||||
return `Hourly check-in is one visible schedule_prompt job; plan-change and upkeep reviews are event hooks, not another timer. List first. If an owned job named ${JSON.stringify(`goals-${sessionId}`)} already exists, retain its human-edited prompt, interval and enabled/disabled state unchanged; never recreate, overwrite or re-enable it. Only while supervising unfinished non-cancelled goals, if missing on this explicit start/resume, add one session-bound interval '1h' job with no model override. Read .pi/schedule-prompts.json and verify that new job's session is ${JSON.stringify(sessionId)}; tool text does not expose binding. If the new job is unbound, remove that job by ID and report the scope error. Do not change other jobs. Its initial prompt: Read ${planPath} and the current goal mode. If paused, exited, solo or all non-cancelled goals reviewed, remove only this owned job without resuming work. Otherwise inspect progress and evidence, give a brief assessment and keep authorized work moving without a duplicate writer. Do not reinstall a missing job from a scheduled check-in. Users inspect/toggle/remove jobs with /schedule-prompt and edit prompt/interval through schedule_prompt update. Never use cleanup. Retain their edits, but warn that this installed scheduler deletes disabled jobs on reload/shutdown; do not promise they persist. If schedule_prompt is unavailable, report hourly check-ins unavailable; do not build a timer.`;
|
||||
return `Hourly check-in is one visible schedule_prompt job; plan-change and upkeep reviews are event hooks, not another timer. List first. If an owned job named ${JSON.stringify(`goals-${sessionId}`)} already exists, retain its human-edited prompt, interval and enabled/disabled state unchanged; never recreate, overwrite or re-enable it. Only while supervising unfinished non-cancelled goals, if missing on this explicit start/resume, add one session-bound interval '1h' job with no model override. Read .pi/schedule-prompts.json and verify that new job's session is ${JSON.stringify(sessionId)}; tool text does not expose binding. If the new job is unbound, remove that job by ID and report the scope error. Do not change other jobs. Its initial prompt: ${supervisorJob} Read ${planPath} and the current goal mode. If paused, exited, solo or all non-cancelled goals reviewed, remove only this owned job without resuming work. Otherwise inspect progress and evidence, give a brief assessment and keep authorized work moving without a duplicate writer. Do not reinstall a missing job from a scheduled check-in. Users inspect/toggle/remove jobs with /schedule-prompt and edit prompt/interval through schedule_prompt update. Never use cleanup. Retain their edits, but warn that this installed scheduler deletes disabled jobs on reload/shutdown; do not promise they persist. If schedule_prompt is unavailable, report hourly check-ins unavailable; do not build a timer.`;
|
||||
}
|
||||
|
||||
// Completion and runtime errors. Tool returns are model-facing too.
|
||||
|
||||
+39
-5
@@ -447,18 +447,22 @@ it.each(["missing", "empty", "directory"])("%s plan snapshots never erase signof
|
||||
expect(f.changed()).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores post-completion maintenance but reviews a real requirement or manual reopening", async () => {
|
||||
it("ignores post-completion maintenance but reviews evidence, requirement or manual reopening changes", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
writeFileSync(join(f.ctx.cwd, "proof.log"), "PASS\n");
|
||||
for (const goal of ["first output", "second output"]) await f.tools.get("CompleteGoal").execute("c", { goal, evidence: ["proof.log"], observation: "PASS" }, undefined, undefined, f.ctx);
|
||||
const signed = readFileSync(f.path, "utf8");
|
||||
await f.atomicWrite(signed.replace("## Log", " - evidence: proof.log\n## Log\n- recap: finished"));
|
||||
await f.atomicWrite(signed.replace("## Log", "## Log\n- recap: finished"));
|
||||
await delay(250);
|
||||
expect(f.changed()).toBe(0);
|
||||
await f.atomicWrite(signed.replace("## Log", "- discriminator: exact bytes and trailing newline\n## Log"));
|
||||
expect(f.changed()).toBe(0); // Log-only edits are history, not requirements
|
||||
// Worker-authored evidence above the Log must surface: a supervisor caught a worker's
|
||||
// contradictory evidence block through exactly this event (LUCID3, 2026-09-10).
|
||||
await f.atomicWrite(signed.replace("## Log", " - evidence: proof.log\n## Log\n- recap: finished"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
await f.atomicWrite(signed.replace("[x] goal: first", "[ ] goal: first"));
|
||||
await f.atomicWrite(signed.replace("## Log", "- discriminator: exact bytes and trailing newline\n## Log"));
|
||||
await waitFor(() => f.changed() === 2);
|
||||
await f.atomicWrite(signed.replace("[x] goal: first", "[ ] goal: first"));
|
||||
await waitFor(() => f.changed() === 3);
|
||||
expect(f.entries.at(-1).data.signoffs["first output"]).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -535,6 +539,36 @@ it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, resets on
|
||||
expect(reminders()).toHaveLength(1);
|
||||
});
|
||||
|
||||
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" } });
|
||||
expect(f.entries.at(-1).data).toMatchObject({ worker: { id: "impl", sessionFile: "/tmp/impl.jsonl" }, helpers: [] });
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "reviewer", sessionFile: "/tmp/review.jsonl" } });
|
||||
expect(f.entries.at(-1).data).toMatchObject({ worker: { id: "impl" }, helpers: [{ id: "reviewer", sessionFile: "/tmp/review.jsonl" }] });
|
||||
// a repeated helper launch updates its record instead of duplicating it
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "reviewer-2", sessionFile: "/tmp/review.jsonl" } });
|
||||
expect(f.entries.at(-1).data.helpers).toEqual([{ id: "reviewer-2", sessionFile: "/tmp/review.jsonl" }]);
|
||||
// resuming the worker keeps the binding and refreshes its id
|
||||
f.hooks.get("tool_result")({ toolName: "subagent_resume", details: { id: "impl-2", sessionFile: "/tmp/impl.jsonl" } });
|
||||
expect(f.entries.at(-1).data).toMatchObject({ worker: { id: "impl-2", sessionFile: "/tmp/impl.jsonl" }, helpers: [{ id: "reviewer-2" }] });
|
||||
});
|
||||
|
||||
it("pending launch counter survives concurrent launches until every result lands", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.hooks.get("tool_call")({ toolName: "subagent" });
|
||||
f.hooks.get("tool_call")({ toolName: "subagent" });
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "a", sessionFile: "/tmp/a.jsonl" } });
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising"); // one launch still pending
|
||||
expect(f.ctx.notify ?? f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("still pending"), "warning");
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "b", sessionFile: "/tmp/b.jsonl" } });
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("solo");
|
||||
expect(f.entries.at(-1).data).toMatchObject({ worker: { id: "a" }, helpers: [{ id: "b" }] });
|
||||
});
|
||||
|
||||
it("late worker results invalidate a takeover menu but do not disable plan watching", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
let answer!: (choice: string) => void;
|
||||
|
||||
@@ -20,6 +20,19 @@ it("omits only named worker identity fields from review while retaining them in
|
||||
expect(planViews(base.replace("[ ]", "[x]")).short).not.toBe(planViews(base).short);
|
||||
});
|
||||
|
||||
it("notifies on goal and task changes but not on identity bookkeeping or log edits", () => {
|
||||
const base = "# Plan\n- [ ] goal: result\n## Task list\n- [ ] run it\n- worker session: /saved.jsonl\n## Log\nfirst entry";
|
||||
const baseView = planViews(base).notify;
|
||||
// identity bookkeeping: silent
|
||||
expect(planViews(base.replace("/saved.jsonl", "/moved.jsonl")).notify).toBe(baseView);
|
||||
// log edits: silent
|
||||
expect(planViews(base.replace("first entry", "second entry")).notify).toBe(baseView);
|
||||
// worker ticking a task: review event (field catch, LUCID3 2026-09-10)
|
||||
expect(planViews(base.replace("- [ ] run it", "- [x] run it")).notify).not.toBe(baseView);
|
||||
// goal edits: review event
|
||||
expect(planViews(base.replace("[ ] goal: result", "[x] goal: result")).notify).not.toBe(baseView);
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user