Files
OpenOPC/opc/layer2_organization/turn_mode.py
T

162 lines
6.0 KiB
Python

"""Stateless turn-mode classifier for role-instance dispatch.
A role's session can be called into action for several different kinds
of turn on the SAME work item. ``infer_turn_mode`` looks at the work
item's state (phase + metadata) and the queue entry type to return
one of five canonical modes. The prompt / context assembly branches
on this value so each mode gets the right context block injected.
EXECUTE — do the work yourself (leaf role, no children)
DELEGATE — break into subtasks (manager role, no children yet)
REVIEW — evaluate a subordinate's deliverable and emit a verdict
INTEGRATE — parent resumes after all children APPROVED; produce the
rolled-up deliverable for upstream review
REWORK — reviewer rejected your prior turn; address the feedback
REPORT — worker DONE; resume the same session under a dedicated
prompt to produce a structured handoff for the reviewer
The classifier is pure: given the same work item + queue entry kind,
it always returns the same mode. It does **not** load the store; all
state must be present on the work item (phase + metadata). This is a
deliberate tradeoff so the mode can be recomputed cheaply at any
point in the dispatcher or context-assembly path.
"""
from __future__ import annotations
from enum import Enum
from typing import Any, Mapping
from opc.core.models import Phase
MANAGER_DISPATCH_TURN_METADATA_KEYS: tuple[str, ...] = (
"manager_board_mutation_performed",
"manager_board_modified_work_item_ids",
"manager_board_deleted_work_item_ids",
"manager_no_delegation_justification",
"no_delegation_justification",
"manager_dispatch_guard_unresolved",
)
def reset_manager_dispatch_turn_metadata(metadata: Mapping[str, Any] | None) -> dict[str, Any]:
"""Return mutable metadata with prior manager-turn outcomes removed.
These keys describe what happened in one agent turn. They must not be
carried into a retry, rework turn, or user follow-up; durable board state
(dependencies and child mutation revisions) is intentionally untouched.
"""
result = dict(metadata or {})
for key in MANAGER_DISPATCH_TURN_METADATA_KEYS:
result.pop(key, None)
return result
class TurnMode(str, Enum):
EXECUTE = "execute"
DELEGATE = "delegate"
REVIEW = "review"
INTEGRATE = "integrate"
REWORK = "rework"
REPORT = "report"
def _as_phase(value: Any) -> Phase | None:
if isinstance(value, Phase):
return value
if isinstance(value, str):
try:
return Phase(value.strip().lower())
except Exception:
return None
return None
def _as_mapping(value: Any) -> dict[str, Any]:
if isinstance(value, Mapping):
return dict(value)
return {}
def infer_turn_mode(
work_item: Any,
*,
is_review_entry: bool = False,
) -> TurnMode:
"""Classify the turn the agent is about to run.
``work_item`` is the DelegationWorkItem (or any object exposing
the same ``phase`` / ``kind`` / ``metadata`` attributes).
``is_review_entry`` should be True when the dispatcher popped a
``review-work-item::`` queue entry — those are always reviews,
even if the underlying work_item metadata is ambiguous.
"""
metadata = _as_mapping(getattr(work_item, "metadata", None))
kind = str(getattr(work_item, "kind", "") or "").strip().lower()
phase = _as_phase(getattr(work_item, "phase", None))
# Priority 0: report turn. The hidden auxiliary card spawned after
# a worker DONE so the same session can produce a structured
# handoff before the reviewer is invoked. Detected purely from the
# work item's metadata flag or kind.
if (
bool(metadata.get("report_execution_work_item", False))
or kind == "report"
):
return TurnMode.REPORT
# Priority 1: review turn. Either the queue entry tag says so,
# the work item is explicitly marked as the hidden review card,
# or kind == "review".
if (
is_review_entry
or bool(metadata.get("review_execution_work_item", False))
or kind == "review"
):
return TurnMode.REVIEW
# Priority 2: rework. Phase READY_FOR_REWORK is the canonical
# signal, but the dispatcher flips the work item to RUNNING
# before the prompt is built — by the time the agent runs we
# may only see RUNNING. Fall back to the metadata trail the
# reviewer leaves: ``rework_feedback`` is set on rejection and
# cleared on approval, and ``review_rework_count`` increments
# on each rejection. Either signal means "the previous turn
# was rejected", so render this as REWORK.
if phase == Phase.READY_FOR_REWORK:
return TurnMode.REWORK
rework_feedback = str(metadata.get("rework_feedback", "") or "").strip()
rework_count = int(metadata.get("review_rework_count", 0) or 0)
if rework_feedback or rework_count > 0:
return TurnMode.REWORK
# Priority 3: integrate. The parent has dependency_work_item_ids
# (it delegated previously) AND is currently runnable (RUNNING /
# READY). That can only mean children have completed and the
# parent is being dispatched for its integration turn. The
# metadata.frontier == "resumed" flag is also set by the wake
# edge when present.
dependency_ids = [
str(x).strip()
for x in list(metadata.get("dependency_work_item_ids", []) or [])
if str(x).strip()
]
frontier = str(metadata.get("frontier", "") or "").strip().lower()
if dependency_ids and (
phase in {Phase.RUNNING, Phase.READY} or frontier == "resumed"
):
return TurnMode.INTEGRATE
# Priority 4: delegate. Manager role with nothing spawned yet.
allowed_delegate_role_ids = [
str(x).strip()
for x in list(metadata.get("allowed_delegate_role_ids", []) or [])
if str(x).strip()
]
if allowed_delegate_role_ids and not dependency_ids:
return TurnMode.DELEGATE
# Default: worker executing their own work item.
return TurnMode.EXECUTE