80e4fb5995
An agent turn can spawn a long-running background subprocess (e.g. `next build`) and later be abandoned via inactivity timeout, /stop, /new, or a client disconnect. Before this fix the gateway interrupted the agent loop but never touched the subprocess: it kept running inside the gateway's cgroup, unbounded, until memory pressure starved the event loop and made every platform/cron look hung (#76115). The process registry already knew how to kill a process tree — the missing piece was per-turn ownership: nothing distinguished a process that predates the turn (must survive), a process the turn started and finished successfully (must survive), and a process an abandoned turn left running (must be reaped). - tools/process_registry.py: snapshot_running_ids() captures a turn's starting baseline; kill_started_since() reaps only IDs created after it, scoped to one task_id. - gateway/turn_context.py: TurnContext carries process_task_id + process_baseline so the timeout/interrupt paths can reach them. - gateway/run.py: baseline is snapshotted right before the turn's executor task starts; the inactivity-timeout path and the explicit /stop|/new|disconnect interrupt path both reap via the same helper. A daemon-thread watchdog backs up the asyncio-based timeout poll, since a starved event loop is exactly the failure mode this bug causes. The turn's own worker clears its ownership markers the instant it finishes, closing a race where a /stop landing right after normal completion could reap a background process the turn deliberately left running. Related but insufficient on their own: #37454 (cgroup ExecStopPost reaper only fires on service restart) and #68915 (orphaned-pipe grandchild detection, a registry bug not a turn-lifecycle gap). Neither ties process cleanup to turn abandonment.