Idle-evicted roots now give back their per-context threads, config file watches, semantic embedding models and compiled output filters, so a long-running daemon no longer grows with every project it has served.
Replaced each root's status-debounce and inspect-dispatch threads with one process-wide debounce thread and direct submission onto the shared inspect pool.
Status pushes now build and send on their own short-lived threads, so one project on a slow or unavailable mount no longer delays other projects' status.
The filter-registry release is skipped while the root still holds background task records; reaper sweeps retry it once the last record is gone, and a background bash spawn reloads a released registry when needed.
A semantic lane still loading is released by idle eviction too, so its worker exits instead of waiting for as long as the root stays unbound.
Windows now counts the process's threads from a Toolhelp snapshot, so the retention tests cannot pass on an uncounted census.
Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues were found.
Summary
Idle projects release config watches, embedding models, compiled filters, and semantic lanes. Status updates share one timer thread, and inspect jobs go straight to the shared pool.
Idle roots let go of resources they can rebuild later.
Status updates from one project no longer hold up another.
Inspect jobs go straight to the shared process-wide pool.
Windows lifecycle counts now include the process’s threads.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Project becomes idle and unbound] --> B[Evict loaded artifacts]
B --> C[Release watches, embedding model, and semantic lane]
B --> D{Task records remain?}
D -->|No| E[Release compiled filters]
D -->|Yes| F[Keep filters for retained output]
F --> G[Later sweep finds no task records]
G --> E