@gotgenes/pi-subagents 16.6.0 → 17.0.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 CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [17.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.6.0...pi-subagents-v17.0.0) (2026-06-18)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * SUBAGENT_EVENTS.ACTIVITY ("subagents:activity") is removed. It was never emitted, so no consumer could act on it, and there is no replacement — the activity tier was removed in Phase 18. Consumers should subscribe to the now-declared FAILED, COMPACTED, CREATED, and STEERED channels instead.
14
+
15
+ ### Features
16
+
17
+ * reconcile SUBAGENT_EVENTS with emitted channels ([#425](https://github.com/gotgenes/pi-packages/issues/425)) ([ba0c48b](https://github.com/gotgenes/pi-packages/commit/ba0c48b294123c1498db2040b1037adcd7ad4e4e))
18
+
8
19
  ## [16.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v16.5.0...pi-subagents-v16.6.0) (2026-06-17)
9
20
 
10
21
 
package/dist/public.d.ts CHANGED
@@ -154,7 +154,10 @@ interface SubagentsService {
154
154
  declare const SUBAGENT_EVENTS: {
155
155
  readonly STARTED: "subagents:started";
156
156
  readonly COMPLETED: "subagents:completed";
157
- readonly ACTIVITY: "subagents:activity";
157
+ readonly FAILED: "subagents:failed";
158
+ readonly COMPACTED: "subagents:compacted";
159
+ readonly CREATED: "subagents:created";
160
+ readonly STEERED: "subagents:steered";
158
161
  };
159
162
  /** Publish the SubagentsService on globalThis for cross-extension access. */
160
163
  declare function publishSubagentsService(service: SubagentsService): void;
@@ -63,9 +63,8 @@ flowchart TB
63
63
 
64
64
  subgraph observation["Observation domain"]
65
65
  direction TB
66
- RecordObserver["record-observer<br/>(stats via events)"]
66
+ RecordObserver["record-observer<br/>(stats + live activity via events)"]
67
67
  Notification["notification<br/>(completion nudges)"]
68
- UIObserver["ui-observer<br/>(streaming state)"]
69
68
  end
70
69
 
71
70
  subgraph tools["Tools domain"]
@@ -95,8 +94,8 @@ flowchart TB
95
94
  SessionConfig --> Prompts & Env
96
95
  AgentTypeRegistry --> DefaultAgents & CustomAgents
97
96
  RecordObserver -.->|subscribes| SubagentSession
98
- UIObserver -.->|subscribes| SubagentSession
99
97
  Widget -.->|polls| SubagentManager
98
+ SubagentManager -.->|notifies| Widget
100
99
  ```
101
100
 
102
101
  ### Key domain types
@@ -258,7 +257,7 @@ sequenceDiagram
258
257
  Asm-->>Factory: SessionConfig
259
258
  Factory->>Child: create session + bind extensions
260
259
  Factory-->>Ag: SubagentSession (born complete)
261
- Note over Ag: agent-observer + ui-observer subscribe to session events
260
+ Note over Ag: record-observer subscribes to session events
262
261
  Ag->>Sub: runTurnLoop(prompt, opts)
263
262
  Sub->>Child: prompt + drive turn loop
264
263
  Child-->>Sub: result text
@@ -322,6 +321,7 @@ src/
322
321
  │ ├── notification.ts completion nudges
323
322
  │ ├── notification-state.ts per-agent notification tracking
324
323
  │ ├── renderer.ts notification TUI component
324
+ │ ├── composite-subagent-observer.ts fans manager notifications out to multiple observers
325
325
  │ └── subagent-events-observer.ts manager lifecycle observer (event emission + persistence + notification)
326
326
 
327
327
  ├── service/ cross-extension API boundary
@@ -346,10 +346,8 @@ src/
346
346
  │ ├── agent-creation-wizard.ts agent creation (AgentCreationWizard class)
347
347
  │ ├── conversation-viewer.ts scrollable session overlay
348
348
  │ ├── message-formatters.ts pure per-message-type formatters (extracted from conversation-viewer)
349
- │ ├── agent-activity-tracker.ts live activity state tracker
350
349
  │ ├── agent-file-ops.ts filesystem abstraction
351
350
  │ ├── agent-file-writer.ts overwrite-guard + write + reload + notify helper
352
- │ ├── ui-observer.ts session-event observer for streaming
353
351
  │ └── display.ts pure formatters and shared types
354
352
 
355
353
  └── handlers/ event handlers
@@ -361,11 +359,11 @@ src/
361
359
 
362
360
  ### Observation model
363
361
 
364
- Record statistics (tool uses, token usage, compaction counts) are updated by `record-observer.ts`, which subscribes directly to session events.
365
- UI streaming (active tools, response text, turn counts) is handled by `ui/ui-observer.ts`, which subscribes to the same session events independently.
366
- Neither observer wraps or forwards the other — both subscribe directly to the session.
362
+ Record statistics (tool uses, token usage, compaction counts) and live activity (active tools, response text, turn counts) are updated by `record-observer.ts`, which subscribes directly to session events.
363
+ This is the single per-child session subscription all run state lives on the `Subagent` record.
367
364
 
368
- The widget reads agent state by polling a shared `Map<string, AgentActivityTracker>` on `SubagentRuntime` every 80 ms. The conversation viewer subscribes to session events via `Subagent.subscribeToUpdates()` and reads messages via `Subagent.messages` no direct `AgentSession` reference (#277).
365
+ The widget reads agent state by polling the records exposed via `SubagentManager.listAgents()` every 80 ms; that poll loop is now started by the manager's lifecycle notifications (the widget subscribes as a `SubagentManagerObserver` fanned out through `CompositeSubagentObserver`), not by inbound calls from the spawn tools.
366
+ The conversation viewer subscribes to session events via `Subagent.subscribeToUpdates()` and reads messages via `Subagent.messages` — no direct `AgentSession` reference (#277).
369
367
 
370
368
  ## Cross-extension architecture
371
369
 
@@ -483,11 +481,14 @@ If Pi gains a native service registry ([earendil-works/pi#4207]), these accessor
483
481
 
484
482
  The core emits events on `pi.events` that any extension can observe:
485
483
 
486
- | Channel | Payload | When |
487
- | --------------------- | ------------------------------------------- | -------------------- |
488
- | `subagents:started` | `{ id, type, description }` | Agent begins running |
489
- | `subagents:completed` | `{ id, type, status, result?, error? }` | Agent finishes |
490
- | `subagents:activity` | `{ id, toolName?, textDelta?, turnCount? }` | Streaming progress |
484
+ | Channel | Payload | When |
485
+ | --------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- |
486
+ | `subagents:started` | `{ id, type, description }` | Agent begins running |
487
+ | `subagents:completed` | `{ id, type, description, status, result?, error?, toolUses, durationMs, tokens? }` | Agent finishes successfully |
488
+ | `subagents:failed` | same as `completed` (`buildEventData` shape) | Agent ends in `error`/`stopped`/`aborted` |
489
+ | `subagents:compacted` | `{ id, type, description, reason, tokensBefore, compactionCount }` | Child session compacts |
490
+ | `subagents:created` | `{ id, type, description, isBackground }` | Background agent created (pre-admission) |
491
+ | `subagents:steered` | `{ id, message }` | Steering message delivered to a running agent |
491
492
 
492
493
  These are fire-and-forget broadcast events — no request IDs, no reply channels.
493
494
 
@@ -965,25 +966,30 @@ Folding the live activity onto the record (the single owner of run state, consis
965
966
  Smell: Category C (Law of Demeter).
966
967
  Outcome: no consumer references `AgentActivityTracker`.
967
968
  Landed: `WidgetAgent` folds the live-activity fields and a `contextPercent` projection; `AgentWidget` projects records; `ConversationViewer`, `AgentsMenuHandler`, `buildDetails`, and `buildNotificationDetails` read off the record; `NotificationSystem.cleanupCompleted` removed; `SubagentEventsObserver` returns early on consumed results; +8 tests (1058 → 1066).
968
- 3. **Delete `AgentActivityTracker` and `ui-observer`; drop the activity map from the runtime and spawn tools.**
969
+ 3. **✅ Delete `AgentActivityTracker` and `ui-observer`; drop the activity map from the runtime and spawn tools — complete.**
969
970
  ([#422]) Target: `ui/agent-activity-tracker.ts` (delete), `ui/ui-observer.ts` (delete), `runtime.ts`, `tools/foreground-runner.ts`, `tools/background-spawner.ts`.
970
971
  The spawn tools stop constructing trackers, subscribing, and populating maps; `SubagentRuntime.agentActivity` is removed.
971
972
  Smell: Category A + C. Outcome: −145 LOC, one session subscription per child, runtime holds zero UI state.
972
- 4. **Make the widget self-drive from lifecycle events.**
973
- ([#423]) Target: `ui/agent-widget.ts`, `lifecycle/subagent-manager.ts` (observer), `index.ts`.
973
+ Landed: deleted `agent-activity-tracker.ts` (84) + `ui-observer.ts` (61) and their unit suites; dropped the `agentActivity` parameter from `runForeground`/`spawnBackground`, the `AgentActivityAccess` interface, and the `AgentToolRuntime`/`SubagentRuntime` activity-map fields; the foreground `observer.onSessionCreated` keeps `recordRef`/`fgId` binding and `widget.ensureTimer`; −34 tests (1066 → 1032).
974
+ 4. **✅ Make the widget self-drive from lifecycle events — complete.**
975
+ ([#423]) Target: `ui/agent-widget.ts`, `observation/composite-subagent-observer.ts` (new), `index.ts`.
974
976
  The widget starts/stops its timer in response to started/created/completed notifications instead of tool calls; spawn tools no longer call `ensureTimer`/`update`/`markFinished`.
975
977
  Smell: Category C (coupling direction).
976
978
  Outcome: the widget is a reactive consumer; no inbound calls from core spawn tools.
977
- 5. **Drop the widget and activity-map dependencies from the `subagent` tool.**
979
+ Landed: `AgentWidget implements SubagentManagerObserver` (starts the 80 ms loop on `onSubagentStarted`/`onSubagentCreated`, re-renders on `onSubagentCompleted`/`onSubagentCompacted`); a new `CompositeSubagentObserver` fans the manager's single observer slot out to `[eventsObserver, widget]` (wired in `index.ts`, keeping `SubagentManager` unchanged); dropped the `widget` parameter and `ForegroundWidgetDeps`/`BackgroundWidgetDeps` interfaces from both spawners, deleted `AgentWidget.markFinished` (redundant with `seedFinishedAgents`), made `ensureTimer` private, and narrowed `AgentToolWidget` to `setUICtx`; +11 tests (1032 → 1043, then −4 with the dropped spawner widget-driving tests → 1039).
980
+ 5. **✅ Drop the widget dependency from the `subagent` tool — complete.**
978
981
  ([#424]) Target: `tools/agent-tool.ts`, `test/helpers/make-deps.ts`.
979
- `AgentTool` loses its `widget` and `agentActivity` constructor params (UICtx capture stays in `ToolStartHandler`); `createToolDeps` sheds the widget and map stubs.
982
+ `AgentTool` loses its `widget` constructor param (UICtx capture stays in `ToolStartHandler`); `createToolDeps` sheds the widget stub.
983
+ The activity-map (`agentActivity`) dependency this step originally named was already removed from the tool and runtime in Step 3 ([#422]), so only `widget` remained to drop.
980
984
  Smell: Category C/D.
981
- Outcome: the LLM tool depends only on manager/runtime/settings/registry; fixture drops 2 fields.
982
- 6. **Reconcile the public event contract.**
985
+ Outcome: the LLM tool depends only on manager/runtime/settings/registry; fixture drops 1 field.
986
+ Landed: removed the `widget` constructor param and the redundant `this.widget.setUICtx(ctx.ui)` call from `AgentTool.execute`, deleted the `AgentToolWidget` interface and its `UICtx` import, updated the sole `index.ts` call site, and dropped the `widget` field/stub plus two now-obsolete tests (`agent-tool` UICtx capture, `make-deps` widget defaults) — UICtx capture is pinned by `handlers/tool-start.test.ts`; −2 tests (1039 → 1037).
987
+ 6. **✅ Reconcile the public event contract — complete.**
983
988
  ([#425]) Target: `service/service.ts`, this document's lifecycle-events table.
984
989
  Remove the vacant `ACTIVITY` channel (or emit a real broadcast for it) and add the emitted `failed`/`compacted`/`created` channels so declared constants match emitted events.
985
990
  Smell: Category A/E.
986
991
  Outcome: declared channels equal emitted channels; no vacant hook.
992
+ Landed: removed `SUBAGENT_EVENTS.ACTIVITY` (breaking) and added `FAILED`/`COMPACTED`/`CREATED`/`STEERED` — `subagents:steered` (from `steer-tool.ts`) was also emitted-but-undeclared, so all four emitted agent-lifecycle channels are now declared; corrected the stale `subagents:completed` payload in the lifecycle-events table to the real `buildEventData` shape; +1 test (1037 → 1038).
987
993
  7. **Consolidate residual test clone families.**
988
994
  ([#426]) Target: `test/settings.test.ts` + `test/layered-settings.test.ts`, `test/lifecycle/create-subagent-session.test.ts`, `test/ui/agent-config-editor.test.ts`.
989
995
  Extract shared fixtures for the clone families fallow reports that the spine does not already rewrite.
@@ -1010,10 +1016,10 @@ Folding the live activity onto the record (the single owner of run state, consis
1010
1016
  flowchart TB
1011
1017
  S1["1 — Fold metrics + activity onto record (#420) ✅"]
1012
1018
  S2["2 — Migrate readers to record getters (#421) ✅"]
1013
- S3["3 — Delete tracker + ui-observer, drop activity map (#422)"]
1014
- S4["4 — Widget self-drives on events (#423)"]
1015
- S5["5 — Drop widget dep from subagent tool (#424)"]
1016
- S6["6 — Reconcile public event contract (#425)"]
1019
+ S3["3 — Delete tracker + ui-observer, drop activity map (#422)"]
1020
+ S4["4 — Widget self-drives on events (#423)"]
1021
+ S5["5 — Drop widget dep from subagent tool (#424)"]
1022
+ S6["6 — Reconcile public event contract (#425)"]
1017
1023
  S7["7 — Consolidate test clone families (#426)"]
1018
1024
  S8["8 — Reconsider UI direction, ADR (#427)"]
1019
1025
 
@@ -0,0 +1,204 @@
1
+ ---
2
+ issue: 422
3
+ issue_title: "pi-subagents: delete AgentActivityTracker and ui-observer, drop the activity map from the core"
4
+ ---
5
+
6
+ # Delete AgentActivityTracker and ui-observer, drop the activity map from the core
7
+
8
+ ## Problem Statement
9
+
10
+ The activity tier is the UI's live-streaming bookkeeping, but today it lives inside the core.
11
+ A `Map<string, AgentActivityTracker>` sits on the core composition root (`SubagentRuntime`), and the two spawn tools construct trackers, open a second session subscription (`subscribeUIObserver`), and populate or delete the map.
12
+ After Phase 18 Step 2 ([#421]) migrated every reader to the record getters, nothing reads the trackers or the map any longer — they are dead bookkeeping the core still maintains.
13
+ This is the third step of the Phase 18 spine (activity-tier disentanglement): delete `AgentActivityTracker` and `ui-observer`, and remove the activity map from `SubagentRuntime` and the spawn tools.
14
+
15
+ ## Goals
16
+
17
+ - Delete `src/ui/agent-activity-tracker.ts` and `src/ui/ui-observer.ts` (~145 LOC).
18
+ - Remove `agentActivity` from `SubagentRuntime` (`src/runtime.ts`) and from the `AgentToolRuntime` slice (`src/tools/agent-tool.ts`).
19
+ - Stop constructing trackers, calling `subscribeUIObserver` / `setSession`, and populating or deleting the map in `src/tools/foreground-runner.ts` and `src/tools/background-spawner.ts`.
20
+ - Remove the now-unused `AgentActivityAccess` interface and the `agentActivity` parameter from both spawner functions.
21
+ - Leave each child session with exactly one session subscription (the core's own `record-observer`) and zero UI state on the runtime.
22
+ - Keep the existing test suite green at every commit.
23
+
24
+ This is **not** a breaking change.
25
+ `AgentActivityTracker`, `ui-observer`, and `SubagentRuntime.agentActivity` are internal symbols — none appear in the public service surface (`src/service/service.ts`) or the settings entry (`src/layered-settings.ts`).
26
+ Observable behavior is unchanged; commits use `refactor:` / `docs:`.
27
+
28
+ ## Non-Goals
29
+
30
+ - Changing how the widget starts/stops its timer — the spawn tools still call `widget.ensureTimer` / `update` / `markFinished` after this step; that decoupling is Phase 18 Step 4 ([#423]).
31
+ - Dropping the `widget` or activity-map constructor params from the `subagent` tool — that is Phase 18 Step 5 ([#424]).
32
+ - Touching `record-observer.ts`, `SubagentState`, or the record getters added in Steps 1–2.
33
+ - Reconciling the public event contract (Step 6) or any UI-distribution decision (Step 8).
34
+
35
+ ## Background
36
+
37
+ Relevant modules and their current relationships:
38
+
39
+ - `src/ui/agent-activity-tracker.ts` — `AgentActivityTracker` class (active tools, response text, turn count, session ref).
40
+ Constructed per-spawn in both spawner tools; written via transition methods; no longer read after [#421].
41
+ - `src/ui/ui-observer.ts` — `subscribeUIObserver(session, tracker, onUpdate?)` opens a second session subscription parallel to `record-observer` and streams events into the tracker.
42
+ - `src/runtime.ts` — `SubagentRuntime.agentActivity: Map<string, AgentActivityTracker>`, the UI streaming-state map on the core composition root.
43
+ - `src/tools/agent-tool.ts` — declares the `AgentActivityAccess` interface and the `AgentToolRuntime.agentActivity` field; passes `this.runtime.agentActivity` into both spawners.
44
+ - `src/tools/foreground-runner.ts` — constructs `fgState`, calls `setSession` + `subscribeUIObserver`, populates and deletes the map; its `streamUpdate` already reads activity off the record (`recordRef`), not the tracker.
45
+ - `src/tools/background-spawner.ts` — constructs `bgState`, calls `setSession` + `subscribeUIObserver` inside an `observer.onSessionCreated`, populates the map.
46
+
47
+ The architecture doc records this as Phase 18 Step 3 (`docs/architecture/architecture.md`, roadmap and step-dependency diagram), with the explicit outcome "−145 LOC, one session subscription per child, runtime holds zero UI state."
48
+
49
+ AGENTS.md constraints that apply:
50
+
51
+ - Run `pnpm fallow dead-code` locally before pushing — deleting modules can orphan sibling exports.
52
+ - pi-subagents is a narrow core with no UI policy; removing UI bookkeeping from the core moves it in the documented direction (dependency arrows point inward).
53
+
54
+ ## Design Overview
55
+
56
+ This step is a deletion enabled by Step 2, not a new abstraction.
57
+ The two structural wins are removing an output-argument pattern (`agentActivity.set(...)` / `.delete(...)` writes into a received map) and narrowing two function signatures by dropping the `agentActivity` parameter.
58
+
59
+ ### Foreground runner
60
+
61
+ The `observer.onSessionCreated` callback stays — it is still the only place `recordRef` and `fgId` are bound mid-flight (while `spawnAndWait` is in progress) and where `widget.ensureTimer()` fires.
62
+ Only the tracker lines are removed.
63
+ The post-change callback:
64
+
65
+ ```typescript
66
+ observer: {
67
+ onSessionCreated: (agent) => {
68
+ recordRef = agent;
69
+ fgId = agent.id;
70
+ widget.ensureTimer();
71
+ },
72
+ },
73
+ ```
74
+
75
+ The `fgState` construction, the `unsubUI` variable and its two `unsubUI?.()` cleanup calls, and `agentActivity.set` / `agentActivity.delete` all go.
76
+ `streamUpdate` is unchanged — it already reads `recordRef?.turnCount`, `recordRef?.activeTools`, `recordRef?.responseText`.
77
+ Re-renders are driven by the existing 80 ms spinner interval, which reads fresh record state populated by the core's `record-observer`.
78
+
79
+ ### Background spawner
80
+
81
+ `onSessionCreated` in the background spawner did **only** tracker work (`bgState.setSession`, `subscribeUIObserver`), so the entire `observer` block is removed from the spawn opts.
82
+ The post-spawn `widget.ensureTimer()` and `widget.update()` calls (outside the observer) stay.
83
+ The `bgState` construction and `agentActivity.set(id, bgState)` go.
84
+
85
+ ### Agent tool
86
+
87
+ `AgentTool` stops passing `this.runtime.agentActivity` to both spawners (Step "remove param").
88
+ The `AgentActivityAccess` interface, the `AgentToolRuntime.agentActivity` field, and the `import { AgentActivityTracker }` are removed once no caller references them.
89
+
90
+ ### Runtime
91
+
92
+ `SubagentRuntime.agentActivity` and its `import type { AgentActivityTracker }` are removed, along with the field's doc comment.
93
+ The session-context methods and `createSubagentRuntime` are untouched.
94
+
95
+ ## Module-Level Changes
96
+
97
+ Source:
98
+
99
+ - `src/tools/foreground-runner.ts` — remove `AgentActivityTracker` import, `subscribeUIObserver` import, `AgentActivityAccess` import, the `agentActivity` parameter, `fgState`, `unsubUI` (+ both `unsubUI?.()` calls), `agentActivity.set`, `agentActivity.delete`; trim the doc comment that lists "AgentActivityTracker creation, UI observer subscription".
100
+ - `src/tools/background-spawner.ts` — remove `AgentActivityTracker` import, `subscribeUIObserver` import, `AgentActivityAccess` import, the `agentActivity` parameter, `bgState`, the entire `observer` block, `agentActivity.set`; trim the doc comment.
101
+ - `src/tools/agent-tool.ts` — remove `AgentActivityAccess` interface, `AgentToolRuntime.agentActivity` field, `import { AgentActivityTracker }`, and the two `this.runtime.agentActivity` arguments to `spawnBackground` / `runForeground`.
102
+ - `src/runtime.ts` — remove `agentActivity` field (+ doc comment) and `import type { AgentActivityTracker }`.
103
+ - `src/types.ts` — reword the `SubscribableSession` doc comment from "Used by record-observer and ui-observer" to reference `record-observer` only.
104
+ - `src/ui/agent-activity-tracker.ts` — **delete**.
105
+ - `src/ui/ui-observer.ts` — **delete**.
106
+
107
+ Tests:
108
+
109
+ - `test/ui/agent-activity-tracker.test.ts` — **delete**.
110
+ - `test/ui/ui-observer.test.ts` — **delete**.
111
+ - `test/tools/foreground-runner.test.ts` — drop the `runtime.agentActivity` argument from every `runForeground(...)` call; remove the "registers activity tracker in agentActivity on session creation" test (redundant with the ensureTimer/markFinished test).
112
+ - `test/tools/background-spawner.test.ts` — drop the `runtime.agentActivity` argument from every `spawnBackground(...)` call; remove the "registers an AgentActivityTracker in agentActivity map" test and the `AgentActivityTracker` import.
113
+ - `test/tools/agent-tool.test.ts` — remove the "registers activity in agentActivity map" test.
114
+ - `test/runtime.test.ts` — remove the "agentActivity map is independently mutable" and "multiple instances are isolated" tests, the two `agentActivity` assertions in "returns correct defaults", and the `AgentActivityTracker` import.
115
+ - `test/helpers/make-deps.ts` — remove the `agentActivity` map construction, the `agentActivity` field on the runtime stub, and the `AgentActivityTracker` import; update the runtime doc comment.
116
+ - `test/helpers/make-deps.test.ts` — remove the "agentActivity is an empty Map on the runtime" test, the "runtime.agentActivity satisfies AgentActivityAccess" test, and the `AgentActivityTracker` import.
117
+
118
+ Docs:
119
+
120
+ - `docs/architecture/architecture.md`:
121
+ - File tree (around line 350) — remove the `agent-activity-tracker.ts` and `ui-observer.ts` entries.
122
+ - "Observation model" prose (around lines 365–368) — drop the "UI streaming … is handled by `ui/ui-observer.ts`" sentence and the stale "widget reads agent state by polling a shared `Map<string, AgentActivityTracker>`" sentence (the widget reads off records since [#421]).
123
+ - System diagram (around line 68) — remove the `ui-observer` node.
124
+ - Sequence-diagram note (around line 261) — drop "+ ui-observer" so it reads "agent-observer subscribes to session events".
125
+ - Phase 18 roadmap Step 3 entry — mark complete (✅) with a `Landed:` bullet, matching the Steps 1–2 format.
126
+ - Phase 18 health-metrics table — the "Activity-tier modules slated for removal" row becomes historical; leave the snapshot but ensure the Step 3 `Landed:` bullet records the realized LOC delta.
127
+ - `.pi/skills/package-pi-subagents/SKILL.md`:
128
+ - Module-dependency-flow block — remove `←─subscribes─ ui-observer` from the `record-observer ─subscribes─→ AgentSession` line.
129
+ - Domain table — UI directory count `12 → 10`; domain header "seven domains (59 files)" → "(57 files)".
130
+
131
+ Historical docs under `docs/plans/`, `docs/retro/`, and `docs/architecture/history/` reference these symbols as records of past work and are **not** edited.
132
+
133
+ ## Test Impact Analysis
134
+
135
+ This is a deletion, not an extraction, so it enables no new lower-level tests.
136
+
137
+ 1. New tests enabled: none — the deleted modules' behavior is gone, not relocated.
138
+ 2. Tests that become redundant: the tracker/observer unit suites (`agent-activity-tracker.test.ts`, `ui-observer.test.ts`) test deleted code and are removed wholesale; the "registers in agentActivity map" tests across `foreground-runner`, `background-spawner`, `agent-tool`, `runtime`, and `make-deps` test removed state and are deleted.
139
+ 3. Tests that must stay: the spawner result-text tests, the `ensureTimer` / `markFinished` / `update` widget-driving tests, the foreground streaming-`onUpdate` test, and the runtime session-context tests — they exercise behavior that survives this step.
140
+
141
+ ## Invariants at risk
142
+
143
+ This step touches surfaces refactored by Phase 18 Steps 1–2.
144
+ The relevant prior `Landed:` invariants and the tests that pin them:
145
+
146
+ - "no consumer references `AgentActivityTracker`" (Step 2 outcome) — pinned by the type checker: after this step the symbol is deleted, so any surviving reference fails `pnpm run check`.
147
+ The foreground `streamUpdate` reading off `recordRef` (Step 2) must remain — covered by `test/tools/foreground-runner.test.ts` "calls onUpdate with streaming details while running".
148
+ - "`Subagent` is the single home for all run state; getters available" (Step 1 outcome) — unchanged here; the getters keep their existing coverage in `test/lifecycle/` and `test/observation/`.
149
+ - Foreground re-render after dropping the second subscription — pinned by the streaming-`onUpdate` test (spinner-driven tick), confirming re-renders survive without `subscribeUIObserver`.
150
+
151
+ No new test is required; the type checker plus the surviving streaming test cover the at-risk invariants.
152
+
153
+ ## TDD Order
154
+
155
+ This is primarily code deletion; each step is a `refactor:`/`docs:` commit that keeps `pnpm run check` and the full suite green.
156
+ Steps are ordered so no commit leaves a dangling type reference.
157
+
158
+ 1. **Remove tracker wiring + the `agentActivity` parameter from the spawners.**
159
+ Edit `foreground-runner.ts` and `background-spawner.ts` to drop tracker construction, `subscribeUIObserver`/`setSession`, map writes, and the `agentActivity` parameter; update the two `AgentTool` call sites; update `foreground-runner.test.ts` and `background-spawner.test.ts` (drop the arg, remove the "registers tracker" tests and the `AgentActivityTracker` import in the background test).
160
+ The parameter removal cascades to call sites and tests at the type level, so they land together.
161
+ Run `pnpm run check` immediately after — shared-signature change.
162
+ Commit: `refactor: stop wiring activity trackers in the spawn tools (#422)`.
163
+
164
+ 2. **Remove the activity map from the runtime and the agent-tool slice.**
165
+ Delete `SubagentRuntime.agentActivity` (`runtime.ts`), the `AgentActivityAccess` interface, the `AgentToolRuntime.agentActivity` field, and the `AgentActivityTracker` import in `agent-tool.ts`; update `make-deps.ts` (drop the map + field), and all tests reading the field (`runtime.test.ts`, `make-deps.test.ts`, `agent-tool.test.ts`).
166
+ Removing the field and `AgentActivityAccess` export breaks every reader at the type level, so all consumer + test updates land in this commit.
167
+ Run `pnpm run check` immediately after.
168
+ Commit: `refactor: drop the activity map from the runtime and agent tool (#422)`.
169
+
170
+ 3. **Delete the dead modules and their tests.**
171
+ Delete `src/ui/agent-activity-tracker.ts`, `src/ui/ui-observer.ts`, `test/ui/agent-activity-tracker.test.ts`, `test/ui/ui-observer.test.ts`; reword the `SubscribableSession` comment in `types.ts`.
172
+ Run `pnpm fallow dead-code` to confirm no sibling export (e.g. `SessionLike`, `SubscribableSession`) was orphaned.
173
+ Commit: `refactor: delete AgentActivityTracker and ui-observer (#422)`.
174
+
175
+ 4. **Update the architecture doc and package skill.**
176
+ Apply the doc edits listed in Module-Level Changes (file tree, observation-model prose, system + sequence diagrams, Phase 18 Step 3 ✅ + `Landed:` bullet, SKILL.md flow diagram and domain counts).
177
+ Verify any touched Mermaid renders (load the `mermaid` skill).
178
+ Commit: `docs: mark Phase 18 Step 3 complete and remove activity-tier references (#422)`.
179
+
180
+ ## Risks and Mitigations
181
+
182
+ - **Foreground re-render cadence.**
183
+ Dropping `subscribeUIObserver` removes event-driven re-renders; updates now rely solely on the 80 ms spinner poll.
184
+ The displayed content is identical within ≤80 ms latency (the poll reads the same record the core observer populates), and the spinner already ran at this cadence — no perceptible regression.
185
+ Pinned by the streaming-`onUpdate` test.
186
+ - **Stale doc references.**
187
+ The architecture doc and SKILL.md describe the activity map as current state.
188
+ Mitigation: Step 4 grep-driven sweep; the file tree, prose, and two diagrams are all listed explicitly.
189
+ - **Orphaned sibling exports.**
190
+ Deleting the modules could orphan `SessionLike` or `SubscribableSession`.
191
+ Verified during planning: both remain used (`SessionLike` by `subagent-session.ts`; `SubscribableSession` by `record-observer.ts`, `subagent-session.ts`, `types.ts`).
192
+ Mitigation: `pnpm fallow dead-code` in Step 3.
193
+ - **Commit ordering.**
194
+ Removing the field before the spawners stop passing it would break the build.
195
+ Mitigation: Step 1 (stop passing) strictly precedes Step 2 (remove field).
196
+
197
+ ## Open Questions
198
+
199
+ None blocking.
200
+ Whether the widget should self-drive from lifecycle events (so the spawners no longer call `ensureTimer`/`update`/`markFinished`) is deferred to Phase 18 Step 4 ([#423]).
201
+
202
+ [#421]: https://github.com/gotgenes/pi-packages/issues/421
203
+ [#423]: https://github.com/gotgenes/pi-packages/issues/423
204
+ [#424]: https://github.com/gotgenes/pi-packages/issues/424