Skip to content
Open
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
13 changes: 11 additions & 2 deletions loopx/capabilities/periodic_report/cadence_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import shlex

from ...agent_registry import registered_agent_ids_for_goal
from ...control_plane.goals.activation import goal_is_stopped
from ...control_plane.capability_hooks import (
TURN_START_HOOK_RESULT_SCHEMA_VERSION, TurnStartHookRegistration, dispatch_turn_start_hooks,
)
Expand All @@ -25,7 +26,11 @@ def periodic_report_cadence_hooks(
if not agent_id:
return ()
goal = find_registry_goal(load_registry(registry_path), goal_id)
if not isinstance(goal, dict) or goal.get("status") in {"stopped", "paused", "archived"}:
if (
not isinstance(goal, dict)
or goal_is_stopped(goal)
or goal.get("status") in {"stopped", "paused", "archived"}
):
return ()
subscription = resolve_goal_periodic_report_subscription(
goal, read_periodic_report_machine_defaults(runtime_root))
Expand All @@ -50,7 +55,11 @@ def produce():
def resolve_current_subscription():
nonlocal current_reporters
current_goal = find_registry_goal(load_registry(registry_path), goal_id)
if not isinstance(current_goal, dict) or current_goal.get("status") in {"stopped", "paused", "archived"}:
if (
not isinstance(current_goal, dict)
or goal_is_stopped(current_goal)
or current_goal.get("status") in {"stopped", "paused", "archived"}
):
return None
current_reporters = registered_agent_ids_for_goal(current_goal)
if not current_reporters or agent_id not in current_reporters:
Expand Down
7 changes: 6 additions & 1 deletion loopx/capabilities/periodic_report/pending_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
POST_WRITEBACK_HOOK_RECEIPT_SCHEMA_VERSION,
InteractionProjectionHookRegistration,
)
from ...control_plane.goals.activation import goal_is_stopped
from ...control_plane.effect_runtime import effect_runtime_result
from ...control_plane.digest_envelope import sha256_envelope
from ...history import load_registry
Expand Down Expand Up @@ -231,7 +232,11 @@ def _active_delivery_subscription(

registry = load_registry(registry_path)
goal = find_registry_goal(registry, goal_id)
if not isinstance(goal, Mapping) or goal.get("status") in {"stopped", "paused", "archived"}:
if (
not isinstance(goal, Mapping)
or goal_is_stopped(goal)
or goal.get("status") in {"stopped", "paused", "archived"}
):
return None
subscription = resolve_goal_periodic_report_subscription(
goal,
Expand Down
26 changes: 26 additions & 0 deletions tests/capabilities/test_periodic_report_pending_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,32 @@ def test_inactive_goal_does_not_reactivate_an_admitted_report(tmp_path, status):
assert read_cadence_journal(runtime_root=runtime, goal_id=GOAL_ID) == original


def test_canonically_stopped_goal_does_not_admit_periodic_report_window(tmp_path):
from datetime import datetime
from loopx.capabilities.periodic_report.cadence_journal import read_cadence_journal
from loopx.capabilities.periodic_report.cadence_runtime import extend_cadence_turn_start_dispatch
from loopx.capabilities.periodic_report.pending_intent import _active_delivery_subscription

registry, runtime = _calendar_fixture(tmp_path)
config = json.loads(registry.read_text())
config["goals"][0]["activation"] = {
"schema_version": "loopx_goal_activation_v1", "state": "stopped"
}
registry.write_text(json.dumps(config))

kwargs = dict(registry_path=registry, runtime_root=runtime, goal_id=GOAL_ID)
dispatch = extend_cadence_turn_start_dispatch(
{}, **kwargs, agent_id=AGENT_ID,
now=datetime.fromisoformat("2026-09-11T10:00:00+00:00"),
)

assert (
dispatch["invoked_count"],
read_cadence_journal(runtime_root=runtime, goal_id=GOAL_ID),
_active_delivery_subscription(**kwargs),
) == (0, None, None)


def test_calendar_does_not_preempt_existing_stage_work(tmp_path):
from datetime import datetime
from loopx.capabilities.periodic_report.cadence_runtime import extend_cadence_turn_start_dispatch
Expand Down
Loading