From b72c7e7ca1a1fc2d542fa74cd5b5f6ebcb017cf8 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:30:04 -0400 Subject: [PATCH 1/3] P248 count an unjudged backlog as one judging slice --- src/agent_comms/commander/capacity.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/commander/capacity.py b/src/agent_comms/commander/capacity.py index d62875f89b..e071aab680 100644 --- a/src/agent_comms/commander/capacity.py +++ b/src/agent_comms/commander/capacity.py @@ -1,8 +1,9 @@ """Commander load and forks (HARD RULE: as soon as the commander is overwhelmed it forks; a structural reflex). Load is measured from the files the fleet already writes, never from how the commander feels: -each open order counts 1, each overdue order 2 more, and every 10 unjudged edits 1. One commander -carries PER_COMMANDER load. Forks register by passing `--fork NAME` to order, which touches +each open order counts 1 and each overdue order 2 more. Any unjudged edits together count as +one PER_COMMANDER slice of judging work; no edits count as zero. One commander carries +PER_COMMANDER load. Forks register by passing `--fork NAME` to order, which touches forks/NAME.beat; a beat younger than BEAT_AGE is a live fork. While load needs more forks than are live, order refuses the main commander's actions and rotate prints FORK NOW, so the only way forward is forking a slice. @@ -49,8 +50,13 @@ def of(cls, comms: Comms, commander: str) -> Load: CommsUnjudgedTool.count(comms, commander), tuple(live_forks())) @property - def total(self) -> float: - return self.owed + 2 * self.overdue + self.unjudged / 10 + def judging_load(self) -> int: + """The whole unjudged backlog is one commander's slice, regardless of its length.""" + return PER_COMMANDER if self.unjudged else 0 + + @property + def total(self) -> int: + return self.owed + 2 * self.overdue + self.judging_load @property def needed(self) -> int: @@ -63,7 +69,8 @@ def short(self) -> bool: def __str__(self) -> str: return (f'load {self.total:.0f} ({self.owed} open orders, {self.overdue} overdue, ' - f'{self.unjudged} unjudged edits) needs {self.needed} live fork(s), ' + f'{self.unjudged} unjudged edits: {self.judging_load} judging load) ' + f'needs {self.needed} live fork(s), ' f'{len(self.forks)} live {list(self.forks)}; fork a slice now (each fork passes --fork NAME to order)') def require(self) -> None: From d01c8f6246acc65f3b7b8238770aa0bdea7bafc5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:32:19 -0400 Subject: [PATCH 2/3] P248 confirm backlog weighting and nine-fork coverage --- tests/test_commander_capacity.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_commander_capacity.py diff --git a/tests/test_commander_capacity.py b/tests/test_commander_capacity.py new file mode 100644 index 0000000000..f20c4124ec --- /dev/null +++ b/tests/test_commander_capacity.py @@ -0,0 +1,25 @@ +"""The existing capacity gate counts judging as one slice of work.""" + +import pytest + + +@pytest.mark.parametrize("unjudged", [0, 1, 395, 1_000_000]) +def test_judging_backlog_is_one_slice_used_by_total_and_description(commander_script, unjudged): + script, _ = commander_script + load = script.capacity.Load(24, 14, unjudged, tuple(f"fork-{n}" for n in range(9))) + assert load.judging_load == (script.capacity.PER_COMMANDER if unjudged else 0) + assert load.total == 24 + 2 * 14 + load.judging_load + assert f"{unjudged} unjudged edits: {load.judging_load} judging load" in str(load) + assert load.needed == (9 if unjudged else 8) + assert not load.short + load.require() + + +def test_nine_forks_cover_the_reported_fleet_and_eight_do_not(commander_script): + script, _ = commander_script + forks = tuple(f"fork-{n}" for n in range(9)) + load = script.capacity.Load(24, 14, 395, forks) + assert load.total == 58 and load.needed == 9 + load.require() + with pytest.raises(SystemExit, match="needs 9 live fork"): + script.capacity.Load(24, 14, 395, forks[:-1]).require() From cad44fe597998e16b87ac4a4921e54f88f3816f6 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:29:32 -0400 Subject: [PATCH 3/3] P253: delete commander overload refusal and fork-now standing rule --- src/agent_comms/commander/capacity.py | 43 +++++---------------------- src/agent_comms/commander/order.py | 17 +++-------- src/agent_comms/commander/standing.py | 17 +---------- tests/test_commander_capacity.py | 27 +++++------------ tests/test_standing_rules.py | 25 ++-------------- 5 files changed, 23 insertions(+), 106 deletions(-) diff --git a/src/agent_comms/commander/capacity.py b/src/agent_comms/commander/capacity.py index e071aab680..68de8b53cf 100644 --- a/src/agent_comms/commander/capacity.py +++ b/src/agent_comms/commander/capacity.py @@ -1,16 +1,11 @@ -"""Commander load and forks (HARD RULE: as soon as the commander is overwhelmed it forks; a structural reflex). +"""Report commander load and live forks; no command refuses on capacity. -Load is measured from the files the fleet already writes, never from how the commander feels: -each open order counts 1 and each overdue order 2 more. Any unjudged edits together count as -one PER_COMMANDER slice of judging work; no edits count as zero. One commander carries -PER_COMMANDER load. Forks register by passing `--fork NAME` to order, which touches -forks/NAME.beat; a beat younger than BEAT_AGE is a live fork. While load needs more forks than are -live, order refuses the main commander's actions and rotate prints FORK NOW, so the only -way forward is forking a slice. +Each open order counts 1, each overdue order 2 more, and every 10 unjudged edits 1. +Forks register with `--fork NAME`; a heartbeat younger than BEAT_AGE is live. """ from __future__ import annotations -import math, os, sys, time +import os, time from dataclasses import dataclass from ..comms import Comms @@ -18,7 +13,6 @@ from .config import CommanderConfig FORKS = f'{CommanderConfig.installed().state}/forks' -PER_COMMANDER = 6 BEAT_AGE = 10 * 60 @@ -50,34 +44,13 @@ def of(cls, comms: Comms, commander: str) -> Load: CommsUnjudgedTool.count(comms, commander), tuple(live_forks())) @property - def judging_load(self) -> int: - """The whole unjudged backlog is one commander's slice, regardless of its length.""" - return PER_COMMANDER if self.unjudged else 0 - - @property - def total(self) -> int: - return self.owed + 2 * self.overdue + self.judging_load - - @property - def needed(self) -> int: - return math.ceil(self.total / PER_COMMANDER) - 1 - - @property - def short(self) -> bool: - """Live forks do not cover the load: the commander must fork now.""" - return len(self.forks) < self.needed + def total(self) -> float: + return self.owed + 2 * self.overdue + self.unjudged / 10 def __str__(self) -> str: return (f'load {self.total:.0f} ({self.owed} open orders, {self.overdue} overdue, ' - f'{self.unjudged} unjudged edits: {self.judging_load} judging load) ' - f'needs {self.needed} live fork(s), ' - f'{len(self.forks)} live {list(self.forks)}; fork a slice now (each fork passes --fork NAME to order)') - - def require(self) -> None: - if self.short: - sys.exit(f'REFUSED, nothing recorded: overloaded: {self}.') + f'{self.unjudged} unjudged edits), {len(self.forks)} live forks {list(self.forks)}') def main(a): - load = Load.of(order.COMMS, order.ME) - print(load if load.short else f'covered: load {load.total:.0f}, forks {list(load.forks)}') + print(Load.of(order.COMMS, order.ME)) diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index 92f37f8993..de6a972d2e 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -759,21 +759,12 @@ def fire(unit: str, why: str) -> None: print(f'{todo.id}: released from {unit}: {why}') -class LoadedVerb(RuledVerb): - """HARD RULE: overloaded means fork, not more orders from me; a fork is never blocked.""" - - def gate(self) -> None: - super().gate() - if not self.fork: - capacity.Load.of(COMMS, ME).require() - - class OrderVerb(CommanderVerb): """A verb of `order`.""" @dataclass(frozen=True, kw_only=True) -class SendVerb(OrderVerb, LoadedVerb): +class SendVerb(OrderVerb, RuledVerb): help = "record the order in Core and send it" unit: str = option("unit") due: str = option("due", help="HH:MM local time") @@ -916,7 +907,7 @@ def run(self) -> None: @dataclass(frozen=True, kw_only=True) -class SayVerb(OrderVerb, LoadedVerb): +class SayVerb(OrderVerb, RuledVerb): help = "a non-order message; refused if it orders work" units: str = option("units", metavar="UNIT[,UNIT]") body_file: str = option("body_file", metavar="BODYFILE") @@ -926,7 +917,7 @@ def run(self) -> None: @dataclass(frozen=True, kw_only=True) -class ProblemVerb(OrderVerb, LoadedVerb): +class ProblemVerb(OrderVerb, RuledVerb): help = "Core records the problem with its order, or nothing" text: str = option("text") to: str = option("--to") @@ -987,7 +978,7 @@ def run(self) -> None: @dataclass(frozen=True, kw_only=True) -class HuntVerb(OrderVerb, LoadedVerb): +class HuntVerb(OrderVerb, RuledVerb): help = "a hot-path read: clean unless --sent names the order it became" where: str = option("where", metavar="FILE:LINE") why: str = option("--why") diff --git a/src/agent_comms/commander/standing.py b/src/agent_comms/commander/standing.py index b8408a26b7..4232f88329 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -17,7 +17,7 @@ from ..threads import Thread from ..todos import FlatTrend, OrderProgress, PullRequests, StrikeTodo, Todo from ..turn_digests import TurnDigestRead, TurnsSince -from . import capacity, judge +from . import judge from .config import CommanderConfig, Repo CONFIG = CommanderConfig.installed() @@ -150,21 +150,6 @@ def enforce(self, comms: Comms, commander: str) -> None: self.assign(comms, commander, comms.registry.require(self.unit)) -@dataclass(frozen=True) -class ForkNow(UndatedStandingRule): - """The load on the asked Comms needs more live forks than there are.""" - - load: capacity.Load - - @classmethod - def violations(cls, comms: Comms, commander: str) -> tuple[ForkNow, ...]: - load = capacity.Load.of(comms, commander) - return (cls(load),) * load.short - - def line(self) -> str: - return f"FORK NOW: {self.load}" - - @dataclass(frozen=True) class Unjudged(ReasonedStandingRule, UndatedStandingRule): limit: ClassVar[int] = 30 diff --git a/tests/test_commander_capacity.py b/tests/test_commander_capacity.py index f20c4124ec..fe0ded1098 100644 --- a/tests/test_commander_capacity.py +++ b/tests/test_commander_capacity.py @@ -1,25 +1,14 @@ -"""The existing capacity gate counts judging as one slice of work.""" +"""Capacity reports the existing load formula and live forks without policy advice.""" import pytest @pytest.mark.parametrize("unjudged", [0, 1, 395, 1_000_000]) -def test_judging_backlog_is_one_slice_used_by_total_and_description(commander_script, unjudged): +def test_capacity_reports_load_and_forks(commander_script, unjudged): script, _ = commander_script - load = script.capacity.Load(24, 14, unjudged, tuple(f"fork-{n}" for n in range(9))) - assert load.judging_load == (script.capacity.PER_COMMANDER if unjudged else 0) - assert load.total == 24 + 2 * 14 + load.judging_load - assert f"{unjudged} unjudged edits: {load.judging_load} judging load" in str(load) - assert load.needed == (9 if unjudged else 8) - assert not load.short - load.require() - - -def test_nine_forks_cover_the_reported_fleet_and_eight_do_not(commander_script): - script, _ = commander_script - forks = tuple(f"fork-{n}" for n in range(9)) - load = script.capacity.Load(24, 14, 395, forks) - assert load.total == 58 and load.needed == 9 - load.require() - with pytest.raises(SystemExit, match="needs 9 live fork"): - script.capacity.Load(24, 14, 395, forks[:-1]).require() + load = script.capacity.Load(24, 14, unjudged, ("judge",)) + assert load.total == 24 + 2 * 14 + unjudged / 10 + assert str(load) == ( + f"load {load.total:.0f} (24 open orders, 14 overdue, {unjudged} unjudged edits), " + "1 live forks ['judge']" + ) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index e1e5534aa9..beca82731b 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -215,34 +215,13 @@ def progress(): assert comms.goals.todos.get("P2").holder == "worker" -def test_fork_now_and_unjudged_follow_the_unjudged_edits(commander, standing): +def test_unjudged_follows_the_unjudged_edits(commander, standing): comms, state, module = standing - assert lines(comms, module.ForkNow) == lines(comms, module.Unjudged) == [] + assert lines(comms, module.Unjudged) == [] create(commander, 'P1') native_judge_edits(commander, module.judge, [dict(id=f'e{n}', at=200., path='a.py', hunks=[dict(old='before', new='after')]) for n in range(70)]) assert lines(comms, module.Unjudged) == ["UNJUDGED 70 edits -> judge or fork a judge slice"] - [fork] = lines(comms, module.ForkNow) - assert fork.startswith("FORK NOW: load 10 (1 open orders, 1 overdue, 70 unjudged edits) needs 1") - - -def test_fork_now_reads_the_load_of_the_bus_it_is_asked_about(commander, standing, tmp_path): - """Three overdue orders on one bus need a fork there; a second bus without orders carries no load.""" - from agent_comms.tool_caller import ThreadCaller - - comms, _, _ = standing - for problem in ("P1", "P2", "P3"): - create(commander, problem) - other = Comms(tmp_path / "other-bus") - other.registry.declare(other.registry.new_thread("lead", frozenset(), str(tmp_path), created_at=1.0)) - - def fork_now(bus): - read = CommsStandingRulesTool().apply(bus, ThreadCaller("lead")) - return [line for line in read.broken if line.startswith("FORK NOW")] - - assert [line.split(") ")[0] for line in fork_now(comms)] == [ - "FORK NOW: load 9 (3 open orders, 3 overdue, 0 unjudged edits"] - assert fork_now(other) == [] def test_rules_due_until_every_drawn_rule_is_answered_and_fresh(commander, monkeypatch):