From 86f9f8e1c3757be9a03d9735829a65934b61ac60 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 03:58:22 -0400 Subject: [PATCH 1/3] Read commander HUD fields from one native order frame --- src/agent_comms/commander/vibe_check.py | 15 ++- src/agent_comms/commander_heartbeat.py | 117 +++++++++++++++++++++++- src/agent_comms/todos.py | 15 ++- src/agent_comms/tools.py | 13 +++ 4 files changed, 151 insertions(+), 9 deletions(-) diff --git a/src/agent_comms/commander/vibe_check.py b/src/agent_comms/commander/vibe_check.py index 9c45c4ff0d..9096489391 100644 --- a/src/agent_comms/commander/vibe_check.py +++ b/src/agent_comms/commander/vibe_check.py @@ -189,16 +189,25 @@ class OverloadDegradation(Degradation, BlocksRelease, ToolResult): @classmethod def of(cls, comms: Comms, commander: str) -> Self: - return cls( - len(StandingRule.pending_todos(comms, commander)), + return cls.from_orders( + StandingRule.todos(comms, commander), len(ReviewTimerUp.violations(comms, commander)), OrderEditCountRead.count(comms, commander), ) + @classmethod + def from_orders(cls, orders: Sequence[Todo], timers_up: int, unjudged: int) -> Self: + """The same load projection for a captured HUD read and an independent rule read.""" + return cls(sum(todo.pending for todo in orders), timers_up, unjudged) + @projected(view="wire") def total(self) -> float: return self.owed + 2 * self.timers_up + self.unjudged / 10 + @projected(view="wire") + def overloaded(self) -> bool: + return self.total >= self.ceiling + def __str__(self) -> str: return ( f"load {self.total:.0f} ({self.owed} open orders, " @@ -208,7 +217,7 @@ def __str__(self) -> str: @classmethod def found(cls, comms: Comms, commander: str) -> tuple[Sign, ...]: load = cls.of(comms, commander) - if load.total < cls.ceiling: + if not load.overloaded: return () return (Sign(cls, (int(time.time() // cls.round_seconds),), f"{load}, over {cls.ceiling:.0f}"),) diff --git a/src/agent_comms/commander_heartbeat.py b/src/agent_comms/commander_heartbeat.py index b3da6bbc0c..594ed23e3d 100644 --- a/src/agent_comms/commander_heartbeat.py +++ b/src/agent_comms/commander_heartbeat.py @@ -2,22 +2,135 @@ from __future__ import annotations +import time from dataclasses import dataclass, field from typing import TYPE_CHECKING +from agent_comms.commander.config import CommanderConfig +from agent_comms.commander.vibe_check import OverloadDegradation +from agent_comms.field_codec import projected from agent_comms.message_bus import WatchPass +from agent_comms.plan_phases import PlanPhase +from agent_comms.recorded_time import RecordedEventTime from agent_comms.registration import Registration from agent_comms.service_sender import ServiceSender from agent_comms.standing_rules import StandingRule from agent_comms.thread_identity import ThreadIncarnation from agent_comms.threads import Thread -from agent_comms.todos import ProgressRead +from agent_comms.todos import ProgressRead, PullRequest, Todo, UnmergedListedPullRequest +from agent_comms.tool_result import ToolResult +from agent_comms.turn_digests import OrderEditCountRead from agent_comms.ui_model.unit_state import UnitState if TYPE_CHECKING: from agent_comms.comms import Comms from agent_comms.goal_management import Goals - from agent_comms.todos import OwnerRequest, Todo + from agent_comms.todos import OwnerRequest + + +@dataclass(frozen=True) +class CommanderOrder: + """Small order card; identifiers/text come from Todo, review rounds from TodoStore.""" + + problem: str + text: str + order: str + review_timers_up: tuple[RecordedEventTime, ...] + strikes: int + unjudged_edits: int + + +@dataclass(frozen=True) +class CommanderUnit: + """One holder's orders, live strikes and GitHub's open ready/draft requests.""" + + unit: ThreadIncarnation + orders: tuple[CommanderOrder, ...] + strikes: int + ready_prs: tuple[PullRequest, ...] + draft_prs: tuple[PullRequest, ...] + + +@dataclass(frozen=True) +class CommanderPhase: + """PlanPhase supplies every answer; no HUD status taxonomy is maintained beside it.""" + + phase: type[PlanPhase] + done: bool + ready: bool + holders: tuple[ThreadIncarnation, ...] + + +@dataclass(frozen=True) +class CommanderHud(ToolResult): + """One disposable view of native records, shared by comms_hud and the commander board. + + TodoStore owns orders, review timers and strikes; OrderEditCountRead owns edit counts; + PullRequests owns ready/draft facts; PlanPhase owns phase state; OverloadDegradation + owns the load formula and decision. Nothing here writes or retains a board. + """ + + commander: ThreadIncarnation + units: tuple[CommanderUnit, ...] + phases: tuple[CommanderPhase, ...] + load: OverloadDegradation + + @classmethod + def read(cls, comms: Comms, commander: str) -> CommanderHud: + snapshot, now = comms.registry.snapshot(), time.time() + owner = snapshot.require(commander).incarnation + store = comms.goals.todos + history = store.history_for_commander(owner) + held = tuple(todo for todo in history if todo.holder) + pulls = ProgressRead(held) + timers = store.timers_up_in(held, comms.registry, now, pulls) + edits = { + read.todo.id: read.read() + for read in OrderEditCountRead.for_orders(comms.root, snapshot, held) + } + grouped: dict[ThreadIncarnation, list[Todo]] = {} + for todo in held: + grouped.setdefault(todo.held().owner_identity, []).append(todo) + units = [] + for unit, orders in grouped.items(): + requests = { + (todo.repo, request.number): request + for todo in orders + for request in pulls.listed(todo.repo, UnmergedListedPullRequest, todo) + } + units.append(CommanderUnit( + unit, + tuple(CommanderOrder( + todo.id, todo.text, todo.order.checked().id, + tuple(at for due, at in timers if due.id == todo.id), + todo.strike_count, edits[todo.id], + ) for todo in orders), + sum(len(todo.live_strikes(unit.name)) for todo in history), + tuple(request for request in requests.values() if not request.draft), + tuple(request for request in requests.values() if request.draft), + )) + reading = CommanderConfig.read_root(comms.root).plan_reading(history) + phases = tuple(CommanderPhase( + phase, phase.done(reading), phase.ready(reading), + tuple(dict.fromkeys(todo.held().owner_identity for todo in phase.holding(reading))), + ) for phase in PlanPhase.members_with(PlanPhase)) + return cls(owner, tuple(units), phases, + OverloadDegradation.from_orders(held, len(timers), sum(edits.values()))) + + @projected(view="wire") + def frame(self) -> tuple[str, ...]: + """One line per declared phase or holder, with their original order and PR identities.""" + return ( + str(self.load), + *(f"{row.phase.plan.declared_name}/{row.phase.declared_name} " + f"done={row.done} ready={row.ready} " + f"holders={','.join(unit.name for unit in row.holders)}" for row in self.phases), + *(f"{row.unit.name}: orders={','.join(order.problem for order in row.orders)} " + f"review timers={sum(len(order.review_timers_up) for order in row.orders)} " + f"strikes={row.strikes} edits={sum(order.unjudged_edits for order in row.orders)} " + f"ready={','.join(str(pr.number) for pr in row.ready_prs)} " + f"draft={','.join(str(pr.number) for pr in row.draft_prs)}" for row in self.units), + ) @dataclass diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index f1008ce2f8..69f662f920 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -511,6 +511,7 @@ class PullRequest(DeclaredFamily, affix="PullRequest"): orders: tuple[str, ...] = () base: str = field(default=WorkBranches.main, kw_only=True) problems: tuple[str, ...] = field(default=(), kw_only=True) + draft: bool = field(default=False, kw_only=True) def belongs_to(self, todo: Todo) -> bool: return (todo.id in self.problems @@ -563,10 +564,11 @@ class ListedPullRequest(DeclaredFamily, affix="ListedPullRequest"): head: str updated: str base: str + draft: bool = field(default=False, kw_only=True) def parsed(self, problems: tuple[str, ...], orders: tuple[str, ...]) -> PullRequest: return UnmergedPullRequest(self.number, self.branch, self.head, _github_time(self.updated), - orders, base=self.base, problems=problems) + orders, base=self.base, problems=problems, draft=self.draft) class UnmergedListedPullRequest(ListedPullRequest): @@ -586,7 +588,7 @@ def parsed(self, problems: tuple[str, ...], orders: tuple[str, ...]) -> MergedPu return MergedPullRequest(self.number, self.branch, self.head, _github_time(self.updated), orders, base=self.base, merge=self.merge.oid, merged_at=_github_time(self.merged), - problems=problems) + problems=problems, draft=self.draft) class PullRequests: @@ -625,7 +627,7 @@ def read(self, repo: RepositoryName, kind: type[ListedPullRequest], return () selections = [ f'p{number}: pullRequest(number: {number}) {{ number branch: headRefName ' - 'head: headRefOid base: baseRefName closed merge: mergeCommit { oid } ' + 'head: headRefOid base: baseRefName draft: isDraft closed merge: mergeCommit { oid } ' 'merged: mergedAt updated: updatedAt }' for number in linked] query = (f'query {{ repository(owner: {json.dumps(repo.owner)}, ' f'name: {json.dumps(repo.name)}) {{ {" ".join(selections)} }} }}') @@ -3844,7 +3846,12 @@ def timers_up_for_commander(self, commander: ThreadIncarnation, registry: Regist now: float) -> tuple[tuple[Todo, RecordedEventTime], ...]: """Held work whose review timer is up, with its round; merged work waits on install.""" held = self.held_for_commander(commander) - pulls = ProgressRead(held) + return self.timers_up_in(held, registry, now, ProgressRead(held)) + + @staticmethod + def timers_up_in(held: Sequence[Todo], registry: Registration, now: float, + pulls: ProgressRead) -> tuple[tuple[Todo, RecordedEventTime], ...]: + """Review rounds from this read's orders and PR facts, excluding installation waits.""" return tuple((todo, up) for todo in held for up in todo.timer_up(now) if not todo.order.waiting_on_install( todo, {todo.id: OrderProgress.read(todo, registry, pulls)})) diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index dc48c865a3..d7a075200f 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -57,6 +57,7 @@ ) from .cli_result import ToolCatalogResult from .commander.vibe_check import OverloadDegradation +from .commander_heartbeat import CommanderHud from .coordination_tables.refusals import SendRefusal from .declared_family import DeclaredFamily from .display_order import ChannelSort, ThreadSort @@ -982,6 +983,18 @@ def apply(self, comms: Comms, caller: ToolCaller) -> OverloadDegradation: return OverloadDegradation.of(comms, caller.subject(self.thread)) +@dataclass(frozen=True, kw_only=True) +class CommsHudTool(ThreadReadTool): + label = "Commander HUD" + description = ( + "comms_hud reads orders by holder, review timers, strikes, unjudged edit counts, " + "ready/draft PRs, declared plan phases and the overload decision from their owners." + ) + + def apply(self, comms: Comms, caller: ToolCaller) -> CommanderHud: + return CommanderHud.read(comms, caller.subject(self.thread)) + + @dataclass(frozen=True, kw_only=True) class CommsFireTool(ThreadToolRequest): label = "Fire unit" From f9c4efd40277906309285da9aee8fc7b3abfe7af Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 04:02:02 -0400 Subject: [PATCH 2/3] Replace private board rendering with the native HUD view --- src/agent_comms/commander/order.py | 22 --- src/agent_comms/commander_heartbeat.py | 117 +-------------- src/agent_comms/commander_hud.py | 140 ++++++++++++++++++ .../skills/comms-commander/SKILL.md | 2 +- src/agent_comms/todos.py | 37 ++--- src/agent_comms/tools.py | 2 +- tests/test_commander_forks.py | 5 +- tests/test_order_claims.py | 4 +- tests/test_todos.py | 3 +- 9 files changed, 166 insertions(+), 166 deletions(-) create mode 100644 src/agent_comms/commander_hud.py diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index 8f545458b9..d66444aa01 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -543,23 +543,6 @@ def last_activity(self, unit): thread = self.comms.registry.require(unit) return thread.runtime.history_source(self.comms.root, thread.incarnation).last_tool_call() - def board_text(self) -> str: - """Render the live Todo view without saving a competing board file.""" - lines = ['# Fleet board', '', - "Live view of Core's Todo store; no board file is saved.", '', - '| Problem | Owner file | Owner | Order | Due | Check | Progress | Order state ' - '| Strikes | Owner last acted |', - '|---|---|---|---|---|---|---|---|---|---|'] - listed, read = self.todos(), self.progress() - activity = {unit: self.last_activity(unit) for unit in {t.holder for t in listed if t.holder}} - for t in sorted(listed, key=lambda t: ProblemNumber.parse(t.id).number): - lines.append(f'| {t.id}: {t.text.replace("|", "|")} | {t.order.board_cells(t)} | ' - f'{read[t.id].line if t.id in read else "-"} | {t.order.line} | ' - f'{t.strike_count} | {activity[t.holder].clock() if t.holder else "-"} |') - history = self.comms.goals.todos.history_for_commander(self.incarnation) - lines += ['', '## Withdrawn orders', '', - *(f'- {line}' for t in history for line in t.order.withdrawals(t))] - return '\n'.join(lines) + '\n' class OrderedProblems(ABC): @@ -1415,11 +1398,6 @@ def run(self, ctx: Commander) -> None: print(thread.name) -class BoardVerb(OrderVerb): - help = "show Core's current Todo store without saving a board file" - - def run(self, ctx: Commander) -> None: - print(ctx.board_text(), end='') def main(a, ctx: Commander): diff --git a/src/agent_comms/commander_heartbeat.py b/src/agent_comms/commander_heartbeat.py index 594ed23e3d..b3da6bbc0c 100644 --- a/src/agent_comms/commander_heartbeat.py +++ b/src/agent_comms/commander_heartbeat.py @@ -2,135 +2,22 @@ from __future__ import annotations -import time from dataclasses import dataclass, field from typing import TYPE_CHECKING -from agent_comms.commander.config import CommanderConfig -from agent_comms.commander.vibe_check import OverloadDegradation -from agent_comms.field_codec import projected from agent_comms.message_bus import WatchPass -from agent_comms.plan_phases import PlanPhase -from agent_comms.recorded_time import RecordedEventTime from agent_comms.registration import Registration from agent_comms.service_sender import ServiceSender from agent_comms.standing_rules import StandingRule from agent_comms.thread_identity import ThreadIncarnation from agent_comms.threads import Thread -from agent_comms.todos import ProgressRead, PullRequest, Todo, UnmergedListedPullRequest -from agent_comms.tool_result import ToolResult -from agent_comms.turn_digests import OrderEditCountRead +from agent_comms.todos import ProgressRead from agent_comms.ui_model.unit_state import UnitState if TYPE_CHECKING: from agent_comms.comms import Comms from agent_comms.goal_management import Goals - from agent_comms.todos import OwnerRequest - - -@dataclass(frozen=True) -class CommanderOrder: - """Small order card; identifiers/text come from Todo, review rounds from TodoStore.""" - - problem: str - text: str - order: str - review_timers_up: tuple[RecordedEventTime, ...] - strikes: int - unjudged_edits: int - - -@dataclass(frozen=True) -class CommanderUnit: - """One holder's orders, live strikes and GitHub's open ready/draft requests.""" - - unit: ThreadIncarnation - orders: tuple[CommanderOrder, ...] - strikes: int - ready_prs: tuple[PullRequest, ...] - draft_prs: tuple[PullRequest, ...] - - -@dataclass(frozen=True) -class CommanderPhase: - """PlanPhase supplies every answer; no HUD status taxonomy is maintained beside it.""" - - phase: type[PlanPhase] - done: bool - ready: bool - holders: tuple[ThreadIncarnation, ...] - - -@dataclass(frozen=True) -class CommanderHud(ToolResult): - """One disposable view of native records, shared by comms_hud and the commander board. - - TodoStore owns orders, review timers and strikes; OrderEditCountRead owns edit counts; - PullRequests owns ready/draft facts; PlanPhase owns phase state; OverloadDegradation - owns the load formula and decision. Nothing here writes or retains a board. - """ - - commander: ThreadIncarnation - units: tuple[CommanderUnit, ...] - phases: tuple[CommanderPhase, ...] - load: OverloadDegradation - - @classmethod - def read(cls, comms: Comms, commander: str) -> CommanderHud: - snapshot, now = comms.registry.snapshot(), time.time() - owner = snapshot.require(commander).incarnation - store = comms.goals.todos - history = store.history_for_commander(owner) - held = tuple(todo for todo in history if todo.holder) - pulls = ProgressRead(held) - timers = store.timers_up_in(held, comms.registry, now, pulls) - edits = { - read.todo.id: read.read() - for read in OrderEditCountRead.for_orders(comms.root, snapshot, held) - } - grouped: dict[ThreadIncarnation, list[Todo]] = {} - for todo in held: - grouped.setdefault(todo.held().owner_identity, []).append(todo) - units = [] - for unit, orders in grouped.items(): - requests = { - (todo.repo, request.number): request - for todo in orders - for request in pulls.listed(todo.repo, UnmergedListedPullRequest, todo) - } - units.append(CommanderUnit( - unit, - tuple(CommanderOrder( - todo.id, todo.text, todo.order.checked().id, - tuple(at for due, at in timers if due.id == todo.id), - todo.strike_count, edits[todo.id], - ) for todo in orders), - sum(len(todo.live_strikes(unit.name)) for todo in history), - tuple(request for request in requests.values() if not request.draft), - tuple(request for request in requests.values() if request.draft), - )) - reading = CommanderConfig.read_root(comms.root).plan_reading(history) - phases = tuple(CommanderPhase( - phase, phase.done(reading), phase.ready(reading), - tuple(dict.fromkeys(todo.held().owner_identity for todo in phase.holding(reading))), - ) for phase in PlanPhase.members_with(PlanPhase)) - return cls(owner, tuple(units), phases, - OverloadDegradation.from_orders(held, len(timers), sum(edits.values()))) - - @projected(view="wire") - def frame(self) -> tuple[str, ...]: - """One line per declared phase or holder, with their original order and PR identities.""" - return ( - str(self.load), - *(f"{row.phase.plan.declared_name}/{row.phase.declared_name} " - f"done={row.done} ready={row.ready} " - f"holders={','.join(unit.name for unit in row.holders)}" for row in self.phases), - *(f"{row.unit.name}: orders={','.join(order.problem for order in row.orders)} " - f"review timers={sum(len(order.review_timers_up) for order in row.orders)} " - f"strikes={row.strikes} edits={sum(order.unjudged_edits for order in row.orders)} " - f"ready={','.join(str(pr.number) for pr in row.ready_prs)} " - f"draft={','.join(str(pr.number) for pr in row.draft_prs)}" for row in self.units), - ) + from agent_comms.todos import OwnerRequest, Todo @dataclass diff --git a/src/agent_comms/commander_hud.py b/src/agent_comms/commander_hud.py new file mode 100644 index 0000000000..dd8eaa5560 --- /dev/null +++ b/src/agent_comms/commander_hud.py @@ -0,0 +1,140 @@ +"""Commander display derived from the native record owners in one read.""" + +from __future__ import annotations + +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from agent_comms.commander.config import CommanderConfig +from agent_comms.commander.vibe_check import OverloadDegradation +from agent_comms.field_codec import projected +from agent_comms.plan_phases import PlanPhase +from agent_comms.recorded_time import RecordedEventTime +from agent_comms.thread_identity import ThreadIncarnation +from agent_comms.todos import OrderProgress, ProgressRead, PullRequest, Todo, UnmergedListedPullRequest +from agent_comms.tool_result import ToolResult +from agent_comms.turn_digests import OrderEditCountRead + +if TYPE_CHECKING: + from agent_comms.comms import Comms + + +@dataclass(frozen=True) +class CommanderOrder: + """Small order card; identifiers/text come from Todo, review rounds from TodoStore.""" + + problem: str + text: str + order: str + state: str + review_timers_up: tuple[RecordedEventTime, ...] + strikes: int + unjudged_edits: int + + +@dataclass(frozen=True) +class CommanderUnit: + """One holder's orders, live strikes and GitHub's open ready/draft requests.""" + + unit: ThreadIncarnation + orders: tuple[CommanderOrder, ...] + strikes: int + ready_prs: tuple[PullRequest, ...] + draft_prs: tuple[PullRequest, ...] + + def line(self) -> str: + orders = "; ".join(f"{order.problem}: {order.state}" for order in self.orders) + return ( + f"{self.unit.name}: orders={orders} " + f"review timers={sum(len(order.review_timers_up) for order in self.orders)} " + f"strikes={self.strikes} edits={sum(order.unjudged_edits for order in self.orders)} " + f"ready={','.join(str(pr.number) for pr in self.ready_prs)} " + f"draft={','.join(str(pr.number) for pr in self.draft_prs)}" + ) + + +@dataclass(frozen=True) +class CommanderPhase: + """PlanPhase supplies every answer; no HUD status taxonomy is maintained beside it.""" + + phase: type[PlanPhase] + done: bool + ready: bool + holders: tuple[ThreadIncarnation, ...] + + def line(self) -> str: + return ( + f"{self.phase.plan.declared_name}/{self.phase.declared_name} " + f"done={self.done} ready={self.ready} " + f"holders={','.join(unit.name for unit in self.holders)}" + ) + + +@dataclass(frozen=True) +class CommanderHud(ToolResult): + """One disposable view of native records, shared by comms_hud and the commander board. + + TodoStore owns orders, review timers and strikes; OrderEditCountRead owns edit counts; + PullRequests owns ready/draft facts; PlanPhase owns phase state; OverloadDegradation + owns the load formula and decision. Nothing here writes or retains a board. + """ + + commander: ThreadIncarnation + units: tuple[CommanderUnit, ...] + phases: tuple[CommanderPhase, ...] + load: OverloadDegradation + + @classmethod + def read(cls, comms: Comms, commander: str) -> CommanderHud: + snapshot, now = comms.registry.snapshot(), time.time() + owner = snapshot.require(commander).incarnation + store = comms.goals.todos + history = store.history_for_commander(owner) + held = tuple(todo for todo in history if todo.holder) + pulls = ProgressRead(held) + progress = {todo.id: OrderProgress.read(todo, comms.registry, pulls) for todo in held} + timers = store.timers_up_in(held, now, progress) + edits = { + read.todo.id: read.read() + for read in OrderEditCountRead.for_orders(comms.root, snapshot, held) + } + grouped: dict[ThreadIncarnation, list[Todo]] = {} + for todo in held: + grouped.setdefault(todo.held().owner_identity, []).append(todo) + units = [] + for unit, orders in grouped.items(): + requests = { + (todo.repo, request.number): request + for todo in orders + for request in pulls.listed(todo.repo, UnmergedListedPullRequest, todo) + } + units.append(CommanderUnit( + unit, + tuple(CommanderOrder( + todo.id, todo.text, todo.order.checked().id, todo.order.line, + tuple(at for due, at in timers if due.id == todo.id), + todo.strike_count, edits[todo.id], + ) for todo in orders), + sum(len(todo.live_strikes(unit.name)) for todo in history), + tuple(request for request in requests.values() if not request.draft), + tuple(request for request in requests.values() if request.draft), + )) + reading = CommanderConfig.read_root(comms.root).plan_reading(history) + phases = tuple(CommanderPhase( + phase, phase.done(reading), phase.ready(reading), + tuple(dict.fromkeys(todo.held().owner_identity for todo in phase.holding(reading))), + ) for phase in PlanPhase.members_with(PlanPhase)) + return cls(owner, tuple(units), phases, + OverloadDegradation.from_orders(held, len(timers), sum(edits.values()))) + + @projected(view="wire") + def frame(self) -> tuple[str, ...]: + """One line per declared phase or holder, with their original order and PR identities.""" + return ( + str(self.load), + *(row.line() for row in self.phases), + *(row.line() for row in self.units), + ) + + diff --git a/src/agent_comms/skills/comms-commander/SKILL.md b/src/agent_comms/skills/comms-commander/SKILL.md index 9ba0a225c7..a0d07b626d 100644 --- a/src/agent_comms/skills/comms-commander/SKILL.md +++ b/src/agent_comms/skills/comms-commander/SKILL.md @@ -72,7 +72,7 @@ The declaration supplies the ID and first-call times when omitted. A mechanical ## The board -Core's Todo store is the only record of fixes and their history; `order board` renders it. Orders, timers, owners, delivery and installed evidence change only through Core's Todo operations. A defect with no owner gets one the same tick. +Core's Todo store is the only record of fixes and their history; `comms_hud` reads it beside plan phases, PRs and overload. Orders, timers, owners, delivery and installed evidence change only through Core's Todo operations. A defect with no owner gets one the same tick. Priority: the owner's latest message; a broken install, lost or replayed input, wrong messages, or a unit blocked on you; then the owner's plans, which are the default work. Slop found outside a plan goes into the plan's surface or a plan phase, never ahead of it. diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 69f662f920..fd8696ff4b 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -642,16 +642,18 @@ def read(self, repo: RepositoryName, kind: type[ListedPullRequest], raise WorkBranchError( listed.stderr.strip() or f"GitHub order lookup in {repo.to_wire()} failed.") rows = FieldCodec.decode(dict[str, ListedPullRequest], FieldCodec.parse(listed.stdout)) - requests = tuple(row.parsed( + requests = tuple((row, row.parsed( tuple(candidate.id for candidate in linked[row.number]), tuple(dict.fromkeys(order.id for candidate in linked[row.number] for order in candidate.order_history - if PullRequestLink(repo, row.number) in order.pull_requests))) - for row in rows.values() if isinstance(row, kind)) - for candidate in problems.values(): - self._read[repo, kind, candidate.id] = tuple(sorted( - (request for request in requests if request.belongs_to(candidate)), - key=lambda request: request.updated_at, reverse=True)) + if PullRequestLink(repo, row.number) in order.pull_requests)))) + for row in rows.values()) + for selection in dict.fromkeys((kind, *ListedPullRequest.members_with(ListedPullRequest))): + for candidate in problems.values(): + self._read[repo, selection, candidate.id] = tuple(sorted( + (request for row, request in requests + if isinstance(row, selection) and request.belongs_to(candidate)), + key=lambda request: request.updated_at, reverse=True)) return self._read[repo, kind, todo.id] @@ -1564,9 +1566,6 @@ def reissued(self, at: float) -> PendingProblemOrder: """This order sent again at ``at`` to a new owner, its first-call window kept.""" raise ValueError("Only an undelivered order can change owner.") - def board_cells(self, todo: Todo) -> str: - return f"{self.finding.place()} | unassigned | - | - | -" - def checked(self) -> CheckedProblemOrder: raise ValueError("This problem has no checked order.") @@ -1845,13 +1844,6 @@ def declarations(self) -> tuple[CheckedProblemOrder, ...]: ) serves: Annotated[type[OrderPhase], WireOmitDefault()] = UnplannedPhase - def board_cells(self, todo: Todo) -> str: - last = todo.released[-1:] - owner = todo.holder or (f"unassigned: released from {last[0].previous.owner}: " - f"{last[0].reason.text}") - check = json.dumps(FieldCodec.encode(self.check)).replace("|", "|") - return f"{self.owner.to_wire()} | {owner} | {self.id} | {todo.review_at.clock()} | {check}" - @property def phase(self) -> type[OrderPhase]: return self.serves @@ -3846,15 +3838,16 @@ def timers_up_for_commander(self, commander: ThreadIncarnation, registry: Regist now: float) -> tuple[tuple[Todo, RecordedEventTime], ...]: """Held work whose review timer is up, with its round; merged work waits on install.""" held = self.held_for_commander(commander) - return self.timers_up_in(held, registry, now, ProgressRead(held)) + pulls = ProgressRead(held) + progress = {todo.id: OrderProgress.read(todo, registry, pulls) for todo in held} + return self.timers_up_in(held, now, progress) @staticmethod - def timers_up_in(held: Sequence[Todo], registry: Registration, now: float, - pulls: ProgressRead) -> tuple[tuple[Todo, RecordedEventTime], ...]: + def timers_up_in(held: Sequence[Todo], now: float, + progress: Mapping[str, OrderProgress]) -> tuple[tuple[Todo, RecordedEventTime], ...]: """Review rounds from this read's orders and PR facts, excluding installation waits.""" return tuple((todo, up) for todo in held for up in todo.timer_up(now) - if not todo.order.waiting_on_install( - todo, {todo.id: OrderProgress.read(todo, registry, pulls)})) + if not todo.order.waiting_on_install(todo, progress)) def held_for_commander(self, commander: ThreadIncarnation) -> tuple[Todo, ...]: return self._select("creator=? AND creator_created=? AND assignment IS NOT NULL", diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index d7a075200f..861e6b19a9 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -57,7 +57,7 @@ ) from .cli_result import ToolCatalogResult from .commander.vibe_check import OverloadDegradation -from .commander_heartbeat import CommanderHud +from .commander_hud import CommanderHud from .coordination_tables.refusals import SendRefusal from .declared_family import DeclaredFamily from .display_order import ChannelSort, ThreadSort diff --git a/tests/test_commander_forks.py b/tests/test_commander_forks.py index 4bd444d1a1..a0320eee22 100644 --- a/tests/test_commander_forks.py +++ b/tests/test_commander_forks.py @@ -35,7 +35,7 @@ from agent_comms.problem_number import ProblemNumber from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_identity import RecordedThreadIncarnation -from agent_comms.tools import CommsCommanderLoadTool, CommsTodoListTool +from agent_comms.tools import CommsCommanderLoadTool, CommsHudTool, CommsTodoListTool from agent_comms.thread_model import DeclaredModel from agent_comms.todos import ( DeclarationDerivation, PendingProblemOrder, ProblemAllocation, ReleaseTodo, @@ -145,7 +145,8 @@ def test_registered_fork_sends_parent_problem_with_fork_actor(fork_bus, tmp_path store.require_unit_in(db, saved.held().owner_identity, reissuer) print(f'Core send actor: {saved.held().parent}; problem commander: {saved.creator}') ctx = CommanderContext.read_root(comms.root).model_copy(update={'actor': CommanderForkCaller(fork.name)}) - assert ctx.board_text() == Commander.read_root(comms.root).board_text() + assert ctx.call(CommsHudTool(thread=ctx.commander)) == Commander.read_root(comms.root).call( + CommsHudTool(thread=ctx.commander)) assert set(ctx.model_copy(deep=True).progress()) == {'P40'} assert ctx.held_orders() == (saved,) assert ctx.call(CommsTodoListTool(thread=ctx.commander)).todos == (saved,) diff --git a/tests/test_order_claims.py b/tests/test_order_claims.py index 4e41fc9200..91860fbda1 100644 --- a/tests/test_order_claims.py +++ b/tests/test_order_claims.py @@ -28,7 +28,7 @@ ) from agent_comms.tool_caller import ThreadCaller from agent_comms.tools import ( - CommsHistoryTool, CommsOrderDeliverTool, CommsVerdictTool, TodoChanged, + CommsHistoryTool, CommsHudTool, CommsOrderDeliverTool, CommsVerdictTool, TodoChanged, ) from test_held_problem_orders import ( # noqa: F401 - shared copied-bus fixture family held_commander, @@ -68,7 +68,7 @@ def test_claim_verdict_family_on_retained_copy(held_commander): if message.message_id == claimed.order.claim.message.message_id) assert sent.sender == "worker" and sent.target == ctx.commander assert sent.answers and sent.answers == claimed.order.claim.message.answers - assert "Claimed by the unit" in ctx.board_text() + assert "Claimed by the unit" in "\n".join(ctx.call(CommsHudTool()).frame) with pytest.raises(TodoError, match="Only a pending order"): ThreadCaller("worker").invoke(request, comms) assert comms.goals.todos.get("P1") == claimed diff --git a/tests/test_todos.py b/tests/test_todos.py index 4374a3d6d6..9e417ad11f 100644 --- a/tests/test_todos.py +++ b/tests/test_todos.py @@ -42,6 +42,7 @@ from agent_comms.store_files import store_lock_path from agent_comms.thread_identity import CoreWatcherIncarnation from agent_comms.threads import Thread +from agent_comms.tools import CommsHudTool from agent_comms.todos import ( Assignment, ChangeTodoState, @@ -529,7 +530,7 @@ def test_installed_merge_closes_only_the_run_that_has_been_proven(pr_commander): assert application.order.sha == mechanical.order.sha assert not application.order.accepted assert application.order.line == application.order.checked().run.merged_line - board = checked.board_text() + board = "\n".join(checked.call(CommsHudTool()).frame) assert application.order.line in board assert worker.name in board store.record_merges(store.merged_orders(checked.incarnation, checked.comms.registry)) From be3118000e5f67151bb53aa684f416c1a54da34e Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 04:05:55 -0400 Subject: [PATCH 3/3] Expose native progress on HUD cards and migrate board consumers --- src/agent_comms/commander/rules/tests-and-commands.md | 2 +- src/agent_comms/commander_hud.py | 7 +++++-- tests/test_commander_forks.py | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/commander/rules/tests-and-commands.md b/src/agent_comms/commander/rules/tests-and-commands.md index 6258c64fbe..72dde39bea 100644 --- a/src/agent_comms/commander/rules/tests-and-commands.md +++ b/src/agent_comms/commander/rules/tests-and-commands.md @@ -12,5 +12,5 @@ type: feedback - Refactor tests with the code: per-member copies become one family test, hand-built setup one fixture owner. Tristan, 2026-10-18: "no stupid test theater, refactor the slop out of tests as you go, they're fucking sloppy". - A red test or failure you meet is fixed at its owner in the change at hand, or handed the same tick to the agent whose open work touches that code, named with its PR. "Not my change" without a named owner is a hidden failure, and every later run pays for it by asking whether a red is real. Tristan, 2026-10-22: "imagine if it doesn't get fixed and in the future you always end up checking oh is this a real failure?"; "not my change is valid if another agent is touching it, but they must be named". - An order check counts over the whole source tree (`grep -r ... src`), never one file. -- Run verb gate loops under `bash -c`: zsh passes a quoted "order board" as one argument. +- Run verb gate loops under `bash -c`: zsh passes a quoted "order idle" as one argument. - A wait or kill by process pattern brackets its first letter (`grep -q "[v]erify"`) or waits on a file or PID, or it matches itself. diff --git a/src/agent_comms/commander_hud.py b/src/agent_comms/commander_hud.py index dd8eaa5560..467cd6f074 100644 --- a/src/agent_comms/commander_hud.py +++ b/src/agent_comms/commander_hud.py @@ -28,6 +28,7 @@ class CommanderOrder: text: str order: str state: str + progress: OrderProgress review_timers_up: tuple[RecordedEventTime, ...] strikes: int unjudged_edits: int @@ -44,7 +45,9 @@ class CommanderUnit: draft_prs: tuple[PullRequest, ...] def line(self) -> str: - orders = "; ".join(f"{order.problem}: {order.state}" for order in self.orders) + orders = "; ".join( + f"{order.problem}: {order.state}; {order.progress.line}" for order in self.orders + ) return ( f"{self.unit.name}: orders={orders} " f"review timers={sum(len(order.review_timers_up) for order in self.orders)} " @@ -112,7 +115,7 @@ def read(cls, comms: Comms, commander: str) -> CommanderHud: units.append(CommanderUnit( unit, tuple(CommanderOrder( - todo.id, todo.text, todo.order.checked().id, todo.order.line, + todo.id, todo.text, todo.order.checked().id, todo.order.line, progress[todo.id], tuple(at for due, at in timers if due.id == todo.id), todo.strike_count, edits[todo.id], ) for todo in orders), diff --git a/tests/test_commander_forks.py b/tests/test_commander_forks.py index a0320eee22..e90b4c2f8a 100644 --- a/tests/test_commander_forks.py +++ b/tests/test_commander_forks.py @@ -145,8 +145,10 @@ def test_registered_fork_sends_parent_problem_with_fork_actor(fork_bus, tmp_path store.require_unit_in(db, saved.held().owner_identity, reissuer) print(f'Core send actor: {saved.held().parent}; problem commander: {saved.creator}') ctx = CommanderContext.read_root(comms.root).model_copy(update={'actor': CommanderForkCaller(fork.name)}) - assert ctx.call(CommsHudTool(thread=ctx.commander)) == Commander.read_root(comms.root).call( - CommsHudTool(thread=ctx.commander)) + hud = ctx.call(CommsHudTool(thread=ctx.commander)) + assert hud.commander == parent.incarnation + assert [(unit.unit, tuple(order.problem for order in unit.orders)) + for unit in hud.units] == [(saved.held().owner_identity, ('P40',))] assert set(ctx.model_copy(deep=True).progress()) == {'P40'} assert ctx.held_orders() == (saved,) assert ctx.call(CommsTodoListTool(thread=ctx.commander)).todos == (saved,)