@trevonistrevon/pi-loop 0.4.11 → 0.5.1

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.
Files changed (87) hide show
  1. package/README.md +20 -9
  2. package/dist/commands/loop-command.d.ts +22 -0
  3. package/dist/commands/loop-command.js +148 -0
  4. package/dist/commands/tasks-command.d.ts +15 -0
  5. package/dist/commands/tasks-command.js +117 -0
  6. package/dist/coordinator.d.ts +35 -0
  7. package/dist/coordinator.js +56 -0
  8. package/dist/goal-coordinator.d.ts +22 -0
  9. package/dist/goal-coordinator.js +28 -0
  10. package/dist/goal-reducer.d.ts +89 -0
  11. package/dist/goal-reducer.js +181 -0
  12. package/dist/goal-store.d.ts +31 -0
  13. package/dist/goal-store.js +298 -0
  14. package/dist/goal-types.d.ts +82 -0
  15. package/dist/goal-types.js +1 -0
  16. package/dist/goal-verifier.d.ts +20 -0
  17. package/dist/goal-verifier.js +198 -0
  18. package/dist/index.js +130 -1087
  19. package/dist/loop-reducer.d.ts +63 -0
  20. package/dist/loop-reducer.js +67 -0
  21. package/dist/monitor-completion-coordinator.d.ts +10 -0
  22. package/dist/monitor-completion-coordinator.js +13 -0
  23. package/dist/monitor-manager.d.ts +2 -0
  24. package/dist/monitor-manager.js +107 -29
  25. package/dist/monitor-reducer.d.ts +82 -0
  26. package/dist/monitor-reducer.js +69 -0
  27. package/dist/notification-reducer.d.ts +81 -0
  28. package/dist/notification-reducer.js +65 -0
  29. package/dist/runtime/monitor-ondone-runtime.d.ts +13 -0
  30. package/dist/runtime/monitor-ondone-runtime.js +49 -0
  31. package/dist/runtime/notification-runtime.d.ts +34 -0
  32. package/dist/runtime/notification-runtime.js +152 -0
  33. package/dist/runtime/scope.d.ts +8 -0
  34. package/dist/runtime/scope.js +33 -0
  35. package/dist/runtime/session-runtime.d.ts +39 -0
  36. package/dist/runtime/session-runtime.js +110 -0
  37. package/dist/runtime/task-backlog-runtime.d.ts +36 -0
  38. package/dist/runtime/task-backlog-runtime.js +105 -0
  39. package/dist/runtime/task-rpc.d.ts +19 -0
  40. package/dist/runtime/task-rpc.js +118 -0
  41. package/dist/store.d.ts +7 -4
  42. package/dist/store.js +129 -49
  43. package/dist/task-backlog-coordinator.d.ts +12 -0
  44. package/dist/task-backlog-coordinator.js +22 -0
  45. package/dist/task-reducer.d.ts +66 -0
  46. package/dist/task-reducer.js +76 -0
  47. package/dist/task-store.d.ts +8 -4
  48. package/dist/task-store.js +102 -33
  49. package/dist/tools/loop-tools.d.ts +41 -0
  50. package/dist/tools/loop-tools.js +241 -0
  51. package/dist/tools/monitor-tools.d.ts +25 -0
  52. package/dist/tools/monitor-tools.js +110 -0
  53. package/dist/tools/native-task-tools.d.ts +15 -0
  54. package/dist/tools/native-task-tools.js +127 -0
  55. package/docs/architecture/goal-state-schema.md +505 -0
  56. package/docs/architecture/state-machine-migration.md +546 -0
  57. package/docs/architecture/state-machine-reducer-event-model.md +823 -0
  58. package/docs/architecture/state-machine-test-matrix.md +249 -0
  59. package/docs/architecture/state-machine-transition-map.md +436 -0
  60. package/package.json +1 -1
  61. package/src/commands/loop-command.ts +184 -0
  62. package/src/commands/tasks-command.ts +135 -0
  63. package/src/coordinator.ts +115 -0
  64. package/src/goal-coordinator.ts +58 -0
  65. package/src/goal-reducer.ts +280 -0
  66. package/src/goal-store.ts +315 -0
  67. package/src/goal-types.ts +104 -0
  68. package/src/goal-verifier.ts +241 -0
  69. package/src/index.ts +134 -1147
  70. package/src/loop-reducer.ts +148 -0
  71. package/src/monitor-completion-coordinator.ts +24 -0
  72. package/src/monitor-manager.ts +115 -27
  73. package/src/monitor-reducer.ts +166 -0
  74. package/src/notification-reducer.ts +155 -0
  75. package/src/runtime/monitor-ondone-runtime.ts +75 -0
  76. package/src/runtime/notification-runtime.ts +212 -0
  77. package/src/runtime/scope.ts +37 -0
  78. package/src/runtime/session-runtime.ts +168 -0
  79. package/src/runtime/task-backlog-runtime.ts +163 -0
  80. package/src/runtime/task-rpc.ts +150 -0
  81. package/src/store.ts +132 -50
  82. package/src/task-backlog-coordinator.ts +32 -0
  83. package/src/task-reducer.ts +152 -0
  84. package/src/task-store.ts +103 -31
  85. package/src/tools/loop-tools.ts +304 -0
  86. package/src/tools/monitor-tools.ts +145 -0
  87. package/src/tools/native-task-tools.ts +144 -0
