tenseleyflow/loader / f2c9862

Browse files

Adopt runtime context for response policy helpers

Authored by espadonne
SHA
f2c9862fa68bd6e7c04d3fb01847b5f4ff1c6f86
Parents
825fa06
Tree
b69d0c2

4 changed files

StatusFile+-
M src/loader/runtime/completion_policy.py 8 7
M src/loader/runtime/conversation.py 3 3
M src/loader/runtime/repair.py 8 12
M src/loader/runtime/turn_completion.py 6 5
src/loader/runtime/completion_policy.pymodified
@@ -6,6 +6,7 @@ from collections.abc import Awaitable, Callable
66
 from dataclasses import dataclass
77
 
88
 from ..llm.base import Message, Role
9
+from .context import RuntimeContext
910
 from .events import AgentEvent, TurnSummary
1011
 from .reasoning_types import TaskCompletionCheck
1112
 from .task_completion import detect_premature_completion, get_continuation_prompt
@@ -31,8 +32,8 @@ class ContinuationDecision:
3132
 class CompletionPolicy:
3233
     """Owns loop bailout, non-mutating completion nudges, and response cleanup."""
3334
 
34
-    def __init__(self, agent) -> None:
35
-        self.agent = agent
35
+    def __init__(self, context: RuntimeContext) -> None:
36
+        self.context = context
3637
 
3738
     async def maybe_stop_for_text_loop(
3839
         self,
@@ -43,7 +44,7 @@ class CompletionPolicy:
4344
     ) -> TextLoopDecision:
4445
         """Stop the turn when the assistant starts repeating textually."""
4546
 
46
-        is_text_loop, loop_description = self.agent.safeguards.detect_text_loop(content)
47
+        is_text_loop, loop_description = self.context.safeguards.detect_text_loop(content)
4748
         if not is_text_loop:
4849
             return TextLoopDecision(should_stop=False)
4950
 
@@ -54,7 +55,7 @@ class CompletionPolicy:
5455
         summary.final_response = final_response
5556
         summary.failures.append(loop_description)
5657
         final_message = Message(role=Role.ASSISTANT, content=final_response)
57
-        self.agent.session.append(final_message)
58
+        self.context.session.append(final_message)
5859
         summary.assistant_messages.append(final_message)
5960
         await emit(
6061
             AgentEvent(
@@ -81,7 +82,7 @@ class CompletionPolicy:
8182
     ) -> ContinuationDecision:
8283
         """Nudge non-mutating tasks to continue when completion looks premature."""
8384
 
84
-        cfg = self.agent.config.reasoning
85
+        cfg = self.context.config.reasoning
8586
         if continuation_count >= cfg.max_continuation_prompts:
8687
             return ContinuationDecision(should_continue=False)
8788
 
@@ -110,8 +111,8 @@ class CompletionPolicy:
110111
                 ),
111112
             )
112113
         )
113
-        self.agent.session.append(Message(role=Role.ASSISTANT, content=response_content))
114
-        self.agent.session.append(Message(role=Role.USER, content=continuation_prompt))
114
+        self.context.session.append(Message(role=Role.ASSISTANT, content=response_content))
115
+        self.context.session.append(Message(role=Role.USER, content=continuation_prompt))
115116
         return ContinuationDecision(should_continue=True)
116117
 
117118
     @staticmethod
src/loader/runtime/conversation.pymodified
@@ -69,8 +69,8 @@ class ConversationRuntime:
6969
             append_timeline=self.workflow_state.append_timeline_from_decision,
7070
             append_execute_bridge=self.workflow_state.maybe_append_execute_bridge,
7171
         )
72
-        self.repairer = ResponseRepairer(agent)
73
-        self.completion_policy = CompletionPolicy(agent)
72
+        self.repairer = ResponseRepairer(self.context)
73
+        self.completion_policy = CompletionPolicy(self.context)
7474
         self.phase_tracker = TurnPhaseTracker(self.context, self.tracer)
7575
         self.finalizer = TurnFinalizer(
7676
             agent,
@@ -79,7 +79,7 @@ class ConversationRuntime:
7979
             self.workflow_state.set_workflow_mode,
8080
         )
