fix(kvb-org-organize): the resource watch never acts on a stale stage
block and status compute the stage from the live free space once the stored measurement is older than interval_seconds, so a disk that filled while no hook ran no longer passes heavy jobs as ok. watch writes the stage before it mirrors the history, and the mirror takes its lock with LOCK_NB: an agent holding a flock on the kept file no longer stalls every hook, the round is skipped and the next one catches up. 🎯 Fix: 92 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 💰 Value: ? 🏆 Score: 88 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb815216d2
commit
94542e7b92
3 files changed
+89
-12
No files matched your search
@@ -58,4 +58,5 @@ and stays silent below the thresholds.
|
||||
projection window raises the stage by one at once.
|
||||
- Caches together stay under their budget; the least recently used go first.
|
||||
|
||||
`watch.py status` shows the last measurement.
|
||||
`watch.py status` shows the stage computed from the live free space and the
|
||||
stored samples, and the age of the stored measurement.
|
||||
@@ -17,7 +17,8 @@ Usage:
|
||||
watch.py cleanup (--caches | --full)
|
||||
trim caches to their budget; --full also runs the
|
||||
configured low-risk cleanup commands
|
||||
watch.py status print the last measurement
|
||||
watch.py status print the stage computed from the live free space and the
|
||||
stored samples, and the stored measurement's age in seconds
|
||||
|
||||
Environment:
|
||||
AGENTS_COUNCIL_ROOT: shared state directory; /tmp/agents-council unless set,
|
||||
@@ -250,7 +251,7 @@ def keep_rows(origin: Path, target: Path) -> None:
|
||||
return
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with target.open("a+", encoding="utf-8") as kept:
|
||||
fcntl.flock(kept, fcntl.LOCK_EX)
|
||||
fcntl.flock(kept, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
kept.seek(0)
|
||||
known = {row_id for row_id, _ in history_rows(kept.read())}
|
||||
for row_id, line in history_rows(nofollow.read_text(ROOT, origin)):
|
||||
@@ -268,12 +269,15 @@ def keep_history() -> None:
|
||||
keep_rows(origin, target)
|
||||
|
||||
|
||||
def stale(state: dict, cfg: dict, now: float) -> bool:
|
||||
return now - state.get("checked", 0) >= cfg["interval_seconds"]
|
||||
|
||||
|
||||
def cmd_watch(cfg: dict) -> int:
|
||||
payload = json.load(sys.stdin)
|
||||
keep_history()
|
||||
now = time.time()
|
||||
state = read_json(STATE)
|
||||
if now - state.get("checked", 0) >= cfg["interval_seconds"]:
|
||||
if stale(state, cfg, now):
|
||||
free, _ = disk(cfg)
|
||||
samples = [s for s in state.get("samples", []) if now - s[0] <= cfg["projection_minutes"] * 60]
|
||||
stage = stage_for(free, samples, cfg, now)
|
||||
@@ -288,6 +292,7 @@ def cmd_watch(cfg: dict) -> int:
|
||||
act(cfg, stage, previous, free)
|
||||
elif due:
|
||||
spawn_cleanup("--caches")
|
||||
keep_history()
|
||||
texts = unseen_alerts(payload["session_id"])
|
||||
if texts:
|
||||
print(json.dumps({"hookSpecificOutput": {
|
||||
@@ -313,7 +318,11 @@ def silence() -> dict | None:
|
||||
def cmd_block(cfg: dict) -> int:
|
||||
payload = json.load(sys.stdin)
|
||||
command = executable_text((payload.get("tool_input") or {}).get("command", ""))
|
||||
stage = read_json(STATE).get("stage", "ok")
|
||||
state = read_json(STATE)
|
||||
now = time.time()
|
||||
stage = state.get("stage", "ok")
|
||||
if stale(state, cfg, now):
|
||||
stage = stage_for(disk(cfg)[0], state.get("samples", []), cfg, now)
|
||||
mayday = silence()
|
||||
if stage not in ("stop", "critical") and not mayday:
|
||||
return 0
|
||||
@@ -492,7 +501,10 @@ def cmd_cleanup(cfg: dict, args: argparse.Namespace) -> int:
|
||||
def cmd_status(cfg: dict) -> int:
|
||||
state = read_json(STATE)
|
||||
free, total = disk(cfg)
|
||||
print(f"stage={state.get('stage', 'unmeasured')} free={free:.1f}GB total={total:.1f}GB samples={len(state.get('samples', []))}")
|
||||
now = time.time()
|
||||
samples = state.get("samples", [])
|
||||
age = f"{now - state['checked']:.0f}s" if "checked" in state else "unmeasured"
|
||||
print(f"stage={stage_for(free, samples, cfg, now)} free={free:.1f}GB total={total:.1f}GB samples={len(samples)} age={age}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user