From de1a682a00092fa41a91514e63893fe3ea688fdd Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:39:43 -0400 Subject: [PATCH 1/4] Preserve missing check counts through progress and overdue reporting Keep check validation strict while distance observations retain the original count state. A missing latest count names its check instead of aborting the fleet report. Src-Budget: 64 --- src/agent_comms/child_process.py | 38 +++++++++++++++++++++++---- src/agent_comms/goals.py | 18 ++++++------- src/agent_comms/todos.py | 45 +++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 27 deletions(-) diff --git a/src/agent_comms/child_process.py b/src/agent_comms/child_process.py index c327025401..12370fc36b 100644 --- a/src/agent_comms/child_process.py +++ b/src/agent_comms/child_process.py @@ -1024,15 +1024,34 @@ def decoded(stdout: bytes) -> CheckPrinted: def unproven(self, command: str, expected: int) -> tuple[str, ...]: ... @abstractmethod - def distance(self, expected: int) -> int: ... + def distance(self, expected: int) -> CheckPrinted: ... + + @abstractmethod + def plus(self, amount: int) -> CheckPrinted: ... + + @abstractmethod + def require(self, expected: object) -> None: ... + + @abstractmethod + def project[T](self, counted: Callable[[int], T], uncounted: Callable[[], T]) -> T: ... @dataclass(frozen=True) class CountPrinted(CheckPrinted): count: int - def distance(self, expected: int) -> int: - return abs(self.count - expected) + def distance(self, expected: int) -> CheckPrinted: + return CountPrinted(abs(self.count - expected)) + + def plus(self, amount: int) -> CheckPrinted: + return CountPrinted(self.count + amount) + + def require(self, expected: object) -> None: + if self.count: + raise ValueError(f"The order check is {self.count} away from {expected}.") + + def project[T](self, counted: Callable[[int], T], uncounted: Callable[[], T]) -> T: + return counted(self.count) def unproven(self, command: str, expected: int) -> tuple[str, ...]: return ((f"Check already prints {self.count} before any work: `{command}` proves nothing; " @@ -1041,8 +1060,17 @@ def unproven(self, command: str, expected: int) -> tuple[str, ...]: @dataclass(frozen=True) class NoIntegerPrinted(CheckPrinted): - def distance(self, expected: int) -> int: - raise ValueError(f"The order check printed no integer, so no count of {expected}.") + def distance(self, expected: int) -> CheckPrinted: + return self + + def plus(self, amount: int) -> CheckPrinted: + return self + + def require(self, expected: object) -> None: + raise ValueError(f"The order check printed no integer; cannot prove {expected}.") + + def project[T](self, counted: Callable[[int], T], uncounted: Callable[[], T]) -> T: + return uncounted() def unproven(self, command: str, expected: int) -> tuple[str, ...]: return ("the check printed no integer; use grep -c PATTERN FILE or ... | wc -l",) diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index 05055b3114..85653a9dcc 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -15,8 +15,8 @@ from pathlib import Path from typing import TYPE_CHECKING, ClassVar, Literal -from .child_process import (BoundedRun, ChildOutcome, ChildResult, ExitedOutcome, SignaledOutcome, - TimedOutOutcome) +from .child_process import (BoundedRun, CheckPrinted, ChildOutcome, ChildResult, CountPrinted, + ExitedOutcome, SignaledOutcome, TimedOutOutcome) from .declared_family import DeclaredFamily from .errors import RelationViolationError from .field_codec import ( @@ -230,19 +230,17 @@ def require_identity(self, goal_id: str) -> None: class CheckResult(DeclaredFamily, affix="CheckResult"): def require(self, result: ChildResult) -> None: - distance = self.distance(result) - if distance: - raise ValueError(f"The order check is {distance} away from {self}.") + self.distance(result).require(self) @abstractmethod - def distance(self, result: ChildResult) -> int: - """How far this run is from the result the order requires; zero when it is met.""" + def distance(self, result: ChildResult) -> CheckPrinted: + """The measured distance, or the run's declared missing-count state.""" @dataclass(frozen=True) class SilentCheckResult(CheckResult): - def distance(self, result: ChildResult) -> int: - return int(result.outcome != ExitedOutcome(0) or bool(result.stdout)) + def distance(self, result: ChildResult) -> CheckPrinted: + return CountPrinted(int(result.outcome != ExitedOutcome(0) or bool(result.stdout))) @dataclass(frozen=True) @@ -253,7 +251,7 @@ def __post_init__(self): if self.expected < 0: raise ValueError("An expected count must be nonnegative.") - def distance(self, result: ChildResult) -> int: + def distance(self, result: ChildResult) -> CheckPrinted: return result.outcome.printed_count(result.stdout).distance(self.expected) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 21893e014e..93fb16f8a8 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -22,7 +22,7 @@ from uuid import uuid4 from .change_report import FileLine -from .child_process import BoundedRun, ChildResult, ExitedOutcome +from .child_process import BoundedRun, CheckPrinted, ChildResult, ExitedOutcome from .command import Command from .declared_family import DeclaredFamily from .errors import ToolRefusedError @@ -258,7 +258,7 @@ def _clock(at: float) -> str: @dataclass(frozen=True) class DistanceReading: at: float - distance: int + distance: CheckPrinted @dataclass(frozen=True) @@ -266,18 +266,29 @@ class DistanceReadings: """The order's last two distance readings, oldest first: its base at the order, then its newest commit. Fewer than two read, or a last reading at done, project nothing.""" readings: tuple[DistanceReading, ...] = () + command: str = "" @property def trend(self) -> DistanceTrend: - """Velocity across the two readings: falling projects the finish, else it is flat.""" + """A missing count diagnoses the check; only measured counts project a finish.""" + if not self.readings: + return UnreadTrend() + return self.readings[-1].distance.project( + self.counted_trend, lambda: UncountedTrend(self.command)) + + def counted_trend(self, final: int) -> DistanceTrend: last = self.readings[-2:] - if len(last) < 2 or not last[1].distance: + if len(last) < 2 or not final: return UnreadTrend() - first, final = last - if final.distance >= first.distance: - return FlatTrend(final.distance, final.at) - rate = (first.distance - final.distance) / max(final.at - first.at, 1.0) - return MovingTrend(final.distance, final.at + final.distance / rate) + first, latest = last + + def compared(previous: int) -> DistanceTrend: + if final >= previous: + return FlatTrend(final, latest.at) + rate = (previous - final) / max(latest.at - first.at, 1.0) + return MovingTrend(final, latest.at + final / rate) + + return first.distance.project(compared, UnreadTrend) class DistanceTrend(DeclaredFamily, affix="Trend"): @@ -310,6 +321,15 @@ def projection(self) -> str: return f"; distance {self.distance}, not falling" +@dataclass(frozen=True) +class UncountedTrend(DistanceTrend): + command: str + + @property + def projection(self) -> str: + return f"; check prints no integer: {self.command}" + + @dataclass(frozen=True) class UnreadTrend(DistanceTrend): """Fewer than two readings, or the last one at done: nothing to project from.""" @@ -362,15 +382,16 @@ def readings(self) -> DistanceReadings: "--not", base).splitlines()))] points = [(base, self.since), *commits][-2:] return DistanceReadings( - tuple(DistanceReading(at, self.distance(base, sha)) for sha, at in points)) + tuple(DistanceReading(at, self.distance(base, sha)) for sha, at in points), + self.todo.order.check.command) - def distance(self, base: str, sha: str) -> int: + def distance(self, base: str, sha: str) -> CheckPrinted: """The order's Check run on ``sha`` against its N, plus its numbered edits the diff since ``base`` has not touched.""" check = self.todo.order.check touched = self.git("diff", "--name-only", base, sha).split() run = check.at(sha, str(self.checkout)) - return check.result.distance(run) + self.todo.order.untouched(touched) + return check.result.distance(run).plus(self.todo.order.untouched(touched)) @dataclass(frozen=True) From 5f12ed9e96f402920795e206832f4064027474f5 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:42:47 -0400 Subject: [PATCH 2/4] Cover missing-count progress alongside unaffected overdue orders --- tests/test_commander_problem_orders.py | 2 +- tests/test_order_progress.py | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_commander_problem_orders.py b/tests/test_commander_problem_orders.py index 4db2cc46e9..bc6b80fbfa 100644 --- a/tests/test_commander_problem_orders.py +++ b/tests/test_commander_problem_orders.py @@ -620,7 +620,7 @@ def test_commander_refuses_a_check_that_prints_no_count(commander_script): @pytest.mark.parametrize('printed, refused', [ - ("printf 'no count here'", 'The order check printed no integer, so no count of 1.'), + ("printf 'no count here'", 'The order check printed no integer; cannot prove CountCheckResult(expected=1).'), ('echo 2', 'The order check is 1 away from CountCheckResult(expected=1).'), ('echo found 1', None), ]) diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index 9b42b53046..db9b899947 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -65,6 +65,34 @@ def test_no_commit_since_the_order_is_explicit(checkout): assert progress.line == 'no commit since the order' +def test_uncounted_check_keeps_other_overdue_orders_visible(commander, commander_script, checkout, monkeypatch): + from agent_comms.child_process import CountPrinted, NoIntegerPrinted + from agent_comms.todos import DistanceReading, UncountedTrend + + script, _ = commander_script + todo, unit = checkout + comms, _ = commander + monkeypatch.setattr(script.COMMS.registry, 'storage_rule', StorageRule(checkout_base=unit.parent)) + monkeypatch.setattr(PullRequests, 'read', lambda *args: ()) + broken = replace(todo.order, check=replace(todo.order.check, command="printf 'no count here'")) + with comms.goals.todos.writing() as db: + replace(todo, order=broken).upsert(db) + create(commander, 'P2', deadline=1) + commit(unit) + + progress = read((comms.goals.todos.get('P1'), unit)) + assert progress.line.endswith("; check prints no integer: printf 'no count here'") + lines = deadline_lines(script) + alarm = next(line for line in lines if line.startswith('OVERDUE:')) + assert "order order-P1" in alarm and "order order-P2" in alarm + assert "check prints no integer: printf 'no count here'" in alarm + assert 'distance 0' not in alarm and 'projected done' not in alarm + missing = NoIntegerPrinted() + assert missing.distance(1) is missing and missing.plus(3) is missing + assert DistanceReadings((DistanceReading(1, missing),), 'printf missing').trend == UncountedTrend('printf missing') + assert DistanceReadings((DistanceReading(1, missing), DistanceReading(2, CountPrinted(2)))).trend.projection == '' + + def test_an_unpushed_commit_names_its_sha_and_time(checkout): sha = commit(checkout[1]) progress = read(checkout) From 28960d3f49f9c1ff23955bee9c419f719b3e5233 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:52:50 -0400 Subject: [PATCH 3/4] Retain the original check command on each distance observation --- src/agent_comms/todos.py | 14 +++++++------- tests/test_order_progress.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index ac85650989..b31cf455b1 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -262,6 +262,7 @@ def _clock(at: float) -> str: class DistanceReading: at: float distance: CheckPrinted + command: str = "" @dataclass(frozen=True) @@ -269,7 +270,6 @@ class DistanceReadings: """The order's last two distance readings, oldest first: its base at the order, then its newest commit. Fewer than two read, or a last reading at done, project nothing.""" readings: tuple[DistanceReading, ...] = () - command: str = "" @property def trend(self) -> DistanceTrend: @@ -277,7 +277,7 @@ def trend(self) -> DistanceTrend: if not self.readings: return UnreadTrend() return self.readings[-1].distance.project( - self.counted_trend, lambda: UncountedTrend(self.command)) + self.counted_trend, lambda: UncountedTrend(self.readings[-1].command)) def counted_trend(self, final: int) -> DistanceTrend: last = self.readings[-2:] @@ -389,8 +389,7 @@ def readings(self, source: ProgressSource) -> DistanceReadings: "--not", base).splitlines()))] points = [(base, source.since), *commits][-2:] return DistanceReadings( - tuple(DistanceReading(at, source.distance(self, base, sha)) for sha, at in points), - source.todo.order.check.command) + tuple(source.reading(self, base, sha, at) for sha, at in points)) @dataclass(frozen=True) @@ -421,12 +420,13 @@ def commits(self, *excluded: str) -> Iterator[tuple[str, float]]: def readings(self) -> DistanceReadings: return self.checkout.readings(self) - def distance(self, checkout: PresentCheckout, base: str, sha: str) -> CheckPrinted: - """Run the order's check and preserve its count state beside untouched edits.""" + def reading(self, checkout: PresentCheckout, base: str, sha: str, at: float) -> DistanceReading: + """Keep the check command with its measured or missing distance.""" check = self.todo.order.check touched = checkout.git("diff", "--name-only", base, sha).split() run = check.at(sha, str(checkout.path)) - return check.result.distance(run).plus(self.todo.order.untouched(touched)) + return DistanceReading(at, check.result.distance(run).plus( + self.todo.order.untouched(touched)), check.command) @dataclass(frozen=True) diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index ee1eddce87..b676797076 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -90,7 +90,7 @@ def test_uncounted_check_keeps_other_overdue_orders_visible(commander, commander assert 'distance 0' not in alarm and 'projected done' not in alarm missing = NoIntegerPrinted() assert missing.distance(1) is missing and missing.plus(3) is missing - assert DistanceReadings((DistanceReading(1, missing),), 'printf missing').trend == UncountedTrend('printf missing') + assert DistanceReadings((DistanceReading(1, missing, 'printf missing'),)).trend == UncountedTrend('printf missing') assert DistanceReadings((DistanceReading(1, missing), DistanceReading(2, CountPrinted(2)))).trend.projection == '' From 1a044998771dfb82869d1d7a3a1b0fe093b43375 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:59:22 -0400 Subject: [PATCH 4/4] Keep proof refusal distinct from non-throwing distance observations --- src/agent_comms/child_process.py | 2 +- tests/test_commander_problem_orders.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/child_process.py b/src/agent_comms/child_process.py index 12370fc36b..f12253f903 100644 --- a/src/agent_comms/child_process.py +++ b/src/agent_comms/child_process.py @@ -1067,7 +1067,7 @@ def plus(self, amount: int) -> CheckPrinted: return self def require(self, expected: object) -> None: - raise ValueError(f"The order check printed no integer; cannot prove {expected}.") + raise ValueError(f"The order check cannot prove {expected}: it printed no integer.") def project[T](self, counted: Callable[[int], T], uncounted: Callable[[], T]) -> T: return uncounted() diff --git a/tests/test_commander_problem_orders.py b/tests/test_commander_problem_orders.py index bb6a80f282..f98a2d39f4 100644 --- a/tests/test_commander_problem_orders.py +++ b/tests/test_commander_problem_orders.py @@ -659,7 +659,7 @@ def test_commander_refuses_a_check_that_prints_no_count(commander_script): @pytest.mark.parametrize('printed, refused', [ - ("printf 'no count here'", 'The order check printed no integer; cannot prove CountCheckResult(expected=1).'), + ("printf 'no count here'", 'The order check cannot prove CountCheckResult(expected=1): it printed no integer.'), ('echo 2', 'The order check is 1 away from CountCheckResult(expected=1).'), ('echo found 1', None), ])