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
23 changes: 22 additions & 1 deletion src/agent_comms/goal_presentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, fields
from functools import cached_property
from typing import TYPE_CHECKING, ClassVar, Self
Expand Down Expand Up @@ -74,6 +75,18 @@ def execution(self) -> GoalExecution:
class GoalExecution(DeclaredFamily, affix="Execution"):
"""The complete execution family consumed by commands, views and the commander."""

toggle_label: ClassVar[str] = "Pause"

def can_toggle(self) -> bool:
return False

async def apply_toggle(
self,
change: Callable[[type[GoalAction]], Awaitable[None]],
unavailable: Callable[[], None],
) -> None:
unavailable()

def details_heading(self, heading: str) -> str:
return heading

Expand Down Expand Up @@ -138,7 +151,15 @@ def waiting_presentation(self, ordinary: ThreadPresentation) -> ThreadPresentati
class ControlledExecution(BoundGoalExecution, declared_name=None):
"""An execution the owner moves on with one control; the others offer none."""

toggle_label: ClassVar[str] = "Pause"
def can_toggle(self) -> bool:
return True

async def apply_toggle(
self,
change: Callable[[type[GoalAction]], Awaitable[None]],
unavailable: Callable[[], None],
) -> None:
await change(self.toggle)

@property
@abstractmethod
Expand Down
36 changes: 35 additions & 1 deletion tests/test_goal_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from agent_comms.comms import wire
from agent_comms.nominal.field_codec import AcpSessionId, FieldCodec, GoalId
from agent_comms.goal_actions import (
ActiveGoalAction,
GoalAction,
PausedGoalAction,
RetryGoalAction,
SetGoalAction,
Expand All @@ -26,13 +28,19 @@
from agent_comms.goal_history import RecordedGoalHistory
from agent_comms.goal_presentation import (
AttemptFailedExecution,
AttemptUnresolvedExecution,
CompletedExecution,
GoalExecution,
GoalSnapshot,
IdleGoalExecution,
PausedExecution,
RetryableBlockExecution,
RunnableExecution,
RunningExecution,
StandbyExecution,
)
from agent_comms.goal_scope import OneGoal
from agent_comms.goal_states import ActiveGoal
from agent_comms.goal_states import ActiveGoal, ModelPause
from agent_comms.maintenance_barrier import MaintenanceOperator
from agent_comms.owned_turn import OwnedTurn
from agent_comms.threads import Thread
Expand Down Expand Up @@ -68,6 +76,32 @@ def test_execution_identity_survives_wire_and_change_detection(execution_bus, ex
assert current.changed_by(GoalSnapshot(goal, execution)) == (current.execution != execution)


@pytest.mark.asyncio
@pytest.mark.parametrize("execution, expected_actions", (
(IdleGoalExecution(), ()),
(CompletedExecution(GoalId.mint()), ()),
(AttemptUnresolvedExecution(GoalId.mint()), ()),
(RunnableExecution(GoalId.mint()), (PausedGoalAction,)),
(RunningExecution(GoalId.mint()), (PausedGoalAction,)),
(StandbyExecution(GoalId.mint(), (), ()), (PausedGoalAction,)),
(PausedExecution(GoalId.mint(), ModelPause()), (ActiveGoalAction,)),
(RetryableBlockExecution(GoalId.mint(), "source fixture"), (RetryGoalAction,)),
))
async def test_execution_owns_toggle_dispatch_and_unavailability(
execution: GoalExecution, expected_actions: tuple[type[GoalAction], ...],
) -> None:
actions: list[type[GoalAction]] = []
unavailable: list[None] = []

async def change(action: type[GoalAction]) -> None:
actions.append(action)

await execution.apply_toggle(change, lambda: unavailable.append(None))
assert tuple(actions) == expected_actions
assert execution.can_toggle() == bool(expected_actions)
assert len(unavailable) == int(not expected_actions)


@asynccontextmanager
async def turn_on(comms, *, original_owner_input=False):
agent = CommsAgent(comms, auto_wake=False)
Expand Down
Loading