Python · 8473 bytes Raw Blame History
1 """Typed turn engine for Loader runtime execution."""
2
3 from __future__ import annotations
4
5 from collections.abc import Awaitable, Callable
6 from typing import Any
7
8 from .artifact_invalidation import ArtifactInvalidationAssessor
9 from .assistant_turns import AssistantTurnRequester
10 from .bootstrap import (
11 RuntimeBootstrapSource,
12 RuntimeBootstrapView,
13 build_runtime_bootstrap_source,
14 build_runtime_context,
15 sync_runtime_context,
16 )
17 from .completion_policy import CompletionPolicy
18 from .dod import DefinitionOfDoneStore
19 from .events import AgentEvent, TurnSummary
20 from .executor import ToolExecutor
21 from .finalization import TurnFinalizer
22 from .logging import reset_runtime_logger
23 from .phases import TurnPhase, TurnPhaseTracker, TurnTransitionKind
24 from .repair import ResponseRepairer
25 from .response_routing import AssistantResponseRouter
26 from .tool_batches import ToolBatchRunner
27 from .tracing import RuntimeTracer
28 from .turn_completion import TurnCompletionController
29 from .turn_iteration import TurnIterationController
30 from .turn_loop import TurnLoopController
31 from .turn_preamble import TurnPreludeController
32 from .turn_preparation import TurnPreparationController
33 from .workflow import (
34 WorkflowArtifactStore,
35 WorkflowPolicy,
36 WorkflowSignalExtractor,
37 )
38 from .workflow_lanes import WorkflowLaneRunner
39 from .workflow_recovery import WorkflowRecoveryController
40 from .workflow_state import WorkflowStateController
41
42 EventSink = Callable[[AgentEvent], Awaitable[None]]
43 ConfirmationHandler = (
44 Callable[[str, str, str, dict[str, Any] | None], Awaitable[bool]] | None
45 )
46 UserQuestionHandler = Callable[[str, list[str] | None], Awaitable[str]] | None
47
48
49 class ConversationRuntime:
50 """Runs one explicit conversation turn against the current session."""
51
52 def __init__(self, source: RuntimeBootstrapSource) -> None:
53 self.source: RuntimeBootstrapView = build_runtime_bootstrap_source(source)
54 self.context = build_runtime_context(self.source)
55 self.tracer = RuntimeTracer()
56 self.executor: ToolExecutor | None = None
57 self.dod_store = DefinitionOfDoneStore(self.context.project_root)
58 self.workflow_signals = WorkflowSignalExtractor()
59 self.workflow_policy = WorkflowPolicy(self.workflow_signals)
60 self.artifact_invalidation = ArtifactInvalidationAssessor()
61 self.artifact_store = WorkflowArtifactStore(self.context.project_root)
62 self.workflow_state = WorkflowStateController(
63 self.context,
64 dod_store=self.dod_store,
65 )
66 self.workflow_lanes = WorkflowLaneRunner(
67 self.context,
68 artifact_store=self.artifact_store,
69 dod_store=self.dod_store,
70 workflow_policy=self.workflow_policy,
71 )
72 self.workflow_recovery = WorkflowRecoveryController(
73 self.context,
74 artifact_invalidation=self.artifact_invalidation,
75 workflow_policy=self.workflow_policy,
76 workflow_signals=self.workflow_signals,
77 workflow_lanes=self.workflow_lanes,
78 set_workflow_mode=self.workflow_state.set_workflow_mode,
79 append_timeline=self.workflow_state.append_timeline_from_decision,
80 append_execute_bridge=self.workflow_state.maybe_append_execute_bridge,
81 )
82 self.repairer = ResponseRepairer(self.context)
83 self.completion_policy = CompletionPolicy(self.context)
84 self.phase_tracker = TurnPhaseTracker(self.context, self.tracer)
85 self.finalizer = TurnFinalizer(
86 self.context,
87 self.tracer,
88 self.dod_store,
89 self.workflow_state.set_workflow_mode,
90 )
91 self.turn_completion = TurnCompletionController(
92 self.context,
93 repairer=self.repairer,
94 completion_policy=self.completion_policy,
95 finalizer=self.finalizer,
96 phase_tracker=self.phase_tracker,
97 )
98 self.response_router = AssistantResponseRouter(
99 self.context,
100 tracer=self.tracer,
101 phase_tracker=self.phase_tracker,
102 tool_batches=ToolBatchRunner(self.context, self.dod_store),
103 turn_completion=self.turn_completion,
104 )
105 self.turn_iteration = TurnIterationController(
106 self.context,
107 phase_tracker=self.phase_tracker,
108 turn_requester=AssistantTurnRequester(self.context, self.tracer),
109 repairer=self.repairer,
110 response_router=self.response_router,
111 )
112 self.turn_preparation = TurnPreparationController(
113 self.context,
114 tracer=self.tracer,
115 phase_tracker=self.phase_tracker,
116 dod_store=self.dod_store,
117 workflow_policy=self.workflow_policy,
118 workflow_signals=self.workflow_signals,
119 workflow_lanes=self.workflow_lanes,
120 finalizer=self.finalizer,
121 set_workflow_mode=self.workflow_state.set_workflow_mode,
122 append_timeline=self.workflow_state.append_timeline_from_decision,
123 append_execute_bridge=self.workflow_state.maybe_append_execute_bridge,
124 )
125 self.turn_preamble = TurnPreludeController(
126 self.context,
127 tracer=self.tracer,
128 workflow_recovery=self.workflow_recovery,
129 )
130 self.turn_loop = TurnLoopController(
131 self.context,
132 turn_preamble=self.turn_preamble,
133 turn_iteration=self.turn_iteration,
134 )
135
136 async def run_turn(
137 self,
138 task: str,
139 emit: EventSink,
140 on_confirmation: ConfirmationHandler = None,
141 on_user_question: UserQuestionHandler = None,
142 requested_mode: str | None = None,
143 original_task: str | None = None,
144 ) -> TurnSummary:
145 """Run one task turn and return a structured summary."""
146
147 reset_runtime_logger()
148 self.context.safeguards.reset_response_history()
149
150 prepared_turn = await self.turn_preparation.prepare(
151 task=task,
152 emit=emit,
153 requested_mode=requested_mode,
154 original_task=original_task,
155 on_user_question=on_user_question,
156 )
157 sync_runtime_context(self.context, self.source)
158 self.executor = prepared_turn.executor
159 summary = prepared_turn.summary
160 dod = prepared_turn.definition_of_done
161 task = prepared_turn.task
162 effective_task = prepared_turn.effective_task
163 effective_max_tokens = prepared_turn.effective_max_tokens
164 rollback_plan = prepared_turn.rollback_plan
165
166 assert self.executor is not None
167 loop_exit = await self.turn_loop.run_loop(
168 task=task,
169 effective_task=effective_task,
170 original_task=original_task,
171 effective_max_tokens=effective_max_tokens,
172 dod=dod,
173 emit=emit,
174 summary=summary,
175 executor=self.executor,
176 rollback_plan=rollback_plan,
177 on_confirmation=on_confirmation,
178 on_user_question=on_user_question,
179 emit_confirmation=self._emit_confirmation(emit),
180 )
181 return await self._finalize_turn(
182 summary,
183 emit,
184 reason_code=loop_exit.reason_code,
185 reason_summary=loop_exit.reason_summary,
186 )
187
188 async def _finalize_turn(
189 self,
190 summary: TurnSummary,
191 emit: EventSink,
192 *,
193 reason_code: str,
194 reason_summary: str,
195 ) -> TurnSummary:
196 await self.phase_tracker.enter(
197 TurnPhase.FINALIZE,
198 emit,
199 detail=reason_summary,
200 reason_code=reason_code,
201 kind=TurnTransitionKind.TERMINAL,
202 )
203 final_summary = self.finalizer.finalize_summary(summary)
204 self.phase_tracker.clear()
205 return final_summary
206
207 @staticmethod
208 def _emit_confirmation(emit: EventSink):
209 async def _emit(
210 tool_name: str,
211 message: str,
212 details: str,
213 preview: dict[str, Any] | None,
214 ) -> None:
215 await emit(
216 AgentEvent(
217 type="confirmation",
218 tool_name=tool_name,
219 confirm_message=message,
220 confirm_details=details,
221 confirm_preview=preview,
222 )
223 )
224
225 return _emit