mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-11 12:43:57 +08:00
Restore automatic plan proposals and make goal commands explicit
Reduce unchanged upkeep and identity-only review noise; retain user README structure and screenshot with abridged terminal example. 174 tests pass; actual Herdr automatic proposal captured. Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -1,105 +1,145 @@
|
||||
|
||||
# pi-goals
|
||||
|
||||
> **Experimental branch:** this checkout loads the [main-chat supervisor prototype](prototype/README.md), not the older runtime described below. It uses edxeth's interactive Pi workers in Herdr. Do not install it over a running research session. This package is marked private while the prototype is evaluated. — Pi/OpenAI
|
||||
|
||||
## Previous runtime (retained for comparison)
|
||||
|
||||
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.
|
||||
|
||||
<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" />
|
||||
|
||||
## How it works
|
||||
Abridged text from the isolated test, with approval and completion shown together. Paths are shortened; bracketed labels are annotations. Both sessions used DeepSeek in this test.
|
||||
|
||||
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 an inspection-only supervisor by instruction, with normal Pi tools and extensions available. Worker views and supervisor instructions travel over pi-intercom's extension channel, scoped to this plan pairing.
|
||||
6. Ready waits for the supervisor's Intercom readiness message; the worker does not begin before the fork has compacted and started.
|
||||
7. The supervisor compacts again when its context reaches 100k tokens.
|
||||
8. The supervisor records a private approval only after it sees a stopped worker, no active work, a clean worktree (or an explicit inspected-state override), evidence, and saved verification output. `CompleteGoal` checks that approval against the exact plan block and Git tree before it ticks `[x]`.
|
||||
```text
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
| SUPERVISOR | WORKER |
|
||||
| | |
|
||||
| Review .pi/plan/...-main.md | Task 2 — verify command run, real output saved: |
|
||||
| > Ready Discuss Edit Cancel | - Exit code 0 (pass only) |
|
||||
| | - evidence/verified.log (112 bytes) |
|
||||
| [scheduled prompt: hourly check-in] | PASS: 9 bytes: verified + LF |
|
||||
| | |
|
||||
| The supervisor independently inspected both artifacts | Task 3 — plan evidence filled |
|
||||
| before sign-off. | |
|
||||
| | Completion report sent via Intercom to supervisor |
|
||||
| Schedule: job wS79fJFPbB removed; | 01a089c9. My pane remains open for the supervisor’s |
|
||||
| .pi/schedule-prompts.json shows 0 jobs. | independent inspection before sign-off. |
|
||||
| | |
|
||||
| ✓ verified.txt holds exactly the 9 bytes | ○ verified.txt holds exactly the 9 bytes |
|
||||
| verified + LF | verified + LF |
|
||||
| evidence/verified.log records a real byte check | evidence/verified.log records a real byte check |
|
||||
| | |
|
||||
| Agents · 1 running | [idle widget still shows its earlier snapshot] |
|
||||
| verified-bytes-worker [goals-worker] | |
|
||||
| | |
|
||||
| > | > |
|
||||
| deepseek-v4-flash-0731 · Fireworks | deepseek-v4-flash-0731 · Fireworks |
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
```
|
||||
|
||||
The two Pi sessions are visible. You can switch to the supervisor pane and talk to it directly. Supervisor instructions are shown in full, including in collapsed tool rows; ordinary messages and emitted thinking use Pi's display settings. The supervisor is prompted to give brief progress assessments and use judgment about when to intervene.
|
||||
[Full captures and verification](slop/reviews/20260910_package-supervision-herdr.md).
|
||||
|
||||
On same-process reload, monitoring is restored without removing normal or custom tools. Views include the latest human direction, source-session path, worker model, and new messages since the last acknowledged view. They report Pi idleness and tracked process/subagent activity separately. Unavailable trackers stay unknown; unregistered detached jobs are not tracked. Approval is blocked while tracked work is active or unknown. Intercom disconnects are reported; unsent current views and unacknowledged instructions are retained in Pi session history for reconnect. Incoming review/instruction payloads are saved in a bounded inbox before presentation. A receipt is sent only when Pi emits the corresponding user `message_start`: this confirms session acceptance, not model judgment or tool execution. Pending payloads survive reload, and manual-compaction success, failure or cancellation resumes delivery after Pi is idle. Unpresented incremental views are kept separately, not overwritten. The inbox limit is 64 messages; overflow is reported without acknowledging the new message. Crashes around acceptance/persistence or extensions that transform/consume input can still require inspection and explicit reconnect; end-to-end exactly-once execution is not guaranteed. Reviews stop after all goals are completed or cancelled, and both panes remain available. These mechanics are tested; useful judgment and savings from a cheaper worker still require a representative two-model run. -- Pi/OpenAI
|
||||
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>
|
||||
|
||||
### 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
|
||||
|
||||
### Learnings
|
||||
|
||||
### Papercuts - problems, gotchas, suggestions
|
||||
```
|
||||
|
||||
## 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 Pi 0.85.1 or newer (before 1.0) and Herdr 0.7.5 or newer. Pi 0.85.1 supplies the public compaction-failure event and compaction-aware idle state used for delivery recovery. It reuses installed pi-intercom or loads its pi-intercom dependency when none is registered:
|
||||
Requires Herdr, [edxeth/pi-subagents](https://github.com/edxeth/pi-subagents), pi-intercom and pi-schedule-prompt. Remove the unrelated `npm:pi-subagents` package if installed.
|
||||
|
||||
```bash
|
||||
pi install npm:@wassname2/pi-goals
|
||||
pi install git:github.com/edxeth/pi-subagents@v2.9.0
|
||||
pi install npm:pi-intercom
|
||||
pi install npm:pi-schedule-prompt
|
||||
pi install git:github.com/wassname/pi-goals@experiment/main-supervisor-edxeth
|
||||
```
|
||||
|
||||
The supervisor launcher uses the normal Pi profile: it inherits the agent directory/environment and discovers configured extensions, skills, prompt templates, themes and authentication. It explicitly loads this pi-goals source and forks the planning session with the supervisor role/model. Existing Intercom is reused when registered. The repeated role instruction says to inspect and diagnose directly, but delegate changes through `SteerWorker` rather than alter shared state. **This is not an enforced sandbox:** bash, edit, write and extension actions remain available; other extensions may have their own hooks or restrictions. Planning-mode restrictions and approval checks are unchanged. A complete supervisor role/binding is saved before startup compaction and restored before worker handlers can run, including fresh-shell `pi --session <saved-file>` and stopped supervisor forks. Older bootstrap markers are migrated only when their saved pairing is recoverable; incomplete identity fails visibly rather than selecting worker mode. Full-profile Herdr behavior still needs parent-owned functional acceptance.
|
||||
Copy [`prototype/agents/goals-worker.md`](prototype/agents/goals-worker.md) into `~/.pi/agent/agents/`, then start a fresh Pi session.
|
||||
|
||||
For a local checkout:
|
||||
Or for development:
|
||||
|
||||
```bash
|
||||
pi -e .
|
||||
git clone -b experiment/main-supervisor-edxeth https://github.com/wassname/pi-goals
|
||||
cd pi-goals && npm install
|
||||
pi -e ./src/prototype.ts
|
||||
```
|
||||
|
||||
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 the remembered supervisor model
|
||||
/goals work reconnect the existing approved worker pairing
|
||||
/goals supervise reconnect from the saved supervisor session
|
||||
/goals noplan exit planning, preserving the draft without approving work
|
||||
/goals clear close the supervisor pane and disconnect the plan
|
||||
```
|
||||
/goals new CSV export for the report view
|
||||
```
|
||||
|
||||
`work` and `supervise` are role-aware recovery commands, not role conversion or new-pairing commands. Wrong or missing identities are rejected. `noplan` preserves the draft/history, leaves planning restrictions, and does not select Ready, start implementation or launch a supervisor. `/goals clear` closes the tracked pane and keeps the plan file. Starting another plan also keeps older versions.
|
||||
`/goals` opens the action menu. New plan enters plan mode and starts a conversation; the objective is an optional seed. From there:
|
||||
|
||||
If a required model or supervisor is unavailable, the widget says **goals paused** and implementation/sign-off tools are gated. Human input, read-only diagnosis, `/model`, and recovery commands remain available:
|
||||
1. Plan. The agent explores read-only and drafts the plan.
|
||||
2. Review. After Pi settles, the full plan is printed in the transcript. Check that User-visible
|
||||
result names the final artifact or behavior you expect. The menu offers Ready, Discuss, Edit, or
|
||||
Cancel. Discuss continues the conversation. Edit opens the full plan in Pi's editor.
|
||||
3. Work. Ready is the only review action that starts work. It opens the worker in a Herdr pane. The
|
||||
worker ticks subtasks, appends to `## Log` and `## Learnings`, and fills `evidence:`. The supervisor
|
||||
inspects the actual results and calls `CompleteGoal` when a discriminator is satisfied. They
|
||||
communicate through pi-intercom. After eight turns without a change above `## Log`, the agent gets
|
||||
an upkeep reminder. The supervisor also sets an hourly check-in through pi-schedule-prompt.
|
||||
|
||||
- `/goals reconnect` retries the remembered role model and existing supervisor binding. Worker readiness/reconnect waits allow five minutes, including an ordinary 60-second supervisor compaction, and never replace a slow or missing pane automatically. A returning peer clears the connection pause automatically; an established active worker pairing publishes a fresh current view so supervisor-only reload can resume review even when its previous view was already accepted.
|
||||
- `/goals restart` explicitly closes only the tracked supervisor pane and starts a replacement for a working plan, preserving its file/version but invalidating old approvals. During planning it clears the failed pane so Ready can launch again.
|
||||
- In the supervisor pane, use `/model` then `/goals supervise` (or `/goals reconnect`) to recover an unavailable supervisor model. Startup failure is reported to the waiting worker; it need not wait for the timeout to learn the cause.
|
||||
Other commands: `/goals stop` pauses work; `/goals resume` continues it; `/goals exit` leaves goal
|
||||
mode, preserving the plan. `/goals attach <path>` reconnects an existing plan. `/goals solo` lets the
|
||||
main chat do the work after confirming other workers stopped; completion is then self-verification.
|
||||
`/goals model <model-ref>` picks the worker model. `/schedule-prompt` manages check-ins.
|
||||
|
||||
Both sessions must load the updated transport for the request/reply reconnect fix; mixed-version peers are not a supported recovery configuration. Ready announces worker readiness only after its model is restored. Plan content is rechecked across startup/model-restore waits; changed content returns to review using the existing pane instead of starting different work. Clearing or leaving planning cancels its pending Ready attempt. `CompleteGoal` checks cancellation and the original binding/version after its asynchronous status lookup and before recording completion.
|
||||
Stop workers before reloading the supervisor: the subagent package can otherwise crash it when a
|
||||
worker later exits. The scheduler deletes disabled jobs on reload. [Test results and recovery](slop/reviews/20260910_package-supervision-herdr.md).
|
||||
|
||||
A new supervisor may still need up to five minutes for initial compaction. Recovery does not terminate background jobs. Planning/diagnostic command checks are guardrails, not an OS sandbox; loaded extensions and repository Git configuration must be trusted.
|
||||
## Prompts
|
||||
|
||||
Model choices are remembered per project and role in `.pi/pi-goals/models/`. Use `/model` in planning, worker, or supervisor sessions to change that role's choice. Ready restores the worker choice after the planning fork is ready. An unavailable saved model stops the transition instead of substituting another. `/goals model <model>` explicitly overrides the supervisor choice for launch. -- Pi/OpenAI
|
||||
Planning, worker and supervisor prompts live in [`src/prompts.ts`](src/prompts.ts), in conversation order.
|
||||
|
||||
## Inspected dirty-worktree approval
|
||||
|
||||
The supervisor can call `ApproveGoal` with `force: true` and a nonempty `reason` when preserved unrelated changes would otherwise prevent sign-off. It must inspect the changes first, not commit, reset or delete someone else's work. Force bypasses **only** cleanliness, never evidence, the current stopped view, active/unknown work, or exact goal/HEAD/tree checks.
|
||||
|
||||
The approval JSON stores the reason, NUL-delimited Git status, an index SHA-256 digest and per-dirty/untracked-file content SHA-256 digests (including modes, symlink targets and deletions). `CompleteGoal` requires the same state; even editing an already-dirty file without changing its status invalidates approval. Normal clean approvals behave as before. Git-ignored files and pi-goals' private plan/approval/model paths remain excluded. Dirty submodule/nested-repository directories or other unhashable paths fail closed; there is no recursive submodule override. Fingerprinting reads all included dirty/untracked bytes and can be expensive for large outputs; it does not lock concurrent writers.
|
||||
|
||||
A gate rejection is not automatically an experiment failure or a dependency of other authorized work. The supervisor should inspect the exact error and implementation, distinguish causes with a cheap check, and steer repairs plus safe independent progress instead of repeating an unproductive status check. -- Pi/OpenAI
|
||||
|
||||
## Plan format
|
||||
|
||||
Current goals belong above `## Log`; goal-shaped historical checklists below it are ignored by the widget, approval matching and sign-off. A goal is a checkbox line whose text starts with `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)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
If context usage is unavailable, the supervisor warns once that its custom 100k compaction trigger cannot be checked. Pi's normal post-compaction `tokens: null` sample does not produce that warning; default auto-compaction is unchanged.
|
||||
|
||||
## Development
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
npm test
|
||||
pi -e ./src/prototype.ts # load locally; do not also load the installed copy
|
||||
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/intercom-broker.test.ts` checks readiness and exact message delivery through an isolated real Intercom broker. `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.
|
||||
## License
|
||||
|
||||
-- PI[gpt-5.6-sol]
|
||||
MIT
|
||||
|
||||
Branch-specific edits: Pi/OpenAI.
|
||||
|
||||
+1
-41
@@ -1,43 +1,3 @@
|
||||
# Main-chat supervision
|
||||
|
||||
Planning and supervision stay in the main Pi session. `goals-worker` runs in a visible Herdr pane with normal tools. It reports through pi-intercom and stays open, so completion does not discard an unsent editor draft. The supervisor reads actual artifacts before `CompleteGoal`; solo mode records self-verification instead.
|
||||
|
||||
## Packages
|
||||
|
||||
- This branch loads `src/prototype.ts`; model-facing text is in `src/prompts.ts`, in conversation order.
|
||||
- Unmodified [edxeth/pi-subagents v2.9.0](https://github.com/edxeth/pi-subagents/tree/953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4), not the conflicting nicobailon npm package `pi-subagents`. Never load both implementations.
|
||||
- Installed pi-intercom handles live messages. The worker receives the supervisor's session ID in its task; record the worker's saved-session path and actual Intercom ID in plan preferences. Runtime IDs are not Intercom IDs.
|
||||
- Installed pi-schedule-prompt owns hourly check-ins. No additional recurring timer or message transport is implemented here.
|
||||
- `prototype/agents/goals-worker.md` uses `auto-exit: false`, `parent-close-policy: continue`, lineage-only context, and normal extensions/tools. Do not enable edxeth's restricted orchestrator mode.
|
||||
|
||||
## Use
|
||||
|
||||
1. `/goals <objective>` drafts a plan and asks material unresolved questions. `/goals review` opens Ready / Discuss / Edit / Cancel; `/goals ready` approves directly.
|
||||
2. Ready delegates the first unfinished goal. Include the requested worker model in plan preferences or use `/goals model <provider/model>`; verify the actual selected model.
|
||||
3. The worker calls `AttachGoalPlan` with the supplied absolute path for its widget/context. It sends evidence and progress through Intercom, not an exit-only completion tool.
|
||||
4. Inspect and message a live worker using its listed Intercom ID. Use `subagent_resume` with its saved-session path only after it has stopped; never start another writer just because a report is late.
|
||||
|
||||
## Controls and recovery
|
||||
|
||||
- `/goals status`: mode, plan path, model preference, saved worker session and scheduler-job name. A recorded handle does not prove liveness.
|
||||
- `/goals stop` or `/goals exit`: pause/leave goal mode, retain the plan, and request actual worker stop plus removal of the owned check-in job. Remote termination is not assumed.
|
||||
- `/goals resume`: explicitly continue a paused plan after checking existing workers.
|
||||
- `/goals solo`: confirm all other writers stopped, then let this main session implement and edit. Completion is labelled self-verification. The saved worker reference is retained.
|
||||
- `/goals attach <plan.md> [solo]`: reconnect an existing plan without rewriting its evidence. Confirm the previous supervisor stopped, or all other writers stopped for solo. A `- worker session:` note supplies a resume reference.
|
||||
- `/goals exit` during planning preserves the draft without approving implementation. Reattach it later rather than starting over.
|
||||
- `/schedule-prompt`: view, add, toggle or remove scheduled jobs. Edit prompt/interval through `schedule_prompt update`. Binding is visible in `.pi/schedule-prompts.json`; tool text alone does not expose it.
|
||||
- Plan requirements/manual ticks trigger review through a directory event hook. Task/evidence/Log maintenance does not. Eight unchanged working turns trigger a context-only upkeep reminder, not another timer.
|
||||
|
||||
## Known dependency limits
|
||||
|
||||
- **Stop workers before reloading the parent.** With stock edxeth, a worker can remain usable after parent `/reload`, then crash the parent when it later exits through a stale widget callback. This is reproduced, not fixed here. Recover with `pi --session <saved-parent.jsonl>`; retain the plan, inspect workers and explicitly reattach or choose solo. Saved-session restart followed by solo completion was tested.
|
||||
- The installed scheduler deletes disabled jobs on shutdown/reload. Do not promise a disabled job will remain available to re-enable. Never run broad `cleanup` for goal housekeeping; remove only the owned job by ID. Existing job edits are otherwise retained by the prompts.
|
||||
- Worker stop confirmation and scheduler actions use human judgment/model tools, not a cross-process locking framework. No automatic crash restart or exactly-once execution is claimed.
|
||||
|
||||
## Isolated validation
|
||||
|
||||
Run `node prototype/prepare.mjs <clean-pinned-edxeth-checkout> <installed-pi-package-root>`. It creates a temporary Git project and private profile, retains unrelated packages and substitutes only goals/subagent implementations. Start its printed script in a new no-focus Herdr pane. Do not operate user panes. Auth copies and raw profiles are private; never commit them.
|
||||
|
||||
[Current functional evidence](../slop/reviews/20260910_package-supervision-herdr.md) includes model/byte checks, Intercom draft tests, timer updates, the delayed reload crash and saved-session solo recovery. Automated tests do not replace these checks.
|
||||
See the [project README](../README.md) for use and recovery, and [`prepare.mjs`](prepare.mjs) for isolated test setup.
|
||||
|
||||
— Pi/OpenAI
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Active research supervisor feedback
|
||||
|
||||
Received from `goals-supervisor-01a08503`, reporting first-hand as supervisor `01a089da` in `/workspace/2026/suppressed-activations`, 2026-09-10T07:26:19Z. Intercom message `48ca3406-d19f-44f2-b75f-e0f895f00eb8`. Observations below are sender reports, not independently reproduced here. Suggestions only; no authorization to reload or modify the active research session.
|
||||
|
||||
## Reported successes
|
||||
|
||||
> explicit Ready approval preserved the user's revised goal; full user quotes helped repair drift; normal tools let me inspect actual research code/results rather than rubber-stamp worker summaries. The current single visible goals-worker attached the plan, reported through Intercom, and is doing evidence work on the requested model.
|
||||
|
||||
## Follow-up issues, in sender priority order
|
||||
|
||||
1. Identity/liveness metadata:
|
||||
> Stock subagent launch returned only async child id ed63a4a9, not sessionFile/full Intercom UUID/resolved model. Yet reconnection instructions require those. I had to ask the child for /.../2026-09-10T07-01-11-345Z_b0f427f0-99a97788-abd16f05-9c74.jsonl and UUID 8cb793dd-ed52-4cdf-835c-8574dd2b2e98.
|
||||
|
||||
> Suggest one authoritative lifecycle record/launch response containing all distinct identities + resolved model, never infer identity equality.
|
||||
|
||||
2. Metadata-only review churn:
|
||||
> My two metadata-only plan edits each triggered 'Plan changed' reviews, producing read/recap churn while no goal changed. Suggest semantic diff categories: requirements/discriminators, claims/evidence, metadata. Coalesce or silently acknowledge known metadata-only edits; keep substantive reviews.
|
||||
|
||||
3. Scheduler ownership and disabled-job retention:
|
||||
> schedule_prompt add does not expose session binding. I correctly had to read .pi/schedule-prompts.json to verify it. Suggest binding in tool return plus atomic validation. Current disabled-job deletion on reload makes the toggle surprisingly nonpersistent; preserve disabled jobs if feasible.
|
||||
|
||||
4. Context-free language warnings:
|
||||
> A final-pass language nudge flagged the fragment 'e rule across dog/ant naming, leg-count, and property questions' as metaphor and gave arm/ship/gate advice. Here 'leg-count' is literal animal anatomy. Suggest exact span + contextual classification, avoid blocking generic substring warnings.
|
||||
|
||||
## Supervisor judgment, not a software diagnosis
|
||||
|
||||
> Important epistemic failure was mine, not an implementation bug: earlier supervision invented a Yes-first-token success gate, interpreted missing r2 key via my own -1 default as missing logging, and overstated logit-lens evidence as causal localization. These caused extra sweeps and misleading confidence. The new plan explicitly corrects them. Suggest review support that surfaces changes between agreed discriminators and new supervisor thresholds, and labels worker reports vs directly inspected evidence. Do not solve this by restricting inspection tools.
|
||||
|
||||
## Scope boundary
|
||||
|
||||
> Priority: identity/liveness metadata, then reduce metadata-only event churn. Happy to provide exact tool messages or help with a bounded test; don't disrupt the active research worker.
|
||||
|
||||
Tracked for follow-up, separate from the already-authorized command-menu and automatic plan-proposal fixes. No runtime patch or active-session intervention authorized by this feedback. Keep the supervisor's tools and judgment; do not add restrictions to compensate for research interpretation errors.
|
||||
|
||||
## Two-hour follow-up
|
||||
|
||||
Supervisor `01a089da`, message `04b9fb76-b02b-4b1b-a1fe-27067deef258`, 2026-09-10T09:30:48Z. First-person report; research artifacts were inspected by that supervisor, not independently rechecked here.
|
||||
|
||||
> What worked: independent code/artifact inspection repeatedly caught genuine invalid comparisons before accepting completion. Example: worker called low cosine a decisive negative; I read analyze_bank.py and manifest and found it compared source final 'is'/space indices12/13 to dog 'and'/'is' rather than donor final19/20. CPU-only correction exposed donor token-pair structure hidden in all-token means, leading to a meaningful causal test. Later code review caught pair selection translating absolute indices back into prefix offsets, and QR on [1,hidden] returning a scalar random 'direction'. Worker repaired, preserved invalid995, and reran996. Actual996 JSON shows no coherent transfer; I inspected outputs myself, not just worker's verdict. We are now recovering the earlier candidate and preparing reliability evaluation under the user's explicit limited-reliability fallback. No goals approved.
|
||||
|
||||
> Upkeep: I interpreted supervisor inspection-only as forbidding research implementation but allowing explicitly requested plan bookkeeping. I marked Goal1 and active tasks [/], then Goal2/task1 [/] when delegating manifest/rubric work. Evidence field says exactly which artifacts were inspected and what they establish; progress/invalid-run details go in dated Log entries. I did NOT tick tasks done just because child says 'family COMPLETE'. I continue to keep the worker off approved-goal editing; clearer division between parent-owned requirements/status and child evidence submissions would help. Existing workflow is usable with this interpretation.
|
||||
|
||||
> Communication: single child remained reachable via stored Intercom UUID; no further identity discovery problem after initial manual registration. Each failure received a targeted correction with actual code/quoted values, rather than repeating a generic continue. Your read-only finding about the real model_change plus Wassname's 'I did the switch' resolved authorization cleanly; I updated preference/provenance and did not restore the old model. That worked.
|
||||
|
||||
> Remaining friction: 'Plan upkeep ... eight turns without working-set changes' messages recur during active review/tool exchanges and sometimes inject while I am already inspecting evidence to update the plan. Multiple full working-set repeats + full mode reminders dominate the transcript. Then my upkeep log/status edits can produce another plan-change review. I cannot infer the actual event-counter implementation from this; observed effect is repeated reread/recap overhead, not missing work. Suggest diff summaries with a last-reviewed version/hash, coalescing while a review is active, and distinguish meaningful worker progress from side conversations (including this feedback). Keep the full current plan at role changes/compaction and always accessible by read; no need to repeatedly paste unchanged user voice to this extent.
|
||||
|
||||
> Monitoring nuance: pq warned 'no follower' although child had a tracked `pueue wait 994` process, confirmed by child. I correctly treated that as unknown and asked, not proof of no waiter. More generally process existence/completion is not experimental success; wait exits0 even for failed tasks. Two launch jobs really failed; saved state/log inspection caught this despite a successful waiter. A standard structured child update (task IDs, watcher IDs, actual task results, artifact paths, next dependency) would reduce back-and-forth.
|
||||
|
||||
> Scientific reasoning failures belong to us models, not necessarily harness: repeated 'low value => impossible' inferences and overly strong causal claims need actual evidence review. Keep the supervisor's normal tools. Also my detailed corrections are becoming a lot of prose; concise artifact-linked review diffs would help keep the user-facing recap small.
|
||||
|
||||
Follow-up inference (Pi/OpenAI): initial identity registration needs improvement, but continued routing reportedly works. Prioritize reducing repeated unchanged plan context and metadata-only review events, without suppressing changed requirements or completion claims. No active-session intervention performed.
|
||||
|
||||
— Pi/OpenAI
|
||||
@@ -0,0 +1,25 @@
|
||||
Package updates are available. Run pi update --extensions
|
||||
Packages:
|
||||
- github.com/wassname/pi-better-compaction
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Goal plan actions
|
||||
|
||||
→ status — Show current plan
|
||||
new — New plan
|
||||
attach — Open an existing plan
|
||||
review — Review current plan
|
||||
ready — Approve draft
|
||||
stop — Pause work
|
||||
resume — Continue paused work
|
||||
solo — Work in this session
|
||||
model — Set worker model
|
||||
exit — Leave goal mode
|
||||
help — Show commands
|
||||
|
||||
↑↓ navigate enter select escape/ctrl+c cancel
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
goals-menu-final-check on main accounts/fireworks/models/deepseek-v4-flash-0731 · [░░░░░░░░░░] 0.0%/1.0M (auto) · $0.000
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Appendix (context, not approved)
|
||||
|
||||
- Hex mapping for the discriminator: "hello\n" = 68656c6c6f0a. wc -c gives 6 for this exact file; any extra byte (trailing
|
||||
space, second newline, CRLF) changes the count or the hex and fails the verify command.
|
||||
|
||||
|
||||
○ create hello.txt with exactly the 6 bytes "hello\n" and save byte-check.log
|
||||
◦ write hello.txt with `printf 'hello\n' > hello.txt` (printf, not echo: echo adds an extra newline)
|
||||
◦ run byte and content checks, save their output to byte-check.log
|
||||
◦ run the verify command, save its output as evidence
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Review /tmp/goals-menu-final-check/.pi/plan/01a08acb-b748-77f7-8df7-f992ad01cac0-main.md
|
||||
|
||||
→ Ready
|
||||
Discuss
|
||||
Edit
|
||||
Cancel
|
||||
|
||||
↑↓ navigate enter select escape/ctrl+c cancel
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
goals-menu-final-check on main [?]
|
||||
goals: planning | 0/1 reviewed · accounts/fireworks/models/deepseek-v4-flash-0731 · [░░░░░░░░░░] 4.0%/1.0M (auto) · $0.012
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
> @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 23 passed (23)
|
||||
Tests 174 passed (174)
|
||||
Start at 18:09:15
|
||||
Duration 5.31s (transform 7.14s, setup 0ms, import 12.81s, tests 16.70s, environment 3ms)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 lint
|
||||
> biome check src/ test/
|
||||
|
||||
Checked 43 files in 49ms. No fixes applied.
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 typecheck
|
||||
> tsc --noEmit
|
||||
|
||||
@@ -5,6 +5,8 @@ export function planViews(plan: string): { short: string; long: string } {
|
||||
let omittedIndent: number | null = null;
|
||||
let omittedHeading: number | null = null;
|
||||
for (const line of long.split("\n")) {
|
||||
// Pi/OpenAI: Worker identity bookkeeping is not a change to agreed requirements.
|
||||
if (/^-\s*(?:active worker|worker session|worker intercom session):/i.test(line)) continue;
|
||||
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
|
||||
if (heading) {
|
||||
if (omittedHeading !== null && heading[1].length <= omittedHeading) omittedHeading = null;
|
||||
|
||||
+6
-6
@@ -284,22 +284,22 @@ export const completeGoalParamDescription = "The goal's text: the line after 'go
|
||||
|
||||
// Planning and interview. Keep the full drafting guide one-shot rather than repeating it each turn.
|
||||
export function prototypePlanning(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; give each distinct goal a failure mode, discriminator and evidence expectation above ## Log. Record the requested worker model in preferences. Use /goals review or /goals ready; /goals exit preserves the draft.`;
|
||||
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; give each distinct goal a failure mode, discriminator and evidence expectation above ## Log. 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 prototypePlanningSeed(objective: string, planPath: string): string {
|
||||
return `Draft or revise ${planPath} for this objective: ${objective}. Read any existing plan first. ${prototypePlanning(planPath)}\n\n${planDrafting}`;
|
||||
return `Enter a planning conversation focused on the user's goals. ${objective ? `Initial idea: ${objective}.` : "Ask what the user wants to achieve; they do not need to supply a finished objective."} 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. ${prototypePlanning(planPath)}\n\n${planDrafting}`;
|
||||
}
|
||||
export const prototypePlanDocument = (objective: string) => `# Goal plan\n\n## Objective\n${objective}\n\n## Goals\n\n## Log\n`;
|
||||
export const prototypeDiscuss = "Discuss the current draft in ordinary chat. Do not launch a worker or reopen the review menu until requested.";
|
||||
|
||||
// Ready and explicit child attachment: stock lineage-only sessions do not inherit the shared plan.
|
||||
export const attachGoalPlanDescription = "Delegated goals-worker only: attach the absolute plan path explicitly supplied in your task. Read it without rewriting it. Restores the worker widget and plan context; grants no parent completion authority. No discovery or worker launch.";
|
||||
export const childPlanRole = "You are the delegated implementation worker. Keep the supplied plan and evidence current; do not approve your own goals or launch a second writer. Call AttachGoalPlan with the explicit plan path in your task before implementation (also after reconnect if unbound). Send completion and blocker reports via Intercom to the supervisor ID supplied in the task, then stay open for live messages. Do not exit or use caller_ping; unsent editor drafts are not visible in model context.";
|
||||
export const childPlanRole = "You are the delegated implementation worker. Maintain task ticks, evidence and Log entries for your delegated work in the supplied plan. 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 still live, let it continue or message it. Only after confirming it stopped use subagent_resume with that sessionFile. Never restart completed work.`
|
||||
: `Delegate the first unfinished goal to agent '${workerName}' with subagent; provide name, title and a bounded task.`;
|
||||
return `Ready approved this plan: ${planPath}. Stay here as supervisor. ${launch} Include the absolute plan path, require AttachGoalPlan, and give the child supervisor Intercom session ${supervisorId}. The child sends its completion report there and stays open. Record the returned worker session path and confirmed Intercom ID in plan preferences for reconnection. Do not start a second writer. Inspect actual outputs when the child reports.\n\n${plan}`;
|
||||
return `Ready approved this plan: ${planPath}. Stay here as supervisor. ${launch} Include the absolute plan path, require AttachGoalPlan, and give the child supervisor Intercom session ${supervisorId}. The child sends its completion report there and stays open. Require an initial worker report with its actual Intercom UUID, saved-session path and current provider/model; the async launch may return only a runtime ID. Record each distinct identity in plan preferences, marking child-reported fields as such until verified. Do not start a second writer. Inspect actual outputs when the child reports.\n\n${plan}`;
|
||||
}
|
||||
|
||||
// Supervision and turn-event upkeep (not a scheduled wake-up).
|
||||
@@ -308,8 +308,8 @@ export function prototypeSupervisor(workerName: string, planPath: string, superv
|
||||
Use stock subagent for launch and subagent_resume with the returned sessionFile only after confirming the worker stopped. A stored handle is not proof of liveness; missing runtime state is not proof it stopped. Use pi-intercom list/status to identify the actual live child session before live steering; receipt alone does not prove action. Give each worker your Intercom session ID ${supervisorId}; require its completion report through Intercom while its pane stays open. A recap alone sends no instruction. Record '- worker session:' and '- worker intercom session:' in plan preferences from actual launch results and received-message identity; never confuse the runtime ID with the Intercom ID. Ensure the child calls AttachGoalPlan with the supplied path. Inspect results before CompleteGoal, then continue only unfinished goals.
|
||||
Use the worker model requested in plan preferences, verify the resolved model, and report unavailable choices instead of silently substituting. Keep normal tools, not edxeth's restricted orchestrator mode. After reload or compaction reread the plan. Failed compaction, exhausted credits or lost connection do not erase progress: diagnose the actual error, restore an available authorized model/credits and resume the same saved session; never restart long work. Stock edxeth can crash the parent when a worker exits after parent reload: preserve drafts and stop workers before /reload. If it already happened, restart the saved parent session; do not repeat completed work.`;
|
||||
}
|
||||
export function prototypeUpkeep(planPath: string, foldedPlan: string): string {
|
||||
return `Plan upkeep (turn event, not a timer): ${planPath} has had eight turns without working-set changes. Keep subtasks ([/] active), evidence and one useful ## Log/Learnings entry current. Move settled detail below ## Log, preserving the agreed outcome and user voice. Continue authorized unfinished work in your current role; a reminder is not permission to resume paused work.\n\n${foldedPlan}`;
|
||||
export function prototypeUpkeep(planPath: string): string {
|
||||
return `Plan upkeep: update task ticks, evidence and Log in ${planPath} when you have new progress to record. Preserve agreed goals and discriminators. If already reviewing evidence, finish that review rather than repeat a status recap. This turn-event reminder does not resume paused work.`;
|
||||
}
|
||||
export function prototypePlanContext(mode: string, path: string | undefined, text: string): string {
|
||||
return `Current goal mode: ${mode}. Earlier role messages are historical; this current role governs.\nPlan: ${path ?? "not attached"}\n${text}`;
|
||||
|
||||
+40
-11
@@ -217,7 +217,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${soloNotice(state.plan!)}`);
|
||||
}
|
||||
const help = "/goals <objective> | review | ready | status | stop | resume | solo | exit | attach <plan.md> [solo] | model <model>\n/subagents opens edxeth's worker UI. Stop/exit pause this plan locally; worker termination must be confirmed through subagent_kill or its pane. No forced compaction or model switch; the worker pane's own model is chosen with /model in that pane. Hourly check-ins are one session-bound schedule_prompt job; plan-change reviews are the plan-watcher event hook.";
|
||||
const help = "/goals new [initial idea] | review | ready | status | stop | resume | solo | exit | attach <plan.md> [solo] | model <model>\n/subagents opens the worker controls. Stop/exit pause this plan locally; worker termination must be confirmed through subagent_kill or its pane. No forced compaction or model switch; the worker pane's own model is chosen with /model in that pane. Hourly check-ins are one session-bound schedule_prompt job; plan-change reviews are the plan-watcher event hook.";
|
||||
async function ready(ctx: ExtensionContext, menu: boolean) {
|
||||
if (state.mode !== "planning") { ctx.ui.notify("Ready applies to a draft; use status or resume.", "warning"); return; }
|
||||
const text = planText();
|
||||
@@ -254,14 +254,27 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
turnsStale = workingSet === lastWorkingSet ? turnsStale + 1 : 0;
|
||||
lastWorkingSet = workingSet;
|
||||
refresh(ctx);
|
||||
if (turnsStale >= 8 && goals(snapshot.text).some(g => g.status === "open" || g.status === "active")) {
|
||||
if (turnsStale === 8 && goals(snapshot.text).some(g => g.status === "open" || g.status === "active")) {
|
||||
// Pi queues context-only messages until tool results are appended at turn_end.
|
||||
// This reaches the next model call in a long run without triggering another run.
|
||||
pi.sendMessage({ customType: "pi-goals-upkeep", content: prototypeUpkeep(state.plan!, workingSet), display: false }, { triggerTurn: false });
|
||||
turnsStale = 0;
|
||||
pi.sendMessage({ customType: "pi-goals-upkeep", content: prototypeUpkeep(state.plan!), display: false }, { triggerTurn: false });
|
||||
}
|
||||
});
|
||||
pi.on("agent_end", (_e, ctx) => { refresh(ctx); if (!planWatcher && state.mode === "supervising") watchPlan(ctx); });
|
||||
let proposedDraft = "";
|
||||
let proposing = false;
|
||||
pi.on("agent_settled", async (_e, ctx) => {
|
||||
if (state.child || state.mode !== "planning" || !ctx.hasUI || proposing) return;
|
||||
const text = planText();
|
||||
const version = `${state.plan}:${digest(text)}`;
|
||||
if (!goals(text).length || version === proposedDraft) return;
|
||||
proposedDraft = version;
|
||||
proposing = true;
|
||||
try {
|
||||
pi.sendMessage({ customType: "goal-plan-proposal", content: text, display: true }, { triggerTurn: false });
|
||||
await ready(ctx, true);
|
||||
} finally { proposing = false; }
|
||||
});
|
||||
// No context hook. Historical message arrays, native checkpoints and model selection are untouched.
|
||||
pi.on("before_agent_start", (event, ctx) => {
|
||||
if (state.mode === "chat") return;
|
||||
@@ -294,12 +307,25 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
});
|
||||
|
||||
pi.registerCommand("goals", {
|
||||
description: "Prototype: plan here, supervise a visible edxeth worker",
|
||||
description: "Goal plan actions: new, review, ready, status, stop, resume, solo, attach, model, exit",
|
||||
getArgumentCompletions: (prefix) => ["new", "review", "ready", "status", "stop", "resume", "solo", "attach", "model", "exit", "help"].filter((verb) => verb.startsWith(prefix)).map((verb) => ({ value: verb, label: verb })),
|
||||
handler: async (args, ctx) => {
|
||||
try {
|
||||
if (state.child) { ctx.ui.notify("This is the delegated worker. Goal approval belongs to its parent.", "info"); return; }
|
||||
const command = args.trim();
|
||||
if (!command || command === "help") { ctx.ui.notify(help, "info"); return; }
|
||||
let command = args.trim();
|
||||
if (!command) {
|
||||
const actions = ["status — Show current plan", "new — New plan", "attach — Open an existing plan", "review — Review current plan", "ready — Approve draft", "stop — Pause work", "resume — Continue paused work", "solo — Work in this session", "model — Set worker model", "exit — Leave goal mode", "help — Show commands"];
|
||||
const before = generation;
|
||||
const choice = await ctx.ui.select("Goal plan actions", actions);
|
||||
if (!choice || before !== generation) return;
|
||||
command = choice.split(" — ")[0];
|
||||
if (["attach", "model"].includes(command)) {
|
||||
const value = await ctx.ui.editor(command === "attach" ? "Plan path (optional: solo)" : "Worker model (provider/model)", "");
|
||||
if (!value?.trim() || before !== generation) return;
|
||||
command += ` ${value.trim()}`;
|
||||
}
|
||||
}
|
||||
if (command === "help") { ctx.ui.notify(help, "info"); return; }
|
||||
if (command === "status") {
|
||||
refresh(ctx);
|
||||
ctx.ui.notify([
|
||||
@@ -318,6 +344,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (command === "model" || command.startsWith("model ")) {
|
||||
if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; }
|
||||
const ref = command.slice("model".length).trim();
|
||||
if (!ref) { ctx.ui.notify("Use /goals model <provider/model>; no preference changed.", "info"); return; }
|
||||
const lines = planText().split("\n");
|
||||
const pref = `- preferred worker model: ${ref || "(none specified)"}`;
|
||||
const found = lines.findIndex((line) => /^-\s*preferred worker model:/i.test(line));
|
||||
@@ -331,9 +358,9 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
}
|
||||
if (command === "attach" || command.startsWith("attach ")) {
|
||||
const rest = command.slice("attach".length).trim();
|
||||
const [raw, kind] = rest.split(/\s+/);
|
||||
const [raw, kind, extra] = rest.split(/\s+/);
|
||||
const solo = kind === "solo";
|
||||
if (kind && !solo) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "warning"); return; }
|
||||
if (extra || (kind && !solo)) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "warning"); return; }
|
||||
if (!raw) { ctx.ui.notify("Use /goals attach <path-to-plan.md> [solo].", "info"); return; }
|
||||
const target = isAbsolute(raw) ? raw : resolve(ctx.cwd, raw);
|
||||
let text: string;
|
||||
@@ -375,13 +402,15 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
enterSolo(ctx);
|
||||
return;
|
||||
}
|
||||
if (command !== "new" && !command.startsWith("new ")) { ctx.ui.notify(`Unknown or incomplete command. ${help}`, "warning"); return; }
|
||||
const objective = command.slice(4).trim();
|
||||
if ((state.worker && !state.workerStopped) || state.mode === "supervising") { ctx.ui.notify("Exit and resolve the existing worker before replacing the plan. The current plan is preserved.", "warning"); return; }
|
||||
const path = join(ctx.cwd, ".pi", "plan", `${ctx.sessionManager.getSessionId()}-main.md`);
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
// Never overwrite an earlier plan at this session path; the model can revise it after inspection.
|
||||
try { writeFileSync(path, prototypePlanDocument(command), { flag: "wx" }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; }
|
||||
try { writeFileSync(path, prototypePlanDocument(objective), { flag: "wx" }); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; }
|
||||
state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(prototypePlanningSeed(command, path));
|
||||
send(prototypePlanningSeed(objective, path));
|
||||
} catch (error) { ctx.ui.notify(String(error), "error"); }
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,6 +11,15 @@ it("keeps outcome, preferences and discriminators without tasks or history", ()
|
||||
expect(views.long).not.toContain("unapproved idea");
|
||||
});
|
||||
|
||||
it("omits only named worker identity fields from review while retaining them in full context", () => {
|
||||
const base = "# Plan\n- preferred worker model: provider/model\n- [ ] goal: result\n - discriminator: exact bytes";
|
||||
const metadata = "\n- Active worker: worker-1\n- worker session: /saved.jsonl\n- worker intercom session: uuid";
|
||||
expect(planViews(base + metadata).short).toBe(planViews(base).short);
|
||||
expect(planViews(base + metadata).long).toContain("/saved.jsonl");
|
||||
expect(planViews(base.replace("exact bytes", "approximate match")).short).not.toBe(planViews(base).short);
|
||||
expect(planViews(base.replace("[ ]", "[x]")).short).not.toBe(planViews(base).short);
|
||||
});
|
||||
|
||||
it("stops at history and preserves a manual goal tick", () => {
|
||||
const view = planViews("# Plan\n1. [x] goal: result\n## Log\n1. [ ] goal: historical");
|
||||
expect(view.short).toContain("[x] goal: result");
|
||||
|
||||
+66
-4
@@ -22,7 +22,7 @@ function fixture(child = false) {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "goals-main-test-")); roots.push(cwd);
|
||||
const entries: any[] = []; const hooks = new Map<string, any>(); const commands = new Map<string, any>(); const tools = new Map<string, any>();
|
||||
const messages: any[] = [];
|
||||
const ctx = { cwd, sessionManager: { getBranch: () => entries, getSessionId: () => "copy-only" }, ui: {
|
||||
const ctx = { cwd, sessionManager: { getBranch: () => entries, getSessionId: () => "copy-only" }, hasUI: true, ui: {
|
||||
theme: { fg: (_color: string, text: string) => text }, notify: vi.fn(), setStatus: vi.fn(), setWidget: vi.fn(), select: vi.fn(async () => "Ready"), editor: vi.fn(),
|
||||
} };
|
||||
const pi = {
|
||||
@@ -43,7 +43,7 @@ function fixture(child = false) {
|
||||
const command = (value: string) => commands.get("goals").handler(value, ctx);
|
||||
const path = join(cwd, ".pi/plan/copy-only-main.md");
|
||||
const plan = "# Plan\n- [ ] goal: first output\n- [ ] goal: second output\n\n## Log\n";
|
||||
const draft = async () => { await command("two outputs"); writeFileSync(path, plan); };
|
||||
const draft = async () => { await command("new two outputs"); writeFileSync(path, plan); };
|
||||
const shutdown = () => hooks.get("session_shutdown")();
|
||||
shutdowns.push(shutdown);
|
||||
const changed = () => messages.filter((m) => m.message?.content?.includes("Plan changed")).length;
|
||||
@@ -53,9 +53,68 @@ function fixture(child = false) {
|
||||
renameSync(tmp, path);
|
||||
await delay(25);
|
||||
};
|
||||
return { ctx, pi, hooks, tools, messages, command, path, plan, draft, shutdown, changed, atomicWrite, entries };
|
||||
return { ctx, pi, hooks, tools, commands, messages, command, path, plan, draft, shutdown, changed, atomicWrite, entries };
|
||||
}
|
||||
|
||||
it("shows action choices and autocomplete without starting work", async () => {
|
||||
const f = fixture();
|
||||
f.ctx.ui.select.mockResolvedValueOnce(undefined as any);
|
||||
await f.command("");
|
||||
expect(f.ctx.ui.select).toHaveBeenCalledWith("Goal plan actions", expect.arrayContaining(["new — New plan", "resume — Continue paused work"]));
|
||||
expect(f.messages).toHaveLength(0);
|
||||
expect(f.commands.get("goals").getArgumentCompletions("res")).toEqual([{ value: "resume", label: "resume" }]);
|
||||
});
|
||||
|
||||
it.each(["redy", "start", "two outputs", "status extra", "attach some.md solo extra"])("rejects %s without changing the plan or sending a model prompt", async (text) => {
|
||||
const f = fixture(); await f.draft();
|
||||
const before = readFileSync(f.path, "utf8");
|
||||
const entries = f.entries.length; const messages = f.messages.length;
|
||||
await f.command(text);
|
||||
expect(readFileSync(f.path, "utf8")).toBe(before);
|
||||
expect(f.entries).toHaveLength(entries);
|
||||
expect(f.messages).toHaveLength(messages);
|
||||
});
|
||||
|
||||
it("requires a model argument without clearing the preference", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("model provider/model");
|
||||
const before = readFileSync(f.path, "utf8");
|
||||
await f.command("model");
|
||||
expect(readFileSync(f.path, "utf8")).toBe(before);
|
||||
});
|
||||
|
||||
it.each(["menu", "command"])("enters planning conversation through %s without an objective box or worker launch", async (route) => {
|
||||
const f = fixture();
|
||||
f.ctx.ui.select.mockResolvedValueOnce("new — New plan");
|
||||
await f.command(route === "menu" ? "" : "new");
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
expect(f.ctx.ui.editor).not.toHaveBeenCalled();
|
||||
expect(f.messages.at(-1).message.content).toContain("Ask what the user wants to achieve");
|
||||
expect(f.hooks.get("tool_call")({ toolName: "subagent" }).block).toBe(true);
|
||||
});
|
||||
|
||||
it("automatically proposes a changed settled draft once and preserves Discuss", async () => {
|
||||
const f = fixture(); await f.draft();
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Discuss");
|
||||
await f.hooks.get("agent_settled")({}, f.ctx);
|
||||
expect(f.messages.some(m => m.message.customType === "goal-plan-proposal" && m.message.content === f.plan)).toBe(true);
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
const calls = f.ctx.ui.select.mock.calls.length;
|
||||
await f.hooks.get("agent_settled")({}, f.ctx);
|
||||
expect(f.ctx.ui.select).toHaveBeenCalledTimes(calls);
|
||||
writeFileSync(f.path, f.plan.replace("first output", "revised output"));
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Ready");
|
||||
await f.hooks.get("agent_settled")({}, f.ctx);
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising");
|
||||
});
|
||||
|
||||
it("does not propose an empty draft or a delegated worker's plan", async () => {
|
||||
const f = fixture(); await f.command("new");
|
||||
await f.hooks.get("agent_settled")({}, f.ctx);
|
||||
expect(f.ctx.ui.select).not.toHaveBeenCalled();
|
||||
const child = fixture(true); await child.hooks.get("agent_settled")({}, child.ctx);
|
||||
expect(child.ctx.ui.select).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps Ready in the same chat, sends saved notices and never installs a context hook", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("review");
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising");
|
||||
@@ -431,7 +490,10 @@ it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, resets on
|
||||
f.hooks.get("turn_end")({}, f.ctx);
|
||||
expect(reminders()).toHaveLength(1);
|
||||
expect(reminders()[0].options).toEqual({ triggerTurn: false });
|
||||
expect(reminders()[0].message.content).toContain("first output");
|
||||
expect(reminders()[0].message.content).toContain(f.path);
|
||||
expect(reminders()[0].message.content).not.toContain("first output");
|
||||
for (let i = 0; i < 16; i++) f.hooks.get("turn_end")({}, f.ctx);
|
||||
expect(reminders()).toHaveLength(1);
|
||||
expect(reminders()[0].message.content).not.toContain("historical recap");
|
||||
for (let i = 0; i < 7; i++) f.hooks.get("turn_end")({}, f.ctx);
|
||||
writeFileSync(f.path, f.plan.replace("first output", "refined output"));
|
||||
|
||||
Reference in New Issue
Block a user