Skip to content
Merged
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
132 changes: 87 additions & 45 deletions src/agent_comms/commander/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from agent_comms.errors import RelationViolationError
from agent_comms.field_codec import (
Argument,
DecodedValue,
FieldCodec,
MalformedRecord,
Normalize,
Expand All @@ -54,23 +55,24 @@
from agent_comms.native_tool_call import SelectedToolDenied
from agent_comms.plan_phases import OrderPhase
from agent_comms.problem_number import ProblemNumber
from agent_comms.progress_claims import MergedClaim, WorkingClaim
from agent_comms.recorded_time import RecordedEventTime
from agent_comms.repo import Repo
from agent_comms.standing_rules import HuntDue, OwedAnswer
from agent_comms.storage_rules import UnitStorage
from agent_comms.thread_identity import ThreadIncarnation
from agent_comms.todos import (
BluntText,
CheckedDelivery,
CleanHunt,
CommitDelivery,
DeliveryEvidence,
DeliverySource,
DirectiveText,
FileDeliveryEvidence,
LocatedProblemFinding,
MeasuredReview,
NoDeliveryEvidence,
NoSuccessor,
OperationDelivery,
OrderDelivery,
OrderedHunt,
OrderMessage,
OrderProgress,
Expand Down Expand Up @@ -178,6 +180,61 @@ def forwarders_added(diff, sources=()):
if m.group(2) not in kept and not state_call.search(diff[m.start():m.end() + 200].split('\n+', 2)[1] if '\n+' in diff[m.start():m.end()+200] else '')])


class CommandDeliverySource(DeliverySource, declared_name=None):
@classmethod
def from_text(cls, text: str) -> CommandDeliverySource:
# Reserved arguments derive from declarations; every other token is a Git reference.
return ArgumentDeliverySource.member_or_default(text, ReferenceDeliverySource).from_argument(text)


class ArgumentDeliverySource(CommandDeliverySource, declared_name=None):
@classmethod
def from_argument(cls, text: str) -> Self:
return cls()


@dataclass(frozen=True)
class ReferenceDeliverySource(ArgumentDeliverySource, declared_name=None):
reference: str

@classmethod
def from_argument(cls, text: str) -> Self:
if not text.strip():
raise ValueError("A commit delivery needs a nonempty Git reference.")
return cls(text)


@dataclass(frozen=True)
class NoDeliverySource(CommandDeliverySource):
def require_report(self) -> None:
pass

def delivery(self, repo: Path, evidence: DeliveryEvidence) -> CheckedDelivery:
raise ValueError("A fix delivery needs --sha COMMIT or --sha op.")


@dataclass(frozen=True)
class OpDeliverySource(ArgumentDeliverySource):
def delivery(self, repo: Path, evidence: DeliveryEvidence) -> CheckedDelivery:
return OperationDelivery(evidence=evidence)


@dataclass(frozen=True)
class CommitDeliverySource(ReferenceDeliverySource):
def delivery(self, repo: Path, evidence: DeliveryEvidence) -> CheckedDelivery:
delivered = Repo.git(repo, 'rev-parse', '--verify', f'{self.reference}^{{commit}}').strip()
diff = Repo.git(repo, 'show', '--first-parent', '-m', '--format=', delivered)
changed = re.findall(r'^\+\+\+ b/(\S+\.py)$', diff, re.M)
sources = [Repo.git(repo, 'show', f'{delivered}:{path}') for path in changed]
if added := forwarders_added(diff, sources):
raise ValueError(f'Refused: {delivered} adds forwarders {added[:5]}; '
'slop never counts as delivery. Order the hierarchy fix.')
return CommitDelivery(
commit=Repo.git(repo, 'rev-parse', '--verify', 'HEAD^{commit}').strip(),
delivered_commit=delivered, evidence=evidence,
)


