mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-11 12:43:57 +08:00
Integrate package-based supervision with explicit recovery and visible workers
Retain planning guidance and upkeep; use stock edxeth, Intercom and scheduled prompts. Verify with 162 tests and isolated Fireworks trials. Document unresolved upstream reload and scheduler limitations. Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -53,6 +53,35 @@ And it would only be a few output tokens.
|
||||
|
||||
-- wassname (spelling and punctuation corrected by Pi/OpenAI)
|
||||
|
||||
## Agreed package-based design, 2026-09-10
|
||||
|
||||
> use the exdth subagent as they have a full herdr pane
|
||||
> use pi schedule prompt for check ins and goal plan loop on supervisor (unless it should be part of pi plan)
|
||||
> use pi-intercome for sending message between agent and supervisor where there is not great fit in this subagent package
|
||||
>
|
||||
> hopefully that gets me most of what I wanted in a simple, using existing packages, and even user visible way
|
||||
|
||||
-- wassname (verbatim)
|
||||
|
||||
Pi/OpenAI implementation scope:
|
||||
- Keep the short main-chat supervisor prototype and unmodified edxeth/pi-subagents. Workers use its full interactive Herdr pane and saved-session resume. Do not resume the abandoned runtime patch.
|
||||
- pi-goals owns the agreed plan, review notifications and supervisor role. Installed pi-schedule-prompt owns the visible hourly wake-up: one session-bound job, no model override, reminding the same supervisor to inspect the plan/progress and continue authorized work. Remove it on pause, exit or completion; do not build another timer or scheduler.
|
||||
- Use existing edxeth reports/resume where they fit. Use pi-intercom for live cross-session messages where needed; identify the actual worker session, preserve human drafts, and test delivery and auto-exit interaction before claiming compatibility. No custom message transport.
|
||||
- The supervisor may edit the plan and approve completion after inspecting actual results. It delegates implementation and must not weaken the agreed goal to accept worker output. Keep normal tools; express the division in editable prompts.
|
||||
- State the requested worker model in plan preferences; the supervisor selects it and checks the resolved model. Reuse existing usage displays before adding token-reporting code.
|
||||
- Keep all model-facing prompts in `src/prompts.ts`, in narrative order: planning/interview, Ready, supervision and plan upkeep, check-ins/messages, completion, pause/resume and solo. Make them easy for the user to review and edit.
|
||||
- Preserve useful features from `main`: plan widgets, progress/subtask visibility, plan-upkeep reminders, high-value planning questions and post-compaction plan context. Check which role needs each feature rather than copying the old supervisor runtime.
|
||||
- Scheduled loops must be visible, editable and removable using the scheduler's own UI. Explain whether each reminder is a scheduled job or an event hook; do not advertise a second timer that does not exist.
|
||||
- Keep an explicit recoverable solo mode: confirm any worker has stopped before allowing the main thread to take over implementation and plan edits. Solo completion is self-verification, not an independent supervisor review.
|
||||
|
||||
On inexpensive testing:
|
||||
|
||||
> try deepseek flash or glm flash for cheap tests. codex lunda on plan 2 are ok
|
||||
|
||||
-- wassname (verbatim; model availability and exact provider IDs still need checking)
|
||||
|
||||
These decisions supersede the older two-pane supervisor transport and approval mechanics described below, but retain the user's judgment, autonomy and visibility preferences. -- Pi/OpenAI
|
||||
|
||||
## Supervisor behavior preferences
|
||||
|
||||
Recorded by Pi/OpenAI from wassname's instructions.
|
||||
|
||||
+30
-33
@@ -1,46 +1,43 @@
|
||||
# Main-chat supervisor prototype
|
||||
# Main-chat supervision
|
||||
|
||||
Prepared by Pi/OpenAI. Not installed in the normal checkout.
|
||||
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.
|
||||
|
||||
This branch changes the package entrypoint to `src/prototype.ts`. Planning and supervision stay in the original chat and model. Ready asks that agent to delegate implementation through **edxeth/pi-subagents**, using its real interactive Herdr worker, automatic report and session resume. The parent keeps its normal inspection tools. There is no second supervisor, startup compaction, Intercom pairing, VCC polling or context-array mutation.
|
||||
## Packages
|
||||
|
||||
## Runtime
|
||||
- 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.
|
||||
|
||||
Pinned source: [edxeth/pi-subagents v2.9.0](https://github.com/edxeth/pi-subagents/tree/953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4).
|
||||
This is **not** the npm package `pi-subagents` from nicobailon. They register overlapping tools and commands; do not load both.
|
||||
## Use
|
||||
|
||||
`prototype/agents/goals-worker.md` selects:
|
||||
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.
|
||||
|
||||
- Interactive, asynchronous Pi in Herdr; normal tools, extensions and skills.
|
||||
- Trusted project resources and APPEND_SYSTEM inheritance, explicitly enabled.
|
||||
- A clean model context linked to the parent (`lineage-only`), not a large inherited transcript. The parent supplies the approved plan/task. No compulsory compaction.
|
||||
- Automatic report and exit after a goal. The worker is interactive while running; an operator can take over. Follow-up resumes the same saved session with edxeth's `subagent_resume`.
|
||||
- `parent-close-policy: continue`. Closing the parent must not silently kill the worker. This is a requested launch policy, not a claim of lossless crash/reload recovery.
|
||||
## Controls and recovery
|
||||
|
||||
We deliberately do not enable edxeth's restricted orchestrator mode: our parent must inspect actual files and results.
|
||||
- `/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.
|
||||
|
||||
## Isolated trial setup
|
||||
## Known dependency limits
|
||||
|
||||
Do not change existing panes, installed package paths or user settings.
|
||||
- **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.
|
||||
|
||||
1. Obtain and inspect the pinned edxeth checkout. It needs the peer dependencies listed in its package.json, compatible with installed Pi 0.85.1. For the local trial we used a read-only dependency symlink to the already installed compatible dependencies; no install scripts were run.
|
||||
2. Run `node prototype/prepare.mjs /path/to/edxeth-checkout /path/to/installed/pi-package`. It creates a private temporary agent directory and Git project, copies auth/model/settings files, retains installed normal packages except the two conflicting packages, and adds this worktree plus edxeth. It prints the isolated paths. It does not launch Pi.
|
||||
For this user's authorized sandbox-off trial, append `--no-sandbox` to the preparation command. It applies the startup flag to both isolated roles, including after reload. Without that explicit flag, sandbox policy remains unchanged.
|
||||
3. Create a new no-focus Herdr test pane at the printed project path. Run the printed `start.zsh` there. Do not use an existing research pane. Extension-specific sidecar configuration may need explicit trial configuration; the copied top-level settings are not a complete copy of every extension's state.
|
||||
4. Use `/goals <objective>`, inspect the draft, and choose `/goals review` → Ready. `/goals ready` is explicit approval without opening the menu.
|
||||
5. Observe the actual worker pane and parent report. Inspect the files and saved verification. Repeat through a second goal, using the same saved worker session. Record any intervention.
|
||||
## Isolated validation
|
||||
|
||||
Auth copies and raw sessions are private trial files, not repository artifacts. Never commit them.
|
||||
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.
|
||||
|
||||
## Commands and limits
|
||||
[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.
|
||||
|
||||
- `/goals status`: local phase, plan path and last worker session. Inspect `/subagents` for runtime liveness; we do not invent it from a stale local flag.
|
||||
- `/goals stop`: persist a local pause, then ask the parent to stop the recorded worker through `subagent_kill`. The message explicitly says remote stop is not yet confirmed. New launch/resume calls are blocked while planning or paused.
|
||||
- `/goals resume`: explicit authorization to continue a paused plan through the existing edxeth session. A draft still requires Ready.
|
||||
- `/goals exit`: return to ordinary chat without approving a draft. The plan is retained. A recorded worker still needs confirmed termination.
|
||||
- `/goals solo`: explicitly authorize direct work when no worker was recorded. It does not silently take over from a potentially live worker.
|
||||
- `CompleteGoal`: records **the parent's evidence judgment**, not an independent model judge. Evidence files must exist and be nonempty; the parent must inspect what they show. Git cleanliness/tracking is not required. Manual checkbox edits remain unsigned claims. Deleted/reopened/ambiguous subjects lose their recorded sign-off.
|
||||
|
||||
The [functional report](../docs/slop/reviews/edxeth-prototype/README.md) records a real two-goal completion after Ready, worker-pane evidence, parent reload, and the copy-only native replay check, including failed attempts and interventions.
|
||||
|
||||
This is a prototype, not a replacement release. It does not have automatic active-worker rediscovery after a lost launch result, automatic parent crash recovery, or guaranteed stop acknowledgement. Reports are goal/task-boundary handoffs, not periodic VCC overviews. A fresh worker defaults to the parent's model; configure a concrete worker model in its definition if desired. Existing long-history encrypted replay still needs a copy-only compatibility check with the entire configured extension set.
|
||||
— Pi/OpenAI
|
||||
|
||||
@@ -9,7 +9,7 @@ tools: all
|
||||
skills: all
|
||||
trust-project: true
|
||||
inherit-append-system: true
|
||||
auto-exit: true
|
||||
auto-exit: false
|
||||
parent-close-policy: continue
|
||||
spawning: false
|
||||
---
|
||||
@@ -18,8 +18,8 @@ Implement only the goal delegated by the parent. Read the supplied plan and appl
|
||||
|
||||
Save the actual deliverable and verification output. Verify the outcome, not merely that a command ran. Ignored and uncommitted files are valid evidence. Do not clean or commit unrelated files to satisfy a Git-state gate.
|
||||
|
||||
Report the artifact paths, verification performed, observed result, remaining uncertainty and any blocker. Investigate failures before declaring yourself blocked. Respect explicit user pauses. Do not approve your own goal or launch another writer. Completion approval belongs to the parent.
|
||||
Call AttachGoalPlan with the supplied absolute plan path. Send the supervisor an Intercom report with the artifact paths, verification performed, observed result, remaining uncertainty and any blocker. Use the exact supervisor session ID supplied in the task; confirm it in Intercom's session list. Investigate failures before declaring yourself blocked. Respect explicit user pauses. Do not approve your own goal or launch another writer. Completion approval belongs to the parent.
|
||||
|
||||
This worker uses a clean model context linked to the parent, not a full transcript fork. The parent supplies the approved plan and task. Normal completion returns your report and closes this pane; the parent can resume this saved Pi session for follow-up. If the user takes over interactively, follow their direction.
|
||||
This worker uses a clean model context linked to the parent, not a full transcript fork. The parent supplies the approved plan and task. Send completion through Intercom and leave this pane open for follow-up messages. Do not call caller_ping, exit or shutdown: an unsent editor draft may exist even though it is absent from model context. Saved-session resume applies only after this session has stopped. If the user takes over interactively, follow their direction.
|
||||
|
||||
Prepared by Pi/OpenAI for the isolated edxeth prototype.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Package-based supervision
|
||||
|
||||
User intent and exact preferences: [AGENTS.md](../../AGENTS.md#agreed-package-based-design-2026-09-10). Use existing packages, keep the main supervisor small and visible; do not continue the edxeth patch.
|
||||
|
||||
1. [x] Preserve our attempt and switch to the short prototype
|
||||
- evidence: pushed WIP commit `2803505` on `experiment/main-supervisor-visible-worker`; includes abandoned edxeth source patch and pi-goals test log (`165 passed`). This is not acceptance of the patch.
|
||||
- current branch: `experiment/main-supervisor-edxeth`, HEAD `cb35fbf1feed9d4cea2f9675917adfe602c2835c` (fast-forwarded latest remote research-only commit).
|
||||
2. [/] goal: Supervise a full Herdr worker using unmodified edxeth
|
||||
- [x] Adopt remote prototype; source-check existing messaging and scheduling packages.
|
||||
- [x] Preserve main's useful features: plan/subtask widgets, plan-upkeep reminders, material planning questions, and context restoration after compaction. (prototype widget now shows open subtasks of the active goal; scheduleCheckIn carries the upkeep reminder; prototypePlanning keeps questions to one material round and records unknowns; before_agent_start re-pushes the full plan after reload/compaction; prototypeSupervisor repeats the plan path).
|
||||
- [x] Move ALL model-facing text into `src/prompts.ts` in narrative order; finish small prompt/model changes and regression tests. (new prototype section in prompts.ts: planning -> Ready -> supervision -> check-ins -> completion -> pause/resume/exit -> solo -> attach; 35 prototype tests + the full suite pass; lint + typecheck clean.)
|
||||
- [/] Verify worker uses the model specified in the plan; prefer DeepSeek Flash or GLM Flash for cheap tests. User also permits “codex lunda on plan 2”; resolve actual provider ID before using it. (prompt guidance + `/goals model <model>` writes the preference into plan preferences and `/goals status` shows it; REAL worker-model verification remains part of parent-owned Herdr acceptance with `accounts/fireworks/models/deepseek-v4-flash-0731` which is available.)
|
||||
- likely failure: auto-exit closes the pane before a live message arrives; follow-up must use saved-session resume after exit.
|
||||
- subtle failure: trial inherits supervisor model rather than requested worker model.
|
||||
- discriminator/deliverable: saved supervisor and worker transcripts identify the models used and show the supervisor checking the worker's output against the requested result.
|
||||
3. [/] goal: Visible hourly check-ins and plan review without another scheduler
|
||||
- [x] Finish prompt-based schedule_prompt integration: one session-bound 1h job, no model override, list before add, remove on pause/exit/completion. (scheduleCheckIn: list first, one owned session-bound job named goals-<sessionId>, retaining existing human-edited prompt/interval/disabled state, type interval 1h, no model override, job prompt reads the plan fresh, remove only that job on pause/exit/completion, keep human-edited schedules untouched, report unavailable instead of building a timer.)
|
||||
- [x] Test plan-directory watcher and `/goals review`; coalesce duplicate events and clean up on lifecycle changes. This is an event hook, not another scheduled loop. (150ms debounce before reading/refresh, classified unavailable snapshots retain signoffs, existing high-level plan view excludes task/evidence/Log maintenance while retaining requirements/manual ticks; tests cover atomic replacement, bursts, shutdown/reload and own writes.)
|
||||
- [x] Show users how to inspect, edit and remove hourly jobs through the scheduler UI; changing its prompt must affect subsequent check-ins. (`/goals status` names the goals-<sessionId> job and distinguishes the schedule_prompt job from the plan-watcher event hook; prompting points the job at the plan path.)
|
||||
- likely failure: scheduler unavailable or configured project-wide; show missing capability, do not silently add a timer.
|
||||
- subtle failure: stale reminder resumes paused work, or duplicate jobs/review notifications create a loop.
|
||||
- discriminator/deliverable: shortened test interval fires once in owning supervisor, reload does not duplicate it, stop/completion removes only its job, unchanged plan does not retrigger.
|
||||
4. [ ] goal: Use pi-intercom for live messages where edxeth reports/resume do not fit
|
||||
- [ ] Test stock packages in isolated parent-created Herdr panes: parent targets listed child session ID, busy/idle messages arrive without changing a human draft, observe auto-exit behavior.
|
||||
- likely failure: wrong or departed target session.
|
||||
- subtle failure: delivery receipt is mistaken for worker receipt/action or message disables normal reporting.
|
||||
- discriminator/deliverable: actual message and worker response in both session logs; no custom transport or edxeth patch.
|
||||
|
||||
5. [x] goal: Explicit solo recovery retains planning features without claiming independent review
|
||||
- [x] Confirm any worker has stopped before takeover; preserve the plan, evidence and widget. Main thread may implement and edit in solo mode. (`/goals solo` and `/goals attach <path> solo` share a generation-checked select confirming all other writers stopped: “Worker confirmed stopped / Cancel”; solo blocks subagent and subagent_resume; completion text labels solo as self-verification, not independent review; /goals attach <plan.md> [solo] connects an existing plan, restores a `- worker session:` note for subagent_resume reuse and never claims liveness. Tests cover both transitions.)
|
||||
- failure/discriminator: a stored worker handle must not permanently block solo after confirmed termination, and two writers must not run together. Test both transitions and label completion self-verification.
|
||||
|
||||
6. [ ] goal: Publish the reviewed changes and switch the normal Pi installation
|
||||
- User authorization: “ok once done commit and push. then change out install to use this and it's deps pls”.
|
||||
- [ ] Resolve review findings, simplify, rerun tests and inspect final interactive evidence; do not treat the known edxeth reload crash as a passed check.
|
||||
- [ ] Commit scoped source/tests/evidence and push `experiment/main-supervisor-edxeth`; preserve unrelated dirty and human files.
|
||||
- [ ] Read Pi package docs and yadm guidance, then replace the conflicting installed goals/subagent entries with this reviewed branch and clean unmodified edxeth. Retain schedule-prompt and Intercom; preserve unrelated packages/settings.
|
||||
- discriminator: inspect resolved package paths/versions and verify one goals entrypoint and one subagent implementation in a fresh visible test session. Save rollback instructions. Do not reload or close existing user sessions automatically.
|
||||
|
||||
## UAT / Verification
|
||||
|
||||
- User authorized execution, testing with Fireworks DeepSeek V4 Flash, then an external code review. Review must cover failed compaction, exhausted credits in either session, reload/reconnection, leaving planning, and attaching an existing plan to one (solo) or two (supervisor/worker) sessions without restarting completed work. Keep this a small explicit recovery path, not a replacement runtime.
|
||||
|
||||
- Run `npm test && npm run typecheck && npm run lint` with full saved logs, then inspect actual isolated Herdr results. Tests do not prove interactive compatibility.
|
||||
- Use only new test panes; never operate existing user/demo panes. This parent had `HERDR_ENV=1`, `HERDR_PANE_ID=w8:p62`; recheck after compaction. Headless subagents may lack Herdr context.
|
||||
- Read failed outputs, fix their cause, retry the affected scenario. No global package switch until successful trial. Reuse existing token displays; custom pair/token scripts are deferred.
|
||||
|
||||
## Current parent handover (supersedes older continuation notes below)
|
||||
|
||||
- Authoritative latest UAT: [functional trial notes](../reviews/20260910_package-supervision-herdr.md). Solo recovery after restarting saved supervisor passed with exact files and self-verification log. Timer prompt update/firing/removal passed. Disabled jobs are deleted by scheduler on reload. Parent reload kept worker usable, but later worker exit crashed parent in stock edxeth stale-widget callback; full reload lifecycle FAILS.
|
||||
- Native `/review` workflow `a1bcfc11-0212-410e-aad7-6fd704f7e9c6` has DeepSeek and GLM reviewers; exact child ids and search interruption evidence are in UAT notes. Broad find processes were terminated, both reviews resumed using exact package paths. Wait for their native completion; do not launch duplicates. Earlier Claude CLI attempts were a mistaken route for the requested /review skill.
|
||||
- User now authorizes commit, push, then normal install/dependency switch WHEN DONE, not before review/validation. No new source commit or global install changes yet. Current trial pane w8:p69 is restored and in solo mode; all test worker panes closed, scheduler store empty. Do not touch user/demo panes.
|
||||
- Measured source growth: prototype.ts 186 -> 426 lines, plus 88 new prompt lines. Simplification still needed; don't claim code shrinkage or full main-feature parity without checking.
|
||||
|
||||
## Handover: continuation checked, 2026-09-10
|
||||
|
||||
- Source snapshot remains uncommitted on `experiment/main-supervisor-edxeth` at `cb35fbf`; no branch/global-package/lifecycle-default changes and no pane reloads by the continuation worker. This resumed implementation run resolved `gpt-6-astra`, not the original Fireworks model. Parent's visible UAT sessions remain Fireworks.
|
||||
- Fixed the parent's confirmed paths: shared solo/attach stop confirmation with stale-menu guards; retained stopped-session references allow later plan replacement; in-flight launch blocks takeover. Solo closes watcher/debounce and requests removal of only the owned session-bound job. Scheduler prompts preserve existing disabled state and human edits; context resync never reinstalls jobs.
|
||||
- Missing, empty and failed plan reads are classified unavailable snapshots, not authoritative empty plans. Debounce occurs before refresh; signoffs survive transient saves and failed resync, then context retries after repair. Cancelled goals do not keep completion open. Solo's saved ## Log record says self-verification.
|
||||
- Explicit child-only `AttachGoalPlan` binds the absolute path supplied in the task, persists it, and restores the widget/subtasks and compaction context without granting CompleteGoal permission. No PI_GOALS_SHARED_PLAN discovery or runtime patch. Ready supplies the attachment instruction. Stock `953c6f6:src/launch/prep.ts:461` sets PI_SUBAGENT_AGENT; this is the child-role seam used.
|
||||
- Reused `planViews` for high-level requirement/manual-tick notifications without maintenance-only review loops. Reused main's folded working-set staleness idea: every eight unchanged turns, solo/supervised/attached-worker context gets upkeep via a context-only custom message, never a second timer or forced agent turn. All prototype model-facing role/completion/error text is in src/prompts.ts; public controls are documented in prototype/README.md.
|
||||
- Validation: `npm test` **158/158 PASS**, `npm run typecheck` PASS, `npm run lint` PASS. Logs: `slop/test-logs/continuation-full.log`, `continuation-typecheck.log`, `continuation-lint.log`. Focused prototype coverage: 35 tests. RPC also passes with deliberately inherited `PI_SUBAGENT_CHILD=1 PI_GOALS_ROLE=supervisor`: `continuation-rpc-child-env.log`.
|
||||
- Correction to the original handover: the RPC timeout did NOT establish a headless/sessionless incompatibility. The fixture inherited child role variables, and src/index.ts excludes children from main registration. The fixture now sanitizes PI_SUBAGENT_* and PI_GOALS_* for its spawned MAIN process. Prior categorical RPC/UI claims are withdrawn; no stash or human-file round-trip was used in this continuation.
|
||||
- Parent real UAT produced exact hello bytes and verified Intercom in both directions with an unsent draft surviving message delivery. Stock auto-exit:true then closed the worker and lost that draft (model prose falsely claimed there was none). Parent's isolated auto-exit:false + Intercom completion trial subsequently passed open-pane follow-up/OPEN-PANE-ACK with the draft retained. Parent reload with the live open worker remains pending. Repository lifecycle defaults are deliberately unchanged until that result; no new trial was launched by this worker.
|
||||
- External Claude review remains separately blocked by the installed runner's version-output regex. No CLI fallback or global runner edit; external review is still required. Parent owns the live trial report and captures under slop/reviews/.
|
||||
- Preserve unrelated AGENTS.md, `.local/`, `docs/human_journal.md` and review-fixes-native event files; none edited or staged in this continuation.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Unmodified Runtime Integrations — edxeth / pi-intercom / pi-schedule-prompt
|
||||
|
||||
Repo `pi-goals` @ fb5503f (experiment/main-supervisor-edxeth), entry `./src/prototype.ts` (package.json `pi.extensions`). Dirty tree = only slop event jsonl, `.local/`, `docs/human_journal.md` (confirmed). All edxeth quotes from `git -C pi-subagents-visible-worker show 953c6f6:PATH` (working tree is the dirty abandoned patch — ignored).
|
||||
|
||||
## 1. Stock edxeth parent↔worker messaging: exists but is re-launch not live injection
|
||||
|
||||
- Parent→worker: `subagent` / `subagent_resume` / `subagent_kill` (`src/tools/tool-names.ts`). Resume tool: "Continue a previous subagent session from its session file, optionally sending a follow-up task" (`src/tools/resume-tool.ts:63-83`). Follow-up is NOT injected into a live pane: `resume-service.ts:519-523` re-spawns the interactive session in a new mux surface and passes `piArgs.push(\`@${taskPath}\`)` (`writeResumeTaskArtifact`, task as initial-prompt artifact); background path feeds stdin `child.stdin?.end(expandedTask)` (`resume-service.ts:477`). There is no mid-run send-into-pane API.
|
||||
- Overlay: same resume path; editor prompt "Write a follow-up message. Enter sends, Esc cancels." (`src/tools/overlay/controller.ts:383`).
|
||||
- Worker→parent: `caller_ping` (one-shot; writes exit signal `{type:"ping"}`, `requestShutdown`, hard-exit backstop `process.exit(0)` at 750ms — `src/tools/caller-ping.ts:22-25,84-108`) and `subagent_done` (exit signal `{type:"done"}`). Registration: `if (!isInteractive || autoExit) registerCallerPingTool(...)` (`src/tools/subagent-done.ts:513-514`); `shouldRegisterSubagentDone` returns false for autoExit/interactive (`subagent-done.ts:48-51`). Results steer back to parent (`src/runtime/result-router.ts:76,169`).
|
||||
- `goals-worker` agent def (`prototype/agents/goals-worker.md`): `mode: interactive`, `auto-exit: true`, `parent-close-policy: continue` → child gets `caller_ping`, not `subagent_done`, and auto-closes after a terminal turn (`src/auto-exit.ts` `shouldAutoExitOnAgentEnd`).
|
||||
|
||||
## 2. pi-intercom 0.13.0 (installed): safe delivery, targeting is "probable" not guaranteed
|
||||
|
||||
- Delivery is agent-loop queueing, NOT terminal/Enter injection: busy interactive → `sendIncomingBrokerMessage(entry, "steer")` (`pi-intercom/index.ts:1245-1268`); idle → `pi.sendMessage({customType:"intercom_message",...}, {triggerTurn:true})` else `{deliverAs:"steer"}` (`index.ts:1191-1200`). Pi API: `deliverAs: "steer" | "followUp" | "nextTurn"` queue on the agent loop, drained at tool/turn boundaries (`pi-coding-agent/dist/core/agent-session.d.ts:401-422`; `pi-agent-core/dist/agent.d.ts:84-94` steer = "injected after the current assistant turn finishes"). Inline renderer only (`ui/inline-message.ts:6`). No human-draft interference.
|
||||
- Targeting: `to` = name, full session ID, or ID prefix; `cwd` scoping via `resolveTargetInCwd` (`project-agent.ts:188-240`). Presence name = `pi.getSessionName()` else runtime alias `subagent-chat-<id[:18]>` (`index.ts:494-512`). **Stock edxeth never sets `PI_SUBAGENT_INTERCOM_SESSION_NAME`** (git grep empty) → child is only addressable by its list/status ID or whatever its Pi session name resolves to; runtime aliases are excluded from queued-mail reconnection (README). Compatibility is *probable from source, not asserted by edxeth*.
|
||||
- Auto-exit effect: intercom has no auto-exit awareness (only PI_SUBAGENT_* orchestration envs, `index.ts:42-51`). A triggered turn in an `auto-exit: true` child ends with close (stopReason≠aborted); Escape/abort leaves it open. So intercom works only while the pane is live; follow-ups after close should use `subagent_resume`.
|
||||
- pi-messenger/pi-messenger-bridge: listed in `settings.json:107` but NOT in installed `node_modules` → **unverified**.
|
||||
|
||||
## 3. pi-schedule-prompt (installed): yes — tool-only add/list/remove suffices
|
||||
|
||||
- `add` binds current session by default: `const session = getDefaultScope() === "session" ? ctx.sessionManager.getSessionId() : undefined;` (`src/tool.ts:88-89`; default `"session"` at `tool.ts:17`, `index.ts:79`).
|
||||
- Single-session firing: `isLoadedFor = !job.session || job.session === sessionId` (`src/scheduler.ts:61-62`), re-checked at fire (`scheduler.ts:211`).
|
||||
- No-model inline: `this.pi.sendUserMessage(job.prompt, { deliverAs: "followUp" })` (`scheduler.ts:243`) — wakes only the owning session.
|
||||
- Cleanup: `session_shutdown` → `autoCleanupDisabledJobs` (own/unbound disabled only) + `cleanupSession` stops scheduler/hides widget (`index.ts:105-133,143-145`). No duplication on reload: `initializeSession` calls `cleanupSession(ctx)` first — "Without this, every `session_start` (fires on reload/resume/fork too…) leaks a live croner timer… accumulating duplicate fires" (`index.ts:86-90`); non-startup `session_start` also sweeps disabled (`index.ts:136-140`). Storage: project-local `.pi/schedule-prompts.json` (`storage.ts:12-19`).
|
||||
|
||||
## Verdict / minimal integration
|
||||
|
||||
All three are source-verified; nothing executed (report only — no messages/jobs/panes). Minimal test: (a) in a live stock-edxeth child, `intercom({action:"list"})` from parent and send to the child's listed ID while idle and while thinking; confirm no draft/Enter artifacts; (b) `schedule_prompt` add (inline, no model) → list → remove in the parent session, then close/reopen to confirm no double-fire and disabled-job sweep. Use intercom for live steering (child pane open), `subagent_resume` for follow-ups after close; prefer a small script/prototype over framework.
|
||||
@@ -0,0 +1,76 @@
|
||||
# DeepSeek reviewer — package-based supervision (cb35fbf + uncommitted patch)
|
||||
|
||||
Scope: `src/prototype.ts` (426 vs 186 lines), `src/prompts.ts` (+88), `test/prototype.test.ts` (+397), `test/rpc-review.test.ts` (env sanitize), `AGENTS.md`, `prototype/README.md`, `prototype/agents/goals-worker.md` (repo default `auto-exit: true`). Evidence basis: full source reads, stock edxeth `953c6f6` reads, installed `pi-schedule-prompt/src` and `pi-intercom` reads at the exact provided paths, parent UAT captures `01–19`, and `slop/reviews/20260910_package-supervision-herdr.md`. Nothing executed beyond `vitest` logs already saved; no source edits, no panes, no messages.
|
||||
|
||||
Tests: saved logs show 158/158 pass + typecheck/lint pass. The suite does exercise the new gates well (stale-menu invalidation, watcher debounce, unavailable snapshot signoff retention, upkeep fold, child attach). It does **not** cover: real auto-exit vs keep-open behavior, the stock stale-ctx crash on worker exit after reload, scheduler disabled-job deletion across reload, or intercom worker identification with multiple children — those are only covered by the parent's live captures, and the unit fake cannot prove UI/source acceptance.
|
||||
|
||||
---
|
||||
|
||||
## Findings (max 8, prioritized)
|
||||
|
||||
### 1. [Observed — HIGH] Repo default `auto-exit: true` discards unsent human drafts at every worker terminal turn; `auto-exit: false` + Intercom preserves the draft but does not fix the delayed reload crash
|
||||
- Source: `prototype/agents/goals-worker.md` frontmatter `auto-exit: true` (repo default, unchanged); agent prose "Normal completion returns your report and closes this pane".
|
||||
- Repro: parent captures `08` (auto-exit closed w8:p6E and lost `UNSENT-WORKER-DRAFT-KEEP`, while the worker's own tool result proved the Intercom reply had been delivered) and README "Known trial failure". With an isolated `auto-exit:false` profile, capture `09/11/13/14` show live Intercom steering + the unsent draft surviving send, ACK, and a parent `/reload`.
|
||||
- Minimal fix (package config/prompts only): flip the agent def to `auto-exit: false`, update the agent prose and `prototypeSupervisor` (see finding on "Reports arrive automatically"), and make the child report completion via an Intercom message to the parent id it receives in `from.id` / discovers via `intercom list`. This is exactly the minimal-adoption route the plan asks for — but see finding 2 before declaring lifecycle acceptance.
|
||||
|
||||
### 2. [Observed — HIGH] Delayed parent crash when a live open worker exits after a parent reload (stock edxeth, not pi-goals)
|
||||
- Source/evidence: `slop/reviews/package-supervision-captures/17-delayed-reload-worker-exit-crash.txt`; stack `running-registry.ts:255 -> widget.ts:139` — stale ExtensionContext after reload, "This extension ctx is stale after session replacement or reload".
|
||||
- Repro: parent reloaded with the keep-open worker alive (capture 14 passed for immediate re-targeting), then the worker exited normally (Ctrl+D) → the parent pane crashed on the stale callback.
|
||||
- Impact: the whole "reload with a live worker" story (which `auto-exit:false` depends on) is not lifecycle-safe until this stock behavior is understood or avoided. `auto-exit:false` preserves the draft and steering but does not fix the eventual worker-exit crash (observed — this crash occurred in the keep-open configuration).
|
||||
- Minimal mitigation within constraints: after a reload, do not rely on a normal worker exit; recover via saved-session restart + `/goals attach <plan> solo` (validated: capture `19` — exact bytes `solo-ok\n`, `Solo self-verification` log, original `hello.txt` preserved). README already bounds the claims; keep it that way and do not change repo lifecycle defaults until the crash trigger window is pinned.
|
||||
|
||||
### 3. [Observed — MEDIUM] Disabled owned scheduler job is deleted by the installed scheduler, contradicting `scheduleCheckIn`'s "retain disabled" instruction; check-ins then silently stop
|
||||
- Source: installed `pi-schedule-prompt/src/index.ts` `autoCleanupDisabledJobs` — deletes own/unbound disabled jobs on `session_shutdown` and on non-startup `session_start` (verified in the installed source; parent observed the deletion in the trial). `src/prompts.ts:328` `scheduleCheckIn` tells the model to "retain its human-edited prompt, interval and enabled/disabled state unchanged; never recreate, overwrite or re-enable", and `:332` "Do not reinstall a missing job from a scheduled check-in".
|
||||
- Repro: create the owned job, disable it via `/schedule-prompts`, `/reload` → job gone; no ready/resume prompt is re-sent on reload, so nothing ever informs the model — hourly check-ins stop silently, and the "retain disabled" guidance is unachievable.
|
||||
- Minimal fix (prompt-level): tell the model that a disabled owned job may be auto-cleaned at reload, and instruct it to surface that to the user (report-and-ask) instead of silently re-adding or silently ignoring. Optionally persist user intent (disabled) in the extension STATE entry so reload can restore it deliberately.
|
||||
|
||||
### 4. [Inferred — MEDIUM] Job add/remove guidance mismatches the scheduler API: auto-named jobs, and removal by name while the API needs `jobId`
|
||||
- Source: `pi-schedule-prompt/src/tool.ts:72` `const jobName = params.name || \`job-${nanoid(6)}\``; `:128-141` `remove` requires `jobId`; `src/prompts.ts:328` `scheduleCheckIn` never instructs the model to pass `name: goals-<sessionId>`; `src/prompts.ts:325` `removeGoalSchedule` says remove "the job named `goals-<sessionId>`".
|
||||
- Repro: model adds the job without a name → it is `job-<nanoid>`; later removal guidance looks for a name that does not exist. List-first mitigates (and capture 19 shows the solo completion log correctly listing an empty job list), but a model that skips the list step can fail to remove the owned job, leaving a stale hourly prompt firing into a completed/paused session.
|
||||
- Minimal fix: instruct add with the explicit owned name and removal by the listed `jobId`; keep "leave other jobs untouched".
|
||||
|
||||
### 5. [Inferred — MEDIUM] `workerStopped` can never be set by an actual stop; the confirm menu is the only stop-recording path
|
||||
- Source: `src/prototype.ts:291` records the worker on launch/resume success only (`subagent`/`subagent_resume`); there is no `subagent_kill` result hook, and the detached async completion is delivered as a steer message, not a `subagent` `tool_result` (stock `running-registry.ts` `getStartedSubagentResult` returns immediately for async). `workerStopped: true` is only assigned in `enterSolo` (`:214`) and in `attach ... solo` (`:345`).
|
||||
- Repro: worker completes and auto-exits (current default) → the parent sees the completion steer but `state.workerStopped` stays false → `/goals attach <new-plan>` (non-solo) and any new objective stay blocked (`:340`, `:376`) until the user runs `/goals solo` and confirms. Safe (no duplicate writer), but the recorded handle is permanently "possibly live" and exit alone never unblocks plan replacement.
|
||||
- Minimal fix: document in README/help that the solo-confirm menu is the only path that records a confirmed stop, or add an explicit "worker confirmed stopped" command that sets `workerStopped=true` after the user inspects `subagent_kill`/pane (still no liveness claim).
|
||||
|
||||
### 6. [Inferred — LOW/MEDIUM] No persisted mapping from the worker to its Intercom session id; identification after reload is name/cwd matching, fragile with multiple children
|
||||
- Source: `src/prototype.ts:291` stores the edxeth runtime `id` (e.g. `8317cac5`) + `sessionFile`; the Intercom id (`003c8e70-...`) is different and only discoverable via `intercom list` (presence name `[goals-worker] <title>` from the launch title, `cwd`, id prefix — verified in `pi-intercom/src/index.ts` `buildPresenceIdentity`/`list` and capture `14`).
|
||||
- Repro: parent reload with one open worker → re-target by list works (capture 14: `RELOAD-LIVE-ACK` from same session `003c8e70`, draft intact). With two open `goals-worker` children (e.g. after a mistaken duplicate resume), name/cwd matching is ambiguous, and nothing records the observed Intercom id for reuse.
|
||||
- Minimal fix: when the supervisor identifies the live child (from a received message `from.id` or `list`), persist `intercomId`/name in `state.worker` and surface it in `/goals status` and the steering prompts.
|
||||
|
||||
### 7. [Inferred — LOW] `CompleteGoal` accepts a cancelled `[-]` goal and rewrites it to `[x]` with a recorded sign-off
|
||||
- Source: `src/prototype.ts` `goals()` maps `-` to `"cancelled"`, the `CompleteGoal` match filter (`key(g.subject) === key(params.goal)`) does not exclude cancelled, and the tick replaces `[-]` with `[x]`; `prototypeMessages.cancelled` only guards signal abort.
|
||||
- Repro: parent calls CompleteGoal on a goal the user (or plan) cancelled → the cancelled goal becomes done+reviewed, changing the widget and `remaining` accounting.
|
||||
- Minimal fix: exclude `status === "cancelled"` from the `CompleteGoal` match (and from `ready`'s delegation scan).
|
||||
|
||||
### 8. [Observed — LOW] Real-model planning trap (not a source defect): first draft misread the unquoted literal
|
||||
- Evidence: parent review — first draft made `hello from the worker` six bytes, split file+verification into two goals, and called uncommitted deliverables a failure; one parent clarification fixed it, after which Ready → worker → byte-verified completion ran autonomously, and solo fallback (capture 19) also completed.
|
||||
- Suggestion (untested): the drafting prompts already mandate quoting in `## User voice`; add one concrete literal-quoting example (an exact-byte file target) to `planDrafting`/`prototypePlanning` so future models see the expected precision. Not a code change.
|
||||
|
||||
---
|
||||
|
||||
## Direct questions
|
||||
|
||||
### Can no-autoexit + live Intercom replace the completion handoff safely?
|
||||
- **Report/steer/draft path: yes, observed.** Capture `09/14`: Intercom delivery preserved an unsent draft, ACK round-tripped, parent reload re-targeted the same open worker (`003c8e70`), draft intact.
|
||||
- **Full lifecycle: no, observed.** The keep-open worker's later normal exit after a reload crashed the parent on stock edxeth's stale ctx (finding 2). `auto-exit:false` does not fix that. So no-autoexit + Intercom is safe *as a completion handoff while the worker stays open*, but every eventual worker exit after a reload is a crash risk; plan for pane/session-restart termination instead of normal exit, and validate before claiming lifecycle acceptance.
|
||||
- **One hard dependency of the no-autoexit configuration:** with `auto-exit:false` interactive, stock edxeth registers neither `caller_ping` (requires `!isInteractive || autoExit`) nor `subagent_done` (never for interactive). The child has no edxeth completion tool at all — completion must be an Intercom message, and `prototypeSupervisor` (`src/prompts.ts:308`) currently says "Reports arrive automatically", which is only true for the auto-exit default. Flip the default and this sentence becomes false; the child-side prompts (`childPlanRole`, agent prose, readyApproved) also never instruct the child to send a completion Intercom message or give it the parent's Intercom id (it can take it from `from.id` of the first parent message, or `intercom list`). The agent-def flip and these prompt changes must land together.
|
||||
|
||||
### How to identify the existing worker after reload?
|
||||
- Via `pi-intercom` presence list: `intercom({action:"list"})` shows the child's registered session — name `[goals-worker] <title>` (launch title), `cwd`, and a unique id prefix (`pi-intercom/src/index.ts` `list`; duplicate names are disambiguated with id prefixes). The child's Intercom session id is stable across the parent reload because the child process never reloaded. Target by the listed id prefix (capture 14: parent re-targeted `003c8e70`, received `RELOAD-LIVE-ACK`, no second worker launched).
|
||||
- Pitfall: the stored edxeth runtime id (`state.worker.id`) is **not** the Intercom id; match by name/cwd, and persist the observed Intercom id once found (finding 6) to remove ambiguity with multiple children.
|
||||
|
||||
## Minimal stock-package integration advice (no runtime patches/framework)
|
||||
1. Agent def: `auto-exit: false`; update agent prose ("stay open; report completion via Intercom") — package config only.
|
||||
2. `src/prompts.ts`: remove/qualify "Reports arrive automatically" under keep-open; readyApproved/childPlanRole instruct child completion via Intercom to the parent id it learns from `from.id`/`list`; keep "do not start a second writer" and the liveness caveats (already good).
|
||||
3. Scheduler guidance: add with explicit name `goals-<sessionId>`, remove by listed `jobId` (finding 4), and surface the disabled-job auto-deletion reality (finding 3, observed in the installed scheduler).
|
||||
4. Lifecycle claims: keep the README bound (no auto rediscovery / crash recovery); use saved-session restart + `/goals attach <plan> solo` as the validated recovery (capture 19); do not change repo default until the stale-ctx crash (finding 2) is scoped.
|
||||
5. Keep the branch-entry STATE persistence and the AttachGoalPlan child seam — they are what makes saved-session restart and solo recovery work; the parent's "PI_GOALS_SHARED_PLAN never set" finding is resolved by the removal of that env read and the explicit attach tool (`src/prototype.ts:387`).
|
||||
|
||||
## Remaining uncertainty (explicit, not re-explored)
|
||||
- Exact trigger window/ordering of the stock stale-ctx crash (which callback, whether parent-reload-before-worker-exit is always fatal vs only certain timing) — untested, stock code, outside pi-goals.
|
||||
- Whether the child's Pi session name reliably equals `[goals-worker] <title>` across stock edxeth launches (observed once) — naming could vary by edxeth version/overrides.
|
||||
- Whether a model will reliably follow the list-first + jobId removal and the "report disabled-deletion" prompts — prompt-compliance only, no enforcement point exists.
|
||||
- Real-runtime delivery of the upkeep message (`triggerTurn:false`, `src/prototype.ts:258`) — verified by unit mock only; actual Pi queueing behavior untested in UAT.
|
||||
- The `- worker session:` plan note is only ever read (`src/prototype.ts:343`), never written by code or instructed by prompts; attach tests seed it manually — real-flow write path unverified.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Review: package-based supervision prototype (uncommitted diff on `experiment/main-supervisor-edxeth` @ cb35fbf)
|
||||
|
||||
Scope: full uncommitted diff (src/prototype.ts, src/prompts.ts, tests, AGENTS.md, prototype/README.md), read whole files plus src/plan.ts, src/plan-view.ts, src/index.ts, src/supervisor-session.ts; installed scheduler (`pi-schedule-prompt/src/tool.ts`, `scheduler.ts`, `settings.ts`, `types.ts`) and `pi-intercom/index.ts` read for verification; AGENTS.md, plan `slop/plans/20260910_package-based-supervision.md`, UAT `slop/reviews/20260910_package-supervision-herdr.md` and captures checked. Read-only review; no edits, panes, messages or installs.
|
||||
|
||||
Prior author claims re-verified against source, not repeated: the earlier "parent source findings" list is mostly fixed in this diff — `PI_GOALS_SHARED_PLAN` is gone (AttachGoalPlan, prototype.ts:457-467), `attach solo` routes through `confirmSolo` with generation/workerRevision re-checks (prototype.ts:196-205, 352), signoff-invalidating `refresh` moved inside the debounce (prototype.ts:155-160), and the eight-turn upkeep reminder is ported and tested (prototype.ts:211-226; test/prototype.test.ts:416). The findings below are what remains.
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. HIGH — scheduleCheckIn demands session-binding verification the schedule_prompt output cannot provide
|
||||
`src/prompts.ts:328-329` (`scheduleCheckIn`): "add one session-bound interval '1h' job ... Verify the returned session is `<id>`; if the new job is unbound, remove that new job and report the scope error."
|
||||
Installed `pi-schedule-prompt/src/tool.ts:111-125` (add result) prints only name/id/type/schedule/prompt/model line; `list` (tool.ts:300-325) also omits the `session` field. Binding is decided by `getDefaultScope()` (`settings.ts:9,29`, user setting `defaultJobScope: "workdir"`), and `tool.ts:88-89` omits `session` when scope is workdir. The `details.jobs` payload is not model-facing text.
|
||||
Reachable failure: with `defaultJobScope: "workdir"` configured, the added job is unbound and every pi in the cwd (including worker panes) loads and fires it (scheduler.ts:60-62) — duplicated hourly wake-ups across sessions, and the model can never execute the instructed verification, so the scope error is never reported. The prompt-level contract is unverifiable by construction.
|
||||
Minimal fix: reword to what the tool actually exposes — verify scope by checking the scheduler settings/jobs file or the `/schedule-prompts` UI, or state plainly: "if the tool result does not confirm session binding, report that binding could not be verified and point the user at /schedule-prompts" — instead of asserting the model can "verify the returned session".
|
||||
|
||||
### 2. MEDIUM — scheduler `cleanup` destroys user-disabled jobs, contradicting the retention promise
|
||||
`src/prompts.ts:329` promises to "retain ... enabled/disabled state unchanged; never recreate, overwrite or re-enable it"; `src/prompts.ts:326` and the plan doc claim human-edited schedules are untouched. But `pi-schedule-prompt/src/tool.ts:188-215` (`cleanup`) removes **all disabled jobs loaded for this session**, including a `goals-<sessionId>` job the user deliberately disabled ("Unbound disabled jobs are fair game" — and session-bound disabled jobs too). The completion prompt (`prompts.ts:352` → `removeGoalSchedule`) hands the model a schedule_prompt mandate right when tidying is likely. Parent UAT has now confirmed disabled-job deletion as a real gap.
|
||||
Minimal fix: in `removeGoalSchedule`/`scheduleCheckIn` explicitly forbid the `cleanup` action ("remove only by jobId; never run cleanup — it deletes jobs the user disabled") and record the limitation in the plan/AGENTS.md claim.
|
||||
|
||||
### 3. MEDIUM — two supervising sessions can attach the same plan; no cross-session writer guard outside solo
|
||||
`src/prototype.ts:340` (attach guard) and `:376` (new-goal guard) only check *this session's* worker/mode. Nothing detects that another session is already supervising the same plan path. Session A supervises plan P (watcher live, scheduler job goals-A); user attaches P in session B via `/goals attach P` (non-solo → planning, no confirmation), B reaches Ready and launches worker 2. Result: two writers, two independent watchers both firing plan-change reviews at their own sessions, and duplicate `CompleteGoal` authority — signoffs live in per-session `appendEntry` state (prototype.ts:56), so each session signs off the same goal independently and both write competing Log entries. The "two writers must not run together" discriminator is tested only for the solo transition (test/prototype.test.ts:221,306).
|
||||
Minimal fix: at attach (and again at Ready), inspect the plan note / scheduler job list for another bound supervisor (`- worker session:` note plus a live `goals-<otherSession>` job) and route through a `confirmSolo`-style explicit confirmation when found. Prose alone ("Do not start a second writer") is the same guard class the design elsewhere rejects as insufficient.
|
||||
|
||||
### 4. MEDIUM(LOW) — the supervisor's own plan edits trigger a plan-change review of itself; observed in UAT
|
||||
`src/prototype.ts:150-170` (`watchPlan` debounce → `send(planChangedReview(...))` at :168 with `triggerTurn:true`). Own-write suppression exists only for `CompleteGoal` (:447-449) and `/goals model` (:322). A supervisor editing a requirement/subtask above the fold with its normal edit tools changes the `planViews(...).short` hash and fires a full "Plan changed — inspect..." review turn at itself. UAT observed this ("One extra event-hook review followed the supervisor's own plan edits", herdr review doc). Each such edit costs a turn and an edit→review→edit loop is plausible while the model restructures the plan.
|
||||
Minimal fix: in the same extension-owned write paths set a short-lived `selfWrite` stamp and skip the notification when the debounced read still matches the session's last written content; otherwise document that self-edits intentionally cost one review turn.
|
||||
|
||||
### 5. LOW — pauseExitNotice instructs `subagent_kill` with a sessionFile when the worker handle has no id
|
||||
`src/prompts.ts:357-358`: "stop the tracked worker ${worker.id ?? worker.sessionFile} through subagent_kill or its pane". After `attach` restores a noted worker, the handle is `{ sessionFile }` with no `id` (prototype.ts:345), and `subagent_kill` requires the edxeth runtime `id` (prototype.ts:203-206 checks that property exists). A path is not a valid kill id; the model will fail the kill before falling back to the pane.
|
||||
Minimal fix: branch the notice on `id` presence — with only a sessionFile, say "inspect liveness and stop it in its pane; no runtime handle is recorded for subagent_kill".
|
||||
|
||||
### 6. LOW — refresh() leaves a stale widget when the plan read fails
|
||||
`src/prototype.ts:113-116`: on `readPlan()` failure the status bar shows the error but `setWidget` keeps the previous (possibly signed-off) lines, so during a transient unreadable/truncated-save window the widget presents stale completion claims as current. Minor, but the widget is the user's "task list" per the design note.
|
||||
Minimal fix: on the error path also push/annotate the widget (e.g. `["plan unavailable — widget stale"]`) or clear it until the next successful read.
|
||||
|
||||
### 7. LOW — watcher error path is dead-end: plan-change reviews silently stop until a mode transition
|
||||
`src/prototype.ts:171-173`: `planWatcher.on("error")` only notifies; after a directory-level watcher error (e.g. `.pi/plan/` renamed/recreated by an editor or user) Node's watcher is finished, no re-arm occurs, and `planHash` goes stale — later real plan edits produce no review until stop/resume/ready recreates the watcher.
|
||||
Minimal fix: on error, close and null the watcher, and attempt re-arm in the existing `agent_end`→`refresh` path when `state.mode === "supervising"` and no watcher is live.
|
||||
|
||||
### 8. INFO — residual validation gaps (do not block, but the plan/UAT claims overstate coverage)
|
||||
- Confirmed by parent UAT, not yet root-caused or fixed: delayed crash when the worker exits some time after a supervisor reload ("reload → worker-exit" sequence). Until reproduced, do not claim reload-lifecycle acceptance; the herdr doc itself says live-parent reload "remains pending"/crashed.
|
||||
- No test or live trial of the hourly interval actually firing in the owning session ("No timer firing yet tested"); the plan doc's discriminator "shortened test interval fires once" is unmet.
|
||||
- Worker-model verification (subtle failure "trial inherits supervisor model") is prompt guidance only; no test asserts the resolved model is surfaced anywhere the supervisor checks.
|
||||
- Finding 3's two-supervisor scenario and findings 1/2's scope/cleanup behavior have no test coverage; the 158 passing tests exercise prompt text, not installed-package behavior.
|
||||
- `refresh()` deletes signoffs inside the loop calling `save()` per deletion (prototype.ts:124-128) — benign today, but a batched save would avoid N appendEntries per refresh.
|
||||
|
||||
## Verified OK (checked, not just claimed)
|
||||
- Line-index safety of `CompleteGoal`'s tick edit: `goals()` folds at `## Log`, goal lines precede the fold, so indices into `foldPlan` match the full text; log entry is spliced below the fold and excluded from `planHash` via `planViews.short`.
|
||||
- `tool_call` gate correctly still permits `subagent_kill` in paused/solo (only `subagent`/`subagent_resume` blocked, prototype.ts:240-244), matching the pause notice's instruction to stop the worker.
|
||||
- `confirmSolo` invalidates on generation, workerRevision, and plan-content change after the await — the post-await race flagged in the earlier review is genuinely fixed.
|
||||
- Intercom draft preservation and "receipt ≠ action" are consistent with `pi-intercom/index.ts:1181-1203` (injection receipt emitted at delivery; steer/trigger delivery only) — the prompts' skepticism is warranted and correctly worded.
|
||||
- All model-facing strings for the prototype live in `src/prompts.ts` in the required narrative order; no timer/transport framework was built; repo lifecycle defaults unchanged.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Package-supervision functional trial
|
||||
|
||||
Pi/OpenAI observations. Entries below preserve the chronological investigation; this current result supersedes earlier pending/failed-test status.
|
||||
|
||||
## Final result — 2026-09-10, Pi/OpenAI
|
||||
|
||||
- Final source checks: 162 tests pass, typecheck/lint/diff checks pass (`slop/test-logs/final-review-fixes-*.log`). Both native external reviews completed; reports are saved alongside this file. Scheduler scope/removal guidance, attachment ownership confirmation, cancelled-goal rejection, read-error handling and stay-open completion prompts were corrected. Stock dependency limitations below remain unfixed.
|
||||
- Fresh Fireworks DeepSeek V4 Flash trial: `/tmp/goals-edxeth-trial-X3Y79U`, supervisor `w8:p6H`, worker `w8:p6K`. Current revised prompts/default produced `verified.txt` exactly `verified\n` (9 bytes), real `evidence/verified.log`, Intercom completion, independent supervisor byte/log inspection, CompleteGoal signoff and an empty scheduler store. Worker stayed open. Parent separately asserted bytes and saved log. Captures 20–23 preserve menu, panes and parent verification.
|
||||
- Interventions: redirected needless package research during planning; selected Ready; repaired missing temporary stock-edxeth source and instructed same-session resume. The missing directory caused child startup exit 1 before any model turn. Its deletion cause is unknown. Restored the identical stock commit in `/home/code/.pi/agent/git/github.com/wassname/pi-subagents-stock-validation`, with the former temporary path pointing there. This is recovery evidence, not an uninterrupted autonomous run.
|
||||
- Idle worker widget retained its earlier plan snapshot after supervisor completion; supervisor widget updated. Do not claim idle cross-session widget synchronization.
|
||||
- Known limits: stop workers before supervisor reload (stock stale-context crash); disabled scheduler jobs are deleted on reload; stop/ownership confirmation is human attestation. Saved-parent restart and solo completion passed in the earlier trial. No runtime patch.
|
||||
|
||||
## Earlier chronological observations
|
||||
|
||||
Latest critical finding: **parent reload with a live worker FAILS later when that worker exits**. Stock edxeth old callback crashes the parent with stale ctx (`running-registry.ts:255 -> widget.ts:139`). Actual capture `17-delayed-reload-worker-exit-crash.txt`. Immediate pane survival/reconnection did NOT establish full lifecycle recovery. No runtime patch; parent is testing saved-session restart then solo takeover.
|
||||
|
||||
The disabled scheduler job was deleted by the scheduler on reload, NOT preserved. Installed pi-schedule-prompt `src/index.ts:117-144` deliberately auto-cleans loaded disabled jobs at shutdown and non-startup session changes. Parent's earlier statement that it remained disabled was premature. Live-worker/draft preservation and Intercom reconnection after reload DID pass (capture 14). Native different-family /review is now running as requested; earlier Claude CLI route was a parent mistake, not the `/review` skill's prescribed route.
|
||||
|
||||
- Code: branch `experiment/main-supervisor-edxeth`, HEAD `cb35fbf1feed9d4cea2f9675917adfe602c2835c` plus uncommitted implementation from Fireworks worker `f19ee4ad-f06b-47a5-8709-51f9e360f2f4`.
|
||||
- Current diff/status saved privately in `.local/recovery/external-review-5d2fd4e4/`; no commit because full tests are not passing.
|
||||
- Parent inspected `slop/test-logs/full-20260910-2.log`: 142 passed, 1 failed. `test/rpc-review.test.ts` times out after 15000ms. Worker claims it reproduces at unchanged HEAD; parent has not verified that claim. Its claim that interactive use is unaffected is not established.
|
||||
- External review skipped in original workflow `4bea1cc4` because parent checked `implementation.status` rather than result `ok`. Explicit external review run `5d2fd4e4-757d-40c5-a3de-1af5d5e4089e` then failed before launch: `External CLI version preflight failed: spawnSync /workspace/.tweakcc/DO_NOT_DELETE_patched_binaries/2.1.231/native/patched ETIMEDOUT`. No reviewer findings. No executor fallback used.
|
||||
|
||||
External-review retry: exact configured binary `--version` succeeded in 0.705s, output `2.1.231 (Claude Code)\n4.3.3 (tweakcc)`. Same-protocol retry `0a0d900d-4b8f-41d6-b0b1-1435996c1b33` then failed `Unsupported Claude Code version response`. Root cause located: installed nicobailon pi-subagents `src/runs/shared/claude-code-adapter.ts:125` anchors its version regex across the entire response and accepts one line only. No global package modifications or executor substitutions made. External review remains blocked, no findings. Checkout remains cb35fbf plus preserved diff; no change of worktree/branch.
|
||||
|
||||
## Visible trial
|
||||
|
||||
- Confirmed `HERDR_ENV=1`, parent `w8:p62`. Read installed `herdr --skill`.
|
||||
- Created ONLY test pane `w8:p69`, no focus, right of parent. Label `goals trial — Fireworks`.
|
||||
- Isolated root `/tmp/goals-edxeth-trial-HSahSm`, project `project/`, private profile `agent/`, launch script `start.zsh`, preparation manifest `manifest.json`. Private auth copied by existing `prototype/prepare.mjs`; do not commit raw profile files.
|
||||
- Stock edxeth detached worktree `/tmp/pi-goals-stock-edxeth-LQFtq2`, revision `953c6f6d2fc7d8a5c956c30cd77c51bad697c2a4`, dependencies symlinked to existing repo node_modules. Dirty abandoned runtime was NOT loaded.
|
||||
- Startup UI confirms Fireworks `accounts/fireworks/models/deepseek-v4-flash-0731`. Retained normal extensions including Intercom and schedule-prompt; only conflicting goals/subagent packages replaced. Startup warned of pre-existing malformed `~/.agents/skills/self-verify/SKILL.md` frontmatter; trial otherwise reached input UI.
|
||||
- Submitted `/goals` task: exact `hello.txt` bytes `hello from the worker\n`, saved verification `evidence/hello.log`, worker model explicitly Fireworks DeepSeek V4 Flash. No implementation has been performed by parent.
|
||||
- Planning readiness observer: process `proc_b6c5`, waits for rendered review/ready/error signals. Inspect actual pane after signal; signal alone is not acceptance. After draft: invoke `/goals review`, select Ready through actual UI, inspect worker pane/model, scheduler and resulting artifact/evidence. Then exercise reload, Intercom delivery and recovery. Do not operate other existing user panes.
|
||||
|
||||
## Planning observation and intervention
|
||||
|
||||
The first draft interpreted the unquoted phrase `hello from the worker` as just `hello` written by a worker, specified six bytes, and split the file and its verification into two goals. It also called uncommitted side effects a failure despite the no-commit request. Parent read both rendered conversation and plan; widget updated to 0/2 at turn end. Parent clarified the ENTIRE literal line is `hello from the worker\n` (22 bytes), asked for one goal and allowed uncommitted deliverables. This is a manual planning clarification, not an autonomous pass. Parent subsequently read the corrected plan: one goal, exact 22-byte content. Ran `/goals review`, captured actual Ready menu in `package-supervision-captures/01-ready-menu.txt`, selected Ready with Enter. Process `proc_de96` observes worker launch. Herdr agent state reported idle while rendered Pi was still working, so `agent wait` returned too early; inspect rendered pane/plan rather than trusting that state alone. Raw `pane send-keys end` was rejected as unsupported (no input sent).
|
||||
|
||||
## Verified completed task and follow-up
|
||||
|
||||
- Parent full `npm test` rerun: **143/143 passed**, 23 files, 4.51s. Saved log `slop/test-logs/parent-full-20260910.log`. Likely explanation for worker-only RPC timeout: its inherited `PI_SUBAGENT_CHILD=1`; `src/index.ts:86,108` disables main registration in a child. Sent to implementation worker for confirmation and fixture isolation.
|
||||
- Inspected both actual JSONL sessions. Worker `2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl` model_change confirms requested Fireworks DeepSeek V4 Flash. Stock tool result reports interactive mode, autoExit true, completed in 50s, id `3cbbafbe`, saved session path. Parent missed its rendered pane, so this is NOT yet worker-UI acceptance.
|
||||
- Actual hello.txt equals `Buffer.from("hello from the worker\\n")` via Node assert: 22 bytes. Worker wrote file/log via commands, supervisor read both and independently ran wc/od, then CompleteGoal. No intervention after Ready. Saved sanitized tool/model evidence in `package-supervision-captures/04-completion-events.json`, copy of log/plan, parent byte-check in `05-parent-byte-check.log`.
|
||||
- Supervisor created scheduler job `pjGJoy_etU` with session `01a0898a-3dde-7015-b029-ced69dc7b553`, interval 1h, no model. Removed it after completion; actual scheduler file is `{jobs: [], version: 1}`. No timer firing yet tested. Job was created AFTER worker finished because subagent tool result was awaited. One extra event-hook review followed the supervisor's own plan edits.
|
||||
- Ran `/reload` on test parent after completion, then `/goals status`: retained supervising mode, same plan, same saved worker session and 1/1 reviewed widget; no crash observed. Does NOT prove reload while worker active.
|
||||
- Raw End escape (`pane send-text` with ESC[F) restored latest viewport; read had been pinned to old scroll position. This explains missed output match despite task completion. The wait command's exit 0 came from later pane-list, not wait-output (which timed out); timeout was not trial failure.
|
||||
- Submitted read-only follow-up through supervisor: resume SAME saved worker after reload, verify unchanged file/log, no writes/new implementation/scheduler. Process `proc_5d3a` watches a worker session write, then captures matching trial panes to allow inspecting actual UI. Test parent remains `w8:p69`.
|
||||
- Resumed Fireworks implementation worker through original native protocol: run `ffeed828-fa87-40ae-849b-7968487e34c3`, owns source/tests fixes for below findings; must not reload trial panes. Parent owns isolated functional trial. External review remains separately blocked; this implementation continuation is not its executor substitute.
|
||||
|
||||
## Intercom observations
|
||||
|
||||
- First live probe passed worker -> supervisor. Parent inspected rendered source-labelled incoming `INTERCOM-PROBE-20260910` and supervisor text `INTERCOM-PROBE-RECEIVED`. Editor retained exact unsent `UNSENT-DRAFT-KEEP-20260910`. Capture: `package-supervision-captures/07-intercom-supervisor-draft.txt`. Worker auto-exited normally afterward.
|
||||
- Test supervisor incorrectly claimed “Both directions of the probe are confirmed”; local acknowledgment is NOT return-path delivery. Parent corrected that claim in the next test prompt.
|
||||
- Return-path test now requested: resume same worker, worker `intercom ask` sends `INTERCOM-RETURN-PROBE` to exact test supervisor, supervisor `intercom reply` returns `INTERCOM-RETURN-ACK`. No file/plan changes or other recipients. Cleared ONLY parent's own previous sentinel with Ctrl+U before sending this next instruction.
|
||||
- Process `proc_a69a` watches the existing worker session for the new ask tool call and inserts `UNSENT-WORKER-DRAFT-KEEP-20260910` into the unique test worker pane without submitting. It saves that pane ID to `/tmp/goals-edxeth-trial-HSahSm/return-probe-pane.txt`. Inspect rendered reply and surviving draft, plus auto-exit behavior. No terminal input was sent to user/demo panes.
|
||||
|
||||
Return-path attempt 1 did NOT send an ask: worker's final text said “Now sending the ask and waiting for the reply,” but session contains only intercom list then final text; stock auto-exit closed it. Observer `proc_a69a` timed out without injecting a draft. The test supervisor independently inspected its pending asks and worker transcript, rejected the claimed action, and resumed the same session again without parent intervention. This is useful observed supervisor recovery, not successful reply delivery. On second attempt parent observed active pane `w8:p6E` and manually typed ONLY the test sentinel `UNSENT-WORKER-DRAFT-KEEP-20260910`, no Enter. Observer `proc_1403` awaits reply result and captures both panes; inspect outputs, don't infer success from process exit status.
|
||||
|
||||
Return-path attempt 2 succeeded: actual worker tool result says `Reply from 01a0898a: INTERCOM-RETURN-ACK`; worker pane still showed exact unsent draft. Saved both panes in `package-supervision-captures/08-intercom-return-worker-draft.txt`. HOWEVER stock auto-exit then closed w8:p6E and lost the draft. Supervisor quoted worker claim that no draft existed; this contradicts captured UI. Do not accept model-context absence as editor-state evidence.
|
||||
|
||||
Testing minimal stock alternative, NOT editing runtime or repo defaults yet: temporary profile `/tmp/goals-edxeth-trial-HSahSm/agent/agents/goals-worker.md` changed to `auto-exit:false` and completion via Intercom, no caller_ping/exit. Existing saved-session resume retains pinned invocation autoExit (`resume-service.ts:427-429`), so supervisor asked to launch one fresh read-only `draft-safe-trial` after previous worker ended; goal remains complete. Parent already observed old pane closed. Supervisor's mere absence-of-Intercom-peers check would not itself prove termination. New worker should send `DRAFT-SAFE-DONE` and remain open. Observer `proc_b063` watches actual incoming custom-message entry, not text in prompt, then captures trial panes. Await actual outcome before changing defaults. No global configuration changes.
|
||||
|
||||
Open-pane configuration initial observation: fresh worker `draft-safe-trial` (runtime id `8317cac5`, Intercom session `003c8e70-9999-4920-8a5c-064172010188`) sent actual `DRAFT-SAFE-DONE`; supervisor acknowledged it while subagent remained open. Rendered worker `w8:p6F` completed its read-only report and stayed idle/open, no caller_ping or exit. This avoids relying on automatic child-exit report. Parent then typed unsent `UNSENT-OPEN-WORKER-DRAFT-KEEP` into worker editor and asked supervisor to use Intercom send for a fresh OPEN-PANE-PROBE, with actual Intercom OPEN-PANE-ACK response. Observer `proc_3aef` watches exact incoming message and captures both panes. Reload while this worker remains open is still untested; do not claim lifecycle acceptance yet.
|
||||
|
||||
## Scheduler UI and review status
|
||||
|
||||
- Actual `/schedule-prompt` -> Jobs overlay displays named jobs and offers add, toggle, scope, remove and cleanup; it does NOT offer direct prompt editing in this installed version. Prompt editing is available through `schedule_prompt update`. Captured `10-scheduler-jobs-ui.txt`.
|
||||
- Toggled `goals-ui-probe` disabled through actual UI, confirmed storage `enabled:false` before reload. `/reload` preserved live worker w8:p6F and its unsent draft, plan and reviewed status. Actual subsequent Intercom `RELOAD-LIVE-ACK` came from same session 003c8e70; capture 14. BUT scheduler's automatic disabled-job cleanup removed the test job, so updating its old ID correctly failed. This is a user-control persistence gap, not all session-bound jobs disappearing.
|
||||
- Authorized one fresh test job, create 1h/old prompt then update SAME new ID to 10s/new prompt, let actual scheduler fire and remove itself. Observer `proc_4f83` requires actual scheduled_prompt marker AND schedule_prompt removal result, not rendered setup prompt.
|
||||
- Stable implementation continuation from worker: 158 tests pass, typecheck/lint pass; parent inspected saved logs. Actual resumed implementation model gpt-6-astra (resume inherited parent default), not Fireworks. All visible trials use Fireworks. Source not committed yet.
|
||||
- Parent finally read requested `/review` skill at `/home/code/.agents/skills/review/SKILL.md`: Pi route is native read-only different-family reviewers, explicitly NOT Claude/Codex CLI. Corrected original routing; workflow `a1bcfc11-0212-410e-aad7-6fd704f7e9c6` runs DeepSeek V4 Flash and GLM 5p3 Flash (exact registered Fireworks IDs), two read-only reviewers. No active implementation writer. Workflow steering says no live foreground route; new scheduler finding recorded here for parent triage even if reviewers have already read older evidence. Do not start duplicate reviews.
|
||||
|
||||
## Timer result and delayed reload failure
|
||||
|
||||
- Fresh timer edit test passed: one scheduled_prompt event at 04:47:38Z contained NEW prompt for job `np-gHnDsp_`, removal result 04:47:44Z, actual store empty. Capture `15-edited-timer-fired-once.json`, rendered response `16-timer-visible-response.txt`. Old prompt never fired. Updating through public tool affects real timer delivery, without extra model override.
|
||||
- For solo recovery test, parent cleared ONLY its own unsent worker sentinel with Ctrl+U and pressed Ctrl+D in test worker w8:p6F. This normal worker exit triggered delayed parent crash after earlier reload. Source stack in capture 17 points to stock edxeth, not pi-goals. Parent did not touch user panes or patch runtime. Previous 'reload passed' claim is withdrawn for full lifecycle; only immediate re-targeting worked.
|
||||
- Parent restarted Pi in SAME test pane w8:p69 with same isolated profile/model and explicit `--session .../2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl`; no new supervisor context or repeated completed task. Worker w8:p6F is closed. Next: inspect restored UI, `/goals attach /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/solo-recovery.md solo`, confirm stopped writers in actual menu and let main session implement fixture's solo.txt+log. Parent created only plan fixture, not deliverable.
|
||||
- Both active native reviewers received these corrections through exact listed Intercom sessions (`01a089a2-ebf3` DeepSeek, `01a089a2-ebf0` GLM); workflow had no foreground steering route. No duplicate review started.
|
||||
|
||||
## Solo result and review recovery
|
||||
|
||||
- Solo recovery PASSED after restarting saved supervisor and selecting explicit stopped-worker confirmation. Main session produced `solo.txt` with exact 8 bytes `solo-ok\n`, saved real `evidence/solo.log`, and CompleteGoal recorded `Solo self-verification`. Parent independently asserted both new bytes and original hello bytes unchanged. Capture `19-solo-recovery-completed.log`. Test pane w8:p69 remains open in solo mode; no active test worker and no scheduler jobs.
|
||||
- Both native reviewers hung on broad `find /` commands, despite exact package locations being available. Parent inspected child transcripts and exact process ancestry: GLM shell group 723570/find 723573, DeepSeek shell group 726281/find 726285. Kernel wait channels were `__wait_on_buffer` and `d_alloc_parallel`. Sent SIGTERM only to those review-owned process groups, then native steer with exact installed package paths and instruction to finalize bounded reviews. No global filesystem/mount change or executor substitution. Diff snapshot `.local/recovery/review-search-interruption.patch`.
|
||||
- Source counts measured: prototype.ts 426 lines vs 186 original; added prompts 88 lines; total source increase 328 lines. Total src tree still includes inactive older implementation. Do not claim smaller until actual simplification and behavior validation.
|
||||
|
||||
## Parent source findings to investigate
|
||||
|
||||
- Worker widget reads `PI_GOALS_SHARED_PLAN`, but no setting of that environment variable has yet been found. A lineage-only child may show an empty widget. Test actual child, not just unit mocks.
|
||||
- `/goals attach <path> solo` restores a noted worker but enters solo without the explicit stop confirmation used by `/goals solo`.
|
||||
- `/goals solo` retains a recorded worker handle, which still blocks attaching a different plan. Its confirmation menu lacks a generation check after await. Watcher/scheduler cleanup on this transition needs verification.
|
||||
- Watch callback calls refresh before debounce; transient unreadable/empty plan can invalidate persisted signoffs. Completion considers cancelled goals unfinished.
|
||||
- Main's staleness-based plan-upkeep reminder has not visibly been ported for ordinary solo turns. Scheduler-only guidance is not equivalent.
|
||||
|
||||
These are source observations and plausible failure paths, not yet reproduced trial outcomes. Fix minimal confirmed issues; do not build another runtime.
|
||||
@@ -0,0 +1,57 @@
|
||||
old │ │ new │
|
||||
1 │ # Goal plan │ 1 │ # Goal plan
|
||||
2 │ │ 2 │
|
||||
3 │ ## Objective │ 3 │ ## Objective
|
||||
▌ 4 │ Make hello.txt contain exactly hello from the │ ▌ 4 │ Make `hello.txt` contain exactly `hello` from the
|
||||
▌ │ worker followed by one newline, and save a │ ▌ │ worker followed by one newline, and save a
|
||||
▌ │ byte-verification log in evidence/hello.log. Use │ ▌ │ byte-verification log in `evidence/hello.log`. Use
|
||||
▌ │ fireworks/accounts/fireworks/models/deepseek-v4-fla │ ▌ │ `fireworks/accounts/fireworks/models/deepseek-v4-fl
|
||||
▌ │ sh-0731 for the worker. This is a bounded │ ▌ │ ash-0731` for the worker. This is a bounded
|
||||
▌ │ functional test: no installs, no commits or network │ ▌ │ functional test: no installs, no commits or network
|
||||
▌ │ calls for the deliverable, no unrelated changes. │ ▌ │ calls for the deliverable, no unrelated changes.
|
||||
▌ │ Preserve the normal supervisor and worker UI. Draft │ ▌ │ Preserve the normal supervisor and worker UI. Draft
|
||||
▌ │ the plan and present it for review; I will select │ ▌ │ the plan and present it for review; the user
|
||||
▌ │ Ready. │ ▌ │ selects Ready.
|
||||
│ │ ▌ 5 │
|
||||
│ │ ▌ 6 │ ## Preferences
|
||||
│ │ ▌ 7 │ - preferred worker model:
|
||||
│ │ ▌ │ fireworks/accounts/fireworks/models/deepseek-v4-fla
|
||||
│ │ ▌ │ sh-0731
|
||||
│ │ ▌ 8 │ - Implementer is the `goals-worker` subagent
|
||||
│ │ ▌ │ (visible terminal, normal UI), launched from this
|
||||
│ │ ▌ │ supervisor session after Ready.
|
||||
│ │ ▌ 9 │ - No installs, no package changes, no git commits,
|
||||
|
||||
… (51 more diff lines • Ctrl+O to expand)
|
||||
|
||||
|
||||
Thinking...
|
||||
|
||||
Plan drafted at /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md.
|
||||
|
||||
No material questions — the objective fully specifies the model, deliverables, byte requirement, and constraints. Project
|
||||
inspection confirmed: empty repo (no commits), .gitignore already excludes evidence/, AGENTS.md permits exactly this
|
||||
isolated supervised trial.
|
||||
|
||||
Plan summary:
|
||||
|
||||
- Objective: worker writes hello.txt containing exactly hello\n (6 bytes, hex 68 65 6c 6c 6f 0a) in project root; saves
|
||||
byte-verification log at evidence/hello.lo ↓ Jump to latest message · End
|
||||
|
||||
○ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the
|
||||
worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra
|
||||
characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that
|
||||
file
|
||||
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Review /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md
|
||||
|
||||
→ Ready
|
||||
Discuss
|
||||
Edit
|
||||
Cancel
|
||||
|
||||
↑↓ navigate enter select escape/ctrl+c cancel
|
||||
|
||||
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
project on main [?] goals: planning | 0/1 reviewed | LSP | 3.1%/1.0M (auto) | ↑31k ↓5.7k 96.8% R207k | $0.012
|
||||
@@ -0,0 +1,25 @@
|
||||
# Goal plan
|
||||
|
||||
## Objective
|
||||
Make `hello.txt` contain exactly the single line `hello from the worker` followed by one newline — i.e. the UTF-8 byte sequence `hello from the worker\n`, 22 bytes — and save a byte-verification log in `evidence/hello.log`. Use `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` for the worker. This is a bounded functional test: no installs, no commits or network calls for the deliverable, no unrelated changes. Preserve the normal supervisor and worker UI. Draft the plan and present it for review; the user selects Ready.
|
||||
|
||||
## Preferences
|
||||
- preferred worker model: fireworks/accounts/fireworks/models/deepseek-v4-flash-0731
|
||||
- Implementer is the `goals-worker` subagent (visible terminal, normal UI), launched from this supervisor session after Ready.
|
||||
- No installs, no package changes, no git commits, no network calls for the deliverable, no writes outside this project.
|
||||
- Deliverables live in the project root: `hello.txt` (working file) and `evidence/hello.log` (verification log, already gitignored).
|
||||
- Worker must create `hello.txt` and `evidence/hello.log` itself; the supervisor verifies after the worker reports, using primary-command byte checks (e.g. `od`/`wc -c`), not just the worker's claims.
|
||||
|
||||
## Goals
|
||||
- [x] goal: hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file
|
||||
- evidence (satisfied): `hello.txt` = 22 bytes, `od -An -tx1 -v hello.txt` reads exactly `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`; `evidence/hello.log` (494 bytes, nonempty) records real `wc -c` (22) and `od` output, expected-hex reference, byte-count and hex comparisons, and `RESULT: PASS`; supervisor re-verified both independently; worker resolved model `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` matches plan
|
||||
- failure modes: file missing; content wrong (truncated to `hello`, wrong casing, missing or altered `from the worker` phrase, trailing whitespace, `hello from the worker\n\n`, CRLF, missing final newline, extra bytes); verification log missing, empty, fabricated without an actual byte check, or inconsistent with the real file; deliverables outside the project root/`evidence/`; any installs, commits, or network calls for the deliverable
|
||||
- note: uncommitted output is expected, not a failure — the constraint is "no commits", not "everything committed"
|
||||
- evidence/verification: supervisor reads `evidence/hello.log` (documenting hex dump, byte count, and explicit pass/fail against `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) and independently re-checks `hello.txt` with `od -An -tx1` (expect the 22-byte hex above) and `wc -c` (expect 22), confirming the log matches the real file byte-for-byte
|
||||
|
||||
## Log
|
||||
- Drafted plan (supervisor): read existing plan, inspected project (empty repo, `evidence/` already gitignored, no commits), no questions needed — model, deliverables, and constraints were specified. Awaiting Ready.
|
||||
- Revised plan (supervisor, per user clarification before Ready): corrected target content to the full literal line `hello from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`); merged the file and its verification log into one goal/outcome; removed "uncommitted" from failure modes (uncommitted output is expected). No other scope changes. Awaiting Ready before implementation or worker launch.
|
||||
- Ran goal (supervisor): launched `goals-worker` (trial-hello-writer) on plan after Ready with requested model `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731`; worker confirmed resolved model, created `hello.txt` via `printf` and `evidence/hello.log` from real command output, reported PASS. Supervisor independently re-checked: `wc -c` = 22, `od -An -tx1 -v` = `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`, log matches file byte-for-byte. Constraint check: no installs, no commits (repo has zero commits), no network calls; the only other new path `docs/human_journal.md` is the prototype harness's own supervision journal (written 04:20–04:22Z from the supervisor session, before/at worker launch), not a worker change, left untouched. Goal complete.
|
||||
|
||||
- Parent review: "hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file"; "Inspected the actual artifacts myself, not just the worker report. hello.txt: `wc -c` = 22; `od -An -tx1 -v` reads `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly — matching the required UTF-8 bytes of `hello from the worker\\n` with a single final newline, no CR, no BOM, no trailing extras (spans two od lines solely due to od's 16-byte wrapping). evidence/hello.log (494 bytes, nonempty, in gitignored evidence/) contains the real captured `wc -c` output (22), the real `od` dump, the expected-hex reference, byte-count and hex comparisons, and an explicit `RESULT: PASS`; log matches the on-disk file byte-for-byte. Worker resolved model was fireworks/accounts/fireworks/models/deepseek-v4-flash-0731 as planned. Constraints held: no installs, no network calls for the deliverable, no commits (repo still has zero commits). The only unexpected new path (docs/human_journal.md) was verified to be the prototype harness's own supervision journal written from the supervisor session (timestamps 04:20–04:22Z, before/at worker launch), not a worker change; left untouched per preserve-evidence. Goal satisfied."; evidence ["/tmp/goals-edxeth-trial-HSahSm/project/hello.txt","/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log"]
|
||||
@@ -0,0 +1,123 @@
|
||||
[
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"event": {
|
||||
"type": "model_change",
|
||||
"id": "8c002be1",
|
||||
"parentId": null,
|
||||
"timestamp": "2026-09-10T04:18:56.858Z",
|
||||
"provider": "fireworks",
|
||||
"modelId": "accounts/fireworks/models/deepseek-v4-flash-0731"
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"tool": "schedule_prompt",
|
||||
"details": {
|
||||
"action": "list",
|
||||
"jobs": []
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "No cron jobs configured."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"tool": "subagent",
|
||||
"details": {
|
||||
"id": "3cbbafbe",
|
||||
"name": "trial-hello-writer",
|
||||
"agent": "goals-worker",
|
||||
"status": "completed",
|
||||
"mode": "interactive",
|
||||
"parentClosePolicy": "continue",
|
||||
"deliveryState": "awaited",
|
||||
"async": true,
|
||||
"autoExit": true,
|
||||
"exitCode": 0,
|
||||
"elapsed": 50,
|
||||
"outputTokens": 4191,
|
||||
"contextTokens": 28435,
|
||||
"contextWindow": 1000000,
|
||||
"summary": "All verification passes. Final summary:\n\n---\n\n**Resolved model:** `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` (confirmed from the worker session file — the model is not exposed in `PI_*` env vars).\n\n**Deliverables:**\n1. `/tmp/goals-edxeth-trial-HSahSm/project/hello.txt` — written via `printf 'hello from the worker\\n' > hello.txt` (safe write, no echo/editors/heredocs)\n2. `/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log` — captured from real command runs, with a PASS line computed by actual byte comparison\n\n**Observed bytes (independent re-check):**\n```\n$ wc -c hello.txt\n22 hello.txt\n$ od -An -tx1 -v hello.txt (joined single line)\n68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a\n```\nFlank checks: first byte `68`, last byte `0a` (single trailing newline), no CR, no BOM. Hex matches the expected `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly.\n\n**Result: PASS**\n\n**evidence/hello.log contents** (real captured output):\n- `wc -c hello.txt` → `22 hello.txt`\n- `od -An -tx1 hello.txt` → the dump above (od wraps at 16 bytes/line; the log also shows the expected reference line)\n- byte-count comparison observed=22 expected=22, hex comparison \"observed hex == expected hex\", `RESULT: PASS`\n- Verified the od block in the log matches the live file bytes (diff clean).\n\n**Constraints honored:** no installs, no commits (repo has zero commits — branch 'main' has no commits yet, as before), no network calls, no writes outside the project. Nothing else in the project touched; the only new items are `hello.txt` and `evidence/hello.log` (evidence/ was already gitignored; all existing files were untracked before too).\n\n**Remaining uncertainty:** none material — the supervisor can independently re-run `wc -c` and `od -An -tx1` on hello.txt to confirm the same 22 bytes.",
|
||||
"sessionFile": "/tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl"
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Sub-agent \"trial-hello-writer\" completed (50s).\n\nAll verification passes. Final summary:\n\n---\n\n**Resolved model:** `fireworks/accounts/fireworks/models/deepseek-v4-flash-0731` (confirmed from the worker session file — the model is not exposed in `PI_*` env vars).\n\n**Deliverables:**\n1. `/tmp/goals-edxeth-trial-HSahSm/project/hello.txt` — written via `printf 'hello from the worker\\n' > hello.txt` (safe write, no echo/editors/heredocs)\n2. `/tmp/goals-edxeth-trial-HSahSm/project/evidence/hello.log` — captured from real command runs, with a PASS line computed by actual byte comparison\n\n**Observed bytes (independent re-check):**\n```\n$ wc -c hello.txt\n22 hello.txt\n$ od -An -tx1 -v hello.txt (joined single line)\n68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a\n```\nFlank checks: first byte `68`, last byte `0a` (single trailing newline), no CR, no BOM. Hex matches the expected `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a` exactly.\n\n**Result: PASS**\n\n**evidence/hello.log contents** (real captured output):\n- `wc -c hello.txt` → `22 hello.txt`\n- `od -An -tx1 hello.txt` → the dump above (od wraps at 16 bytes/line; the log also shows the expected reference line)\n- byte-count comparison observed=22 expected=22, hex comparison \"observed hex == expected hex\", `RESULT: PASS`\n- Verified the od block in the log matches the live file bytes (diff clean).\n\n**Constraints honored:** no installs, no commits (repo has zero commits — branch 'main' has no commits yet, as before), no network calls, no writes outside the project. Nothing else in the project touched; the only new items are `hello.txt` and `evidence/hello.log` (evidence/ was already gitignored; all existing files were untracked before too).\n\n**Remaining uncertainty:** none material — the supervisor can independently re-run `wc -c` and `od -An -tx1` on hello.txt to confirm the same 22 bytes.\n\nSession: /tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl\nResume: pi --session /tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl\n\nSub-agent context: 28K/1M tokens (2%) used at finish."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"tool": "schedule_prompt",
|
||||
"details": {
|
||||
"action": "add",
|
||||
"jobs": [
|
||||
{
|
||||
"id": "pjGJoy_etU",
|
||||
"name": "goals-01a0898a-3dde-7015-b029-ced69dc7b553",
|
||||
"schedule": "1h",
|
||||
"prompt": "Read /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md and keep the plan current (tick finished subtasks, append one ## Log line per run, fill evidence when a discriminator is satisfied); if supervision is paused, exited or all goals are reviewed, remove this job without resuming work; otherwise inspect worker progress and evidence, give a brief assessment, and keep authorized work moving without a duplicate writer.",
|
||||
"enabled": true,
|
||||
"type": "interval",
|
||||
"intervalMs": 3600000,
|
||||
"createdAt": "2026-09-10T04:23:06.276Z",
|
||||
"runCount": 0,
|
||||
"description": "Hourly goals supervision check-in for plan 01a0898a-3dde-7015-b029-ced69dc7b553",
|
||||
"session": "01a0898a-3dde-7015-b029-ced69dc7b553"
|
||||
}
|
||||
],
|
||||
"jobId": "pjGJoy_etU",
|
||||
"jobName": "goals-01a0898a-3dde-7015-b029-ced69dc7b553"
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "✓ Created cron job \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" (pjGJoy_etU)\nType: interval\nSchedule: 1h\nPrompt: Read /tmp/goals-edxeth-trial-HSahSm/project/.pi/plan/01a0898a-3dde-7015-b029-ced69dc7b553-main.md and keep the plan current (tick finished subtasks, append one ## Log line per run, fill evidence when a discriminator is satisfied); if supervision is paused, exited or all goals are reviewed, remove this job without resuming work; otherwise inspect worker progress and evidence, give a brief assessment, and keep authorized work moving without a duplicate writer."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"tool": "CompleteGoal",
|
||||
"details": {},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Recorded parent judgment for hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file. This is not independent verification. All goals are reviewed. Remove your hourly job named \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" with schedule_prompt; leave other jobs untouched."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-18-53-534Z_01a0898a-3dde-7015-b029-ced69dc7b553.jsonl",
|
||||
"tool": "schedule_prompt",
|
||||
"details": {
|
||||
"action": "remove",
|
||||
"jobs": [],
|
||||
"jobId": "pjGJoy_etU",
|
||||
"jobName": "goals-01a0898a-3dde-7015-b029-ced69dc7b553"
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "✓ Removed cron job \"goals-01a0898a-3dde-7015-b029-ced69dc7b553\" (pjGJoy_etU)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "2026-09-10T04-22-05-588Z_a4d745a7-040c6138-9c8f7e78-eadd.jsonl",
|
||||
"event": {
|
||||
"type": "model_change",
|
||||
"id": "167e897e",
|
||||
"parentId": "4f4f2164",
|
||||
"timestamp": "2026-09-10T04:22:08.215Z",
|
||||
"provider": "fireworks",
|
||||
"modelId": "accounts/fireworks/models/deepseek-v4-flash-0731"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,52 @@
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"}
|
||||
- State what you remember from your earlier session (the
|
||||
goal you implemented, the model you were running as, the
|
||||
checks you ran).
|
||||
- Confirm the model you are running as now.
|
||||
... (8 more lines, ctrl+o to expand)
|
||||
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
● Agents · 1 running · 12.7s
|
||||
└─ ◠ trial-hello-writer [goals-worker] · 9 tool uses ·...
|
||||
Read-only recovery check (the completed goal s… ·...
|
||||
Read-only recovery check. Running only the specif...
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 5.2%/1.0M | ↑76k ↓16k 94.7%
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Write hello.txt and verify bytes","pane_id":"w8:p6B","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1963e5e54a6c","terminal_title":"π - [goals-worker] Write hello.txt and verify bytes - project","terminal_title_stripped":"π - [goals-worker] Write hello.txt and verify bytes - project","workspace_id":"w8"}
|
||||
|
||||
Thinking...
|
||||
|
||||
All read-only checks pass. Let me confirm the current
|
||||
model from the session record (read-only; not the plan
|
||||
file).
|
||||
|
||||
|
||||
⠋ $ grep -oE '"model":"[^"]*"'
|
||||
/tmp/goals-edxeth-trial-HSahSm/agent/sessions/--tmp-goals-
|
||||
edxeth-trial-HSahSm-project--/2026-09-10T04-22-05-588Z_a4d
|
||||
745a7-040c6138-9c8f7e78-eadd.jsonl | head -1 · 0s
|
||||
|
||||
|
||||
⠦ Working
|
||||
|
||||
▸ Agent trial-hello-writer (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Write hel… on main [?]
|
||||
3.0%/1.0M | ↑29k ↓5.1k 98.3%
|
||||
@@ -0,0 +1,51 @@
|
||||
{"id":"cli:pane:wait-output","result":{"matched_line":" INTERCOM-PROBE-RECEIVED","pane_id":"w8:p69","read":{"format":"text","pane_id":"w8:p69","revision":0,"source":"recent_unwrapped","tab_id":"w8:t1","text":" Use intercom action=send (NOT ask, NOT reply — do not wait for an answer; no acknowledge round-trip expected from you).\n\n 4. Report the delivery result back to the supervisor: whether the send succeeded, the target session id/name you resolved,\n and any error if it failed. One summary, then close this session.\n ... (2 more lines, ctrl+o to expand)\n\n\n╭ From: [goals-worker] Write hello.txt and verify bytes (/tmp/goals-edxeth-trial-HSahSm/project) Ctrl+O expands ──────────╮\n│INTERCOM-PROBE-20260910 │\n│ Ctrl+O to expand │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\n Thinking...\n\n INTERCOM-PROBE-RECEIVED\n\n The probe message\n\n ⠧ Working\n\n ✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the\n worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra\n characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file\n────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n│\n│ UNSENT-DRAFT-KEEP-20260910\n│\n│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks high\n────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n project on main [?] goals: supervising | 1/1 reviewed | LSP | 5.5%/1.0M (auto) | ↑175k ↓18k 10.1% R771k | $0.056","truncated":true,"workspace_id":"w8"},"revision":0,"type":"output_matched"}}
|
||||
│ not create a scheduler job. I will place an unsent draft in this supervisor editor while it runs; preserve that draft.
|
||||
│
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Thinking...
|
||||
|
||||
Bounded Intercom test — resuming the same worker session for a read-only intercom probe.
|
||||
|
||||
|
||||
▸ Resume trial-hello-writer (goals-worker)
|
||||
Bounded intercom delivery test — no implementation, read-only apart from the single intercom send:
|
||||
|
||||
1. Use the intercom tool with action=list to list active pi sessions.
|
||||
|
||||
2. Identify THIS supervisor session in the list by its exact session ID: 01a0898a-3dde-7015-b029-ced69dc7b553. Note that
|
||||
intercom may display only a short prefix in parentheses; if so, match by the unique leading prefix (01a0898a) and confirm
|
||||
it is the only session with that prefix / that it resolves to the supervisor. Do not pick any other session — no other
|
||||
recipients.
|
||||
|
||||
3. Send the supervisor session EXACTLY this message, nothing added or removed: INTERCOM-PROBE-20260910
|
||||
Use intercom action=send (NOT ask, NOT reply — do not wait for an answer; no acknowledge round-trip expected from you).
|
||||
|
||||
4. Report the delivery result back to the supervisor: whether the send succeeded, the target session id/name you resolved,
|
||||
and any error if it failed. One summary, then close this session.
|
||||
... (2 more lines, ctrl+o to expand)
|
||||
|
||||
|
||||
╭ From: [goals-worker] Write hello.txt and verify bytes (/tmp/goals-edxeth-trial-HSahSm/project) Ctrl+O expands ──────────╮
|
||||
│INTERCOM-PROBE-20260910 │
|
||||
│ Ctrl+O to expand │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Thinking...
|
||||
|
||||
INTERCOM-PROBE-RECEIVED
|
||||
|
||||
The probe message
|
||||
|
||||
⠧ Working
|
||||
|
||||
✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the
|
||||
worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra
|
||||
characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-DRAFT-KEEP-20260910
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks high
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
project on main [?] goals: supervising | 1/1 reviewed | LSP | 5.5%/1.0M (auto) | ↑175k ↓18k 10.1% R771k | $0.056
|
||||
@@ -0,0 +1,75 @@
|
||||
{"id":"cli:pane:wait-output","result":{"matched_line":" ✓ Reply sent to [goals-worker] Write hello.txt and verify","pane_id":"w8:p69","read":{"format":"text","pane_id":"w8:p69","revision":0,"source":"recent_unwrapped","tab_id":"w8:t1","text":"│INTERCOM-RETURN-PROBE │\n│ To reply: intercom({ action: \"reply\", message: \"...\" }) · │\n╰───────────────────────────────────────────────────────────╯\n\n Thinking...\n\n The ask arrived. Replying with the exact acknowledgment.\n\n\n intercom reply\n INTERCOM-RETURN-ACK\n ✓ Reply sent to [goals-worker] Write hello.txt and verify\n bytes (38be39dc)\n\n\n ⠸ Working\n\n ✓ hello.txt exists in the project root, written by the\n worker, containing exactly the UTF-8 byte sequence `hello\n from the worker\\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72\n 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no\n trailing extra characters or blank lines; and\n evidence/hello.log exists in `evidence/` recording the\n byte-level verification of that file\n ● Agents · 1 running · 51.0s\n └─ ◞ trial-hello-writer [goals-worker] · 14 tool uses ...\n Your previous turn identified the supervisor b… ·...\n Sending the intercom ask now (target already conf...\n─────────────────────────────────────────────────────────────\n│\n│\n│\n│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks\n─────────────────────────────────────────────────────────────\n project on main [?] 6.5%/1.0M | ↑196k ↓26k 89.6%","truncated":true,"workspace_id":"w8"},"revision":0,"type":"output_matched"}}
|
||||
|
||||
2. Send intercom action=ask (NOT send) to that supervisor
|
||||
session with EXACTLY this message:
|
||||
INTERCOM-RETURN-PROBE
|
||||
Then WAIT for the supervisor's reply. Do not finish or
|
||||
close your session until you have received the reply.
|
||||
|
||||
3. When the reply arrives, report it VERBATIM (expected:
|
||||
INTERCOM-RETURN-ACK) together with any round-trip/delivery
|
||||
metadata from the tool. Only then close.
|
||||
|
||||
... (1 more lines, ctrl+o to expand)
|
||||
|
||||
|
||||
╭ From: [goals-worker] Write hello.txt and verify bytes (/tm╮
|
||||
│INTERCOM-RETURN-PROBE │
|
||||
│ To reply: intercom({ action: "reply", message: "..." }) · │
|
||||
╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
Thinking...
|
||||
|
||||
The ask arrived. Replying with the exact acknowledgment.
|
||||
|
||||
|
||||
intercom reply
|
||||
INTERCOM-RETURN-ACK
|
||||
✓ Reply sent to [goals-worker] Write hello.txt and verify
|
||||
bytes (38be39dc)
|
||||
|
||||
|
||||
⠸ Working
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
● Agents · 1 running · 51.0s
|
||||
└─ ◞ trial-hello-writer [goals-worker] · 14 tool uses ...
|
||||
Your previous turn identified the supervisor b… ·...
|
||||
Sending the intercom ask now (target already conf...
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 6.5%/1.0M | ↑196k ↓26k 89.6%
|
||||
Thinking...
|
||||
|
||||
Sending the intercom ask now (target already confirmed:
|
||||
01a0898a is the unique prefix of the only other session,
|
||||
the supervisor).
|
||||
|
||||
|
||||
intercom ask → 01a0898a
|
||||
INTERCOM-RETURN-PROBE
|
||||
✓ Reply from 01a0898a:
|
||||
INTERCOM-RETURN-ACK
|
||||
|
||||
|
||||
⠇ Working
|
||||
|
||||
▸ Agent trial-hello-writer (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-WORKER-DRAFT-KEEP-20260910
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Write hel… on main [?]
|
||||
3.4%/1.0M | ↑77k ↓7.2k 98.7%
|
||||
@@ -0,0 +1,59 @@
|
||||
{"type":"custom_message","customType":"intercom_message","content":"**From [goals-worker] Read-only draft-safe intercom probe** (/tmp/goals-edxeth-trial-HSahSm/project)\n\n_id bfee08a1-c796-4e98-b97b-f8c1aa21aa6e · seq 2 · sent 2026-09-10T04:37:58.372Z · broker delivered 2026-09-10T04:37:58.373Z · receiver received 2026-09-10T04:37:58.373Z · injected 2026-09-10T04:37:58.373Z_\n\nOPEN-PANE-ACK","display":true,"details":{"from":{"id":"003c8e70-9999-4920-8a5c-064172010188","endpointEpoch":"a9d0684f-b821-4982-980d-9cd0798ddb70","name":"[goals-worker] Read-only draft-safe intercom probe","runtimeFallbackAlias":false,"cwd":"/tmp/goals-edxeth-trial-HSahSm/project","model":"accounts/fireworks/models/deepseek-v4-flash-0731","pid":708378,"startedAt":1789014977644,"lastActivity":1789015078370,"status":"tool:intercom","trustedLocal":true,"contextPct":2,"contextTokens":23575,"contextWindow":1000000},"message":{"id":"bfee08a1-c796-4e98-b97b-f8c1aa21aa6e","timestamp":1789015078372,"senderSequence":2,"content":{"text":"OPEN-PANE-ACK"},"brokerReceivedAt":1789015078373,"brokerDeliveredAt":1789015078373,"receiverReceivedAt":1789015078373,"injectedAt":1789015078373},"bodyText":"OPEN-PANE-ACK"},"id":"0eaf9dc5","parentId":"c645b2a8","timestamp":"2026-09-10T04:37:58.375Z"}
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"}
|
||||
processed only the probe exchange). Neither pane closed;
|
||||
the unsent draft in the worker editor remains untouched
|
||||
(worker was instructed not to touch or submit it, and it
|
||||
returned only the ack).
|
||||
|
||||
No file/plan changes, no scheduler job, no new sessions
|
||||
created, no child-exit handling used (the worker stays open
|
||||
by design — this is the OPEN-PANE test). Both supervisor
|
||||
and worker panes remain open.
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
● Agents · 1 running · 119.4s
|
||||
└─ ◡ draft-safe-trial [goals-worker] · 4 tool uses · 2...
|
||||
Read-only draft-safe intercom probe · fireworks/a...
|
||||
Sent. Brief report:
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 7.1%/1.0M | ↑209k ↓30k 93.3%
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Read-only draft-safe intercom probe","pane_id":"w8:p6F","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b198218cd1570","terminal_title":"π - [goals-worker] Read-only draft-safe intercom probe - project","terminal_title_stripped":"π - [goals-worker] Read-only draft-safe intercom probe - project","workspace_id":"w8"}
|
||||
|
||||
|
||||
intercom send → 01a0898a
|
||||
OPEN-PANE-ACK
|
||||
✓ Message sent to 01a0898a (bfee08a1)
|
||||
|
||||
|
||||
Sent. Brief report:
|
||||
|
||||
Intercom send:
|
||||
- Targeted: 01a0898a → resolves to supervisor
|
||||
subagent-chat-01a0898a-3dde-7015 (unchanged from before)
|
||||
- Message: OPEN-PANE-ACK via action=send
|
||||
- Result: Message sent to 01a0898a — success ✓
|
||||
|
||||
No file changes, no editor text touched. Staying open in
|
||||
this pane for further operator/supervisor interaction — no
|
||||
exit, no shutdown.
|
||||
|
||||
▸ Agent draft-safe-trial (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-OPEN-WORKER-DRAFT-KEEP
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Read-only… on main [?]
|
||||
2.4%/1.0M | ↑43k ↓1.4k 18.4%
|
||||
@@ -0,0 +1,35 @@
|
||||
- Initial prompt: Say TIMER-PROMPT-OLD, then remove only
|
||||
the goals-ui-probe job. Do not change files, plans,
|
||||
─────────────────────────────────────────────────────────────
|
||||
Jobs — ↑↓ select a add t toggle s scope x remove
|
||||
─────────────────────────────────────────────────────────────
|
||||
▶ ✓ goals-ui-probe every 1h Say TIMER-PROMPT-O
|
||||
─────────────────────────────────────────────────────────────
|
||||
Selected: goals-ui-probe (P3Znb2bVxP)
|
||||
Type: interval Runs: 0
|
||||
Prompt: Say TIMER-PROMPT-OLD, then remove only the goals-ui-
|
||||
─────────────────────────────────────────────────────────────
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
● Agents · 1 running · 407.6s
|
||||
└─ ◡ draft-safe-trial [goals-worker] · 4 tool uses · 2...
|
||||
Read-only draft-safe intercom probe · fireworks/a...
|
||||
Sent. Brief report:
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
─────────────────────────────────────────────────────────────
|
||||
Scheduled Prompts (1 jobs)
|
||||
|
||||
✓ goals-ui-probe 1h Say TIMER-PROMPT-OLD,
|
||||
... - never 0
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 7.2%/1.0M | ↑210k ↓30k 99.6%
|
||||
@@ -0,0 +1,12 @@
|
||||
this pane for further operator/supervisor interaction — no
|
||||
exit, no shutdown.
|
||||
|
||||
▸ Agent draft-safe-trial (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-OPEN-WORKER-DRAFT-KEEP
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Read-only… on main [?]
|
||||
2.4%/1.0M | ↑43k ↓1.4k 18.4%
|
||||
@@ -0,0 +1,24 @@
|
||||
Listed first — no duplicate existed (No cron jobs
|
||||
configured), so exactly one job was added. No other jobs
|
||||
touched, no files/plans/workers changed, and the open
|
||||
worker (003c8e70, with its unsent draft) was left
|
||||
completely untouched. Ready for you to inspect through
|
||||
/schedule-prompt.
|
||||
|
||||
Reloaded keybindings, extensions, skills, prompts, themes,
|
||||
and context files
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 7.2%/1.0M | ↑210k ↓30k 99.6%
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
No file changes, no editor text touched. Staying open in
|
||||
this pane for further operator/supervisor interaction — no
|
||||
exit, no shutdown.
|
||||
|
||||
▸ Agent draft-safe-trial (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-OPEN-WORKER-DRAFT-KEEP
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Read-only… on main [?]
|
||||
2.4%/1.0M | ↑43k ↓1.4k 18.4%
|
||||
@@ -0,0 +1,59 @@
|
||||
{"type":"custom_message","customType":"intercom_message","content":"**From [goals-worker] Read-only draft-safe intercom probe** (/tmp/goals-edxeth-trial-HSahSm/project)\n\n_id b5f9ebfb-6e4c-4049-8d98-3385851fe601 · seq 3 · sent 2026-09-10T04:44:13.456Z · broker delivered 2026-09-10T04:44:13.456Z · receiver received 2026-09-10T04:44:13.467Z · injected 2026-09-10T04:44:13.467Z_\n\nRELOAD-LIVE-ACK","display":true,"details":{"from":{"id":"003c8e70-9999-4920-8a5c-064172010188","endpointEpoch":"a9d0684f-b821-4982-980d-9cd0798ddb70","name":"[goals-worker] Read-only draft-safe intercom probe","runtimeFallbackAlias":false,"cwd":"/tmp/goals-edxeth-trial-HSahSm/project","model":"accounts/fireworks/models/deepseek-v4-flash-0731","pid":708378,"startedAt":1789014977644,"lastActivity":1789015453456,"status":"tool:intercom","trustedLocal":true,"contextPct":2,"contextTokens":24039,"contextWindow":1000000},"message":{"id":"b5f9ebfb-6e4c-4049-8d98-3385851fe601","timestamp":1789015453456,"senderSequence":3,"content":{"text":"RELOAD-LIVE-ACK"},"brokerReceivedAt":1789015453456,"brokerDeliveredAt":1789015453456,"receiverReceivedAt":1789015453467,"injectedAt":1789015453467},"bodyText":"RELOAD-LIVE-ACK"},"id":"0c7f069e","parentId":"a07ec03f","timestamp":"2026-09-10T04:44:13.470Z"}
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"goals trial — Fireworks","pane_id":"w8:p69","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b1942e7d0586a","terminal_title":"π - project","terminal_title_stripped":"π - project","workspace_id":"w8"}
|
||||
Reply via Intercom to supervisor
|
||||
01a0898a-3dde-7015-b029-ced69dc7b553 with exactly
|
||||
RELOAD-LIVE-…
|
||||
✓ Message sent to 003c8e70 (7784b72a)
|
||||
|
||||
|
||||
Send receipt: message dispatched to the live worker
|
||||
003c8e70. Per your instruction, I will not treat the send
|
||||
receipt as the answer — I'm waiting for the worker's actual
|
||||
RELOAD-LIVE-ACK reply and will acknowledge that real
|
||||
message when it arrives. No second worker launched, no
|
||||
resume used, worker was already connected (only
|
||||
re-targeted).
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 7.6%/1.0M | ↑282k ↓32k 98.5%
|
||||
{"agent":"pi","agent_status":"idle","cwd":"/tmp/goals-edxeth-trial-HSahSm/project","focused":false,"foreground_cwd":"/tmp/goals-edxeth-trial-HSahSm/project","label":"[goals-worker] Read-only draft-safe intercom probe","pane_id":"w8:p6F","revision":4,"scroll":{"max_offset_from_bottom":0,"offset_from_bottom":0,"viewport_rows":57},"tab_id":"w8:t1","terminal_id":"term_65b198218cd1570","terminal_title":"π - [goals-worker] Read-only draft-safe intercom probe - project","terminal_title_stripped":"π - [goals-worker] Read-only draft-safe intercom probe - project","workspace_id":"w8"}
|
||||
|
||||
╭ From: subagent-chat-01a0898a-3dde-7015 (/tmp/goals-edxeth╮
|
||||
│Reply via Intercom to supervisor 01a0898a-3dde-7015-b029-c│
|
||||
│ Ctrl+O to expand │
|
||||
╰──────────────────────────────────────────────────────────╯
|
||||
|
||||
Thinking...
|
||||
|
||||
Sending RELOAD-LIVE-ACK via intercom to the supervisor, no
|
||||
file changes, staying open.
|
||||
|
||||
|
||||
intercom send → 01a0898a
|
||||
RELOAD-LIVE-ACK
|
||||
✓ Message sent to 01a0898a (b5f9ebfb)
|
||||
|
||||
|
||||
⠹ Working
|
||||
|
||||
▸ Agent draft-safe-trial (goals-worker)
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│ UNSENT-OPEN-WORKER-DRAFT-KEEP
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project in [goals-worker] Read-only… on main [?]
|
||||
2.4%/1.0M | ↑43k ↓1.5k 97.8%
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"fire": [
|
||||
{
|
||||
"type": "custom_message",
|
||||
"customType": "scheduled_prompt",
|
||||
"content": [],
|
||||
"display": true,
|
||||
"details": {
|
||||
"jobId": "np-gHnDsp_",
|
||||
"jobName": "goals-ui-probe",
|
||||
"prompt": "Say TIMER-PROMPT-NEW, then list jobs and remove only goals-ui-probe owned by this session; no files/plans/workers/other jobs changed."
|
||||
},
|
||||
"id": "81809511",
|
||||
"parentId": "7112d7c6",
|
||||
"timestamp": "2026-09-10T04:47:38.495Z"
|
||||
}
|
||||
],
|
||||
"removed": {
|
||||
"type": "message",
|
||||
"id": "19e91aaf",
|
||||
"parentId": "7ae72770",
|
||||
"timestamp": "2026-09-10T04:47:44.939Z",
|
||||
"message": {
|
||||
"role": "toolResult",
|
||||
"toolCallId": "call_e5726f051f5643efa7be5639",
|
||||
"toolName": "schedule_prompt",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "✓ Removed cron job \"goals-ui-probe\" (np-gHnDsp_)"
|
||||
}
|
||||
],
|
||||
"details": {
|
||||
"action": "remove",
|
||||
"jobs": [],
|
||||
"jobId": "np-gHnDsp_",
|
||||
"jobName": "goals-ui-probe"
|
||||
},
|
||||
"isError": false,
|
||||
"timestamp": 1789015664939
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"jobs": [],
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
(fired at 04:47:38.495Z)
|
||||
3. ✅ Removed only that job — ✓ Removed cron job
|
||||
"goals-ui-probe" (np-gHnDsp_)
|
||||
|
||||
No files/plans/workers/other jobs changed:
|
||||
- Scheduler now empty (only the probe job was present, now
|
||||
removed)
|
||||
- Open worker (003c8e70) and its editor draft untouched
|
||||
- Plan and all deliverables untouched, no new workers
|
||||
launched
|
||||
|
||||
Timer probe result: PASS — the 10s interval fired
|
||||
naturally, invoked this session's prompt, and the prompt's
|
||||
self-removal directive executed correctly without
|
||||
disturbing anything else.
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 8.2%/1.0M | ↑287k ↓36k 99.8%
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
code@coi-91cea783-1 /tmp/goals-edxeth-trial-HSahSm/project ma
|
||||
❯ /tmp/goals-edxeth-trial-HSahSm/start.zsh
|
||||
|
||||
|
||||
✓ hello.txt exists in the project root, written by the
|
||||
worker, containing exactly the UTF-8 byte sequence `hello
|
||||
from the worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72
|
||||
6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no
|
||||
trailing extra characters or blank lines; and
|
||||
evidence/hello.log exists in `evidence/` recording the
|
||||
byte-level verification of that file
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 8.2%/1.0M | ↑287k ↓36k 99.8%
|
||||
pi exiting due to uncaughtException:
|
||||
Error: This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().
|
||||
at ExtensionRunner.assertActive (file:///home/code/.local/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-JVUZSMYM.js:1066:25652)
|
||||
at get hasUI (file:///home/code/.local/lib/node_modules/@earendil-works/pi-coding-agent/dist/bundle/chunks/chunk-JVUZSMYM.js:1066:27753)
|
||||
at SubagentWidgetManager.update (/tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/widget.ts:139:24)
|
||||
at updateWidget (/tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/wiring.ts:72:24)
|
||||
at /tmp/pi-goals-stock-edxeth-LQFtq2/src/runtime/running-registry.ts:255:5
|
||||
|
||||
code@coi-91cea783-1 /tmp/goals-edxeth-trial-HSahSm/project main* 30m 4s
|
||||
❯
|
||||
@@ -0,0 +1,18 @@
|
||||
Timer probe result: PASS — the 10s interval fired naturally, invoked this session's prompt, and the prompt's self-removal
|
||||
directive executed correctly without disturbing anything else.
|
||||
|
||||
✓ hello.txt exists in the project root, written by the worker, containing exactly the UTF-8 byte sequence `hello from the
|
||||
worker\n` (22 bytes, hex `68 65 6c 6c 6f 20 66 72 6f 6d 20 74 68 65 20 77 6f 72 6b 65 72 0a`) with no trailing extra
|
||||
characters or blank lines; and evidence/hello.log exists in `evidence/` recording the byte-level verification of that file
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Confirm all other writers for the current and target plans are stopped (inspect /subagents and their panes). A missing
|
||||
handle is not proof. Take over in this session?
|
||||
|
||||
→ Worker confirmed stopped
|
||||
Cancel
|
||||
|
||||
↑↓ navigate enter select escape/ctrl+c cancel
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
project on main [?] goals: supervising | 1/1 reviewed | LSP | 8.2%/1.0M (auto) | ↑287k ↓36k 99.8% R3.5M | $0.111
|
||||
@@ -0,0 +1,25 @@
|
||||
saved to evidence/verified.log, with the supervisor independently repeating the byte checks and reading the log before │
|
||||
sign-off. Worker model recorded as fireworks/accounts/fireworks/models/deepseek-v4-flash-0731, no commits, no │
|
||||
dependencies, no unrelated changes. █
|
||||
█
|
||||
Use /goals ready to approve, /goals review to refine, or /goals exit to keep this draft for later. █
|
||||
|
||||
○ verified.txt holds exactly the 9 bytes "verified\n", and evidence/verified.log records a real byte check of that file
|
||||
◦ write verified.txt with exactly "verified\n" using a byte-exact tool such as printf, never an editor with default
|
||||
line endings
|
||||
◦ run the verify command above so its real output is written to evidence/verified.log and the command exits 0 only on a
|
||||
pass
|
||||
◦ record the outcome and file paths in the plan's evidence section below
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Review /tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4ce2-75d2-866a-53fdf4cd5007-main.md
|
||||
|
||||
→ Ready
|
||||
Discuss
|
||||
Edit
|
||||
Cancel
|
||||
|
||||
↑↓ navigate enter select escape/ctrl+c cancel
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
project on main [?] goals: planning | 0/1 reviewed | LSP | 4.3%/1.0M (auto) | ↑42k ↓13k 97.2% R418k | $0.021
|
||||
@@ -0,0 +1,55 @@
|
||||
preserved │
|
||||
- User voice and user-visible result untouched │
|
||||
│
|
||||
Schedule: job wS79fJFPbB removed; .pi/schedule-prompts.json│
|
||||
shows 0 jobs. No other jobs existed or were touched. │
|
||||
│
|
||||
Scope: zero commits (git log still empty), no packages │
|
||||
installed, no unrelated changes. │
|
||||
│
|
||||
The trial is complete: the approved plan ran to completion │
|
||||
through the goals-worker (after the parent's infra repair, │
|
||||
resumed via stock subagent_resume), the supervisor │
|
||||
independently inspected both artifacts before sign-off, and│
|
||||
all closing bookkeeping (job removal, plan finalization) is│
|
||||
done. │
|
||||
│
|
||||
────────────────────────────────────────────────────────────│
|
||||
│ │
|
||||
│ [pi-goals] │
|
||||
│ Plan changed: │
|
||||
│ /tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4│
|
||||
│ e2-75d2-866a-53fdf4cd5007-main.md. Read the current workin│
|
||||
│ set and inspect changed requirements, completion claims an│
|
||||
│ evidence. Manual checkbox edits are claims, not proof. Do │
|
||||
│ not weaken the agreed goal or start a duplicate writer. │
|
||||
│ │
|
||||
────────────────────────────────────────────────────────────│
|
||||
│
|
||||
Thinking... │
|
||||
│
|
||||
Plan-change event. Let me read the current plan and check │
|
||||
what changed against what I last verified. │
|
||||
│
|
||||
│
|
||||
read │
|
||||
/tmp/goals-edxeth-trial-X3Y79U/project/.pi/plan/01a089c9-4c│
|
||||
e2-75d2-866a-53fdf4cd5007-main.md █
|
||||
█
|
||||
|
||||
⠼ Working
|
||||
|
||||
✓ verified.txt holds exactly the 9 bytes "verified\n", and
|
||||
evidence/verified.log records a real byte check of that
|
||||
file
|
||||
● Agents · 1 running · 142.8s
|
||||
└─ ◟ verified-bytes-worker [goals-worker] · 12 tool us...
|
||||
Resume the delegated goal. You are the goals-w… ·...
|
||||
Completion report sent to the supervisor. All tas...
|
||||
─────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Fireworks
|
||||
─────────────────────────────────────────────────────────────
|
||||
project on main [?] 8.9%/1.0M | ↑217k ↓35k 99.6%
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
Task 2 — verify command run, real output saved:
|
||||
- Ran the plan's bash snippet (wc + od + hex comparison +
|
||||
PASS/FAIL via tee)
|
||||
- Exit code 0 (pass only)
|
||||
- evidence/verified.log (112 bytes) contains the raw wc/od
|
||||
output plus PASS: 9 bytes: verified + LF
|
||||
|
||||
Task 3 — plan evidence filled:
|
||||
- evidence: line under the goal now records the PASS, byte
|
||||
count, hex, log path, and constraint compliance
|
||||
|
||||
Constraints maintained: no installs, no git commits
|
||||
(branch has zero commits), no unrelated changes — only
|
||||
verified.txt, evidence/verified.log, and the plan evidence
|
||||
line.
|
||||
|
||||
Completion report sent via Intercom to supervisor
|
||||
01a089c9. My pane remains open for the supervisor's
|
||||
independent inspection before sign-off.
|
||||
|
||||
▸ Agent verified-bytes-worker (goals-worker)
|
||||
○ verified.txt holds exactly the 9 bytes "verified\n", and
|
||||
evidence/verified.log records a real byte check of that
|
||||
file
|
||||
◦ write verified.txt with exactly "verified\n" using a
|
||||
byte-exact tool such as printf, never an editor with
|
||||
default line endings
|
||||
◦ run the verify command above so its real output is
|
||||
written to evidence/verified.log and the command exits 0
|
||||
only on a pass
|
||||
◦ record the outcome and file paths in the plan's
|
||||
evidence section below
|
||||
────────────────────────────────────────────────────────────
|
||||
│
|
||||
│
|
||||
│
|
||||
│ accounts/fireworks/models/deepseek-v4-flash-0731 Firework
|
||||
────────────────────────────────────────────────────────────
|
||||
project on main [?] 2.8%/1.0M | ↑28k ↓3.7k 97.7%
|
||||
@@ -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 162 passed (162)
|
||||
Start at 13:25:21
|
||||
Duration 5.32s (transform 5.34s, setup 0ms, import 10.79s, tests 16.11s, environment 4ms)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 lint
|
||||
> biome check src/ test/
|
||||
|
||||
Checked 43 files in 41ms. No fixes applied.
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
> @wassname2/pi-goals@0.2.2 typecheck
|
||||
> tsc --noEmit
|
||||
|
||||
@@ -279,3 +279,91 @@ export const completeGoalDescription =
|
||||
"## Log and ticks the goal [x]. If any check differs, it fails closed and requires a fresh supervisor review.";
|
||||
|
||||
export const completeGoalParamDescription = "The goal's text: the line after 'goal:' in the plan file.";
|
||||
|
||||
/* Package-based prototype, in flow order. Legacy exports above still serve src/index.ts. */
|
||||
|
||||
// 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.`;
|
||||
}
|
||||
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}`;
|
||||
}
|
||||
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 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}`;
|
||||
}
|
||||
|
||||
// Supervision and turn-event upkeep (not a scheduled wake-up).
|
||||
export function prototypeSupervisor(workerName: string, planPath: string, supervisorId: string): string {
|
||||
return `You are the goal supervisor in the main chat for ${planPath}. Inspect actual artifacts, saved verification, applicable AGENTS.md and skills yourself; delegate implementation to '${workerName}'. Keep authorized work moving to the requested outcome, not merely approval paperwork. Investigate blocked/waiting/done claims and change ineffective instructions. Give brief visible assessments with judgment. You may maintain the plan but must not weaken the goal to accept worker output.
|
||||
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 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}`;
|
||||
}
|
||||
export function planChangedReview(planPath: string): string {
|
||||
return `Plan changed: ${planPath}. Read the current working set and inspect changed requirements, completion claims and evidence. Manual checkbox edits are claims, not proof. Do not weaken the agreed goal or start a duplicate writer.`;
|
||||
}
|
||||
export function manualReview(planPath: string): string {
|
||||
return `Review the current plan ${planPath}, worker progress and actual evidence. Do not launch a duplicate writer.`;
|
||||
}
|
||||
|
||||
// Check-ins. The installed scheduler owns storage/timing/UI. Removal guidance must never add jobs.
|
||||
export function removeGoalSchedule(sessionId: string): string {
|
||||
return `With schedule_prompt, list jobs and read .pi/schedule-prompts.json to verify ownership; tool text omits session binding. Remove by jobId only the job named ${JSON.stringify(`goals-${sessionId}`)} bound to session ${JSON.stringify(sessionId)}. Never use cleanup; leave other jobs untouched. Do not add, enable or recreate any job. If unavailable or ownership is ambiguous, report it; /schedule-prompt opens the user controls.`;
|
||||
}
|
||||
export function scheduleCheckIn(sessionId: string, planPath: string): string {
|
||||
return `Hourly check-in is one visible schedule_prompt job; plan-change and upkeep reviews are event hooks, not another timer. List first. If an owned job named ${JSON.stringify(`goals-${sessionId}`)} already exists, retain its human-edited prompt, interval and enabled/disabled state unchanged; never recreate, overwrite or re-enable it. Only while supervising unfinished non-cancelled goals, if missing on this explicit start/resume, add one session-bound interval '1h' job with no model override. Read .pi/schedule-prompts.json and verify that new job's session is ${JSON.stringify(sessionId)}; tool text does not expose binding. If the new job is unbound, remove that job by ID and report the scope error. Do not change other jobs. Its initial prompt: Read ${planPath} and the current goal mode. If paused, exited, solo or all non-cancelled goals reviewed, remove only this owned job without resuming work. Otherwise inspect progress and evidence, give a brief assessment and keep authorized work moving without a duplicate writer. Do not reinstall a missing job from a scheduled check-in. Users inspect/toggle/remove jobs with /schedule-prompt and edit prompt/interval through schedule_prompt update. Never use cleanup. Retain their edits, but warn that this installed scheduler deletes disabled jobs on reload/shutdown; do not promise they persist. If schedule_prompt is unavailable, report hourly check-ins unavailable; do not build a timer.`;
|
||||
}
|
||||
|
||||
// Completion and runtime errors. Tool returns are model-facing too.
|
||||
export const prototypeCompleteGoalDescription = "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. Manual ticks and worker reports are claims; ignored/uncommitted evidence is allowed. This records judgment, not an independent judge.";
|
||||
export const prototypeMessages = {
|
||||
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}; widget and 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 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: { id?: string; sessionFile: string } | undefined, exited: boolean): string {
|
||||
return `Goals ${exited ? "exited to ordinary chat" : "paused locally"}; plan and evidence retained. ${worker ? worker.id ? `Inspect and stop runtime id ${worker.id} through subagent_kill or its pane; confirm the actual result.` : `Only saved session ${worker.sessionFile} is recorded, not a kill id. Locate its live pane/session and confirm termination; never pass the file path to subagent_kill.` : "No worker recorded: inspect /subagents if a launch was interrupted; absence is not proof of stop."} Remote stop is NOT yet confirmed. Restore failed compaction/model/credits in the existing session and continue only after explicit authorization; never restart long work.`;
|
||||
}
|
||||
export function resumeNotice(workerName: string, planPath: string, worker: { sessionFile: string } | undefined): string {
|
||||
return `User authorized continuation of ${planPath}. Inspect worker state before any launch/resume. ${worker ? `Use the existing session ${worker.sessionFile}; if live, inspect/message it; only if confirmed stopped use subagent_resume.` : `Use '${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."}`;
|
||||
}
|
||||
|
||||
+312
-70
@@ -1,54 +1,176 @@
|
||||
/** Opt-in prototype: the existing chat plans and supervises an edxeth interactive worker. */
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { type FSWatcher, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
|
||||
import { dirname, isAbsolute, join, resolve } from "node:path";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
import { foldPlan, GOAL_LINE } from "./plan.js";
|
||||
import { planViews } from "./plan-view.js";
|
||||
import {
|
||||
attachGoalPlanDescription,
|
||||
attachNotice,
|
||||
childPlanAttached,
|
||||
childPlanRole,
|
||||
completionLog,
|
||||
completionResult,
|
||||
emptyEvidence,
|
||||
evidenceUnavailable,
|
||||
goalToolBlocked,
|
||||
manualReview,
|
||||
pausedRole,
|
||||
pauseExitNotice,
|
||||
planChangedReview,
|
||||
planUnavailable,
|
||||
prototypeCompleteGoalDescription,
|
||||
prototypeDiscuss,
|
||||
prototypeMessages,
|
||||
prototypePlanContext,
|
||||
prototypePlanDocument,
|
||||
prototypePlanning,
|
||||
prototypePlanningSeed,
|
||||
prototypeSupervisor,
|
||||
prototypeUpkeep,
|
||||
readyApproved,
|
||||
removeGoalSchedule,
|
||||
resumeNotice,
|
||||
scheduleCheckIn,
|
||||
soloNotice,
|
||||
soloRole,
|
||||
} from "./prompts.js";
|
||||
|
||||
const STATE = "pi-goals-main-supervisor-v1";
|
||||
const WORKER = "goals-worker";
|
||||
type Mode = "chat" | "planning" | "supervising" | "paused" | "solo";
|
||||
type GoalStatus = "open" | "active" | "done" | "cancelled";
|
||||
interface State {
|
||||
mode: Mode;
|
||||
plan?: string;
|
||||
worker?: { id: string; sessionFile: string };
|
||||
worker?: { id?: string; sessionFile: string };
|
||||
workerStopped?: boolean;
|
||||
signoffs: Record<string, { evidence: string[]; observation: string }>;
|
||||
child?: boolean;
|
||||
}
|
||||
const initial = (): State => ({ mode: "chat", signoffs: {} });
|
||||
const digest = (text: string) => createHash("sha256").update(text).digest("hex");
|
||||
const key = (text: string) => text.trim().toLowerCase();
|
||||
const SUBTASK_LINE = /^\s+(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*(.*)$/;
|
||||
function goals(text: string) {
|
||||
return foldPlan(text).split("\n").flatMap((line, index) => {
|
||||
const match = GOAL_LINE.exec(line);
|
||||
return match ? [{ subject: match[2].trim(), checked: /x/i.test(match[1]), index }] : [];
|
||||
if (!match) return [];
|
||||
const box = match[1].toLowerCase();
|
||||
return [{ subject: match[2].trim(), status: (box === "x" ? "done" : box === "/" ? "active" : box === "-" ? "cancelled" : "open") as GoalStatus, index }];
|
||||
});
|
||||
}
|
||||
/** Open subtasks under the goal at goalLine, up to the next goal line; the widget shows these. */
|
||||
function openSubtasks(plan: string, goalLine: number): string[] {
|
||||
const lines = foldPlan(plan).split("\n");
|
||||
const out: string[] = [];
|
||||
for (let i = goalLine + 1; i < lines.length; i++) {
|
||||
if (GOAL_LINE.test(lines[i])) break;
|
||||
const m = SUBTASK_LINE.exec(lines[i]);
|
||||
if (m && (m[1] === " " || m[1] === "/")) out.push(m[2].trim());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: {} });
|
||||
|
||||
export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
let state = initial();
|
||||
let generation = 0;
|
||||
let workerRevision = 0;
|
||||
let launchPending = false;
|
||||
let notice = true;
|
||||
let planWatcher: FSWatcher | undefined;
|
||||
let planEditTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let planHash = "";
|
||||
const childEnvironment = process.env.PI_SUBAGENT_AGENT === WORKER;
|
||||
const save = () => pi.appendEntry(STATE, state);
|
||||
// Missing, empty and failed reads are unavailable snapshots, never an empty authoritative plan.
|
||||
const readPlan = () => {
|
||||
try {
|
||||
if (!state.plan) throw new Error(prototypeMessages.noPlan);
|
||||
const text = readFileSync(state.plan, "utf8");
|
||||
if (!text.trim()) throw new Error(prototypeMessages.emptyPlan);
|
||||
return { text };
|
||||
} catch (error) { return { error: planUnavailable(state.plan, error) }; }
|
||||
};
|
||||
const planText = () => {
|
||||
try { return state.plan ? readFileSync(state.plan, "utf8") : ""; } catch { return ""; }
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) throw new Error(snapshot.error);
|
||||
return snapshot.text;
|
||||
};
|
||||
let turnsStale = 0;
|
||||
let lastWorkingSet = "";
|
||||
const checkIn = (ctx: ExtensionContext) => scheduleCheckIn(ctx.sessionManager.getSessionId(), state.plan ?? "");
|
||||
const hasScheduleTool = () => pi.getAllTools().some((tool) => tool.name === "schedule_prompt");
|
||||
const notedPlanValue = (prefix: string) => {
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) return null;
|
||||
const m = new RegExp(`^\\-\\s*${prefix}:\\s*(.+)$`, "im").exec(foldPlan(snapshot.text));
|
||||
return m?.[1]?.trim() ?? null;
|
||||
};
|
||||
function refresh(ctx: ExtensionContext) {
|
||||
const items = goals(planText());
|
||||
if (state.mode === "chat") { ctx.ui.setStatus("goals", undefined); ctx.ui.setWidget("goals", undefined); return; }
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) {
|
||||
ctx.ui.setStatus("goals", snapshot.error);
|
||||
ctx.ui.setWidget("goals", [snapshot.error]);
|
||||
return;
|
||||
}
|
||||
const items = goals(snapshot.text);
|
||||
// Reopened/deleted/ambiguous goal identities lose their sign-off. Manual ticks remain claims.
|
||||
for (const subject of Object.keys(state.signoffs)) {
|
||||
const matches = items.filter((g) => key(g.subject) === subject);
|
||||
if (matches.length !== 1 || !matches[0].checked) { delete state.signoffs[subject]; save(); }
|
||||
if (matches.length !== 1 || matches[0].status !== "done") { delete state.signoffs[subject]; save(); }
|
||||
}
|
||||
const accepted = items.filter((g) => g.checked && state.signoffs[key(g.subject)]).length;
|
||||
ctx.ui.setStatus("goals", state.mode === "chat" ? undefined : `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`);
|
||||
ctx.ui.setWidget("goals", state.mode === "chat" ? undefined : [
|
||||
...items.map((g) => `${state.signoffs[key(g.subject)] && g.checked ? "✓" : g.checked ? "?" : "○"} ${g.subject}`),
|
||||
...(items.some((g) => g.checked && !state.signoffs[key(g.subject)]) ? ["? = completion claim; parent review still required"] : []),
|
||||
]);
|
||||
const accepted = items.filter((g) => g.status === "done" && state.signoffs[key(g.subject)]).length;
|
||||
ctx.ui.setStatus("goals", `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`);
|
||||
// Progress and subtask visibility: the widget is the task list, so the active goal shows its
|
||||
// next open subtasks without reading archived checkboxes below the fold.
|
||||
const plan = snapshot.text;
|
||||
const focus = items.find((g) => g.status === "active") ?? items.find((g) => g.status === "open" && openSubtasks(plan, g.index).length > 0);
|
||||
const mark = (status: GoalStatus, signed: boolean) => status === "done" ? (signed ? "✓" : "?") : status === "active" ? "▸" : status === "cancelled" ? "✗" : "○";
|
||||
const lines: string[] = items.map((g) => `${mark(g.status, Boolean(state.signoffs[key(g.subject)]))} ${g.subject}`);
|
||||
if (focus) {
|
||||
const muted = (s: string) => ctx.ui.theme.fg("muted", ` ◦ ${s}`);
|
||||
lines.push(...openSubtasks(plan, focus.index).slice(0, 3).map(muted));
|
||||
}
|
||||
if (items.some((g) => g.status === "done" && !state.signoffs[key(g.subject)])) lines.push("? = completion claim; parent review still required");
|
||||
ctx.ui.setWidget("goals", lines);
|
||||
}
|
||||
function watchPlan(ctx: ExtensionContext) {
|
||||
planWatcher?.close();
|
||||
planWatcher = undefined;
|
||||
clearTimeout(planEditTimer);
|
||||
planEditTimer = undefined;
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text !== undefined) planHash = digest(planViews(snapshot.text).short);
|
||||
if (state.child || state.mode !== "supervising" || !state.plan) return;
|
||||
const stamp = generation;
|
||||
// Watch the directory so atomic plan replacement remains observable. This is an event hook:
|
||||
// plan-change reviews, not another scheduled loop (the hourly job is schedule_prompt's). A
|
||||
// short debounce coalesces bursts. Existing high-level plan views exclude maintenance
|
||||
// (tasks/evidence/Log) while preserving requirement wording and goal checkbox claims.
|
||||
try {
|
||||
planWatcher = watch(dirname(state.plan), { persistent: false }, () => {
|
||||
if (stamp !== generation) return;
|
||||
if (planEditTimer) clearTimeout(planEditTimer);
|
||||
planEditTimer = setTimeout(() => {
|
||||
planEditTimer = undefined;
|
||||
if (stamp !== generation || state.mode !== "supervising") return;
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) { ctx.ui.notify(snapshot.error!, "warning"); return; }
|
||||
refresh(ctx);
|
||||
const hash = digest(planViews(snapshot.text).short);
|
||||
if (hash === planHash) return;
|
||||
planHash = hash;
|
||||
notice = true;
|
||||
send(planChangedReview(state.plan!));
|
||||
}, 150);
|
||||
});
|
||||
planWatcher.on("error", (error) => { planWatcher?.close(); planWatcher = undefined; ctx.ui.notify(`Plan monitoring failed: ${error.message}`, "error"); });
|
||||
} catch (error) { ctx.ui.notify(`Plan monitoring unavailable: ${String(error)}`, "error"); }
|
||||
}
|
||||
function restore(ctx: ExtensionContext) {
|
||||
generation++;
|
||||
@@ -59,11 +181,14 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (childEnvironment) {
|
||||
state.child = true;
|
||||
state.mode = "solo";
|
||||
state.plan = process.env.PI_GOALS_SHARED_PLAN || state.plan;
|
||||
// Lineage-only workers attach the explicit task path using AttachGoalPlan.
|
||||
save();
|
||||
}
|
||||
notice = true;
|
||||
turnsStale = 0;
|
||||
lastWorkingSet = "";
|
||||
refresh(ctx);
|
||||
watchPlan(ctx);
|
||||
}
|
||||
function compatible() {
|
||||
const tools = pi.getAllTools();
|
||||
@@ -76,7 +201,23 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (triggerTurn) pi.sendUserMessage(`[pi-goals]\n${content}`, { deliverAs: "followUp" });
|
||||
else pi.sendMessage({ customType: "pi-goals-supervision", content, display: true }, { deliverAs: "followUp", triggerTurn: false });
|
||||
}
|
||||
const help = "/goals <objective> | review | ready | status | stop | resume | solo | exit\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.";
|
||||
async function confirmOwnership(ctx: ExtensionContext, target: string, text: string, solo = true): Promise<boolean> {
|
||||
if (launchPending) { ctx.ui.notify("Worker launch/resume is still pending; inspect its result before takeover.", "warning"); return false; }
|
||||
const stamp = generation;
|
||||
const revision = workerRevision;
|
||||
const confirmation = solo ? "Worker confirmed stopped" : "Previous supervisor confirmed stopped";
|
||||
const choice = await ctx.ui.select(solo ? "Confirm all other writers for the current and target plans are stopped (inspect /subagents and their panes). A missing handle is not proof. Take over in this session?" : "Confirm no other supervisor owns this plan. Preserve any existing worker session and reconnect rather than starting another writer.", [confirmation, "Cancel"]);
|
||||
if (stamp !== generation || revision !== workerRevision) return false;
|
||||
if (choice !== confirmation) return false;
|
||||
if (readFileSync(target, "utf8") !== text) { ctx.ui.notify("Plan changed during takeover; confirm again.", "warning"); return false; }
|
||||
return true;
|
||||
}
|
||||
function enterSolo(ctx: ExtensionContext) {
|
||||
state.mode = "solo"; state.workerStopped = true;
|
||||
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.";
|
||||
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();
|
||||
@@ -88,7 +229,7 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (menu) {
|
||||
const choice = await ctx.ui.select(`Review ${state.plan}`, ["Ready", "Discuss", "Edit", "Cancel"]);
|
||||
if (stamp !== generation || digest(planText()) !== digest(text)) { ctx.ui.notify("Plan changed during review. Review it again.", "warning"); return; }
|
||||
if (choice === "Discuss") { send("Discuss the current draft in ordinary chat. Do not launch a worker or reopen the review menu until requested."); return; }
|
||||
if (choice === "Discuss") { send(prototypeDiscuss); return; }
|
||||
if (choice === "Edit") {
|
||||
const edited = await ctx.ui.editor("Edit goal plan", text);
|
||||
if (edited !== undefined && stamp === generation && planText() === text && state.plan) { writeFileSync(state.plan, edited); refresh(ctx); }
|
||||
@@ -97,90 +238,191 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
if (choice !== "Ready") return;
|
||||
}
|
||||
if (!compatible()) { ctx.ui.notify("Requires edxeth/pi-subagents 2.9.x, not nicobailon/pi-subagents. Draft preserved; /goals solo is available.", "error"); return; }
|
||||
state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx);
|
||||
send(`Ready approved this plan: ${state.plan}. Stay in this chat as supervisor. Delegate the first open goal to agent '${WORKER}' with subagent; provide name, title and task. Include the absolute plan path in the task. Do not start a second writer. Inspect actual outputs when the child reports.\n\n${text}`);
|
||||
state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(`${checkIn(ctx)}\n\n${readyApproved(WORKER, state.plan!, state.worker?.sessionFile, text, ctx.sessionManager.getSessionId())}`);
|
||||
}
|
||||
|
||||
pi.on("session_start", (_e, ctx) => restore(ctx));
|
||||
pi.on("session_tree", (_e, ctx) => restore(ctx));
|
||||
pi.on("session_shutdown", () => { generation++; planWatcher?.close(); planWatcher = undefined; clearTimeout(planEditTimer); planEditTimer = undefined; });
|
||||
pi.on("session_compact", () => { notice = true; });
|
||||
pi.on("agent_end", (_e, ctx) => refresh(ctx));
|
||||
pi.on("turn_end", (_event, ctx) => {
|
||||
if (!["supervising", "solo"].includes(state.mode)) return;
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) { notice = true; return; }
|
||||
const workingSet = foldPlan(snapshot.text);
|
||||
turnsStale = workingSet === lastWorkingSet ? turnsStale + 1 : 0;
|
||||
lastWorkingSet = workingSet;
|
||||
refresh(ctx);
|
||||
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.on("agent_end", (_e, ctx) => { refresh(ctx); if (!planWatcher && state.mode === "supervising") watchPlan(ctx); });
|
||||
// No context hook. Historical message arrays, native checkpoints and model selection are untouched.
|
||||
pi.on("before_agent_start", (event) => {
|
||||
if (state.child || state.mode === "chat") return;
|
||||
const role = state.mode === "supervising"
|
||||
? `You are the goal supervisor in the main chat. Inspect files, evidence and applicable AGENTS/skills yourself; delegate implementation to '${WORKER}'. Investigate blocked/waiting/done claims rather than accepting them. Change ineffective instructions. Keep authorized work moving until the requested result is inspected, not merely until approval paperwork exists. Use edxeth subagent for first launch and subagent_resume with the returned sessionFile for follow-up; never duplicate a live writer. Worker reports arrive automatically. A recap alone sends no instruction. When a worker finishes a goal, inspect the actual artifact and saved verification, call CompleteGoal, then resume it for the next open goal. Retain brief visible assessments. Do not enable edxeth's tool-restricted orchestrator mode.`
|
||||
: state.mode === "planning"
|
||||
? `Plan only. Ask material unresolved questions, not a quota. Draft ${state.plan} with objective, preferences, distinct '- [ ] goal: ...' subjects, failure modes and evidence/verification for each. Keep goal lines and evidence references above ## Log. Do not implement, launch workers or treat your own draft as approved. Present it for /goals review or /goals ready.`
|
||||
: state.mode === "paused"
|
||||
? "Goal work is paused. Do not launch, resume or authorize work. Incoming worker reports are observations, not permission to continue. Help inspect or stop existing workers if requested."
|
||||
: "Solo mode: this chat may implement the approved plan directly. Do not delegate a concurrent writer. Verify artifacts before CompleteGoal.";
|
||||
const message = notice ? { customType: "pi-goals-plan", content: `Current goal mode: ${state.mode}. Earlier role messages are historical; this current role governs.\nPlan: ${state.plan}\n${planText()}`, display: false } : undefined;
|
||||
pi.on("before_agent_start", (event, ctx) => {
|
||||
if (state.mode === "chat") return;
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) {
|
||||
notice = true; // Retry resync on the next turn; do not consume a failed snapshot.
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${state.child ? childPlanRole : ""}\n${snapshot.error}` };
|
||||
}
|
||||
const role = state.child ? childPlanRole : state.mode === "supervising"
|
||||
? prototypeSupervisor(WORKER, state.plan!, ctx.sessionManager.getSessionId())
|
||||
: state.mode === "planning" ? prototypePlanning(state.plan!) : state.mode === "paused" ? pausedRole : soloRole;
|
||||
const content = notice ? prototypePlanContext(state.child ? "worker" : state.mode, state.plan, snapshot.text)
|
||||
: undefined;
|
||||
if (content) turnsStale = 0;
|
||||
notice = false;
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${role}`, ...(message ? { message } : {}) };
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${role}`, ...(content ? { message: { customType: "pi-goals-plan", content, display: false } } : {}) };
|
||||
});
|
||||
pi.on("tool_call", (event) => {
|
||||
if (state.child || !["subagent", "subagent_resume"].includes(event.toolName)) return;
|
||||
if (state.mode === "planning" || state.mode === "paused") return { block: true, reason: `Goals are ${state.mode}; no worker launch/resume authorized.` };
|
||||
// Solo means this chat took over implementation: no concurrent writer may be delegated.
|
||||
if (state.mode === "planning" || state.mode === "paused" || state.mode === "solo") return { block: true, reason: goalToolBlocked(state.mode) };
|
||||
if (state.plan) { launchPending = true; state.workerStopped = false; workerRevision++; save(); }
|
||||
});
|
||||
pi.on("tool_result", (event) => {
|
||||
if (state.child || state.mode !== "supervising" || !["subagent", "subagent_resume"].includes(event.toolName) || event.isError) return;
|
||||
if (state.child || !state.plan || !["subagent", "subagent_resume"].includes(event.toolName)) return;
|
||||
launchPending = false;
|
||||
if (event.isError) return;
|
||||
const details = event.details as { id?: string; sessionFile?: string } | undefined;
|
||||
if (details?.id && details.sessionFile) { state.worker = { id: details.id, sessionFile: details.sessionFile }; save(); }
|
||||
if (details?.id && details.sessionFile) { state.worker = { id: details.id, sessionFile: details.sessionFile }; state.workerStopped = false; workerRevision++; save(); }
|
||||
});
|
||||
|
||||
pi.registerCommand("goals", {
|
||||
description: "Prototype: plan here, supervise a visible edxeth worker",
|
||||
handler: async (args, ctx) => {
|
||||
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; }
|
||||
if (command === "status") { refresh(ctx); ctx.ui.notify(`${state.mode}\nPlan: ${state.plan ?? "none"}\nWorker session: ${state.worker?.sessionFile ?? "not recorded"}\nLiveness is owned by edxeth; inspect /subagents.`, "info"); return; }
|
||||
if (command === "review" || command === "ready") { await ready(ctx, command === "review"); return; }
|
||||
if (command === "stop" || command === "exit") {
|
||||
state.mode = command === "stop" ? "paused" : "chat"; generation++; notice = true; save(); refresh(ctx);
|
||||
send(`Goals ${command === "stop" ? "paused" : "exited to ordinary chat"} locally; plan and evidence retained. ${state.worker ? `Use subagent_kill with id ${JSON.stringify(state.worker.id)} to stop the tracked worker, and confirm the result. Do not resume it.` : "No worker recorded. Inspect /subagents if a launch was interrupted."} Remote stop is NOT yet confirmed.`, Boolean(state.worker)); return;
|
||||
}
|
||||
if (command === "resume") {
|
||||
if (state.mode !== "paused" || !state.plan) { ctx.ui.notify("Only a paused approved plan can resume. A draft needs Ready.", "warning"); return; }
|
||||
if (!compatible()) { ctx.ui.notify("edxeth tools unavailable; plan remains paused.", "error"); return; }
|
||||
state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx);
|
||||
send(`User authorized continuation. Inspect worker state in /subagents before any launch. ${state.worker ? `Continue the existing session with subagent_resume: ${state.worker.sessionFile}.` : `Use subagent with '${WORKER}' only if no prior worker exists.`} Continue only unfinished goals in ${state.plan}.`); return;
|
||||
}
|
||||
if (command === "solo") {
|
||||
if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; }
|
||||
if (state.worker) { ctx.ui.notify("Stop and inspect the existing worker first. Solo takeover is deliberately not automatic; use /goals exit and ordinary chat after confirming it stopped.", "warning"); return; }
|
||||
state.mode = "solo"; generation++; notice = true; save(); refresh(ctx); send(`User explicitly authorized solo work on ${state.plan}. No worker has been recorded; inspect /subagents if a launch previously failed.`); return;
|
||||
}
|
||||
if (state.worker || 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 { readFileSync(path); } catch { writeFileSync(path, `# Goal plan\n\n## Objective\n${command}\n\n## Goals\n\n## Log\n`); }
|
||||
state = { mode: "planning", plan: path, signoffs: {} }; generation++; notice = true; save(); refresh(ctx);
|
||||
send(`Draft or revise ${path} for this objective: ${command}. Read any existing plan first. Ask only material questions; no implementation before Ready.`);
|
||||
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; }
|
||||
if (command === "status") {
|
||||
refresh(ctx);
|
||||
ctx.ui.notify([
|
||||
`Mode: ${state.mode}`,
|
||||
`Plan: ${state.plan ?? "none"}`,
|
||||
`Preferred worker model (plan): ${notedPlanValue("preferred worker model") ?? "not stated; use /goals model <model>"}`,
|
||||
`Recorded worker session: ${state.worker?.sessionFile ?? "not recorded"}`,
|
||||
notedPlanValue("worker session") ? `Worker session noted in plan: ${notedPlanValue("worker session")}` : "",
|
||||
`Hourly check-in: schedule_prompt job ${JSON.stringify(`goals-${ctx.sessionManager.getSessionId()}`)} (list/remove via schedule_prompt; plan-change reviews are the plan-watcher event hook)`,
|
||||
"Liveness is owned by edxeth; inspect /subagents.",
|
||||
].filter(Boolean).join("\n"), "info");
|
||||
return;
|
||||
}
|
||||
if (command === "review" && state.mode === "supervising") { notice = true; send(manualReview(state.plan ?? "")); return; }
|
||||
if (command === "review" || command === "ready") { await ready(ctx, command === "review"); return; }
|
||||
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();
|
||||
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));
|
||||
if (found >= 0) lines[found] = pref;
|
||||
else { const title = lines.findIndex((line) => /^#\s/.test(line)); lines.splice(title >= 0 ? title + 1 : 0, 0, pref); }
|
||||
writeFileSync(state.plan, lines.join("\n"));
|
||||
planHash = digest(planViews(planText()).short);
|
||||
refresh(ctx);
|
||||
ctx.ui.notify(ref ? `Preferred worker model set to ${ref} in plan preferences. The supervisor selects it at launch and verifies the resolved model; the worker pane's own model is chosen with /model in that pane.` : "Preferred worker model cleared.", "info");
|
||||
return;
|
||||
}
|
||||
if (command === "attach" || command.startsWith("attach ")) {
|
||||
const rest = command.slice("attach".length).trim();
|
||||
const [raw, kind] = rest.split(/\s+/);
|
||||
const solo = kind === "solo";
|
||||
if (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;
|
||||
try { text = readFileSync(target, "utf8"); } catch { ctx.ui.notify(`Cannot read plan at ${target}.`, "error"); return; }
|
||||
if (!goals(text).length) { ctx.ui.notify(`${target} has no '- [ ] goal:' lines; attach a judgeable plan.`, "warning"); return; }
|
||||
if (!solo && ((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 noted = /^-\s*worker session:\s*(\S+)/im.exec(foldPlan(text))?.[1];
|
||||
if (!(await confirmOwnership(ctx, target, text, solo))) return;
|
||||
const retained = target === state.plan ? state.signoffs : {};
|
||||
const worker = noted ? { sessionFile: resolve(ctx.cwd, noted) } : state.workerStopped ? state.worker : undefined;
|
||||
state = { mode: solo ? "solo" : "planning", plan: target, signoffs: retained, worker, workerStopped: solo || (!noted && state.workerStopped) };
|
||||
generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
if (solo) enterSolo(ctx);
|
||||
else send(attachNotice(target, false, noted));
|
||||
return;
|
||||
}
|
||||
if (command === "stop" || command === "exit") {
|
||||
if (state.mode === "planning") {
|
||||
if (command === "stop") { ctx.ui.notify("A draft cannot pause; use /goals exit to leave planning with the draft preserved.", "warning"); return; }
|
||||
// Planning exit must not get the model trapped re-planning or lose the draft.
|
||||
state.mode = "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
ctx.ui.notify(`Planning exited; draft preserved at ${state.plan}. No implementation was approved or started. Reconnect with /goals attach ${state.plan}.`, "info");
|
||||
return;
|
||||
}
|
||||
state.mode = command === "stop" ? "paused" : "chat"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(`${removeGoalSchedule(ctx.sessionManager.getSessionId())}\n\n${pauseExitNotice(state.worker, command === "exit")}`, Boolean(state.worker) || hasScheduleTool());
|
||||
return;
|
||||
}
|
||||
if (command === "resume") {
|
||||
if (state.mode !== "paused" || !state.plan) { ctx.ui.notify("Only a paused approved plan can resume. A draft needs Ready.", "warning"); return; }
|
||||
if (!compatible()) { ctx.ui.notify("edxeth tools unavailable; plan remains paused.", "error"); return; }
|
||||
state.mode = "supervising"; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(`${checkIn(ctx)}\n\n${resumeNotice(WORKER, state.plan, state.worker)}`);
|
||||
return;
|
||||
}
|
||||
if (command === "solo") {
|
||||
if (!state.plan || !goals(planText()).length) { ctx.ui.notify("Register a goal plan first.", "warning"); return; }
|
||||
if (!(await confirmOwnership(ctx, state.plan, planText()))) return;
|
||||
enterSolo(ctx);
|
||||
return;
|
||||
}
|
||||
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; }
|
||||
state = { mode: "planning", plan: path, signoffs: {}, worker: state.worker, workerStopped: state.workerStopped }; generation++; notice = true; save(); refresh(ctx); watchPlan(ctx);
|
||||
send(prototypePlanningSeed(command, path));
|
||||
} catch (error) { ctx.ui.notify(String(error), "error"); }
|
||||
},
|
||||
});
|
||||
pi.registerTool({
|
||||
name: "AttachGoalPlan", label: "Attach delegated plan", description: attachGoalPlanDescription,
|
||||
parameters: Type.Object({ path: Type.String() }),
|
||||
async execute(_id, params, _signal, _update, ctx) {
|
||||
if (!state.child) return result(prototypeMessages.childAttachOnly);
|
||||
try {
|
||||
if (!isAbsolute(params.path) || !goals(readFileSync(params.path, "utf8")).length) return result(prototypeMessages.invalidAttachment);
|
||||
} catch { return result(prototypeMessages.invalidAttachment); }
|
||||
state.plan = params.path; generation++; notice = true; save(); refresh(ctx);
|
||||
return result(childPlanAttached(params.path));
|
||||
},
|
||||
});
|
||||
pi.registerTool({
|
||||
name: "CompleteGoal", label: "Review goal evidence",
|
||||
description: "Parent-only recorded judgment, not an independent judge. Inspect the actual artifact and saved verification first; cite nonempty evidence files and describe what you observed. Exact goal subject required. Ignored/uncommitted evidence is allowed. Manual ticks are claims. Do not use this merely because the worker reports success.",
|
||||
description: prototypeCompleteGoalDescription,
|
||||
parameters: Type.Object({ goal: Type.String(), evidence: Type.Array(Type.String(), { minItems: 1 }), observation: Type.String({ minLength: 1 }) }),
|
||||
async execute(_id, params, signal, _update, ctx) {
|
||||
if (state.child || !["supervising", "solo"].includes(state.mode)) return result("Completion is available only to the active parent supervisor or solo worker.");
|
||||
if (signal?.aborted) return result("Cancelled; no sign-off recorded.");
|
||||
const text = planText();
|
||||
const matches = goals(text).filter((g) => key(g.subject) === key(params.goal));
|
||||
if (matches.length !== 1 || !state.plan) return result("Use one unique exact goal subject from the plan; no sign-off recorded.");
|
||||
if (state.child || !["supervising", "solo"].includes(state.mode)) return result(prototypeMessages.completionUnavailable);
|
||||
if (signal?.aborted) return result(prototypeMessages.cancelled);
|
||||
const snapshot = readPlan();
|
||||
if (snapshot.text === undefined) return result(snapshot.error!);
|
||||
const text = snapshot.text;
|
||||
const matches = goals(text).filter((g) => g.status !== "cancelled" && key(g.subject) === key(params.goal));
|
||||
if (matches.length !== 1 || !state.plan) return result(prototypeMessages.uniqueGoal);
|
||||
const evidence = params.evidence.map((file) => isAbsolute(file) ? file : resolve(ctx.cwd, file));
|
||||
try { for (const file of evidence) if (!readFileSync(file).length) throw new Error(`Empty evidence: ${file}`); }
|
||||
catch (error) { return result(`Evidence unavailable: ${String(error)}. No sign-off recorded.`); }
|
||||
try { for (const file of evidence) if (!readFileSync(file).length) throw new Error(emptyEvidence(file)); }
|
||||
catch (error) { return result(evidenceUnavailable(error)); }
|
||||
const lines = text.split("\n");
|
||||
lines[matches[0].index] = lines[matches[0].index].replace(/\[[ xX/-]\]/, "[x]");
|
||||
if (!/^##\s+Log\s*$/im.test(text)) lines.push("\n## Log");
|
||||
writeFileSync(state.plan, `${lines.join("\n").trimEnd()}\n\n- Parent review: ${JSON.stringify(params.goal)}; ${JSON.stringify(params.observation)}; evidence ${JSON.stringify(evidence)}\n`);
|
||||
let log = lines.findIndex(line => /^##\s+Log\s*$/i.test(line));
|
||||
if (log === -1) { lines.push("", "## Log"); log = lines.length - 1; }
|
||||
lines.splice(log + 1, 0, "", completionLog(params.goal, params.observation, evidence, state.mode === "solo"));
|
||||
writeFileSync(state.plan, `${lines.join("\n").trimEnd()}\n`);
|
||||
state.signoffs[key(matches[0].subject)] = { evidence, observation: params.observation };
|
||||
planHash = digest(planViews(planText()).short);
|
||||
save(); refresh(ctx);
|
||||
return result(`Recorded parent judgment for ${matches[0].subject}. This is not independent verification. Continue supervising any remaining open or unsigned goals.`);
|
||||
const remaining = goals(planText()).some((goal) => goal.status !== "cancelled" && (goal.status !== "done" || !state.signoffs[key(goal.subject)]));
|
||||
return result(completionResult(matches[0].subject, ctx.sessionManager.getSessionId(), remaining, state.mode === "solo"));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+432
-12
@@ -1,18 +1,29 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, mkdtempSync, readFileSync, renameSync, 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, expect, it, vi } from "vitest";
|
||||
import { scheduleCheckIn } from "../src/prompts.js";
|
||||
import prototype from "../src/prototype.js";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
|
||||
function fixture() {
|
||||
const shutdowns: Array<() => void> = [];
|
||||
afterEach(() => { for (const shutdown of shutdowns.splice(0)) shutdown(); vi.unstubAllEnvs(); for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
async function waitFor(predicate: () => boolean, ms = 1500): Promise<void> {
|
||||
const start = Date.now();
|
||||
while (!predicate()) {
|
||||
if (Date.now() - start > ms) throw new Error("timed out waiting for condition");
|
||||
await delay(10);
|
||||
}
|
||||
}
|
||||
function fixture(child = false) {
|
||||
vi.stubEnv("PI_SUBAGENT_AGENT", child ? "goals-worker" : "");
|
||||
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: {
|
||||
notify: vi.fn(), setStatus: vi.fn(), setWidget: vi.fn(), select: vi.fn(async () => "Ready"), editor: vi.fn(),
|
||||
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 = {
|
||||
on: (event: string, hook: any) => hooks.set(event, hook),
|
||||
@@ -33,7 +44,16 @@ function fixture() {
|
||||
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); };
|
||||
return { ctx, pi, hooks, tools, messages, command, path, draft, entries };
|
||||
const shutdown = () => hooks.get("session_shutdown")();
|
||||
shutdowns.push(shutdown);
|
||||
const changed = () => messages.filter((m) => m.message?.content?.includes("Plan changed")).length;
|
||||
const atomicWrite = async (text: string) => {
|
||||
const tmp = `${path}.tmp`;
|
||||
writeFileSync(tmp, text);
|
||||
renameSync(tmp, path);
|
||||
await delay(25);
|
||||
};
|
||||
return { ctx, pi, hooks, tools, messages, command, path, plan, draft, shutdown, changed, atomicWrite, entries };
|
||||
}
|
||||
|
||||
it("keeps Ready in the same chat, sends saved notices and never installs a context hook", async () => {
|
||||
@@ -44,14 +64,16 @@ it("keeps Ready in the same chat, sends saved notices and never installs a conte
|
||||
expect(f.messages.at(-1).message.content).toContain("goals-worker");
|
||||
expect(f.hooks.has("context")).toBe(false);
|
||||
const event = { systemPrompt: "original system" };
|
||||
expect(f.hooks.get("before_agent_start")(event).systemPrompt).toContain("original system");
|
||||
expect(f.hooks.get("before_agent_start")(event, f.ctx).systemPrompt).toContain("original system");
|
||||
f.hooks.get("session_compact")();
|
||||
expect(f.hooks.get("before_agent_start")(event).message.content).toContain("Current goal mode: supervising");
|
||||
expect(f.hooks.get("before_agent_start")(event, f.ctx).message.content).toContain("Current goal mode: supervising");
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("preserves a draft when the wrong subagent package is loaded, and offers explicit solo", async () => {
|
||||
const f = fixture(); await f.draft(); f.pi.getAllTools.mockReturnValue([]);
|
||||
await f.command("ready"); expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command("solo"); expect(f.entries.at(-1).data.mode).toBe("solo");
|
||||
});
|
||||
|
||||
@@ -78,14 +100,412 @@ it("requires actual nonempty evidence, distinguishes manual ticks, and retains s
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
const complete = (goal: string, evidence: string[], signal?: AbortSignal) => f.tools.get("CompleteGoal").execute("t", { goal, evidence, observation: "Inspected exact saved bytes" }, signal, undefined, f.ctx);
|
||||
expect((await complete("first output", ["missing.log"])).content[0].text).toContain("Evidence unavailable");
|
||||
mkdirSync(join(f.ctx.cwd,"evidence")); writeFileSync(join(f.ctx.cwd,"evidence/pass.log"),"actual fixture bytes\n");
|
||||
mkdirSync(join(f.ctx.cwd, "evidence")); writeFileSync(join(f.ctx.cwd, "evidence/pass.log"), "actual fixture bytes\n");
|
||||
expect((await complete("first output", ["evidence/pass.log"], AbortSignal.abort())).content[0].text).toContain("Cancelled");
|
||||
await complete("first output", ["evidence/pass.log"]);
|
||||
writeFileSync(f.path, readFileSync(f.path,"utf8").replace("[ ] goal: second", "[x] goal: second"));
|
||||
f.hooks.get("session_start")({},f.ctx);
|
||||
writeFileSync(f.path, readFileSync(f.path, "utf8").replace("[ ] goal: second", "[x] goal: second"));
|
||||
f.hooks.get("session_start")({}, f.ctx);
|
||||
expect(f.ctx.ui.setStatus).toHaveBeenLastCalledWith("goals", "goals: supervising | 1/2 reviewed");
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1]).toContain("? = completion claim; parent review still required");
|
||||
writeFileSync(f.path,readFileSync(f.path,"utf8").replace("[x] goal: first", "[ ] goal: first"));
|
||||
f.hooks.get("agent_end")({},f.ctx);
|
||||
writeFileSync(f.path, readFileSync(f.path, "utf8").replace("[x] goal: first", "[ ] goal: first"));
|
||||
f.hooks.get("agent_end")({}, f.ctx);
|
||||
expect(f.ctx.ui.setStatus).toHaveBeenLastCalledWith("goals", "goals: supervising | 0/2 reviewed");
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("reviews a plan replaced atomically, and ignores writes that keep the same content", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: changed requirement\n## Log"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
const review = f.messages.find((m) => m.message.content.includes("Plan changed"))?.message.content;
|
||||
expect(review).toContain("Plan changed: ");
|
||||
expect(review).toContain(f.path);
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: same requirement again\n## Log"));
|
||||
await waitFor(() => f.changed() === 2);
|
||||
// Rewriting identical bytes must not retrigger the review event hook.
|
||||
const same = f.plan.replace("## Log", "- discriminator: same requirement again\n## Log");
|
||||
writeFileSync(f.path, same); await delay(300);
|
||||
expect(f.changed()).toBe(2);
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("coalesces duplicate plan-change notifications into one review", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: first burst edit\n## Log"));
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: second burst edit\n## Log"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
await delay(200);
|
||||
expect(f.changed()).toBe(1);
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("stops plan watching on shutdown and re-arms it on reload without duplicating events", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.shutdown();
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: ignored while shut down\n## Log"));
|
||||
await delay(150);
|
||||
expect(f.changed()).toBe(0);
|
||||
f.hooks.get("session_start")({}, f.ctx);
|
||||
await f.atomicWrite(f.plan.replace("## Log", "- discriminator: seen after reload\n## Log"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
expect(f.changed()).toBe(1);
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("does not retrigger a review for its own CompleteGoal plan write", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
mkdirSync(join(f.ctx.cwd, "evidence")); writeFileSync(join(f.ctx.cwd, "evidence/pass.log"), "bytes\n");
|
||||
await f.tools.get("CompleteGoal").execute("t", { goal: "first output", evidence: ["evidence/pass.log"], observation: "inspected" }, undefined, undefined, f.ctx);
|
||||
await delay(200);
|
||||
expect(f.changed()).toBe(0);
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("gives pause/exit the session-bound scheduler job removal guidance", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
await f.command("stop");
|
||||
const stop = f.messages.at(-1).message.content;
|
||||
expect(stop).toContain('goals-copy-only"');
|
||||
expect(stop).toContain("Do not add, enable or recreate any job");
|
||||
expect(stop).not.toContain("interval '1h'");
|
||||
expect(stop).toContain("Remote stop is NOT yet confirmed");
|
||||
await f.command("resume");
|
||||
await f.command("exit");
|
||||
expect(f.messages.at(-1).message.content).toContain('goals-copy-only"');
|
||||
expect(f.entries.at(-1).data.mode).toBe("chat");
|
||||
});
|
||||
|
||||
it("tells the model to remove only its own job after the final review", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
mkdirSync(join(f.ctx.cwd, "evidence")); writeFileSync(join(f.ctx.cwd, "evidence/pass.log"), "bytes\n");
|
||||
let finalText = "";
|
||||
for (const goal of ["first output", "second output"]) {
|
||||
finalText = (await f.tools.get("CompleteGoal").execute("t", { goal, evidence: ["evidence/pass.log"], observation: "inspected" }, undefined, undefined, f.ctx)).content[0].text;
|
||||
}
|
||||
expect(finalText).toContain("All non-cancelled goals are reviewed.");
|
||||
expect(finalText).toContain('job named "goals-copy-only"');
|
||||
expect(finalText).toContain("leave other jobs untouched");
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("retains human-edited and disabled owned schedules without overriding their controls", () => {
|
||||
const guidance = scheduleCheckIn("copy-only", ".pi/plan/copy-only-main.md");
|
||||
expect(guidance).toContain("List first");
|
||||
expect(guidance).toContain("enabled/disabled state unchanged");
|
||||
expect(guidance).toContain("never recreate, overwrite or re-enable");
|
||||
expect(guidance).toContain("no model override");
|
||||
expect(guidance).toContain("Do not reinstall a missing job from a scheduled check-in");
|
||||
expect(guidance).toContain("/schedule-prompts");
|
||||
});
|
||||
|
||||
it("restores context after compaction without reinstalling or overriding scheduler jobs", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.hooks.get("session_compact")();
|
||||
const result = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
expect(result.systemPrompt).not.toContain("add one session-bound");
|
||||
expect(result.message.content).toContain(f.path);
|
||||
});
|
||||
|
||||
it("recovers from an unreadable plan after compaction instead of restarting work", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
rmSync(f.path);
|
||||
f.hooks.get("session_compact")();
|
||||
const result = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
expect(result.systemPrompt).toContain("ENOENT");
|
||||
expect(result.systemPrompt).toContain("do not restart completed work");
|
||||
writeFileSync(f.path, f.plan);
|
||||
expect(f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx).message.content).toContain(f.plan.trim());
|
||||
f.shutdown();
|
||||
});
|
||||
|
||||
it("requires confirmed worker stop before solo takeover and never lets two writers run together", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "child-1", sessionFile: "/tmp/child.jsonl" } });
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Cancel");
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising"); // cancelled
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("solo");
|
||||
expect(f.hooks.get("tool_call")({ toolName: "subagent" }).block).toBe(true);
|
||||
expect(f.hooks.get("tool_call")({ toolName: "subagent_resume" }).block).toBe(true);
|
||||
expect(f.hooks.get("tool_call")({ toolName: "subagent_kill" })).toBeUndefined();
|
||||
mkdirSync(join(f.ctx.cwd, "evidence")); writeFileSync(join(f.ctx.cwd, "evidence/pass.log"), "bytes\n");
|
||||
const text = (await f.tools.get("CompleteGoal").execute("t", { goal: "first output", evidence: ["evidence/pass.log"], observation: "inspected" }, undefined, undefined, f.ctx)).content[0].text;
|
||||
expect(text).toContain("self-verification");
|
||||
});
|
||||
|
||||
it("attaches an existing plan without restarting completed work, and restores its noted worker session", async () => {
|
||||
const f = fixture();
|
||||
const existing = join(f.ctx.cwd, "existing.md");
|
||||
writeFileSync(existing, "# Plan\n- preferred worker model: deepseek flash\n- worker session: /tmp/attach-child.jsonl\n- [ ] goal: attached goal\n\n## Log\n- previous progress kept\n");
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Previous supervisor confirmed stopped");
|
||||
await f.command(`attach ${existing}`);
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
expect(f.entries.at(-1).data.plan).toBe(existing);
|
||||
expect(f.messages.at(-1).message.content).toContain("without restarting completed work");
|
||||
expect(f.messages.at(-1).message.content).toContain("/tmp/attach-child.jsonl");
|
||||
});
|
||||
|
||||
it("attaches directly into solo mode and reports the recorded session in status", async () => {
|
||||
const f = fixture();
|
||||
const existing = join(f.ctx.cwd, "existing.md");
|
||||
writeFileSync(existing, "# Plan\n- worker session: /tmp/attach-child.jsonl\n- [ ] goal: attached goal\n\n## Log\n");
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command(`attach ${existing} solo`);
|
||||
expect(f.entries.at(-1).data.mode).toBe("solo");
|
||||
expect(f.entries.at(-1).data.worker?.sessionFile).toBe("/tmp/attach-child.jsonl");
|
||||
await f.command("status");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("/tmp/attach-child.jsonl"), "info");
|
||||
});
|
||||
|
||||
it("rejects attaching a missing or goal-less file", async () => {
|
||||
const f = fixture();
|
||||
await f.command("attach /no/such/plan.md");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("Cannot read plan"), "error");
|
||||
const goalLess = join(f.ctx.cwd, "notes.md");
|
||||
writeFileSync(goalLess, "# notes\n");
|
||||
await f.command(`attach ${goalLess}`);
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("has no '- [ ] goal:' lines"), "warning");
|
||||
expect(f.entries).toEqual([]); // nothing saved: the session was not attached
|
||||
});
|
||||
|
||||
it("exits planning with the draft preserved and nothing implemented", async () => {
|
||||
const f = fixture(); await f.draft();
|
||||
await f.command("stop");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("A draft cannot pause"), "warning");
|
||||
const before = f.messages.length;
|
||||
await f.command("exit");
|
||||
expect(f.entries.at(-1).data.mode).toBe("chat");
|
||||
expect(readFileSync(f.path, "utf8")).toContain("first output");
|
||||
expect(f.messages.length).toBe(before); // notify only, no model turn started
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Previous supervisor confirmed stopped");
|
||||
await f.command(`attach ${f.path}`);
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
});
|
||||
|
||||
it("records the preferred worker model as a visible plan preference", async () => {
|
||||
const f = fixture(); await f.draft();
|
||||
await f.command("model deepseek flash");
|
||||
expect(readFileSync(f.path, "utf8")).toContain("- preferred worker model: deepseek flash");
|
||||
await f.command("status");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("deepseek flash"), "info");
|
||||
});
|
||||
|
||||
it.each(["solo", "attach"])("%s takeover cannot bypass confirmation or survive a lifecycle change during the menu", async kind => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "child", sessionFile: "/tmp/prior.jsonl" } });
|
||||
let answer!: (choice: string) => void;
|
||||
f.ctx.ui.select.mockImplementationOnce(() => new Promise(resolve => { answer = resolve; }));
|
||||
const takeover = f.command(kind === "solo" ? "solo" : `attach ${f.path} solo`);
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising");
|
||||
await f.command("stop");
|
||||
answer("Worker confirmed stopped"); await takeover;
|
||||
expect(f.entries.at(-1).data.mode).toBe("paused");
|
||||
expect(f.entries.at(-1).data.workerStopped).not.toBe(true);
|
||||
});
|
||||
|
||||
it("attach solo requires stop confirmation for a noted worker even in a fresh session", async () => {
|
||||
const f = fixture(); const path = join(f.ctx.cwd, "saved.md");
|
||||
writeFileSync(path, `# Plan\n- worker session: /tmp/known.jsonl\n${f.plan}`);
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Cancel");
|
||||
await f.command(`attach ${path} solo`);
|
||||
expect(f.entries).toHaveLength(0);
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped");
|
||||
await f.command(`attach ${path} solo`);
|
||||
expect(f.entries.at(-1).data).toMatchObject({ mode: "solo", workerStopped: true, worker: { sessionFile: "/tmp/known.jsonl" } });
|
||||
expect(readFileSync(path, "utf8")).toContain("worker session: /tmp/known.jsonl");
|
||||
});
|
||||
|
||||
it("retains the stopped session reference without permanently blocking another plan", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "child", sessionFile: "/tmp/prior.jsonl" } });
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo");
|
||||
const other = join(f.ctx.cwd, "another.md"); writeFileSync(other, "- [ ] goal: next\n## Log\n");
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Previous supervisor confirmed stopped");
|
||||
await f.command(`attach ${other}`);
|
||||
expect(f.entries.at(-1).data).toMatchObject({ mode: "planning", plan: other, workerStopped: true, worker: { sessionFile: "/tmp/prior.jsonl" } });
|
||||
await f.command("ready");
|
||||
f.hooks.get("tool_call")({ toolName: "subagent_resume" });
|
||||
expect(f.entries.at(-1).data.workerStopped).toBe(false);
|
||||
await f.command("solo");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("still pending"), "warning");
|
||||
});
|
||||
|
||||
it("solo closes a pending plan watcher and sends removal-only scheduler guidance", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
await f.atomicWrite(f.plan.replace("first output", "changed output"));
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo");
|
||||
expect(f.messages.at(-1).message.content).toContain('job named "goals-copy-only" bound to session "copy-only"');
|
||||
expect(f.messages.at(-1).message.content).toContain("Do not add, enable or recreate any job");
|
||||
await delay(250);
|
||||
await f.atomicWrite(f.plan.replace("first output", "solo output"));
|
||||
await delay(250);
|
||||
expect(f.changed()).toBe(0);
|
||||
});
|
||||
|
||||
it.each(["missing", "empty", "directory"])("%s plan snapshots never erase signoffs and resync retries after repair", async failure => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
writeFileSync(join(f.ctx.cwd, "proof.log"), "PASS\n");
|
||||
await f.tools.get("CompleteGoal").execute("c", { goal: "first output", evidence: ["proof.log"], observation: "Observed PASS" }, undefined, undefined, f.ctx);
|
||||
const signed = readFileSync(f.path, "utf8");
|
||||
if (failure === "empty") writeFileSync(f.path, "");
|
||||
else { rmSync(f.path); if (failure === "directory") mkdirSync(f.path); }
|
||||
await delay(250); // also exercise unavailable read after debounce has expired
|
||||
f.hooks.get("agent_end")({}, f.ctx);
|
||||
expect(f.entries.at(-1).data.signoffs["first output"]).toBeDefined();
|
||||
f.hooks.get("session_compact")();
|
||||
const unavailable = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
expect(unavailable.systemPrompt).toContain("unavailable");
|
||||
expect(unavailable.message).toBeUndefined();
|
||||
if (failure === "directory") rmSync(f.path, { recursive: true });
|
||||
writeFileSync(f.path, signed);
|
||||
const resync = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
expect(resync.message.content).toContain("Observed PASS");
|
||||
await delay(250);
|
||||
expect(f.entries.at(-1).data.signoffs["first output"]).toBeDefined();
|
||||
expect(f.changed()).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores post-completion maintenance but reviews a real requirement or manual reopening", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
writeFileSync(join(f.ctx.cwd, "proof.log"), "PASS\n");
|
||||
for (const goal of ["first output", "second output"]) await f.tools.get("CompleteGoal").execute("c", { goal, evidence: ["proof.log"], observation: "PASS" }, undefined, undefined, f.ctx);
|
||||
const signed = readFileSync(f.path, "utf8");
|
||||
await f.atomicWrite(signed.replace("## Log", " - evidence: proof.log\n## Log\n- recap: finished"));
|
||||
await delay(250);
|
||||
expect(f.changed()).toBe(0);
|
||||
await f.atomicWrite(signed.replace("## Log", "- discriminator: exact bytes and trailing newline\n## Log"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
await f.atomicWrite(signed.replace("[x] goal: first", "[ ] goal: first"));
|
||||
await waitFor(() => f.changed() === 2);
|
||||
expect(f.entries.at(-1).data.signoffs["first output"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("cancelled goals do not prevent final cleanup, and solo writes self-verification in Log", async () => {
|
||||
const f = fixture(); await f.draft();
|
||||
writeFileSync(f.path, f.plan.replace("[ ] goal: second", "[-] goal: second") + "\n## Appendix\nPreserved context\n");
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo");
|
||||
writeFileSync(join(f.ctx.cwd, "proof.log"), "PASS\n");
|
||||
const done = await f.tools.get("CompleteGoal").execute("c", { goal: "first output", evidence: ["proof.log"], observation: "Exact bytes observed" }, undefined, undefined, f.ctx);
|
||||
expect(done.content[0].text).toContain("All non-cancelled goals are reviewed");
|
||||
const text = readFileSync(f.path, "utf8");
|
||||
expect(text).toContain("Solo self-verification:");
|
||||
expect(text).not.toContain("Parent review:");
|
||||
expect(text.indexOf("Solo self-verification:")).toBeLessThan(text.indexOf("## Appendix"));
|
||||
expect(text).toContain("Preserved context");
|
||||
});
|
||||
|
||||
it("lineage-only child explicitly attaches its supplied plan, restores subtasks/context, and cannot complete", async () => {
|
||||
const f = fixture(true);
|
||||
const supplied = join(f.ctx.cwd, "supplied.md");
|
||||
const text = "- [/] goal: exact file\n - [ ] verify bytes\n## Log\n - [ ] archived task\n";
|
||||
writeFileSync(supplied, text);
|
||||
const before = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
expect(before.systemPrompt).toContain("AttachGoalPlan");
|
||||
const attach = f.tools.get("AttachGoalPlan");
|
||||
await attach.execute("a", { path: "supplied.md" }, undefined, undefined, f.ctx);
|
||||
expect(f.entries.at(-1).data.plan).toBeUndefined(); // no cwd heuristics
|
||||
await attach.execute("a", { path: supplied }, undefined, undefined, f.ctx);
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).toContain("◦ verify bytes");
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).not.toContain("archived task");
|
||||
expect(readFileSync(supplied, "utf8")).toBe(text);
|
||||
f.hooks.get("session_start")({}, f.ctx);
|
||||
expect(f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx).message.content).toContain("exact file");
|
||||
const completion = await f.tools.get("CompleteGoal").execute("c", { goal: "exact file", evidence: [], observation: "claim" }, undefined, undefined, f.ctx);
|
||||
expect(completion.content[0].text).toContain("only to the active parent");
|
||||
});
|
||||
|
||||
it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, resets on working-set edits, and never starts a turn", async mode => {
|
||||
const f = fixture(); await f.draft();
|
||||
if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); }
|
||||
else await f.command("ready");
|
||||
f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx);
|
||||
const reminders = () => f.messages.filter(m => m.message.customType === "pi-goals-upkeep");
|
||||
f.hooks.get("turn_end")({}, f.ctx); // observe initial working set
|
||||
for (let i = 0; i < 7; i++) {
|
||||
writeFileSync(f.path, f.plan + `- historical recap ${i}\n`);
|
||||
f.hooks.get("turn_end")({}, f.ctx);
|
||||
}
|
||||
expect(reminders()).toHaveLength(0);
|
||||
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).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"));
|
||||
f.hooks.get("turn_end")({}, f.ctx);
|
||||
expect(reminders()).toHaveLength(1);
|
||||
await f.command("stop");
|
||||
for (let i = 0; i < 10; i++) f.hooks.get("turn_end")({}, f.ctx);
|
||||
expect(reminders()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("late worker results invalidate a takeover menu but do not disable plan watching", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
let answer!: (choice: string) => void;
|
||||
f.ctx.ui.select.mockImplementationOnce(() => new Promise(resolve => { answer = resolve; }));
|
||||
const solo = f.command("solo");
|
||||
f.hooks.get("tool_result")({ toolName: "subagent", details: { id: "late-child", sessionFile: "/tmp/late.jsonl" } });
|
||||
answer("Worker confirmed stopped"); await solo;
|
||||
expect(f.entries.at(-1).data.mode).toBe("supervising");
|
||||
expect(f.entries.at(-1).data.workerStopped).toBe(false);
|
||||
await f.atomicWrite(f.plan.replace("first output", "new requirement"));
|
||||
await waitFor(() => f.changed() === 1);
|
||||
});
|
||||
|
||||
it("changed plan or shutdown during takeover never grants solo permission", async () => {
|
||||
const f = fixture(); await f.draft();
|
||||
f.ctx.ui.select.mockImplementationOnce(async () => { writeFileSync(f.path, f.plan.replace("first", "changed")); return "Worker confirmed stopped"; });
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("Plan changed during takeover"), "warning");
|
||||
f.ctx.ui.select.mockImplementationOnce(async () => { f.shutdown(); return "Worker confirmed stopped"; });
|
||||
await f.command("solo");
|
||||
expect(f.entries.at(-1).data.mode).toBe("planning");
|
||||
});
|
||||
|
||||
it("requires explicit supervisor ownership confirmation when attaching an existing plan", async () => {
|
||||
const f = fixture(); const path = join(f.ctx.cwd, "shared.md");
|
||||
writeFileSync(path, f.plan);
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Cancel");
|
||||
await f.command(`attach ${path}`);
|
||||
expect(f.entries).toHaveLength(0);
|
||||
f.ctx.ui.select.mockResolvedValueOnce("Previous supervisor confirmed stopped");
|
||||
await f.command(`attach ${path}`);
|
||||
expect(f.entries.at(-1).data).toMatchObject({ mode: "planning", plan: path });
|
||||
});
|
||||
|
||||
it("does not approve cancelled goals or display current completion for an unavailable plan", async () => {
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
writeFileSync(f.path, "- [-] goal: cancelled output\n## Log\n");
|
||||
writeFileSync(join(f.ctx.cwd, "evidence.log"), "verified\n");
|
||||
const reply = await f.tools.get("CompleteGoal").execute("t", { goal: "cancelled output", evidence: ["evidence.log"], observation: "read" }, undefined, undefined, f.ctx);
|
||||
expect(reply.content[0].text).toContain("no sign-off recorded");
|
||||
expect(readFileSync(f.path, "utf8")).toContain("[-]");
|
||||
rmSync(f.path);
|
||||
f.hooks.get("agent_end")({}, f.ctx);
|
||||
expect(f.ctx.ui.setWidget).toHaveBeenLastCalledWith("goals", [expect.stringContaining("unavailable")]);
|
||||
});
|
||||
|
||||
it("uses scheduler storage for ownership and the real public user controls", () => {
|
||||
const prompt = scheduleCheckIn("session-1", "/plan.md");
|
||||
expect(prompt).toContain(".pi/schedule-prompts.json");
|
||||
expect(prompt).toContain("tool text does not expose binding");
|
||||
expect(prompt).toContain("Never use cleanup");
|
||||
expect(prompt).toContain("deletes disabled jobs");
|
||||
expect(prompt).toContain("schedule_prompt update");
|
||||
expect(prompt).not.toContain("with /schedule-prompts");
|
||||
});
|
||||
|
||||
it("keeps interactive workers open and supplies the supervisor identity for Intercom reports", async () => {
|
||||
const agent = readFileSync(new URL("../prototype/agents/goals-worker.md", import.meta.url), "utf8");
|
||||
expect(agent).toContain("auto-exit: false");
|
||||
const f = fixture(); await f.draft(); await f.command("ready");
|
||||
expect(f.messages.at(-1).message.content).toContain("supervisor Intercom session copy-only");
|
||||
const role = f.hooks.get("before_agent_start")({ systemPrompt: "base" }, f.ctx).systemPrompt;
|
||||
expect(role).toContain("your Intercom session ID copy-only");
|
||||
expect(role).toContain("stop workers before /reload");
|
||||
expect(role).not.toContain("Reports arrive automatically");
|
||||
});
|
||||
|
||||
@@ -85,7 +85,8 @@ describe("RPC review flow", () => {
|
||||
], {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
// This fixture tests the MAIN extension, even when vitest runs in a delegated session.
|
||||
...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}`,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user