Adopt runtime context for response policy helpers
- SHA
f2c9862fa68bd6e7c04d3fb01847b5f4ff1c6f86- Parents
-
825fa06 - Tree
b69d0c2
f2c9862
f2c9862fa68bd6e7c04d3fb01847b5f4ff1c6f86825fa06
b69d0c2| Status | File | + | - |
|---|---|---|---|
| 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 | ||
| 6 | 6 | from dataclasses import dataclass |
| 7 | 7 | |
| 8 | 8 | from ..llm.base import Message, Role |
| 9 | +from .context import RuntimeContext | |
| 9 | 10 | from .events import AgentEvent, TurnSummary |
| 10 | 11 | from .reasoning_types import TaskCompletionCheck |
| 11 | 12 | from .task_completion import detect_premature_completion, get_continuation_prompt |
@@ -31,8 +32,8 @@ class ContinuationDecision: | ||
| 31 | 32 | class CompletionPolicy: |
| 32 | 33 | """Owns loop bailout, non-mutating completion nudges, and response cleanup.""" |
| 33 | 34 | |
| 34 | - def __init__(self, agent) -> None: | |
| 35 | - self.agent = agent | |
| 35 | + def __init__(self, context: RuntimeContext) -> None: | |
| 36 | + self.context = context | |
| 36 | 37 | |
| 37 | 38 | async def maybe_stop_for_text_loop( |
| 38 | 39 | self, |
@@ -43,7 +44,7 @@ class CompletionPolicy: | ||
| 43 | 44 | ) -> TextLoopDecision: |
| 44 | 45 | """Stop the turn when the assistant starts repeating textually.""" |
| 45 | 46 | |
| 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) | |
| 47 | 48 | if not is_text_loop: |
| 48 | 49 | return TextLoopDecision(should_stop=False) |
| 49 | 50 | |
@@ -54,7 +55,7 @@ class CompletionPolicy: | ||
| 54 | 55 | summary.final_response = final_response |
| 55 | 56 | summary.failures.append(loop_description) |
| 56 | 57 | final_message = Message(role=Role.ASSISTANT, content=final_response) |
| 57 | - self.agent.session.append(final_message) | |
| 58 | + self.context.session.append(final_message) | |
| 58 | 59 | summary.assistant_messages.append(final_message) |
| 59 | 60 | await emit( |
| 60 | 61 | AgentEvent( |
@@ -81,7 +82,7 @@ class CompletionPolicy: | ||
| 81 | 82 | ) -> ContinuationDecision: |
| 82 | 83 | """Nudge non-mutating tasks to continue when completion looks premature.""" |
| 83 | 84 | |
| 84 | - cfg = self.agent.config.reasoning | |
| 85 | + cfg = self.context.config.reasoning | |
| 85 | 86 | if continuation_count >= cfg.max_continuation_prompts: |
| 86 | 87 | return ContinuationDecision(should_continue=False) |
| 87 | 88 | |
@@ -110,8 +111,8 @@ class CompletionPolicy: | ||
| 110 | 111 | ), |
| 111 | 112 | ) |
| 112 | 113 | ) |
| 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)) | |
| 115 | 116 | return ContinuationDecision(should_continue=True) |
| 116 | 117 | |
| 117 | 118 | @staticmethod |
src/loader/runtime/conversation.pymodified@@ -69,8 +69,8 @@ class ConversationRuntime: | ||
| 69 | 69 | append_timeline=self.workflow_state.append_timeline_from_decision, |
| 70 | 70 | append_execute_bridge=self.workflow_state.maybe_append_execute_bridge, |
| 71 | 71 | ) |
| 72 | - self.repairer = ResponseRepairer(agent) | |
| 73 | - self.completion_policy = CompletionPolicy(agent) | |
| 72 | + self.repairer = ResponseRepairer(self.context) | |
| 73 | + self.completion_policy = CompletionPolicy(self.context) | |
| 74 | 74 | self.phase_tracker = TurnPhaseTracker(self.context, self.tracer) |
| 75 | 75 | self.finalizer = TurnFinalizer( |
| 76 | 76 | agent, |
@@ -79,7 +79,7 @@ class ConversationRuntime: | ||
| 79 | 79 | self.workflow_state.set_workflow_mode, |
| 80 | 80 | ) |
| 81 | 81 | self.turn_completion = TurnCompletionController( |
| 82 | - agent, | |
| 82 | + self.context, | |
| 83 | 83 | repairer=self.repairer, |
| 84 | 84 | completion_policy=self.completion_policy, |
| 85 | 85 | finalizer=self.finalizer, |
src/loader/runtime/repair.pymodified@@ -5,6 +5,7 @@ from __future__ import annotations | ||
| 5 | 5 | from dataclasses import dataclass, field |
| 6 | 6 | |
| 7 | 7 | from ..llm.base import ToolCall |
| 8 | +from .context import RuntimeContext | |
| 8 | 9 | from .parsing import parse_tool_calls |
| 9 | 10 | |
| 10 | 11 | |
@@ -37,8 +38,8 @@ class ToolCallAnalysis: | ||
| 37 | 38 | class ResponseRepairer: |
| 38 | 39 | """Owns response-repair heuristics that used to live inline in the loop.""" |
| 39 | 40 | |
| 40 | - def __init__(self, agent) -> None: | |
| 41 | - self.agent = agent | |
| 41 | + def __init__(self, context: RuntimeContext) -> None: | |
| 42 | + self.context = context | |
| 42 | 43 | |
| 43 | 44 | def handle_empty_response( |
| 44 | 45 | self, |
@@ -85,7 +86,7 @@ class ResponseRepairer: | ||
| 85 | 86 | normalized_tool_calls = list(tool_calls) |
| 86 | 87 | tool_source = "native" |
| 87 | 88 | |
| 88 | - if self.agent.use_react: | |
| 89 | + if self.context.use_react: | |
| 89 | 90 | parsed = parse_tool_calls(content) |
| 90 | 91 | normalized_tool_calls = parsed.tool_calls |
| 91 | 92 | normalized_content = parsed.content |
@@ -135,16 +136,11 @@ class ResponseRepairer: | ||
| 135 | 136 | ) |
| 136 | 137 | |
| 137 | 138 | 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.""" | |
| 139 | 140 | |
| 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 | + ] | |
| 148 | 144 | parsed = parse_tool_calls( |
| 149 | 145 | response_content, |
| 150 | 146 | allowed_tool_names=allowed_tool_names, |
src/loader/runtime/turn_completion.pymodified@@ -8,6 +8,7 @@ from enum import StrEnum | ||
| 8 | 8 | |
| 9 | 9 | from ..llm.base import Message, Role |
| 10 | 10 | from .completion_policy import CompletionPolicy |
| 11 | +from .context import RuntimeContext | |
| 11 | 12 | from .dod import DefinitionOfDone |
| 12 | 13 | from .events import AgentEvent, TurnSummary |
| 13 | 14 | from .executor import ToolExecutor |
@@ -42,14 +43,14 @@ class TurnCompletionController: | ||
| 42 | 43 | |
| 43 | 44 | def __init__( |
| 44 | 45 | self, |
| 45 | - agent, | |
| 46 | + context: RuntimeContext, | |
| 46 | 47 | *, |
| 47 | 48 | repairer: ResponseRepairer, |
| 48 | 49 | completion_policy: CompletionPolicy, |
| 49 | 50 | finalizer: TurnFinalizer, |
| 50 | 51 | phase_tracker: TurnPhaseTracker, |
| 51 | 52 | ) -> None: |
| 52 | - self.agent = agent | |
| 53 | + self.context = context | |
| 53 | 54 | self.repairer = repairer |
| 54 | 55 | self.completion_policy = completion_policy |
| 55 | 56 | self.finalizer = finalizer |
@@ -93,8 +94,8 @@ class TurnCompletionController: | ||
| 93 | 94 | finalize_reason_summary="Finalizing after text-loop bailout", |
| 94 | 95 | ) |
| 95 | 96 | |
| 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) | |
| 98 | 99 | if ( |
| 99 | 100 | cfg.completion_check |
| 100 | 101 | and not dod.mutating_actions |
@@ -122,7 +123,7 @@ class TurnCompletionController: | ||
| 122 | 123 | ) |
| 123 | 124 | |
| 124 | 125 | final_message = Message(role=Role.ASSISTANT, content=response_content) |
| 125 | - self.agent.session.append(final_message) | |
| 126 | + self.context.session.append(final_message) | |
| 126 | 127 | summary.assistant_messages.append(final_message) |
| 127 | 128 | |
| 128 | 129 | gate_result = await self.finalizer.run_definition_of_done_gate( |