8181
         self.turn_completion = TurnCompletionController(
82
-            agent,
82
+            self.context,
8383
             repairer=self.repairer,
8484
             completion_policy=self.completion_policy,
8585
             finalizer=self.finalizer,
src/loader/runtime/repair.pymodified
@@ -5,6 +5,7 @@ from __future__ import annotations
55
 from dataclasses import dataclass, field
66
 
77
 from ..llm.base import ToolCall
8
+from .context import RuntimeContext
89
 from .parsing import parse_tool_calls
910
 
1011
 
@@ -37,8 +38,8 @@ class ToolCallAnalysis:
3738
 class ResponseRepairer:
3839
     """Owns response-repair heuristics that used to live inline in the loop."""
3940
 
40
-    def __init__(self, agent) -> None:
41
-        self.agent = agent
41
+    def __init__(self, context: RuntimeContext) -> None:
42
+        self.context = context
4243
 
4344
     def handle_empty_response(
4445
         self,
@@ -85,7 +86,7 @@ class ResponseRepairer:
8586
         normalized_tool_calls = list(tool_calls)
8687
         tool_source = "native"
8788
 
88
-        if self.agent.use_react:
89
+        if self.context.use_react:
8990
             parsed = parse_tool_calls(content)
9091
             normalized_tool_calls = parsed.tool_calls
9192
             normalized_content = parsed.content
@@ -135,16 +136,11 @@ class ResponseRepairer:
135136
         )
136137
 
137138
     def _extract_raw_tool_calls(self, response_content: str) -> list[ToolCall]:
138
-        """Recover raw-text tool calls for either agent or RuntimeContext callers."""
139
+        """Recover raw-text tool calls from the runtime parser and registry."""
139140
 
140
-        extractor = getattr(self.agent, "_extract_raw_json_tool_calls", None)
141
-        if callable(extractor):
142
-            return extractor(response_content)
143
-
144
-        registry = getattr(self.agent, "registry", None)
145
-        allowed_tool_names = None
146
-        if registry is not None:
147
-            allowed_tool_names = [tool.name for tool in registry.list_tools()]
141
+        allowed_tool_names = [
142
+            tool.name for tool in self.context.registry.list_tools()
143
+        ]
148144
         parsed = parse_tool_calls(
149145
             response_content,
150146
             allowed_tool_names=allowed_tool_names,
src/loader/runtime/turn_completion.pymodified
@@ -8,6 +8,7 @@ from enum import StrEnum
88
 
99
 from ..llm.base import Message, Role
1010
 from .completion_policy import CompletionPolicy
11
+from .context import RuntimeContext
1112
 from .dod import DefinitionOfDone
1213
 from .events import AgentEvent, TurnSummary
1314
 from .executor import ToolExecutor
@@ -42,14 +43,14 @@ class TurnCompletionController:
4243
 
4344
     def __init__(
4445
         self,
45
-        agent,
46
+        context: RuntimeContext,
4647
         *,
4748
         repairer: ResponseRepairer,
4849
         completion_policy: CompletionPolicy,
4950
         finalizer: TurnFinalizer,
5051
         phase_tracker: TurnPhaseTracker,
5152
     ) -> None:
52
-        self.agent = agent
53
+        self.context = context
5354
         self.repairer = repairer
5455
         self.completion_policy = completion_policy
5556
         self.finalizer = finalizer
@@ -93,8 +94,8 @@ class TurnCompletionController:
9394
                 finalize_reason_summary="Finalizing after text-loop bailout",
9495
             )
9596
 
96
-        cfg = self.agent.config.reasoning
97
-        self.agent.safeguards.record_response(content)
97
+        cfg = self.context.config.reasoning
98
+        self.context.safeguards.record_response(content)
9899
         if (
99100
             cfg.completion_check
100101
             and not dod.mutating_actions
@@ -122,7 +123,7 @@ class TurnCompletionController:
122123
         )
123124
 
124125
         final_message = Message(role=Role.ASSISTANT, content=response_content)
125
-        self.agent.session.append(final_message)
126
+        self.context.session.append(final_message)
126127
         summary.assistant_messages.append(final_message)
127128
 
128129
         gate_result = await self.finalizer.run_definition_of_done_gate(