@gotgenes/pi-subagents 17.0.0 → 17.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/docs/architecture/architecture.md +222 -159
- package/docs/architecture/history/phase-18-reconsider-ui.md +166 -0
- package/docs/decisions/0004-reconsider-ui-direction.md +220 -0
- package/docs/plans/0426-consolidate-residual-test-clone-families.md +235 -0
- package/docs/plans/0427-reconsider-ui-direction.md +212 -0
- package/docs/plans/0446-spike-session-navigation-entry-criteria.md +171 -0
- package/docs/plans/0447-extract-subagents-settings-command.md +220 -0
- package/docs/plans/0448-block-disabled-agent-spawn.md +207 -0
- package/docs/retro/0425-reconcile-subagent-events-contract.md +55 -0
- package/docs/retro/0426-consolidate-residual-test-clone-families.md +100 -0
- package/docs/retro/0427-reconsider-ui-direction.md +102 -0
- package/docs/retro/0446-spike-session-navigation-entry-criteria.md +99 -0
- package/docs/retro/0447-extract-subagents-settings-command.md +46 -0
- package/docs/retro/0448-block-disabled-agent-spawn.md +75 -0
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/tools/helpers.ts +3 -2
- package/src/tools/spawn-config.ts +6 -0
- package/src/ui/subagents-settings.ts +83 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 427
|
|
3
|
+
issue_title: "pi-subagents: reconsider the UI direction from first principles (ADR)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #427 — pi-subagents: reconsider the UI direction from first principles (ADR)
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-18T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned the Phase 18 Step 8 decision-only ADR for the pi-subagents UI direction.
|
|
13
|
+
Two `ask_user` rounds with the operator (their own issue) settled a per-component decision and surfaced a key SDK finding — Pi's `switchSession(sessionPath)` — that reshapes the conversation-viewer direction.
|
|
14
|
+
The plan writes `docs/decisions/0004-reconsider-ui-direction.md` plus an architecture-doc update; no `src/`/`test/` changes (implementation deferred to a separately-planned Phase 19).
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- **Decision-only ADR → `/build-plan`, not `/tdd-plan`.**
|
|
19
|
+
The operator chose to record decisions and defer all code to Phase 19, so the plan has a docs-only Build Order, no test cycles.
|
|
20
|
+
- **Per-component decisions recorded:**
|
|
21
|
+
(A) foreground widget shrinks to background-agents-only;
|
|
22
|
+
(B) conversation viewer replaced by native session navigation (remove the bespoke `ConversationViewer`);
|
|
23
|
+
(C) `/agents` menu dissolved — **remove** both agent-management surfaces outright (creation wizard and agent-types config editor; managing definitions belongs in an editor/IDE or a Pi agent, not the menu), re-home running-agent visibility onto the widget + session navigation, extract settings to a focused `/subagents:settings` command;
|
|
24
|
+
(D) distribution = keep surviving UI in-core (substitutable, _not_ extracted to `@gotgenes/pi-subagents-ui`).
|
|
25
|
+
- **Key SDK finding — `switchSession`.**
|
|
26
|
+
`@earendil-works/pi-coding-agent@0.79.1` exposes `ExtensionActions.switchSession(sessionPath, { withSession })`.
|
|
27
|
+
It is a _full active-session takeover_ (fires `session_before_switch`/`session_shutdown`, invalidates the current context), and the switched-to session is interactive (`ReplacedSessionContext.sendUserMessage`).
|
|
28
|
+
A read-only alternative exists: `loadEntriesFromFile`/`parseSessionEntries` render a transcript without switching.
|
|
29
|
+
These tensions are recorded as Phase 19 spike gates rather than pretend-resolved — the ADR commits to the _direction_ (native session machinery over a bespoke renderer), not the _mechanism_.
|
|
30
|
+
- **Operator-raised open questions (now Phase 19 entry criteria):** root-continuity during a session switch, view-only vs interactive, parallel-agent navigation gesture, settings command namespace, and confirming the creation-wizard's value is covered by "generate via a Pi agent" before deleting it.
|
|
31
|
+
- **Release:** ship independently — Phase 18 carries no `Release:` batch tag; this issue completes the phase.
|
|
32
|
+
- **Numbering:** plan `0427`, ADR `0004` (next free in `docs/decisions/`).
|
|
33
|
+
|
|
34
|
+
## Stage: Implementation — Build (2026-06-18T20:05:00Z)
|
|
35
|
+
|
|
36
|
+
### Session summary
|
|
37
|
+
|
|
38
|
+
Executed the decision-only ADR plan in two docs steps: wrote `docs/decisions/0004-reconsider-ui-direction.md` (per-component decisions A–D plus Phase 19 entry criteria) and updated `docs/architecture/architecture.md` (Step 8 + phase row marked `✅` complete, `S8` Mermaid node `✅`, ADR-0004 Landed line gateway-ing Phase 19).
|
|
39
|
+
No `src/`/`test/`/`.ts` files were touched, so the type-check and suite were correctly skipped; `pnpm run lint` is green.
|
|
40
|
+
|
|
41
|
+
### Observations
|
|
42
|
+
|
|
43
|
+
- **Decision-only ADR held to scope:** four docs files total (ADR, arch doc, plan, retro); zero runtime change, matching the plan's Non-Goals.
|
|
44
|
+
- **Pre-completion reviewer: PASS.**
|
|
45
|
+
One non-blocking WARN — architecture design-principle #5 still read "UI extraction is deferred … first candidate for extraction," which ADR-0004's Decision D now contradicts.
|
|
46
|
+
- **Reviewer warning addressed in-session:** rewrote principle #5 to "UI is an in-core, substitutable consumer" pointing at ADR-0004 (commit `1c445ed4`), rather than deferring it to Phase 19 — it lived in the same doc and directly conflicted with the just-landed ADR.
|
|
47
|
+
- **Lint gotcha:** the relative ADR link from `docs/architecture/` needs `../decisions/…` (the Step 8 Landed line already had it); an initial `decisions/…` tripped `MD057` and was fixed by amend.
|
|
48
|
+
Also note `pnpm … lint | tail -N` masks the pipeline exit status — check `PIPESTATUS`/run lint unpiped to gate `&&` chains.
|
|
49
|
+
- **Commit count:** 4 build/doc commits for this stage (`17b0546a`, `7b1d9316`, `1c445ed4` for the ADR + arch doc; planning commits `12e7814a`/`e4895548`/`f1e65a14` predate this stage).
|
|
50
|
+
|
|
51
|
+
## Stage: Final Retrospective (2026-06-18T21:00:00Z)
|
|
52
|
+
|
|
53
|
+
### Session summary
|
|
54
|
+
|
|
55
|
+
Shipped issue #427 — a decision-only ADR (`docs/decisions/0004-reconsider-ui-direction.md`) completing Phase 18 of the `pi-subagents` roadmap — across planning, build, and ship stages in one session.
|
|
56
|
+
The whole arc held to docs-only scope: four docs files, zero runtime change, all `docs:` commits, so release-please correctly batched (no version cut).
|
|
57
|
+
CI passed; #427 closed with a per-component decision summary; siblings #425/#426/#434 were already closed.
|
|
58
|
+
|
|
59
|
+
### Observations
|
|
60
|
+
|
|
61
|
+
#### What went well
|
|
62
|
+
|
|
63
|
+
- **Verified the SDK surface before recording it (novel).**
|
|
64
|
+
Rather than build the ADR on an assumed `switchSession` API, I read `@earendil-works/pi-coding-agent@0.79.1` `.d.ts` files directly in `node_modules` and confirmed the real contract — full active-session takeover, interactive `ReplacedSessionContext`, plus the `loadEntriesFromFile` read-only alternative.
|
|
65
|
+
This generalizes the AGENTS.md "verify the remediation exists in the real surface" rule (written for breaking-change migration notes) to an ADR's SDK claims, and it directly shaped Decision B's "direction not mechanism" framing.
|
|
66
|
+
- **Decision-only scope discipline.**
|
|
67
|
+
Held to four docs files matching the plan's Non-Goals; no `src/`/`test/` drift despite the ADR describing future code.
|
|
68
|
+
- **Pre-completion reviewer earned its keep on a docs-only change.**
|
|
69
|
+
It caught a stale design-principle (#5) that directly contradicted the just-landed ADR — a real inconsistency, not a code defect — showing the reviewer adds value beyond test/lint gates.
|
|
70
|
+
- **`ask_user` used well for a decision-heavy issue.**
|
|
71
|
+
Two structured rounds (per-component fates, then the `switchSession`-informed follow-up) let the operator drive the ADR's content; the second round was correctly gated on the first's SDK finding.
|
|
72
|
+
|
|
73
|
+
#### What caused friction (agent side)
|
|
74
|
+
|
|
75
|
+
- `other` (shell footgun) — gated a commit on a lint check piped through `tail`: `pnpm … lint 2>&1 | tail -3 && git add … && git commit`.
|
|
76
|
+
A pipeline's exit status is the last command (`tail`), so the lint failure was masked and a broken-link commit (`89ad57a3`, `MD057`) landed before I noticed.
|
|
77
|
+
Impact: one extra fix + `git commit --amend`; self-caught on the next unpiped lint run.
|
|
78
|
+
No bad push (the amend preceded `/ship-issue`).
|
|
79
|
+
- `other` (doc-link slip) — wrote a relative ADR link as `decisions/0004-…` from `docs/architecture/`, when the same session's earlier Step 8 Landed line already used the correct `../decisions/…`.
|
|
80
|
+
Impact: the `MD057` failure above; had the correct pattern in hand and didn't reuse it.
|
|
81
|
+
- `other` (malformed tool call) — one `Edit` call included a stray `newText_unused` property and was rejected; retried cleanly.
|
|
82
|
+
Impact: one wasted call, self-caught.
|
|
83
|
+
|
|
84
|
+
#### What caused friction (user side)
|
|
85
|
+
|
|
86
|
+
- None blocking.
|
|
87
|
+
The operator firmed the `/agents` decision (remove create+edit surfaces) one turn after the plan was first committed, costing a small follow-up commit (`f1e65a14`).
|
|
88
|
+
Opportunity, not criticism: the `ask_user` `/agents` option labeled "shrink — keep config management" may have under-signaled that outright removal was on the table; a crisper "remove agent-management surfaces" option could have surfaced the firm decision in the first round.
|
|
89
|
+
|
|
90
|
+
### Diagnostic details
|
|
91
|
+
|
|
92
|
+
- **Model-performance correlation** — one subagent dispatch (`pre-completion-reviewer`) for judgment-heavy review; appropriate (per AGENTS.md its `model:` frontmatter must be a `provider/id` alias to actually run).
|
|
93
|
+
The main session's `model_change` log shows frequent toggling (`glm-5.2`, `opus-4-8`, `sonnet-4-6`, `deepseek-v4-flash`) but turns can't be cleanly attributed and nothing looks mismatched — not actionable.
|
|
94
|
+
- **Feedback-loop gap analysis** — `pnpm run lint` ran incrementally after each build step (good), not just at the end; baseline `check`+`lint` ran before any change.
|
|
95
|
+
The one gap was process, not timing: the `tail`-masked lint let a failure slip a commit (above).
|
|
96
|
+
- **Escalation-delay / unused-tool** — no rabbit-holes; longest same-error streak was 1–2 calls (the `MD057` fix).
|
|
97
|
+
Direct `node_modules` `.d.ts` inspection substituted well for `web_search`/`code_search` on the SDK contract — no missing-context gap.
|
|
98
|
+
|
|
99
|
+
### Changes made
|
|
100
|
+
|
|
101
|
+
1. `AGENTS.md` § Commits — added a two-line rule: don't gate a commit (or any `&&` step) on a check piped through `tail`/`head`, since the pipeline's exit status is the filter's and a failed `pnpm run lint`/`check` is masked; run the check unpiped or test `${PIPESTATUS[0]}`.
|
|
102
|
+
2. `packages/pi-subagents/docs/retro/0427-reconsider-ui-direction.md` — appended this Final Retrospective stage entry.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 446
|
|
3
|
+
issue_title: "pi-subagents: spike — resolve ADR-0004 session-navigation entry criteria"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #446 — pi-subagents: spike — resolve ADR-0004 session-navigation entry criteria
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-20T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned the Phase 19 Step 1 spike that answers the four ADR-0004 session-navigation entry criteria and records them as an ADR-0004 addendum.
|
|
13
|
+
Confirmed the release is independent and that the only committed artifact is the addendum.
|
|
14
|
+
The plan lives at `packages/pi-subagents/docs/plans/0446-spike-session-navigation-entry-criteria.md`; next stage is `/build-plan` (docs/spike deliverable, no committed TDD cycles).
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Operator owns the issue (`gotgenes` == gh user), so the "Proposed change" is the working hypothesis.
|
|
19
|
+
Used `ask_user` once to resolve two method ambiguities: spike method = **automated observed test (vitest)**, committed artifact = **ADR addendum only** (the vitest harness is throwaway, discarded).
|
|
20
|
+
- Gathered the SDK evidence up front so the addendum's expected answers are grounded: `switchSession` is a full active-session takeover that tears down the current runtime via `session_shutdown` (so it threatens the root's in-flight turn); `ReplacedSessionContext` exposes `sendUserMessage` (switch makes the child interactive); `loadEntriesFromFile`/`parseSessionEntries` read entries without switching; `Subagent.outputFile` already exposes the child JSONL path; sibling commands use flat hyphenated names (`agents`, `colgrep-reindex`, `permission-system`).
|
|
21
|
+
- Expected recommendations the spike will confirm: read-only `loadEntriesFromFile` transcript (resolves root-continuity by construction), command-first parallel-agent selection (widget gesture deferred), and `/subagents-settings` (reject the ADR's tentative `/subagents:settings`).
|
|
22
|
+
- `setBeforeSessionInvalidate` is a **host** runtime seam (`agent-session-runtime`/`interactive-mode`), not on the extension command context — noted in Background so Step 4 does not assume the extension can call it.
|
|
23
|
+
- No production code changes and no invariants at risk; the read-only path was chosen partly to keep transcript rendering out of core (preserving the Phase 18 spine invariants from issues #422–#425).
|
|
24
|
+
|
|
25
|
+
## Stage: Implementation — Build (2026-06-20T10:00:00Z)
|
|
26
|
+
|
|
27
|
+
### Session summary
|
|
28
|
+
|
|
29
|
+
Executed the spike: ran a throwaway vitest harness against a real 43-entry child session JSONL, confirmed the read-only transcript path, then wrote the ADR-0004 addendum answering all four entry criteria.
|
|
30
|
+
Discarded the harness (operator decision: addendum only) and folded the architecture.md doc-sync into this build rather than deferring it.
|
|
31
|
+
Four `docs:` commits; pre-completion reviewer returned WARN, whose findings were then resolved.
|
|
32
|
+
|
|
33
|
+
### Observations
|
|
34
|
+
|
|
35
|
+
- **Key divergence from the plan (Finding 0):** the plan's Design Overview assumed `loadEntriesFromFile(path)` would be the read mechanism, but it is **not part of the package's public surface** — it lives in the deep `core/session-manager` module (marked `/** Exported for testing */`) and the public barrel (`src/index.ts` → `dist/index.{d.ts,js}`) re-exports only a curated subset that includes `parseSessionEntries` but not `loadEntriesFromFile`; the `exports` map exposes only `"."`, so the deep import is unsupported too.
|
|
36
|
+
This is **not** a types/runtime mismatch — both barrels agree, and `tsc` rejects the import with `TS2305`.
|
|
37
|
+
My first harness reached a runtime `is not a function` only because Vitest/esbuild strips types without type-checking; `pnpm run check` (`tsc`) would have caught it at compile time.
|
|
38
|
+
My earlier "types/runtime mismatch" framing in the addendum/architecture was wrong and was corrected in a follow-up `docs:` commit.
|
|
39
|
+
Viable path: `parseSessionEntries(readFileSync(outputFile, "utf8"))` (`parseSessionEntries` is public).
|
|
40
|
+
- **Upgrade check (operator question):** verified the omission is **not** version-specific — the latest `0.79.8` barrel omits it identically to the pinned `0.79.1`, so an SDK bump does not surface `loadEntriesFromFile`.
|
|
41
|
+
No upgrade pursued (out of scope for a docs-only spike); noted the routine `0.79.1` → `0.79.8` freshness gap as a separate, unrelated item.
|
|
42
|
+
- **Doc-sync landed now, not deferred:** the reviewer flagged architecture.md line 997 ("Mechanism (confirmed by Step 1): `switchSession` … or `loadEntriesFromFile`") as actively contradicting the spike.
|
|
43
|
+
Since the spike now exists, I marked Step 1 ✅ (heading + Mermaid node `S1`), corrected the Phase 18 summary line, and rewrote the Step 4 mechanism line to `parseSessionEntries(readFileSync(...))` — closing the WARN.
|
|
44
|
+
- **Pre-completion reviewer: WARN** (no FAILs) — three architecture.md staleness findings, all addressed in the final `docs:` commit (`74e2374f`).
|
|
45
|
+
No `src/`/`test/` changes; `pnpm run check` + `pnpm run lint` + `pnpm fallow dead-code` all green at baseline and after.
|
|
46
|
+
- Release recommendation unchanged: **ship independently** (`Release: independent`).
|
|
47
|
+
|
|
48
|
+
## Stage: Final Retrospective (2026-06-20T18:00:00Z)
|
|
49
|
+
|
|
50
|
+
### Session summary
|
|
51
|
+
|
|
52
|
+
Planned, built, and shipped the Phase 19 Step 1 spike across three workflow stages, producing the ADR-0004 addendum that answers all four session-navigation entry criteria and unblocks #445.
|
|
53
|
+
The spike did its job — it rejected ADR-0004's literally-named `loadEntriesFromFile` mechanism before Step 4 coded against it — but the build stage committed an incorrect "types/runtime mismatch" characterization that took two user questions and a correction commit to fix.
|
|
54
|
+
Eight `docs:` commits landed; no release (all `docs:`, auto-batched).
|
|
55
|
+
|
|
56
|
+
### Observations
|
|
57
|
+
|
|
58
|
+
#### What went well
|
|
59
|
+
|
|
60
|
+
- **The spike paid for itself, then over-delivered.**
|
|
61
|
+
It caught that ADR-0004's named candidate (`loadEntriesFromFile`) is not in the SDK's public surface — a finding about the *implementation*, not the harness — before Step 4 started.
|
|
62
|
+
The operator's three follow-up questions (upgrade?
|
|
63
|
+
describe-the-mismatch?
|
|
64
|
+
spike-vs-implementation impact?) then drove the addendum from a thin "use `parseSessionEntries`" note into a complete, link-backed render pipeline (Finding 1: `parseSessionEntries` → `buildSessionContext` → Pi's public entry components / `serializeConversation`).
|
|
65
|
+
Step 4 now starts from a verified, public-API-only design.
|
|
66
|
+
- **Clean, non-defensive self-correction.**
|
|
67
|
+
When the operator asked me to substantiate the claim with links (turn 74), running the actual `tsc` probe surfaced my own error; I corrected all three artifacts (addendum, `architecture.md`, retro) in commit `112c4254` without hedging.
|
|
68
|
+
"Ask the agent to cite/substantiate a claim" proved a high-leverage verification gesture.
|
|
69
|
+
|
|
70
|
+
#### What caused friction (agent side)
|
|
71
|
+
|
|
72
|
+
- `wrong-abstraction` (compounded by `missing-context`) — I diagnosed a question about the SDK's *export surface* from a *runtime* symptom.
|
|
73
|
+
The throwaway vitest harness threw `loadEntriesFromFile is not a function`, and I leapt to the dramatic reading ("declared in `.d.ts` but absent at runtime — a types/runtime mismatch") instead of running `tsc`, which disambiguates instantly (`TS2305: has no exported member`).
|
|
74
|
+
The symbol is simply not in the public barrel — types and runtime agree.
|
|
75
|
+
Impact: an incorrect technical claim shipped into three committed artifacts (addendum `7c505b78`, `architecture.md`, the build retro note) and required a 3-file correction commit (`112c4254`) plus two operator questions to surface and fix.
|
|
76
|
+
- `instruction-violation` (user-caught) — the `testing` skill already states "Vitest uses esbuild and does not typecheck; run `pnpm run check`."
|
|
77
|
+
I never ran `tsc` against the harness or the export claim during the build.
|
|
78
|
+
Two reasons it slipped: (1) the skill was not loaded — `/build-plan` only loads `testing` "if the plan involves test changes or TDD steps," and a docs-only spike using a throwaway harness does not obviously match; (2) the existing rule is framed for "type-only changes," not for *claims/findings about what a module exports*, so it would not clearly have fired even if loaded.
|
|
79
|
+
- `other` (tooling friction, minor) — getting the `tsc` probe to resolve the package took four attempts (turns 77–80: `npx` blocked → `pnpm exec` → files-on-cmdline tsconfig error → probe-inside-package).
|
|
80
|
+
Impact: added friction, no rework; not a conceptual rabbit-hole.
|
|
81
|
+
|
|
82
|
+
#### What caused friction (user side)
|
|
83
|
+
|
|
84
|
+
- None material — the operator's three interventions were strategic probes (dependency-freshness, claim substantiation, spike-vs-implementation scope) that each improved the artifact.
|
|
85
|
+
The only "earlier" opportunity is an agent-side gap: I should have self-applied the `tsc` check so the operator did not need to ask "describe the mismatch" to trigger verification.
|
|
86
|
+
|
|
87
|
+
### Diagnostic details
|
|
88
|
+
|
|
89
|
+
- **Model-performance correlation** — Planning + Build + the correction work all ran on `anthropic/claude-opus-4-8` (turns 2–104); the mischaracterization therefore occurred on the *strong* model, so the fix is process (run `tsc` on export claims), not model selection.
|
|
90
|
+
The Ship stage (turns 106–122) ran on `opencode-go/deepseek-v4-flash` — appropriate cost-matching for a mechanical git/CI/close procedure, which it executed cleanly.
|
|
91
|
+
A transient `anthropic/claude-sonnet-4-6` `model_change` carried no assistant turn (never ran).
|
|
92
|
+
- **Escalation-delay tracking** — no rabbit-hole exceeded five consecutive tool calls on one error; the runtime `is not a function` was resolved in ~2 calls (the issue was the *interpretation*, not a stuck loop).
|
|
93
|
+
- **Unused-tool detection** — for the mischaracterization, `tsc` / `pnpm run check` was available and routine but applied only at baseline (turn 29) and not again until the operator forced it (turns 77–80); it was never run against the harness or the claim.
|
|
94
|
+
- **Feedback-loop gap analysis** — `pnpm run lint` ran after every doc edit (good incremental hygiene), but the *type-check* loop was skipped for precisely the finding that needed it — the harness ran under vitest (no typecheck) and `tsc` was not run on the export claim before committing it.
|
|
95
|
+
|
|
96
|
+
### Changes made
|
|
97
|
+
|
|
98
|
+
1. Extended the `testing` skill's `## Type checking` section to cover verifying claims about module exports with `tsc`, not runtime symptoms — `Refs #446`.
|
|
99
|
+
File: `.pi/skills/testing/SKILL.md`.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 447
|
|
3
|
+
issue_title: "pi-subagents: extract subagent settings to a focused /subagents-settings command"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #447 — pi-subagents: extract subagent settings to a focused /subagents-settings command
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-20T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced a numbered TDD plan for Phase 19 Step 2: a purely additive extraction of `AgentsMenuHandler.showSettings` into a standalone `SubagentsSettingsHandler` registered as the `/subagents-settings` command.
|
|
13
|
+
Confirmed the command name against the closed spike (#446) and its ADR-0004 addendum (Criterion 4), and verified `SettingsManager` already structurally satisfies the new narrow manager interface so it can be passed directly.
|
|
14
|
+
The plan ships independently (roadmap `Release: independent`).
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- The extraction is a faithful verbatim lift — `showSettings` already had zero coupling beyond `this.settings` and `ui`, so the design-review checklist came back clean (100% field usage on both new narrow interfaces, no LoD/output-arg smells).
|
|
19
|
+
Classified as a genuine collaborator extraction, not procedure-splitting.
|
|
20
|
+
- Declared two narrow interfaces owned by the new module: `SubagentsSettingsManager` (shape-identical to the doomed `AgentMenuSettings` but with no import from `agent-menu.ts`) and `SubagentsSettingsUI` (drops `confirm`/`editor`/`custom` from `MenuUI` — ISP).
|
|
21
|
+
- Strictly additive: `agent-menu.ts` is untouched, and its settings tests stay as-is because the in-menu path keeps shipping until Step 5 (#442) deletes the file.
|
|
22
|
+
Removing them now would drop coverage of a live surface.
|
|
23
|
+
- Preserved the single-selection-then-return semantics of `showSettings` verbatim (no re-show loop) — flagged a settings re-show loop as a deferred UX open question.
|
|
24
|
+
- Two small TDD steps (handler+tests, then `index.ts` registration); noted they may fold into one commit since the export and its sole call site are tiny, with `pnpm run check` required right after the wiring step.
|
|
25
|
+
- No third-party `ask_user` gate needed — issue filed by the operator (`gotgenes`), direction fixed by ADR-0004, design unambiguous.
|
|
26
|
+
|
|
27
|
+
## Stage: Implementation — TDD (2026-06-20T14:30:00Z)
|
|
28
|
+
|
|
29
|
+
### Session summary
|
|
30
|
+
|
|
31
|
+
Implemented Phase 19 Step 2 in two TDD cycles plus a doc-sync commit: added `SubagentsSettingsHandler` (with narrow `SubagentsSettingsManager` and `SubagentsSettingsUI` interfaces) lifted verbatim from `AgentsMenuHandler.showSettings`, then registered the `/subagents-settings` command in `index.ts`.
|
|
32
|
+
Test count went 1051 → 1062 (+11, one new file `test/ui/subagents-settings.test.ts`).
|
|
33
|
+
Full suite, `tsc`, root lint, and `fallow dead-code` all green.
|
|
34
|
+
|
|
35
|
+
### Observations
|
|
36
|
+
|
|
37
|
+
- The lift was clean: `SettingsManager` already exposed all six members of the new narrow manager interface, so the `index.ts` wiring passed `settings` directly with zero adapter — `pnpm run check` confirmed structural satisfaction.
|
|
38
|
+
- `makeMenuUI` from `ui-stubs.ts` was reused as the UI stub without modification — its wider shape (`confirm`/`editor`/`custom`) structurally satisfies the narrower `SubagentsSettingsUI`, so no new helper was needed.
|
|
39
|
+
- Kept the two cycles as separate commits rather than folding them; the export and its sole call site were small enough that either would have been valid.
|
|
40
|
+
- Deviation from plan: flipped `✅` on the architecture Step 2 heading and its Mermaid node now (the plan had flagged this as a ship-time open question).
|
|
41
|
+
Applied per the `/tdd-plan` template's roadmap-completion rule; verified the diagram still renders via `mmdc`.
|
|
42
|
+
The phase status row was left unchanged (only 1 of 7 steps done).
|
|
43
|
+
- `agent-menu.ts` was not touched (verified by the reviewer via `git log`), preserving the additive-only constraint.
|
|
44
|
+
- Pre-completion reviewer: WARN.
|
|
45
|
+
Reviewer warnings: one non-blocking finding — `.pi/skills/package-pi-subagents/SKILL.md` records `ui/` as 10 modules (now 11); the plan intentionally deferred this coarse-summary update to a later Phase 19 doc-sync.
|
|
46
|
+
No FAILs; all deterministic checks PASS, verbatim-lift fidelity and ISP of both narrow interfaces confirmed.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 448
|
|
3
|
+
issue_title: "`enabled: false` does not prevent explicitly spawning disabled agents"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #448 — `enabled: false` does not prevent explicitly spawning disabled agents
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-20T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned the fix for a third-party bug report: `enabled: false` agent overrides are hidden from the available-types list but still spawnable when named explicitly via `subagent_type`.
|
|
13
|
+
The plan adds a disabled-type gate in `resolveSpawnConfig` (returning an explicit error) and a `enabled` filter in `buildTypeListText`, both localized changes with no new collaborators or interface changes.
|
|
14
|
+
|
|
15
|
+
### Observations
|
|
16
|
+
|
|
17
|
+
- Issue author (`nickadminroot`) is not the operator (`gotgenes`), so I ran the `ask-user` direction gate.
|
|
18
|
+
Operator confirmed: fix it, **return an explicit error** (`Agent type "<Name>" is disabled`) rather than the lenient fall-back-to-`general-purpose` alternative, and **include both fixes** (spawn path + tool-description list).
|
|
19
|
+
- Root cause is `resolveType` → `resolveKey` ignoring `enabled`; the registry already has `isValidType` (checks `enabled`) but it was unused on the spawn path.
|
|
20
|
+
The gate reuses `isValidType`, leaving `resolveType` / `resolveAgentConfig` untouched so UI consumers that intentionally resolve disabled configs keep working.
|
|
21
|
+
- Rejected changing `resolveType` or `resolveAgentConfig` directly — `agent-config-editor.ts` and `agent-menu.ts` rely on resolving disabled agents to display/edit/re-enable them.
|
|
22
|
+
- For the tool-description fix, chose to filter inside `buildTypeListText` rather than re-define `getDefaultAgentNames` / `getUserAgentNames` semantics; those two methods have `buildTypeListText` as their sole consumer (verified by grep), but keeping their meaning intact is cleaner.
|
|
23
|
+
- Classified as non-breaking `fix:` — the change aligns code with the documented README/registry contract; explicit spawning of a disabled agent was undocumented buggy behavior.
|
|
24
|
+
- Not in any architecture roadmap step (no `#448` reference in `docs/`), so **ship independently**.
|
|
25
|
+
|
|
26
|
+
## Stage: Implementation — TDD (2026-06-20T12:40:00Z)
|
|
27
|
+
|
|
28
|
+
### Session summary
|
|
29
|
+
|
|
30
|
+
Completed 2 TDD cycles and all post-step verification gates.
|
|
31
|
+
Step 1 added a 3-line enabled-type gate in `resolveSpawnConfig` (reusing `isValidType`) and 2 new test cases in `test/tools/spawn-config.test.ts`.
|
|
32
|
+
Step 2 added an `isEnabled` predicate filter in `buildTypeListText` and 2 new test cases in `test/tools/helpers.test.ts`.
|
|
33
|
+
Test count delta: 1047 → 1051 (+4).
|
|
34
|
+
|
|
35
|
+
### Observations
|
|
36
|
+
|
|
37
|
+
- No deviations from the plan.
|
|
38
|
+
Both changes were as small as designed: 3 lines in `spawn-config.ts`, 2 lines in `helpers.ts`.
|
|
39
|
+
- Extended the `makeRegistry` stub's `resolve` type in `test/tools/helpers.test.ts` to include an optional `enabled` field, so the `isEnabled` predicate could be exercised without touching production code.
|
|
40
|
+
- The `makeAgentConfig` helper was added to `test/tools/spawn-config.test.ts` (mirroring the pattern in `test/config/agent-types.test.ts`) rather than importing from a shared fixture, since the existing spawn-config test fixture infrastructure didn't need modification.
|
|
41
|
+
- All three plan-enumerated cross-step invariants held green throughout: `resolveAgentConfig` disabled-config behavior, unknown-type fallback, and `getAllTypes` disabled-agent listing.
|
|
42
|
+
- Pre-completion reviewer: **PASS** — all deterministic checks, code design, test artifacts, and cross-step invariants clean.
|
|
43
|
+
|
|
44
|
+
## Stage: Land — worktree (2026-06-20T17:57:54Z)
|
|
45
|
+
|
|
46
|
+
### Session summary
|
|
47
|
+
|
|
48
|
+
Ran `/land-worktree 448` from the root checkout: fast-forward merged the peer branch `issue-448-enabled-false-does-not-prevent-explicitl` onto linear `main`, pushed, verified CI green, closed issue #448, merged the release-please PR, and tore down the worktree.
|
|
49
|
+
Released `pi-subagents-v17.0.1` (the plan marked `Release: ship independently`).
|
|
50
|
+
|
|
51
|
+
### Observations
|
|
52
|
+
|
|
53
|
+
#### What went well
|
|
54
|
+
|
|
55
|
+
- The new `/land-worktree` flow (added in `7cbfea46`) ran cleanly end-to-end on a real issue: ff-merge → push → CI → `issue_close` → release → teardown, with no blockers or rework.
|
|
56
|
+
- The release-please PR returned `MERGEABLE` / `UNSTABLE` with an empty `statusCheckRollup` — the documented `GITHUB_TOKEN` no-checks case.
|
|
57
|
+
Falling back from `release_pr_merge` to `gh pr merge 449 --rebase` followed by `git pull --ff-only` worked exactly as the prompt anticipates.
|
|
58
|
+
|
|
59
|
+
#### What caused friction (agent side)
|
|
60
|
+
|
|
61
|
+
- `instruction-violation` (self-identified, in retro) — the prompt's step 6.2 says to check the **full** release-PR body for which packages it bumps **before** merging.
|
|
62
|
+
I requested `body` in `gh pr view 449 --json body,...` but the `--jq` filter only printed `title`/`state`/`mergeStateStatus`/`statusCheckRollup`, so the body was never actually inspected.
|
|
63
|
+
I learned the bumped package (`pi-subagents`) only **after** merging, from the `git pull` output.
|
|
64
|
+
Impact: none this time (a single expected package bumped), but skipping the pre-merge body check means an unexpected sibling-package bump would slip through unnoticed.
|
|
65
|
+
|
|
66
|
+
### Diagnostic details
|
|
67
|
+
|
|
68
|
+
- **Model-performance correlation** — no subagents dispatched; the land flow ran entirely on the parent session model.
|
|
69
|
+
No mismatch.
|
|
70
|
+
- **Escalation-delay tracking** — no rabbit-holes; no error retried more than once.
|
|
71
|
+
- **Feedback-loop gap analysis** — no code changes in the land session, so CI (run on the pushed SHA, conclusion `success`) was the appropriate and only verification gate.
|
|
72
|
+
|
|
73
|
+
### Changes made
|
|
74
|
+
|
|
75
|
+
1. `.pi/prompts/land-worktree.md` — added a one-line nudge to step 6.2 to print the release-PR body explicitly with `gh pr view <N> --json body -q .body`, since a `--jq` that drops `body` skips the package-bump check silently.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { SteerTool } from "#src/tools/steer-tool";
|
|
|
47
47
|
import { FsAgentFileOps } from "#src/ui/agent-file-ops";
|
|
48
48
|
import { AgentsMenuHandler } from "#src/ui/agent-menu";
|
|
49
49
|
import { AgentWidget } from "#src/ui/agent-widget";
|
|
50
|
+
import { SubagentsSettingsHandler } from "#src/ui/subagents-settings";
|
|
50
51
|
|
|
51
52
|
export default function (pi: ExtensionAPI) {
|
|
52
53
|
// ---- Register custom notification renderer ----
|
|
@@ -180,4 +181,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
181
|
});
|
|
181
182
|
},
|
|
182
183
|
});
|
|
184
|
+
|
|
185
|
+
// ---- /subagents-settings command ----
|
|
186
|
+
|
|
187
|
+
const subagentsSettings = new SubagentsSettingsHandler(settings);
|
|
188
|
+
|
|
189
|
+
pi.registerCommand("subagents-settings", {
|
|
190
|
+
description: "Configure subagent settings (concurrency, turn limits)",
|
|
191
|
+
handler: async (_args, ctx) => {
|
|
192
|
+
await subagentsSettings.handle({ ui: ctx.ui });
|
|
193
|
+
},
|
|
194
|
+
});
|
|
183
195
|
}
|
package/src/tools/helpers.ts
CHANGED
|
@@ -72,8 +72,9 @@ export interface TypeListRegistry extends AgentConfigLookup {
|
|
|
72
72
|
* Extracted from index.ts so it can be called inside createAgentTool.
|
|
73
73
|
*/
|
|
74
74
|
export function buildTypeListText(registry: TypeListRegistry, agentDir: string): string {
|
|
75
|
-
const
|
|
76
|
-
const
|
|
75
|
+
const isEnabled = (name: string) => registry.resolveAgentConfig(name).enabled !== false;
|
|
76
|
+
const defaultNames = registry.getDefaultAgentNames().filter(isEnabled);
|
|
77
|
+
const userNames = registry.getUserAgentNames().filter(isEnabled);
|
|
77
78
|
|
|
78
79
|
const defaultDescs = defaultNames.map((name) => {
|
|
79
80
|
const cfg = registry.resolveAgentConfig(name);
|
|
@@ -79,6 +79,12 @@ export function resolveSpawnConfig(
|
|
|
79
79
|
): ResolvedSpawnConfig | SpawnConfigError {
|
|
80
80
|
const rawType = params.subagent_type as SubagentType;
|
|
81
81
|
const resolved = registry.resolveType(rawType);
|
|
82
|
+
|
|
83
|
+
// A known-but-disabled type is an explicit error, not a silent unknown-type fallback.
|
|
84
|
+
if (resolved !== undefined && !registry.isValidType(resolved)) {
|
|
85
|
+
return { error: `Agent type "${resolved}" is disabled` };
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
const subagentType = resolved ?? "general-purpose";
|
|
83
89
|
const fellBack = resolved === undefined;
|
|
84
90
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// ---- Narrow interfaces ----
|
|
2
|
+
|
|
3
|
+
/** Narrow settings interface required by the subagents-settings command. */
|
|
4
|
+
export interface SubagentsSettingsManager {
|
|
5
|
+
readonly maxConcurrent: number;
|
|
6
|
+
readonly defaultMaxTurns: number | undefined;
|
|
7
|
+
readonly graceTurns: number;
|
|
8
|
+
applyMaxConcurrent(n: number): { message: string; level: "info" | "warning" };
|
|
9
|
+
applyDefaultMaxTurns(n: number): { message: string; level: "info" | "warning" };
|
|
10
|
+
applyGraceTurns(n: number): { message: string; level: "info" | "warning" };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Narrow UI interface — only the ctx.ui methods the settings handler calls. */
|
|
14
|
+
export interface SubagentsSettingsUI {
|
|
15
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
16
|
+
input(title: string, defaultValue?: string): Promise<string | undefined>;
|
|
17
|
+
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---- Class ----
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handler for the `/subagents-settings` slash command.
|
|
24
|
+
*
|
|
25
|
+
* Call `handle({ ui })` from the Pi command registration to open the interactive
|
|
26
|
+
* settings list. Lifted from `AgentsMenuHandler.showSettings`.
|
|
27
|
+
*/
|
|
28
|
+
export class SubagentsSettingsHandler {
|
|
29
|
+
constructor(private readonly settings: SubagentsSettingsManager) {}
|
|
30
|
+
|
|
31
|
+
async handle({ ui }: { ui: SubagentsSettingsUI }): Promise<void> {
|
|
32
|
+
const choice = await ui.select("Settings", [
|
|
33
|
+
`Max concurrency (current: ${this.settings.maxConcurrent})`,
|
|
34
|
+
`Default max turns (current: ${this.settings.defaultMaxTurns ?? "unlimited"})`,
|
|
35
|
+
`Grace turns (current: ${this.settings.graceTurns})`,
|
|
36
|
+
]);
|
|
37
|
+
if (!choice) return;
|
|
38
|
+
|
|
39
|
+
if (choice.startsWith("Max concurrency")) {
|
|
40
|
+
const val = await ui.input(
|
|
41
|
+
"Max concurrent background agents",
|
|
42
|
+
String(this.settings.maxConcurrent),
|
|
43
|
+
);
|
|
44
|
+
if (val) {
|
|
45
|
+
const n = parseInt(val, 10);
|
|
46
|
+
if (n >= 1) {
|
|
47
|
+
const toast = this.settings.applyMaxConcurrent(n);
|
|
48
|
+
ui.notify(toast.message, toast.level);
|
|
49
|
+
} else {
|
|
50
|
+
ui.notify("Must be a positive integer.", "warning");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else if (choice.startsWith("Default max turns")) {
|
|
54
|
+
const val = await ui.input(
|
|
55
|
+
"Default max turns before wrap-up (0 = unlimited)",
|
|
56
|
+
String(this.settings.defaultMaxTurns ?? 0),
|
|
57
|
+
);
|
|
58
|
+
if (val) {
|
|
59
|
+
const n = parseInt(val, 10);
|
|
60
|
+
if (n >= 0) {
|
|
61
|
+
const toast = this.settings.applyDefaultMaxTurns(n);
|
|
62
|
+
ui.notify(toast.message, toast.level);
|
|
63
|
+
} else {
|
|
64
|
+
ui.notify("Must be 0 (unlimited) or a positive integer.", "warning");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else if (choice.startsWith("Grace turns")) {
|
|
68
|
+
const val = await ui.input(
|
|
69
|
+
"Grace turns after wrap-up steer",
|
|
70
|
+
String(this.settings.graceTurns),
|
|
71
|
+
);
|
|
72
|
+
if (val) {
|
|
73
|
+
const n = parseInt(val, 10);
|
|
74
|
+
if (n >= 1) {
|
|
75
|
+
const toast = this.settings.applyGraceTurns(n);
|
|
76
|
+
ui.notify(toast.message, toast.level);
|
|
77
|
+
} else {
|
|
78
|
+
ui.notify("Must be a positive integer.", "warning");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|