Coverage for little_loops / fsm / types.py: 72%
36 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-26 17:38 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-26 17:38 -0500
1"""FSM result types for loop and action execution."""
3from __future__ import annotations
5from collections.abc import Callable
6from dataclasses import dataclass, field
7from typing import TYPE_CHECKING, Any
9from little_loops.subprocess_utils import TokenUsage
11if TYPE_CHECKING:
12 from little_loops.fsm.evaluators import EvaluationResult
13 from little_loops.fsm.interpolation import InterpolationContext
14 from little_loops.fsm.schema import EvaluateConfig
17@dataclass
18class ExecutionResult:
19 """Result from FSM execution.
21 Attributes:
22 final_state: Name of the state when execution stopped
23 iterations: Total step executions (state enters)
24 terminated_by: Reason for termination. Values: "terminal", "max_steps" (step cap reached;
25 legacy "max_iterations" renamed), "max_iterations_reached" (full-pass cap reached),
26 "timeout", "signal", "error", "handoff", "cycle_detected", "stall_detected".
27 duration_ms: Total execution time in milliseconds
28 captured: All captured variable values
29 error: Error message if terminated_by is "error"
30 handoff: True if execution stopped due to handoff signal
31 continuation_prompt: Continuation context from handoff signal
32 """
34 final_state: str
35 iterations: int
36 terminated_by: str # "terminal", "max_steps", "max_iterations_reached", "timeout", "signal", "error", "handoff", "cycle_detected"
37 duration_ms: int
38 captured: dict[str, dict[str, Any]]
39 error: str | None = None
40 handoff: bool = False
41 continuation_prompt: str | None = None
42 messages: list[str] = field(default_factory=list)
44 def to_dict(self) -> dict[str, Any]:
45 """Convert to dictionary for JSON serialization."""
46 result: dict[str, Any] = {
47 "final_state": self.final_state,
48 "iterations": self.iterations,
49 "terminated_by": self.terminated_by,
50 "duration_ms": self.duration_ms,
51 "captured": self.captured,
52 }
53 if self.error is not None:
54 result["error"] = self.error
55 if self.handoff:
56 result["handoff"] = self.handoff
57 if self.continuation_prompt is not None:
58 result["continuation_prompt"] = self.continuation_prompt
59 if self.messages:
60 result["messages"] = self.messages
61 return result
64@dataclass
65class ActionResult:
66 """Result from action execution.
68 Attributes:
69 output: stdout from the action
70 stderr: stderr from the action
71 exit_code: Exit code from the action
72 duration_ms: Execution time in milliseconds
73 usage_events: Token usage events from host-CLI invocations (empty for shell actions)
74 """
76 output: str
77 stderr: str
78 exit_code: int
79 duration_ms: int
80 usage_events: list[TokenUsage] = field(default_factory=list)
83# Type for event callback
84EventCallback = Callable[[dict[str, Any]], None]
86# Type for evaluator functions
87# Parameter order: config, output, exit_code, context — matches evaluate() call signature
88Evaluator = Callable[
89 ["EvaluateConfig", str, int, "InterpolationContext"],
90 "EvaluationResult",
91]