Engine Lifecycle Doctrine — Nothing Kills Another Engine's Process
SkillDocs & knowledgeEngine lifecycle & contention doctrine for llama-server under AIDE, why engines die with silent "exit code=1, zero stderr", the scoped-kill law (never taskkill /IM llama-server.exe), the memory drain-wait before spawn, the early-exit retry guard, the twin-orchestrator hazard (legacy daemon 4779 vs arch-ts 4778 both managing the same manifest), port-hijack via SO_REUSEADDR, and the exact diagnostic drill to find a machine-wide engine killer. Use whenever engines die unexpectedly, chat returns 409 NOT_READY, a model won't stay loaded, an external job (training corpus generator, supervisor, watchdog) coexists with AIDE engines, or before adding any process-kill logic anywhere.
Use Engine Lifecycle Doctrine — Nothing Kills Another Engine's Process in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Engine Lifecycle Doctrine — Nothing Kills Another Engine's Process and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Engine Lifecycle Doctrine skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by anonymousnomad/covert-coder in skills/packs/aide-engine-lifecycle-doctrine/SKILL.md and read by Ahel’s review.
Born 2026-08-27: chat returned 409 NOT_READY ("route aide-cipher-4b is down (down)
and no fallback is ready") for days. The engine "crashed with exit code 1 in ~0.1s
with zero stderr" under AIDE's spawn path but ran fine standalone. Root cause was
NOT VRAM, NOT ports, NOT the binary: an unrelated training-corpus Python job
(gen_chat_corpus_v2.py) ran taskkill /IM llama-server.exe /F — a machine-wide
kill — inside its self-heal loop, murdering every engine on the box whenever its
5s teacher probe hiccuped (which cipher's own load made MORE likely: loading a
4 GB model slows the whole machine → qwen's probe times out → kill-all fires →
cipher dies → repeat). Verified live: fix the kill scope, and cipher + qwen
coexist and chat returns 200 in 768 ms.
The three simultaneous truths (all verified 2026-08-27)
- The killer was external.
taskkill /IM llama-server.exe /Ffrom E:\pip_temp\opencode\gen_chat_corpus_v2.py (self-heal, lines ~541) killed ALL llama-servers machine-wide. TerminateProcess → victim exits code 1 with ZERO stderr (buffered stderr is lost on hard kill — absent logs ≠ early death). - The spawn path was fragile anyway. daemon/model-manager.mjs
start()didstopAll()then spawned instantly — the dead engine had not returned its commit charge to the OS, so a 4 GB mmap load hit commit exhaustion. Live reproduction: cipher loading while qwen resident wedged the ENTIRE machine twice (mouse-level unresponsiveness, 30 s tool timeouts). - Twin orchestrators share one manifest. facade route map sends
/api/chat→ arch-ts (node/src/services/model-runtime.ts + model-router.ts, port 4778), NOT the legacy daemon (daemon/server.mjs, 4779) — both spawn engines from models/manifest.json onto the same ports. Both must obey this doctrine or each kills/relocates the other's engines.
LAWS (do not violate)
- SCOPED-KILL LAW: never
taskkill /IM llama-server.exe(or any /IM image kill) anywhere — scripts, skills, watchdogs, supervisors, tests. Kill by PORT → PID (netstat -ano parse → taskkill /PID /F /T). The corpus job's self-heal was patched exactly this way (kill only the 127.0.0.1:8081 listener); its "never pile up servers on 8081" law is preserved. - DRAIN-WAIT LAW: after stopping any engine, poll
os.freemem()until ≥ 2.5 GB floor (20 s cap) BEFORE spawning a replacement. A killed engine releases commit asynchronously; spawning into the transient hole causes commit-exhaustion thrash that wedges the whole machine (16 GB RAM, GTX 1060). - EARLY-EXIT GUARD LAW: after spawn, race the child's
exitevent against an 8 s timer. Died early → drain-wait → retry ONCE → then fail LOUDLY with an actionable message pointing at logs/engine-.err.log. Survived 8 s → past the danger window; waitReady() owns readiness from there. - SILENT-EXIT SIGNATURE LAW: on Windows,
exit code=1 signal=nullwith no stderr = TerminateProcess by SOMEBODY (uv SIGTERM maps to TerminateProcess(1); taskkill /F gives 1). Never diagnose it as an engine bug before finding the killer: audit every process on the box for kill logic first. - PORT-HIJACK LAW: two llama-servers CAN bind the same port on Windows (SO_REUSEADDR double-bind — upstream llama.cpp issue #26822, Aug 2026: second server silently waits instead of failing). "Port is listening" proves NOTHING about which engine serves it. Always verify via /v1/models and compare served model identity (daemon verifyEndpointModel does this — keep it).
- TWIN-ORCHESTRATOR LAW: arch-ts model-runtime.ts (4778, serves the UI chat via facade route map common/facade-route-map.json) and legacy daemon model-manager.mjs (4779) both manage models/manifest.json. Any lifecycle fix must land in BOTH or the unfixed twin reintroduces the failure. Arch side already relocates on port conflict (allocateFreePort) — relocation is the polite pattern; DO NOT add cross-service kills.
Diagnostic drill (when engines die)
- Snapshot
tasklist | findstr llamaevery ~30 s alongside engine stderr — death with zero stderr = external kill; death with llama.cpp error text = real crash. - Grep EVERY python/node/batch on the box for
taskkill,/IM,kill(,os.kill,Stop-Process— the killer is rarely in the repo you're editing.Get-CimInstance Win32_Process | ? CommandLine -match 'llama|8081'finds keep-alive loops (parents/PPIDs matter: find who respawns what). - Reproduce contention deliberately: spawn the second engine while the first
is resident, with EXACT production args, detached (cmd
start "" /minbatch — a tool harness tree-kill on timeout murders children launched inline; WMIInvoke-CimMethod Win32_Process Createchildren also die with the caller's tree; only a fully detachedstartsurvives). - Distinguish loader stall from death:
curl /v1/models→ 503 "Loading model" means ALIVE (llama.cpp binds HTTP before weights finish streaming; mmap load takes ~72 s+ under coexistence — patience is a diagnostic tool).
Code anchors (where the doctrine lives)
- daemon/model-manager.mjs:
FREE_RAM_FLOOR_BYTES(2.5 GB, P7 law),waitForMemoryDrain(), floor-gate + drain-wait instart(), early-exit retry loop aroundspawnProcess(2026-08-27). - E:\pip_temp\opencode\gen_chat_corpus_v2.py:
_kill_port_8081()— the patched scoped self-heal (keep this pattern if the script is regenerated). - E:\pip_temp\opencode\supervisor_v2.py: single-writer law (
kill_stale_generators) — kills duplicate gen instances by design; do not run the corpus job manually while the supervisor lives, or your instance gets killed (correctly). - node/src/services/model-runtime.ts
start()— arch twin PORTED (2026-08-27): pre-spawn memory re-check +waitForMemoryDrain()+ 8s early-exit guard with one retry and stderr-tail in the error. Arch test suite green (tests/arch/model-runtime.test.ts, ARCH_TEST_EXIT=0). - VULKAN PROBE CONTENTION (2026-08-27, live-verified): the Vulkan build's
--list-devicesHANGS while any engine is loaded on this box and returns instantly when none is. probeBackend therefore must never cache failures (fixed — only successful parses are cached) and degrades to the CPU binary with a loud warning. Practical rule: probe/start models when no engine is up, or accept the CPU fallback for that start. The old permanent empty-cache silently pinned cipher to the CPU build (1.38 tok/s) for a daemon's whole life — watch for[backend] ... GPU flags will be SILENTLY IGNOREDin the daemon log; it means the vulkan probe ran while an engine was resident. Verified: daemon restart with engines stopped → probe returnsVulkan0: NVIDIA GeForce GTX 1060 (6245 MiB, 5473 MiB free)→ cipher serves via E:\llama-cpp-vulkan with device=Vulkan0, chat 200 in 1.66s.
Threat matrix
| Threat | Signature | Defense |
|---|---|---|
| External kill-all (/IM) | exit 1, zero stderr, all engines die at once | scoped-kill law; audit box for kill logic |
| Commit-exhaustion during load | machine-wide wedge, tool timeouts, engine dies mid-load | drain-wait; RAM floor; one heavy load at a time |
| Port hijack (SO_REUSEADDR) | port LISTENING but wrong/second engine; chat hits random instance | verify /v1/models identity; relocate on conflict |
| Twin-orchestrator races | engine killed by "the other" service; status flips ready↔down | fix both runtimes; relocation not retaliation |
| Keep-alive supervisor fights | engine PID churns every few minutes | find the respawner (PPID chain) before killing anything |
| Buffered-stderr mirage | "no error message" → blamed on binary/profile | stderr is block-buffered via pipe; hard kill loses it |
Pitfalls (each cost real time on 2026-08-27)
- Diagnosing memory before auditing for killers: the concurrency correlation was real but INDIRECT (cipher load → box slows → teacher probe times out → kill-all). The engine was never the patient.
- Tool-harness tree-kill: inline
Start-Processand WMI-created children die when the agent tool call times out; usestart "" /minbatch files for anything that must outlive the command. - PowerShell
curlis Invoke-WebRequest (chokes on-s -S); usecurl.exe. JSON bodies with quotes get mangled through cmd — write a body FILE and--data @file. $argsis a reserved automatic variable in PowerShell — naming a script variable$argssilently breaks the script.schtasks /runon this box fails "cannot find the file" even right after a successful /create — don't burn time; usestart "" /mininstead.- npx/eslint cold-start exceeds a 30 s tool window on this box — run it via a
detached batch writing to a log file, or accept
node --checkfor syntax. - Multiple corpus-job instances = qwen respawn ping-pong (each sees the other's loading engine as "down"). ONE writer only (supervisor enforces it).
Signals
- GitHub stars
- 43
- Forks
- 14
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
aide-engine-lifecycle-doctrine- Source
- github.com/anonymousnomad/covert-coder
github.com/anonymousnomad/covert-coder
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptpython-performance-optimization
Skill · wshobson
The pick for Pythonpython-pro
Skill · jeffallan
The pick for Pythoncpp-pro
Skill · davila7
The pick for C / C++handoff
Skill · mattpocock
More in Docs & knowledge