Coverage for little_loops / fsm / types.py: 72%
36 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-16 13:12 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-06-16 13:12 -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 iterations executed
24 terminated_by: Reason for termination (terminal, max_iterations, timeout, signal, error, handoff)
25 duration_ms: Total execution time in milliseconds
26 captured: All captured variable values
27 error: Error message if terminated_by is "error"
28 handoff: True if execution stopped due to handoff signal
29 continuation_prompt: Continuation context from handoff signal
30 """
32 final_state: str
33 iterations: int
34 terminated_by: str # "terminal", "max_iterations", "timeout", "signal", "error", "handoff", "cycle_detected"
35 duration_ms: int
36 captured: dict[str, dict[str, Any]]
37 error: str | None = None
38 handoff: bool = False
39 continuation_prompt: str | None = None
40 messages: list[str] = field(default_factory=list)
42 def to_dict(self) -> dict[str, Any]:
43 """Convert to dictionary for JSON serialization."""
44 result: dict[str, Any] = {
45 "final_state": self.final_state,
46 "iterations": self.iterations,
47 "terminated_by": self.terminated_by,
48 "duration_ms": self.duration_ms,
49 "captured": self.captured,
50 }
51 if self.error is not None:
52 result["error"] = self.error
53 if self.handoff:
54 result["handoff"] = self.handoff
55 if self.continuation_prompt is not None:
56 result["continuation_prompt"] = self.continuation_prompt
57 if self.messages:
58 result["messages"] = self.messages
59 return result
62@dataclass
63class ActionResult:
64 """Result from action execution.
66 Attributes:
67 output: stdout from the action
68 stderr: stderr from the action
69 exit_code: Exit code from the action
70 duration_ms: Execution time in milliseconds
71 usage_events: Token usage events from host-CLI invocations (empty for shell actions)
72 """
74 output: str
75 stderr: str
76 exit_code: int
77 duration_ms: int
78 usage_events: list[TokenUsage] = field(default_factory=list)
81# Type for event callback
82EventCallback = Callable[[dict[str, Any]], None]
84# Type for evaluator functions
85# Parameter order: config, output, exit_code, context — matches evaluate() call signature
86Evaluator = Callable[
87 ["EvaluateConfig", str, int, "InterpolationContext"],
88 "EvaluationResult",
89]