Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions src/agent_comms/commander/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion src/agent_comms/commander/rules/tests-and-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
15 changes: 12 additions & 3 deletions src/agent_comms/commander/vibe_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, "
Expand All @@ -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}"),)
Expand Down
143 changes: 143 additions & 0 deletions src/agent_comms/commander_hud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""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
progress: OrderProgress
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}; {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)} "
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, 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),
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),
)


2 changes: 1 addition & 1 deletion src/agent_comms/skills/comms-commander/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
44 changes: 22 additions & 22 deletions src/agent_comms/todos.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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:
Expand Down Expand Up @@ -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)} }} }}')
Expand All @@ -640,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]


Expand Down Expand Up @@ -1562,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.")

Expand Down Expand Up @@ -1843,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("|", "&#124;")
return f"{self.owner.to_wire()} | {owner} | {self.id} | {todo.review_at.clock()} | {check}"

@property
def phase(self) -> type[OrderPhase]:
return self.serves
Expand Down Expand Up @@ -3845,9 +3839,15 @@ def timers_up_for_commander(self, commander: ThreadIncarnation, registry: Regist
"""Held work whose review timer is up, with its round; merged work waits on install."""
held = self.held_for_commander(commander)
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], 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",
Expand Down
13 changes: 13 additions & 0 deletions src/agent_comms/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
)
from .cli_result import ToolCatalogResult
from .commander.vibe_check import OverloadDegradation
from .commander_hud import CommanderHud
from .coordination_tables.refusals import SendRefusal
from .declared_family import DeclaredFamily
from .display_order import ChannelSort, ThreadSort
Expand Down Expand Up @@ -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"
Expand Down
7 changes: 5 additions & 2 deletions tests/test_commander_forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -145,7 +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.board_text() == Commander.read_root(comms.root).board_text()
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,)
Expand Down
Loading