@dataclass(frozen=True)
class OrderFile:
"""CLI context and prose around the same order the native tools accept."""
Expand Down Expand Up @@ -227,12 +284,6 @@ def problems_found(self, clean: bool) -> list[str]:
) # where a mistake was caught; later is worse


def revision(repo: str, ref: str) -> str:
"""Resolve a supplied Git revision once at the command boundary."""
return subprocess.check_output(
['git', '-C', repo, 'rev-parse', '--verify', f'{ref}^{{commit}}'], text=True).strip()


class CommanderContext(CommanderConfig):
"""The selected root's bus and canonical reads, shared by command families."""

Expand Down Expand Up @@ -470,12 +521,14 @@ def send(self, unit, document: OrderFile, clean=False,
validate()
except MalformedRecord as error:
problems.append(f'{label}: {error}; fix: name an existing source location or directory')
# Send and correction ask the check's owner the same question on clean main.
# The new order owns the source against which its check must fail.
try:
with MalformedRecord.refusing('order.check'):
order.require_writable()
if Path(check.directory).is_dir():
check.require_acceptance(self.worktrees)
Repo.git(Path(check.directory), 'fetch', '-q', 'origin', 'main')
base = order.check_base(Path(check.directory))
check.require_acceptance(base, self.worktrees)
except MalformedRecord as error:
problems.append(f'order.check: {error}; fix: supply a focused measurable command')
references = list(dict.fromkeys((
Expand Down Expand Up @@ -558,15 +611,17 @@ def send(self, unit, document: OrderFile, clean=False,
evidence=(order.owner,))

def correct_check(self, pid: str, line: str, why: str) -> None:
"""Replace a wrong check of a pending or claimed order by the rule send uses; Core refuses
any other order or a closed problem (CorrectCheckTodo.apply); done checks the SHA."""
"""Correct the current check against the source preceding this order's delivery."""
todo = self.comms.goals.todos.get(pid)
order = todo.order.checked()
check = self.read_check(line)
if len(why.split()) < 8:
sys.exit('Refused: --why says what was wrong with the recorded check (8+ words)')
check.require_acceptance(self.worktrees)
repo = self.repo_directory(check.directory)
Repo.git(repo, 'fetch', '-q', 'origin', 'main')
check.require_acceptance(order.check_base(repo), self.worktrees)
self.call(CommsOrderCheckTool(problem=pid, check=check, why=why))
print(f'{pid}: order {todo.order.id} now checks `{check.command}`')
print(f'{pid}: order {order.id} now checks `{check.command}`')

def say(self, units: str, body_file: str,
answers: tuple[MessageReference, ...] = ()) -> None:
Expand Down Expand Up @@ -645,23 +700,14 @@ def posted(self, unit: str, todo: Todo, strike: StrikeTodo,
self.on_record(line, 'Strike', VerdictAudience.of(unit, todo.goal), answers)
print(f'{unit}: {n} strike(s) on {todo.id}' + (f'. Reassign {todo.id} now and freeze its branch.' if n >= 2 else ''))

def done(self, pid, sha, screenshot='', seen=''):
"""Core runs the delivery's check at its checked commit and strikes a late delivered commit
in the same revision; this posts the strikes Core recorded."""
def done(self, pid: str, delivery: OrderDelivery) -> None:
"""Storage is checked here; the delivery owns its evidence and claims."""
todo = self.comms.goals.todos.get(pid)
todo.state.require_open(todo)
todo.order.checked().require_delivery(todo.order.id)
order = todo.order.checked()
order.require_delivery(order.id)
unit = todo.held().owner
repo = str(self.repo_directory(todo.order.check.directory))
operation = sha == 'op' # an operation on live state, not a commit: the check runs now and now is delivery
if not operation:
diff = subprocess.run(['git', '-C', repo, 'show', '--first-parent',
'-m', '--format=', sha], capture_output=True, text=True).stdout
changed = re.findall(r'^\+\+\+ b/(\S+\.py)$', diff, re.M)
after = [subprocess.run(['git', '-C', repo, 'show', f'{sha}:{f}'],
capture_output=True, text=True).stdout for f in changed]
if added := forwarders_added(diff, after):
sys.exit(f'Refused: {sha} adds forwarders {added[:5]}; slop never counts as delivery. Order the hierarchy fix.')
claims = delivery.done_claims(order)
thread = self.comms.registry.require(unit)
storage = UnitStorage.read(self.comms.registry.storage_rule, thread)
for checkout in storage.rule.problem_checkouts(unit, pid):
Expand All @@ -672,19 +718,9 @@ def done(self, pid, sha, screenshot='', seen=''):
storage.require_quota()
except SelectedToolDenied as quota:
sys.exit(f'Refused: {unit}: {quota}; storage rule: it cleans up before the order counts as done.')
todo.order.run.require_artifact(seen or screenshot)
evidence = FileDeliveryEvidence(seen or screenshot) if seen or screenshot else NoDeliveryEvidence()
if operation:
delivery = OperationDelivery(evidence=evidence)
claim = WorkingClaim(evidence.artifact())
else:
delivered_commit = revision(repo, sha)
delivery = CommitDelivery(commit=revision(repo, 'HEAD'),
delivered_commit=delivered_commit, evidence=evidence)
claim = MergedClaim(delivery.delivered)
self.call(CommsOrderDoneTool(problem=pid,
order_id=todo.order.id, delivery=delivery, claims=(claim,)))
print(f'{unit}: order {todo.order.id} delivered as {delivery.delivered if not operation else sha}')
order_id=order.id, delivery=delivery, claims=claims))
print(f'{unit}: order {order.id} delivered as {delivery.work}')

def hunt(self, where, why, sent):
"""Record the commander's source read, whether or not it became an order."""
Expand Down Expand Up @@ -964,11 +1000,14 @@ class DoneVerb(OrderVerb):
),
]
sha: Annotated[
str,
CommandDeliverySource,
Normalize(DecodedValue),
Argument(
flags=("--sha",), options={"help": "commit in the recorded checkout, or op"}
flags=("--sha",),
options={"type": CommandDeliverySource.from_text, "choices": None,
"help": "for a fix: commit in the recorded checkout, or op"},
),
]
] = dataclasses.field(default_factory=NoDeliverySource)
screenshot: Annotated[
str,
Argument(
Expand All @@ -984,7 +1023,10 @@ class DoneVerb(OrderVerb):

def run(self, ctx: Commander) -> None:
todo = ctx.comms.goals.todos.resolve_order(ctx.incarnation, self.unit, self.problem)
ctx.done(todo.id, self.sha, self.screenshot, self.seen)
order = todo.order.checked()
delivery = order.scope.delivery(
ctx.repo_directory(order.check.directory), self.sha, self.screenshot, self.seen)
ctx.done(todo.id, delivery)


@dataclass(frozen=True)
Expand Down
9 changes: 4 additions & 5 deletions src/agent_comms/goals.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,14 +362,13 @@ def git_command(words: Sequence[str]) -> str:
return word
return ""

def require_acceptance(self, checkout_base: Path) -> None:
"""A new or corrected check must distinguish origin/main from delivered work."""
def require_acceptance(self, base: str, checkout_base: Path) -> None:
"""The order state supplies the source preceding its delivered work."""
self.require_writable()
CheckedCommit.git(self.directory, "fetch", "-q", "origin", "main")
try:
ran = self.run_at("origin/main", self.directory, checkout_base)
ran = self.run_at(base, self.directory, checkout_base)
except subprocess.TimeoutExpired as error:
raise ValueError("The order check timed out on origin/main.") from error
raise ValueError(f"The order check timed out on {base}.") from error
if refused := self.result.distance(ran).unproven(self.command, 0):
raise ValueError("; ".join(refused))

Expand Down
Loading
Loading