pi-jar 0.1.8 → 0.2.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.
@@ -0,0 +1,731 @@
1
+ # Long-Session Stability and Performance Plan
2
+
3
+ ## Status
4
+
5
+ Implemented in v0.1.9 alongside the legacy footer-settings cleanup. The sections below preserve the audit rationale and acceptance criteria; the delivery sequence was consolidated into this release rather than separate follow-up PRs. The optional P2 quota path was tightened with cached reads and failure backoff, while further side-usage aggregation remains measurement-dependent. The “current behavior” sections and PR A–G steps describe the pre-0.1.9 baseline and original proposal, not the shipped behavior.
6
+
7
+ A separate detailed design for replacing file-backed agent discussion lives in [DISCUSSION_BROKER_PLAN.md](./DISCUSSION_BROKER_PLAN.md).
8
+
9
+ ## Problem
10
+
11
+ Pi Jar is responsive in short sessions but can become increasingly slow or unresponsive as a session accumulates history, retained agents, background activity, worktree recovery state and UI redraw pressure.
12
+
13
+ The audit found seven areas worth fixing:
14
+
15
+ 1. retained Pi subagent processes create steady RAM pressure and may eventually push the host into swap,
16
+ 2. whole-TUI redraw cost grows with session history and is multiplied by background `requestRender()` calls,
17
+ 3. `getContextUsage()` is sampled on every `message_end`, potentially walking an increasingly large session for user, assistant and tool-result messages,
18
+ 4. failed/conflicted worktree recovery records are intentionally retained but have no visible hard global bound,
19
+ 5. background shells are individually bounded but can still retain significant aggregate output/process state,
20
+ 6. `jar_discuss` uses a shared JSON paper plus filesystem locking,
21
+ 7. quota/usage telemetry is mostly bounded already, but should remain explicitly cheap and observable.
22
+
23
+ The goal is not to remove useful Pi Jar features. It is to make their steady-state cost proportional to current work rather than total session age.
24
+
25
+ ## Design principles
26
+
27
+ - Prefer event-driven state over polling.
28
+ - Bound every retained collection by count and/or bytes.
29
+ - Separate durable user state from reconstructable runtime state.
30
+ - Do not keep a full child process alive merely to preserve resumability if a session file/worktree is enough.
31
+ - Avoid work proportional to full session history on high-frequency events.
32
+ - Avoid whole-transcript redraws for background deltas that are not immediately useful.
33
+ - Never silently discard conflicted user changes.
34
+ - Add measurements before or alongside optimizations so regressions can be proven instead of guessed.
35
+
36
+ ## Priority order
37
+
38
+ ### P0
39
+
40
+ 1. Retained subagent process hibernation.
41
+ 2. Background render throttling/coalescing for long sessions.
42
+ 3. Context-usage sampling reduction.
43
+
44
+ These are the most likely contributors to the reported "slow and unresponsive on a long session" behavior.
45
+
46
+ ### P1
47
+
48
+ 4. Bound unresolved worktree recovery state.
49
+ 5. Add aggregate shell budgets and lifecycle diagnostics.
50
+ 6. Replace discussion-paper locking with the parent-owned broker.
51
+
52
+ ### P2
53
+
54
+ 7. Tighten quota/telemetry scheduling and add diagnostics.
55
+
56
+ The lower priority does not mean "ignore"; it means the current implementation is already comparatively bounded.
57
+
58
+ ---
59
+
60
+ # 1. Retained Pi subagent processes
61
+
62
+ ## Current behavior
63
+
64
+ Retained scout/fork/worktree agents remain as live child Pi RPC processes after they settle so the moderator can later peek, steer, pause, resume or ask without rebuilding their context.
65
+
66
+ The retained pool is bounded by `maxSubagents`, but each slot is a full Pi process with its own runtime, model/session state and IPC resources. A configured pool of 8 or 16 can therefore be expensive even when most agents are idle.
67
+
68
+ ## Risk
69
+
70
+ - steady RSS rises with retained children,
71
+ - inactive children retain runtime/context memory,
72
+ - host memory pressure can trigger swap,
73
+ - once swapping begins, both Pi and Pi Jar can appear globally unresponsive,
74
+ - keeping idle processes alive couples resumability to process lifetime unnecessarily.
75
+
76
+ ## Plan: hibernate settled retained agents
77
+
78
+ Split "retained agent identity/state" from "live child process".
79
+
80
+ A retained record should keep only the data required to resume:
81
+
82
+ - agent key/name/role/model,
83
+ - task/status,
84
+ - child session directory/session file reference,
85
+ - worktree path when applicable,
86
+ - bounded latest report/todos/summary,
87
+ - timestamps and lifecycle state.
88
+
89
+ After a retained child reaches a safe settled/idle boundary:
90
+
91
+ 1. persist/flush the child session state,
92
+ 2. close the RPC child process,
93
+ 3. keep a lightweight `hibernated` record,
94
+ 4. preserve its worktree/session directory,
95
+ 5. respawn Pi only when the moderator explicitly resumes, asks or steers that agent.
96
+
97
+ Scouts should be especially aggressive candidates for hibernation because their normal output is a report, not a continuously running service.
98
+
99
+ ## Lifecycle states
100
+
101
+ Prefer explicit states instead of overloading `idle`:
102
+
103
+ ```text
104
+ starting -> running -> idle -> hibernated
105
+ \-> paused
106
+ running/idle/paused/hibernated -> stopping -> stopped
107
+ ```
108
+
109
+ A hibernated record counts against retained-agent capacity but consumes no Pi child process.
110
+
111
+ ## Important implementation caveat
112
+
113
+ Current cleanup logic removes private session directories when child handles close. Hibernation must separate:
114
+
115
+ - "child process closed" from
116
+ - "agent record permanently discarded".
117
+
118
+ Do not delete the fork/session/worktree backing state during hibernation.
119
+
120
+ ## Tests
121
+
122
+ - settled retained agent transitions to hibernated without losing session metadata,
123
+ - resume creates a new RPC child and continues the same child session,
124
+ - worktree contents survive hibernation,
125
+ - stop/discard removes the retained backing state,
126
+ - session shutdown terminates all live children and removes all discardable temporary state,
127
+ - pool accounting includes hibernated records,
128
+ - repeated hibernate/resume cycles do not leak handles/listeners/directories.
129
+
130
+ ## Acceptance criteria
131
+
132
+ - an idle retained agent consumes no child Pi process,
133
+ - retained-agent behavior remains functionally equivalent after resume,
134
+ - process count falls after agents settle,
135
+ - parent + child RSS plateaus in a synthetic long-session test instead of growing with every retained settled agent.
136
+
137
+ ---
138
+
139
+ # 2. Long-session TUI redraw amplification
140
+
141
+ ## Current behavior
142
+
143
+ Pi's regular TUI redraw cost grows with transcript/session size. Pi Jar already acknowledges this with a large-session warning and avoids some timer-driven repaint behavior.
144
+
145
+ However, several background sources can still request renders:
146
+
147
+ - subagent streaming/status updates,
148
+ - shell status/output changes,
149
+ - footer changes,
150
+ - activity state,
151
+ - other extension state transitions.
152
+
153
+ Subagent live updates can happen frequently, while the parent footer repaint is currently coalesced to roughly a few redraws per second.
154
+
155
+ That rate is tolerable for a small transcript but increasingly expensive when each frame must walk/render a large history.
156
+
157
+ ## Risk
158
+
159
+ The underlying cost is multiplicative:
160
+
161
+ ```text
162
+ cost per full render × background render frequency
163
+ ```
164
+
165
+ As transcript size increases, a previously harmless 250 ms repaint cadence becomes expensive.
166
+
167
+ ## Plan: adaptive rendering budget
168
+
169
+ Introduce a central background render scheduler rather than letting independent sources effectively define cadence.
170
+
171
+ The scheduler should distinguish:
172
+
173
+ - foreground/user-visible state transitions,
174
+ - background progress deltas,
175
+ - streaming text deltas,
176
+ - timer-only elapsed-time changes.
177
+
178
+ Suggested behavior:
179
+
180
+ ```text
181
+ small session background minimum interval ~250 ms
182
+ medium session background minimum interval ~500 ms
183
+ large session background minimum interval ~1000 ms
184
+ very large state transitions only unless an activity view is focused
185
+ ```
186
+
187
+ Use measured branch/session entry count as one input, not the only one.
188
+
189
+ ## Separate mutation from repaint
190
+
191
+ Registry/shell state may update often without requiring an immediate whole-TUI repaint.
192
+
193
+ Prefer:
194
+
195
+ ```text
196
+ state changed
197
+ -> mark dirty
198
+ -> one scheduled background render
199
+ ```
200
+
201
+ instead of:
202
+
203
+ ```text
204
+ state changed
205
+ -> requestRender()
206
+ -> state changed
207
+ -> requestRender()
208
+ -> ...
209
+ ```
210
+
211
+ When the dedicated Activity view is open, it may consume a higher-frequency lightweight update path because that is the screen the user explicitly chose to watch.
212
+
213
+ ## Streaming policy
214
+
215
+ For retained subagents:
216
+
217
+ - store live tail updates at the current bounded size,
218
+ - update Activity view responsively,
219
+ - do not repaint the main transcript/footer for every partial text delta,
220
+ - main view should update on coarse progress/state changes or the adaptive budget.
221
+
222
+ ## Instrumentation
223
+
224
+ Count:
225
+
226
+ - requested renders,
227
+ - coalesced renders,
228
+ - actual renders requested from Pi,
229
+ - source of each request,
230
+ - current branch entry count.
231
+
232
+ Expose the counters through diagnostics, not the normal footer.
233
+
234
+ ## Tests
235
+
236
+ - bursts of 100 subagent updates coalesce to the expected number of background requests,
237
+ - long-session mode uses the larger minimum interval,
238
+ - foreground state changes can still render promptly,
239
+ - timer-only activity does not cause excessive main-view repainting,
240
+ - scheduler is disposed on session shutdown.
241
+
242
+ ## Acceptance criteria
243
+
244
+ - background updates cannot cause unbounded render frequency,
245
+ - actual main-view render requests decrease as session size crosses configured thresholds,
246
+ - focused Activity view remains useful,
247
+ - no visible loss of final state when updates are coalesced.
248
+
249
+ ---
250
+
251
+ # 3. Context usage recomputation
252
+
253
+ ## Current behavior
254
+
255
+ Pi Jar samples `ctx.getContextUsage()` on `message_end`.
256
+
257
+ `message_end` is not just "one user turn ended"; it can occur for user, assistant and tool-result messages. In a tool-heavy long session this can therefore call context-usage computation many times per turn.
258
+
259
+ If the underlying computation walks session/context state, its cost grows with session size.
260
+
261
+ ## Risk
262
+
263
+ This creates a classic hidden long-session tax:
264
+
265
+ ```text
266
+ increasing session size × many message_end events
267
+ ```
268
+
269
+ The footer value does not need tool-result-level freshness.
270
+
271
+ ## Plan
272
+
273
+ Move context usage to a lower-frequency event policy.
274
+
275
+ Always refresh on:
276
+
277
+ - session start,
278
+ - session tree change,
279
+ - session compact,
280
+ - model selection,
281
+ - session info change.
282
+
283
+ During an active turn:
284
+
285
+ - mark context usage dirty on message changes,
286
+ - debounce/throttle recomputation,
287
+ - prefer final assistant/agent-settled boundaries,
288
+ - cap active-turn recomputation to at most once per configurable short interval if the UI actually needs it.
289
+
290
+ A default of roughly 500-1000 ms during active work is sufficient; zero recomputations between meaningful render opportunities is even better.
291
+
292
+ ## Cache behavior
293
+
294
+ Keep the last sampled value and timestamp.
295
+
296
+ The footer should display a slightly stale cached context percentage rather than synchronously forcing a full recomputation for every message event.
297
+
298
+ ## Instrumentation
299
+
300
+ Measure:
301
+
302
+ - `getContextUsage()` invocation count,
303
+ - total/median/max duration,
304
+ - last sample age.
305
+
306
+ ## Tests
307
+
308
+ - multiple tool-result `message_end` events inside a burst do not each recompute,
309
+ - session compact/tree/model events force an immediate refresh,
310
+ - cached value remains available between samples,
311
+ - shutdown clears timers.
312
+
313
+ ## Acceptance criteria
314
+
315
+ - context usage recomputation is no longer proportional to tool-result count,
316
+ - invocation count for a synthetic tool-heavy turn drops substantially,
317
+ - context display remains correct after compact/tree/model transitions.
318
+
319
+ ---
320
+
321
+ # 4. Failed/conflicted worktree recovery records
322
+
323
+ ## Current behavior
324
+
325
+ Finished delegate records are normally swept, but records with a retained worktree recovery workspace are excluded because unresolved/conflicted changes are valuable recovery state.
326
+
327
+ That protects user work, but repeated failed/conflicted worktrees can accumulate beyond the normal finished-record cap.
328
+
329
+ ## Risk
330
+
331
+ Each unresolved recovery item may retain:
332
+
333
+ - registry record,
334
+ - bounded transcript/report data,
335
+ - worktree metadata,
336
+ - filesystem worktree contents,
337
+ - other reconciliation state.
338
+
339
+ The correct behavior is not to delete these automatically, but "never delete" is not the same as "allow unlimited accumulation".
340
+
341
+ ## Plan: explicit recovery inventory and admission bound
342
+
343
+ Create an explicit `RecoveryRecord` concept separate from normal finished subagent history.
344
+
345
+ Keep only the metadata needed to recover/reconcile:
346
+
347
+ - agent key/name,
348
+ - worktree path,
349
+ - base/head refs where relevant,
350
+ - conflicting/changed files summary,
351
+ - created/failed timestamp,
352
+ - concise error/reason,
353
+ - state: unresolved/resolved/discarded.
354
+
355
+ Release bulky runtime/transcript state once a failed agent becomes a recovery record.
356
+
357
+ Set a small hard maximum unresolved recovery count, for example 4 or 8.
358
+
359
+ When the limit is reached:
360
+
361
+ - do not silently delete the oldest workspace,
362
+ - refuse to start another worktree delegate that could require recovery,
363
+ - show which recovery records must be resolved/discarded first.
364
+
365
+ ## User operations
366
+
367
+ Expose clear actions through existing subagent/activity controls:
368
+
369
+ - inspect recovery,
370
+ - retry apply/reconcile,
371
+ - discard recovery,
372
+ - optionally open/show path.
373
+
374
+ ## Cleanup
375
+
376
+ Resolved/discarded records must:
377
+
378
+ - remove their temporary worktree,
379
+ - unregister listeners/state,
380
+ - disappear from the recovery inventory.
381
+
382
+ ## Tests
383
+
384
+ - failed worktree is converted to lightweight recovery state,
385
+ - normal finished transcript sweep can proceed independently,
386
+ - unresolved count never exceeds the configured maximum,
387
+ - new worktree delegation is blocked at the limit without deleting existing changes,
388
+ - resolving/discarding frees capacity and removes filesystem state,
389
+ - session shutdown behavior is explicit and does not silently destroy unresolved user changes.
390
+
391
+ ## Acceptance criteria
392
+
393
+ - unresolved user changes remain safe,
394
+ - JS memory associated with a failed child is reduced after conversion to recovery state,
395
+ - recovery-state count is bounded,
396
+ - worktree creation cannot grow recovery state indefinitely.
397
+
398
+ ---
399
+
400
+ # 5. Shell buffers and background processes
401
+
402
+ ## Current behavior
403
+
404
+ `jar_shell` already has useful bounds:
405
+
406
+ - max concurrent shell jobs,
407
+ - per-job line/character limits,
408
+ - approximately bounded retained text per job,
409
+ - pruning of finished shell records,
410
+ - process-group termination on supported platforms.
411
+
412
+ This is substantially safer than an unbounded log collector.
413
+
414
+ ## Remaining risk
415
+
416
+ Aggregate memory can still be meaningful if many retained/finished jobs each hold their maximum buffer. Long-running service jobs also legitimately keep OS processes alive.
417
+
418
+ A user may therefore have:
419
+
420
+ - several live process trees,
421
+ - retained logs across many finished jobs,
422
+ - extra footer/activity update pressure.
423
+
424
+ ## Plan
425
+
426
+ ### Global retained-output budget
427
+
428
+ Add a manager-level byte/character budget in addition to per-job caps.
429
+
430
+ When total retained output exceeds the global budget:
431
+
432
+ 1. trim oldest finished-job output first,
433
+ 2. then oldest noncritical retained output,
434
+ 3. preserve a tail for each live job,
435
+ 4. never kill a process merely to satisfy a log-memory budget.
436
+
437
+ ### Finished-job compaction
438
+
439
+ Once a job result has been surfaced, replace old full output with:
440
+
441
+ - exit status,
442
+ - command,
443
+ - timestamps/duration,
444
+ - bounded tail,
445
+ - matched watch reason if any.
446
+
447
+ ### Service classification
448
+
449
+ Allow long-lived service/watch jobs to be marked clearly so diagnostics can distinguish expected live processes from accidental leftovers.
450
+
451
+ ### Diagnostics
452
+
453
+ Expose:
454
+
455
+ - live shell count,
456
+ - finished retained count,
457
+ - total retained output chars/bytes,
458
+ - per-job retained size,
459
+ - PIDs/process groups,
460
+ - oldest live job.
461
+
462
+ ## Tests
463
+
464
+ - global log budget is enforced across jobs,
465
+ - live jobs retain useful tails,
466
+ - pruning output never alters process lifecycle,
467
+ - process-group kill still cleans descendants,
468
+ - finished jobs compact after result consumption,
469
+ - shutdown leaves no managed live shell processes.
470
+
471
+ ## Acceptance criteria
472
+
473
+ - total shell log memory is globally bounded,
474
+ - many completed jobs cannot accumulate near-per-job maximum indefinitely,
475
+ - live services remain functional and observable.
476
+
477
+ ---
478
+
479
+ # 6. Discussion-paper locking
480
+
481
+ The current shared `paper.json` + `.lock/` mechanism should be replaced, not tuned.
482
+
483
+ See [DISCUSSION_BROKER_PLAN.md](./DISCUSSION_BROKER_PLAN.md) for the detailed design.
484
+
485
+ ## Summary
486
+
487
+ Move discussion ownership into the parent process:
488
+
489
+ - in-memory `DiscussionHub`,
490
+ - session-local IPC,
491
+ - parent-assigned sequence IDs,
492
+ - authenticated child identity,
493
+ - per-agent mailboxes/cursors,
494
+ - targeted unread delivery,
495
+ - bounded retention,
496
+ - no polling,
497
+ - no shared file lock,
498
+ - no whole-paper rewrite per message.
499
+
500
+ ## Acceptance criteria
501
+
502
+ - zero steady-state filesystem operations for discussion,
503
+ - zero lock retries/stale-lock recovery,
504
+ - deterministic concurrent message ordering,
505
+ - bounded state,
506
+ - no automatic LLM ping-pong.
507
+
508
+ ---
509
+
510
+ # 7. Quota and telemetry overhead
511
+
512
+ ## Current behavior
513
+
514
+ The audit did not find a general external telemetry upload loop.
515
+
516
+ Current usage-related work is mostly bounded:
517
+
518
+ - process RSS is sampled lazily,
519
+ - side-model usage records have a cap,
520
+ - quota requests are cached for several minutes,
521
+ - one pending quota request per provider is allowed,
522
+ - network requests have a timeout,
523
+ - session cost is incrementally updated for normal assistant messages and fully recomputed on major session events.
524
+
525
+ This area is therefore lower risk than retained child processes or render/context scans.
526
+
527
+ ## Plan
528
+
529
+ Keep it boring and cheap.
530
+
531
+ ### Central sampling policy
532
+
533
+ Avoid independent UI code deciding when to perform expensive samples.
534
+
535
+ Maintain cached facts with explicit timestamps:
536
+
537
+ - RSS,
538
+ - context usage,
539
+ - quota,
540
+ - session cost,
541
+ - side-call totals.
542
+
543
+ Renderers read cached values only.
544
+
545
+ ### Quota
546
+
547
+ Keep quota:
548
+
549
+ - opt-in/feature-controlled as today,
550
+ - one in-flight request per provider,
551
+ - cached for minutes,
552
+ - never triggered by every render,
553
+ - delayed away from startup critical path.
554
+
555
+ If a cached quota value is stale, render the stale value/unknown state and refresh asynchronously rather than blocking TUI work.
556
+
557
+ ### Usage records
558
+
559
+ Continue to cap side-model usage history. If detailed historic calls are not needed by UI, consider retaining aggregate totals plus a smaller recent-call ring instead of hundreds of full records.
560
+
561
+ ### No generic event log
562
+
563
+ Do not introduce a high-frequency telemetry file just to diagnose performance. Use in-memory counters and an explicit diagnostic snapshot.
564
+
565
+ ## Tests
566
+
567
+ - repeated footer renders do not cause repeated network quota requests,
568
+ - provider requests deduplicate while one is pending,
569
+ - failed quota calls obey TTL/backoff and cannot hot-loop,
570
+ - side-usage history remains bounded,
571
+ - session start clears session-scoped counters.
572
+
573
+ ## Acceptance criteria
574
+
575
+ - telemetry/quota work remains effectively constant with session age,
576
+ - no render path performs blocking network I/O,
577
+ - no diagnostic collection itself becomes a source of long-session growth.
578
+
579
+ ---
580
+
581
+ # Diagnostics: /jar perf
582
+
583
+ Before or alongside the P0 changes, add a compact diagnostic command so future regressions can be measured.
584
+
585
+ Suggested output:
586
+
587
+ ```text
588
+ session
589
+ branch entries 1,142
590
+ context 78%
591
+ parent rss 612 MiB
592
+
593
+ subagents
594
+ retained 4
595
+ live children 2
596
+ hibernated 2
597
+ child rss ...
598
+ recovery worktrees 1 / 4
599
+
600
+ render
601
+ requested ...
602
+ coalesced ...
603
+ actual ...
604
+ background rate ...
605
+ last sources ...
606
+
607
+ context sampling
608
+ calls ...
609
+ total time ...
610
+ max time ...
611
+ last sample age ...
612
+
613
+ shells
614
+ live 2
615
+ retained finished 8
616
+ output retained 2.4 MiB
617
+
618
+ discussion
619
+ transport broker
620
+ messages 31 / 128
621
+ bytes ...
622
+ unanswered 2
623
+
624
+ quota
625
+ provider cache age ...
626
+ pending no
627
+ ```
628
+
629
+ Diagnostics should be calculated on demand where possible and should not create another permanent polling loop. Nature has enough irony already.
630
+
631
+ # Delivery plan
632
+
633
+ The following sequence was the original design decomposition; v0.1.9 delivered PR A–F together.
634
+
635
+ ## PR A: measurement and context sampling
636
+
637
+ - add `/jar perf` counters/snapshot,
638
+ - cache and throttle `getContextUsage()`,
639
+ - add tests for message bursts and lifecycle cleanup.
640
+
641
+ This gives a baseline before the larger lifecycle changes.
642
+
643
+ ## PR B: adaptive background rendering
644
+
645
+ - central background render scheduler,
646
+ - source-aware coalescing,
647
+ - adaptive cadence by session size,
648
+ - focused Activity-view behavior,
649
+ - render counters.
650
+
651
+ ## PR C: retained subagent hibernation
652
+
653
+ - explicit hibernated lifecycle state,
654
+ - respawn/resume from retained session metadata,
655
+ - separate child close from permanent cleanup,
656
+ - process/RSS validation.
657
+
658
+ This is likely the highest-memory-impact change and deserves its own PR.
659
+
660
+ ## PR D: recovery-state bound
661
+
662
+ - lightweight `RecoveryRecord`,
663
+ - hard unresolved-worktree admission limit,
664
+ - resolve/discard flows,
665
+ - release bulky finished-agent state.
666
+
667
+ ## PR E: shell aggregate bounds
668
+
669
+ - global output budget,
670
+ - finished-output compaction,
671
+ - diagnostics.
672
+
673
+ ## PR F: discussion broker
674
+
675
+ Implement [DISCUSSION_BROKER_PLAN.md](./DISCUSSION_BROKER_PLAN.md) in its own focused PR.
676
+
677
+ ## PR G: telemetry cleanup if measurements justify it
678
+
679
+ Only optimize quota/side-usage paths further if `/jar perf` data shows meaningful overhead. Do not churn already-cheap code for decorative optimization.
680
+
681
+ # End-to-end validation
682
+
683
+ Create a deterministic long-session stress harness that does not require real model calls where possible.
684
+
685
+ Exercise:
686
+
687
+ - hundreds/thousands of synthetic session entries,
688
+ - tool-result bursts,
689
+ - repeated background subagent updates,
690
+ - retained agents settling/resuming,
691
+ - several shell jobs,
692
+ - failed worktree recovery,
693
+ - concurrent discussion operations.
694
+
695
+ Measure before and after:
696
+
697
+ - parent RSS,
698
+ - child process count,
699
+ - total child RSS where available,
700
+ - event-loop responsiveness,
701
+ - actual render requests/minute,
702
+ - `getContextUsage()` calls and duration,
703
+ - shell retained bytes,
704
+ - registry/recovery counts,
705
+ - discussion operation latency.
706
+
707
+ ## Success criteria
708
+
709
+ For a fixed workload:
710
+
711
+ - memory reaches a stable plateau after bounded caches fill,
712
+ - settled retained agents do not require live Pi processes,
713
+ - background render rate decreases for large sessions,
714
+ - context-usage calls are not proportional to tool-result count,
715
+ - unresolved recovery state cannot grow beyond its configured bound,
716
+ - shell retained output cannot exceed the global budget,
717
+ - discussion has no lock contention,
718
+ - quota requests remain cache-bound rather than render-bound,
719
+ - session shutdown leaves no live managed child/shell processes or repaint timers.
720
+
721
+ # Related cleanup already included in this PR
722
+
723
+ `$AGENT_DIR/pi-jar-footer.json` support is retired now:
724
+
725
+ - `pi-jar-settings.json` is the sole visual/footer settings source,
726
+ - legacy footer files are no longer read, written or migrated,
727
+ - existing legacy files are ignored and left untouched,
728
+ - obsolete persistence APIs/tests are removed,
729
+ - regression tests verify the legacy file cannot influence current settings.
730
+
731
+ This reduces configuration compatibility surface before the larger runtime lifecycle work begins.