162 lines
6.0 KiB
Python
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
|