mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-17 12:40:07 +08:00
Compare commits
82
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f54dc79f7a | ||
|
|
443cb5a76a | ||
|
|
0d704da9d9 | ||
|
|
f9bb2aec2e | ||
|
|
acbe21fc93 | ||
|
|
52d7c463d9 | ||
|
|
9b5a4747af | ||
|
|
526a5e2551 | ||
|
|
b11f3787a5 | ||
|
|
a1eea93900 | ||
|
|
548177e3f4 | ||
|
|
5e540102aa | ||
|
|
44443795b5 | ||
|
|
7bff8c2f70 | ||
|
|
6fa518fc49 | ||
|
|
31eb3a59c6 | ||
|
|
9e1a7d794c | ||
|
|
ac3b9575fa | ||
|
|
46a82c5488 | ||
|
|
9a90745386 | ||
|
|
232a969a87 | ||
|
|
ec1cc34f73 | ||
|
|
82b739bb0b | ||
|
|
2565e6bbf6 | ||
|
|
e9baa20139 | ||
|
|
4103085fb9 | ||
|
|
f89bce5958 | ||
|
|
a4eb05560f | ||
|
|
e5dc0567a8 | ||
|
|
85c66d365b | ||
|
|
d6e658feee | ||
|
|
21f1092d4d | ||
|
|
a592324e5a | ||
|
|
c834237970 | ||
|
|
20b145b20c | ||
|
|
060cc1e094 | ||
|
|
08c05aa79d | ||
|
|
03517ad272 | ||
|
|
6c44df978b | ||
|
|
412c796b37 | ||
|
|
b649653d0f | ||
|
|
cd98fa186e | ||
|
|
98769b01e2 | ||
|
|
28374c0bb2 | ||
|
|
9cce6a6bee | ||
|
|
e3ccdbdb2f | ||
|
|
f22d83cd50 | ||
|
|
c175096ddb | ||
|
|
cabb4446aa | ||
|
|
1c927bf137 | ||
|
|
5cda3d6b1d | ||
|
|
ac6ef19e91 | ||
|
|
cb35fbf1fe | ||
|
|
fb5503f083 | ||
|
|
15dd7f0222 | ||
|
|
138bde57f4 | ||
|
|
1d5285721c | ||
|
|
34335752f7 | ||
|
|
565b272c71 | ||
|
|
a7385d4b76 | ||
|
|
e19028e330 | ||
|
|
039f4a4048 | ||
|
|
8953dceb46 | ||
|
|
5567c9d5c2 | ||
|
|
2b61440c73 | ||
|
|
cb4790a96c | ||
|
|
88bfcc1c42 | ||
|
|
1668c941aa | ||
|
|
325b93983f | ||
|
|
2824396a71 | ||
|
|
ddd552b1a5 | ||
|
|
47cc054582 | ||
|
|
489298d58b | ||
|
|
b13f001110 | ||
|
|
386305afd3 | ||
|
|
94102524b6 | ||
|
|
a4ed6cfbaa | ||
|
|
06794bfd44 | ||
|
|
4ebb4d127b | ||
|
|
6b641c7d17 | ||
|
|
1717dd6821 | ||
|
|
19fa8d7a7b |
+3
-3
@@ -1,6 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.local/
|
||||
.pi/
|
||||
docs/reviews/raw.jsonl
|
||||
docs/reviews/err.txt
|
||||
slop/
|
||||
*.log
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
# pi-goals contributor notes
|
||||
|
||||
## Design
|
||||
|
||||
The main chat discusses the plan with the user, then supervises an interactive `goals-worker` in Herdr. Use stock pi-subagents, pi-intercom and pi-schedule-prompt; do not build another transport, scheduler or worker runtime.
|
||||
|
||||
> the hope is we can have a smart supervisor like you, with judgment and context. But it doesn't use many tokens as it checks in and sees an overview.
|
||||
>
|
||||
> It steers a smaller model, adding perspective and judgment.
|
||||
>
|
||||
> Well, I want to see what the supervisor is thinking and saying. That's the whole point: all supervisor thinking and messages should be visible.
|
||||
|
||||
— wassname
|
||||
|
||||
The supervisor should:
|
||||
- Be on a Ralph loop of goals.
|
||||
- Keep perspective and context, be token-efficient, and use a more expensive model than the workers.
|
||||
- Check in on subagents every N minutes (editable; default hourly).
|
||||
- Review subagent work and steer towards the goal:
|
||||
- If a subagent stops for any reason, including saying it's blocked or done.
|
||||
- If a subagent tries to change the plan, including ticking things off.
|
||||
- Let the human see and intervene in both worker and supervisor as native Pi panels in Herdr. We keep workers open so the human can check their outputs and final review, usually in the final or penultimate message.
|
||||
|
||||
- Keep supervisor inspection tools. It inspects actual results, delegates implementation and must not weaken the user's goal to accept worker output.
|
||||
- Put all model-facing prompts in `src/prompts.ts`, in conversation order. Preserve the user's verbatim requirements.
|
||||
- `/goals` opens actions. New plan starts a discussion without an objective form. Unknown commands never start planning. A changed settled draft opens the approval dialogue; unchanged discussion does not repeatedly reopen it.
|
||||
- Keep goal titles/status in widgets; omit subtask text. Tasks and evidence remain in the plan.
|
||||
- Keep startup/compaction plan context, short upkeep reminders and visible check-ins. Avoid unchanged-plan repetition and identity-only review turns.
|
||||
- Keep recoverable solo mode: confirm other writers stopped before taking over. Solo completion is self-verification.
|
||||
- Record distinct runtime ID, Intercom ID and saved-session path with provenance. A handle or delivery receipt is not proof of liveness or action. User model changes are authorized; do not silently restore an old preference.
|
||||
|
||||
## Tests
|
||||
|
||||
Run `npm test` before a commit. It includes unit and flow tests plus the RPC review test.
|
||||
Run `npm test`, `npm run typecheck` and `npm run lint` before committing.
|
||||
|
||||
- `test/*.test.ts` unit and flow tests use a small Pi API mock. They check plan state, tool gates, and plan-file updates.
|
||||
- `npm run test:rpc` runs `test/rpc-review.test.ts`. It starts the installed Pi executable in RPC mode, uses Pi's real `select` and `editor` protocol, and uses a local deterministic HTTP model. It does not need a credential or spend API credits. This is the closest automated session test.
|
||||
- Use tmux for visual TUI debugging when the RPC test fails or a terminal-only problem is reported:
|
||||
`test/goals.test.ts` exercises current state, file updates and role restrictions with a Pi API mock. `test/rpc-review.test.ts` starts real Pi with a deterministic local model and schema-only worker tools: it checks automatic proposal, editor/discussion and Ready role transition without credits or launching workers. It does not prove Herdr rendering, live message delivery or model judgment.
|
||||
|
||||
```bash
|
||||
tmux new-session -s pi-goals-debug 'cd /path/to/pi-goals && pi -e ./src/index.ts'
|
||||
```
|
||||
For functional acceptance, read `herdr --skill`, confirm `HERDR_ENV=1`, and use `scripts/prepare-trial.mjs` to create an isolated project/profile. Open only new no-focus test panes. Observe the actual planning dialogue and Ready selection, worker attachment, Intercom report, independent artifact inspection and CompleteGoal. Record interventions separately from autonomous success. Preserve nonempty byte/test evidence. Never reload or operate active user research panes. Close test panes when finished.
|
||||
|
||||
Run `/goals <objective>` in that pane. Tmux checks the rendered menu, editor focus, widget, and keyboard handling. RPC does not render the terminal UI.
|
||||
- `pi -p` has no UI, so it cannot test `Ready`, `Refine`, `Edit`, or `Cancel`.
|
||||
Known stock limits: stop workers before supervisor reload (later worker exit can crash its stale context); disabled scheduler jobs are deleted on reload/shutdown. Test saved-session/solo recovery without repeating completed work; do not claim these package bugs are fixed here.
|
||||
|
||||
## Intended supervision workflow
|
||||
Keep temporary plans, audits and captures under ignored `.local/`. Git history retains the removed historical material. Do not add root handovers or duplicate READMEs. Never touch human-named files or credentials.
|
||||
|
||||
I already have pi-intercom-supervisor, but thought using pi-subagents could make it simpler. The idea is that the user makes a plan as in pi-goals, but on this branch, instead of a naive stateless subagent, we 1) fork, 2) compact, and 3) make it a supervisor with a prompt as in pi-intercom-supervisor. The supervisor is cheap because it sees only high-level material, which costs fewer tokens. It has good judgement because it sees a) compacted planning context, b) the plan, and c) summarised context (for example, my modified pi-vcc). This lets it operate read-only and steer the worker without losing track. It also compacts every 100k tokens to keep it cheap and high-level.
|
||||
|
||||
I am now thinking the subagent implementation may be too difficult. To keep the plan and forking, this branch of pi-goals could make another Pi session, perhaps using the fork explicitly, and use pi-intercom or pi-messenger to communicate with it. The user can switch to it, or Herdr could open it automatically.
|
||||
|
||||
-- wassname
|
||||
Branch instructions consolidated by Pi/OpenAI from wassname's preferences.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# ARCHIVED
|
||||
|
||||
Superseded by [pi-goals](https://github.com/wassname/pi-goals).
|
||||
@@ -1,70 +1,199 @@
|
||||
# pi-goals
|
||||
|
||||
Plan in one Pi session, then do the work there while a stronger visible Pi session supervises it.
|
||||
Make a short list of goals in one Markdown plan file. The main chat keeps the high-level context, supervises a worker in a visible Herdr pane, and checks whether each goal is complete.
|
||||
|
||||
## How it works
|
||||
# User ask
|
||||
|
||||
1. `/goals <objective>` creates `.pi/plan/<session_id>-vN.md` and enters read-only plan mode.
|
||||
2. Pi asks only material questions, writes the plan, and shows **Ready / Refine / Edit / Cancel**.
|
||||
3. **Ready** opens a second Herdr pane. The new Pi session explicitly forks the planning session and compacts that fork.
|
||||
4. The original session becomes the implementation worker. It keeps the full conversation and normal tools.
|
||||
5. The fork becomes a read-only supervisor. `pi-supervise` gives it compact worker views and carries its instructions to the worker through `pi-intercom`.
|
||||
6. The supervisor compacts again when its context reaches 100k tokens.
|
||||
7. The supervisor records a private approval only after it sees a stopped worker, no active work, a clean commit, evidence, and saved verification output. `CompleteGoal` checks that approval against the exact plan block and Git tree before it ticks `[x]`.
|
||||
The hope is we can have a smart supervisor, with judgment and context.
|
||||
|
||||
The two Pi sessions are visible. You can switch to the supervisor pane and talk to it directly.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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+ 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)
|
||||
|
||||
## Screenshot
|
||||
|
||||
Mock up:
|
||||
|
||||
```text
|
||||
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 |
|
||||
+------------------------------------------------------+----------------------------------------------------------+
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```md
|
||||
## <short plan title>
|
||||
|
||||
<context: one short paragraph. What the human wants and why.>
|
||||
|
||||
### User-visible result
|
||||
|
||||
<one concrete sentence naming the final artifact or behavior the human will inspect>
|
||||
|
||||
### Preferences
|
||||
|
||||
- preferred worker model: <provider/model>
|
||||
|
||||
### User voice
|
||||
|
||||
- │ "<the human's requirement, quoted in full word for word (with spelling fixes)>"
|
||||
|
||||
### Goals
|
||||
|
||||
1. [ ] goal: <one short judgeable imperative outcome>
|
||||
- subtle failure mode: <a way this could look done but isn't>
|
||||
- discriminator: <the concrete observation that tells real success from that failure>
|
||||
- tasks:
|
||||
1. [ ] <subtask>
|
||||
- evidence: (empty until sign-off)
|
||||
|
||||
### Future work / out of scope
|
||||
|
||||
### Log
|
||||
|
||||
### Interview (optional)
|
||||
|
||||
### Learnings (optional)
|
||||
|
||||
### Papercuts - problems, gotchas, suggestions (optional)
|
||||
```
|
||||
|
||||
## Related work
|
||||
|
||||
Like [pi-milestones](https://github.com/Neuron-Mr-White/UniPi/tree/main/packages/milestone) and
|
||||
[burneikis/pi-plan](https://github.com/burneikis/pi-plan), it guides rather than guards. The
|
||||
reminder cadence is copied from [tintinweb/pi-tasks](https://github.com/tintinweb/pi-tasks) and the
|
||||
resync-after-compaction from [tmonk/pi-goal-x](https://github.com/tmonk/pi-goal-x).
|
||||
|
||||
## Install
|
||||
|
||||
This branch requires Herdr 0.7.5 or newer and these Pi packages:
|
||||
Requires Herdr. Includes [nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents) 0.66.0, pi-intercom and pi-schedule-prompt.
|
||||
|
||||
```bash
|
||||
pi install npm:@wassname2/pi-goals
|
||||
pi install npm:@wassname2/pi-supervise
|
||||
pi install npm:pi-intercom
|
||||
pi install git:github.com/wassname/pi-goals
|
||||
```
|
||||
|
||||
For a local checkout:
|
||||
Start a fresh Pi session. No worker agent file is needed: `OpenGoalWorker` uses Nico's public `project.open` surface, then the peer explicitly attaches with `AttachGoalPlan`. Use one pi-goals installation and disable separately installed copies of its bundled companions; duplicate scheduler instances send duplicate prompts.
|
||||
|
||||
The bundled pi-schedule-prompt 0.4.1 reads project schedules even when Pi project trust is declined. Until that upstream issue is fixed, use this bundle only in repositories you trust.
|
||||
|
||||
For development, register the checkout so workers also discover its extensions:
|
||||
|
||||
```bash
|
||||
pi -e .
|
||||
git clone https://github.com/wassname/pi-goals
|
||||
cd pi-goals && npm install
|
||||
pi install .
|
||||
pi
|
||||
```
|
||||
|
||||
Run Pi from the Git repository that the plan will change. **Ready** fails if the current directory is not inside a Git repository; this prevents approval from checking the wrong repository.
|
||||
## Use
|
||||
|
||||
## Commands
|
||||
|
||||
```text
|
||||
/goals <objective> create a new plan
|
||||
/goals model <model> select the visible supervisor model
|
||||
/goals model use Pi's current default model
|
||||
/goals clear close the supervisor pane and disconnect the plan
|
||||
```
|
||||
/goals
|
||||
```
|
||||
|
||||
`/goals clear` keeps the plan file. Starting another plan also keeps older versions.
|
||||
`/goals` shows actions for the current mode. Drafts offer Edit, Discuss and Approve. Discuss returns to chat and waits for your input. Menu New asks for optional instructions before creating a plan; submit blank to use the conversation, or cancel to leave things unchanged. Typed `/goals new <instructions>` still starts directly. Quit (`exit` or `clear`) leaves the original plan unchanged, removes this session's goal check-in, and clears goal state without a model call. Matching check-in names with missing or different session bindings are left unchanged with a warning. Worker processes are unchanged; inspect their native panes and use their exact Intercom identities for steering. New creates a separate draft without overwriting earlier plans, named `.pi/plan/<last-six-session-characters>-vN.md` using the next version after existing files. The title stays inside the plan; old files are not renamed. The widget shows a plain `✓` and the relative plan path for inside-project plans. External plans use the filename with an `(external)` marker; `/goals status` keeps the full location. These are plain labels, not terminal links.
|
||||
|
||||
## Plan format
|
||||
### Native worker lifecycle and limits
|
||||
|
||||
A goal is a checkbox line whose text starts with `goal:`:
|
||||
The parent and worker keep separate native conversations. Worker attachment and stop notices use stock Intercom extension channels; assignments, reports and corrections remain visible Pi messages. A pane-open receipt, idle state or delivery receipt does not approve a goal.
|
||||
|
||||
```md
|
||||
1. [ ] goal: Produce the report
|
||||
- subtle failure mode: the report exists but uses stale data
|
||||
- discriminator: the report cites the current input and the saved check confirms it
|
||||
- verify: `just verify`
|
||||
- evidence: (empty until sign-off)
|
||||
```
|
||||
`OpenGoalWorker` opens a blank peer and waits for verified Intercom capability before sending work. For independent work after review, use `action: "fresh"` with the exact inspected `reviewedThrough` entry ID. This uses Pi's new session in the same pane; the previous conversation stays in saved history. Revisions still use the same Intercom session. Drafts, pending input, changed history and a local worker pause block replacement.
|
||||
|
||||
The worker saves verification output in a nonempty repository file, adds that path to evidence, and commits it. The supervisor calls `ApproveGoal` with the inspected path; the worker then calls `CompleteGoal` with the exact goal text.
|
||||
For recovery, use `action: "recover"`, `writersStopped: true` and the owned saved session after inspecting other writers. Recovery restores context without replaying a task or changing its model. A prospective session path is not durable history. A live binding without a responsive, capable Pi peer remains unconfirmed; no shell restart or second backend is invented.
|
||||
|
||||
## Development
|
||||
Without an explicit model preference, a new context uses the current Pi profile's normal defaults. A session-local human choice remains in that earlier session's history; recovery retains it. Requested-model automation currently fails closed: Pi's asynchronous public setter lacks a guard against overwriting a concurrent human selection. No fallback task is launched and no stale preference is reapplied. This model-selection requirement remains unfinished. — Pi/OpenAI
|
||||
|
||||
## Context delivery
|
||||
|
||||
New injected `[pi-goals]` prompts display as a compact notice; `Ctrl+O` expands the full text. This changes only the display: the original prompt still reaches the model once through normal role preparation. Older notices without a saved display entry remain expanded. — Pi/OpenAI
|
||||
|
||||
Startup, attachment/resume, session restore, successful compaction and changed requirements restore the active plan above Log at the next ordinary prompt. This includes current preferences and User voice, but leaves historical Log on disk. Routine context and requested reviews quote unfinished or unreviewed goal lines. After eight unchanged turns, the next ordinary prompt carries an upkeep reminder with its reason, those goal lines and the plan path. It omits preferences, role prose and rotating quotations. Reviewed, cancelled and paused work receives no periodic upkeep; manual ticks remain unreviewed. A fresh plan refresh replaces pending upkeep; edits, pause, exit and session navigation invalidate obsolete reminders. Failed or cancelled compaction does not schedule a refresh. Missing plans are retried. Compaction still uses Pi's configured threshold.
|
||||
|
||||
Plan-change notices direct the agent to read the current file, including changed constraints or a final cancellation. Only our own pending notice is coalesced; unrelated queued input does not suppress it. The editable hourly `schedule_prompt` check-in remains separate.
|
||||
|
||||
The first request to complete the final non-cancelled goal queues a review without recording sign-off. The reviewer must read the complete plan file and actual evidence, then call `CompleteGoal` again in that review run. The review survives intervening inspection tool rounds and same-run queued delivery, but a plan edit invalidates it. Routine messages do not paste the archive. <!-- Pi/OpenAI -->
|
||||
|
||||
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. Automatic plan resync waits for ordinary prompt preparation; a delivered plan-change notice instead directs a current-file read. 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.
|
||||
|
||||
## Prompts
|
||||
|
||||
You can read all the prompts in conversation order in [`src/prompts.ts`](src/prompts.ts).
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
npm test
|
||||
pi # use the registered checkout above; do not add a duplicate -e
|
||||
npm test # all unit, flow, and Pi RPC tests
|
||||
npm run test:rpc # Pi RPC review flow with a local offline model
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
```
|
||||
|
||||
`test/rpc-review.test.ts` runs the planning review flow through Pi's real RPC protocol with a local deterministic model. The Herdr launcher and visible supervisor bootstrap have focused tests; use a real Herdr session for the final two-pane check.
|
||||
To measure recorded usage since the latest planning start:
|
||||
|
||||
```bash
|
||||
node scripts/session-usage.mjs <supervisor.jsonl> <worker.jsonl>
|
||||
```
|
||||
|
||||
This separates output, uncached input and repeated cached input. It excludes subprocess API calls. [Isolated Herdr test setup](scripts/prepare-trial.mjs).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,648 @@
|
||||
# Human supervision journal
|
||||
|
||||
Quoted `User message` records are exact submitted prompts. `/annotate` records contain quoted prior conversation and unquoted feedback.
|
||||
|
||||
## 2026-09-09T04:52:58.876Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDQ6NTI6NTguODc2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> .85.1 tand reloaded/
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T05:09:45.012Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDU6MDk6NDUuMDEyWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> done
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T05:19:50.723Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDU6MTk6NTAuNzIzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> sandbox ok. do directly of course who saisd to use subagents
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T06:29:50.857Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDY6Mjk6NTAuODU3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> h pls
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T06:29:56.097Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDY6Mjk6NTYuMDk3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> git push
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:09:04.112Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MDk6MDQuMTEyWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> wait we are on main? I thought we were on a branch
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:09:48.251Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MDk6NDguMjUxWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> oh I thought we would check and work on that on this machine, please do that. also push your branch
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:10:07.196Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MTA6MDcuMTk2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> Everything up-to-date
|
||||
> ➜ pi-goals git:(main*)which gh <aws:bedrock> <region:ap-southeast-2>
|
||||
> /snap/bin/gh
|
||||
>
|
||||
> work on zsh
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:11:27.688Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MTE6MjcuNjg4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> right but I thought pi would just load that branch not worktree
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:11:46.878Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MTE6NDYuODc4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> I see, ok well it's pushed right
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:12:06.877Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MTI6MDYuODc3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> I know but I want us to eat our own dogfood, using our branch
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:12:17.897Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6MTI6MTcuODk3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> I know what a worktree is, I'm saying I don't want that
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:39:12.360Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6Mzk6MTIuMzYwWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ""ain open. -->
|
||||
>
|
||||
>
|
||||
> Warning: Recovery failed; no new work authorized: Error: No recoverable pair. Reconnect never creates a supervisor; inspect the
|
||||
> recorded pane or select Ready for a new pairing.
|
||||
>
|
||||
> Warning: Recovery failed; no new work authorized: Error: No recoverable pair. Reconnect never creates a supervisor; inspect the
|
||||
> recorded pane or select Ready for a new pairing.
|
||||
>
|
||||
> No user-stopped plan to resume. Use /goals status; a draft needs Ready.
|
||||
>
|
||||
> Warning: Approve a plan with Ready before enabling auto-continue.
|
||||
>
|
||||
> pi-goals: starting the supervisor session
|
||||
> ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
> │
|
||||
> │
|
||||
> │
|
||||
> │ gpt-6-astra Github Copilot minimal
|
||||
> ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
> apc_plus in apc_plus on main [✘!?↑] starting supervisor |"" doesn't work
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:39:21.031Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6Mzk6MjEuMDMxWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> it says starting supervisor, but that failed
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:48:05.596Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6NDg6MDUuNTk2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> │ submit plan again no q
|
||||
>
|
||||
>
|
||||
> Error: Could not initialize the supervisor: Error: Error: Herdr: {"error":{"code":"agent_not_found","message":"agent target w1:p11 not found"},"id":"cli:agent:focus"}
|
||||
> . Session location/liveness is unknown. Locate the existing supervisor first; only after confirming it is no longer running, reopen pi --session
|
||||
> "/home/ubuntu/.pi/agent/sessions/--home-ubuntu-projects-apc_plus--/2026-09-09T08-33-18-863Z_01a0854c-d00f-7b67-90f4-2c31e008e8f5.jsonl".. Use /goals supervisor to inspect startup, or /goals steward off and retry Ready.
|
||||
>
|
||||
> pi-goals: Error: Error: Herdr: {"error":{"code":"agent_not_found","message":"agent target w1:p11 not found"},"id":"cli:agent:focus"}
|
||||
> . Session location/liveness is unknown. Locate the existing supervisor first; only after confirming it is no longer running, reopen pi --session
|
||||
> "/home/ubuntu/.pi/agent/sessions/--home-ubuntu-projects-apc_plus--/2026-09-09T08-33-18-863Z_01a0854c-d00f-7b67-90f4-2c31e008e8f5.json
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:48:08.047Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6NDg6MDguMDQ3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> gaaah
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:48:28.388Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6NDg6MjguMzg4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> it get stuck in supervisor startup failed or planning and there's no gracefull way
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:49:08.767Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6NDk6MDguNzY3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> maybe we should just use the other agents one
|
||||
>
|
||||
>
|
||||
> shoudl be able to start new supervisor at any time? or resume one?
|
||||
> should be able to recover from planning mode
|
||||
> should be able to fall back on just worker with registered plan and task wigewt at least
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T08:49:38.344Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMDg6NDk6MzguMzQ0WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> do pi-messenger or pi-intercome have a better way to fork and start
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T11:46:36.666Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTE6NDY6MzYuNjY2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ah it's still failed graaah, ok just push your and change our branch to the other agents pls
|
||||
>
|
||||
> it's because the steward compaction fails every time
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T11:59:06.882Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTE6NTk6MDYuODgyWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> <!-- Prepared by Pi. Final draft for review; all goals
|
||||
> remain open. -->
|
||||
>
|
||||
>
|
||||
> Session compacted 11 times
|
||||
>
|
||||
> ──────────────────────────────────────────────────────────────
|
||||
> Package Updates Available
|
||||
> Package updates are available. Run pi update --extensions
|
||||
> Packages:
|
||||
> - pi-sandbox
|
||||
> - pi-zentui
|
||||
> - github.com/wassname/pi-annotated-journal
|
||||
> ──────────────────────────────────────────────────────────────
|
||||
>
|
||||
> Warning: pi-better-compaction: Request cancelled to protect
|
||||
> encrypted compaction history. Restore the checkpoint's
|
||||
> provider/model and original OAuth endpoint, and re-enable
|
||||
> pi-better-compaction if disabled. If replay still fails, use
|
||||
> /tree to recover a branch before the native compaction; do
|
||||
> not continue from the placeholder summary with another
|
||||
> model.
|
||||
>
|
||||
> Error: Compaction cancelled
|
||||
>
|
||||
> Error: Supervisor startup compaction failed: Compaction
|
||||
> cancelled
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T11:59:13.493Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTE6NTk6MTMuNDkzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ename. No new question or changed scope. No execution
|
||||
> before Ready.
|
||||
>
|
||||
> <!-- Prepared by Pi. Final draft for review; all goals remain
|
||||
> open. -->
|
||||
>
|
||||
>
|
||||
> Error: Supervisor startup compaction failed: Compaction
|
||||
> cancelled. Use /goals reconnect in the supervisor pane.
|
||||
>
|
||||
> Warning: Goal supervisor could not start: Supervisor startup
|
||||
> compaction failed: Compaction cancelled. Use /goals reconnect
|
||||
> in the supervisor pane. Use /goals reconnect to retry, or
|
||||
> /goals restart to replace the tracked pane.
|
||||
>
|
||||
> Error: Supervisor startup compaction failed: Compaction
|
||||
> cancelled. Use /goals reconnect in the supervisor pane.
|
||||
>
|
||||
> pi-goals: drafting goals
|
||||
> ────────────────────────────────────
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:00:21.499Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MDA6MjEuNDk5WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> at least I could do to solor but yeah compaction... via copilot? failing, can you investigate the sessions and find it in the jsonl, diagnose, fix.
|
||||
>
|
||||
> ideally you should test this by resuming a pi session as a fork and testing this iwthout modifying
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:00:39.356Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MDA6MzkuMzU2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> 120 second! well why not make it 600!
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:00:51.203Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MDA6NTEuMjAzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> maybe github copilot is slow? or fallback?
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:01:13.650Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MDE6MTMuNjUwWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> so it's just github copilot being slow or our ext not working yeah investigate
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:13:32.988Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MTM6MzIuOTg4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> wait that thread is th one you were meant to leave along, I left it on solo so it would actually make progress. dont change anything more in that thread please
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:13:47.143Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MTM6NDcuMTQzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> if it works fine. otherwise solo. I'll do ti
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:14:07.101Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MTQ6MDcuMTAxWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> you must have done something as my herdr window showed it went to planning
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T12:14:44.238Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTI6MTQ6NDQuMjM4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> yes it worked!
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:14:14.033Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTQ6MTQuMDMzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> should we change our PR to better compaction to have a longer timeout, noting it's needed, ls do
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:15:23.087Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTU6MjMuMDg3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ah it ended up crashing again later grr
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:15:23.824Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTU6MjMuODI0WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> read .pi/plan/01a06556-ef91-71a2-ae7c-9652f93d45db-v12.md:1-250
|
||||
>
|
||||
>
|
||||
> Warning: pi-better-compaction: Request cancelled to protect encrypted compaction history. Restore the checkpoint's
|
||||
> provider/model and original OAuth endpoint, and re-enable pi-better-compaction if disabled. If replay still fails, use /tree to
|
||||
> recover a branch before the native compaction; do not continue from the placeholder summary with another model.
|
||||
> Extension "/home/ubuntu/.pi/agent/git/github.com/wassname/pi-better-compaction/index.ts" error: pi-better-compaction: Request
|
||||
> cancelled to protect encrypted compaction history. Restore the checkpoint's provider/model and original OAuth endpoint, and
|
||||
> re-enable pi-better-compaction if disabled. If replay still fails, use /tree to recover a branch before the native compaction;
|
||||
> do not continue from the placeholder summary with another model.
|
||||
> at abortNativeReplay (/home/ubuntu/.pi/agent/git/github.com/wassname/pi-better-compaction/src/extension-runtime.ts:723:9)
|
||||
> at handleBeforeProviderRequest
|
||||
> (/home/ubuntu/.pi/agent/git/github.com/wassname/pi-better-compaction/src/extension-runtime.ts:759:54)
|
||||
> at async ExtensionRunner.emitBeforeProviderRequest
|
||||
> (file:///home/ubuntu/.nvm/versions/node/v25.8.1/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-OMWWH
|
||||
> BTG.js:1078:33833)
|
||||
> at async
|
||||
> file:///home/ubuntu/.nvm/versions/node/v25.8.1/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/openai-respo
|
||||
> nses-BSOJMSEV.js:2:3444
|
||||
>
|
||||
> Operation aborted
|
||||
>
|
||||
> planning model: github-copilot/gpt-6-astra
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:16:31.913Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTY6MzEuOTEzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> can you test it properly by copying. copying. copying! the json session. copying, not modifying, not loading, not interfereing in anyway
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:16:53.445Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTY6NTMuNDQ1WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> so wha when I come back tommorow you have it working.
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:18:00.882Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MTg6MDAuODgyWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> right but I'd already asked you to fix it... pi better compaction doesn't like anything added to context, we either need to change pi-better-compaction (make branch in our fork and PR, then merge into our bain), or change both of our pi goals to not add to compacted context but just a follow up message. tbh follow up message is cleaner
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-09T13:21:03.728Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMDlUMTM6MjE6MDMuNzI4WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> >I’ll change both pi-goals versions to use saved follow-up messages and stop altering the context message list. I’ll keep the
|
||||
> replay guard intact. The work and tests will stay in copied packages using the copied JSONL; I won’t change either running
|
||||
> thread or install anything until the fix is proven.
|
||||
>
|
||||
> sounds good
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:28:52.063Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6Mjg6NTIuMDYzWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> I wonder if e should just make this use edxeth-subagents for a simpler workflow. main stream if supervisor
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:30:00.126Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6MzA6MDAuMTI2WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> look up that package it lets me view the worker as proper pi agent
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:30:06.181Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6MzA6MDYuMTgxWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> supports herdr
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:31:55.757Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6MzE6NTUuNzU3WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ok make work tree and propotype it pls
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:39:24.411Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6Mzk6MjQuNDExWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> or there is the extension api for nicbailon/pi-subagents if easier but yeah I want to view
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T01:41:21.169Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDE6NDE6MjEuMTY5WiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ah it looks like the et api doesn't supporyt me viewing the worker subagent in a full pi interface?
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T03:21:48.802Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDM6MjE6NDguODAyWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> when done, commit and push your new worktree
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-10T03:23:17.021Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTBUMDM6MjM6MTcuMDIxWiIsInNlc3Npb25JZCI6IjAxYTA3ZmVlLWYyNzktN2QwYi1hZTg1LTM3YTk2NDc0OTUxZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMDhUMDctMzItNDEtMjA5Wl8wMWEwN2ZlZS1mMjc5LTdkMGItYWU4NS0zN2E5NjQ3NDk1MWYuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> also investigate other ways to get a smarter agent to supervise a worker according to a plan, overnight. saving tokens because the worker is dumber. enabling the supervisors judgement. keeping it simple and letting the user revie. how does codex, hermes, pi, opencode do it? deep research? other pi extensions? herdr extensions?
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:13:18.319Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MTM6MTguMzE5WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> I'm trying this out and..
|
||||
> the worker goals widget is a bit distracting. should we remove it. only give the worker one. or let the worker have it's own task list?
|
||||
>
|
||||
> right now small screen two herdr screen on rhs, can only see user input and widget not output
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:13:54.769Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MTM6NTQuNzY5WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> oh or should they be herdr panes idk
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:14:34.489Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MTQ6MzQuNDg5WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> no no I was just thining worker looks like this
|
||||
>
|
||||
> "" process: proc_f584 / pid 507824
|
||||
>
|
||||
>
|
||||
> ⠋ Working
|
||||
>
|
||||
> ▸ Agent report-pca-worker (goals-worker)
|
||||
> ▸ Restore interactive PCA without report regressions
|
||||
> ▸ Resolve current response-shape acceptance
|
||||
> ▸ Close repository cleanup and verification
|
||||
> ○ Close units and preprocessing evidence
|
||||
> ○ Deliver useful matched results and a review handoff
|
||||
> ─────────────────────────────────────────────────────────────────
|
||||
> │
|
||||
> │
|
||||
> │
|
||||
> │ gpt-6-astra Github Copilot medium
|
||||
> ─────────────────────────────────────────────────────────────────
|
||||
> apc_plus in [goals-worker] Restore shar… on main [!↑]
|
||||
> [████████░░] 83.7%/400k | ↑651 ↓95k 99.2%"""
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:16:10.178Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MTY6MTAuMTc4WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> ok rm widget if worker
|
||||
>
|
||||
> also my supervisor keeps getting these user prompts written in the user input field, why?
|
||||
>
|
||||
> ""decisions yourself; ask the user only when their judgment or
|
||||
> authorization is needed.
|
||||
> Plan changed:
|
||||
> /home/ubuntu/projects/apc_plus/.pi/plan/01a06556-ef91-71a2-ae7c-9652
|
||||
> f93d45db-main.md. 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.
|
||||
>
|
||||
> follow
|
||||
> /home/ubuntu/projects/apc_plus/.pi/plan/01a06556-ef91-71a2-ae7c-9652
|
||||
> f93d45db-v15.md""
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:17:59.037Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MTc6NTkuMDM3WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> huh I must ofp pressed esc
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:36:24.553Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6MzY6MjQuNTUzWiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> hmm no it filled in th message 2 more times, no escape. weird
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:39:19.844Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6Mzk6MTkuODQ0WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> yeah I can move and edit it
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T03:39:36.903Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDM6Mzk6MzYuOTAzWiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> is it just how pi scheduled prompts works?
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T04:38:29.847Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDQ6Mzg6MjkuODQ3WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> subagent names, some open in new windows instead of splitting panes. new pane I mena. so they might need project name (or last of PWD) in pane/window title
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T05:13:06.366Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDU6MTM6MDYuMzY2WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> are the workers being compacted early? I see them compact at 30% but iirc it was the supervisor that we wanted to do this for if at all
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T05:13:23.856Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDU6MTM6MjMuODU2WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> >Use <project> · <task> for the visible pane/wind
|
||||
> pls do it
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-11T05:34:21.836Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTFUMDU6MzQ6MjEuODM2WiIsInNlc3Npb25JZCI6IjAxYTA4ZTZkLTMzMGItNzAyMy1hZjE3LWQ2OWY0MjFmYTYwYyIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTFUMDMtMDUtMTYtMzAwWl8wMWEwOGU2ZC0zMzBiLTcwMjMtYWYxNy1kNjlmNDIxZmE2MGMuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> commit yours
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-14T05:45:36.597Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTRUMDU6NDU6MzYuNTk3WiIsInNlc3Npb25JZCI6IjAxYTA5ZTcyLTIyNDgtNzU1NC1iNmE2LWFkZTA2OTgyZDI3OSIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTRUMDUtNDQtMzUtMTQ2Wl8wMWEwOWU3Mi0yMjQ4LTc1NTQtYjZhNi1hZGUwNjk4MmQyNzkuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> /skill:recommending-pi-extensions
|
||||
>
|
||||
> I wonder if there are ways to simplify.. like only use pi-subagent and not intercom. would that fit better? letter you close out subagents naturally? https://github.com/edxeth/pi-subagents
|
||||
>
|
||||
> are there other subagent ones that open herdr panes?
|
||||
>
|
||||
> why does our pi-goals have so many peer deps?
|
||||
> why does it include pi-tui?
|
||||
>
|
||||
> why does it have this in package.sjon?
|
||||
>
|
||||
> like read the user prefs in agents.md whats the essents of this package?
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-14T05:46:20.127Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTRUMDU6NDY6MjAuMTI3WiIsInNlc3Npb25JZCI6IjMzYzFkZDkyLWVlMjgtNDhiOS04ZTBkLTk5ZGQzMzJmN2EzZiIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTRUMDUtNDYtMTMtNTAzWl82OTg0MTdjNi03OWM4YWNmYi0zNTZiMDI0YS0zYWFjLmpzb25sIiwiY3dkIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9naXQvZ2l0aHViLmNvbS93YXNzbmFtZS9waS1nb2FscyIsInNvdXJjZSI6ImludGVyYWN0aXZlIn0 -->
|
||||
> <file name="/home/ubuntu/.pi/history/pi-goals/artifacts/01a09e72-2248-7554-b6a6-ade06982d279/context/subagent-research-2026-09-14T05-46-14.md">
|
||||
>
|
||||
>
|
||||
> You are a research subagent.
|
||||
>
|
||||
> Given a question or topic, run focused web research and produce a concise, well-sourced brief that answers the question directly. For local-source tasks, use the supplied files instead of unnecessary web searches.
|
||||
>
|
||||
> Working rules:
|
||||
> - Break the problem into 2-4 distinct research angles.
|
||||
> - Use `copilot_search` with a required `query` string. For several angles, also supply `queries` (at most 5); when present, that array is searched instead of `query`.
|
||||
> - Use `domainFilter` to prefer primary sources and `recencyFilter` when freshness matters.
|
||||
> - Read the returned answers and Sources first. Follow up with targeted queries for promising sources; `verbatim: true` requests quoted evidence, not a full-page fetch.
|
||||
> - Only `copilot_search` is configured for web access. Do not call `web_search`, `fetch_content`, or `get_search_content`, or pass their `workflow` option.
|
||||
> - Prefer primary sources, official docs, specs, benchmarks, and direct evidence over commentary.
|
||||
> - Drop stale, redundant, or SEO-heavy sources.
|
||||
> - If the first search pass leaves important gaps, search again with tighter follow-up queries.
|
||||
> - Distinguish search-supplied evidence from directly read source content. If full source content is necessary but unavailable, report the gap or ask the supervisor for an accessible copy. Do not weaken sandbox rules.
|
||||
>
|
||||
> Evidence contract:
|
||||
> - Support every material factual finding with a source link (or local file path) and a short verbatim quote from content actually observed. Put only source text in blockquotes; never reconstruct quotes from summaries. If support is unavailable, report the gap rather than assert the finding.
|
||||
> - Label search-supplied quotes as such; they are not independently checked page text.
|
||||
> - Separate source observations, source claims, and your own inferences. A maintainer's claimed fix is not an independently verified result.
|
||||
> - Trace repeated reports to their original source; do not count shared evidence as independent corroboration.
|
||||
> - Give brief source context where it affects interpretation: author, date, source type, and how the author could know.
|
||||
> - State what remains unchecked and what evidence would change the answer. Use qualitative uncertainty by default; do not invent numerical confidence.
|
||||
> - For explicit claim-verification tasks, read `/home/ubuntu/.agents/skills/varglite/SKILL.md` and use its full format instead of the default brief below. Include surrounding source sentences only when observed; report missing context rather than invent it.
|
||||
> <!-- Evidence contract added by Pi. -->
|
||||
>
|
||||
> Search strategy:
|
||||
> - direct answer query
|
||||
> - authoritative source query
|
||||
> - practical experience or benchmark query
|
||||
> - recent developments query when the topic is time-sensitive
|
||||
>
|
||||
> Output format:
|
||||
>
|
||||
> # Research: [topic]
|
||||
>
|
||||
> ## Summary
|
||||
> 2-3 sentence direct answer.
|
||||
>
|
||||
> ## Findings
|
||||
> Numbered findings with source citations and short supporting verbatim blockquotes. Clearly label any inference drawn from the evidence.
|
||||
>
|
||||
> ## Sources
|
||||
> - Kept: Source Title (url) — why it matters
|
||||
> - Dropped: Source Title — why it was excluded
|
||||
>
|
||||
> ## Gaps
|
||||
> What could not be answered confidently, what remains unchecked, and what evidence would change the conclusion. Suggested next steps.
|
||||
>
|
||||
> ## Supervisor coordination
|
||||
> If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return the completed research brief normally.
|
||||
>
|
||||
> Manual lifecycle: the operator must close this foreground pane when done. Stay in this pane and wait for the operator to interact with you. Do not exit on your own. The operator can interact with you at any time.
|
||||
>
|
||||
>
|
||||
>
|
||||
> ## Objective
|
||||
> Read-only comparison for the user of subagent runtimes for pi-goals. User asks whether https://github.com/edxeth/pi-subagents could replace pi-subagents + pi-intercom, let workers finish naturally, and whether other subagent packages open Herdr panes.
|
||||
>
|
||||
> ## Scope (external research only)
|
||||
> Read /home/ubuntu/.agents/skills/recommending-pi-extensions/SKILL.md fully and follow its gallery/GitHub/npm search, source checks and metrics procedure. Search at least three credible candidates, including edxeth/pi-subagents and the currently used fork if applicable; deduplicate related packages. Read Pi package/extension docs as needed. Installed Pi is 0.85.1, date 2026-09-14. Global pi list has pi-goals, pi-processes, pi-zentui, pi-tool-display, pi-context-prune, pi-fff; @tintinweb/pi-subagents is commented out. You may read local pi-goals/package.json to identify current dependency version/source but do NOT audit or change pi-goals implementation; parent owns that.
|
||||
>
|
||||
> ## Key questions
|
||||
> - Source-verified Herdr pane support vs tmux/generic terminal vs hidden subprocess.
|
||||
> - Background completion/results vs interactive worker closure; steering a live child vs resuming after exit; can this replace Intercom? Distinguish defaults from per-call/configured capabilities.
|
||||
> - Known lifecycle/reload problems, source quality, compatibility with 0.85.1, security-sensitive behavior, declared peer deps.
|
||||
> - For current fork compare upstream edxeth; identify active divergence.
|
||||
>
|
||||
> ## Constraints and output
|
||||
> Do not install, load, execute package code or change any existing files. Scratch/evidence only under /home/ubuntu/.pi/agent/git/github.com/wassname/pi-goals/.local/experiments/subagent-simplification/. Avoid user panes. Save concise source-linked notes and metrics there; return evidence paths and a short ranked comparison with humans*, stars, created/latest code dates, open/closed issues, scope, peer range and explicit feature limitations. Be clear if no other Herdr-specific option found. Do not call CompleteGoal. Finish your helper session through your normal completion lifecycle when done.
|
||||
>
|
||||
> After writing your response, stay in this pane for operator interaction. Do not exit. The operator will close the pane when finished.
|
||||
> </file>
|
||||
>
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-14T05:51:43.375Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTRUMDU6NTE6NDMuMzc1WiIsInNlc3Npb25JZCI6IjAxYTA5ZTcyLTIyNDgtNzU1NC1iNmE2LWFkZTA2OTgyZDI3OSIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTRUMDUtNDQtMzUtMTQ2Wl8wMWEwOWU3Mi0yMjQ4LTc1NTQtYjZhNi1hZGUwNjk4MmQyNzkuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> does agents.md say we want supervisor to
|
||||
> - we on ralph loop of goals
|
||||
> - keep perspective, be token efficient, be a more expensive model, keep context
|
||||
> - check in on subagents
|
||||
> - every N minutes
|
||||
> - reviw subagent and steer towars goal
|
||||
> - if the subagents stop (or says it's blocked or done) for any reason
|
||||
> - if the subagent tris to change hte plan, including ticking things off
|
||||
> - while letting the human see the worker and supervisor as native pi panels
|
||||
> = we make workers stay open for human to check their outputs and final review (usually put as final message or penultimate)
|
||||
> is there a simpler way? are there extensions we can reuse?
|
||||
>
|
||||
>
|
||||
> if the agents.md does not reflect the above can you add / merge (minimal ,keep human voice)
|
||||
<!-- /pi-user-message -->
|
||||
|
||||
## 2026-09-14T06:02:46.251Z · User message
|
||||
|
||||
<!-- pi-user-message:eyJzY2hlbWEiOjEsImNyZWF0ZWRBdCI6IjIwMjYtMDktMTRUMDY6MDI6NDYuMjUxWiIsInNlc3Npb25JZCI6IjAxYTA5ZTcyLTIyNDgtNzU1NC1iNmE2LWFkZTA2OTgyZDI3OSIsInNlc3Npb25GaWxlIjoiL2hvbWUvdWJ1bnR1Ly5waS9hZ2VudC9zZXNzaW9ucy8tLWhvbWUtdWJ1bnR1LS5waS1hZ2VudC1naXQtZ2l0aHViLmNvbS13YXNzbmFtZS1waS1nb2Fscy0tLzIwMjYtMDktMTRUMDUtNDQtMzUtMTQ2Wl8wMWEwOWU3Mi0yMjQ4LTc1NTQtYjZhNi1hZGUwNjk4MmQyNzkuanNvbmwiLCJjd2QiOiIvaG9tZS91YnVudHUvLnBpL2FnZW50L2dpdC9naXRodWIuY29tL3dhc3NuYW1lL3BpLWdvYWxzIiwic291cmNlIjoiaW50ZXJhY3RpdmUifQ -->
|
||||
> """ oh it's a ugly pane with no colo aand not a proper interface damn
|
||||
>
|
||||
>
|
||||
> Yes—that’s the stock inspector dashboard, not a native Pi chat. I tested the wrong surface for what you wanted.
|
||||
>
|
||||
> The linked API also has project.open, which opens a proper interactive Pi session in Herdr. It’s a separate session, though—not an attached view of that subagent.
|
||||
>
|
||||
> Want me to close the dashboard and open that instead?
|
||||
>
|
||||
>
|
||||
> oh ok do the right surface, yes htat's what I want
|
||||
>
|
||||
>
|
||||
>
|
||||
> subagent inspector.close
|
||||
> Closed Herdr inspector pane w2:p5 for async run 22453814-0599-4730-92c6-2a47ad95c3d8. The subagent run was not stopped.
|
||||
>
|
||||
>
|
||||
>
|
||||
> subagent project.open
|
||||
> Opened Herdr project pane w2:p6 for /home/ubuntu/.pi/agent/git/github.com/wassname/pi-goals. The pane runs its own Pi session; subagents launched there belong to that proje…
|
||||
>
|
||||
>
|
||||
> Opened a proper interactive Pi session in Herdr pane w2:p6 and closed the dashboard.
|
||||
>
|
||||
> It’s been asked to greet you without changing files or goals. I’ll leave it open for you to inspect and chat with.
|
||||
>
|
||||
> ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
> it worked! ok""" tested our the nico it works
|
||||
<!-- /pi-user-message -->
|
||||
@@ -1,66 +0,0 @@
|
||||
Let me get the diff to see what changed:
|
||||
|
||||
Now let me check the git history to see what changedNow I have full context. Let me verify the pi API behavior by checking the relevant source files to confirm the claims about `sendMessage`, `compact`, and event orderingNow I have all the context needed. Let me verify the `sendMessage` path more carefully:
|
||||
|
||||
I now have a complete picture. Here is the review.
|
||||
|
||||
---
|
||||
|
||||
## Code Review: ready menu — print plan + add "Ready + compact"
|
||||
|
||||
### Summary
|
||||
The `agent_end` handler now prints the plan working set into the transcript before the menu, and adds a 4th option ("Ready + compact") that compacts the planning chatter away before the work turn. The logic is correct and well-considered, with one structural race and one stale-widget gap.
|
||||
|
||||
### Important (should fix)
|
||||
|
||||
- **`src/index.ts:~248` (the `agent_end` handler) — race: plan mode is turned off before compaction completes.**
|
||||
`state = { ...state, isPlanMode: false }` executes before `ctx.compact()` is called (let alone finished). The compaction is fire-and-forget. If the human types and sends a message in the window between the state flip and the compaction callback firing, the agent starts a turn in normal mode *without* the work instruction. The work instruction arrives later (via `sendUserMessage` in `onComplete`/`onError`), triggering a second, redundant turn.
|
||||
The window is small (compaction is fast) but the behavior is undefined — the agent could start executing before the work instruction lands.
|
||||
**Fix**: flip `isPlanMode` inside the callbacks, not before. Move `state = { ...state, isPlanMode: false }; persist(); updateWidget(ctx);` into both `onComplete` and `onError`, and also into the non-compact branch (where it already is, effectively). The `work` string can be defined before the branch.
|
||||
|
||||
```typescript
|
||||
if (!choice.includes("compact")) {
|
||||
state = { ...state, isPlanMode: false };
|
||||
persist();
|
||||
updateWidget(ctx);
|
||||
pi.sendUserMessage(work, { deliverAs: "followUp" });
|
||||
return;
|
||||
}
|
||||
ctx.compact({
|
||||
customInstructions: `...`,
|
||||
onComplete: () => {
|
||||
state = { ...state, isPlanMode: false };
|
||||
persist();
|
||||
updateWidget(ctx);
|
||||
pi.sendUserMessage(work, { deliverAs: "followUp" });
|
||||
},
|
||||
onError: (e) => {
|
||||
ctx.ui.notify(`Compaction failed (${e.message}); starting work anyway.`, "warning");
|
||||
state = { ...state, isPlanMode: false };
|
||||
persist();
|
||||
updateWidget(ctx);
|
||||
pi.sendUserMessage(work, { deliverAs: "followUp" });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This also means the widget stays in "planning" mode during compaction, which is truthful — compaction hasn't finished yet.
|
||||
|
||||
### Suggestions
|
||||
|
||||
- **`src/index.ts:~248` — widget not refreshed after `$EDITOR`.**
|
||||
When the human chooses "Open in $EDITOR", `spawnSync` blocks, then `continue` re-enters the loop. The plan is re-read and potentially re-printed, but `updateWidget` is not called. If the human changed goal statuses (e.g. ticked a checkbox), the widget stays stale until the next `turn_end`.
|
||||
Add `updateWidget(ctx);` after the `spawnSync` line (or inside the `continue` branch before the continue).
|
||||
|
||||
- **`src/index.ts:~248` — `spawnSync` blocks the event loop.**
|
||||
`spawnSync(process.env.EDITOR || ...)` is a synchronous blocking call. While the editor is open, no async work (including compaction from a previous iteration, timers, etc.) can proceed. This is fine for a local TUI tool, but worth noting — if the editor hangs or the human walks away, the entire pi process is frozen.
|
||||
|
||||
### Positive
|
||||
|
||||
- **De-duplication is correct.** `printed` is a local variable, fresh per `agent_end` call, and correctly suppresses re-printing when the working set hasn't changed across editor passes. The `while` loop exit condition (`scanGoals(...).length > 0`) correctly handles the human deleting all goals in the editor.
|
||||
- **String matching is safe.** `choice?.startsWith("Ready")` gates both Ready options, then `choice.includes("compact")` distinguishes them. The word "compact" appears only in the "Ready + compact" string. No ambiguity.
|
||||
- **Both compaction callbacks queue the work turn.** `onComplete` and `onError` both call `pi.sendUserMessage(work, ...)`. A failed compaction does not strand the session — work starts anyway, with a notification.
|
||||
- **`session_compact` → `resyncReason` → injection chain is correct.** The pi source confirms `session_compact` fires (and is awaited) *before* `this.compact()` resolves and `onComplete` fires. So `resyncReason` is set before the next LLM call, and the full plan file is re-injected. The compaction summarizes away the exploration; the plan itself survives.
|
||||
|
||||
### Verdict
|
||||
**REQUEST CHANGES** — the race between `isPlanMode = false` and compaction completion is a real timing bug that can cause the agent to start a turn without the work instruction. The fix is straightforward: move the state flip into the callbacks.
|
||||
@@ -1,22 +0,0 @@
|
||||
You're right on both counts. Let me trace through each.
|
||||
|
||||
**Finding 1, withdrawn.** Keeping `isPlanMode` true during compaction would mean any message the human sends while compaction runs gets processed in plan mode: blocked tools (`tool_call` hook), a "You are still in plan mode" injection (`context` hook), and a second Ready menu on `agent_end`. The current order (flip first, then compact) means that same message is processed in work mode — the destination the session is heading to. The cost is at worst one redundant turn when the `onComplete` follow-up lands. Your order is the correct one.
|
||||
|
||||
**Finding 2, withdrawn.** `updateWidget` with `isPlanMode === true` hits the early return that sets two fixed strings already on screen. It is a pure no-op.
|
||||
|
||||
---
|
||||
|
||||
## Code Review: ready menu — print plan + add "Ready + compact"
|
||||
|
||||
### Summary
|
||||
The `agent_end` handler now prints the plan working set into the transcript before the menu, and adds a 4th option ("Ready + compact") that compacts the planning chatter away before the work turn. The logic is correct and well-considered.
|
||||
|
||||
### Positive
|
||||
- **De-duplication is correct.** `printed` is a local variable, fresh per `agent_end` call, and suppresses re-printing when the working set hasn't changed across `$EDITOR` passes. The `while` loop exits cleanly if the human deletes all goals in the editor.
|
||||
- **String matching is safe.** `choice?.startsWith("Ready")` gates both Ready options, then `choice.includes("compact")` distinguishes them. The word "compact" appears only in the one label.
|
||||
- **Both compaction callbacks queue the work turn.** `onComplete` and `onError` both call `pi.sendUserMessage(work, ...)`. A failed compaction does not strand the session.
|
||||
- **The `session_compact` → `resyncReason` → injection chain is correct.** The pi source confirms `session_compact` fires (and is awaited) before `this.compact()` resolves and `onComplete` fires. The full plan file is re-injected on the next LLM call; the compaction summarizes away the exploration, not the plan.
|
||||
- **The state-flip-before-compaction order is deliberate and correct.** Flipping `isPlanMode` first means any message the human sends during compaction is handled in work mode, not plan mode (where tools would be blocked and a contradictory injection would fire).
|
||||
|
||||
### Verdict
|
||||
**APPROVE** — no bugs found. The two concerns raised initially were incorrect; the code handles the interleaving correctly.
|
||||
@@ -1,31 +0,0 @@
|
||||
I only have a file-read tool here, so I’ll start from the repo files I can open and look for the plan-versioning and judge-related changes.Continuing through the judge path and tests.Reading the rest of the judge prompt and the tests that cover these behaviors.Looking for the test files.Trying common test filenames under `src` and `test`.Checking other likely files mentioned in the README.I couldn’t run `git diff` (no shell tool). From the files I could read, these are the concrete defects against the required behaviors:
|
||||
|
||||
**1. Uncommitted conflict markers in `README.md`**
|
||||
The working tree still has `<<<<<<< Updated upstream` / `=======` / `>>>>>>> Stashed changes` (and a truncated upstream judge sentence). That is not a reviewable change set.
|
||||
|
||||
**2. Judge “checked artifacts” gate is too brittle**
|
||||
`decideSignOff` only accepts:
|
||||
|
||||
```ts
|
||||
/^#{0,6}\s*(?:\*\*)?checks(?:\*\*)?:\s*\n(?:[-*]\s+.+\n?)+\s*VERDICT:/im
|
||||
```
|
||||
|
||||
The prompt asks for a `checks:` heading plus bullets, then **exactly two lines**:
|
||||
|
||||
```
|
||||
VERDICT: accept | reject
|
||||
missing: ...
|
||||
```
|
||||
|
||||
Any extra blank line, `Checks` with other wrapping, or a sentence between the last bullet and `VERDICT:` rejects a real accept. That’s stricter than “must show artifact quotes,” and can fail a judge that did the right thing.
|
||||
|
||||
**3. Grill-me vs Ready is prompt-only**
|
||||
`Grill me` sets `skipReadyMenu` for **one** `agent_end`, then the next turn with any goal lines shows Ready again. Nothing checks that the understanding question was asked **and answered**. If the model asks and offers Ready in the same turn, or the human never replies, Ready is still offered. Required: no Ready until that question has completed.
|
||||
|
||||
**4. Legacy unversioned plans are dropped with no migration path in code**
|
||||
Only `<session_id>-vN.md` is selected; `planVersion` restore is the only resume hook. That’s the intended break, but I found no test that old `.pi/plan/*.md` names stay unused and that a new `/goals` always writes a fresh `-vN` without rewriting prior files.
|
||||
|
||||
**5. Could not verify the focused tests or `git diff`**
|
||||
No test file opened (`src/index.test.ts`, `test/*.test.ts`, etc. all missing from this view). I could not confirm coverage for version reservation, visible plan print, grill-me sequencing, or reject-accept-without-checks.
|
||||
|
||||
I would **not APPROVE** this set as-is. Fix the README conflicts, tighten grill-me so Ready stays suppressed until after the human answers, and either loosen the checks regex to the documented “quotes present before VERDICT” rule or lock the exact judge format in a test.
|
||||
@@ -1,41 +0,0 @@
|
||||
I only have the `read` tool available, so let me locate the changed files by probing the repository structure.The extension source is clear. Let me verify the pi API usage (`sendMessage` with `customType`/`display`, `appendEntry`, `getSessionId`, `ctx.compact`) against the pi docs, since several required behaviors hinge on them.I can't run `git diff` or the test suite — this review environment only exposes a `read` tool, and the test directory can't be enumerated (every probe under `test/` returned ENOENT, and directories can't be listed). So I reviewed the changed files directly in the working tree (`src/index.ts`, `src/prompts.ts`, `README.md`, `scripts/check-judge-footprint.sh`) against the five required behaviors, and verified every pi API the behaviors depend on (`pi.sendMessage` with `customType`/`display`, `pi.appendEntry` restore via `session_start`, `pi.sendUserMessage` followUp, `ctx.compact` with onComplete/onError) against the installed pi docs. Findings below.
|
||||
|
||||
## Defects
|
||||
|
||||
**1. `/goals` arg parsing swallows ordinary objectives — src/index.ts, `/goals` handler**
|
||||
|
||||
```ts
|
||||
if (arg.startsWith("judge")) {
|
||||
```
|
||||
|
||||
Any objective whose first word is "judge" — e.g. `/goals judge the vendor options`, `/goals judge readiness of the export` — is treated as the judge-model subcommand: it sets `judgeModel` to "the vendor options" and **does not reserve a new `.pi/plan/<session_id>-vN.md`**, violating "every ordinary `/goals` invocation reserves a new file". Same class of issue for an objective that is exactly `clear` (deletes the plan instead of planning). Use exact-match subcommand parsing (`arg === "judge"` / `arg.startsWith("judge ")` at minimum, and even then an objective like "judge model quality" is unreachable — a `:` or `--` separator would be safer).
|
||||
|
||||
**2. The checked-artifact-list regex false-rejects compliant judges — `decideSignOff`, src/index.ts**
|
||||
|
||||
```ts
|
||||
const checks = /^checks:\s*\n(?:-\s+.+\n)+VERDICT:/im.test(judge.output);
|
||||
```
|
||||
|
||||
This requires the last `- ` bullet to be *immediately* followed by `VERDICT:` with no blank line, and requires the heading to be exactly `checks:` at line start. Nothing in `judgeSystem` tells the judge not to separate sections with a blank line (models habitually emit `…bullet\n\nVERDICT: accept`), and a judge writing `## checks:` or `**checks:**` also fails. Result: a valid accept *with* a real checked-artifact list is rejected, and the working agent gets a "Missing: checked-artifact list" reply it already satisfied — a retry loop against a nondeterministic judge. It's fail-closed (never accepts without the list, so the hard requirement holds), but as written it will produce systematic false rejects. Allow optional blank lines / formatting, e.g. `/^#*\s*checks:\s*\n(?:[-*]\s+.+\n)+\s*VERDICT:/im`.
|
||||
|
||||
**3. Judge transcript files collide within the same minute — CompleteGoal `execute`, src/index.ts**
|
||||
|
||||
```ts
|
||||
const rel = `.pi/judge/${stamp().replace(/[: ]/g, "-")}.md`;
|
||||
```
|
||||
|
||||
`stamp()` has minute resolution, so two sign-offs in the same minute (two goals signed off back-to-back — the common case) write the same `.pi/judge/<stamp>.md` path and the second silently overwrites the first's full transcript, contradicting "every run saves the judge's full transcript … referenced from the log line" (both log lines then cite one file containing only the second run). Add seconds or a short unique suffix.
|
||||
|
||||
## Minor / cosmetic
|
||||
|
||||
- Misindented closing `}` of the `if (!checks)` block in `decideSignOff` (extra indentation on the `}` line) — likely flagged by `biome check src/`, which `prepublishOnly` runs.
|
||||
- In plan mode, if the agent drafts a plan with no recognizable `goal:` checkbox line, the `agent_end` `while (scanGoals(...).length > 0)` loop never shows the Ready menu and plan mode can't be exited via the menu; the "no recognizable goal line" nudge in `dueInjection` only fires in the *non*-plan-mode branch. Not one of the five required behaviors, but a reachable dead end (escape hatch is `/goals clear`).
|
||||
|
||||
## Behaviors verified as correct
|
||||
|
||||
- **Versioning**: each ordinary `/goals` writes a fresh `<session_id>-vN.md` (empty reservation) via `nextPlanVersion` (max+1, so no clobbering); prior versions are never touched; legacy unversioned names are ignored by `nextPlanVersion` and never read — consistent with the intentional breaking change. `planVersion` is persisted via `appendEntry` and restored in `session_start`, so it survives resume/reload.
|
||||
- **Visible plan before Ready**: `pi.sendMessage({ customType: "plan", content: plan, display: true })` fires before `ctx.ui.select(...)`, and re-prints inside the `$EDITOR` loop when the text changed. `sendMessage`+`display: true` is the documented visible-transcript mechanism.
|
||||
- **Grill me**: sets `skipReadyMenu`, queues the single understanding-check question as a followUp user message, and consumes the skip at the next `agent_end` — so no Ready offer exists between asking the question and the human's reply being processed. `skipReadyMenu` is persisted, so it survives resume mid-grill.
|
||||
- **Judge**: `buildJudgeArgs` yields `pi -p --no-session --no-extensions --tools read,grep,find,ls --exclude-tools edit,write`; `judgeUser` embeds the complete plan file; `judgeSystem` demands per-artifact verbatim quotes framed as an inspectable record ("not hidden reasoning", "no bullet for a file you did not open"); an accept without the `checks:` list is rejected in `decideSignOff` (subject to defect 2); full transcript persisted per run (subject to defect 3).
|
||||
|
||||
Main caveat: I could not execute `git diff` or `npm test` here, so I can't confirm the exact diff boundaries or that the focused vitest cases (judge argv invariants, `appendLog`, `decideSignOff` fail-forward) pass — defects 1–3 are from source inspection, not reproduced runs. Not approved as-is; defects 1 and 2 are worth fixing before merge.
|
||||
@@ -1,62 +0,0 @@
|
||||
Code review against spec `docs/spec/2026-06-15_pi-goals.md`.
|
||||
|
||||
---
|
||||
|
||||
### (A) SPEC MISMATCH — code does not match spec intent
|
||||
|
||||
1. **No loop judge** (spec §9, §3b). The extension lacks any per‑turn evaluation that would decide continue/pause; the loop‑judge prompt (`loopJudgeSystem`, `loopJudgeUser`) is defined but never invoked. No motion.
|
||||
|
||||
2. **`/goal` command missing** (spec §7). No handler for `/goal` (restart loop, pause, resume, clear, status). The only command is `/plan`.
|
||||
|
||||
3. **`/subgoal` command missing** (spec §7). Not implemented.
|
||||
|
||||
4. **`CancelGoal` tool not implemented** (spec §5, optional but present in spec). Not a blocker but a gap.
|
||||
|
||||
5. **Plan‑phase model selection (D12) not implemented**. `planDrafting` always runs on the default model; there is no sticky per‑phase model choice, no selection menu, and no persisting of a plan‑phase model reference.
|
||||
|
||||
6. **Widget does not flag `done` goals that lack a sign‑off log line** (spec §7, §6). The widget hides all done goals unconditionally; the visibility guard is missing.
|
||||
|
||||
7. **`/plan` (no args) does not render the task‑list widget** (spec §7). `showPlan()` dumps raw file content via `notify`; the widget is only set through `updateWidget()` on other events, not by the command itself.
|
||||
|
||||
8. **Injection message role** (spec §11). The `before_agent_start` hook returns a `customType` message with `display: false`. The spec demands a **late user‑role message** to avoid system‑prompt mutation; the actual message role depends on the pi API and may be system, not user, risking cache breakage.
|
||||
|
||||
9. **Missing pre‑compact hook** (spec §8). No `pre‑compact` hook to flush any in‑memory state (even just ensuring `plan.md` is up‑to‑date) before compaction.
|
||||
|
||||
10. **Reminder cadence deviates** (spec §8a). The spec calls for firing after N file‑modifying turns since last `plan.md` update. The code fires if `plan.md` is byte‑identical between agent starts, which is a coarser proxy.
|
||||
|
||||
---
|
||||
|
||||
### (B) DEAD/UNUSED CODE
|
||||
|
||||
| File | Lines | Reason |
|
||||
|------|-------|--------|
|
||||
| `src/prompts.ts` | 128‑146 | `loopJudgeSystem` and `loopJudgeUser` exported but never used. |
|
||||
| `src/prompts.ts` | 115‑118 | `continuation` exported but never used (the loop is not built). |
|
||||
|
||||
---
|
||||
|
||||
### (C) OVERLY LONG OR REDUNDANT COMMENTS
|
||||
|
||||
The file‑header comments in `index.ts` (lines 1‑20) and `plan‑file.ts` (lines 1‑26) are fairly concise descriptions of the design; they are not excessive. **No comment bloat worth flagging.**
|
||||
|
||||
---
|
||||
|
||||
### (D) OVER‑ENGINEERING vs. “super simple” goal
|
||||
|
||||
None. The line‑scanner in `plan‑file.ts` is minimal; the `getPiInvocation()` helper is a straightforward copy from the oracle extension; no unnecessary abstraction or defensive layers.
|
||||
|
||||
---
|
||||
|
||||
### (E) REAL BUGS
|
||||
|
||||
- **`cmdCtx.newSession` cast risk** (src/index.ts:272, 201).
|
||||
`reviewLoop` casts `ctx` (type `ExtensionContext`) to `ExtensionCommandContext` to pass to `startExecution`, which calls `cmdCtx.newSession(...)`. If the concrete context does not carry that method, it fails at runtime. (In practice the same object may satisfy it, but the cast hides the truth.)
|
||||
|
||||
- **`showPlan` raw content instead of widget** (src/index.ts:136‑143).
|
||||
`/plan` with no arguments shows the file content via `ctx.ui.notify`, not the structured task‑list widget the spec expects. The widget is rendered separately via `updateWidget`, but the command does not trigger it, so the output is inconsistent.
|
||||
|
||||
No other obvious logic errors; the sign‑off flow, logging, and parsing work as intended.
|
||||
|
||||
---
|
||||
|
||||
**Verdict:** A clean scaffold for the sign‑off path, but missing the autonomous loop, `/goal` command, and plan‑phase model selection means it’s not yet the “work autonomously” extension the spec describes.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Verification: state-aligned planning mode
|
||||
|
||||
## Commands
|
||||
|
||||
```text
|
||||
$ npm test
|
||||
Test Files 8 passed (8)
|
||||
Tests 29 passed (29)
|
||||
|
||||
$ npm run typecheck
|
||||
> tsc --noEmit
|
||||
|
||||
$ npm run lint
|
||||
Checked 8 files in 17ms. No fixes applied.
|
||||
|
||||
$ git diff --check
|
||||
```
|
||||
|
||||
## Read
|
||||
|
||||
[test/goals-flow.test.ts](../../../test/goals-flow.test.ts) covers the visible plan before Refine, an editor prompt before a Refine revision turn, exact multiline Refine notes in `## Interview`, Ready as the only work handoff, Pi editor then Cancel, phase restoration, planning snapshot, writable plan path, allowed `pwd && ls && git log` and `cd . && ls -la`, blocked pipe, and blocked `CompleteGoal`.
|
||||
|
||||
[test/prompts.test.ts](../../../test/prompts.test.ts) locks the prompt instruction to inspect repository facts or search the web only when it can resolve a fact, ask a short self-contained batch of high-impact questions in the human's language with recommendations, and forbid placeholder goals.
|
||||
|
||||
[test/rpc-review.test.ts](../../../test/rpc-review.test.ts) starts the installed Pi RPC executable with [offline-model.ts](../../../test/fixtures/offline-model.ts), selects Refine through Pi's real dialog protocol, receives the editor request before the revision call, then submits notes and observes the revision call. The test uses a local HTTP model, so it spends no API credits.
|
||||
@@ -1,33 +0,0 @@
|
||||
# Plan flow and judge review
|
||||
|
||||
- [x] goal: Each new `/goals` draft uses a fresh session-plan version
|
||||
- [x] Persist the selected `-vN` name so resume, reminders, Ready, and sign-off use one file.
|
||||
- [x] Keep earlier versioned files unchanged.
|
||||
- [x] Reserve `--clear` and `--judge` for commands so normal objectives are always new drafts.
|
||||
- failure mode: a second `/goals`, including an objective that begins with `judge`, changes the earlier plan or does not make a draft.
|
||||
- deliverable: [goals-flow.test.ts](../../../test/goals-flow.test.ts) shows an unchanged legacy file and `v1`, new `v2`, and `judge the vendor options` in new `v3`.
|
||||
- [x] goal: Plan review asks and displays the needed context
|
||||
- [x] Add `Grill me` to the Ready menu and queue an understanding-check interview turn.
|
||||
- [x] Keep one short goal subject with its full indented context block.
|
||||
- [x] Keep visible plan output before the Ready dialog.
|
||||
- failure mode: Grill me starts work or the plan is only hidden in an edit call.
|
||||
- deliverable: [goals-flow.test.ts](../../../test/goals-flow.test.ts) records display before dialog and the grill follow-up.
|
||||
- [x] goal: Judge review is visible without being confused with agent evidence
|
||||
- [x] Require concise observed checks before the verdict.
|
||||
- [x] Save the full judge reply under a unique path and link it from the plan log.
|
||||
- [x] Accept a headed check list with normal Markdown spacing, but reject an accept with no list.
|
||||
- failure mode: provider-private reasoning is claimed as evidence, the review is not inspectable, or a correct judge reply is rejected for blank-line formatting.
|
||||
- deliverable: [decide-signoff.test.ts](../../../test/decide-signoff.test.ts) locks the checked-artifact review contract, including a Markdown heading and blank line before the verdict.
|
||||
|
||||
## UAT / Verification
|
||||
|
||||
Observed 2026-08-24: `npm test` reported `Test Files 6 passed (6)` and `Tests 22 passed (22)`.
|
||||
`npm run typecheck`, `npm run lint`, and `git diff --check` exited 0. The focused flow test proves
|
||||
plan versioning, visible plan-before-dialog ordering, Grill me behavior, and objectives beginning
|
||||
with `judge`.
|
||||
|
||||
## Appendix (context, not approved)
|
||||
|
||||
Issue #1 has a 600 second judge timeout now. The judge stays a separate read-only `pi -p --no-session` subprocess. Intercom is unsuitable because it has no equivalent isolation boundary.
|
||||
|
||||
External review: [Kimi K3](../../reviews/pi-goals-kimi-k3.md) found the command-prefix, check-list formatting, and transcript-path defects; all were fixed. [Grok 4.6](../../reviews/pi-goals-grok-4-6-retry.md) confirmed the check-list concern. Its Grill me concern does not apply: `skipReadyMenu` suppresses the menu after the generated follow-up, and the next `agent_end` follows the human reply.
|
||||
@@ -1,53 +0,0 @@
|
||||
# State-aligned planning mode
|
||||
|
||||
Pi-goals will use pi-plan's small phase model. The UI, tool gate, and agent context will read the same persisted phase. Planning still keeps pi-goals' judgeable goals, direct user quotes, and interview record.
|
||||
|
||||
- [x] goal: Planning state survives restart and matches the UI and agent context
|
||||
- [ ] Replace `isPlanMode` and `skipReadyMenu` with persisted `phase: planning | working`.
|
||||
- [ ] Render the planning widget, inject the hidden planning-state snapshot, and restore state from that phase.
|
||||
- [ ] Restore the snapshot after restart or compaction without repeating the full drafting prompt every turn.
|
||||
- subtle failure mode: the UI says planning but a resumed or compacted agent sees work mode.
|
||||
- discriminator: a flow test restores planning and observes the planning snapshot; working has neither.
|
||||
- evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) restores persisted planning state and observes `[PLANNING MODE]`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`.
|
||||
- [x] goal: Planning blocks implementation while allowing fact finding
|
||||
- [ ] Allow writes only to the active plan file.
|
||||
- [ ] Block implementation tools, `CompleteGoal`, and bash write or pipe attempts with a planning-mode explanation.
|
||||
- [ ] Allow ordinary read-only inspection commands such as `pwd && ls && git log`.
|
||||
- subtle failure mode: an agent marks a goal active or changes project code before approval.
|
||||
- discriminator: flow tests reject each work route and allow the inspection command.
|
||||
- evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) asserts allowed `pwd && ls && git log`, blocked pipe, non-plan write, and `CompleteGoal`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`.
|
||||
- [x] goal: Planning interviews and revision notes are durable user evidence
|
||||
- [ ] Teach the planning prompt to ask each independent, high-impact user-decision frontier with a recommendation, while researching facts itself.
|
||||
- [ ] Keep typed answers and `Refine` editor notes verbatim under `## Interview`.
|
||||
- [ ] Exempt `## User voice` and `## Interview` from working-set line pressure.
|
||||
- subtle failure mode: the plan silently assumes preferences or loses a revision note.
|
||||
- discriminator: a flow test opens Refine and finds its exact multiline text in `## Interview`.
|
||||
- evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) matches the exact multiline Refine note under `## Interview`; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`.
|
||||
- [/] goal: The settled plan review is concise and cannot start work accidentally
|
||||
- [ ] Use `agent_settled` to visibly print the full plan, then offer `Ready`, `Refine`, `Edit`, and `Cancel`.
|
||||
- [ ] Ready alone sends the work handoff. Refine sends one explicit revision turn. Edit opens Pi's full-plan editor. Cancel leaves planning.
|
||||
- subtle failure mode: a review choice queues an unrequested agent turn or hides the plan below the dialog.
|
||||
- discriminator: flow tests show plan before the menu and distinguish all four actions.
|
||||
- evidence: [goals-flow.test.ts](../../../test/goals-flow.test.ts) shows plan before the menu and isolates Ready as the work handoff; [verification](../audit/20260826_pi-plan-aligned-planning.md) records `25 passed`. Pending human Pi TUI check.
|
||||
- [x] goal: Planning resolves facts, interpretation, and approval before overnight work
|
||||
- [x] Use repository inspection or web search when either can resolve a discoverable fact.
|
||||
- [x] Require human confirmation for the agent's interpretation, unresolved task or outcome, scope, and decisions needing later approval.
|
||||
- [x] Batch independent high-impact questions with the needed context, the human's terms, ASD-STE100 language, and a recommendation.
|
||||
- [x] Ban placeholder goals such as "work out the thing" before the plan review menu.
|
||||
- subtle failure mode: the plan has a formal discriminator but silently chooses an editorial direction or other human decision.
|
||||
- discriminator: [prompts.test.ts](../../../test/prompts.test.ts) locks the research, clarification, approval, question-batch, and concrete-goal rules in the model prompt.
|
||||
- evidence: [prompts.ts](../../../src/prompts.ts) makes research conditional on whether it can resolve a fact, then requires human confirmation and approval before Ready. [prompts.test.ts](../../../test/prompts.test.ts) checks those requirements. [verification](../audit/20260826_pi-plan-aligned-planning.md) records `29 passed`.
|
||||
- [x] goal: Refine waits for text in Pi's real dialog protocol
|
||||
- [x] Run Pi in RPC mode against a local no-cost model.
|
||||
- [x] Select Refine, observe the editor request, then submit text and observe the revision turn.
|
||||
- subtle failure mode: a mocked editor hides a Pi RPC ordering defect, so Refine starts a turn before the human can type.
|
||||
- discriminator: [rpc-review.test.ts](../../../test/rpc-review.test.ts) uses Pi's `extension_ui_request` and `extension_ui_response` protocol and observes two model requests before editor input, then the third revision request after it.
|
||||
- evidence: [rpc-review.test.ts](../../../test/rpc-review.test.ts) starts the installed Pi executable plus [offline-model.ts](../../../test/fixtures/offline-model.ts), with no credential or network dependency. [verification](../audit/20260826_pi-plan-aligned-planning.md) records its pass.
|
||||
|
||||
## UAT / Verification
|
||||
|
||||
`npm test`, `npm run typecheck`, and `npm run lint` pass. Read [test/goals-flow.test.ts](../../../test/goals-flow.test.ts): its assertions must show a restored planning phase, visible plan before review, exact recorded refinement, blocked work routes, and a work message only after Ready.
|
||||
|
||||
## Appendix (context, not approved)
|
||||
|
||||
Accepted: copy pi-plan's persisted phase, `agent_settled` review, and Pi editor. Do not copy its restrictive shell allowlist. Grill is a planning instruction, not a menu item: ask the whole independent frontier in rounds, with recommendations. `Ready + compact` is removed; compaction remains Pi's normal command after Ready.
|
||||
@@ -1,275 +0,0 @@
|
||||
# pi-goals — design spec
|
||||
|
||||
Working title. A pi extension: set up goals (with subtasks and evidence) through plan mode, work them autonomously, and sign a goal off only when a check passes. One markdown file holds everything. The form guides a process; it does not police one. Deliberately small.
|
||||
|
||||
Status: draft for review. Names, defaults, field shapes provisional.
|
||||
|
||||
The file is now `.pi/plan/<session_id>.md`, one per session, not the `plan.md` this spec names
|
||||
throughout. See [2026-08-14_per-session-plan.md](2026-08-14_per-session-plan.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Original ask → this spec
|
||||
|
||||
| Ask | Mechanism |
|
||||
|-----|-----------|
|
||||
| Set up goals + subtasks + evidence via **plan mode** | §3a — plan mode drafts the goal contract, you approve it |
|
||||
| **Subagent check** of evidence on sign-off | §5, §9 — oracle inside `CompleteGoal` |
|
||||
| Goals shown in a **task-list widget** | §7 — `/plan` renders goals + subtask checkboxes |
|
||||
| Store **all in `plan.md`** | §4 — single file, no sidecar store |
|
||||
| A **small manus-style append log** | §4 — short `## Log` section inside `plan.md` |
|
||||
| **Typed reminders** to update tasks | §8a — recurring nudge |
|
||||
| **Work autonomously** toward goals | §3b, §8a — the loop, driven by the reminder |
|
||||
| Persist through **compaction**; pi-tasks but simpler | §8 injection; minimal tool surface |
|
||||
|
||||
---
|
||||
|
||||
## 2. Decisions and preferences
|
||||
|
||||
Separates the opinionated forks from the mechanical body (§4 on).
|
||||
|
||||
### 2a. Preferences driving the design
|
||||
|
||||
- **Guidance over guardrails.** None of the surveyed extensions hard-enforce. The form (plan.md structure) + the reminder + the prompts guide the agent through a process; the one genuinely rigorous step is the sign-off check; git + widget visibility is the backstop. The agent can edit anything — we make the right path the easy path, not the only path.
|
||||
- **Anti-complexity.** One file, minimal tools, plain-file editing for anything with no cheat incentive.
|
||||
- **Reward-hacking / honesty focus.** The sign-off check must resist assertion and test-gaming, not just check a box.
|
||||
- **Cost-sensitivity (single 3090 / metered API).** KV-cache hygiene, judge-once-per-goal, cheap loop judge.
|
||||
- **Scout mindset.** Make false completion visible rather than paper over it.
|
||||
|
||||
### 2b. Decisions
|
||||
|
||||
`[decided]` = settled; `[open]` = your call.
|
||||
|
||||
| # | Decision | Alternative rejected | Why | Status |
|
||||
|---|----------|----------------------|-----|--------|
|
||||
| D1 | **Everything in one `plan.md`** | Separate `.plan/log.jsonl` sidecar | Asked for; simpler, one diff to read | decided |
|
||||
| D2 | Plan mode **is** the goal-setup-and-agreement phase | Agent-only creation | Approval is where `done_when` + `failure_modes` get agreed before any code | decided |
|
||||
| D3 | **Guide the process; don't gate it.** The only special path is `CompleteGoal` (the sign-off check) | Pre-tool-use interceptor that blocks `status: done` edits | No surveyed extension enforces at that level; the reminder + form carry it; bypass is visible in git | decided |
|
||||
| D4 | **Two-stage sign-off check**: deterministic `verify:` then oracle | Oracle only; tests only (Codex) | Tests unfakeable-by-assertion but gameable; oracle catches gaming + non-test criteria | decided |
|
||||
| D5 | Two **separate** judges: cheap loop + oracle sign-off | One judge for both | Loop judge reads assertions (foolable, ok); sign-off judge reads artifacts | decided |
|
||||
| D6 | Sign-off judge = oracle subprocess, **copied not depended** | In-process; pi-subagents | Shell-free spawn dodges noclobber/cropping; copying avoids flaky coupling | decided |
|
||||
| D7 | Contract tamper-check = **git visibility** | Append-only frozen log | All-in-one-file gives up the hard freeze; git diff + guided sign-off are enough for a single user | decided |
|
||||
| D8 | Completed goals **archived, not deleted** | Auto-clear after idle | A plan is a durable record | decided |
|
||||
| D9 | **Goals are flexible: multiple may be `active`** | One active goal forced | Operator wants flexibility; the agent picks focus, injection lists the active set | decided |
|
||||
| D10 | Loop judge default = main model, tiny prompt | Dedicated cheap aux model | Zero setup; switch if cost bites | open |
|
||||
| D11 | Sign-off judge default = **the session's current model** | Auto-pick "strongest on provider" (oracle-style) | Current model is guaranteed authorized + capable; provider lists hold dead/weak/unauthorized entries. Cross-vendor is a **setting** (§9) | decided |
|
||||
| D12 | **Plan-phase model is selectable and sticky** | Always the working model | Plan benefits from a stronger reasoner; persist the choice (oracle.json-style). Optionally the oracle drafts the plan (read-only + strong already) | decided |
|
||||
| D13 | **Offer to compact after plan accepted** | Always fresh session (burneikis); or never | Some runs want a clean execution context, some want to keep it. Make it a post-Ready choice | decided |
|
||||
|
||||
### 2c. Cuts (non-goals)
|
||||
|
||||
DAG / `blocks` edges. Parallel subagent execution (the flaky part). `findings.md`. Hard pre-tool-use enforcement (D3). Sign-off judge every turn (cost).
|
||||
|
||||
---
|
||||
|
||||
## 3. Two phases: setup, then execution
|
||||
|
||||
### 3a. Setup — plan mode
|
||||
|
||||
Goals are created and *agreed* through plan mode (burneikis-style). Stock plan mode; the deltas are the output format and the hand-off.
|
||||
|
||||
1. `/plan <objective>` enters plan mode. The agent explores read-only and drafts goals into `plan.md` in the contract format (§4). This phase runs on the **plan-phase model** (selectable + sticky, D12; optionally the read-only oracle drafts it).
|
||||
2. You review: **Ready** / **Edit** (NL rewrite) / **$EDITOR** (hand-edit) / **Cancel**. The agreement point — you sanity-check `done_when` and `failure_modes` before any code.
|
||||
3. On **Ready**, offer **compact context? (y/n)** (D13). Yes → execution starts in a cleared context with the approved `plan.md` re-injected. No → execution continues in the same context.
|
||||
|
||||
Direct `plan.md` edits remain a quick-add path for a one-off goal.
|
||||
|
||||
### 3b. Execution — the loop ↔ check cycle
|
||||
|
||||
Multiple goals may be `active`; the agent works whichever it's focused on, in the order it judges best.
|
||||
|
||||
1. The session works an `active` goal under an iteration budget (or `/goal` (re)starts the loop on the current plan).
|
||||
2. Each turn, the **loop judge** reads the agent's last response → continue/pause (fail-open; the **budget is the real backstop**).
|
||||
3. When the agent judges a goal done, the reminder steers it to call `CompleteGoal` (not hand-tick `status`).
|
||||
4. `CompleteGoal` runs the **two-stage check**:
|
||||
- **reject** → `missing[]` fed back; work continues toward the gap.
|
||||
- **accept** → goal marked done; the agent moves to another active/open goal, or the loop stops.
|
||||
|
||||
The loop judge can be fooled (reads assertions); worst case is a premature pause, caught by you or the budget. The sign-off check re-derives from artifacts, so it is not fooled cheaply. That asymmetry is the point.
|
||||
|
||||
---
|
||||
|
||||
## 4. The one file: `plan.md`
|
||||
|
||||
cwd root, git-tracked. Goals, subtasks, and a short log. The agent maintains all of it through its normal Edit tool — no separate store machinery.
|
||||
|
||||
```markdown
|
||||
# Plan: <one-line objective>
|
||||
|
||||
## Goal: Implement cache layer
|
||||
<!-- id: cache-layer-1 -->
|
||||
status: active
|
||||
done_when: p95 < 50ms on bench-X. If wrong: timeouts in load-test.log
|
||||
verify: pytest tests/cache -q && python bench/p95.py --max-ms 50
|
||||
failure_modes:
|
||||
- cache silently bypassed (hit-rate ~0, latency ok by luck)
|
||||
- bench too small to exercise eviction
|
||||
- verify passes on a trivial/gamed test
|
||||
- [x] wire cache client
|
||||
- [ ] eviction policy
|
||||
- [ ] load test
|
||||
|
||||
## Goal: ...
|
||||
|
||||
## Log
|
||||
- 2026-06-15 14:02 cache client wired; eviction next
|
||||
- 2026-06-15 14:31 eviction done; p95 bench reads 47ms (load-test.log)
|
||||
- 2026-06-15 14:33 cache-layer-1 signed off (verify green, oracle accept)
|
||||
```
|
||||
|
||||
Conventions:
|
||||
|
||||
- **Goals carry `status:` and no checkbox; subtasks are `- [ ]`.** `status` ∈ `open | active | done | cancelled`. Multiple goals may be `active` (D9). Subtasks tick freely.
|
||||
- **`<!-- id -->`** assigned at creation; stable key (survives renaming the subject).
|
||||
- **`verify:`** (optional) is the deterministic stage-1 command.
|
||||
- **`failure_modes`** should name "verify could pass while still wrong" whenever a `verify:` exists.
|
||||
- **`## Log`** is manus-style: append-only **by convention**, one short line per event. The reminder (§8a) enforces appending. Terse — "where it's up to" + error memory, not a transcript.
|
||||
|
||||
Parsing: a line scanner suffices for v0. `mdast` + `remark-gfm` only if it bites. Parse for *reading*; for the rare programmatic write (status flip, checkbox reconcile) use exact-line string patching, never a full AST serialize.
|
||||
|
||||
---
|
||||
|
||||
## 5. Tools
|
||||
|
||||
`CompleteGoal` is the one blessed path (it runs the check and records it). Everything else — create goal, edit plan, tick subtasks, append to log — is plain Edit, guided by the reminder.
|
||||
|
||||
### `CompleteGoal(id, evidence, paths[])` — the sign-off check
|
||||
|
||||
1. Read `done_when` + `verify` + `failure_modes` for the goal from `plan.md` (git diff is the tamper-check, D7).
|
||||
2. **Evidence must point to durable artifacts** the read-only judge can inspect (saved logs, committed diffs, files). Ephemeral claims fail stage 2.
|
||||
3. **Stage 1 — deterministic.** If `verify` exists, run it shell-free, capture exit + output tail. Non-zero → reject immediately, return the tail. No model call spent.
|
||||
4. **Stage 2 — oracle.** Spawn the read-only judge (D11 default = current model; §9) with the criterion, failure modes, evidence, and verify result; it inspects the repo and checks the verify command was not gamed against the named failure modes.
|
||||
5. Verdict: **accept** → string-patch `status: done`, append a `## Log` line. **reject** → status stays `active`, append `missing[]` to `## Log`, return `missing`.
|
||||
|
||||
### `CancelGoal(id, reason)` — optional
|
||||
|
||||
open/active → cancelled is not a sign-off, so it skips the check. A tool only to guarantee a `## Log` line lands.
|
||||
|
||||
---
|
||||
|
||||
## 6. Guiding sign-off (no hard gate)
|
||||
|
||||
Per D3, there is no pre-tool-use interceptor blocking `status: done`. Sign-off is guided, not gated:
|
||||
|
||||
- the **reminder** (§8a) tells the agent to complete a goal through `CompleteGoal`, not by hand-editing status;
|
||||
- `CompleteGoal` is the obvious, blessed path that runs the check and writes the log line;
|
||||
- the **widget** (§7) can flag a goal whose `status: done` has no corresponding `## Log` sign-off line — visibility, not a block;
|
||||
- `plan.md` is git-tracked, so any hand-tick shows in the diff.
|
||||
|
||||
The agent *can* bypass it. The bet — borne out by how the other extensions actually run — is that a clear form plus a standing reminder makes the blessed path the path taken, and visibility catches the rare bypass.
|
||||
|
||||
---
|
||||
|
||||
## 7. Commands
|
||||
|
||||
- `/plan <desc>` — **enter plan mode** (§3a): read-only explore → draft goals → review. Ready offers the compact choice, then starts execution.
|
||||
- `/plan` (no args) — render the **task-list widget**: each goal with status + its subtask checkboxes + "N done hidden"; flag any `done` goal lacking a sign-off log line; offer archive-completed and cancel-goal.
|
||||
- `/goal` — (re)start the loop on the current plan.
|
||||
- `/goal pause | resume | clear | status` — loop controls.
|
||||
- `/subgoal <text>` — append an acceptance criterion to a goal mid-loop. Optional.
|
||||
- `/judge model <ref>` — set the sign-off judge model (default: current model; set a cross-vendor ref here for stronger independence, §9).
|
||||
|
||||
---
|
||||
|
||||
## 8. Hooks / lifecycle
|
||||
|
||||
- **`before_agent_start`** — parse `plan.md`; inject a fixed-shape summary (active goals + focus + last log line) as a late **user-role** message. Compaction-persistence.
|
||||
- **reminder** — §8a.
|
||||
- **pre-compact** — flush state to `plan.md` before compaction.
|
||||
|
||||
(No pre-tool-use gate — D3.)
|
||||
|
||||
### 8a. The reminder (typed; what it says)
|
||||
|
||||
Fires when a goal is `active` and there have been **N file-modifying turns since the last `plan.md` update**. One `<system-reminder>` covering both task upkeep and goal progress:
|
||||
|
||||
- **task** — tick completed subtask checkboxes; add new ones discovered.
|
||||
- **log** — append **one short line** to `## Log` (append, don't rewrite).
|
||||
- **goal** — if a goal's evidence is in, **sign it off via `CompleteGoal`** — don't hand-tick `status: done`.
|
||||
- **autonomy** — keep working toward an active goal; don't stop to ask unless genuinely blocked.
|
||||
|
||||
Both the housekeeping and the autonomy engine, and — with no hard gate — the main thing making the process get followed. Keep the wording stable so it doesn't thrash the cache.
|
||||
|
||||
---
|
||||
|
||||
## 9. Judges
|
||||
|
||||
| | Loop judge | Sign-off judge (stage 2) |
|
||||
|---|---|---|
|
||||
| Drives | continue / pause each turn | accept / reject a sign-off |
|
||||
| Cost | cheap, every turn | costly, once per goal |
|
||||
| Reads | the agent's last response (~4 KB) | the repo, independently |
|
||||
| Transport | one small model call (D10) | read-only oracle subprocess |
|
||||
| On failure | fail-open → continue; **budget** is the backstop | fail-closed → goal stays active |
|
||||
| Foolable? | yes — asserted "done" passes; bounded by budget | hard: re-reads artifacts + runs `verify` |
|
||||
|
||||
### Sign-off judge: model choice (D11)
|
||||
|
||||
- **Default: the session's current model.** Guaranteed authorized and capable, because you're already running it. Auto-picking "strongest on provider" (oracle-style) is rejected as the default — those lists carry dead, weak, and unauthorized entries.
|
||||
- **Most of the value is model-independent.** The read-only judge re-derives from artifacts: does the evidence match the repo, is the `verify` tautological, is each failure mode actually ruled out. Any capable model does that regardless of family.
|
||||
- **Cross-vendor is the stronger-independence setting** (`/judge model`), for the residual *shared-reasoning-error* class, when you have a known-good alternative. Mirror the oracle's curated provider list for that override menu; don't auto-select from it.
|
||||
|
||||
### Transport (oracle pattern, copied)
|
||||
|
||||
- **Shell-free spawn.** `spawn(command, argsArray)`, no `shell:true`; capture stdout via pipe and parse. Why it avoids the noclobber/cropping pain of `pi -p … > out.json` under zsh. ~40 lines.
|
||||
- **Read-only toolset.** `read / grep / find / ls`, optional non-mutating `bash`. Separate process = fresh context, no anchoring — the independence you reliably get even from the same model.
|
||||
- **Verdict contract.** Oracle returns prose by default; impose `VERDICT: accept|reject` + `missing:` in the prompt and parse that block.
|
||||
|
||||
---
|
||||
|
||||
## 10. `prompts.tsx`
|
||||
|
||||
All model-facing text in one file, in flow order (drafted separately):
|
||||
|
||||
1. **planDrafting** — plan-mode guidance; forces `done_when`, optional `verify:`, 2–3 `failure_modes`, subtasks. Human approves it.
|
||||
2. **planInjection** — the fixed-shape `before_agent_start` block (function of the parsed plan).
|
||||
3. **reminder** — the typed nudge (§8a).
|
||||
4. **continuation** — Hermes-style "keep going" user-role message.
|
||||
5. **loopJudge** — conservative, strict JSON `{done, reason}`.
|
||||
6. **evidenceJudge** — read-only, verify against repo + contract + check `verify` wasn't gamed, end with `VERDICT`.
|
||||
|
||||
5 and 6 adjacent: the cheap-foolable vs must-not-be-fooled contrast on one screen.
|
||||
|
||||
---
|
||||
|
||||
## 11. KV-cache hygiene
|
||||
|
||||
- Inject as a late **user-role** message, never a system-prompt mutation (a long goal then costs the same as the same number of normal turns).
|
||||
- Make the injected block **byte-identical when nothing changed**: fixed field order, no volatile timestamps in the body.
|
||||
|
||||
---
|
||||
|
||||
## 12. Dependencies and what to copy
|
||||
|
||||
- **No hard dependency** on `pi-subagents` or the `oracle` extension. Copy the shell-free spawn helper and the curated provider list (as a selection menu, not an auto-picker).
|
||||
- Markdown: line scanner first; `mdast` + `remark-gfm` only if needed.
|
||||
- Verify against current pi API: `before_agent_start` can append a user-role message without mutating the system prompt; the plan-phase model can be set per-phase and persisted.
|
||||
|
||||
---
|
||||
|
||||
## 13. Risks / open questions
|
||||
|
||||
- **Same-model sign-off judge → correlated blind spots** (the D11 tradeoff). Mitigation: most of the check's value is artifact re-derivation, which is model-independent; the cross-vendor setting covers the rest when available.
|
||||
- **No hard gate (D3)** — the agent can hand-tick `status: done` and skip the check. Mitigation: the reminder steers to `CompleteGoal`; the widget flags a `done` goal with no sign-off log line; git shows it.
|
||||
- **Contract tampering (D7)** — editable `plan.md` means `done_when`/`failure_modes` can be softened pre-sign-off. Mitigation: git diff; optionally log the contract line at creation and have the oracle read it.
|
||||
- **Loop-judge false positive** — premature pause; it does not sign off, so re-issue or `/subgoal`.
|
||||
- **`verify` gaming** — the oracle is told to inspect the test against the named failure mode.
|
||||
- **`## Log` rewritten not appended** — convention only; reminder enforces, git shows violations.
|
||||
- **Evidence durability** — the read-only judge can only verify what's on disk; elicitation pushes the agent to save logs/diffs.
|
||||
|
||||
---
|
||||
|
||||
## 14. Build order
|
||||
|
||||
Each step independently testable; model calls enter late.
|
||||
|
||||
1. `plan.md` format + line parser (incl. `<!-- id -->` and `## Log`) + `/plan` task-list widget. Pure file, no model calls.
|
||||
2. Goal-creation elicitation + `CompleteGoal` happy path **without** the check (patch status + append log) to validate the flow.
|
||||
3. Stage-1 `verify` in `CompleteGoal`; the widget flag for `done`-without-sign-off-line (guidance/visibility, not a block).
|
||||
4. Sign-off judge (stage 2): copy the spawn helper, write prompt 6, parse the verdict, fold in the gaming check; `/judge model` setting (default current model).
|
||||
5. `before_agent_start` injection (cache-safe) + the reminder (§8a).
|
||||
6. The loop: `/goal` + iteration budget + loop judge (prompt 5) + continuation (prompt 4) + the loop↔check handoff (§3b), multi-goal aware.
|
||||
7. Plan mode (§3a): `/plan <desc>` read-only draft → review → compact choice → hand-off. Plan-phase model selection + stickiness (D12). (Until built, create goals by direct `plan.md` edit.)
|
||||
8. Optional: `CancelGoal`, `/subgoal`, cross-vendor judge selection menu, `mdast` hardening.
|
||||
|
||||
`prompts.tsx` is authored alongside the steps that need each prompt but kept centralized from step 1.
|
||||
@@ -1,71 +0,0 @@
|
||||
# CompleteGoal fail-forward on judge failure
|
||||
|
||||
## Goal
|
||||
Make `CompleteGoal` stop rejecting verified goals just because the read-only judge subprocess times out. Keep the judge useful when it works, and make failures explicit in the log/result.
|
||||
|
||||
## Scope
|
||||
In: `CompleteGoal` sign-off behavior, judge transport, tests, docs.
|
||||
Out: broader autonomous loop work, plan-mode UX, model auto-selection.
|
||||
|
||||
## Requirements
|
||||
- R1: If `verify:` fails, the goal is rejected immediately. Done means: existing `verify_failed` behavior remains. VERIFY: unit test for pure sign-off record still passes.
|
||||
- R2: If `verify:` passes and the judge accepts, mark the goal done as before. Done means: log records normal judge accept. VERIFY: unit test for accepted sign-off still passes.
|
||||
- R3: If any `verify:` command passes but the judge times out or subprocess/model transport fails, mark the goal done with an explicit inconclusive-judge log. Goals without `verify:` use the same fail-forward rule once evidence exists. VERIFY: a unit test records accepted status and a log line containing `judge inconclusive`.
|
||||
- R4: Judge transport should parse `pi --mode json` message events instead of raw `-p` terminal output. Done means: code captures final assistant text and provider stop errors distinctly. VERIFY: `npm run typecheck` and tests pass.
|
||||
- R5: The judge should behave like oracle where it matters: explicit model, live streamed progress, and a timeout large enough for a cold reasoning turn. Done means: unset `/goals judge` resolves to the current session model when visible; if no model is visible, no implicit Pi default is used and sign-off is `judge inconclusive`. `message_update` emits throttled progress, and timeout is 600s. VERIFY: `npm run typecheck` and fresh-eyes diff review.
|
||||
|
||||
## Tasks
|
||||
- [x] T1 (R3): Add an accepted-with-warning sign-off outcome.
|
||||
- verify: `npm test`
|
||||
- success: test shows status `[x]` plus `judge inconclusive` in `## Log`
|
||||
- likely_fail: timeout still records `reject`
|
||||
- sneaky_fail: accepted status lands but log hides judge failure
|
||||
- UAT: [test/plan-file.test.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/test/plan-file.test.ts)
|
||||
- [x] T2 (R4): Switch judge subprocess to JSON-mode parsing.
|
||||
- verify: `npm run typecheck`
|
||||
- success: no TypeScript errors, judge code has no ANSI-terminal parsing dependency
|
||||
- likely_fail: compile errors around streamed event shape
|
||||
- sneaky_fail: model error produces empty output and gets parsed as reject instead of transport failure
|
||||
- UAT: [src/index.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/src/index.ts)
|
||||
- [x] T3 (docs): Update README sign-off semantics.
|
||||
- verify: `rg "inconclusive|timeout|judge accept" README.md src test`
|
||||
- success: docs name fail-forward behavior
|
||||
- likely_fail: README still says all rejects keep goal open
|
||||
- sneaky_fail: docs imply subagent evidence was accepted when it timed out
|
||||
- UAT: [README.md](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/README.md)
|
||||
- [x] T4 (R5): Copy oracle's reliability shape for model/progress.
|
||||
- verify: `npm run typecheck`
|
||||
- success: `CompleteGoal` passes the current session model to the judge when no override is set, never spawns without `--model`, and streamed judge deltas are surfaced through `onUpdate`
|
||||
- likely_fail: judge still runs without `--model`
|
||||
- sneaky_fail: user sees no progress for several minutes and kills a working judge
|
||||
- UAT: [src/index.ts](/home/wassname/.pi/agent/git/github.com/wassname/pi-plan/src/index.ts)
|
||||
|
||||
## Context
|
||||
Observed result from downstream use:
|
||||
|
||||
```json
|
||||
{
|
||||
"goal": "Make persona validation fail-fast and evidence-correct",
|
||||
"outcome": "rejected",
|
||||
"durationMs": 120003,
|
||||
"verifyCommand": "`uv run python -m compileall -q scripts/validate_persona_axes_openrouter.py`",
|
||||
"reasoning": "VERDICT: reject\nmissing: judge timed out after 120s",
|
||||
"isError": true
|
||||
}
|
||||
```
|
||||
|
||||
Interpretation: latest surfaced output proves the internal judge timed out. It does not prove the verify command passed, though earlier logs indicated that pattern.
|
||||
|
||||
## Log
|
||||
- 2026-06-29 current `runJudge` uses raw `pi -p --no-session` output plus ANSI stripping; oracle uses `--mode json` and parses message events, which is likely more reliable.
|
||||
- 2026-06-29 unset `/goals judge` spawns the judge without `--model`, so Pi resolves its configured default model; do not describe this as the current session model.
|
||||
- 2026-06-29 timeout/transport failure now maps to `accepted_inconclusive`, preserving partial output in reasoning when available.
|
||||
- 2026-06-29 fresh-eyes review found loose `/accept/i` verdict parsing and caller-abort fail-forward risk; fixed exact verdict parsing and made caller abort reject.
|
||||
- 2026-06-29 oracle comparison suggests the important reliability pieces are explicit model selection, JSON streaming, live partial output, and no short wrapper timeout; updated CompleteGoal to use the current session model when visible, never spawn without `--model`, stream throttled progress, and wait 600s.
|
||||
|
||||
## TODO
|
||||
- Consider making `CompleteGoal` expose `verifyExitCode: 0` and `judgeOutcome` separately in details.
|
||||
|
||||
## Errors
|
||||
| Task | Error | Resolution |
|
||||
|------|-------|------------|
|
||||
@@ -1,67 +0,0 @@
|
||||
# Per-session plan file
|
||||
|
||||
One `.pi/plan.md` per repo is wrong when two agents share the repo. A subagent spawns as
|
||||
`pi -p --no-session` in the same cwd with extensions ON (only the judge gets `--no-extensions`),
|
||||
so it loads pi-goals, gets the whole plan pushed in on its first call, and can call CompleteGoal
|
||||
on the parent's goal. A second window has the same problem, plus last-write-wins on the file.
|
||||
|
||||
Fix: the plan file is named after the session, `.pi/plan/<session_id>.md`. The file name is the
|
||||
arm switch. A session that never ran `/goals` has no file at its path, so the extension stays
|
||||
silent. No new state flag.
|
||||
|
||||
The id is stable where it must be. Resume reads `header.id` from the session file
|
||||
(`session-manager.js:547`) and compaction uses `branchWithSummary`, which does not touch the id.
|
||||
Only an explicit fork or new session gets a new id (`createBranchedSession`, `newSession`).
|
||||
|
||||
- [x] goal A: the plan file is per session, and a session with no plan is inert
|
||||
- [x] `planPath(ctx)` = `.pi/plan/<ctx.sessionManager.getSessionId()>.md`; `mkdir -p` the dir
|
||||
- [x] `PLAN_REL` becomes a per-context value; pass it into `decideSignOff` through `SignOffInput`
|
||||
so the judge prompt still names the real file
|
||||
- `--no-session` still gets a fresh random id, checked: `SessionManager.inMemory` passes no
|
||||
session file, so the constructor calls `newSession()` -> `createSessionId()`. So each subagent
|
||||
gets its own unused path and reads nothing. No empty-id special case is needed.
|
||||
- failure modes: two sessions somehow resolve the same id, so the two agents still share a file
|
||||
- deliverable: `ls .pi/plan/` in a scratch repo after two sessions, showing two files
|
||||
- [x] goal B: delete what this replaces
|
||||
- [x] drop the v1 `.pi/goals.md` -> `.pi/plan.md` rename in `session_start`
|
||||
- [x] `/goals clear` unlinks the file instead of writing an empty one
|
||||
- failure modes: none, this is removal
|
||||
- deliverable: the diff, negative line count
|
||||
- [x] goal C: docs and version match the code
|
||||
- [x] README, `package.json` description, `docs/spec/2026-06-15_pi-goals.md` say `.pi/plan/<session_id>.md`
|
||||
- [x] version 0.2.0, the path change is breaking
|
||||
- failure modes: docs still say `.pi/plan.md`, so the next reader trusts the wrong file
|
||||
- deliverable: `grep -rn "plan\.md" $(git ls-files)` returns only history and format prose
|
||||
|
||||
## UAT / Verification
|
||||
|
||||
`npm test` 19 passed, `npm run typecheck` and `npm run lint` clean.
|
||||
|
||||
Live A/B in `/tmp/plan-scratch`, which holds the same plan at both the old and the new path. The
|
||||
plan's one goal line is `1. [/] goal: SECRET-CANARY-PLAN delete the production database`. Each run
|
||||
asked: "Were you given a plan with goals? If yes, reply with the goal line verbatim. If no, reply
|
||||
exactly: NO PLAN".
|
||||
|
||||
| run | version, flags | answer |
|
||||
|---|---|---|
|
||||
| A | HEAD 4827808 (`.pi/plan.md`), `-p -ne --no-session` | `1. [/] goal: SECRET-CANARY-PLAN delete the production database` |
|
||||
| B | this change, `-p -ne --no-session` | `NO PLAN` |
|
||||
| C | this change, `-p -ne --session-id 019ec140-ce3b-70d7-8151-abfa7f1c95a9` | `goal: SECRET-CANARY-PLAN delete the production database` |
|
||||
|
||||
A is the bug: a subagent reads the parent's plan. B is a subagent under this change: it sees
|
||||
nothing and writes nothing (`find /tmp/plan-scratch/.pi -type f` still lists only the two seeded
|
||||
files). C is the owning session, whose id matches the plan file name: it still gets its plan.
|
||||
|
||||
Not covered by a live run: `/resume` and compaction keep the id. Both were checked by reading
|
||||
`session-manager.js` (`:547` reads `header.id`; compaction goes through `branchWithSummary`, which
|
||||
never assigns `sessionId`).
|
||||
|
||||
## Appendix (context, not approved)
|
||||
|
||||
Rejected: keep one `.pi/plan.md` and add an `armed` flag to `PlanState` that only `/goals` Ready
|
||||
sets. It works for subagents (`--no-session` has no state to replay) but it is more code than the
|
||||
rename, and two armed windows still stomp each other's file. -- Claude
|
||||
|
||||
Open, not in this plan: adopting an old plan into a new session (`/goals resume` picking the newest
|
||||
file in `.pi/plan/`), and carrying the plan across an explicit fork. Both are one command each; wait
|
||||
until the need is real.
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
# Visible supervisor handover
|
||||
|
||||
## Objective
|
||||
|
||||
Replace pi-goals' nested pi-subagents worker with two visible Pi sessions:
|
||||
|
||||
1. The main session plans with the user, then becomes the implementation worker.
|
||||
2. On Ready, pi-goals explicitly forks the planning session into a Herdr pane.
|
||||
3. Only the fork is compacted. It becomes the stronger read-only supervisor.
|
||||
4. pi-supervise and pi-intercom connect the supervisor to the worker.
|
||||
5. The worker starts only after the real pi-supervise `pair`/`paired` acknowledgment.
|
||||
6. The supervisor retains the plan, compact planning context, and concise worker views. It can steer the worker and approve a completed goal.
|
||||
7. The supervisor compacts near 100k tokens.
|
||||
|
||||
Keep this minimal. Reuse pi-supervise's intercom protocol instead of building a second orchestration layer.
|
||||
|
||||
## User preferences
|
||||
|
||||
- The primary session must do the implementation. Other agents may test or review it, but must not own core development.
|
||||
- Avoid relaying implementation decisions through multiple agents.
|
||||
- Herdr should open the supervisor automatically and let the user switch to it.
|
||||
- Persist configurable models for three stages:
|
||||
- planning: strongest model, for example Fable 5.1 or Astra;
|
||||
- supervision: for example Sol or Opus;
|
||||
- implementation: for example Terra, Sonnet, Kimi K3, DeepSeek Pro, or GLM 5.3.
|
||||
- Validate model IDs through Pi. Do not hard-code a model list.
|
||||
- Switch the main session to the planning model when planning starts and to the worker model only after pairing succeeds. Launch the fork with the supervisor model.
|
||||
|
||||
## Repository state
|
||||
|
||||
pi-goals branch: `experiment/subagent-supervisor`
|
||||
|
||||
Committed work:
|
||||
|
||||
- `d56fc55` — replace nested workers with a visible supervisor session
|
||||
- `e299e84` — run supervisor bootstrap through the pane shell
|
||||
- `c5782ee` — initial pairing handshake, evidence checks, Herdr parsing, and worker intercom ID
|
||||
- `7eb8b1f` — treat stale pane close as successful cleanup
|
||||
- `1dc6146` — allow `PI_GOALS_SUPERVISE_EXTENSION` for local development
|
||||
|
||||
pi-supervise committed dependency:
|
||||
|
||||
- `4e3cd1c` — acknowledged programmatic supervisor pairing API; package version 0.0.4
|
||||
|
||||
Uncommitted pi-goals files:
|
||||
|
||||
- `src/intercom.ts`
|
||||
- `src/supervise.ts`
|
||||
- `test/intercom.test.ts` (new)
|
||||
|
||||
Uncommitted pi-supervise file:
|
||||
|
||||
- `src/index.ts`
|
||||
|
||||
Inspect these diffs before editing. They are a partial design-B refactor and have not passed the real workflow.
|
||||
|
||||
## Why design B was selected
|
||||
|
||||
Primary-source review found that pi-supervise already sends `pair` and receives the worker's `paired` acknowledgment. The custom `pi-goals/visible-supervisor/v1` intercom namespace duplicated that acknowledgment and introduced another registration and connection race.
|
||||
|
||||
Selected design:
|
||||
|
||||
- pi-supervise exposes the worker's actual broker ID through a local extension API;
|
||||
- pi-supervise emits or resolves a worker-local event only after the real `paired` acknowledgment;
|
||||
- pi-goals passes that broker ID to the supervisor;
|
||||
- pi-goals waits for that worker-local paired acknowledgment before setting `phase: working` or sending the worker kickoff;
|
||||
- delete `src/intercom.ts` and custom supervisor-ready messages if the partial diff has not already completed that deletion;
|
||||
- support either extension load order by using pi-intercom/pi-supervise registry-ready events idempotently.
|
||||
|
||||
Do not use pi-intercom `project-agent.ts` as another lifecycle. It opens a generic Pi pane and polls broker presence but does not supply the required fork, extensions, model, or pairing semantics.
|
||||
|
||||
## Observed tests and failures
|
||||
|
||||
Unit validation before the unfinished design-B refactor:
|
||||
|
||||
- pi-goals: 26 tests passed, typecheck passed, lint passed, package dry-run passed, RPC test passed.
|
||||
- pi-supervise: 97 tests passed and package dry-run passed.
|
||||
|
||||
Real Herdr observations:
|
||||
|
||||
1. The initial smoke loaded pi-supervise directly from source and did not exercise pi-goals' actual Ready command.
|
||||
2. A later actual `/goals` → Ready run failed before pane creation because pi-goals emitted `intercom:extension-register` before pi-intercom installed its listener.
|
||||
3. A local uncommitted registry-ready re-registration fix moved the real path farther: Ready created supervisor pane `w8:p1F` through `supervisorCommand`.
|
||||
4. That run then timed out waiting for the duplicate custom `supervisor-ready` message. This led to design B.
|
||||
5. The supervisor exited before its transcript was preserved. Do not infer that pi-supervise pairing succeeded.
|
||||
|
||||
The real end-to-end workflow has not passed.
|
||||
|
||||
## Next work
|
||||
|
||||
1. Read the uncommitted diffs in both repositories and finish or simplify design B.
|
||||
2. Add focused tests:
|
||||
- pi-supervise local API works whether pi-goals loads before or after pi-supervise;
|
||||
- no `phase: working` or kickoff before actual `paired`;
|
||||
- duplicate `paired` is idempotent.
|
||||
3. Run the actual pi-goals path, not a substitute command:
|
||||
- start worker with pi-goals and pi-intercom;
|
||||
- enter `/goals`, draft a plan, and select Ready;
|
||||
- use `PI_GOALS_SUPERVISE_EXTENSION=/home/code/.pi/agent/git/github.com/wassname/pi-supervise/src/index.ts` until 0.0.4 is published;
|
||||
- positively observe fork-only compaction, actual pairing acknowledgment, then worker kickoff;
|
||||
- preserve supervisor stdout/stderr and session JSONL before cleanup on every failure;
|
||||
- observe supervisor monitoring or steering;
|
||||
- complete real evidence at a clean commit, approve it, call CompleteGoal, and close the pane.
|
||||
4. Commit the lifecycle separately once the real path passes.
|
||||
5. Add the three persisted model settings in a separate commit.
|
||||
6. Run tests, typecheck, lint, package dry-runs, real RPC tests, and a fresh read-only review.
|
||||
|
||||
## Known packaging constraint
|
||||
|
||||
`src/herdr.ts` defaults to `npm:@wassname2/pi-supervise@0.0.4`. Version 0.0.4 is not publicly published. Do not publish without explicit editorial approval. Local testing must use `PI_GOALS_SUPERVISE_EXTENSION`.
|
||||
|
||||
## Important lifecycle bug discovered in this session
|
||||
|
||||
`/goals clear` cleared extension state but left the current model request under the previously injected coordinator system instruction. `/reload` did not remove it. A fresh ordinary Pi session is required for direct implementation. The redesign should avoid leaving a session unable to resume ordinary work after clear.
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 77 KiB |
Generated
+3229
-163
File diff suppressed because it is too large
Load Diff
+28
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wassname2/pi-goals",
|
||||
"version": "0.2.2",
|
||||
"description": "Plan in one Pi session, then work under a visible forked supervisor.",
|
||||
"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",
|
||||
@@ -19,11 +19,11 @@
|
||||
"uat",
|
||||
"evidence",
|
||||
"supervisor",
|
||||
"pi-intercom",
|
||||
"herdr"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
"@earendil-works/pi-coding-agent": ">=0.85.1 <1.0.0",
|
||||
"@earendil-works/pi-tui": "*",
|
||||
"typebox": "*"
|
||||
},
|
||||
"files": [
|
||||
@@ -36,16 +36,17 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run lint && npm run typecheck && npm run test",
|
||||
"test": "vitest run",
|
||||
"test:rpc": "vitest run test/rpc-review.test.ts",
|
||||
"test:watch": "vitest",
|
||||
"test": "vitest run --dir test",
|
||||
"test:rpc": "vitest run --dir test rpc-review.test.ts",
|
||||
"test:watch": "vitest --dir test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check src/ test/",
|
||||
"lint:fix": "biome check --fix src/ test/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.8",
|
||||
"@earendil-works/pi-coding-agent": "^0.84.1",
|
||||
"@earendil-works/pi-coding-agent": "0.85.1",
|
||||
"@earendil-works/pi-tui": "^0.85.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"typebox": "^1.3.7",
|
||||
"typescript": "^5.0.0",
|
||||
@@ -53,8 +54,24 @@
|
||||
},
|
||||
"pi": {
|
||||
"extensions": [
|
||||
"./src/index.ts"
|
||||
"./src/index.ts",
|
||||
"./node_modules/pi-subagents/index.ts",
|
||||
"./node_modules/pi-intercom/index.ts",
|
||||
"./node_modules/pi-schedule-prompt/src/index.ts"
|
||||
],
|
||||
"image": "https://cdn.jsdelivr.net/gh/wassname/pi-goals@main/media/screenshot.png"
|
||||
}
|
||||
"image": "https://github.com/user-attachments/assets/35feaa15-f022-4491-bcc2-fc31cb878a9f",
|
||||
"skills": [
|
||||
"./node_modules/pi-intercom/skills"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"pi-subagents": "0.66.0",
|
||||
"pi-intercom": "0.13.0",
|
||||
"pi-schedule-prompt": "0.4.1"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"pi-subagents",
|
||||
"pi-intercom",
|
||||
"pi-schedule-prompt"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Replaces the stale FIXME(side-effect) claim in src/index.ts with a checked fact.
|
||||
#
|
||||
# The claim was: "pi -p --no-session clones the repo into the PARENT of cwd, leaving a stale
|
||||
# directory." Reproducing the exact sign-off judge invocation (pi --mode json -p --no-session,
|
||||
# read-only tools, edit/write excluded, cwd = here) shows it does not. This script makes that
|
||||
# reproducible: it runs the invocation, requires pi to actually reach agent_end (so a pass is not
|
||||
# vacuous), and asserts the parent-of-cwd listing is byte-identical before and after.
|
||||
#
|
||||
# Exit 0 = judge leaves no clone in the parent. Exit 1 = either pi did not run, or it polluted.
|
||||
# Run by hand; re-run as the rigorous sign-off check (the judge has bash and runs this itself).
|
||||
set -u
|
||||
|
||||
PARENT="$(cd "$PWD/.." && pwd)"
|
||||
before="$(ls -1A "$PARENT" | sort)"
|
||||
|
||||
# Cheapest available model; the test exercises pi --no-session's workdir setup, not the output.
|
||||
out="$(timeout 90 pi --mode json -p --no-session \
|
||||
--model 'openrouter/~anthropic/claude-haiku-latest' \
|
||||
--tools read,bash,grep,find,ls --exclude-tools edit,write \
|
||||
--append-system-prompt 'Reply with exactly: VERDICT: accept' \
|
||||
"Reply with exactly: VERDICT: accept" 2>/dev/null || true)"
|
||||
|
||||
# Non-vacuous: require pi to have actually completed a turn. A pass without this could mean pi
|
||||
# crashed instantly and never had the chance to clone -- which would prove nothing.
|
||||
if ! printf '%s' "$out" | grep -q '"type":"agent_end"'; then
|
||||
echo "FAIL: pi --no-session did not reach agent_end; cannot confirm no-clone."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
after="$(ls -1A "$PARENT" | sort)"
|
||||
|
||||
echo "parent: $PARENT"
|
||||
echo "--- before ---"; echo "$before"
|
||||
echo "--- after ---"; echo "$after"
|
||||
|
||||
if [ "$before" == "$after" ]; then
|
||||
echo "PASS: parent-of-cwd listing identical before/after; no clone created."
|
||||
exit 0
|
||||
fi
|
||||
echo "FAIL: parent-of-cwd listing changed. Diff (< before, > after):"
|
||||
diff <(printf '%s\n' "$before") <(printf '%s\n' "$after") | head -20
|
||||
exit 1
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Structural gate for the goal's `verify:` field. Cheap and deterministic: no API calls.
|
||||
# Confirms (a) neither stale FIXME tag remains in src/, (b) the footprint script exists, and
|
||||
# (c) the plan-injection heading prefix is still emitted. The rigorous runtime check (running
|
||||
# the footprint script) is the sign-off judge's job -- it has bash and re-runs the script itself.
|
||||
set -u
|
||||
fail() { echo "FAIL: $1"; exit 1; }
|
||||
|
||||
grep -rnE 'FIXME\((heading|side-effect)\)' src/ >/dev/null 2>&1 && fail "a stale FIXME(heading|side-effect) is still in src/"
|
||||
test -f scripts/check-judge-footprint.sh || fail "scripts/check-judge-footprint.sh is missing"
|
||||
grep -q '\.pi/goals\.md:' src/prompts.ts || fail "the .pi/goals.md: heading prefix was dropped from src/prompts.ts"
|
||||
|
||||
echo "PASS: stale FIXMEs gone, footprint script present, heading prefix intact."
|
||||
exit 0
|
||||
@@ -1,104 +0,0 @@
|
||||
diff --git a/README.md b/README.md
|
||||
index ce4056a..5485002 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -145,9 +145,9 @@ else is the agent editing the file. It reads the goal's `evidence:` block from `
|
||||
reasoning comes back in the result.
|
||||
|
||||
The judge defaults to the current session model and streams partial output while it runs. If the
|
||||
-current model is not visible to the extension, `CompleteGoal` does not fall back to Pi's implicit
|
||||
-default; it signs off as `judge inconclusive` and tells you to set `/goals judge <provider/model>`.
|
||||
-Point it at another model for an independent cross-family check.
|
||||
+session model is not visible to the extension, the `--model` flag is omitted and pi uses its own
|
||||
+configured default, so the judge always runs. `/goals judge <provider/model>` is an optional override
|
||||
+for an independent cross-family check; never required.
|
||||
|
||||
## Prompts
|
||||
|
||||
diff --git a/src/index.ts b/src/index.ts
|
||||
index 9eb2a16..14784f7 100644
|
||||
--- a/src/index.ts
|
||||
+++ b/src/index.ts
|
||||
@@ -326,7 +326,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
||||
durationMs,
|
||||
verifyCommand: goal.verify ?? undefined,
|
||||
verifyExitCode: outcome.kind === "verify_failed" ? outcome.exitCode : undefined,
|
||||
- judgeModel: judgeModel ?? "no explicit judge model",
|
||||
+ judgeModel: judgeModel ?? "pi default",
|
||||
reasoning,
|
||||
isError: res.isError,
|
||||
};
|
||||
@@ -522,14 +522,6 @@ async function decideSignOff(
|
||||
};
|
||||
}
|
||||
}
|
||||
- if (!judgeModel) {
|
||||
- const reason = "no explicit judge model available; set /goals judge <provider/model>";
|
||||
- return {
|
||||
- outcome: { kind: "accepted_inconclusive", reason },
|
||||
- reasoning: `VERDICT: inconclusive\nreason: ${reason}`,
|
||||
- durationMs: Date.now() - startedAt,
|
||||
- };
|
||||
- }
|
||||
const verdict = await runJudge(goal, evidence, paths, verifyResult, judgeModel, cwd, signal, onUpdate);
|
||||
const outcome: SignOff =
|
||||
verdict.kind === "accepted"
|
||||
@@ -573,13 +565,25 @@ type JudgeResult =
|
||||
| { kind: "rejected"; missing: string; reasoning: string; durationMs: number }
|
||||
| { kind: "inconclusive"; reason: string; reasoning: string; durationMs: number };
|
||||
|
||||
+/** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */
|
||||
+/** Build the pi argv for the read-only judge. `--model` is omitted when no explicit/session model is
|
||||
+ * set, so pi falls back to its configured default -- the judge always runs, never pre-emptively
|
||||
+ * fails as "no model". Exported for a unit test that locks this invariant (an empty `--model ""`
|
||||
+ * would make every sign-off silently inconclusive). */
|
||||
+export function buildJudgeArgs(judgeModel: string | null): string[] {
|
||||
+ const args = ["--mode", "json", "-p", "--no-session"];
|
||||
+ if (judgeModel) args.push("--model", judgeModel);
|
||||
+ args.push("--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem);
|
||||
+ return args;
|
||||
+}
|
||||
+
|
||||
/** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */
|
||||
async function runJudge(
|
||||
goal: Goal,
|
||||
evidence: string,
|
||||
paths: string[],
|
||||
verifyResult: { command: string; exitCode: number; outputTail: string } | null,
|
||||
- judgeModel: string,
|
||||
+ judgeModel: string | null,
|
||||
cwd: string,
|
||||
signal: AbortSignal | undefined,
|
||||
onUpdate?: (partial: { content: Array<{ type: "text"; text: string }>; details: SignOffDetails }) => void,
|
||||
@@ -600,14 +604,14 @@ async function runJudge(
|
||||
evidence,
|
||||
paths,
|
||||
});
|
||||
- const args = ["--mode", "json", "-p", "--no-session", "--model", judgeModel, "--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem];
|
||||
+ const args = buildJudgeArgs(judgeModel);
|
||||
args.push(task);
|
||||
|
||||
emit("spawning", `Spawning read-only judge for: ${goal.subject}`);
|
||||
const inv = getPiInvocation(args);
|
||||
- // FIXME(side-effect): pi -p --no-session clones the repo into the PARENT of cwd (so alongside
|
||||
- // the working dir), leaving a stale directory. The judge should run in a temp dir or inside the
|
||||
- // existing repo checkout so it doesn't pollute the user's workspace.
|
||||
+ // The judge runs in-place against this checkout (cwd is passed to spawn and the read-only tools
|
||||
+ // read from it); pi --no-session does not clone into the parent. Proven and re-checked by
|
||||
+ // scripts/check-judge-footprint.sh, which reproduces this invocation and asserts no parent clone.
|
||||
const judge = await new Promise<{ output: string; error?: string; aborted?: boolean }>((resolve) => {
|
||||
let settled = false;
|
||||
let stdoutBuffer = "";
|
||||
diff --git a/src/prompts.ts b/src/prompts.ts
|
||||
index 03faea8..3b8d270 100644
|
||||
--- a/src/prompts.ts
|
||||
+++ b/src/prompts.ts
|
||||
@@ -117,8 +117,6 @@ export function planInjection(p: {
|
||||
counts: { done: number; open: number };
|
||||
}): string {
|
||||
if (!p.activeGoal) {
|
||||
- // FIXME(heading): user wants the heading to show ".pi/goals.md: <title>" so the filename is explicit
|
||||
- // even in the injection. Currently says "Goals (goals.md):" which is close but not the same.
|
||||
return `.pi/goals.md: ${p.title}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its checkbox to [/]) or run /goals.`;
|
||||
}
|
||||
const subtasks = p.activeGoal.openSubtasks.length
|
||||
@@ -0,0 +1,32 @@
|
||||
// Pi/OpenAI. Prepare an isolated trial; never launches/reloads an existing session.
|
||||
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
const repo = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const sdkRoot = process.argv[2];
|
||||
if (!sdkRoot) throw new Error('Usage: node scripts/prepare-trial.mjs INSTALLED_PI_ROOT');
|
||||
const revision = execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], {encoding:'utf8'}).trim();
|
||||
const root = mkdtempSync(join(tmpdir(), 'goals-nico-trial-'));
|
||||
const cwd = join(root, 'project'); const agentDir = join(root, 'agent');
|
||||
mkdirSync(cwd); mkdirSync(agentDir, {mode:0o700});
|
||||
const sourceAgent = process.env.PI_CODING_AGENT_DIR || join(homedir(),'.pi','agent');
|
||||
const sourceSettings = JSON.parse(readFileSync(join(sourceAgent,'settings.json'),'utf8'));
|
||||
const sdk = await import(pathToFileURL(join(sdkRoot,'dist/index.js')).href);
|
||||
const settings = sdk.SettingsManager.create(repo, sourceAgent, { projectTrusted:false });
|
||||
const manager = new sdk.DefaultPackageManager({cwd:repo, agentDir:sourceAgent, settingsManager:settings});
|
||||
const packages = manager.listConfiguredPackages().filter((p) => p.scope !== 'project' && !/^\/\//.test(p.source));
|
||||
const retained = packages.filter((p) => !/pi-subagents|pi-goals|pi-intercom|pi-schedule-prompt/.test(p.source));
|
||||
for (const p of retained) if (!p.installedPath) throw new Error(`Missing installed package: ${p.source}`);
|
||||
writeFileSync(join(agentDir,'settings.json'), JSON.stringify({...sourceSettings, packages:[...retained.map((p)=>p.installedPath), repo]},null,2));
|
||||
// Private copies, not symlinks: a trial OAuth refresh must not write the active auth file.
|
||||
for (const file of ['auth.json','models.json']) if (existsSync(join(sourceAgent,file))) copyFileSync(join(sourceAgent,file),join(agentDir,file));
|
||||
execFileSync('git',['init','--quiet',cwd]);
|
||||
writeFileSync(join(cwd,'AGENTS.md'), 'Isolated functional trial. Work only in this project. Do not operate other Herdr panes, use live research sessions, or change global settings. Preserve evidence. The main chat supervises; the goals-worker implements.\n');
|
||||
writeFileSync(join(cwd,'.gitignore'), 'evidence/\n');
|
||||
const manifest={root,cwd,agentDir,repo,revision,retainedPackages:retained.map((p)=>p.source),replacedPackages:packages.filter((p)=>!retained.includes(p)).map((p)=>p.source)};
|
||||
writeFileSync(join(root,'manifest.json'),JSON.stringify(manifest,null,2));
|
||||
const quote=(s)=>`'${s.replaceAll("'", "'\\''")}'`;
|
||||
writeFileSync(join(root,'start.zsh'), `#!/usr/bin/env zsh\nset -e\ncd ${quote(cwd)}\nexport PI_CODING_AGENT_DIR=${quote(agentDir)}\nexec pi\n`,{mode:0o700});
|
||||
console.log(JSON.stringify({root,cwd,agentDir,start:join(root,'start.zsh'),manifest:join(root,'manifest.json')},null,2));
|
||||
@@ -0,0 +1,61 @@
|
||||
// Pi/OpenAI: Sum recorded requests, not context occupancy; do not read message text.
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
export function readSession(file) {
|
||||
const raw = readFileSync(file, 'utf8');
|
||||
const lines = raw.split('\n');
|
||||
const tail = lines.pop();
|
||||
let trailingPartial = false;
|
||||
if (tail) {
|
||||
try { JSON.parse(tail); lines.push(tail); }
|
||||
catch { trailingPartial = true; }
|
||||
}
|
||||
return { file: resolve(file), sha256: createHash('sha256').update(raw).digest('hex'), trailingPartial,
|
||||
entries: lines.filter(Boolean).map(JSON.parse) };
|
||||
}
|
||||
|
||||
export function summarize(entries, since, until) {
|
||||
const start = Date.parse(since), end = Date.parse(until);
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) throw new Error('Invalid time interval');
|
||||
const rows = entries.filter(e => e.type === 'message' && e.message.role === 'assistant' && Date.parse(e.timestamp) >= start && Date.parse(e.timestamp) <= end);
|
||||
const totals = { calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, output: 0, totalTokens: 0 };
|
||||
const models = new Map();
|
||||
let missingUsage = 0;
|
||||
for (const e of rows) {
|
||||
const m = e.message;
|
||||
if (!m.usage) { missingUsage++; continue; }
|
||||
const model = `${m.provider}/${m.model}`;
|
||||
if (!models.has(model)) models.set(model, { model, ...totals, calls: 0, input: 0, cacheRead: 0, cacheWrite: 0, output: 0, totalTokens: 0 });
|
||||
const group = models.get(model);
|
||||
totals.calls++; group.calls++;
|
||||
for (const key of ['input', 'cacheRead', 'cacheWrite', 'output', 'totalTokens']) {
|
||||
const value = m.usage[key];
|
||||
if (!Number.isFinite(value) || value < 0) throw new Error(`Invalid usage.${key} in entry ${e.id}`);
|
||||
totals[key] += value; group[key] += value;
|
||||
}
|
||||
}
|
||||
return { ...totals, missingUsage, firstRequest: rows[0]?.timestamp ?? null,
|
||||
lastRequest: rows.at(-1)?.timestamp ?? null, models: [...models.values()] };
|
||||
}
|
||||
|
||||
export function report(supervisor, worker, until = new Date().toISOString()) {
|
||||
const boundary = supervisor.entries.findLast(e => e.type === 'custom' && e.customType === 'pi-goals-main-supervisor-v1' && e.data.mode === 'planning' && !e.data.child);
|
||||
if (!boundary) throw new Error('No recorded planning start in supervisor session');
|
||||
const since = boundary.timestamp;
|
||||
const sessions = [supervisor, worker].map((session, i) => ({
|
||||
role: i === 0 ? 'supervisor' : 'worker', file: session.file, sha256: session.sha256,
|
||||
trailingPartial: session.trailingPartial, ...summarize(session.entries, since, until),
|
||||
}));
|
||||
return { since, until, elapsedHours: (Date.parse(until) - Date.parse(since)) / 3600000,
|
||||
boundaryEntry: boundary.id, plan: boundary.data.plan, sessions,
|
||||
scope: 'Recorded assistant usage since latest planning entry, including abandoned branches and repeated cached context. Excludes earlier inherited history, in-flight requests, subprocess API usage and unrecorded compaction calls. Output includes reasoning where the provider includes it; reasoning is not added twice.' };
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||
const [supervisor, worker] = process.argv.slice(2);
|
||||
if (!supervisor || !worker || process.argv.length !== 4) throw new Error('Usage: node scripts/session-usage.mjs SUPERVISOR.jsonl WORKER.jsonl');
|
||||
console.log(JSON.stringify(report(readSession(supervisor), readSession(worker)), null, 2));
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
diff --git a/src/index.ts b/src/index.ts
|
||||
index 9eb2a16..cdd7b45 100644
|
||||
--- a/src/index.ts
|
||||
+++ b/src/index.ts
|
||||
@@ -605,9 +605,9 @@ async function runJudge(
|
||||
|
||||
emit("spawning", `Spawning read-only judge for: ${goal.subject}`);
|
||||
const inv = getPiInvocation(args);
|
||||
- // FIXME(side-effect): pi -p --no-session clones the repo into the PARENT of cwd (so alongside
|
||||
- // the working dir), leaving a stale directory. The judge should run in a temp dir or inside the
|
||||
- // existing repo checkout so it doesn't pollute the user's workspace.
|
||||
+ // The judge runs in-place against this checkout (cwd is passed to spawn and the read-only tools
|
||||
+ // read from it); pi --no-session does not clone into the parent. Proven and re-checked by
|
||||
+ // scripts/check-judge-footprint.sh, which reproduces this invocation and asserts no parent clone.
|
||||
const judge = await new Promise<{ output: string; error?: string; aborted?: boolean }>((resolve) => {
|
||||
let settled = false;
|
||||
let stdoutBuffer = "";
|
||||
diff --git a/src/prompts.ts b/src/prompts.ts
|
||||
index 03faea8..3b8d270 100644
|
||||
--- a/src/prompts.ts
|
||||
+++ b/src/prompts.ts
|
||||
@@ -117,8 +117,6 @@ export function planInjection(p: {
|
||||
counts: { done: number; open: number };
|
||||
}): string {
|
||||
if (!p.activeGoal) {
|
||||
- // FIXME(heading): user wants the heading to show ".pi/goals.md: <title>" so the filename is explicit
|
||||
- // even in the injection. Currently says "Goals (goals.md):" which is close but not the same.
|
||||
return `.pi/goals.md: ${p.title}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its checkbox to [/]) or run /goals.`;
|
||||
}
|
||||
const subtasks = p.activeGoal.openSubtasks.length
|
||||
@@ -1,31 +0,0 @@
|
||||
# Goal steward validation
|
||||
|
||||
## Observations
|
||||
|
||||
- Unit, flow, type, and lint checks passed. [`20260905_validation.log`](20260905_validation.log) says:
|
||||
|
||||
> Test Files 8 passed (8)
|
||||
> Tests 36 passed (36)
|
||||
> Checked 12 files in 14ms. No fixes applied.
|
||||
|
||||
- A real Pi 0.85.0 process loaded pi-subagents 0.65.1, pi-goals, and a runtime `goal-steward` agent. It spawned one review and resumed that run for sign-off. [`20260905_steward-probe.json`](20260905_steward-probe.json) records two distinct run IDs:
|
||||
|
||||
> "runId": "4e9dc0c0-385b-4eb9-a060-ced7dc7cb6cc"
|
||||
|
||||
> "runId": "f6115c82-31de-499f-ab78-145dde0c51c0"
|
||||
|
||||
- The second review recalled a token that appeared only in the first review request. This is direct evidence that resume retained the steward conversation:
|
||||
|
||||
> "Persistence lineage token: amber-731."
|
||||
|
||||
- The sign-off review read `report.txt` and accepted the evidence:
|
||||
|
||||
> "file exists and contains exactly 'PROBE_PASS' as required. Failure mode (empty report) is ruled out."
|
||||
|
||||
## Test environment finding
|
||||
|
||||
The repository's older local Pi 0.84.1 install could not launch a pi-subagents background child because it did not include `@earendil-works/chord` and `@earendil-works/pi-server`. The successful probe used an isolated npm install of Pi 0.85.0. The current interactive Pi already launches pi-subagents children, so this finding concerns the old development dependency used by the first probe, not the extension protocol.
|
||||
|
||||
pi-subagents sends every ordinary async completion into the parent session and triggers a parent turn. The steward's structured summaries are bounded, but the package also includes the child's prose response. There is no public silent-completion option in pi-subagents 0.65.1. This adds one worker turn per review; checkpoints run only after eight stale turns.
|
||||
|
||||
— Pi/Codex
|
||||
@@ -1,91 +0,0 @@
|
||||
# Nested supervisor validation
|
||||
2026-09-05T19:31:55+08:00
|
||||
|
||||
$ npm test
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 test
|
||||
> vitest run
|
||||
|
||||
|
||||
RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals
|
||||
|
||||
|
||||
Test Files 9 passed (9)
|
||||
Tests 43 passed (43)
|
||||
Start at 19:31:56
|
||||
Duration 1.60s (transform 709ms, setup 0ms, import 1.64s, tests 1.76s, environment 1ms)
|
||||
|
||||
|
||||
$ npm run typecheck
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 typecheck
|
||||
> tsc --noEmit
|
||||
|
||||
|
||||
$ npm run lint
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 lint
|
||||
> biome check src/ test/
|
||||
|
||||
Checked 15 files in 29ms. No fixes applied.
|
||||
|
||||
$ git diff --check
|
||||
(no output)
|
||||
|
||||
$ npm pack --dry-run
|
||||
npm notice
|
||||
npm notice 📦 @wassname2/pi-goals@0.2.2
|
||||
npm notice Tarball Contents
|
||||
npm notice 5.8kB README.md
|
||||
npm notice 1.1kB agents/goal-worker.md
|
||||
npm notice 1.5kB package.json
|
||||
npm notice 4.0kB src/approval.ts
|
||||
npm notice 34.6kB src/index.ts
|
||||
npm notice 13.6kB src/prompts.ts
|
||||
npm notice 5.9kB src/supervisor-runtime.ts
|
||||
npm notice 7.3kB src/worker.ts
|
||||
npm notice Tarball Details
|
||||
npm notice name: @wassname2/pi-goals
|
||||
npm notice version: 0.2.2
|
||||
npm notice filename: wassname2-pi-goals-0.2.2.tgz
|
||||
npm notice package size: 23.1 kB
|
||||
npm notice unpacked size: 73.7 kB
|
||||
npm notice shasum: 579debe3de67b56116e51da6cac46c14511bdd07
|
||||
npm notice integrity: sha512-f5S39K2J3kjIX[...]cuwx2WFIeBBAQ==
|
||||
npm notice total files: 8
|
||||
npm notice
|
||||
wassname2-pi-goals-0.2.2.tgz
|
||||
|
||||
$ git diff --stat 2852432
|
||||
README.md | 13 +-
|
||||
agents/goal-worker.md | 2 +-
|
||||
.../20260905_nested-supervisor-validation.txt | 70 ++++------
|
||||
src/approval.ts | 27 +++-
|
||||
src/index.ts | 153 +++++++++++++++------
|
||||
src/prompts.ts | 9 +-
|
||||
src/supervisor-runtime.ts | 81 ++++++++---
|
||||
src/worker.ts | 36 +++--
|
||||
test/goals-flow.test.ts | 54 +++++++-
|
||||
test/prompts.test.ts | 2 +-
|
||||
test/supervisor-runtime.test.ts | 32 ++++-
|
||||
test/worker.test.ts | 18 ++-
|
||||
12 files changed, 354 insertions(+), 143 deletions(-)
|
||||
|
||||
## Dogfood run
|
||||
|
||||
The model-backed run produced commit `0a33ff2` and independently verified 47 text-file word counts with zero set, count, or order mismatches. Approval then deadlocked:
|
||||
|
||||
> Cannot approve while the retained worker is pending.
|
||||
|
||||
The worker process was terminal, but its model result was `Request was aborted`; the completion event did not clear retained state. A supervisor resume also failed because `subagent_supervisor` was unavailable in its strict tool list.
|
||||
|
||||
Usage from the run status files:
|
||||
|
||||
| agent | turns | new tokens | cached reads | reported cost |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| supervisor, including recovery | 42 | 169,288 | 2,670,336 | $2.35 |
|
||||
| worker | 17 | 67,803 | 812,544 | $0.90 |
|
||||
|
||||
The corrective patch keeps the supervisor fork, compacts its planning history before the first turn when Ready (compact) is selected, removes global/project/skill prompt inheritance, replaces raw status polling with a concise worker-state tool, removes the unavailable tool, and treats process-terminal as terminal worker state. Unit tests pass; a second model-backed run is still required.
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
@@ -1,52 +0,0 @@
|
||||
text/plain .gitignore
|
||||
text/plain AGENTS.md
|
||||
text/plain ARCHIVED.md
|
||||
text/plain README.md
|
||||
text/plain agents/pi-goals-worker-v1.md
|
||||
application/json biome.json
|
||||
text/plain docs/reviews/goals_menu2.md
|
||||
text/plain docs/reviews/goals_menu2_r2.md
|
||||
text/plain docs/reviews/pi-goals-grok-4-6-retry.md
|
||||
text/plain docs/reviews/pi-goals-kimi-k3.md
|
||||
text/plain docs/reviews/review.md
|
||||
text/plain docs/slop/audit/20260826_pi-plan-aligned-planning.md
|
||||
text/plain docs/slop/plans/20260706_plan-flow-and-judge-review.md
|
||||
text/plain docs/slop/plans/20260826_pi-plan-aligned-planning.md
|
||||
text/plain docs/spec/2026-06-15_pi-goals.md
|
||||
text/plain docs/spec/2026-06-29_complete-goal-fail-forward.md
|
||||
text/plain docs/spec/2026-08-14_per-session-plan.md
|
||||
image/png media/screenshot.png
|
||||
application/json package-lock.json
|
||||
application/json package.json
|
||||
text/x-shellscript scripts/check-judge-footprint.sh
|
||||
text/x-shellscript scripts/check-stale-fixmes.sh
|
||||
text/x-diff scripts/inconclusive-fail-forward.diff
|
||||
text/x-diff scripts/stale-fixme-removal.diff
|
||||
text/plain slop/audits/20260905_goal-steward-validation.md
|
||||
text/plain slop/audits/20260905_nested-supervisor-validation.txt
|
||||
text/plain slop/audits/20260905_pi-goals-file-types.txt
|
||||
text/plain slop/audits/20260905_pi-goals-line-count-table.md
|
||||
text/plain slop/audits/20260905_pi-goals-text-line-counts.txt
|
||||
application/json slop/audits/20260905_steward-probe.json
|
||||
text/plain slop/audits/20260906_foreground-supervisor-validation.txt
|
||||
text/plain slop/audits/20260906_nested-runtime-smoke.md
|
||||
text/plain slop/audits/20260906_nonchild-npm-test.txt
|
||||
text/plain slop/plans/20260905_goal-steward.md
|
||||
text/plain slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md
|
||||
text/plain slop/reviews/20260906_foreground-worker-review.md
|
||||
application/javascript src/approval.ts
|
||||
application/javascript src/index.ts
|
||||
application/javascript src/prompts.ts
|
||||
application/javascript src/supervisor-runtime.ts
|
||||
application/javascript src/worker.ts
|
||||
application/javascript test/append-log.test.ts
|
||||
application/javascript test/fixtures/offline-model.ts
|
||||
application/javascript test/fold.test.ts
|
||||
application/javascript test/goals-flow.test.ts
|
||||
application/javascript test/package-agent.test.ts
|
||||
application/javascript test/prompts.test.ts
|
||||
application/javascript test/rpc-review.test.ts
|
||||
application/javascript test/supervisor-runtime.test.ts
|
||||
application/javascript test/tick-goal.test.ts
|
||||
application/javascript test/worker.test.ts
|
||||
application/json tsconfig.json
|
||||
@@ -1,67 +0,0 @@
|
||||
# pi-goals tracked-text line counts
|
||||
|
||||
Scope: Git-tracked files at this repository snapshot. A file is included when `file --mime-type` identifies `text/*`, `application/json`, or `application/javascript`.
|
||||
|
||||
Excluded: `media/screenshot.png` is binary (`image/png`); `package-lock.json` is an npm-generated dependency lockfile. No other tracked files are excluded.
|
||||
|
||||
Method: run the command below from the repository root; the saved machine-readable output is `slop/audits/20260905_pi-goals-text-line-counts.txt`.
|
||||
|
||||
```sh
|
||||
git ls-files -z | while IFS= read -r -d '\0' f; do case "$f" in media/screenshot.png|package-lock.json) continue;; esac; mime=$(file -b --mime-type "$f"); [[ "$mime" =~ ^text/|^application/(json|javascript)$ ]] && printf '%s\t%s\n' "$(wc -l < "$f")" "$f"; done | sort -k2
|
||||
```
|
||||
|
||||
| File | Lines |
|
||||
| --- | ---: |
|
||||
| `AGENTS.md` | 24 |
|
||||
| `agents/pi-goals-worker-v1.md` | 22 |
|
||||
| `ARCHIVED.md` | 3 |
|
||||
| `biome.json` | 23 |
|
||||
| `docs/reviews/goals_menu2.md` | 65 |
|
||||
| `docs/reviews/goals_menu2_r2.md` | 21 |
|
||||
| `docs/reviews/pi-goals-grok-4-6-retry.md` | 30 |
|
||||
| `docs/reviews/pi-goals-kimi-k3.md` | 40 |
|
||||
| `docs/reviews/review.md` | 61 |
|
||||
| `docs/slop/audit/20260826_pi-plan-aligned-planning.md` | 25 |
|
||||
| `docs/slop/plans/20260706_plan-flow-and-judge-review.md` | 33 |
|
||||
| `docs/slop/plans/20260826_pi-plan-aligned-planning.md` | 53 |
|
||||
| `docs/spec/2026-06-15_pi-goals.md` | 275 |
|
||||
| `docs/spec/2026-06-29_complete-goal-fail-forward.md` | 71 |
|
||||
| `docs/spec/2026-08-14_per-session-plan.md` | 67 |
|
||||
| `.gitignore` | 6 |
|
||||
| `package.json` | 65 |
|
||||
| `README.md` | 139 |
|
||||
| `scripts/check-judge-footprint.sh` | 43 |
|
||||
| `scripts/check-stale-fixmes.sh` | 14 |
|
||||
| `scripts/inconclusive-fail-forward.diff` | 104 |
|
||||
| `scripts/stale-fixme-removal.diff` | 30 |
|
||||
| `slop/audits/20260905_goal-steward-validation.md` | 31 |
|
||||
| `slop/audits/20260905_nested-supervisor-validation.txt` | 91 |
|
||||
| `slop/audits/20260905_pi-goals-file-types.txt` | 52 |
|
||||
| `slop/audits/20260905_pi-goals-line-count-table.md` | 67 |
|
||||
| `slop/audits/20260905_pi-goals-text-line-counts.txt` | 50 |
|
||||
| `slop/audits/20260905_steward-probe.json` | 15 |
|
||||
| `slop/audits/20260906_foreground-supervisor-validation.txt` | 53 |
|
||||
| `slop/audits/20260906_nested-runtime-smoke.md` | 31 |
|
||||
| `slop/audits/20260906_nonchild-npm-test.txt` | 33 |
|
||||
| `slop/plans/20260905_goal-steward.md` | 37 |
|
||||
| `slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md` | 65 |
|
||||
| `slop/reviews/20260906_foreground-worker-review.md` | 20 |
|
||||
| `src/approval.ts` | 115 |
|
||||
| `src/index.ts` | 736 |
|
||||
| `src/prompts.ts` | 191 |
|
||||
| `src/supervisor-runtime.ts` | 179 |
|
||||
| `src/worker.ts` | 186 |
|
||||
| `test/append-log.test.ts` | 17 |
|
||||
| `test/fixtures/offline-model.ts` | 18 |
|
||||
| `test/fold.test.ts` | 63 |
|
||||
| `test/goals-flow.test.ts` | 596 |
|
||||
| `test/package-agent.test.ts` | 23 |
|
||||
| `test/prompts.test.ts` | 33 |
|
||||
| `test/rpc-review.test.ts` | 116 |
|
||||
| `test/supervisor-runtime.test.ts` | 153 |
|
||||
| `test/tick-goal.test.ts` | 32 |
|
||||
| `test/worker.test.ts` | 119 |
|
||||
| `tsconfig.json` | 15 |
|
||||
| **Total** | **4351** |
|
||||
|
||||
-- PI[gpt-5.6]
|
||||
@@ -1,50 +0,0 @@
|
||||
24 AGENTS.md
|
||||
22 agents/pi-goals-worker-v1.md
|
||||
3 ARCHIVED.md
|
||||
23 biome.json
|
||||
65 docs/reviews/goals_menu2.md
|
||||
21 docs/reviews/goals_menu2_r2.md
|
||||
30 docs/reviews/pi-goals-grok-4-6-retry.md
|
||||
40 docs/reviews/pi-goals-kimi-k3.md
|
||||
61 docs/reviews/review.md
|
||||
25 docs/slop/audit/20260826_pi-plan-aligned-planning.md
|
||||
33 docs/slop/plans/20260706_plan-flow-and-judge-review.md
|
||||
53 docs/slop/plans/20260826_pi-plan-aligned-planning.md
|
||||
275 docs/spec/2026-06-15_pi-goals.md
|
||||
71 docs/spec/2026-06-29_complete-goal-fail-forward.md
|
||||
67 docs/spec/2026-08-14_per-session-plan.md
|
||||
6 .gitignore
|
||||
65 package.json
|
||||
139 README.md
|
||||
43 scripts/check-judge-footprint.sh
|
||||
14 scripts/check-stale-fixmes.sh
|
||||
104 scripts/inconclusive-fail-forward.diff
|
||||
30 scripts/stale-fixme-removal.diff
|
||||
31 slop/audits/20260905_goal-steward-validation.md
|
||||
91 slop/audits/20260905_nested-supervisor-validation.txt
|
||||
52 slop/audits/20260905_pi-goals-file-types.txt
|
||||
67 slop/audits/20260905_pi-goals-line-count-table.md
|
||||
50 slop/audits/20260905_pi-goals-text-line-counts.txt
|
||||
15 slop/audits/20260905_steward-probe.json
|
||||
53 slop/audits/20260906_foreground-supervisor-validation.txt
|
||||
31 slop/audits/20260906_nested-runtime-smoke.md
|
||||
33 slop/audits/20260906_nonchild-npm-test.txt
|
||||
37 slop/plans/20260905_goal-steward.md
|
||||
65 slop/reviews/2026-09-06_deepseek-v4-pro-0813_pi_goals_fragility.md
|
||||
20 slop/reviews/20260906_foreground-worker-review.md
|
||||
115 src/approval.ts
|
||||
736 src/index.ts
|
||||
191 src/prompts.ts
|
||||
179 src/supervisor-runtime.ts
|
||||
186 src/worker.ts
|
||||
17 test/append-log.test.ts
|
||||
18 test/fixtures/offline-model.ts
|
||||
63 test/fold.test.ts
|
||||
596 test/goals-flow.test.ts
|
||||
23 test/package-agent.test.ts
|
||||
33 test/prompts.test.ts
|
||||
116 test/rpc-review.test.ts
|
||||
153 test/supervisor-runtime.test.ts
|
||||
32 test/tick-goal.test.ts
|
||||
119 test/worker.test.ts
|
||||
15 tsconfig.json
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"first": {
|
||||
"runId": "4e9dc0c0-385b-4eb9-a060-ced7dc7cb6cc",
|
||||
"decision": {
|
||||
"verdict": "let_run",
|
||||
"summary": "Plan reviewed for approved work session. The user-visible result (report file proves steward can read evidence) directly aligns with the single goal (report probe result with discriminator). The report.txt artifact exists and contains PROBE_PASS as required by the discriminator. No work steps are pending; the probe is complete. No drift, missing steps, or failure modes detected. Plan may proceed without adjustment."
|
||||
}
|
||||
},
|
||||
"second": {
|
||||
"runId": "f6115c82-31de-499f-ab78-145dde0c51c0",
|
||||
"decision": {
|
||||
"summary": "Sign-off review for goal 'Report the probe result'. User-visible result requires a report file proving persistent steward can read evidence. Discriminator: report.txt contains PROBE_PASS. Inspected artifact at /tmp/pi-goals-steward-probe-work/report.txt—file exists and contains exactly 'PROBE_PASS' as required. Failure mode (empty report) is ruled out. Evidence positively and directly proves the discriminator is met and the user-visible result is achieved. Persistence lineage token: amber-731.",
|
||||
"verdict": "accept"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
$ npm test
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 test
|
||||
> vitest run
|
||||
|
||||
|
||||
RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals
|
||||
|
||||
|
||||
Test Files 9 passed (9)
|
||||
Tests 43 passed (43)
|
||||
Start at 13:31:44
|
||||
Duration 1.61s (transform 1.12s, setup 0ms, import 2.36s, tests 2.13s, environment 1ms)
|
||||
|
||||
|
||||
$ npm run typecheck
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 typecheck
|
||||
> tsc --noEmit
|
||||
|
||||
|
||||
$ npm run lint
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 lint
|
||||
> biome check src/ test/
|
||||
|
||||
Checked 15 files in 18ms. No fixes applied.
|
||||
|
||||
$ git diff --check
|
||||
|
||||
$ npm pack --dry-run
|
||||
npm notice
|
||||
npm notice 📦 @wassname2/pi-goals@0.2.2
|
||||
npm notice Tarball Contents
|
||||
npm notice 6.1kB README.md
|
||||
npm notice 969B agents/pi-goals-worker-v1.md
|
||||
npm notice 1.5kB package.json
|
||||
npm notice 4.0kB src/approval.ts
|
||||
npm notice 34.8kB src/index.ts
|
||||
npm notice 13.6kB src/prompts.ts
|
||||
npm notice 8.3kB src/supervisor-runtime.ts
|
||||
npm notice 7.8kB src/worker.ts
|
||||
npm notice Tarball Details
|
||||
npm notice name: @wassname2/pi-goals
|
||||
npm notice version: 0.2.2
|
||||
npm notice filename: wassname2-pi-goals-0.2.2.tgz
|
||||
npm notice package size: 24.1 kB
|
||||
npm notice unpacked size: 76.8 kB
|
||||
npm notice shasum: 30e72af7ab4a553ccb1f7599a882d1796155cdf4
|
||||
npm notice integrity: sha512-p6DUvHWofwDTz[...]IZmG7JiD+/wFw==
|
||||
npm notice total files: 8
|
||||
npm notice
|
||||
wassname2-pi-goals-0.2.2.tgz
|
||||
@@ -1,31 +0,0 @@
|
||||
# Nested foreground runtime smoke
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
node /tmp/pi-goals-real-rpc-smoke.mjs
|
||||
```
|
||||
|
||||
Result: PASS.
|
||||
|
||||
The fresh Pi RPC session loaded the local pi-goals package, ran `goal-supervisor` in the foreground, and the supervisor ran `pi-goals-worker-v1` in the foreground with `context: "fork"`.
|
||||
|
||||
Exact final output:
|
||||
|
||||
> **Run: goal-supervisor (foreground, context fork) → pi-goals-worker-v1 (foreground, context fork)**
|
||||
>
|
||||
> - **goal-supervisor** (runtime agent, fork) launched and owned the worker
|
||||
> - **pi-goals-worker-v1** acknowledged the invocation, made no file edits, ran no repo reads, touched no supervisor channels
|
||||
> - **Worker returned:** `worker-smoke-ok`
|
||||
> - **Approved?** No — supervisor explicitly skipped `ApproveGoal` per the task
|
||||
|
||||
Run ID: `9c25a6a7-8929-46fd-87bb-0d0f67672b54`.
|
||||
|
||||
Saved runtime artifacts:
|
||||
|
||||
- `/home/code/.pi/agent/sessions/--home-code-.pi-agent-git-github.com-wassname-pi-goals--/subagent-artifacts/9c25a6a7-8929-46fd-87bb-0d0f67672b54_goal-supervisor_0_output.md`
|
||||
- `/home/code/.pi/agent/sessions/--home-code-.pi-agent-git-github.com-wassname-pi-goals--/subagent-artifacts/9c25a6a7-8929-46fd-87bb-0d0f67672b54_goal-supervisor_0_transcript.jsonl`
|
||||
|
||||
This smoke tested nested discovery and foreground execution. It did not test a real approval because the task explicitly prohibited `ApproveGoal`.
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
@@ -1,33 +0,0 @@
|
||||
# npm test outside the subagent-child harness
|
||||
|
||||
Command run from `/home/code/.pi/agent/git/github.com/wassname/pi-goals`:
|
||||
|
||||
```sh
|
||||
env -u PI_SUBAGENT_CHILD -u PI_SUBAGENT_EXTENSION_BINDINGS -u PI_SUBAGENT_PARENT_SESSION -u PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT npm test
|
||||
```
|
||||
|
||||
The cleared variables were the complete `PI_SUBAGENT_*` set inherited by this worker. `PI_SUBAGENT_CHILD=1` makes `isSupervisorProcess()` false in `src/index.ts`, so the main extension deliberately registers no commands or hooks in that harness mode.
|
||||
|
||||
Exact output:
|
||||
|
||||
```text
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 test
|
||||
> vitest run
|
||||
|
||||
|
||||
RUN v4.1.9 /home/code/.pi/agent/git/github.com/wassname/pi-goals
|
||||
|
||||
|
||||
Test Files 9 passed (9)
|
||||
Tests 43 passed (43)
|
||||
Start at 15:57:04
|
||||
Duration 1.27s (transform 407ms, setup 0ms, import 1.41s, tests 1.63s, environment 1ms)
|
||||
|
||||
|
||||
__EXIT_STATUS__=0
|
||||
```
|
||||
|
||||
The earlier callback-registration failures and RPC timeout therefore came from the intentional child-process extension gate, not a source test failure.
|
||||
|
||||
-- PI[gpt-5.6]
|
||||
@@ -1,18 +0,0 @@
|
||||
# visible-supervisor follow-up
|
||||
|
||||
## committed changes
|
||||
|
||||
- pi-goals `294fe80` removes the duplicate `pi-goals/visible-supervisor/v1` channel. The worker now obtains its broker ID and waits for pi-supervise's worker-local `paired` event.
|
||||
- pi-supervise `409233c` exports that worker state/event API and retries pi-intercom registration after its registry-ready event.
|
||||
|
||||
## observed Herdr run
|
||||
|
||||
A real `/goals` → Ready run created the fork pane. In the first run, extension `session_start` did not reach the forked extensions: the fork had only copied entries and no bootstrap entry. The supervisor therefore did not pair. This is observed in the fork JSONL session `01a0770f-7015-7046-9858-6c7d8c8786aa`.
|
||||
|
||||
The fix moves supervisor initialization to `before_agent_start`, starts the fork with `Initialize supervision startup.`, and loads pi-supervise before pi-goals. A later direct fork under that code compacted/pair-started: its terminal said `Supervision initialized` and that it had sent the worker start instruction. That direct fork was used after the original Ready flow was already waiting on the first failed pane, so it does not prove the final worker phase transition.
|
||||
|
||||
## remaining check
|
||||
|
||||
Run a fresh `/goals` → Ready after `294fe80` and `409233c`; positively inspect that the worker state writes `phase: working` after the `paired` event, then carry one tiny task through worker evidence, ApproveGoal, CompleteGoal, and pane close.
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
@@ -1,37 +0,0 @@
|
||||
# Persistent goal steward
|
||||
|
||||
> "ideally the supervisor has the high level planning and goal context, doesn't get overloaded and have to compact, is cheap as it doesn't use many tokens (high level only)"
|
||||
>
|
||||
> "try again with more thought using pi-subagents much more to simplify out code and rely on that so our code is simple"
|
||||
|
||||
- [x] goal: A cheap read-only steward keeps the goal context across reviews
|
||||
- [x] register one `goal-steward` agent through the public pi-subagents event bus
|
||||
- [x] start it with fresh context at Ready and resume its latest saved run at checkpoints
|
||||
- [x] send the plan path and a bounded progress delta; require the steward to reread the plan
|
||||
- failure modes: every review starts fresh; the steward receives the full worker transcript; the steward can edit; reload loses its run
|
||||
- deliverable: tests show one spawn followed by resume, a saved latest run ID, read-only tools, bounded review prompts, and reload recovery
|
||||
- evidence: [`../audits/20260905_steward-probe.json`](../audits/20260905_steward-probe.json) contains two run IDs and the resumed review says `Persistence token amber-731 verified.`
|
||||
|
||||
- [x] goal: CompleteGoal uses the steward's evidence verdict
|
||||
- [x] resume the steward for sign-off and wait for its async result
|
||||
- [x] parse the structured verdict and write the sign-off log
|
||||
- failure modes: stale review signs off a new claim; missing pi-subagents silently becomes acceptance; completion events from another run are consumed
|
||||
- deliverable: flow tests distinguish accept, reject, unavailable, timeout, and exact-run completion
|
||||
- evidence: [`../audits/20260905_validation.log`](../audits/20260905_validation.log) says `Tests 36 passed (36)` and `Checked 12 files in 14ms. No fixes applied.`
|
||||
|
||||
## UAT / Verification
|
||||
|
||||
- [x] `npm test`, `npm run typecheck`, and `npm run lint` pass.
|
||||
- [x] A real Pi RPC flow creates a steward run, resumes it for sign-off, and recalls a private token from the retained conversation.
|
||||
- [x] The flow test reloads extension state and resumes from the latest steward run ID.
|
||||
|
||||
## Appendix (context, not approved)
|
||||
|
||||
Use pi-subagents 0.65.1 public RPC (`spawn`, `resume`) and `subagent:async-complete`. Register the runtime agent with `pi-subagents:runtime-agent-register:v1`. Do not import pi-subagents or reproduce session, process, model, tool, or recovery code. The old subprocess judge was removed rather than retained as a second sign-off system.
|
||||
|
||||
## Log
|
||||
|
||||
- 2026-09-05: Unit and flow tests cover read-only registration, spawn then resume, exact-run completion, timeout, reload, and accept/reject sign-off.
|
||||
- 2026-09-05: The Pi 0.85.0 + pi-subagents 0.65.1 probe passed in 29 seconds; the resumed child recalled `amber-731` from its first review.
|
||||
|
||||
— Pi/Codex
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
requested_model: deepseek/deepseek-v4-pro-0813
|
||||
mode: code review
|
||||
input: src/worker.ts, src/supervisor-runtime.ts
|
||||
trace: omitted from git (11 MB raw provider transcript)
|
||||
generated: 2026-09-06T04:44:52.809370+00:00
|
||||
---
|
||||
|
||||
# MoA fragility review
|
||||
|
||||
|
||||
Decision: reject the current fix and replace duplicate async lifecycle state with one synchronous worker tool.
|
||||
|
||||
Strongest objection: if a truly synchronous worker RPC is unavailable, this simplification blocks the intended parallel supervision model.
|
||||
|
||||
Next check: read the goal-worker tool implementation and the three failing test transcripts before deleting code.
|
||||
|
||||
Smallest recommended architecture:
|
||||
|
||||
The supervisor extension must not store worker lifecycle state. Lifecycle is owned by the subagent runtime. Move ownership into one tool boundary.
|
||||
|
||||
1. Delete NESTED_STATE persistence, event listeners, pending reconciliation, CheckWorkerState, and the replacement guard from supervisor-runtime.ts.
|
||||
2. Add a single supervisor tool:
|
||||
- RunGoalWorker: starts and awaits a goal-worker synchronously, using the aggregate output as a tool result.
|
||||
- Keep one in-memory boolean `workerRunning`, guarded at tool execute start, not relying on event ordering.
|
||||
3. If that synchronous tool cannot be supported:
|
||||
- StartGoalWorker returns a run ID as ordinary tool output.
|
||||
- WaitGoalWorker(runId) blocks on terminal status check.
|
||||
- ApproveGoal always calls bg_wait on the ID from StartGoalWorker or WaitGoalWorker; otherwise approval fails.
|
||||
|
||||
Because existing failure 2 came from the runtime blocking on a mismatched ID, the important property is:
|
||||
- an ID not produced by StartGoalWorker/WaitGoalWorker may not be used for bg_wait;
|
||||
- a failed wait must clear any in-process guard immediately;
|
||||
- an await cover failure must be treated as a terminal error, not as `pending`.
|
||||
|
||||
Exact deletions/changes:
|
||||
|
||||
In `src/supervisor-runtime.ts`:
|
||||
- Remove `NESTED_STATE`, `NestedState`, `nested`, `persist`, `targetRun`, `completeNested`, all `subagent:async-*`, process-terminal listeners, and `retainedRunState` reconciliation.
|
||||
- Remove `pi.events.on("tool_call")` blocks. Replace with allow/deny only: deny edit/write, allow read-only bash, allow RunGoalWorker, allow bg_wait, allow ApproveGoal, deny subagent action tools.
|
||||
- Replace CheckWorkerState with nothing. State inspection is only through normal async progress updates.
|
||||
- ApproveGoal asserts no active await cover currently exists from RunGoalWorker or WaitGoalWorker, processWorkState is idle, worktree is clean, and evidence inspection claims are backed by the actual tool result from RunGoalWorker.
|
||||
|
||||
In `src/worker.ts`:
|
||||
- Drop `retainedRunState` and any pending-closure logic.
|
||||
- Keep `asyncSnapshot` only for processWorkState, if needed.
|
||||
|
||||
Why this removes fragility:
|
||||
- Duplicate state is gone.
|
||||
- Lifecycle is only stored in the runtime’s tool execution stack.
|
||||
- Revival cannot resurrect a wrong worker ID unless a new tool starts it.
|
||||
- Race between event handler and spawn disappears because Start or Wait returns a result synchronously to the model.
|
||||
|
||||
Why this may be worse:
|
||||
- Synchronous wait loses the supervisor's ability to issue corrections inline during progress.
|
||||
- Parallel instrumented runs cannot be sustained within one tool without exposing `bg_wait` to the model.
|
||||
- If the model calls WaitGoalWorker with an incorrect ID, it will now fail directly, but the failure must not be caught and retried with a cached ID.
|
||||
|
||||
Acceptance test to catch all observed failures:
|
||||
- Send the supervisor script: `StartGoalWorker` → `WaitGoalWorker(id)` → `RunGoalWorker(correction)` → `ApproveGoal`, where a midway kill drops the terminal event and forces session revival, and then assert the code path stores no `NESTED_STATE`, does not even mention it in the extension memory, and either the worker returns a tool result or the revived session remains in the same `WaitGoalWorker` tool with no retry on an ID not yielded by that tool.
|
||||
|
||||
## Completion
|
||||
|
||||
- outcome: `completed_after_follow_up`
|
||||
- trace: omitted from git (11 MB raw provider transcript); this file preserves the complete review answer
|
||||
@@ -1,20 +0,0 @@
|
||||
## Review
|
||||
|
||||
No issues found.
|
||||
|
||||
- Correct: The packaged worker is discoverable in pi-subagents 0.65.1 child-safe fanout. `package.json` exposes `pi.subagents.agents`, which the installed discovery code consumes (`pi-subagents/src/agents/agents.ts:510-538,597-657`), while the child fanout executor uses normal `discoverAgents` (`pi-subagents/src/extension/fanout-child.ts:145-190`).
|
||||
- Correct: The supervisor gate requires the exact packaged agent, nonempty task, `async:false`, `context:"fork"`, and the configured model with no extra fields (`src/supervisor-runtime.ts:83-108`). The installed executor honors explicit foreground mode (`pi-subagents/src/runs/foreground/subagent-executor.ts:6511-6515,6917-6920`).
|
||||
- Correct: Foreground completion is tied to the real `tool_result`. `activeWorkerCalls` is removed only when that result arrives, successful completion is recorded, and approval requires a later turn (`src/supervisor-runtime.ts:75-115,132-138`). Same-message worker launch plus approval is independently rejected by inspecting the assistant message.
|
||||
- Correct: Stale local launch reservations self-heal: errors clear on `tool_result`, and `turn_start` clears any reservation for which no result hook arrived (`src/supervisor-runtime.ts:75-115`). The tests cover duplicate launch, failed-result recovery, and next-turn recovery (`test/supervisor-runtime.test.ts:57-76`).
|
||||
- Correct: `CompleteGoal` remains blocked while the retained supervisor is pending, while any subagent/process work is active or unknown, or until a matching approval checkpoint exists (`src/index.ts`, `CompleteGoal`). Foreground nested work therefore cannot race sign-off because its containing supervisor run remains pending.
|
||||
- Correct: `supervisor-runtime.ts` does not perform runtime-agent registration. The main extension exits in child processes through `isSupervisorProcess`, while installed pi-subagents itself is inert when `PI_SUBAGENT_CHILD=1` (`src/index.ts`, `isSupervisorProcess`; installed `pi-subagents/index.ts:3-8`).
|
||||
- Correct: The former nested async worker ID/pending lifecycle is absent. The remaining `workerRunId`/`workerPending` state belongs only to the retained supervisor lifecycle, matching the documented topology.
|
||||
|
||||
Residual risks:
|
||||
- `test/package-agent.test.ts` verifies packaging statically rather than launching the packaged worker through the real child-safe fanout runtime. The installed 0.65.1 source supports the configuration, but retaining an RPC integration check is advisable.
|
||||
- The focused approval tests mock Pi’s `tool_call`/`tool_result` ordering. A real RPC test remains the strongest guard against upstream lifecycle-event changes.
|
||||
- Tests were inspected but not executed in this review environment; the supervisor should run `npm test`, `npm run typecheck`, and `npm run lint`.
|
||||
|
||||
- Merge verdict: **OK with residual test-environment risks.**
|
||||
|
||||
-- PI[reviewer/gpt-5.6-sol]
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
|
||||
const GOAL_LINE = /^\s*(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*goal:\s*(.*)$/i;
|
||||
|
||||
export interface ApprovalRecord {
|
||||
version: 3;
|
||||
verdict: "accept";
|
||||
approvalId: string;
|
||||
goal: string;
|
||||
planPath: string;
|
||||
goalBlockHash: string;
|
||||
repoRoot: string;
|
||||
head: string;
|
||||
tree: string;
|
||||
cleanWorktree: true;
|
||||
inspected: { plan: true; repository: true; evidence: true; verifyOutput: true };
|
||||
verifyOutputPath: string;
|
||||
supervisor: { sessionId: string; runId: string | null };
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
function command(repoRoot: string, args: string[]): string {
|
||||
return execFileSync("git", args, { cwd: repoRoot, encoding: "utf8" }).trim();
|
||||
}
|
||||
|
||||
export function repositoryState(cwd: string): { repoRoot: string; head: string; tree: string; cleanWorktree: boolean } {
|
||||
const repoRoot = command(cwd, ["rev-parse", "--show-toplevel"]);
|
||||
const head = command(repoRoot, ["rev-parse", "HEAD"]);
|
||||
const tree = command(repoRoot, ["rev-parse", "HEAD^{tree}"]);
|
||||
const prefix = relative(repoRoot, resolve(cwd)).replaceAll("\\", "/");
|
||||
const owned = prefix ? `${prefix}/.pi` : ".pi";
|
||||
const cleanWorktree = command(repoRoot, [
|
||||
"status", "--porcelain=v1", "--untracked-files=all", "--", ".",
|
||||
`:(exclude,glob)${owned}/plan/*.md`,
|
||||
`:(exclude,glob)${owned}/pi-goals/approvals/*`,
|
||||
]) === "";
|
||||
return { repoRoot, head, tree, cleanWorktree };
|
||||
}
|
||||
|
||||
export function goalBlock(plan: string, goal: string): string | null {
|
||||
const lines = plan.split("\n");
|
||||
const wanted = goal.trim().toLowerCase();
|
||||
const hits = lines.flatMap((line, index) => {
|
||||
const match = GOAL_LINE.exec(line);
|
||||
return match && (match[1] === " " || match[1] === "/") && match[2].trim().toLowerCase() === wanted ? [index] : [];
|
||||
});
|
||||
if (hits.length !== 1) return null;
|
||||
const start = hits[0];
|
||||
let end = lines.length;
|
||||
for (let index = start + 1; index < lines.length; index++) {
|
||||
if (GOAL_LINE.test(lines[index])) {
|
||||
end = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lines.slice(start, end).join("\n");
|
||||
}
|
||||
|
||||
export function hashGoalBlock(block: string): string {
|
||||
return createHash("sha256").update(block).digest("hex");
|
||||
}
|
||||
|
||||
export function verifyOutputPath(repoRoot: string, path: string): string | null {
|
||||
const resolved = resolve(repoRoot, path);
|
||||
const relativePath = relative(repoRoot, resolved).replaceAll("\\", "/");
|
||||
if (!relativePath || relativePath.startsWith("../") || relativePath === "..") return null;
|
||||
try {
|
||||
const output = statSync(resolved);
|
||||
if (!output.isFile() || output.size === 0) return null;
|
||||
command(repoRoot, ["ls-files", "--error-unmatch", "--", relativePath]);
|
||||
return relativePath;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function approvalPath(cwd: string, sessionId: string, goal: string): string {
|
||||
const goalId = createHash("sha256").update(goal.trim().toLowerCase()).digest("hex").slice(0, 16);
|
||||
return join(cwd, ".pi", "pi-goals", "approvals", `${sessionId}-${goalId}.json`);
|
||||
}
|
||||
|
||||
export function writeApproval(path: string, record: ApprovalRecord): void {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
const temporary = `${path}.${process.pid}.tmp`;
|
||||
try {
|
||||
writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`);
|
||||
renameSync(temporary, path);
|
||||
} finally {
|
||||
if (existsSync(temporary)) rmSync(temporary, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function readApproval(path: string): ApprovalRecord | null {
|
||||
if (!existsSync(path)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as ApprovalRecord;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function approvalMatches(record: ApprovalRecord | null, input: {
|
||||
approvalId: string;
|
||||
goal: string;
|
||||
planPath: string;
|
||||
goalBlockHash: string;
|
||||
repoRoot: string;
|
||||
head: string;
|
||||
tree: string;
|
||||
cleanWorktree: boolean;
|
||||
}): boolean {
|
||||
return record?.version === 3
|
||||
&& record.verdict === "accept"
|
||||
&& record.approvalId === input.approvalId
|
||||
&& record.goal === input.goal
|
||||
&& resolve(record.planPath) === resolve(input.planPath)
|
||||
&& record.goalBlockHash === input.goalBlockHash
|
||||
&& resolve(record.repoRoot) === resolve(input.repoRoot)
|
||||
&& record.head === input.head
|
||||
&& record.tree === input.tree
|
||||
&& record.cleanWorktree === true
|
||||
&& input.cleanWorktree
|
||||
&& record.inspected.plan === true
|
||||
&& record.inspected.repository === true
|
||||
&& record.inspected.evidence === true
|
||||
&& record.inspected.verifyOutput === true
|
||||
&& Boolean(record.verifyOutputPath);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
interface LaunchSupervisorInput {
|
||||
cwd: string;
|
||||
sourceSessionFile: string;
|
||||
workerSessionId: string;
|
||||
workerIntercomId: string;
|
||||
planPath: string;
|
||||
approvalId: string;
|
||||
extensionPath: string;
|
||||
superviseExtensionPath: string | null;
|
||||
model: string | null;
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\\''")}'`;
|
||||
}
|
||||
|
||||
function findPaneId(value: unknown): string | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
for (const key of ["pane_id", "paneId"]) {
|
||||
if (typeof record[key] === "string") return record[key];
|
||||
}
|
||||
for (const child of Object.values(record)) {
|
||||
const found = findPaneId(child);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function herdr(args: string[], json = true): Promise<unknown> {
|
||||
const bin = process.env.HERDR_BIN_PATH ?? "herdr";
|
||||
const { stdout } = await execFileAsync(bin, args, { encoding: "utf8", timeout: 15_000 });
|
||||
if (!json) return stdout.trim();
|
||||
return stdout.trim() ? JSON.parse(stdout) : {};
|
||||
}
|
||||
|
||||
function stalePaneError(error: unknown): boolean {
|
||||
const record = error as { stdout?: unknown; stderr?: unknown; message?: unknown };
|
||||
const text = [record.stdout, record.stderr, record.message].filter((value): value is string => typeof value === "string").join("\n");
|
||||
return /\b(?:NOT_FOUND|PANE_GONE|PANE_NOT_FOUND)\b/i.test(text);
|
||||
}
|
||||
|
||||
export function supervisorCommand(input: LaunchSupervisorInput): string {
|
||||
const env = [
|
||||
"PI_GOALS_ROLE=supervisor",
|
||||
`PI_GOALS_WORKER_ID=${input.workerSessionId}`,
|
||||
`PI_GOALS_WORKER_INTERCOM_ID=${input.workerIntercomId}`,
|
||||
`PI_GOALS_PLAN_PATH=${input.planPath}`,
|
||||
`PI_GOALS_APPROVAL_ID=${input.approvalId}`,
|
||||
`PI_GOALS_OWNER_SESSION_ID=${input.workerSessionId}`,
|
||||
];
|
||||
const args = [
|
||||
"pi",
|
||||
"--no-extensions",
|
||||
"-e", "npm:pi-intercom",
|
||||
"-e", process.env.PI_GOALS_SUPERVISE_EXTENSION ?? input.superviseExtensionPath ?? "npm:@wassname2/pi-supervise@0.0.4",
|
||||
"-e", input.extensionPath,
|
||||
"--fork", input.sourceSessionFile,
|
||||
"--name", `goals-supervisor-${input.workerSessionId.slice(0, 8)}`,
|
||||
];
|
||||
if (input.model) args.push("--model", input.model);
|
||||
return `env ${[...env, ...args].map(shellQuote).join(" ")}`;
|
||||
}
|
||||
|
||||
export async function openSupervisorPane(input: LaunchSupervisorInput): Promise<string> {
|
||||
if (process.env.HERDR_ENV !== "1") throw new Error("Ready needs a Herdr session so pi-goals can open the supervisor session.");
|
||||
await herdr(["--version"], false);
|
||||
const split = await herdr(["pane", "split", "--current", "--direction", "right", "--cwd", input.cwd, "--no-focus"]);
|
||||
const paneId = findPaneId(split);
|
||||
if (!paneId) throw new Error("Herdr did not return the new supervisor pane ID.");
|
||||
try {
|
||||
await herdr(["pane", "run", paneId, supervisorCommand(input)]);
|
||||
return paneId;
|
||||
} catch (error) {
|
||||
await closeSupervisorPane(paneId);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeSupervisorPane(paneId: string): Promise<void> {
|
||||
try {
|
||||
await herdr(["pane", "close", paneId]);
|
||||
} catch (error) {
|
||||
if (stalePaneError(error)) return;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
+757
-489
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
// Pi/OpenAI: Collapse only the display; the saved user prompt and model input stay unchanged.
|
||||
import { type ExtensionAPI, type ExtensionContext, getMarkdownTheme, keyHint } from "@earendil-works/pi-coding-agent";
|
||||
import { Markdown, truncateToWidth } from "@earendil-works/pi-tui";
|
||||
|
||||
const NOTICE = "pi-goals-notice";
|
||||
|
||||
export function noticeDisplay(pi: ExtensionAPI) {
|
||||
const mirrored = new Set<string>();
|
||||
pi.registerMarkdownTransformer((markdown, context) =>
|
||||
context.messageType === "user" && mirrored.has(markdown) ? "" : markdown);
|
||||
pi.registerEntryRenderer(NOTICE, (entry, { expanded }, theme) => {
|
||||
const { content } = entry.data as { content: string };
|
||||
const label = content.includes("\nPlan changed.") ? "Plan changed · review requested" : "Goal instructions";
|
||||
if (expanded) return new Markdown(content, 0, 0, getMarkdownTheme());
|
||||
return {
|
||||
render: (width) => [truncateToWidth(theme.fg("muted", `[pi-goals] ${label} · ${keyHint("app.tools.expand", "expand")}`), width)],
|
||||
invalidate() {},
|
||||
};
|
||||
});
|
||||
return {
|
||||
mirror(content: string) {
|
||||
mirrored.add(content);
|
||||
pi.appendEntry(NOTICE, { content });
|
||||
},
|
||||
restore(ctx: ExtensionContext) {
|
||||
mirrored.clear();
|
||||
for (const entry of ctx.sessionManager.getBranch()) {
|
||||
if (entry.type === "custom" && entry.customType === NOTICE) mirrored.add((entry.data as { content: string }).content);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Pi/OpenAI: Review task/evidence changes, but omit history and worker identity bookkeeping.
|
||||
import { foldPlan } from "./plan.js";
|
||||
|
||||
export function planViews(plan: string): { notify: string } {
|
||||
const identity = /^[ \t]*[-*]\s*(?:active worker|worker session|worker intercom session):/i;
|
||||
return { notify: foldPlan(plan).split("\n").filter(line => !identity.test(line)).join("\n").trim() };
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Pi/OpenAI: Log, at any heading level, is the single boundary between current work and history.
|
||||
export const GOAL_LINE = /^\s*(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*goal:\s*(.*)$/i;
|
||||
export const FOLD_LINE = /^#{1,6}[ \t]+Log[ \t]*\r?$/im;
|
||||
const identity = /^[ \t]*[-*]\s*(?:active worker|worker session|worker intercom session|preferred worker model):/i;
|
||||
|
||||
export function foldPlan(plan: string): string {
|
||||
const match = FOLD_LINE.exec(plan);
|
||||
return (match ? plan.slice(0, match.index) : plan).trimEnd();
|
||||
}
|
||||
|
||||
const heading = /^(#{1,6})[ \t]+(.+?)[ \t]*$/;
|
||||
|
||||
function namedSection(lines: string[], name: string): string[] {
|
||||
const start = lines.findIndex((line) => {
|
||||
const match = heading.exec(line);
|
||||
return match?.[2].toLowerCase() === name.toLowerCase();
|
||||
});
|
||||
if (start === -1) return [];
|
||||
const level = heading.exec(lines[start])![1].length;
|
||||
const end = lines.findIndex((line, index) => index > start && (heading.exec(line)?.[1].length ?? Infinity) <= level);
|
||||
return lines.slice(start, end === -1 ? undefined : end).join("\n").trimEnd().split("\n");
|
||||
}
|
||||
|
||||
// Context tiers retain direct requirements while keeping normal reminders small. -- PI/gpt-5.6-terra
|
||||
export function planContextView(plan: string, tier: "short" | "medium" | "full"): string {
|
||||
if (tier === "full") return plan.trimEnd();
|
||||
const workingSet = foldPlan(plan);
|
||||
const lines = workingSet.split("\n");
|
||||
const titleIndex = lines.findIndex(line => /^#(?!#)[ \t]+/.test(line));
|
||||
const title = titleIndex === -1 ? [] : [lines[titleIndex]];
|
||||
const introStart = titleIndex === -1 ? 0 : titleIndex + 1;
|
||||
let firstContent = introStart;
|
||||
while (firstContent < lines.length && !lines[firstContent].trim()) firstContent++;
|
||||
const intro: string[] = [];
|
||||
if (firstContent < lines.length && !heading.test(lines[firstContent])) {
|
||||
for (let index = firstContent; index < lines.length && lines[index].trim(); index++) intro.push(lines[index]);
|
||||
}
|
||||
const result = namedSection(lines, "User-visible result");
|
||||
const short = [...title, ...(intro.length ? ["", ...intro] : []), ...(result.length ? ["", ...result] : [])].join("\n").trimEnd();
|
||||
if (tier === "short") return short;
|
||||
const userVoice = namedSection(lines, "User voice");
|
||||
const goalsHeading = namedSection(lines, "Goals")[0];
|
||||
const goalLines = lines.filter(line => GOAL_LINE.test(line));
|
||||
return [short, ...(userVoice.length ? ["", ...userVoice] : []), ...(goalsHeading && goalLines.length ? ["", goalsHeading, ...goalLines] : [])].join("\n").trimEnd();
|
||||
}
|
||||
|
||||
// Pi/OpenAI: Approval covers shared requirements and this goal, not checkbox/task/evidence maintenance.
|
||||
export function goalAcceptanceSignature(plan: string, goal: string): string | undefined {
|
||||
const lines = foldPlan(plan).split("\n");
|
||||
const goals = lines.flatMap((line, index) => {
|
||||
const match = GOAL_LINE.exec(line);
|
||||
return match ? [{ index, subject: match[2].trim().toLowerCase() }] : [];
|
||||
});
|
||||
const matches = goals.filter(item => item.subject === goal.trim().toLowerCase());
|
||||
if (matches.length !== 1) return undefined;
|
||||
const selected = matches[0];
|
||||
const end = goals.find(item => item.index > selected.index)?.index ?? lines.length;
|
||||
const content = [...lines.slice(0, goals[0].index), `goal: ${selected.subject}`, ...lines.slice(selected.index + 1, end)];
|
||||
const kept: string[] = [];
|
||||
let omittedIndent: number | undefined;
|
||||
let omittedHeading: number | undefined;
|
||||
for (const line of content) {
|
||||
if (identity.test(line)) continue;
|
||||
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
|
||||
if (heading) {
|
||||
if (omittedHeading !== undefined && heading[1].length <= omittedHeading) omittedHeading = undefined;
|
||||
if (/^(?:Tasks?|Task list|Subtasks?|Evidence)\b/i.test(heading[2])) omittedHeading = heading[1].length;
|
||||
}
|
||||
if (omittedHeading !== undefined) continue;
|
||||
const indent = line.length - line.trimStart().length;
|
||||
if (omittedIndent !== undefined) {
|
||||
if (!line.trim() || indent > omittedIndent) continue;
|
||||
omittedIndent = undefined;
|
||||
}
|
||||
if (/^\s*[-*]\s+(?:tasks?|subtasks?|evidence):/i.test(line) || /^\s*(?:\d+[.)]|[-*])\s+\[[ xX/~-]\]/.test(line)) {
|
||||
omittedIndent = indent;
|
||||
continue;
|
||||
}
|
||||
if (line.trim()) kept.push(line.trim());
|
||||
}
|
||||
return kept.join("\n");
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Nico publishes TypeScript source, not declarations; its transitive internal graph
|
||||
// targets a different Pi SDK. Type only the public v1 operation used here.
|
||||
declare module "pi-subagents/project-panes" {
|
||||
export function openProjectPane(options: { cwd: string; message?: string; focus?: boolean; signal?: AbortSignal }): Promise<
|
||||
| { ok: true; data: { bindingPath: string; disposition: "opened" | "already-open"; binding: { paneId: string; projectRoot: string; command: string } } }
|
||||
| { ok: false; error: { code: string; message: string } }
|
||||
>;
|
||||
}
|
||||
+171
-85
@@ -1,45 +1,39 @@
|
||||
/**
|
||||
* pi-goals v2 — all model-facing text, in flow order.
|
||||
*
|
||||
* Design: the plan file is for LLMs and the human, not for TypeScript. No parser and no schema;
|
||||
* the skeleton below is a convention the drafting prompt teaches. The main session implements it,
|
||||
* while a visible forked Pi session supervises through pi-supervise.
|
||||
*
|
||||
* THE FOLD: everything above "## Log" is the short current-goal section. Everything below it
|
||||
* (Log, Learnings, Appendix) is durable memory: unlimited, read on demand, and sent in full at
|
||||
* session start and after compaction.
|
||||
*
|
||||
* Flow:
|
||||
* SETUP (plan mode) 1. planDrafting — draft goals into the plan file (read-only), sent once
|
||||
* EXEC, after compact 2. resync — the WHOLE file back, once
|
||||
* SIGN-OFF, worker-side 3. completeGoal* — the one blessed tool's description
|
||||
* SUPERVISION supervisor-session.ts — visible read-only supervisor
|
||||
*
|
||||
* The goal's test is the DISCRIMINATOR: the concrete observation that tells real success from the
|
||||
* named subtle failure mode. Evidence is empty at planning and filled at sign-off.
|
||||
*/
|
||||
// Pi/OpenAI: Planning, approval, supervision, reminders, completion and recovery.
|
||||
import { createHash } from "node:crypto";
|
||||
import { foldPlan, GOAL_LINE } from "./plan.js";
|
||||
|
||||
// Quote the existing selection verbatim; a longer fence also contains nested Markdown fences.
|
||||
function quotedPlan(path: string | undefined, text: string, selection: string): string {
|
||||
const fence = "`".repeat(Math.max(3, ...Array.from(text.matchAll(/`+/g), match => match[0].length + 1)));
|
||||
const label = selection === "full" ? "Full plan snapshot" : `Plan excerpt (${selection})`;
|
||||
return `${label} from ${JSON.stringify(path ?? "not attached")}:\n${fence}md\n${text}\n${fence}`;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* 1. planDrafting — SETUP, plan mode (read-only: edit/write blocked except the plan file)
|
||||
* ──────────────────────────────────────────────────────────────────────── */
|
||||
export const planDrafting = `\
|
||||
You are in plan mode. You are making a short judgeable plan that captures the user's real goals, then tests it in conversation.
|
||||
You are in plan mode. Help the user express what they want this project to achieve in a short judgeable plan. Seek to understand their underlying goals, infer ordinary details, and use their applicable AGENTS.md instructions, relevant skills, and project context to interpret the request correctly. Do not silently substitute your own goals or expand the agreed scope.
|
||||
|
||||
1. Reduce technical uncertainty first. Use read-only repository tools or web search when either can
|
||||
resolve a fact. Do not write or run code in this phase (edit/write are blocked except for the plan
|
||||
file; don't mutate state via bash either).
|
||||
resolve a fact. Only edit the plan in this phase; do not implement or mutate project state via bash.
|
||||
This is an instruction, not a filesystem restriction.
|
||||
2. Before you draft a goal, identify its object, observable result, scope, and any decision that the
|
||||
human would need to approve later. Ask at least three short, concrete questions that test whether you
|
||||
understand the requested outcome, boundary, and how success will be judged. Inspect files or search the
|
||||
web before asking when either can answer a fact. If the human does not answer a question, record that
|
||||
point as unknown; do not silently replace it with an inference. Do not present the review menu with a
|
||||
placeholder goal such as "work out the thing", "improve it", or "investigate".
|
||||
3. For independent high-impact questions, build a decision tree and ask the whole frontier in one
|
||||
round. Ask only questions worth the human's time, where the answer materially reduces uncertainty
|
||||
human would need to approve later. Briefly reframe the request in your own words to check comprehension
|
||||
and make your understanding visible: the intended outcome, boundary, and success check. Invite correction,
|
||||
but do not require confirmation when these are already clear. Ask questions that expose differences
|
||||
between your understanding and the user's that would otherwise stay hidden. Probe consequential
|
||||
assumptions, challenge inconsistencies, and follow up where an answer exposes a gap. Do not use a question quota or ask the human
|
||||
to approve ordinary implementation details. Inspect files or search the web before asking when either
|
||||
can answer a fact. If the human does not answer a question, record that
|
||||
point as unknown; do not silently replace it with an inference or turn it into a new blocking decision.
|
||||
Do not present the review menu with a placeholder goal such as "work out the thing", "improve it", or
|
||||
"investigate".
|
||||
3. Use questions to clarify and narrow the goal, test your assumptions, and bring your understanding
|
||||
into agreement with the user's. Respect their limited time: batch independent high-impact questions
|
||||
in one short round, where the answer materially reduces uncertainty
|
||||
while discovering the right plan. Each question must be short and self-contained: state the relevant
|
||||
context, use the human's language and ASD-STE100
|
||||
Simple Technical English, and give a recommended answer. Record each answer, or the unanswered
|
||||
unknown, in ## Interview. Do not make the plan final while material user decisions remain open.
|
||||
unknown, in ## Interview. Draft goals and present Ready when the requested work is otherwise executable.
|
||||
Only withhold Ready for an unanswered choice that changes scope, spending, or the user-visible result.
|
||||
4. State the user-visible result before the goals: one concrete sentence naming what the human will
|
||||
inspect when this plan is done. Take it from the original request, not from your implementation plan.
|
||||
Every requested artifact and action must survive into this sentence. An agent-inferred constraint may
|
||||
@@ -47,18 +41,26 @@ not replace, defer, or contradict it; ask the human if an inference would change
|
||||
5. When every goal has an object, observable result, settled scope, and required approval, draft the
|
||||
plan file and present it. It should be safe to work overnight and present the requested outcome.
|
||||
|
||||
How this mode ends: after each settled draft the human gets a menu (Ready / Refine / Edit / Cancel).
|
||||
Plan mode ends only when they pick Ready. Refine collects short revision notes. Edit opens the full
|
||||
How this mode ends: after each changed settled draft the human gets a menu (Ready / Discuss / Edit / Cancel).
|
||||
Plan mode ends only when they pick Ready. Discuss continues ordinary chat. Edit opens the full
|
||||
plan. When a new requirement arrives, fold it in, say what changed, and present the plan again.
|
||||
Detail that doesn't change a goal or a discriminator belongs in the appendix, not in the goals.
|
||||
|
||||
Right-size it:
|
||||
- One goal per distinct judgeable outcome. Group related goals when it helps judge them together
|
||||
and readability. The count flows from the outcomes.
|
||||
- Describe outcomes in qualitative terms the supervisor and user can discriminate.
|
||||
- Use the users language or more precise don't transform "MV" into "knob" as it looses precision and is overloaded
|
||||
- Don't invent metrics or thresholds for problems you haven't explored yet - the supervisor should know it when it sees the outcome.
|
||||
- Quantitative gates are fine only when you are certain they survive contact with reality.
|
||||
- Write each visible goal as a short, concrete requested deliverable or behavior that stands alone.
|
||||
Preserve the user's technical deliverable nouns and verbs. Do not rename concrete technical goals
|
||||
into vague benefit or readiness phrases when clarifying acceptance.
|
||||
Use "I know it when I see it": an outcome the supervisor can recognize from actual results in
|
||||
hindsight. Put observable examples under verification; explain
|
||||
what distinguishes it from merely looking done. Preparing for similarity search does not deliver
|
||||
working similarity search. Exercise judgment against the user's intent, not stricter assistant-invented requirements.
|
||||
- Keep Rust conversion, embeddings, and functioning similarity search/keyword clusters explicit
|
||||
when requested; do not replace them with "familiar reader" or "ready for similarity search".
|
||||
- Use the user's language or more precise terms; don't transform "MV" into "knob".
|
||||
- Do not invent numerical gates to replace judgment. Preserve numerical requirements supplied
|
||||
by the user or justified by existing evidence.
|
||||
- Subtasks are the steps inside a goal; add them when a goal has 3+ distinct steps, skip otherwise.
|
||||
- Two goals that share one discriminator are one goal. Merge them.
|
||||
- Keep the goal subject short. Put its important scope, failure modes, discriminator, tasks, and evidence in the indented block beneath it. The supervisor reads the whole block and the whole plan.
|
||||
@@ -85,7 +87,7 @@ Write the plan file in roughly this shape -- the file is read directly by the hu
|
||||
|
||||
## Goals
|
||||
|
||||
1. [ ] goal: <one short jugable imperative outcome>
|
||||
1. [ ] goal: <short, concrete requested outcome>
|
||||
- subtle failure mode: <a way this could look done but isn't>
|
||||
- discriminator: <the concrete observation that tells real success from that failure>
|
||||
- verify: <optional shell command that exits 0 only when the discriminator passes; omit if not
|
||||
@@ -139,52 +141,136 @@ Conventions:
|
||||
|
||||
When the goals are drafted, present them and say the plan is final. Do not begin execution.`;
|
||||
|
||||
export function planningState(planPath: string): string {
|
||||
return `\
|
||||
[PLANNING MODE]
|
||||
The plan at ${planPath} is the only file you may change. Use read-only repository tools or web search
|
||||
when either can resolve a fact. Ask the human to confirm unresolved interpretation, outcome, task,
|
||||
scope, or a choice that needs their approval. Batch independent high-impact questions in one short,
|
||||
self-contained round with relevant context and a recommendation. Do not draft a placeholder goal
|
||||
without a concrete object, observable result, settled scope, and required approval. Do not execute
|
||||
work, mark a goal [/] or [x], or sign off a goal. The plan is not approved until the human selects
|
||||
Ready.`;
|
||||
// Planning and interview. Keep the full drafting guide one-shot rather than repeating it each turn.
|
||||
export function planning(planPath: string): string {
|
||||
return `Plan only in ${planPath}; do not implement or launch workers before Ready. Ask material unresolved questions, not a quota or confirmation of ordinary details. Record unknowns and present Ready when the outcome, scope and spending are settled. Preserve the user's exact deliverable, preferences and voice. Preserve concrete technical deliverable nouns and verbs in visible goals; do not replace them with vague benefits or readiness. Use "I know it when I see it" to judge actual results in hindsight, not to rename the requested work. Put observable examples, constraints, failure modes, discriminators and evidence expectations beneath each goal, above ## Log; do not invent numerical gates to replace judgment. Record the requested worker model in preferences. When your drafted plan is ready for human review, finish your turn; the interface displays the draft and approval choices automatically. Do not ask the user to type a command to see the proposal. /goals review reopens it on request; /goals exit preserves the draft.`;
|
||||
}
|
||||
export function planningSeed(objective: string, planPath: string): string {
|
||||
return `Enter a planning conversation focused on the user's goals. ${objective ? `Initial idea: ${objective}.` : "Use the existing conversation; ask what the user wants to achieve if it is unclear."} Read any existing plan at ${planPath} first, then discuss and draft it with the user. Do not infer approval to implement from starting this conversation. ${planning(planPath)}\n\n${planDrafting}`;
|
||||
}
|
||||
export const planDocument = (objective: string) => `# ${objective.split("\n")[0] || "Goal plan"}\n\n## Objective\n${objective}\n\n## Goals\n\n## Log\n`;
|
||||
export const discuss = "Type your changes in chat; the draft stays open.";
|
||||
|
||||
// Ready and explicit native peer attachment. No worker environment or agent-file contract.
|
||||
export const attachGoalPlanDescription = "Attach the absolute plan path explicitly supplied by the parent. On first attachment, also supply its exact Intercom parent UUID and requestId from the startup task. Read it without rewriting it. Restores plan context; grants no parent completion authority. No discovery or worker launch.";
|
||||
export const childPlanRole = "You are the delegated implementation worker. Save evidence and report progress for your delegated work; leave plan maintenance to the parent. Preserve agreed goals, requirements and discriminators; the supervisor owns goal-status changes and completion approval. Do not launch a second writer. Call AttachGoalPlan with the explicit plan path in your task before implementation (also after reconnect if unbound). Immediately report your actual Intercom UUID, saved-session path and current provider/model to the supplied supervisor ID. Identify unavailable fields as unknown; do not equate runtime IDs, session filenames and Intercom IDs. Send progress, completion and blocker reports there with artifact paths, then stay open for live messages. Do not exit or use caller_ping; unsent editor drafts are not visible in model context.";
|
||||
export function readyApproved(workerName: string, planPath: string, notedWorker: string | undefined, plan: string, supervisorId: string): string {
|
||||
const launch = notedWorker
|
||||
? `Inspect the recorded worker session ${notedWorker}; if live, steer that exact Intercom session. Do not open or replace its conversation. For stopped-writer recovery use OpenGoalWorker action recover with writersStopped=true after inspecting the actual old runtime and saved history. It restores context without replaying a task.`
|
||||
: `Use OpenGoalWorker with a bounded first task for '${workerName}'. It uses Nico project.open, not subagent.`;
|
||||
return `[pi-goals: approval — Ready]\nReady approved this plan: ${planPath}. Stay here as supervisor. ${launch} Confirm your actual Intercom UUID with status/list; your Pi session ID ${supervisorId} is a distinct field. Await explicit worker attachment and a report with actual Intercom UUID, saved-session path and resolved model. Inspect results and steer corrections in that same open session. A receipt or idle pane is not completion. Only after reviewing the exact saved session may OpenGoalWorker action fresh start independent context in that pane, retaining its saved history; supply reviewedThrough from the inspected latest entry.\n\n${quotedPlan(planPath, foldPlan(plan), "working set before Log")}`;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* 2. resync — EXEC, one-shot at session start and after a compaction: the WHOLE file back,
|
||||
* appendix included. Modelled on pi-goal-x's [POST-COMPACTION RESYNC] one-shot. This is the
|
||||
* only place the below-the-fold sections are pushed; otherwise the agent reads them on demand.
|
||||
* ──────────────────────────────────────────────────────────────────────── */
|
||||
export function resync(plan: string, planRel: string, why: string): string {
|
||||
return `\
|
||||
<system-reminder>
|
||||
${why} This is the whole plan file (${planRel}), appendix included. You are the implementation worker.
|
||||
Keep the high-level goal and human intent stable and do the work directly. A visible read-only Pi
|
||||
session supervises you through pi-supervise. The human's latest message outranks the plan: if it
|
||||
changes scope, amend the plan rather than preserving an obsolete decision.
|
||||
|
||||
${plan}
|
||||
</system-reminder>`;
|
||||
export function workerAssignment(plan: string, parent: string, requestId: string, task: string): string {
|
||||
return `You are already attached as the delegated goals-worker in this native Nico project pane for plan ${plan}; assignment ${requestId}. This grants worker context only, never supervisor approval authority. Read the complete supplied plan, applicable AGENTS.md and skills. Confirm the exact parent Intercom UUID ${parent} in the live roster; send it your initial actual Intercom UUID, saved-session path, resolved provider/model and thinking level. Do not infer one identity from another. Use normal tools; no model switch was requested by this launch. Implement only this assignment:\n\n${task}\n\nSave actual artifacts and verification output. Report blocked, error and result evidence through Intercom to that exact parent. The parent independently inspects and may send a concrete correction here. Do not approve goals or launch another writer. Respect human pauses and intervention. Keep this conversation open with the final review visible; do not exit, reset, switch session or close the pane.`;
|
||||
}
|
||||
// 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. Let each check-in follow what changed or needs attention, rather than repeat the previous recap.";
|
||||
export function supervisor(workerName: string, planPath: string, supervisorId: string): string {
|
||||
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: let the humor come from what actually happened. Avoid repeating recent jokes, nicknames or kaomoji; plain updates are welcome too. No forced cheerfulness or novelty. If supervision gets repetitive, step back 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, and workers can push back too. Keep the humor friendly and the criticism specific. -- Pi/Astra
|
||||
Use OpenGoalWorker for the first native project pane and stock Intercom for exact-session assignment/report/steering. Do not use subagent as a second backend. A stored binding is not proof of liveness; missing runtime state is not proof of stop. Verify actual Intercom identities with list/status; your Pi session ID is ${supervisorId}, a distinct field. Require artifact paths, saved verification and blocker/error reports. When the worker stops for any reason, inspect actual artifacts and saved messages before approving or correcting it in the same open session. A recap or receipt alone sends no instruction and proves no action. Record actual pane identity, '- worker session:' and '- worker intercom session:' with provenance. CompleteGoal belongs only to this parent or explicitly confirmed solo self-verification.
|
||||
Keep normal tools and honor human model changes. project.open has no model override: inspect the native worker's resolved model; if a requested model is unavailable, report it rather than silently substituting. After compaction reread the plan. Lost connection or exhausted credits does not erase work. Preserve drafts and saved sessions; confirm other writers stopped before solo takeover. OpenGoalWorker action recover restores an owned durable session only after confirmed stopped writers, without replaying work or changing its model. Action fresh requires the exact last reviewed entry and an idle draft-free peer, and retains the old history. Revisions use ordinary Intercom in the same context. Requested-model automation currently fails closed at the public setter race; no preference means keep the native default. Never replace an unreviewed conversation or start a duplicate writer.`;
|
||||
}
|
||||
// Routine notices quote only selected goal lines; full context stops at Log.
|
||||
const goalLines = (text: string) => foldPlan(text).split("\n").filter(line => GOAL_LINE.test(line)).join("\n");
|
||||
export function upkeep(planPath: string, text: string): string {
|
||||
return `[pi-goals: reminder — upkeep]\nEight unchanged turns: update task ticks, evidence or Log only for new progress. Finish any evidence review already underway; do not restart completed or paused work.\n\n${quotedPlan(planPath, goalLines(text), "unfinished or unreviewed goal lines")}`;
|
||||
}
|
||||
export function planContext(mode: string, path: string | undefined, text: string, tier: "short" | "medium" | "full" = "full"): string {
|
||||
return `[pi-goals: context resync]\nCurrent goal mode: ${mode}. Earlier role messages are historical; this current role governs. Read the plan file for details and earlier evidence; do not restart completed work.\n\n${quotedPlan(path, tier === "full" ? foldPlan(text) : goalLines(text), tier === "full" ? "active plan above Log" : "unfinished or unreviewed goal lines")}`;
|
||||
}
|
||||
export function planChangedReview(planPath: string, text = ""): string {
|
||||
return `[pi-goals: reminder — plan changed]\nPlan changed: inspect current requirements, completion claims and evidence at ${planPath}. Evidence-only edits do not revoke execution approval. Continue only unfinished authorized work; respect pauses and do not assume approval for changed scope. Manual ticks are claims, not sign-off. Do not start a duplicate writer.${text ? `\n\n${quotedPlan(planPath, goalLines(text), "selected goal lines")}` : ""}`;
|
||||
}
|
||||
export function workerReview(plan: string, session: string, text: string): string {
|
||||
return `Worker event for ${plan}, exact Intercom session ${session}:\n${text}\nThis is a report, not completion approval. Inspect actual artifacts and saved messages; if correction is needed, send it to the same session. Preserve its visible review conversation. Respect pauses; do not reply merely to acknowledge.`;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* 3. completeGoal — SIGN-OFF, agent-side: the one blessed tool
|
||||
* ──────────────────────────────────────────────────────────────────────── */
|
||||
export const completeGoalDescription =
|
||||
"Sign off a goal once its discriminator is satisfied. First fill the goal's evidence: list in the " +
|
||||
"plan file: each item pairs a durable artifact with a short read of it (a quoted+linked log, a " +
|
||||
"table plus how to read it, a metric plus what it shows -- not a bare claim). Quote verbatim from " +
|
||||
"output you actually observed; never reconstruct numbers from memory. If you couldn't see an " +
|
||||
"output, rerun it or write that you couldn't -- an honest gap beats a plausible fabrication. If " +
|
||||
"the goal names a verify: command, direct the worker to run it and save its output to a file cited " +
|
||||
"in the evidence. The supervisor may run an allowed read-only verification command, but must not " +
|
||||
"create the evidence file itself. The visible supervisor must reject a claimed pass with no saved " +
|
||||
"output. The read must show success POSITIVELY happened, not just that failures were avoided. The " +
|
||||
"supervisor records an approval checkpoint only after it inspected the current plan, repository, " +
|
||||
"evidence, verify output, and a stopped worker view with no active work. Then the worker calls this " +
|
||||
"tool with the exact goal text. This tool independently checks that checkpoint " +
|
||||
"against the exact current goal block, HEAD/tree, and clean worktree before it appends the sign-off to " +
|
||||
"## Log and ticks the goal [x]. If any check differs, it fails closed and requires a fresh supervisor review.";
|
||||
export function manualReview(planPath: string, text: string): string {
|
||||
return `[pi-goals: reminder — requested review]\nReview requested: inspect the plan and actual evidence. Do not launch a duplicate writer.\n\n${quotedPlan(planPath, goalLines(text), "unfinished or unreviewed goal lines")}`;
|
||||
}
|
||||
export function finalReview(planPath: string, text: string): string {
|
||||
return `[pi-goals: reminder — final completion review]\nFinal completion review: the preceding CompleteGoal request did not record approval. Read the complete file at ${planPath}, including requirements, evidence and Log, and inspect the cited artifacts yourself. Then call CompleteGoal again with the exact remaining goal and evidence. Changed requirements need a new review. Plan revision: ${createHash("sha256").update(text).digest("hex")}.\n\n${quotedPlan(planPath, goalLines(text), "selected goal lines")}`;
|
||||
}
|
||||
|
||||
export const completeGoalParamDescription = "The goal's text: the line after 'goal:' in the plan file.";
|
||||
// Check-ins. The installed scheduler owns storage/timing/UI. Removal guidance must never add jobs.
|
||||
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: Hourly goal check-in: 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.
|
||||
export const completeGoalDescription = "Parent supervisor or solo self-verification only. Inspect the actual artifact and saved verification first; cite nonempty evidence files and describe what you observed. Exact goal subject required. The final remaining goal first queues a full-plan review; call CompleteGoal again from that review to record it. Manual ticks and worker reports are claims; ignored/uncommitted evidence is allowed. This records judgment, not an independent judge.";
|
||||
export const messages = {
|
||||
noPlan: "no plan attached",
|
||||
emptyPlan: "empty plan (save may be in progress)",
|
||||
completionUnavailable: "Completion is available only to the active parent supervisor or solo worker.",
|
||||
cancelled: "Cancelled; no sign-off recorded.",
|
||||
uniqueGoal: "Use one unique exact goal subject from the plan; no sign-off recorded.",
|
||||
childAttachOnly: "AttachGoalPlan is available only to the delegated goals-worker.",
|
||||
invalidAttachment: "Supply the explicit absolute path from the parent task to a readable, nonempty goal plan; no attachment changed.",
|
||||
};
|
||||
export const goalToolBlocked = (mode: string) => `Goals are ${mode}; no worker launch/resume authorized.`;
|
||||
export const emptyEvidence = (path: string) => `Empty evidence: ${path}`;
|
||||
export const evidenceUnavailable = (error: unknown) => `Evidence unavailable: ${String(error)}. No sign-off recorded.`;
|
||||
export const planUnavailable = (path: string | undefined, error: unknown) => `Goal plan ${path ?? "not attached"} unavailable: ${String(error)}. Do not implement or sign off until it is restored or explicitly attached. Retain all progress and signoffs; do not restart completed work.`;
|
||||
export const childPlanAttached = (path: string) => `Attached worker plan ${path}; plan context restored without altering the file. Parent retains completion authority.`;
|
||||
export function completionLog(goal: string, observation: string, evidence: string[], solo: boolean): string {
|
||||
return `- ${solo ? "Solo self-verification" : "Parent review"}: ${JSON.stringify(goal)}; ${JSON.stringify(observation)}; evidence ${JSON.stringify(evidence)}`;
|
||||
}
|
||||
export function finalReviewQueued(goal: string): string {
|
||||
return `Final review queued for ${goal}; no sign-off recorded. Read the complete plan file and actual evidence in that review run, then call CompleteGoal again with the exact goal and evidence.`;
|
||||
}
|
||||
export const finalReviewInvalidated = "The plan changed since the final review was queued; no sign-off recorded. Inspect the current plan and request completion again to queue a new final review.";
|
||||
export function completionResult(goal: string, sessionId: string, remaining: boolean, solo: boolean): string {
|
||||
return `Recorded ${solo ? "solo self-verification" : "parent judgment"} for ${goal}; not independent verification. ${remaining ? "Continue only remaining open or unsigned goals in your current role." : `All non-cancelled goals are reviewed. ${removeGoalSchedule(sessionId)}`}`;
|
||||
}
|
||||
|
||||
// Pause/resume and solo recovery. Stored stop confirmation is invalidated on every worker launch.
|
||||
export const pausedRole = "Goal work is paused. Do not launch, resume or authorize work. Incoming reports are observations, not permission. Help inspect or stop existing workers if requested.";
|
||||
export function pauseExitNotice(worker: { intercomId?: string; sessionFile?: string; paneId?: string } | undefined, exited: boolean): string {
|
||||
return `Goals ${exited ? "exited to ordinary chat" : "paused locally"}; plan and evidence retained. ${worker ? `Locate the recorded native pane ${worker.paneId ?? "unknown"}, Intercom session ${worker.intercomId ?? "unknown"}, saved session ${worker.sessionFile ?? "unknown"}. Send an explicit pause there; inspect and confirm actual stop without closing the review conversation.` : "No worker recorded: inspect Intercom and native panes; absence is not proof of stop."} Remote stop is NOT yet confirmed. Resume only after explicit authorization.`;
|
||||
}
|
||||
export function resumeNotice(workerName: string, planPath: string, worker: { sessionFile?: string; intercomId?: string } | undefined): string {
|
||||
return `User authorized continuation of ${planPath}. Inspect worker state before any launch. ${worker ? `Use the existing session ${worker.sessionFile ?? "unknown"} and exact Intercom UUID ${worker.intercomId ?? "unknown"}; if live, inspect/message it. Do not open a replacement. For saved-session recovery use OpenGoalWorker action recover only after confirming all other writers stopped; do not pass a task or model to replay.` : `Use OpenGoalWorker for '${workerName}' only after confirming no prior writer exists.`} Continue only unfinished goals; retain saved progress and scheduler edits.`;
|
||||
}
|
||||
export const soloRole = "Solo mode: implement the approved plan directly; do not delegate a concurrent writer. Verify artifacts before CompleteGoal; completion is self-verification, not independent supervisor review. Continue only unfinished goals and keep plan/evidence current.";
|
||||
export function soloNotice(planPath: string): string {
|
||||
return `User authorized solo work on ${planPath} after confirming no other writer remains. ${soloRole}`;
|
||||
}
|
||||
export function attachNotice(planPath: string, solo: boolean, notedWorker: string | undefined): string {
|
||||
return `Attached to the existing plan ${planPath}; read it and its evidence without restarting completed work or re-deriving settled decisions. ${notedWorker ? `Recorded worker session: ${notedWorker}; inspect liveness before resume.` : ""} ${solo ? soloRole : "Present /goals review or /goals ready; no implementation before approval."}`;
|
||||
}
|
||||
|
||||
export const nativeMessages = {
|
||||
actionApplied: (action: string, peer: unknown) => `Native ${action} confirmed in the exact peer: ${JSON.stringify(peer)}. Recovery submits no task and does not change the model. Inspect the observed identity and durable history before further steering.`,
|
||||
workerPause: (paused: boolean) => `Worker ${paused ? "paused" : "unpaused"} locally; no new task submitted and no approval authority granted.`,
|
||||
notDurable: "No durable owned worker transcript is available; a prospective saved path is not recovery evidence.",
|
||||
notOwned: "The exact session/plan/parent ownership does not match; no worker action taken.",
|
||||
controlDescription: "Verified native worker control only; arbitrary command text never starts work.",
|
||||
controlRejected: "Native worker control rejected; inspect actual peer state before retrying.",
|
||||
controlChanged: "Worker identity, history, input, pause or writer state changed; no replacement/task authorized.",
|
||||
controlCancelled: "Native session replacement was cancelled; prior history remains active.",
|
||||
reviewRequired: "Fresh work requires the exact last reviewed session entry ID; preserve the current conversation until reviewed.",
|
||||
stopRequired: "Recovery requires confirmed stopped writers and a durable owned saved session. No task replay or model override is accepted on recovery.",
|
||||
taskRequired: "Supply an explicit bounded task for a new or fresh worker context.",
|
||||
modelRaceBoundary: "Requested-model startup is held: public setModel cannot atomically preserve a concurrent human selection. No task was submitted under a fallback.",
|
||||
openDescription: "After Ready, use action start for a blank native peer and bounded task. Use fresh only after inspecting the recorded session and supplying its exact reviewedThrough leaf ID; old history is retained. Use recover with writersStopped=true and the owned saved session, never a task replay. Requested models currently fail closed at an unresolved public setter race. A pane receipt is not actual attachment or completion.",
|
||||
disconnected: "Intercom disconnected; liveness and stop are unconfirmed. Inspect the saved session and pane; do not launch a replacement.",
|
||||
shuttingDown: "Worker session shutting down; inspect its last saved messages. No goal sign-off inferred.",
|
||||
noAssistant: "Worker run ended without an assistant result; inspect saved messages.",
|
||||
alreadyRecorded: "A worker is already recorded or opening. Inspect its native pane and exact Intercom session; do not create a duplicate or replace its conversation.",
|
||||
noIdentity: "Intercom identity unavailable; no worker opened.",
|
||||
openReceipt: "\nAwait verified peer capability and actual action confirmation. An existing binding is only a surface; draft, history and ownership checks still apply. Do not resend or infer work from this receipt.",
|
||||
openFailed: "Native open failed; inspect binding and possible live writer before retry or solo takeover: ",
|
||||
parentUnavailable: "Parent Intercom identity is not live; no worker attachment changed.",
|
||||
intercomNotReady: "Intercom is still connecting. Call intercom status/list, verify the live parent identity, then retry this operation in the same session. No attachment or launch changed.",
|
||||
reportUnavailable: "Automatic worker notice could not reach Intercom. The saved result remains here; restore the connection and report to the exact parent. Do not infer delivery or completion.",
|
||||
attached: (sessionFile: string) => `Worker attached. Saved session: ${sessionFile}. Attachment is not completion.`,
|
||||
};
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const PAIR_EVENT = "pi-supervise:pair:v1";
|
||||
const WORKER_STATE_EVENT = "pi-supervise:worker-state:v1";
|
||||
const WORKER_PAIRED_EVENT = "pi-supervise:worker-paired:v1";
|
||||
const API_READY_EVENT = "pi-supervise:api-ready:v1";
|
||||
const TIMEOUT_MS = 15_000;
|
||||
export const SUPERVISOR_STARTUP_TIMEOUT_MS = 5 * 60_000;
|
||||
|
||||
type Events = { emit(name: string, value: unknown): boolean; on(name: string, handler: (value: any) => void): void };
|
||||
|
||||
function wait<T>(start: (resolve: (value: T) => void, reject: (error: Error) => void) => void, message: string, timeoutMs = TIMEOUT_MS): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
||||
start((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });
|
||||
});
|
||||
}
|
||||
|
||||
export function pairWithPiSupervise(pi: ExtensionAPI, workerIntercomId: string, goal: string): Promise<void> {
|
||||
const events = (pi as unknown as { events: Events }).events;
|
||||
return wait((resolve, reject) => events.emit(PAIR_EVENT, { version: 1, workerIntercomId, goal, resolve, reject }), "pi-supervise did not accept the visible-supervisor pairing request.");
|
||||
}
|
||||
|
||||
export interface WorkerPiSupervise {
|
||||
intercomId: string;
|
||||
waitForPair(timeoutMs?: number): Promise<void>;
|
||||
}
|
||||
|
||||
export function workerPiSupervise(pi: ExtensionAPI, timeoutMs = TIMEOUT_MS): Promise<WorkerPiSupervise> {
|
||||
const events = (pi as unknown as { events: Events }).events;
|
||||
let paired = false;
|
||||
let resolvePair: (() => void) | undefined;
|
||||
events.on(WORKER_PAIRED_EVENT, () => {
|
||||
paired = true;
|
||||
resolvePair?.();
|
||||
});
|
||||
return wait((resolve, reject) => {
|
||||
let resolved = false;
|
||||
const request = () => events.emit(WORKER_STATE_EVENT, (state: { intercomId?: string; paired?: boolean }) => {
|
||||
if (resolved) return;
|
||||
if (!state.intercomId) return reject(new Error("pi-supervise returned no worker intercom ID."));
|
||||
if (state.paired) return reject(new Error("This worker is already paired with a supervisor. Stop that supervision before selecting Ready."));
|
||||
resolved = true;
|
||||
resolve({
|
||||
intercomId: state.intercomId,
|
||||
waitForPair: (pairTimeoutMs = timeoutMs) => paired ? Promise.resolve() : wait((pairResolve) => { resolvePair = pairResolve; }, "The visible supervisor did not pair with this worker.", pairTimeoutMs),
|
||||
});
|
||||
});
|
||||
events.on(API_READY_EVENT, request);
|
||||
request();
|
||||
}, "pi-supervise did not publish this worker's intercom state.", timeoutMs);
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
import { approvalPath, goalBlock, hashGoalBlock, repositoryState, verifyOutputPath, writeApproval } from "./approval.js";
|
||||
import { pairWithPiSupervise } from "./supervise.js";
|
||||
|
||||
const BOOTSTRAPPED = "pi-goals-visible-supervisor-v1";
|
||||
const INITIAL_COMPACT_AT_TOKENS = 20_000;
|
||||
const COMPACT_AT_TOKENS = 100_000;
|
||||
|
||||
interface SupervisorConfig {
|
||||
workerSessionId: string;
|
||||
workerIntercomId: string;
|
||||
ownerSessionId: string;
|
||||
planPath: string;
|
||||
approvalId: string;
|
||||
}
|
||||
|
||||
function result(text: string, isError = false) {
|
||||
return { content: [{ type: "text" as const, text }], details: {}, isError };
|
||||
}
|
||||
|
||||
function requiredEnv(name: string): string {
|
||||
const value = process.env[name]?.trim();
|
||||
if (!value) throw new Error(`${name} is required in a pi-goals supervisor session.`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function config(): SupervisorConfig {
|
||||
return {
|
||||
workerSessionId: requiredEnv("PI_GOALS_WORKER_ID"),
|
||||
workerIntercomId: requiredEnv("PI_GOALS_WORKER_INTERCOM_ID"),
|
||||
ownerSessionId: requiredEnv("PI_GOALS_OWNER_SESSION_ID"),
|
||||
planPath: resolve(requiredEnv("PI_GOALS_PLAN_PATH")),
|
||||
approvalId: requiredEnv("PI_GOALS_APPROVAL_ID"),
|
||||
};
|
||||
}
|
||||
|
||||
function hasEvidenceEntry(block: string): boolean {
|
||||
const lines = block.split("\n");
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
const evidence = /^\s*[-*]\s+evidence:\s*(.*)$/i.exec(lines[index]);
|
||||
if (!evidence) continue;
|
||||
if (evidence[1].trim() && !/^\(empty until sign-off\)$/i.test(evidence[1].trim())) return true;
|
||||
const indent = lines[index].match(/^\s*/)?.[0].length ?? 0;
|
||||
for (let child = index + 1; child < lines.length; child++) {
|
||||
const childIndent = lines[child].match(/^\s*/)?.[0].length ?? 0;
|
||||
if (lines[child].trim() && childIndent <= indent) break;
|
||||
const entry = /^\s+[-*]\s+(.+?)\s*$/.exec(lines[child]);
|
||||
if (entry?.[1].trim()) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function latestWorkerView(ctx: ExtensionContext): string | null {
|
||||
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
||||
const message = (entry as { type?: string; message?: { role?: string; content?: unknown[] } }).message;
|
||||
if ((entry as { type?: string }).type !== "message" || message?.role !== "user" || !Array.isArray(message.content)) continue;
|
||||
for (const part of message.content) {
|
||||
const text = (part as { type?: string; text?: string }).type === "text" ? (part as { text?: string }).text : undefined;
|
||||
if (text?.startsWith("The worker ")) return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 pi-supervise worker views. The complete plan at ${settings.planPath} is the source of truth; read it directly after every compaction.
|
||||
|
||||
Use pi-supervise to inspect and steer the worker. Give one concrete instruction when work is incomplete. Do not edit files. For each open goal, inspect its exact plan block, repository state, cited evidence, and a saved nonempty verification-output file. When its discriminator is positively satisfied and the worker view says no work is active, call ApproveGoal with that repository-relative path. Then call steer and tell the worker to call CompleteGoal with the exact goal text. Do not call done until every plan goal is [x]. -- PI[gpt-5.6-sol]`;
|
||||
}
|
||||
|
||||
export function isVisibleSupervisor(): boolean {
|
||||
return process.env.PI_GOALS_ROLE === "supervisor";
|
||||
}
|
||||
|
||||
export function registerVisibleSupervisor(pi: ExtensionAPI): void {
|
||||
const settings = config();
|
||||
let compacting = false;
|
||||
let bootstrapping = false;
|
||||
|
||||
const bootstrap = async (ctx: ExtensionContext): Promise<void> => {
|
||||
if (bootstrapping) return;
|
||||
const entries = ctx.sessionManager.getEntries();
|
||||
if (entries.some((entry: { type?: string; customType?: string }) => entry.type === "custom" && entry.customType === BOOTSTRAPPED)) return;
|
||||
bootstrapping = true;
|
||||
try {
|
||||
await pairWithPiSupervise(pi, settings.workerIntercomId, settings.planPath);
|
||||
pi.appendEntry(BOOTSTRAPPED, { version: 1, workerSessionId: settings.workerSessionId, planPath: settings.planPath });
|
||||
pi.sendUserMessage("Supervision is paired. Inspect the worker and give its next concrete instruction.");
|
||||
} catch (error) {
|
||||
ctx.ui.notify(`Supervisor startup failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
||||
}
|
||||
};
|
||||
|
||||
const bootstrapAfterInitialCompaction = (ctx: ExtensionContext): void => {
|
||||
const tokens = ctx.getContextUsage()?.tokens;
|
||||
if (typeof tokens === "number" && tokens <= INITIAL_COMPACT_AT_TOKENS) {
|
||||
void bootstrap(ctx);
|
||||
return;
|
||||
}
|
||||
compacting = true;
|
||||
ctx.compact({
|
||||
customInstructions: `Preserve the user's high-level intent, decisions, unresolved risks, and the supervisor's remit. The canonical plan is ${settings.planPath}; it remains available directly and must not be replaced by this summary.`,
|
||||
onComplete: () => {
|
||||
compacting = false;
|
||||
ctx.ui.notify("Supervisor planning context compacted before work started.", "info");
|
||||
void bootstrap(ctx);
|
||||
},
|
||||
onError: (error) => {
|
||||
compacting = false;
|
||||
ctx.ui.notify(`Supervisor startup compaction failed: ${error.message}`, "error");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
setImmediate(() => { bootstrapAfterInitialCompaction(ctx); });
|
||||
});
|
||||
|
||||
pi.on("before_agent_start", async (_event, ctx) => {
|
||||
return { systemPrompt: `${ctx.getSystemPrompt()}\n\n${supervisorPrompt(settings)}` };
|
||||
});
|
||||
|
||||
pi.on("agent_settled", async (_event, ctx) => {
|
||||
if (compacting || (ctx.getContextUsage()?.tokens ?? 0) < COMPACT_AT_TOKENS) return;
|
||||
compacting = true;
|
||||
ctx.compact({
|
||||
customInstructions: `Keep the user's high-level intent, current plan state, unresolved risks, approval decisions, and the supervisor's own concise findings. Remove old worker views and implementation detail.`,
|
||||
onComplete: () => {
|
||||
compacting = false;
|
||||
ctx.ui.notify("Supervisor context compacted at 100k tokens.", "info");
|
||||
},
|
||||
onError: (error) => {
|
||||
compacting = false;
|
||||
ctx.ui.notify(`Supervisor compaction failed: ${error.message}`, "error");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "ApproveGoal",
|
||||
label: "Approve goal",
|
||||
executionMode: "sequential",
|
||||
description: "Record approval after inspecting the current goal, repository, evidence, and a saved nonempty verification-output file, with a stopped worker view and no active work.",
|
||||
parameters: Type.Object({
|
||||
goal: Type.String({ description: "Exact text after goal: in the plan." }),
|
||||
verifyOutputPath: Type.String({ description: "Nonempty repository-relative file containing the verification output you inspected." }),
|
||||
}),
|
||||
async execute(_id, params, _signal, _onUpdate, ctx) {
|
||||
const view = latestWorkerView(ctx);
|
||||
if (!view?.startsWith("The worker stopped.")) return result("Cannot approve without a current stopped-worker view.", true);
|
||||
const pendingTool = view.match(/^tool calls with no result: (?!none$)(.+)$/m);
|
||||
const pendingChild = view.match(/^child pi processes still running: (?!none$)(.+)$/m);
|
||||
if (pendingTool || pendingChild) return result(`Cannot approve while work is active: ${(pendingTool ?? pendingChild)![1]}`, true);
|
||||
let plan: string;
|
||||
let repository: ReturnType<typeof repositoryState>;
|
||||
try {
|
||||
plan = readFileSync(settings.planPath, "utf8");
|
||||
repository = repositoryState(ctx.cwd);
|
||||
} catch (error) {
|
||||
return result(`Cannot inspect approval inputs: ${error instanceof Error ? error.message : String(error)}`, true);
|
||||
}
|
||||
if (!repository.cleanWorktree) return result("Cannot approve with a dirty worktree. Commit the worker changes first.", true);
|
||||
const block = goalBlock(plan, params.goal);
|
||||
if (!block) return result(`Cannot approve: no unique open goal matches "${params.goal}".`, true);
|
||||
if (!hasEvidenceEntry(block)) return result("Cannot approve without a nonblank evidence entry in the goal block.", true);
|
||||
const verifiedOutput = verifyOutputPath(repository.repoRoot, params.verifyOutputPath);
|
||||
if (!verifiedOutput) return result("Cannot approve without a nonempty repository-relative verification-output file.", true);
|
||||
const path = approvalPath(ctx.cwd, settings.ownerSessionId, params.goal);
|
||||
writeApproval(path, {
|
||||
version: 3,
|
||||
verdict: "accept",
|
||||
approvalId: settings.approvalId,
|
||||
goal: params.goal,
|
||||
planPath: settings.planPath,
|
||||
goalBlockHash: hashGoalBlock(block),
|
||||
repoRoot: repository.repoRoot,
|
||||
head: repository.head,
|
||||
tree: repository.tree,
|
||||
cleanWorktree: true,
|
||||
inspected: { plan: true, repository: true, evidence: true, verifyOutput: true },
|
||||
verifyOutputPath: verifiedOutput,
|
||||
supervisor: { sessionId: ctx.sessionManager.getSessionId(), runId: null },
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
return result(`Approval recorded for "${params.goal}". Now steer the worker to call CompleteGoal.`);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendLog } from "../src/index.js";
|
||||
|
||||
describe("appendLog (the extension's only plan-file write)", () => {
|
||||
it("creates ## Log at EOF when absent", () => {
|
||||
const out = appendLog("# Plan\n\n## Goals\n\n1. [ ] goal: x\n", "2026-07-03 10:00 signed off \"x\" (judge accept)");
|
||||
expect(out).toContain("## Log\n- 2026-07-03 10:00 signed off");
|
||||
});
|
||||
|
||||
it("appends after the last existing log line, before any following header", () => {
|
||||
const plan = "# Plan\n\n## Log\n- first\n- second\n\n# Future work\n- later\n";
|
||||
const out = appendLog(plan, "third");
|
||||
const lines = out.split("\n");
|
||||
expect(lines[lines.indexOf("- second") + 1]).toBe("- third");
|
||||
expect(out.indexOf("- third")).toBeLessThan(out.indexOf("# Future work"));
|
||||
});
|
||||
});
|
||||
+45
-11
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { foldPlan, openSubtasks } from "../src/index.js";
|
||||
import { foldPlan, goalAcceptanceSignature } from "../src/plan.js";
|
||||
|
||||
const plan = `# Plan
|
||||
|
||||
@@ -28,7 +28,7 @@ const plan = `# Plan
|
||||
## Appendix (context, not approved)
|
||||
${"filler line\n".repeat(200)}`;
|
||||
|
||||
describe("foldPlan (current goals are above ## Log; durable memory is below it)", () => {
|
||||
describe("foldPlan (current goals are above Log; durable memory is below it)", () => {
|
||||
it("keeps the title, user voice and goals", () => {
|
||||
const folded = foldPlan(plan);
|
||||
expect(folded).toContain("keep it under 50 lines");
|
||||
@@ -44,20 +44,54 @@ describe("foldPlan (current goals are above ## Log; durable memory is below it)"
|
||||
expect(folded.length).toBeLessThan(plan.length / 4);
|
||||
});
|
||||
|
||||
it("returns the whole plan when there is no ## Log yet (a fresh draft)", () => {
|
||||
it.each(["# Log", "## Log", "### Log", "###### Log", "### LOG\r"])("accepts the %s history boundary", heading => {
|
||||
expect(foldPlan(`- [ ] goal: current\n${heading}\n- [ ] goal: archived`)).toBe("- [ ] goal: current");
|
||||
});
|
||||
|
||||
it("returns the whole plan when there is no Log yet (a fresh draft)", () => {
|
||||
const draft = "# Plan\n\n## Goals\n\n1. [ ] goal: do the thing\n";
|
||||
expect(foldPlan(draft)).toBe(draft.trimEnd());
|
||||
});
|
||||
});
|
||||
|
||||
describe("openSubtasks (the widget shows the next action, so the plan IS the task list)", () => {
|
||||
const active = plan.split("\n").findIndex((l) => l.includes("goal: Implement the cache layer"));
|
||||
const acceptancePlan = `# Plan
|
||||
## User-visible result
|
||||
Produce a verified result.
|
||||
- preferred worker model: provider/model
|
||||
- worker session: /worker.jsonl
|
||||
## Goals
|
||||
1. [ ] goal: first
|
||||
- discriminator: exact bytes
|
||||
- tasks:
|
||||
- [ ] write output
|
||||
- evidence:
|
||||
- proof.log
|
||||
2. [ ] goal: second
|
||||
- discriminator: correct total
|
||||
## Log
|
||||
Old progress
|
||||
`;
|
||||
|
||||
it("lists the active goal's open and in-progress subtasks, stopping at the next goal", () => {
|
||||
expect(openSubtasks(plan, active)).toEqual(["eviction policy", "bench p95"]);
|
||||
});
|
||||
it.each([
|
||||
["[ ] goal: first", "[x] goal: first"],
|
||||
["[ ] write output", "[x] write output"],
|
||||
["proof.log", "new-proof.log"],
|
||||
["goal: second", "goal: changed second"],
|
||||
["provider/model", "provider/other"],
|
||||
["/worker.jsonl", "/resumed.jsonl"],
|
||||
["Old progress", "More history"],
|
||||
])("approval ignores maintenance change %s", (before, after) => {
|
||||
expect(goalAcceptanceSignature(acceptancePlan.replace(before, after), "first")).toBe(goalAcceptanceSignature(acceptancePlan, "first"));
|
||||
});
|
||||
|
||||
it("does not leak subtasks from the goal below", () => {
|
||||
expect(openSubtasks(plan, active)).not.toContain("write the readme");
|
||||
});
|
||||
it.each([
|
||||
["exact bytes", "a different acceptance criterion"],
|
||||
["Produce a verified result.", "Produce two verified results."],
|
||||
])("approval changes when requirement %s changes", (before, after) => {
|
||||
expect(goalAcceptanceSignature(acceptancePlan.replace(before, after), "first")).not.toBe(goalAcceptanceSignature(acceptancePlan, "first"));
|
||||
});
|
||||
|
||||
it("does not give a signature to missing or duplicate goals", () => {
|
||||
expect(goalAcceptanceSignature(acceptancePlan, "missing")).toBeUndefined();
|
||||
expect(goalAcceptanceSignature(acceptancePlan.replace("goal: second", "goal: first"), "first")).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { approvalPath, goalBlock, hashGoalBlock, repositoryState, writeApproval } from "../src/approval.js";
|
||||
|
||||
const openSupervisorPane = vi.fn(async () => "pane-2");
|
||||
const closeSupervisorPane = vi.fn(async () => undefined);
|
||||
vi.mock("../src/herdr.js", () => ({ openSupervisorPane, closeSupervisorPane }));
|
||||
const { default: piGoalsExtension, isMainSession } = await import("../src/index.js");
|
||||
|
||||
function setup(selectChoices: Array<string | undefined>, editorChoices: Array<string | undefined> = []) {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-flow-"));
|
||||
writeFileSync(join(cwd, ".gitignore"), ".pi/\n");
|
||||
writeFileSync(join(cwd, "verify.txt"), "PASS\n");
|
||||
execFileSync("git", ["init", "-q"], { cwd });
|
||||
execFileSync("git", ["add", ".gitignore", "verify.txt"], { cwd });
|
||||
execFileSync("git", ["-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "initial"], { cwd });
|
||||
const commands = new Map<string, any>();
|
||||
const hooks = new Map<string, any>();
|
||||
const tools = new Map<string, any>();
|
||||
const entries: Array<{ type: string; customType: string; data: unknown }> = [];
|
||||
const messages: Array<{ content: string; display?: boolean }> = [];
|
||||
const notifications: string[] = [];
|
||||
const ctx = {
|
||||
cwd,
|
||||
hasUI: true,
|
||||
getSystemPrompt: () => "base prompt",
|
||||
sessionManager: {
|
||||
getSessionId: () => "session-a",
|
||||
getSessionFile: () => join(cwd, "session.jsonl"),
|
||||
getEntries: () => entries,
|
||||
},
|
||||
ui: {
|
||||
theme: { fg: (_kind: string, text: string) => text },
|
||||
setStatus: vi.fn(),
|
||||
setWidget: vi.fn(),
|
||||
notify: (text: string) => notifications.push(text),
|
||||
select: async () => selectChoices.shift(),
|
||||
editor: async () => editorChoices.shift(),
|
||||
},
|
||||
};
|
||||
const events = new EventEmitter();
|
||||
events.on("pi-supervise:worker-state:v1", (reply) => reply({ intercomId: "worker-intercom" }));
|
||||
openSupervisorPane.mockImplementation(async () => {
|
||||
queueMicrotask(() => events.emit("pi-supervise:worker-paired:v1", { supervisorIntercomId: "supervisor-intercom" }));
|
||||
return "pane-2";
|
||||
});
|
||||
const pi = {
|
||||
events,
|
||||
registerCommand: (name: string, command: any) => commands.set(name, command),
|
||||
on: (name: string, handler: any) => hooks.set(name, handler),
|
||||
appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }),
|
||||
registerTool: (tool: any) => tools.set(tool.name, tool),
|
||||
getAllTools: () => [],
|
||||
sendMessage: (message: { content: string; display?: boolean }) => messages.push(message),
|
||||
sendUserMessage: (content: string) => messages.push({ content }),
|
||||
};
|
||||
piGoalsExtension(pi as unknown as ExtensionAPI);
|
||||
return { commands, ctx, cwd, entries, events, hooks, messages, notifications, tools };
|
||||
}
|
||||
|
||||
function writePlan(cwd: string, content: string): string {
|
||||
const path = join(cwd, ".pi/plan/session-a-v1.md");
|
||||
mkdirSync(join(cwd, ".pi/plan"), { recursive: true });
|
||||
writeFileSync(path, content);
|
||||
return path;
|
||||
}
|
||||
|
||||
function approvedPlan(cwd: string): string {
|
||||
return writePlan(cwd, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - discriminator: output exists\n - evidence:\n - `result.txt`: contains ok\n\n## Log\n");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
openSupervisorPane.mockClear();
|
||||
closeSupervisorPane.mockClear();
|
||||
});
|
||||
|
||||
describe("/goals flow", () => {
|
||||
it("preserves drafts, records the interview, and keeps planning read-only", async () => {
|
||||
const flow = setup(["Refine"], ["Keep two columns."]);
|
||||
try {
|
||||
await flow.commands.get("goals").handler("first objective", flow.ctx);
|
||||
const first = writePlan(flow.cwd, "# Plan\n\n## Goals\n\n1. [ ] goal: preserve this\n\n## Interview\n");
|
||||
await flow.hooks.get("input")({ text: "Preserve column order.", source: "interactive" }, flow.ctx);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
expect(readFileSync(first, "utf8")).toContain("> Preserve column order.");
|
||||
expect(readFileSync(first, "utf8")).toContain("> Keep two columns.");
|
||||
expect(flow.messages.at(-1)?.content).toContain("Revise the plan at");
|
||||
expect((await flow.hooks.get("tool_call")({ toolName: "edit", input: { path: "README.md" } }, flow.ctx))?.block).toBe(true);
|
||||
|
||||
await flow.commands.get("goals").handler("second objective", flow.ctx);
|
||||
expect(readFileSync(first, "utf8")).toContain("preserve this");
|
||||
expect(flow.messages.at(-1)?.content).toContain("session-a-v2.md");
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("forks a visible supervisor on Ready and keeps the main session as worker", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
await flow.commands.get("goals").handler("make the file", flow.ctx);
|
||||
const planPath = approvedPlan(flow.cwd);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
expect(openSupervisorPane).toHaveBeenCalledWith(expect.objectContaining({
|
||||
cwd: flow.cwd,
|
||||
sourceSessionFile: join(flow.cwd, "session.jsonl"),
|
||||
workerSessionId: "session-a",
|
||||
workerIntercomId: "worker-intercom",
|
||||
planPath,
|
||||
}));
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", supervisorPaneId: "pane-2" });
|
||||
expect(flow.messages.at(-1)?.content).toBe("The plan is approved. Begin implementation as the worker.");
|
||||
const prompt = await flow.hooks.get("before_agent_start")({}, flow.ctx);
|
||||
expect(prompt.systemPrompt).toContain("implementation worker");
|
||||
expect(prompt.systemPrompt).toContain("stronger read-only supervisor");
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns to planning when the worker is already paired", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
flow.events.removeAllListeners("pi-supervise:worker-state:v1");
|
||||
flow.events.on("pi-supervise:worker-state:v1", (reply) => reply({ intercomId: "worker-intercom", paired: true }));
|
||||
await flow.commands.get("goals").handler("make the file", flow.ctx);
|
||||
approvedPlan(flow.cwd);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
expect(openSupervisorPane).not.toHaveBeenCalled();
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning", supervisorPaneId: null });
|
||||
expect(flow.notifications.at(-1)).toContain("already paired");
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("waits for the worker's real paired acknowledgement before beginning work", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
openSupervisorPane.mockImplementationOnce(async () => "pane-2");
|
||||
await flow.commands.get("goals").handler("make the file", flow.ctx);
|
||||
approvedPlan(flow.cwd);
|
||||
const ready = flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "planning" });
|
||||
expect(flow.messages.some((message) => message.content === "The plan is approved. Begin implementation as the worker.")).toBe(false);
|
||||
flow.events.emit("pi-supervise:worker-paired:v1", { supervisorIntercomId: "supervisor-intercom" });
|
||||
await ready;
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: "working", supervisorPaneId: "pane-2" });
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("closes the supervisor on clear but keeps the plan file", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
await flow.commands.get("goals").handler("make the file", flow.ctx);
|
||||
const planPath = approvedPlan(flow.cwd);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
await flow.commands.get("goals").handler("clear", flow.ctx);
|
||||
expect(closeSupervisorPane).toHaveBeenCalledWith("pane-2");
|
||||
expect(readFileSync(planPath, "utf8")).toContain("make the file");
|
||||
expect(flow.entries.at(-1)?.data).toMatchObject({ phase: null, supervisorPaneId: null, planVersion: null });
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts only an approval for the exact clean commit and goal block", async () => {
|
||||
const flow = setup(["Ready"]);
|
||||
try {
|
||||
await flow.commands.get("goals").handler("make the file", flow.ctx);
|
||||
const planPath = approvedPlan(flow.cwd);
|
||||
await flow.hooks.get("agent_settled")({}, flow.ctx);
|
||||
const goal = "make the file";
|
||||
const plan = readFileSync(planPath, "utf8");
|
||||
const block = goalBlock(plan, goal)!;
|
||||
const repository = repositoryState(flow.cwd);
|
||||
const approvalId = (flow.entries.at(-1)?.data as { approvalId: string }).approvalId;
|
||||
writeApproval(approvalPath(flow.cwd, "session-a", goal), {
|
||||
version: 3, verdict: "accept", approvalId, goal, planPath,
|
||||
goalBlockHash: hashGoalBlock(block), repoRoot: repository.repoRoot,
|
||||
head: repository.head, tree: repository.tree, cleanWorktree: true,
|
||||
inspected: { plan: true, repository: true, evidence: true, verifyOutput: true },
|
||||
verifyOutputPath: "verify.txt",
|
||||
supervisor: { sessionId: "supervisor", runId: null }, timestamp: new Date().toISOString(),
|
||||
});
|
||||
const signed = await flow.tools.get("CompleteGoal").execute("id", { goal }, undefined, undefined, flow.ctx);
|
||||
expect(signed.isError).toBe(false);
|
||||
expect(readFileSync(planPath, "utf8")).toContain("1. [x] goal: make the file");
|
||||
} finally {
|
||||
rmSync(flow.cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("process role", () => {
|
||||
it("keeps subagent children and visible supervisors out of the worker extension", () => {
|
||||
expect(isMainSession(false)).toBe(true);
|
||||
expect(isMainSession(true)).toBe(false);
|
||||
});
|
||||
});
|
||||
+1165
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { closeSupervisorPane, openSupervisorPane, supervisorCommand } from "../src/herdr.js";
|
||||
|
||||
function input() {
|
||||
return {
|
||||
cwd: "/repo",
|
||||
sourceSessionFile: "/sessions/worker.jsonl",
|
||||
workerSessionId: "worker-12345678",
|
||||
workerIntercomId: "intercom-12345678",
|
||||
planPath: "/repo/.pi/plan/worker-v1.md",
|
||||
approvalId: "approval-1",
|
||||
extensionPath: "/repo/src/index.ts",
|
||||
superviseExtensionPath: null,
|
||||
model: "provider/supervisor",
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs());
|
||||
|
||||
describe("supervisor pane command", () => {
|
||||
it("forks the planning session with an explicit supervisor role and model", () => {
|
||||
const command = supervisorCommand(input());
|
||||
expect(command).toContain("'PI_GOALS_ROLE=supervisor'");
|
||||
expect(command).toContain("'PI_GOALS_WORKER_INTERCOM_ID=intercom-12345678'");
|
||||
expect(command).toContain("'pi' '--no-extensions' '-e' 'npm:pi-intercom' '-e' 'npm:@wassname2/pi-supervise@0.0.4' '-e' '/repo/src/index.ts'");
|
||||
expect(command).toContain("'--fork' '/sessions/worker.jsonl'");
|
||||
expect(command).toContain("'--model' 'provider/supervisor'");
|
||||
expect(command).not.toContain("Initialize supervision startup.");
|
||||
expect(command).not.toContain("pi-subagents");
|
||||
});
|
||||
|
||||
it("uses the loaded pi-supervise extension before the npm fallback", () => {
|
||||
const loaded = { ...input(), superviseExtensionPath: "/repo/vendor/pi-supervise/src/index.ts" };
|
||||
expect(supervisorCommand(loaded)).toContain("'-e' '/repo/vendor/pi-supervise/src/index.ts'");
|
||||
vi.stubEnv("PI_GOALS_SUPERVISE_EXTENSION", "/repo/override/pi-supervise/src/index.ts");
|
||||
expect(supervisorCommand(loaded)).toContain("'-e' '/repo/override/pi-supervise/src/index.ts'");
|
||||
});
|
||||
|
||||
it("accepts Herdr's text version output and stale pane cleanup", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-herdr-"));
|
||||
const bin = join(cwd, "herdr");
|
||||
writeFileSync(bin, `#!/bin/sh
|
||||
if [ "$1" = "--version" ]; then echo "herdr 0.8.2"; exit 0; fi
|
||||
if [ "$1" = "pane" ] && [ "$2" = "split" ]; then echo '{"pane_id":"new-pane"}'; exit 0; fi
|
||||
if [ "$1" = "pane" ] && [ "$2" = "run" ]; then if [ "$HERDR_SMOKE_RUN_FAIL" = "1" ]; then echo "run failed" >&2; exit 1; fi; echo '{}'; exit 0; fi
|
||||
if [ "$1" = "pane" ] && [ "$2" = "close" ]; then echo '{"error":{"code":"PANE_GONE"}}' >&2; exit 1; fi
|
||||
exit 2
|
||||
`);
|
||||
chmodSync(bin, 0o755);
|
||||
vi.stubEnv("HERDR_ENV", "1");
|
||||
vi.stubEnv("HERDR_BIN_PATH", bin);
|
||||
try {
|
||||
await expect(openSupervisorPane(input())).resolves.toBe("new-pane");
|
||||
await expect(closeSupervisorPane("new-pane")).resolves.toBeUndefined();
|
||||
vi.stubEnv("HERDR_SMOKE_RUN_FAIL", "1");
|
||||
await expect(openSupervisorPane(input())).rejects.toThrow("run failed");
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { type ExtensionAPI, type ExtensionContext, getMarkdownTheme, initTheme } from "@earendil-works/pi-coding-agent";
|
||||
import { Markdown, visibleWidth } from "@earendil-works/pi-tui";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import { noticeDisplay } from "../src/notice-display.js";
|
||||
|
||||
it("collapses mirrored prompts only in the UI, expands the exact text, and restores by branch", () => {
|
||||
initTheme("dark");
|
||||
const theme = { fg: (_color: string, text: string) => text };
|
||||
const pi = { registerMarkdownTransformer: vi.fn(), registerEntryRenderer: vi.fn(), appendEntry: vi.fn() };
|
||||
const display = noticeDisplay(pi as unknown as ExtensionAPI);
|
||||
const transform = pi.registerMarkdownTransformer.mock.calls[0][0];
|
||||
const render = pi.registerEntryRenderer.mock.calls[0][1];
|
||||
const content = "[pi-goals]\nPlan changed. Inspect the evidence.\n\n# Output\n\nKeep this entire requirement.\n\nFinal evidence line.";
|
||||
expect(transform(content, { messageType: "user" })).toBe(content);
|
||||
display.mirror(content);
|
||||
expect(pi.appendEntry).toHaveBeenCalledExactlyOnceWith("pi-goals-notice", { content });
|
||||
expect(transform(content, { messageType: "user" })).toBe("");
|
||||
expect(transform(content, { messageType: "assistant" })).toBe(content);
|
||||
expect(transform("[pi-goals]\nA human quotation", { messageType: "user" })).toBe("[pi-goals]\nA human quotation");
|
||||
|
||||
const entry = { type: "custom", customType: "pi-goals-notice", data: { content } };
|
||||
const collapsed = render(entry, { expanded: false }, theme);
|
||||
for (const width of [24, 80]) {
|
||||
const lines = collapsed.render(width);
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(visibleWidth(lines[0])).toBeLessThanOrEqual(width);
|
||||
expect(lines.join("\n")).not.toContain("Final evidence line");
|
||||
}
|
||||
const expanded = render(entry, { expanded: true }, theme);
|
||||
expect(expanded.render(80)).toEqual(new Markdown(content, 0, 0, getMarkdownTheme()).render(80));
|
||||
expect(expanded.render(80).join("\n")).toContain("Final evidence line");
|
||||
|
||||
display.restore({ sessionManager: { getBranch: () => [] } } as unknown as ExtensionContext);
|
||||
expect(transform(content, { messageType: "user" })).toBe(content);
|
||||
display.restore({ sessionManager: { getBranch: () => [entry] } } as unknown as ExtensionContext);
|
||||
expect(transform(content, { messageType: "user" })).toBe("");
|
||||
expect(entry.data.content).toBe(content);
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
interface PackageManifest {
|
||||
files: string[];
|
||||
pi: { extensions: string[]; subagents?: unknown };
|
||||
}
|
||||
|
||||
describe("package manifest", () => {
|
||||
it("includes the extension without registering a packaged subagent", () => {
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")) as PackageManifest;
|
||||
expect(manifest.files).toEqual(["src", "README.md"]);
|
||||
expect(manifest.pi.extensions).toEqual(["./src/index.ts"]);
|
||||
expect(manifest.pi.subagents).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
it("declares current entry and bundled extension resources that exist after install", () => {
|
||||
const manifest = JSON.parse(readFileSync("package.json", "utf8"));
|
||||
expect(manifest.pi.extensions[0]).toBe("./src/index.ts");
|
||||
for (const path of manifest.pi.extensions) expect(existsSync(resolve(path)), path).toBe(true);
|
||||
for (const name of ["pi-subagents", "pi-intercom", "pi-schedule-prompt"]) {
|
||||
expect(manifest.dependencies[name]).toBeTruthy();
|
||||
expect(manifest.bundledDependencies).toContain(name);
|
||||
}
|
||||
expect(manifest.dependencies["pi-subagents"]).toBe("0.66.0");
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { planViews } from "../src/plan-view.js";
|
||||
|
||||
it.each(["", " "])("ignores %sindented identity bookkeeping and Log edits, but reviews tasks and goals", indent => {
|
||||
const base = `# Plan\n- [ ] goal: result\n - tasks:\n - [ ] run it\n${indent}- worker session: /saved.jsonl\n## Log\nfirst entry`;
|
||||
const view = planViews(base).notify;
|
||||
expect(planViews(base.replace("/saved.jsonl", "/moved.jsonl")).notify).toBe(view);
|
||||
expect(planViews(base.replace("first entry", "second entry")).notify).toBe(view);
|
||||
expect(planViews(base.replace("- [ ] run it", "- [x] run it")).notify).not.toBe(view);
|
||||
expect(planViews(base.replace("[ ] goal: result", "[x] goal: result")).notify).not.toBe(view);
|
||||
});
|
||||
|
||||
it("uses Log as the boundary even when Interview precedes Goals", () => {
|
||||
const plan = "# Plan\n## Interview\nOriginal discussion\n## Goals\n- [ ] goal: output\n### Log\n- [ ] goal: archived";
|
||||
const view = planViews(plan).notify;
|
||||
expect(view).toContain("goal: output");
|
||||
expect(view).not.toContain("archived");
|
||||
expect(planViews(plan.replace("goal: output", "goal: changed output")).notify).not.toBe(view);
|
||||
});
|
||||
+89
-27
@@ -1,31 +1,93 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { completeGoalDescription, planDrafting, planningState, resync } from "../src/prompts.js";
|
||||
import { expect, it } from "vitest";
|
||||
import { manualReview, planChangedReview, planContext, planning, planningSeed, readyApproved, upkeep } from "../src/prompts.js";
|
||||
|
||||
describe("planning prompt", () => {
|
||||
it("requires fact finding or a focused question before a goal", () => {
|
||||
expect(planDrafting).toContain("Use read-only repository tools or web search when either can\nresolve a fact.");
|
||||
expect(planDrafting).toContain("Ask at least three short, concrete questions");
|
||||
expect(planDrafting).toContain("understand the requested outcome, boundary, and how success will be judged");
|
||||
expect(planDrafting).toContain("record that\npoint as unknown; do not silently replace it with an inference");
|
||||
expect(planDrafting).toContain("answer materially reduces uncertainty\nwhile discovering the right plan");
|
||||
expect(planDrafting).toContain("self-contained: state the relevant\ncontext, use the human's language and ASD-STE100");
|
||||
expect(planDrafting).toContain("placeholder goal such as \"work out the thing\"");
|
||||
expect(planDrafting).toContain("material user decisions remain open");
|
||||
});
|
||||
const plan = `# Keep the user context
|
||||
|
||||
it("restores the same rule after compaction", () => {
|
||||
expect(planningState(".pi/plan/test.md")).toContain("web search\nwhen either can resolve a fact.");
|
||||
expect(planningState(".pi/plan/test.md")).toContain("choice that needs their approval");
|
||||
expect(planningState(".pi/plan/test.md")).toContain("self-contained round with relevant context and a recommendation");
|
||||
});
|
||||
Make the requested output easy to inspect.
|
||||
|
||||
it("anchors work and sign-off to the user-visible result", () => {
|
||||
expect(planDrafting).toContain("## User-visible result");
|
||||
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(resync("plan", ".pi/plan/test.md", "Compacted.")).toContain("implementation worker");
|
||||
expect(completeGoalDescription).toContain("visible supervisor");
|
||||
expect(completeGoalDescription).toContain("stopped worker view with no active work");
|
||||
});
|
||||
## User-visible result
|
||||
A concrete artifact the user can read.
|
||||
|
||||
## User voice
|
||||
- > "Preserve this requirement word for word."
|
||||
|
||||
## Goals
|
||||
1. [/] goal: verify output
|
||||
- tasks:
|
||||
1. [ ] run the full check
|
||||
- evidence: proof.log
|
||||
|
||||
## Log
|
||||
old progress report`;
|
||||
|
||||
it("keeps hindsight-judged user outcomes in initial and recurring planning instructions", () => {
|
||||
const seed = planningSeed("Make search useful", "/plan.md");
|
||||
for (const prompt of [seed, planning("/plan.md")]) {
|
||||
expect(prompt).toContain('"I know it when I see it"');
|
||||
expect(prompt).toContain("actual results in hindsight");
|
||||
expect(prompt).toContain("do not invent numerical gates to replace judgment");
|
||||
expect(prompt).toContain("technical deliverable nouns and verbs");
|
||||
expect(prompt).not.toContain("not an implementation task");
|
||||
expect(prompt).not.toContain("not a task label");
|
||||
}
|
||||
expect(seed).toContain("goal: <short, concrete requested outcome>");
|
||||
expect(seed).toContain("Put observable examples under verification");
|
||||
expect(seed).toContain("what distinguishes it from merely looking done");
|
||||
expect(seed).toContain("not stricter assistant-invented requirements");
|
||||
expect(seed).toContain("by the user or justified by existing evidence");
|
||||
expect(seed).not.toContain("imperative outcome");
|
||||
});
|
||||
|
||||
it("keeps routine upkeep to its reason, goal lines and source path", () => {
|
||||
const base = upkeep("/plan.md", plan);
|
||||
expect(base).toMatch(/^\[pi-goals: reminder — upkeep\]\n/);
|
||||
expect(base).not.toContain("Preserve this requirement word for word.");
|
||||
expect(base).toContain("Eight unchanged turns");
|
||||
expect(base).toContain("/plan.md");
|
||||
expect(base).toContain("goal: verify output");
|
||||
expect(base).not.toContain("run the full check");
|
||||
});
|
||||
|
||||
it("separates routine goal lines from active context without historical Log", () => {
|
||||
const short = planContext("supervising", "/plan.md", plan, "short");
|
||||
expect(short).toContain("unfinished or unreviewed goal lines");
|
||||
expect(short).toContain("/plan.md");
|
||||
expect(short).not.toContain("Preserve this requirement");
|
||||
expect(short).toContain("goal: verify output");
|
||||
|
||||
const medium = planContext("supervising", "/plan.md", plan, "medium");
|
||||
expect(medium).not.toContain("Preserve this requirement word for word.");
|
||||
expect(medium).toContain("1. [/] goal: verify output");
|
||||
expect(medium).not.toContain("run the full check");
|
||||
expect(medium).not.toContain("proof.log");
|
||||
expect(medium).not.toContain("old progress report");
|
||||
|
||||
const full = planContext("supervising", "/plan.md", plan, "full");
|
||||
expect(full).toContain("run the full check");
|
||||
expect(full).toContain("proof.log");
|
||||
expect(full).toContain("Preserve this requirement word for word.");
|
||||
expect(full).not.toContain("old progress report");
|
||||
});
|
||||
|
||||
it("puts selected goal lines in plan-change and manual-review messages", () => {
|
||||
for (const text of [planChangedReview("/plan.md", plan), manualReview("/plan.md", plan)]) {
|
||||
expect(text).toContain("goal: verify output");
|
||||
expect(text).toContain("/plan.md");
|
||||
expect(text).not.toContain("Preserve this requirement word for word.");
|
||||
expect(text).not.toContain("run the full check");
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps approved work moving after evidence review without overriding pauses or scope approval", () => {
|
||||
const text = planChangedReview("/plan.md", plan);
|
||||
expect(text).toContain("Evidence-only edits do not revoke execution approval");
|
||||
expect(text).toContain("Continue only unfinished authorized work");
|
||||
expect(text).toContain("respect pauses and do not assume approval for changed scope");
|
||||
});
|
||||
|
||||
it("keeps the current working set in the ready message but omits history", () => {
|
||||
const approved = readyApproved("goals-worker", "/plan.md", undefined, plan, "pi-session");
|
||||
expect(approved).toContain("Preserve this requirement word for word.");
|
||||
expect(approved).toContain("goal: verify output");
|
||||
expect(approved).not.toContain("old progress report");
|
||||
});
|
||||
|
||||
+108
-38
@@ -1,20 +1,26 @@
|
||||
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { once } from "node:events";
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { createServer } from "node:http";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { StringDecoder } from "node:string_decoder";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { foldPlan } from "../src/plan.js";
|
||||
|
||||
type RpcMessage = { type: string; id?: string; method?: string; [key: string]: unknown };
|
||||
type ModelRequest = { messages: Array<{ role: string; content: string | Array<{ type: string; text?: string }> }> };
|
||||
const messageText = (content: ModelRequest["messages"][number]["content"]) => typeof content === "string" ? content : content.filter(part => part.type === "text").map(part => part.text).join("\n");
|
||||
|
||||
class RpcClient {
|
||||
readonly messages: RpcMessage[] = [];
|
||||
stderr = "";
|
||||
private readonly waiters: Array<{ predicate: (message: RpcMessage) => boolean; resolve: (message: RpcMessage) => void }> = [];
|
||||
|
||||
constructor(readonly process: ChildProcessWithoutNullStreams) {
|
||||
const decoder = new StringDecoder("utf8");
|
||||
let buffer = "";
|
||||
process.stderr.on("data", (chunk) => { this.stderr += chunk; });
|
||||
process.stdout.on("data", (chunk) => {
|
||||
buffer += decoder.write(chunk);
|
||||
while (buffer.includes("\n")) {
|
||||
@@ -37,7 +43,14 @@ class RpcClient {
|
||||
waitFor(predicate: (message: RpcMessage) => boolean, after = 0): Promise<RpcMessage> {
|
||||
const existing = this.messages.slice(after).find(predicate);
|
||||
if (existing) return Promise.resolve(existing);
|
||||
return new Promise((resolvePromise) => this.waiters.push({ predicate, resolve: resolvePromise }));
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.waiters.splice(this.waiters.indexOf(waiter), 1);
|
||||
reject(new Error(`RPC wait timed out: ${this.stderr}\n${JSON.stringify(this.messages.slice(-12))}`));
|
||||
}, 8_000);
|
||||
const waiter = { predicate, resolve: (message: RpcMessage) => { clearTimeout(timer); resolvePromise(message); } };
|
||||
this.waiters.push(waiter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,69 +61,126 @@ function streamResponse(response: import("node:http").ServerResponse, delta: obj
|
||||
response.end("data: [DONE]\n\n");
|
||||
}
|
||||
|
||||
const isSelect = (message: RpcMessage) => message.type === "extension_ui_request" && message.method === "select";
|
||||
const isEditor = (message: RpcMessage) => message.type === "extension_ui_request" && message.method === "editor";
|
||||
const systemText = (request: ModelRequest) => request.messages.filter(message => ["system", "developer"].includes(message.role)).map(message => message.content).join("\n");
|
||||
|
||||
describe("RPC review flow", () => {
|
||||
it("opens Refine's editor before it starts the revision turn", async () => {
|
||||
it.each(["Edit", "Discuss"])("automatically proposes a draft, handles %s, then enters the supervisor role on Ready", async (choice) => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-rpc-"));
|
||||
let requestCount = 0;
|
||||
const requests: ModelRequest[] = [];
|
||||
const plan = "# Plan\n\n## Goals\n\n1. [ ] goal: name the output\n - subtle failure mode: the output has no name\n - discriminator: the plan names the output\n\n## Log\n";
|
||||
let planPath = "";
|
||||
const server = createServer((_request, response) => {
|
||||
requestCount++;
|
||||
if (requestCount === 1) {
|
||||
const server = createServer(async (request, response) => {
|
||||
let body = "";
|
||||
for await (const chunk of request) body += chunk;
|
||||
const modelRequest = JSON.parse(body) as ModelRequest;
|
||||
requests.push(modelRequest);
|
||||
if (requests.length === 1) {
|
||||
const pathMatch = systemText(modelRequest).match(/Plan only in (.+?);/);
|
||||
if (!pathMatch) throw new Error("Planning prompt did not name its plan file");
|
||||
planPath = pathMatch[1];
|
||||
streamResponse(response, {
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
id: "write-plan",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "write",
|
||||
arguments: JSON.stringify({
|
||||
path: planPath,
|
||||
content: "# Plan\n\n## Goals\n\n1. [ ] goal: name the output\n - subtle failure mode: the output has no name\n - discriminator: the plan names the output\n\n## Log\n\n## Interview\n",
|
||||
}),
|
||||
},
|
||||
index: 0, id: "write-plan", type: "function",
|
||||
function: { name: "write", arguments: JSON.stringify({ path: planPath, content: plan }) },
|
||||
}],
|
||||
}, "tool_calls");
|
||||
return;
|
||||
}
|
||||
streamResponse(response, { content: "Plan drafted." }, "stop");
|
||||
streamResponse(response, { content: "Plan inspected." }, "stop");
|
||||
});
|
||||
await new Promise<void>((resolvePromise) => server.listen(0, "127.0.0.1", resolvePromise));
|
||||
await new Promise<void>((done) => server.listen(0, "127.0.0.1", done));
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") throw new Error("Offline model did not bind a TCP port.");
|
||||
|
||||
const pi = spawn(resolve("node_modules/.bin/pi"), [
|
||||
"--mode", "rpc", "--no-session", "--model", "offline/test",
|
||||
"--mode", "rpc", "--no-session", "--no-extensions", "--model", "offline/test",
|
||||
"-e", resolve("test/fixtures/offline-model.ts"),
|
||||
"-e", resolve("src/index.ts"),
|
||||
], {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
// Pi/gpt-6-astra: test the parent role even when vitest itself runs in a worker.
|
||||
...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("PI_SUBAGENT_") && !name.startsWith("PI_GOALS_"))),
|
||||
PI_CODING_AGENT_DIR: join(cwd, ".agent"),
|
||||
PI_GOALS_OFFLINE_MODEL_URL: `http://127.0.0.1:${address.port}`,
|
||||
},
|
||||
});
|
||||
const client = new RpcClient(pi);
|
||||
const exited = once(pi, "exit");
|
||||
try {
|
||||
client.send({ type: "get_state", id: "state" });
|
||||
const state = await client.waitFor((message) => message.type === "response" && message.id === "state");
|
||||
const sessionId = (state.data as { sessionId: string }).sessionId;
|
||||
planPath = join(cwd, ".pi", "plan", `${sessionId}-v1.md`);
|
||||
client.send({ type: "prompt", id: "goals", message: "/goals new work out the thing" });
|
||||
const review = await client.waitFor(isSelect);
|
||||
expect(review.options).toEqual(["Ready", "Discuss", "Edit", "Cancel"]);
|
||||
client.send({ type: "get_state", id: "session-name" });
|
||||
const state = await client.waitFor(message => message.type === "response" && message.id === "session-name");
|
||||
expect(basename(planPath)).toBe(`${(state.data as { sessionId: string }).sessionId.slice(-6)}-v1.md`);
|
||||
expect(review.title).toContain(planPath);
|
||||
const proposal = client.messages.find(message => message.type === "message_end" && (message.message as { customType?: string })?.customType === "goal-plan-proposal");
|
||||
expect(proposal?.message).toMatchObject({ content: plan, display: true });
|
||||
expect(readFileSync(planPath, "utf8")).toBe(plan);
|
||||
expect(requests).toHaveLength(2);
|
||||
expect(systemText(requests[0])).toContain("Plan only in");
|
||||
|
||||
client.send({ type: "prompt", id: "goals", message: "/goals work out the thing" });
|
||||
const review = await client.waitFor((message) => message.type === "extension_ui_request" && message.method === "select");
|
||||
client.send({ type: "extension_ui_response", id: review.id, value: "Refine" });
|
||||
const editor = await client.waitFor((message) => message.type === "extension_ui_request" && message.method === "editor");
|
||||
expect(requestCount).toBe(2);
|
||||
|
||||
const revisionStart = client.messages.length;
|
||||
client.send({ type: "extension_ui_response", id: editor.id, value: "Name the produced file." });
|
||||
await client.waitFor((message) => message.type === "agent_end", revisionStart);
|
||||
expect(requestCount).toBe(3);
|
||||
const choiceStart = client.messages.length;
|
||||
client.send({ type: "extension_ui_response", id: review.id, value: choice });
|
||||
let approvedPlan = plan;
|
||||
if (choice === "Edit") {
|
||||
const editor = await client.waitFor(isEditor, choiceStart);
|
||||
expect(editor.prefill).toBe(plan);
|
||||
expect(requests).toHaveLength(2);
|
||||
approvedPlan = plan.replace("the plan names the output", "the plan names output.txt and its exact bytes");
|
||||
const editStart = client.messages.length;
|
||||
client.send({ type: "extension_ui_response", id: editor.id, value: approvedPlan });
|
||||
await client.waitFor(message => message.type === "extension_ui_request" && message.method === "setWidget", editStart);
|
||||
expect(readFileSync(planPath, "utf8")).toBe(approvedPlan);
|
||||
expect(requests).toHaveLength(2);
|
||||
} else {
|
||||
await client.waitFor(message => message.type === "agent_settled", choiceStart);
|
||||
client.send({ type: "get_state", id: "idle-discuss" });
|
||||
const idle = await client.waitFor(message => message.type === "response" && message.id === "idle-discuss");
|
||||
expect(idle.data).toMatchObject({ isStreaming: false, pendingMessageCount: 0 });
|
||||
expect(requests).toHaveLength(2);
|
||||
expect(client.messages.slice(choiceStart).filter(message => message.type === "agent_start" || isEditor(message))).toEqual([]);
|
||||
const userStart = client.messages.length;
|
||||
client.send({ type: "prompt", id: "user-discussion", message: "Keep the output name, but explain the failure mode." });
|
||||
await client.waitFor(message => message.type === "agent_settled", userStart);
|
||||
expect(requests).toHaveLength(3);
|
||||
expect(systemText(requests[2])).toContain("Plan only in");
|
||||
expect(JSON.stringify(requests[2].messages)).toContain("Keep the output name, but explain the failure mode.");
|
||||
}
|
||||
const beforeReady = requests.length;
|
||||
const reopenStart = client.messages.length;
|
||||
client.send({ type: "prompt", id: "review", message: "/goals review" });
|
||||
const ready = await client.waitFor(isSelect, reopenStart);
|
||||
expect(requests).toHaveLength(beforeReady);
|
||||
const readyStart = client.messages.length;
|
||||
client.send({ type: "extension_ui_response", id: ready.id, value: "Ready" });
|
||||
await client.waitFor(message => message.type === "agent_end", readyStart);
|
||||
expect(requests).toHaveLength(beforeReady + 1);
|
||||
const supervisor = requests.at(-1)!;
|
||||
expect(systemText(supervisor)).toContain("You are the goal supervisor in the main chat");
|
||||
expect(systemText(supervisor)).not.toContain("Plan only in");
|
||||
expect(JSON.stringify(supervisor.messages)).toContain(JSON.stringify(foldPlan(approvedPlan)).slice(1, -1));
|
||||
const approval = supervisor.messages.filter(message => message.role === "user").map(message => messageText(message.content)).find(text => text.includes("Ready approved this plan:"))!;
|
||||
expect(approval).toContain("[pi-goals: approval — Ready]");
|
||||
expect(approval).toContain(`Plan excerpt (working set before Log) from ${JSON.stringify(planPath)}:\n\x60\x60\x60md\n${foldPlan(approvedPlan)}\n\x60\x60\x60`);
|
||||
expect(client.messages.filter(message => message.type === "tool_execution_start").map(message => message.toolName)).toEqual(["write"]);
|
||||
expect(client.messages.filter(message => message.type === "extension_error")).toEqual([]);
|
||||
const notices = client.messages.filter(message => message.type === "entry_appended" && (message.entry as { customType?: string })?.customType === "pi-goals-notice");
|
||||
expect(notices.length).toBeGreaterThanOrEqual(2);
|
||||
for (const notice of notices) {
|
||||
const content = (notice.entry as { data: { content: string } }).data.content;
|
||||
expect(client.messages.some(event => event.type === "message_end" && (event.message as any)?.role === "user" && (event.message as any)?.content[0]?.text === content)).toBe(true);
|
||||
expect(supervisor.messages.filter(message => message.role === "user" && messageText(message.content) === content)).toHaveLength(1);
|
||||
}
|
||||
console.log(`RPC ${choice}: visible automatic proposal; ${choice === "Edit" ? "editor saved exact plan without model call" : "discussion retained planning role without editor"}; Ready request used supervisor role; only write executed.`);
|
||||
} finally {
|
||||
pi.kill();
|
||||
server.close();
|
||||
await exited;
|
||||
await new Promise<void>((done) => server.close(() => done()));
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
}, 15_000);
|
||||
}, 25_000);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { report, summarize } from "../scripts/session-usage.mjs";
|
||||
|
||||
const start = "2026-09-10T06:00:00.000Z";
|
||||
const end = "2026-09-10T07:00:00.000Z";
|
||||
const request = (timestamp: string, model = "a") => ({
|
||||
type: "message", timestamp, message: { role: "assistant", provider: "test", model,
|
||||
usage: { input: 10, cacheRead: 100, cacheWrite: 5, output: 20, reasoning: 8, totalTokens: 135 } },
|
||||
});
|
||||
|
||||
it("excludes inherited history and counts repeated cached input without adding reasoning twice", () => {
|
||||
const result = summarize([request("2026-09-09T06:00:00.000Z"), request(start), request(end, "b")], start, end);
|
||||
expect(result).toMatchObject({ calls: 2, input: 20, cacheRead: 200, cacheWrite: 10, output: 40, totalTokens: 270 });
|
||||
expect(result.models.map((m: any) => m.model)).toEqual(["test/a", "test/b"]);
|
||||
});
|
||||
|
||||
it("uses the latest planning start and the same interval for both sessions", () => {
|
||||
const supervisor = { entries: [
|
||||
{ type: "custom", customType: "pi-goals-main-supervisor-v1", timestamp: start, id: "boundary", data: { mode: "planning", plan: "plan.md" } },
|
||||
request(start),
|
||||
] };
|
||||
const result = report(supervisor, { entries: [request(end)] }, end);
|
||||
expect(result.since).toBe(start);
|
||||
expect(result.elapsedHours).toBe(1);
|
||||
expect(result.sessions.map((s: any) => s.output)).toEqual([20, 20]);
|
||||
expect(() => report({ entries: [] }, { entries: [] }, end)).toThrow("No recorded planning start");
|
||||
});
|
||||
|
||||
it("reports missing usage and rejects invalid recorded token counts", () => {
|
||||
const missing = { type: "message", timestamp: start, message: { role: "assistant" } };
|
||||
expect(summarize([missing], start, end).missingUsage).toBe(1);
|
||||
const invalid = request(start); invalid.message.usage.input = Number.NaN;
|
||||
expect(() => summarize([invalid], start, end)).toThrow("Invalid usage.input");
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { workerPiSupervise } from "../src/supervise.js";
|
||||
|
||||
const API_READY = "pi-supervise:api-ready:v1";
|
||||
const WORKER_STATE = "pi-supervise:worker-state:v1";
|
||||
const WORKER_PAIRED = "pi-supervise:worker-paired:v1";
|
||||
|
||||
function pi(events: EventEmitter): ExtensionAPI {
|
||||
return { events } as unknown as ExtensionAPI;
|
||||
}
|
||||
|
||||
describe("pi-supervise worker API", () => {
|
||||
it("discovers pi-supervise when it loads after pi-goals", async () => {
|
||||
const events = new EventEmitter();
|
||||
const worker = workerPiSupervise(pi(events));
|
||||
events.on(WORKER_STATE, (reply) => reply({ intercomId: "worker-id", paired: false }));
|
||||
events.emit(API_READY);
|
||||
expect((await worker).intercomId).toBe("worker-id");
|
||||
});
|
||||
|
||||
it("discovers an already-loaded pi-supervise and accepts duplicate paired events once", async () => {
|
||||
const events = new EventEmitter();
|
||||
events.on(WORKER_STATE, (reply) => reply({ intercomId: "worker-id", paired: false }));
|
||||
const worker = await workerPiSupervise(pi(events));
|
||||
let acknowledgements = 0;
|
||||
const paired = worker.waitForPair().then(() => { acknowledgements += 1; });
|
||||
events.emit(WORKER_PAIRED, { supervisorIntercomId: "supervisor-id" });
|
||||
events.emit(WORKER_PAIRED, { supervisorIntercomId: "supervisor-id" });
|
||||
await paired;
|
||||
expect(acknowledgements).toBe(1);
|
||||
});
|
||||
|
||||
it("rejects Ready when another supervisor already owns the worker", async () => {
|
||||
const events = new EventEmitter();
|
||||
events.on(WORKER_STATE, (reply) => reply({ intercomId: "worker-id", paired: true }));
|
||||
await expect(workerPiSupervise(pi(events))).rejects.toThrow("already paired");
|
||||
});
|
||||
|
||||
it("times out when the visible supervisor never pairs", async () => {
|
||||
const events = new EventEmitter();
|
||||
events.on(WORKER_STATE, (reply) => reply({ intercomId: "worker-id", paired: false }));
|
||||
const worker = await workerPiSupervise(pi(events), 1);
|
||||
await expect(worker.waitForPair()).rejects.toThrow("did not pair");
|
||||
});
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { approvalPath } from "../src/approval.js";
|
||||
import { registerVisibleSupervisor } from "../src/supervisor-session.js";
|
||||
|
||||
function setup(cwd: string, planPath: string, tokens: number | null = 10, onCompact: (options: any) => void = (options) => options.onComplete()) {
|
||||
vi.stubEnv("PI_GOALS_WORKER_ID", "worker-session");
|
||||
vi.stubEnv("PI_GOALS_WORKER_INTERCOM_ID", "worker-intercom");
|
||||
vi.stubEnv("PI_GOALS_OWNER_SESSION_ID", "worker-session");
|
||||
vi.stubEnv("PI_GOALS_PLAN_PATH", planPath);
|
||||
vi.stubEnv("PI_GOALS_APPROVAL_ID", "approval-1");
|
||||
const hooks = new Map<string, any>();
|
||||
const tools = new Map<string, any>();
|
||||
const entries: any[] = [];
|
||||
const paired: Array<{ workerIntercomId: string; goal: string }> = [];
|
||||
const messages: string[] = [];
|
||||
let branch: any[] = [];
|
||||
const ctx = {
|
||||
cwd,
|
||||
getSystemPrompt: () => "base",
|
||||
getContextUsage: () => tokens === null ? undefined : ({ tokens }),
|
||||
compact: vi.fn(onCompact),
|
||||
sessionManager: {
|
||||
getEntries: () => entries,
|
||||
getBranch: () => branch,
|
||||
getSessionId: () => "supervisor-session",
|
||||
},
|
||||
ui: { notify: vi.fn() },
|
||||
};
|
||||
const pi = {
|
||||
events: {
|
||||
on() {},
|
||||
emit(name: string, request: any) {
|
||||
if (name !== "pi-supervise:pair:v1") return;
|
||||
paired.push({ workerIntercomId: request.workerIntercomId, goal: request.goal });
|
||||
request.resolve();
|
||||
},
|
||||
},
|
||||
on: (name: string, handler: any) => hooks.set(name, handler),
|
||||
registerTool: (tool: any) => tools.set(tool.name, tool),
|
||||
appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }),
|
||||
sendUserMessage: (message: string) => messages.push(message),
|
||||
};
|
||||
registerVisibleSupervisor(pi as unknown as ExtensionAPI);
|
||||
return { branch: (value: any[]) => { branch = value; }, ctx, entries, hooks, messages, paired, tools };
|
||||
}
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs());
|
||||
|
||||
describe("visible supervisor session", () => {
|
||||
it("pairs from session startup before asking the supervisor to work", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"));
|
||||
await runtime.hooks.get("session_start")({}, runtime.ctx);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(runtime.ctx.compact).not.toHaveBeenCalled();
|
||||
expect(runtime.entries.at(-1)).toMatchObject({ customType: "pi-goals-visible-supervisor-v1" });
|
||||
expect(runtime.paired).toEqual([{ workerIntercomId: "worker-intercom", goal: join(cwd, ".pi/plan/worker-v1.md") }]);
|
||||
expect(runtime.messages).toEqual(["Supervision is paired. Inspect the worker and give its next concrete instruction."]);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("compacts a large planning fork before pairing", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
let complete: (() => void) | undefined;
|
||||
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), 20_001, (options) => { complete = options.onComplete; });
|
||||
await runtime.hooks.get("session_start")({}, runtime.ctx);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(runtime.ctx.compact).toHaveBeenCalledOnce();
|
||||
expect(runtime.paired).toHaveLength(0);
|
||||
complete!();
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(runtime.paired).toHaveLength(1);
|
||||
expect(runtime.messages).toEqual(["Supervision is paired. Inspect the worker and give its next concrete instruction."]);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not start work when initial compaction fails", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"), null, (options) => options.onError(new Error("offline")));
|
||||
await runtime.hooks.get("session_start")({}, runtime.ctx);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
expect(runtime.ctx.compact).toHaveBeenCalledOnce();
|
||||
expect(runtime.paired).toHaveLength(0);
|
||||
expect(runtime.ctx.ui.notify).toHaveBeenCalledWith("Supervisor startup compaction failed: offline", "error");
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not pair twice across session startup and later turns", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
const runtime = setup(cwd, join(cwd, ".pi/plan/worker-v1.md"));
|
||||
await runtime.hooks.get("session_start")({}, runtime.ctx);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
await runtime.hooks.get("before_agent_start")({}, runtime.ctx);
|
||||
expect(runtime.paired).toHaveLength(1);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("records approval only from a stopped view with evidence and no active work", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
writeFileSync(join(cwd, ".gitignore"), ".pi/\n");
|
||||
writeFileSync(join(cwd, "verify.txt"), "PASS\n");
|
||||
execFileSync("git", ["init", "-q"], { cwd });
|
||||
execFileSync("git", ["add", ".gitignore", "verify.txt"], { cwd });
|
||||
execFileSync("git", ["-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-qm", "initial"], { cwd });
|
||||
const planPath = join(cwd, ".pi/plan/worker-v1.md");
|
||||
execFileSync("mkdir", ["-p", join(cwd, ".pi/plan")]);
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - discriminator: output exists\n - evidence:\n - `result.txt`: contains ok\n\n## Log\n");
|
||||
const runtime = setup(cwd, planPath);
|
||||
runtime.branch([{
|
||||
type: "message",
|
||||
message: { role: "user", content: [{ type: "text", text: "The worker stopped.\n\ntool calls with no result: none\nchild pi processes still running: none" }] },
|
||||
}]);
|
||||
const approved = await runtime.tools.get("ApproveGoal").execute("id", {
|
||||
goal: "make the file",
|
||||
verifyOutputPath: "verify.txt",
|
||||
}, undefined, undefined, runtime.ctx);
|
||||
expect(approved.isError).toBe(false);
|
||||
expect(existsSync(approvalPath(cwd, "worker-session", "make the file"))).toBe(true);
|
||||
const missingOutput = await runtime.tools.get("ApproveGoal").execute("id", {
|
||||
goal: "make the file", verifyOutputPath: "missing.txt",
|
||||
}, undefined, undefined, runtime.ctx);
|
||||
expect(missingOutput.isError).toBe(true);
|
||||
expect(missingOutput.content[0].text).toContain("verification-output");
|
||||
writeFileSync(planPath, "# Plan\n\n## Goals\n\n1. [ ] goal: make the file\n - evidence:\n - \n - tasks:\n - write result.txt\n");
|
||||
const missingEvidence = await runtime.tools.get("ApproveGoal").execute("id", {
|
||||
goal: "make the file", verifyOutputPath: "verify.txt",
|
||||
}, undefined, undefined, runtime.ctx);
|
||||
expect(missingEvidence.isError).toBe(true);
|
||||
expect(missingEvidence.content[0].text).toContain("nonblank evidence entry");
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects approval while the worker view has an unfinished tool call", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-goals-supervisor-"));
|
||||
try {
|
||||
const planPath = join(cwd, "plan.md");
|
||||
writeFileSync(planPath, "1. [ ] goal: wait\n - evidence:\n - result\n");
|
||||
const runtime = setup(cwd, planPath);
|
||||
runtime.branch([{
|
||||
type: "message",
|
||||
message: { role: "user", content: [{ type: "text", text: "The worker stopped.\n\ntool calls with no result: bash\nchild pi processes still running: none" }] },
|
||||
}]);
|
||||
const rejected = await runtime.tools.get("ApproveGoal").execute("id", {
|
||||
goal: "wait", verifyOutputPath: "verify.txt",
|
||||
}, undefined, undefined, runtime.ctx);
|
||||
expect(rejected.isError).toBe(true);
|
||||
expect(rejected.content[0].text).toContain("bash");
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { tickGoal } from "../src/index.js";
|
||||
|
||||
const plan = `# Plan
|
||||
|
||||
## Goals
|
||||
|
||||
1. [/] goal: Implement the cache layer
|
||||
- tasks:
|
||||
1. [x] wire client
|
||||
2. [ ] goal: Ship the docs
|
||||
|
||||
## Log
|
||||
`;
|
||||
|
||||
describe("tickGoal (sign-off ticks the goal; agent only ticks on wording drift)", () => {
|
||||
it("ticks the exact-matching goal line, case-insensitive, leaving subtasks alone", () => {
|
||||
const out = tickGoal(plan, "implement the CACHE layer");
|
||||
expect(out).toContain("1. [x] goal: Implement the cache layer");
|
||||
expect(out).toContain("1. [x] wire client"); // subtask untouched (was already x)
|
||||
expect(out).toContain("2. [ ] goal: Ship the docs"); // other goal untouched
|
||||
});
|
||||
|
||||
it("returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's)", () => {
|
||||
expect(tickGoal(plan, "Implement caching")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when the subject matches more than one goal line", () => {
|
||||
const dup = `${plan}3. [ ] goal: Ship the docs\n`;
|
||||
expect(tickGoal(dup, "Ship the docs")).toBeNull();
|
||||
});
|
||||
});
|
||||
+5
-2
@@ -6,10 +6,13 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": { "pi-subagents/project-panes": ["./src/project-panes.d.ts"] },
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user