From 0b6ecc1750246e4924df2c6a7bdc06233dc196c7 Mon Sep 17 00:00:00 2001 From: Trisha Bansal Date: Tue, 7 Apr 2026 17:44:30 +0000 Subject: [PATCH] Fix perception engine unpack crash and interval rule kwarg mismatch _run_reasoning_loop returned 2-tuples but perceive() unpacked 3, and _check_interval_rules passed an unsupported timepoint= kwarg to IntervalRule.matches(). Both errors were swallowed by broad except handlers, silently degrading every prediction to "early" and preventing interval rules from ever firing. Also adds initial_stage/initial_confidence to PerceptionResult and completes the messages history at early returns so the multishot scaffold is type-clean and continuable. --- gently/app/orchestration/timelapse.py | 1 - gently/harness/perception/engine.py | 43 ++++++++++++++++++++++++--- gently/harness/perception/session.py | 4 +++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/gently/app/orchestration/timelapse.py b/gently/app/orchestration/timelapse.py index 6deaf00..61fd1c5 100644 --- a/gently/app/orchestration/timelapse.py +++ b/gently/app/orchestration/timelapse.py @@ -1007,7 +1007,6 @@ def _check_interval_rules( embryo_id=embryo_id, detector_name=detector_name, stage=stage, - timepoint=estate.timepoints_acquired, ): # Round-based: interval rules now modify the global interval old_interval = self._base_interval_seconds diff --git a/gently/harness/perception/engine.py b/gently/harness/perception/engine.py index b732296..83eed33 100644 --- a/gently/harness/perception/engine.py +++ b/gently/harness/perception/engine.py @@ -533,7 +533,8 @@ async def _run_reasoning_loop( """ Run the interleaved reasoning loop with tool use. - Returns (PerceptionResult, ReasoningTrace) + Returns (PerceptionResult, ReasoningTrace, messages) where messages is the + full conversation history, so callers can continue the conversation. """ trace = ReasoningTrace() @@ -591,8 +592,9 @@ async def _run_reasoning_loop( )) # Parse and return + messages.append({"role": "assistant", "content": [{"type": "text", "text": text_response}]}) result = self._parse_response(text_response) - return result, trace + return result, trace, messages # Handle tool use if response.stop_reason == "tool_use": @@ -620,12 +622,33 @@ async def _run_reasoning_loop( tool_input=block.input, )) + assistant_content = [] + for block in response.content: + if block.type == "text": + assistant_content.append({"type": "text", "text": block.text}) + elif block.type == "tool_use": + assistant_content.append({ + "type": "tool_use", + "id": block.id, + "name": block.name, + "input": block.input, + }) + messages.append({"role": "assistant", "content": assistant_content}) + # Run verification and return result result = await self._handle_verification_request( verification_block.input, trace, ) - return result, trace + messages.append({ + "role": "user", + "content": [{ + "type": "tool_result", + "tool_use_id": verification_block.id, + "content": f"Verification complete: stage={result.stage}, confidence={result.confidence:.0%}", + }], + }) + return result, trace, messages # Build assistant message with the response assistant_content = [] @@ -688,7 +711,7 @@ async def _run_reasoning_loop( # Max iterations reached - parse last response logger.warning(f"Max reasoning iterations ({max_iterations}) reached") - return self._parse_response(""), trace + return self._parse_response(""), trace, messages def _handle_tool_call( self, @@ -1060,6 +1083,18 @@ def _build_cached_system_prompt(self) -> List[Dict]: "cache_control": {"type": "ephemeral", "ttl": "1h"} }] + def _build_reconsider_prompt(self, result: "PerceptionResult", turn: int) -> str: + raise NotImplementedError( + "Multishot reconsideration is not yet implemented. " + "Set multishot_turns=0 (the default) to disable." + ) + + async def _call_claude(self, messages: List[Dict], include_tools: bool = True) -> Any: + raise NotImplementedError( + "Multishot reconsideration is not yet implemented. " + "Set multishot_turns=0 (the default) to disable." + ) + async def _call_claude_with_tools(self, messages: List[Dict]) -> Any: """Call Claude API with tools enabled for interleaved reasoning.""" try: diff --git a/gently/harness/perception/session.py b/gently/harness/perception/session.py index aeabb46..e3fbe24 100644 --- a/gently/harness/perception/session.py +++ b/gently/harness/perception/session.py @@ -634,3 +634,7 @@ class PerceptionResult: candidate_stages: Optional[List[CandidateStage]] = None # Candidates from Phase 1 multi_phase_trace: Optional[MultiPhaseReasoningTrace] = None # Full multi-phase trace phase_count: int = 1 # Number of phases executed (1, 2, or 3) + + # Multishot reconsideration: the first answer before any reconsider turns + initial_stage: Optional[str] = None + initial_confidence: Optional[float] = None