@@ -0,0 +1,249 @@
1
+ # pi-loop State Tracking Test Matrix
2
+
3
+ ## Purpose
4
+
5
+ This matrix turns the state inventory in `docs/architecture/state-machine-transition-map.md`
6
+ into a concrete test plan for the reducer/state-machine migration.
7
+
8
+ The goal is to:
9
+
10
+ - lock down current behavior before refactoring
11
+ - separate entity transition tests from coordination scenario tests
12
+ - make hidden lifecycle phases observable via assertions
13
+ - ensure future Goal orchestration builds on a reliable state core
14
+
15
+ ## Test layers
16
+
17
+ We will test state tracking at three layers:
18
+
19
+ 1. **Entity transition tests**
20
+ - task, loop, monitor, and notification transitions in isolation
21
+ 2. **Invariant tests**
22
+ - properties that must always hold across transitions
23
+ 3. **Scenario tests**
24
+ - cross-entity coordination paths and lifecycle races
25
+
26
+ ## 1. Entity transition tests
27
+
28
+ ### 1.1 Task entity transitions
29
+
30
+ | ID | Case | Start | Event | Expected end state | Current coverage | Target file |
31
+ |---|---|---|---|---|---|---|
32
+ | T-01 | create native task | none | `TaskCreate` | `pending` | partial | `test/index.test.ts` |
33
+ | T-02 | quick-create native task | none | `/tasks some text` | `pending` | partial | `test/index.test.ts` |
34
+ | T-03 | start task | `pending` | `TaskUpdate(status=in_progress)` | `in_progress` | missing direct test | `test/index.test.ts` |
35
+ | T-04 | complete pending task | `pending` | `TaskUpdate(status=completed)` | `completed` with `completedAt` | partial | `test/index.test.ts` |
36
+ | T-05 | complete in-progress task | `in_progress` | `TaskUpdate(status=completed)` | `completed` with `completedAt` | missing direct test | `test/index.test.ts` |
37
+ | T-06 | reopen completed task | `completed` | `TaskUpdate(status=pending)` | `pending` | missing | `test/index.test.ts` |
38
+ | T-07 | delete task | any existing | `TaskDelete` | removed | partial | `test/index.test.ts` |
39
+ | T-08 | prune completed tasks | mixed task set | `cleanDoneTasks()` / `sweepCompleted()` | completed removed, active retained | partial | `test/index.test.ts` / `test/task-store.test.ts` if added |
40
+ | T-09 | monotonic ids after prune | completed tasks swept | `TaskCreate` | next id strictly increases | covered | `test/index.test.ts` |
41
+ | T-10 | completedAt semantics on reopen | completed then reopened | `TaskUpdate(status=pending)` | define whether `completedAt` persists or clears | currently unspecified | state-model decision |
42
+
43
+ ### 1.2 Loop entity transitions
44
+
45
+ | ID | Case | Start | Event | Expected end state | Current coverage | Target file |
46
+ |---|---|---|---|---|---|---|
47
+ | L-01 | create cron loop | none | `LoopCreate(trigger=cron)` | `active` and armed | partial | `test/index.test.ts` / `test/store.test.ts` |
48
+ | L-02 | create event loop | none | `LoopCreate(trigger=event)` | `active` and subscribed | partial | `test/index.test.ts` / `test/trigger-system.test.ts` |
49
+ | L-03 | create hybrid loop | none | `LoopCreate(trigger=hybrid)` | `active`, armed, subscribed | partial | `test/index.test.ts` / `test/trigger-system.test.ts` |
50
+ | L-04 | pause active loop | `active` | `LoopDelete(action=pause)` | `paused`, unsubscribed, disarmed | covered wrapper only | `test/index.test.ts` |
51
+ | L-05 | resume paused loop | `paused` | interactive resume / direct store+trigger path | `active`, re-armed, re-subscribed | missing | `test/index.test.ts` |
52
+ | L-06 | delete loop | `active` or `paused` | `LoopDelete(delete)` | removed from store and trigger system | covered | `test/index.test.ts` |
53
+ | L-07 | fire recurring loop | `active` recurring | `onLoopFire` | `fireCount++`, remains active | partial | `test/scheduler.test.ts` / `test/trigger-system.test.ts` |
54
+ | L-08 | fire one-shot loop | `active` non-recurring | `fire` | removed after delivery path | partial | `test/trigger-system.test.ts` / `test/injection.test.ts` |
55
+ | L-09 | maxFires terminal delete | recurring + limit | final fire | removed immediately | covered | `test/index.test.ts` / `test/trigger-system.test.ts` |
56
+ | L-10 | expiry delete | expired active loop | scheduler/session prune | removed | partial | `test/scheduler.test.ts` / `test/store.test.ts` |
57
+ | L-11 | resume-time event loop cleanup | persisted event/hybrid loop from prior session | `expireEventLoops()` | removed | covered store-level only | `test/store.test.ts` |
58
+ | L-12 | backlog loop zero-pending delete | `taskBacklog` loop active | pending count -> 0 | removed | covered | `test/index.test.ts` |
59
+
60
+ ### 1.3 Monitor entity transitions
61
+
62
+ | ID | Case | Start | Event | Expected end state | Current coverage | Target file |
63
+ |---|---|---|---|---|---|---|
64
+ | M-01 | create monitor | none | `MonitorCreate` | `running` | covered | `test/index.test.ts` |
65
+ | M-02 | normal completion | `running` | child close code 0 | `completed` retained briefly | covered indirectly | `test/index.test.ts` / `test/monitor-manager.test.ts` |
66
+ | M-03 | error completion | `running` | child close nonzero | `error` retained briefly | partial | `test/monitor-manager.test.ts` |
67
+ | M-04 | child error path | `running` | child `error` | `error` retained, callbacks cleared | missing direct test | `test/monitor-manager.test.ts` |
68
+ | M-05 | stop monitor | `running` | `MonitorStop` | `stopped` | covered | `test/index.test.ts` |
69
+ | M-06 | retain completed monitor | `completed` | before 30s retention expiry | still listed | partial | `test/index.test.ts` |
70
+ | M-07 | prune completed monitor | `completed` | after retention timeout | removed | missing direct test | `test/monitor-manager.test.ts` |
71
+ | M-08 | onDone callback registration | `running` | `onComplete()` | callback stored | missing direct test | `test/monitor-manager.test.ts` |
72
+ | M-09 | onDone direct delivery after completion | `completed` | registered callback or immediate completed registration | wake delivered once | covered | `test/index.test.ts` |
73
+ | M-10 | onDone does not depend on event dispatch | `completed` | event suppressed | direct wake still delivered | covered | `test/index.test.ts` |
74
+
75
+ ### 1.4 Notification / wake entity transitions
76
+
77
+ | ID | Case | Start | Event | Expected end state | Current coverage | Target file |
78
+ |---|---|---|---|---|---|---|
79
+ | N-01 | idle immediate delivery | none | `loop:fire` while idle | sent immediately | covered | `test/injection.test.ts` |
80
+ | N-02 | active-agent buffering | none | `loop:fire` while agent active | queued, not sent yet | covered | `test/injection.test.ts` |
81
+ | N-03 | flush on idle | queued | `agent_end` / idle flush | delivered | covered | `test/injection.test.ts` |
82
+ | N-04 | recurring dedupe | queued recurring | newer same-loop fire | prior pending replaced | covered | `test/injection.test.ts` |
83
+ | N-05 | one-shot isolation | queued one-shot | separate one-shot fire | both preserved independently | missing explicit test | `test/injection.test.ts` |
84
+ | N-06 | autoTask drop on zero pending at delivery | queued autoTask wake | pending count -> 0 | dropped and cleanup requested | covered | `test/injection.test.ts` |
85
+ | N-07 | force flush on agent_end even with pending messages | queued wake | `agent_end` with `hasPendingMessages()` true | delivered | covered | `test/index.test.ts` |
86
+ | N-08 | clear on session switch | queued wake | `session_switch` | cleared, not delivered | missing direct test | `test/injection.test.ts` |
87
+ | N-09 | clear on shutdown | queued wake | `session_shutdown` | cleared, not delivered | missing direct test | `test/injection.test.ts` |
88
+
89
+ ## 2. Invariant tests
90
+
91
+ These are properties that should remain true across many transitions.
92
+
93
+ ### 2.1 Task invariants
94
+
95
+ | ID | Invariant | Assertion |
96
+ |---|---|---|
97
+ | TI-01 | ids are monotonic | creating after prune never reuses an old id |
98
+ | TI-02 | at most one status per task | task cannot be both `completed` and counted as pending/in-progress |
99
+ | TI-03 | pending count excludes completed | `pendingCount()` only counts `pending` and `in_progress` |
100
+ | TI-04 | pruning preserves active work | sweeping completed tasks never removes pending/in-progress tasks |
101
+ | TI-05 | retention policy is event-driven, not implicit | completed tasks remain listed until an explicit cleanup trigger occurs |
102
+
103
+ ### 2.2 Loop invariants
104
+
105
+ | ID | Invariant | Assertion |
106
+ |---|---|---|
107
+ | LI-01 | terminal loops do not remain active | non-recurring, expired, and maxFires-complete loops are removed |
108
+ | LI-02 | paused loops do not fire | paused loops have no active scheduler/event behavior |
109
+ | LI-03 | backlog loops only self-delete on zero pending | ordinary `tasks:created` watchers stay alive |
110
+ | LI-04 | recurring fire count is monotonic | `fireCount` never decreases |
111
+ | LI-05 | max loop count enforced | cannot exceed 25 active loops |
112
+
113
+ ### 2.3 Monitor invariants
114
+
115
+ | ID | Invariant | Assertion |
116
+ |---|---|---|
117
+ | MI-01 | monitor has one terminal status | running cannot transition to multiple terminal statuses |
118
+ | MI-02 | onDone wake delivered at most once | direct callback + event path do not duplicate the wake |
119
+ | MI-03 | stopped monitors do not later complete | stop path cannot also emit completion wake |
120
+ | MI-04 | retention timeout eventually prunes completed/error monitors | terminal monitors do not linger indefinitely |
121
+
122
+ ### 2.4 Notification invariants
123
+
124
+ | ID | Invariant | Assertion |
125
+ |---|---|---|
126
+ | NI-01 | recurring pending wake key is unique per loop | one recurring loop has at most one buffered wake |
127
+ | NI-02 | one-shot wake key uniqueness | one-shot deliveries do not overwrite each other |
128
+ | NI-03 | no delivery while agent active | queued wakes do not send until flush conditions are met |
129
+ | NI-04 | dropped autoTask wake requests cleanup | zero-pending autoTask drops trigger cleanup side effect |
130
+ | NI-05 | session switch clears buffered wakes | pending notifications are not delivered into a new session unexpectedly |
131
+
132
+ ## 3. Scenario tests
133
+
134
+ These validate cross-entity coordination and races.
135
+
136
+ ### 3.1 Backlog orchestration scenarios
137
+
138
+ | ID | Scenario | Expected outcome | Current coverage |
139
+ |---|---|---|---|
140
+ | S-01 | create 5 native tasks | worker loop auto-created once | covered |
141
+ | S-02 | create 6th+ task | no duplicate worker loop | covered |
142
+ | S-03 | agent busy during worker bootstrap | wake buffered then flushed at `agent_end` | covered |
143
+ | S-04 | queue clears | worker loop auto-deletes | covered |
144
+ | S-05 | manual `taskBacklog` loop queue clears | manual backlog loop auto-deletes | covered |
145
+ | S-06 | plain `tasks:created` watcher queue clears | ordinary watcher stays active | covered |
146
+
147
+ ### 3.2 Monitor completion scenarios
148
+
149
+ | ID | Scenario | Expected outcome | Current coverage |
150
+ |---|---|---|---|
151
+ | S-07 | monitor completes while idle | model receives onDone wake | covered |
152
+ | S-08 | monitor completes while agent busy | wake buffered and later delivered | partial |
153
+ | S-09 | monitor event dispatch missed | direct callback path still wakes model | covered |
154
+ | S-10 | onDone loop created after monitor already finished | immediate direct delivery or expiry behavior is correct | partial |
155
+
156
+ ### 3.3 Task cleanup scenarios
157
+
158
+ | ID | Scenario | Expected outcome | Current coverage |
159
+ |---|---|---|---|
160
+ | S-11 | successful `git commit` | completed tasks pruned, ids preserved | covered |
161
+ | S-12 | failed commit | completed tasks retained | covered |
162
+ | S-13 | non-commit bash tool | completed tasks retained | covered |
163
+ | S-14 | zero-pending autoTask drop | completed tasks swept and wake not delivered | covered |
164
+
165
+ ### 3.4 Session lifecycle scenarios
166
+
167
+ | ID | Scenario | Expected outcome | Current coverage |
168
+ |---|---|---|---|
169
+ | S-15 | session switch with queued wakes | queued notifications cleared | missing |
170
+ | S-16 | session shutdown with queued wakes | queued notifications cleared | missing |
171
+ | S-17 | resume with stale event/hybrid loops | expired event/hybrid loops pruned | partial |
172
+ | S-18 | memory scope new non-resume session | loops cleared | missing direct integration |
173
+ | S-19 | session-scoped storage path | tasks/loops isolated by session id | covered for tasks, partial for loops |
174
+
175
+ ## 4. Priority order for implementation
176
+
177
+ ### Phase A — Close the highest-value gaps first
178
+
179
+ These gaps most directly reduce refactor risk:
180
+
181
+ 1. `T-03`, `T-05`, `T-06`, `T-10`
182
+ 2. `L-05`, `L-08`
183
+ 3. `M-04`, `M-07`, `M-08`
184
+ 4. `N-05`, `N-08`, `N-09`
185
+ 5. `S-08`, `S-10`, `S-15`, `S-16`, `S-18`
186
+
187
+ ### Phase B — Extract testable helpers before reducer migration
188
+
189
+ Once Phase A closes, we should be able to extract helper functions or reducers for:
190
+
191
+ - task transition application
192
+ - loop terminal-deletion rules
193
+ - notification queue keying and flush eligibility
194
+ - monitor completion side effects
195
+
196
+ ### Phase C — Add reducer-specific tests
197
+
198
+ After reducer extraction, add pure transition tests with no Pi mocks:
199
+
200
+ - `reduceTask(state, event)`
201
+ - `reduceLoop(state, event)`
202
+ - `reduceMonitor(state, event)`
203
+ - `reduceNotification(state, event)`
204
+
205
+ Each should assert:
206
+
207
+ - next state
208
+ - emitted effects
209
+ - no hidden mutation of unrelated entities
210
+
211
+ ## 5. Goal feature prerequisites
212
+
213
+ The Goal feature should not land until these test conditions are met:
214
+
215
+ - all Phase A gaps above are closed
216
+ - notification delivery is deterministic under idle/active/session-switch conditions
217
+ - backlog loop ownership and cleanup are explicit and tested
218
+ - monitor completion semantics are single-delivery and race-resistant
219
+ - task pruning policy is stable and monotonic-id safe
220
+
221
+ ## 6. Minimum deliverables for the next subtasks
222
+
223
+ ### Task #3 — Add entity transition tests
224
+
225
+ Deliver:
226
+
227
+ - direct tests for missing task, loop, monitor, and notification transitions
228
+ - no production behavior changes required
229
+
230
+ ### Task #4 — Add scenario state tests
231
+
232
+ Deliver:
233
+
234
+ - end-to-end race and lifecycle scenarios from section 3
235
+ - especially session-switch and monitor-busy completion paths
236
+
237
+ ### Task #5 — Design reducer event model
238
+
239
+ Must align reducer event names to the transition cases above so the matrix remains the stable contract.
240
+
241
+ ## 7. Summary
242
+
243
+ This matrix turns the current behavior into a test contract.
244
+
245
+ The most important principle for the refactor is:
246
+
247
+ > Preserve behavior first, then simplify representation.
248
+
249
+ If a future reducer/state-machine change cannot be mapped back to one of the transition or scenario rows in this document, it is probably changing semantics, not just structure.
@@ -0,0 +1,436 @@
1
+ # pi-loop State and Transition Inventory
2
+
3
+ ## Scope
4
+
5
+ This document inventories the current explicit and implicit state for:
6
+
7
+ - native tasks
8
+ - loops
9
+ - monitors
10
+ - pending notifications / wake delivery
11
+ - cross-entity coordination paths
12
+
13
+ It is the baseline for the planned reducer/state-machine refactor.
14
+
15
+ ## 1. Native task state
16
+
17
+ ### Explicit task states
18
+
19
+ Defined in `src/task-types.ts`:
20
+
21
+ - `pending`
22
+ - `in_progress`
23
+ - `completed`
24
+
25
+ ### Persisted task fields
26
+
27
+ Defined in `TaskEntry`:
28
+
29
+ - `id`
30
+ - `subject`
31
+ - `description`
32
+ - `status`
33
+ - `createdAt`
34
+ - `updatedAt`
35
+ - `completedAt?`
36
+ - `metadata?`
37
+
38
+ ### Persisted task container state
39
+
40
+ Defined in `TaskStore`:
41
+
42
+ - `nextId`
43
+ - `tasks: Map<string, TaskEntry>`
44
+ - optional `filePath` / `lockPath`
45
+
46
+ ### Task transitions
47
+
48
+ | From | Event | To | Notes |
49
+ |---|---|---|---|
50
+ | none | `TaskCreate` / `/tasks` quick-create / autoTask fallback create | `pending` | increments monotonic `nextId` |
51
+ | `pending` | `TaskUpdate(status=in_progress)` / interactive `> Start` | `in_progress` | active work starts |
52
+ | `pending` | `TaskUpdate(status=completed)` / interactive `ok Complete` | `completed` | `completedAt` set |
53
+ | `in_progress` | `TaskUpdate(status=completed)` / interactive `ok Complete` | `completed` | `completedAt` set |
54
+ | `in_progress` | `TaskUpdate(status=pending)` / interactive `* Return to pending` | `pending` | re-queued |
55
+ | `completed` | `TaskUpdate(status=pending)` / interactive `* Reopen` | `pending` | currently clears state semantically, but leaves `completedAt` populated |
56
+ | any existing | `TaskDelete` / interactive delete | deleted | removed from store |
57
+ | `completed` | `cleanDoneTasks()` / `sweepCompleted()` | pruned | removed from store, `nextId` preserved |
58
+
59
+ ### Hidden / implicit task state
60
+
61
+ These are not encoded as task status values, but materially affect behavior:
62
+
63
+ - task provider mode: `tasksAvailable` vs `nativeTaskStore`
64
+ - storage scope: `memory` / `session` / `project`
65
+ - backlog summary state derived in widget: `count`, `active`, `next`
66
+ - completed-task retention policy is event-driven, not time-driven
67
+ - `completedAt` remains populated if a completed task is reopened
68
+
69
+ ### Task sharp edges
70
+
71
+ - `completed` and `pruned` are separate lifecycle phases but only one is explicit.
72
+ - Reopening a completed task does not clear `completedAt`; this is harmless now but muddies semantics.
73
+ - Provider mode (`pi-tasks` vs native fallback) is global session state, not entity state.
74
+ - Cleanup policy is distributed across commit hooks, loop zero-pending paths, and explicit delete/sweep flows.
75
+
76
+ ## 2. Loop state
77
+
78
+ ### Explicit loop states
79
+
80
+ Defined in `src/types.ts`:
81
+
82
+ - `active`
83
+ - `paused`
84
+
85
+ ### Persisted loop fields
86
+
87
+ Defined in `LoopEntry`:
88
+
89
+ - `id`
90
+ - `prompt`
91
+ - `trigger`
92
+ - `status`
93
+ - `recurring`
94
+ - `createdAt`
95
+ - `updatedAt`
96
+ - `expiresAt`
97
+ - `autoTask?`
98
+ - `taskBacklog?`
99
+ - `readOnly?`
100
+ - `maxFires?`
101
+ - `fireCount?`
102
+
103
+ ### Trigger variants
104
+
105
+ - `cron`
106
+ - `event`
107
+ - `hybrid`
108
+
109
+ ### Loop transitions
110
+
111
+ | From | Event | To | Notes |
112
+ |---|---|---|---|
113
+ | none | `LoopCreate` / auto worker creation / monitor onDone helper | `active` | persisted in store |
114
+ | `active` | `LoopDelete(action=pause)` / interactive pause | `paused` | subscriptions/timers removed |
115
+ | `paused` | `LoopDelete(action=resume)` / interactive resume | `active` | subscriptions/timers re-added |
116
+ | `active` | `LoopDelete(delete)` / interactive delete | deleted | removed from store and trigger system |
117
+ | `active` | `onLoopFire` with recurring loop | `active` | `fireCount++`; may queue wake |
118
+ | `active` | `onLoopFire` and `maxFires` reached | deleted | removed immediately |
119
+ | `active` | one-shot event or one-shot monitor completion loop fired | deleted | trigger system removes after fire |
120
+ | `active` | `cleanupTaskBacklogLoops()` and zero pending tasks | deleted | only for `taskBacklog` or auto worker loop |
121
+ | `active` | `clearExpired()` / expiry boundary | deleted | recurring lifetime cap |
122
+ | `active` | `expireEventLoops()` on resumed session | deleted | old event/hybrid loops from prior session pruned |
123
+
124
+ ### Hidden / implicit loop state
125
+
126
+ These states are currently distributed across multiple structures:
127
+
128
+ - scheduler armed/not-armed: `CronScheduler.fireTimes`
129
+ - event subscribed/not-subscribed: `TriggerSystem.eventSubscriptions`
130
+ - hybrid debounce timer pending/not-pending: `TriggerSystem.hybridTimers`
131
+ - last fire timestamp for debounce: `TriggerSystem.lastFireTime`
132
+ - wake queued/not-queued: `pendingNotifications`
133
+ - wake dedupe keying: recurring loops use `loop:<id>`, one-shot uses `loop:<id>:<timestamp>`
134
+ - worker-loop classification is partly semantic (`prompt` string match) and partly structural (`taskBacklog`)
135
+ - bootstrapped-against-backlog is an effect, not persisted state
136
+
137
+ ### Loop sharp edges
138
+
139
+ - `active` covers multiple sub-states: armed, waiting, queued, delivering, debounced.
140
+ - Auto worker loop detection still depends partly on prompt equality (`AUTO_TASK_WORKER_PROMPT`).
141
+ - Task backlog semantics are split between `taskBacklog`, `tasks:created` trigger shape, and prompt heuristics.
142
+ - Monitor `onDone` loops are stored as generic loops, but delivered by a separate direct completion path.
143
+
144
+ ## 3. Monitor state
145
+
146
+ ### Explicit monitor states
147
+
148
+ Defined in `src/types.ts`:
149
+
150
+ - `running`
151
+ - `completed`
152
+ - `error`
153
+ - `stopped`
154
+
155
+ ### Persisted/in-memory monitor fields
156
+
157
+ Defined in `MonitorEntry` / `MonitorProcess`:
158
+
159
+ - `id`
160
+ - `command`
161
+ - `description?`
162
+ - `timeout`
163
+ - `status`
164
+ - `startedAt`
165
+ - `completedAt?`
166
+ - `exitCode?`
167
+ - `outputLines`
168
+ - `outputBuffer`
169
+ - `pid`
170
+ - `proc`
171
+ - `abortController`
172
+ - `waiters`
173
+ - `completionCallbacks`
174
+
175
+ ### Monitor transitions
176
+
177
+ | From | Event | To | Notes |
178
+ |---|---|---|---|
179
+ | none | `MonitorCreate` | `running` | child process spawned |
180
+ | `running` | child `close` exit code 0 | `completed` | emits `monitor:done` and direct completion callbacks |
181
+ | `running` | child `close` nonzero | `error` | emits `monitor:error` |
182
+ | `running` | child `error` | `error` | emits `monitor:error` |
183
+ | `running` | `MonitorStop` | `stopped` | SIGTERM then SIGKILL fallback |
184
+ | `completed/error` | 30s retention timeout | pruned | removed from `processes` map |
185
+
186
+ ### Hidden / implicit monitor state
187
+
188
+ - completion callbacks registered/not-registered for `onDone`
189
+ - monitor retained-for-UI vs logically finished
190
+ - event-based completion signaling and direct callback signaling both exist
191
+ - process liveness is split between child process reality and `entry.status`
192
+
193
+ ### Monitor sharp edges
194
+
195
+ - Completion has two signaling channels: event bus and direct callback.
196
+ - `stopped` bypasses the `finish()` helper and therefore differs from `completed/error` paths.
197
+ - Retention window is time-based but only in memory; no persisted monitor store exists.
198
+
199
+ ## 4. Pending notification / wake delivery state
200
+
201
+ This is the largest implicit state area today.
202
+
203
+ ### Structures
204
+
205
+ - `agentRunning: boolean`
206
+ - `_latestCtx?.hasPendingMessages()`
207
+ - `pendingNotifications: Map<string, PendingNotification>`
208
+ - `flushPromise?: Promise<void>`
209
+
210
+ ### Wake pipeline states
211
+
212
+ These are not explicit enum values today, but they exist behaviorally:
213
+
214
+ - generated (`buildLoopFireMessage`)
215
+ - queued (`pendingNotifications.set(...)`)
216
+ - deduped/replaced (same recurring loop key)
217
+ - blocked by active agent
218
+ - blocked by pending messages
219
+ - delivered via `pi.sendMessage(..., { deliverAs: "steer", triggerTurn: true })`
220
+ - dropped due to zero pending tasks at delivery time
221
+ - cleared on `session_shutdown` / `session_switch`
222
+
223
+ ### Notification transitions
224
+
225
+ | From | Event | To | Notes |
226
+ |---|---|---|---|
227
+ | none | `onLoopFire` / monitor completion / backlog bootstrap | generated | message built |
228
+ | generated | `queueOrDeliverNotification` | queued | stored in map |
229
+ | queued | `flushPendingNotifications` while idle | delivered | sends custom message |
230
+ | queued recurring | newer fire same key | replaced | latest prompt wins |
231
+ | queued | delivery-time pending tasks == 0 and `autoTask` true | dropped | triggers completed-task cleanup |
232
+ | queued | `session_shutdown` / `session_switch` | cleared | dropped without delivery |
233
+
234
+ ### Hidden / implicit notification state
235
+
236
+ - one-shot vs recurring dedupe is encoded in key generation
237
+ - delivery eligibility is a conjunction of agent idle + pending-message policy + autoTask pending count
238
+ - some wake sources are event-driven, some direct-callback driven
239
+
240
+ ## 5. Cross-entity coordination state
241
+
242
+ These are coordination rules that span multiple entities and should likely become explicit reducer events.
243
+
244
+ ### Existing coordination rules
245
+
246
+ 1. **Task backlog threshold -> auto worker loop creation**
247
+ - when native pending count reaches `>= 5`
248
+ - creates a hybrid task-backlog loop
249
+
250
+ 2. **Zero pending tasks -> backlog loop deletion**
251
+ - backlog worker loops are deleted when pending count reaches `0`
252
+
253
+ 3. **Successful git commit -> completed task pruning**
254
+ - `tool_execution_end` for successful `bash` `git commit` calls `cleanDoneTasks()`
255
+
256
+ 4. **Monitor completion -> onDone wake**
257
+ - direct callback path for `MonitorCreate(..., onDone)`
258
+ - generic event path remains for manual `LoopCreate trigger='monitor:done'`
259
+
260
+ 5. **Session lifecycle -> store/scheduler/reset behavior**
261
+ - `turn_start`, `before_agent_start`, `agent_start`, `agent_end`, `session_switch`, `session_shutdown`
262
+
263
+ ### Hidden coordinator state
264
+
265
+ - whether `pi-tasks` is available is global mutable session state
266
+ - whether native task tools are registered is global mutable session state
267
+ - current session id affects storage paths but is not part of entity state
268
+ - worker loop ownership is inferred rather than strongly modeled
269
+
270
+ ## 6. Duplicate / overlapping state encodings
271
+
272
+ ### Loop status is spread across four places
273
+
274
+ A loop's real status is currently determined by all of:
275
+
276
+ - `LoopEntry.status`
277
+ - `CronScheduler.fireTimes`
278
+ - `TriggerSystem.eventSubscriptions`
279
+ - `pendingNotifications`
280
+
281
+ A loop can be `active` in the store while simultaneously:
282
+
283
+ - not armed in scheduler
284
+ - unsubscribed from events
285
+ - queued for delivery
286
+ - already logically terminal but not yet deleted
287
+
288
+ ### Monitor completion is dual-path
289
+
290
+ Completion signaling uses both:
291
+
292
+ - `monitor:done` / `monitor:error` events
293
+ - `completionCallbacks`
294
+
295
+ This is correct for reliability today, but it means state transitions are duplicated conceptually.
296
+
297
+ ### Task completion and pruning are separate lifecycles
298
+
299
+ Task lifecycle currently has at least:
300
+
301
+ - active work state (`pending` / `in_progress`)
302
+ - finished state (`completed`)
303
+ - retention state (still listed vs swept)
304
+
305
+ Only the first two are explicit.
306
+
307
+ ## 7. Suggested normalization targets for the reducer refactor
308
+
309
+ ### Task machine
310
+
311
+ Recommended explicit states:
312
+
313
+ - `pending`
314
+ - `in_progress`
315
+ - `completed_retained`
316
+ - `pruned` (terminal/effect boundary, may remain implicit in storage)
317
+
318
+ Recommended explicit events:
319
+
320
+ - `TASK_CREATED`
321
+ - `TASK_STARTED`
322
+ - `TASK_COMPLETED`
323
+ - `TASK_REOPENED`
324
+ - `TASK_DELETED`
325
+ - `TASKS_PRUNED`
326
+
327
+ ### Loop machine
328
+
329
+ Recommended explicit states:
330
+
331
+ - `active_idle`
332
+ - `active_debounced`
333
+ - `queued_for_delivery`
334
+ - `paused`
335
+ - `completed_terminal`
336
+ - `deleted`
337
+
338
+ Recommended explicit events:
339
+
340
+ - `LOOP_CREATED`
341
+ - `LOOP_ARMED`
342
+ - `LOOP_FIRED`
343
+ - `LOOP_QUEUED`
344
+ - `LOOP_DELIVERED`
345
+ - `LOOP_PAUSED`
346
+ - `LOOP_RESUMED`
347
+ - `LOOP_DELETED`
348
+ - `LOOP_EXPIRED`
349
+ - `LOOP_MAX_FIRES_REACHED`
350
+
351
+ ### Monitor machine
352
+
353
+ Recommended explicit states:
354
+
355
+ - `running`
356
+ - `completed_retained`
357
+ - `error_retained`
358
+ - `stopped_retained`
359
+ - `pruned`
360
+
361
+ Recommended explicit events:
362
+
363
+ - `MONITOR_CREATED`
364
+ - `MONITOR_OUTPUT`
365
+ - `MONITOR_COMPLETED`
366
+ - `MONITOR_ERRORED`
367
+ - `MONITOR_STOPPED`
368
+ - `MONITOR_PRUNED`
369
+ - `MONITOR_ONDONE_REGISTERED`
370
+
371
+ ### Notification machine
372
+
373
+ Recommended explicit states:
374
+
375
+ - `queued`
376
+ - `blocked_by_agent`
377
+ - `blocked_by_pending_messages`
378
+ - `ready_to_deliver`
379
+ - `delivered`
380
+ - `dropped`
381
+ - `cleared`
382
+
383
+ Recommended explicit events:
384
+
385
+ - `NOTIFICATION_QUEUED`
386
+ - `NOTIFICATION_REPLACED`
387
+ - `NOTIFICATION_DELIVERED`
388
+ - `NOTIFICATION_DROPPED`
389
+ - `NOTIFICATION_CLEARED`
390
+
391
+ ## 8. Refactor hotspots
392
+
393
+ These are the highest-value places to extract into reducer-driven logic:
394
+
395
+ 1. `src/index.ts`
396
+ - pending notification logic
397
+ - task backlog loop creation/cleanup
398
+ - commit-driven completed-task pruning
399
+ - session lifecycle hooks
400
+
401
+ 2. `src/trigger-system.ts`
402
+ - event subscription vs fire vs terminal deletion
403
+
404
+ 3. `src/scheduler.ts`
405
+ - active/armed/expired/maxFires scheduling transitions
406
+
407
+ 4. `src/monitor-manager.ts`
408
+ - unify retained terminal states and completion effect dispatch
409
+
410
+ 5. `src/task-store.ts` + native task tool handlers
411
+ - completion retention vs pruning policy
412
+
413
+ ## 9. Minimum next test matrix enabled by this inventory
414
+
415
+ The next test pass should assert:
416
+
417
+ - a loop cannot remain `active` after terminal deletion conditions
418
+ - a monitor `onDone` wake is delivered exactly once
419
+ - a task backlog loop with zero pending tasks is eventually deleted
420
+ - completed tasks can be pruned without resetting `nextId`
421
+ - recurring wake dedupe preserves only the latest queued prompt
422
+ - session switch clears pending notifications but preserves the intended persisted entities
423
+
424
+ ## 10. Summary
425
+
426
+ Current behavior works, but state is encoded across:
427
+
428
+ - persisted entity fields
429
+ - scheduler maps
430
+ - event subscription maps
431
+ - callback arrays
432
+ - booleans
433
+ - pending wake queues
434
+ - provider-mode globals
435
+
436
+ The reducer/state-machine refactor should make these lifecycle phases explicit, move hidden coordination state into named transitions, and separate pure transition logic from effect execution.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.4.11",
3
+ "version": "0.5.1",
4
4
  "description": "A pi extension for cron/event-based agent re-wake loops and background process monitoring.",
5
5
  "author": "trevonistrevon",
6
6
  "license": "MIT",