diff --git a/vision-manager/manager.sh b/vision-manager/manager.sh index 2313df9..00dbd4d 100644 --- a/vision-manager/manager.sh +++ b/vision-manager/manager.sh @@ -200,19 +200,27 @@ while true; do analysis_off=0 fi - # Memory-yield gates (before the backlog logic). The active stop handles a - # running vision the idle path can't; the inhibit forces pending=0 so the - # existing idle-debounce path keeps it down (same shape as the crawl mutex). + # Memory-yield: two SEPARATE levers, never conflated. + # - active stop (HIGH watermark): mem_check_and_stop SIGTERMs a RUNNING + # vision when the host is critically short — the ONLY thing that stops a + # vision that is actively working. + # - start block (LOW watermark / cooldown): holds off STARTING a stopped + # vision when memory is already tight, via the mem_block_start flag. + # It must NOT zero `pending`: doing so made the idle-debounce path read + # "no work" and idle-stop a vision mid-analysis once the box floated past + # LOW (idle stack + LLM ≈ 80% here). LOW is a START gate, not a stop + # threshold. mem_check_and_stop running="$(vision_running)" # refresh: the active stop may have just flipped it + mem_block_start=0 if [ "$(mem_yield_active)" = "1" ]; then + mem_block_start=1 if [ "$mem_inhibited" != "1" ]; then - log "memory-yield: inhibiting vision start (used >= ${MEM_LOW_WATERMARK_PCT}% or cooldown active)" + log "memory-yield: blocking vision START (used >= ${MEM_LOW_WATERMARK_PCT}% or cooldown); a running vision keeps working until the ${MEM_HIGH_WATERMARK_PCT}% active stop" mem_inhibited=1 fi - pending=0 elif [ "$mem_inhibited" = "1" ]; then - log "memory-yield: pressure cleared — start inhibit lifted" + log "memory-yield: pressure cleared — start block lifted" mem_inhibited=0 fi @@ -225,7 +233,12 @@ while true; do if [ "$pending" -gt 0 ]; then idle_for=0 if [ "$running" != "true" ]; then - if [ "$RESPECT_CRAWL_MUTEX" != "0" ] && [ "$(crawl_running)" != "0" ]; then + if [ "$mem_block_start" = "1" ]; then + # Memory pressure / cooldown — hold off STARTING (flip already logged). + # A RUNNING vision never reaches here; idle_for stayed 0 above because + # pending>0, so a working vision is never idle-stopped under pressure. + : + elif [ "$RESPECT_CRAWL_MUTEX" != "0" ] && [ "$(crawl_running)" != "0" ]; then # Log once per deferral episode, not every poll, so a long crawl # doesn't flood the log. if [ "$crawl_deferred" != "1" ]; then