{"slug": "subagents-are-zombie-processes", "title": "Subagents Are Zombie Processes", "summary": "A developer discovered that subagents in multi-agent AI systems can become orphaned zombie processes, continuing to run and incur costs long after their parent tasks complete. The issue stems from a lack of lifecycle management, where no agent is responsible for terminating its children, and existing platforms lack auto-kill mechanisms. The author argues that the solution is to enforce ownership recursively, requiring every parent agent to manage its children's lifecycles, similar to Unix process management.", "body_md": "# Your subagents are zombie processes\n\nI found it because the cursor was blinking.\n\nThe review was done — posted, merged, forgotten. But the session was still flickering, so I opened the background-tasks panel, and there they were: two agents, still running, five and a half hours after they had anything to do. Around a hundred thousand tokens, spent re-deriving an answer I’d already shipped. A week earlier, on another project, a single agent had spawned **106 subagents** for one web search. I find these the same way every time: by accident.\n\nIf you’re building anything multi-agent, you have these too. You just haven’t caught them yet.\n\n## Nobody owned the children\n\nHere’s the mechanics, because they’re mundane and that’s the point.\n\nI asked an agent to review a big pull request by fanning out — a few subagents, each on a different dimension. Good pattern; it caught a real bug. One of those subagents parallelized *its* slice and spawned two of its own. Then the parent finished, reported up, and returned — without stopping the children it had started. It never treated their lifecycle as its job.\n\nSo they orphaned. Running, billing, invisible, one level below anyone who was watching. This wasn’t a bug in any single agent’s reasoning. Every agent in that tree did its job well. It was a gap in *ownership* — nobody was responsible for turning the children off.\n\n## There was no switch\n\nMy first instinct was to find the setting. There isn’t one, and the search is worth sharing, because the absence is the story:\n\n**Lifecycle hooks exist, but they’re the wrong shape.** They fire when an agent*finishes*and hand you its final message. They can’t enumerate a sibling that’s still alive, and can’t reach over to kill it. They react; they don’t reap.**There’s no max-runtime or auto-kill for subagents.** Nothing times out a hung child.**The platform has open bugs in exactly this spot**— finished subagents that never get reconciled and linger in a running state. So part of what I hit wasn’t my orchestration at all. The infrastructure has no reaper either.\n\nThat’s the uncomfortable takeaway for the whole field: we’re wiring up systems that spawn processes, and we haven’t built the process table.\n\n## We solved this in 1985\n\nAn orphaned subagent is a **zombie process**. The vocabulary maps one-to-one:\n\n- A child outlives its parent’s attention →\n**orphan**. - Nobody collects its exit status →\n**zombie**. - No supervisor adopts and cleans it up → no\n.`init`\n\n/reaper\n\nUnix answered this decades ago with two ideas: a parent is expected to `wait()`\n\non its children, and if it doesn’t, `init`\n\nadopts the orphans and reaps them. Multi-agent frameworks today reliably have neither. We skipped the lesson because “agent” sounded novel enough to feel exempt from operating-systems history. It isn’t. It’s `fork()`\n\nwith a language model attached and a credit card.\n\nAnd the agent case is *worse* than the Unix one in three ways that matter:\n\n**The leak is silent.** A zombie process shows up in`ps`\n\n. An orphaned agent shows up as a bigger invoice next month.**The resource is money.** Idle isn’t free — a hung agent can keep thinking, and thinking bills.**There’s no default supervisor.** Your terminal reaps your child processes when it dies. Nothing reaps your agents.\n\n## The fix is the old fix: ownership, delegated recursively\n\nYou don’t need the platform to grow a reaper. You need the thing Unix already assumes: **every parent owns its children, because the parent is the one holding their handles.**\n\nThat last clause is the whole insight. When I couldn’t kill the orphans, it was only because they were my *grand*children, one level below me. But their direct parent *did* hold their IDs. It could have stopped them. It just was never told that was its job.\n\nSo make it the job, and make the instruction travel. Every spawned agent carries a clause like this, with orders to pass it down to anything *it* spawns:\n\nLifecycle contract (include verbatim in any subagent you spawn):Do your task. Before you return, make sure any subagents or jobsyoustarted have finished or been stopped — you hold their handles, so stop any still running. Don’t return while leaving work alive; if a child is stuck, stop it rather than waiting on it. If you spawn helpers, pass them this same paragraph so the rule keeps propagating.\n\nBecause it propagates, it self-assembles into a teardown tree. Level four cleans five, three cleans four, the root cleans the top. No depth cap, no width cap — fan out as hard as the task deserves. The only invariant is that **nothing returns while its own subtree is still alive.**\n\nHere’s the failure and the fix side by side:\n\n```\nWITHOUT the contract — the ghost          WITH the contract — clean teardown\n────────────────────────────────         ──────────────────────────────────\nyou   ✓ done, moved on                    you   ✓ + sweep: verify all clear\n└─ A  ✓ returned findings                 └─ A  ✓ stops B before returning\n   └─ B  ✓ returned findings                 └─ B  ✓ stops C, D before returning\n      ├─ C  ⟳ running 5h  ← orphan             ├─ C  ✓ stopped by B\n      └─ D  ⟳ running 5h  ← orphan             └─ D  ✓ stopped by B\n\n  each parent returned and forgot its       each parent reaps its own children,\n  children → ghosts, billing, one           because it holds their handles →\n  level below anyone watching               teardown climbs the tree\n```\n\nThink of it as nesting dolls: a child is only ever closed by the doll it sits inside. Belt and suspenders:\n\n**The contract is the belt**— recursive self-teardown at every level.** A top-level sweep is the suspenders**— when the whole job is done, verify nothing’s still running, and if something is, surface it*now*, loudly, not on next month’s bill.\n\nBe honest about what this is: a pattern, not a guarantee. It’s an *instruction*, so it depends on each agent honoring it, and a genuinely wedged child — or that platform reconciliation bug — can still slip through. That’s exactly why the rule is “stop, don’t wait,” and why the human-visible sweep stays. Don’t ship the belt and skip the suspenders.\n\n## The real lesson\n\nThe bug that cost me a hundred thousand tokens wasn’t a reasoning error. It was an ownership gap — the kind of unglamorous, structural concern that “just make the model smarter” will never fix.\n\nThat’s the shift I keep relearning as I build with agents: a lot of the hard, valuable work isn’t prompting. It’s the systems discipline *around* the prompting — resource ownership, lifecycles, observability, accountability. The model is the easy part now. The engineering is making sure that when a fast, tireless, unaccountable thing spawns a hundred and six copies of itself, someone, at some level, is holding the handle that turns them off.\n\nWe should probably `wait()`\n\non our agents.", "url": "https://wpnews.pro/news/subagents-are-zombie-processes", "canonical_source": "https://victorvelazquez.dev/blog/your-subagents-are-zombie-processes/", "published_at": "2026-07-07 12:06:13+00:00", "updated_at": "2026-07-07 12:30:29.146089+00:00", "lang": "en", "topics": ["ai-agents", "ai-infrastructure", "ai-safety", "ai-ethics"], "entities": ["Unix"], "alternates": {"html": "https://wpnews.pro/news/subagents-are-zombie-processes", "markdown": "https://wpnews.pro/news/subagents-are-zombie-processes.md", "text": "https://wpnews.pro/news/subagents-are-zombie-processes.txt", "jsonld": "https://wpnews.pro/news/subagents-are-zombie-processes.jsonld"}}