"""Readable runtime values shared by Workflow orchestration modules.""" from __future__ import annotations from dataclasses import dataclass from enum import StrEnum from typing import Any class WorkflowStatus(StrEnum): """The small set of states useful to operators and future debug tooling.""" STARTING = "starting" WAITING_USER = "waiting_user" ROUTING = "routing" RUNNING_AGENT = "running_agent" RUNNING_ACTION = "running_action" HANDOFF = "handoff" ENDED = "ended" class RouteStatus(StrEnum): """A routing error is deliberately different from a valid no-match.""" MATCHED = "matched" NO_MATCH = "no_match" ERROR = "error" class ActionStatus(StrEnum): """Stable Action outcomes used by routing and future debug tooling.""" SUCCESS = "success" FAILURE = "failure" CANCELLED = "cancelled" @dataclass(frozen=True) class ActionError: """Machine-readable failure details without losing the operator message.""" code: str message: str retryable: bool = False @dataclass(frozen=True) class ActionOutcome: """One completed Action invocation. ``result`` remains an in-memory value because a tool response may contain private business data. Trace events publish only its shape and variable names, never the raw response. """ invocation_id: str status: ActionStatus duration_ms: int result: dict[str, Any] | None = None updated_variables: tuple[str, ...] = () error: ActionError | None = None @property def should_route(self) -> bool: """Cancellation is a lifecycle outcome, not a failure branch.""" return self.status != ActionStatus.CANCELLED def trace_payload(self) -> dict[str, Any]: """Return a persistence-safe summary of the execution result.""" payload: dict[str, Any] = { "invocationId": self.invocation_id, "status": self.status.value, "durationMs": self.duration_ms, "updatedVariables": list(self.updated_variables), } if self.result is not None: payload["resultKeys"] = sorted(str(key) for key in self.result) if self.error is not None: payload["error"] = { "code": self.error.code, "message": self.error.message, "retryable": self.error.retryable, } return payload @dataclass(frozen=True) class UserTurn: """One committed user turn that may cross automatic Workflow nodes.""" id: int text: str @dataclass class WorkflowRuntimeState: """Mutable per-call state; graph definitions remain immutable.""" current_node_id: str status: WorkflowStatus = WorkflowStatus.STARTING pending_user_turn: UserTurn | None = None transition_id: int = 0 automatic_hops: int = 0 ended: bool = False _next_turn_id: int = 1 def begin_user_turn(self, text: str) -> UserTurn: turn = UserTurn(id=self._next_turn_id, text=text) self._next_turn_id += 1 self.pending_user_turn = turn self.automatic_hops = 0 return turn def enter(self, node_id: str, status: WorkflowStatus) -> None: self.current_node_id = node_id self.status = status def begin_transition(self) -> int: self.transition_id += 1 return self.transition_id def consume_user_turn(self) -> UserTurn | None: turn = self.pending_user_turn self.pending_user_turn = None return turn def finish(self) -> None: self.ended = True self.status = WorkflowStatus.ENDED self.pending_user_turn = None @dataclass(frozen=True) class LLMRouteResult: """Control-plane result returned by the small routing LLM.""" status: RouteStatus function_name: str | None = None error: str | None = None @dataclass(frozen=True) class EdgeEvaluation: """Final graph-level decision after expression, LLM and default handling.""" status: RouteStatus edge: dict[str, Any] | None = None error: str | None = None