Skip to content

Commit 882cadc

Browse files
committed
🐛 fix(orchestration): surface reflection failures
- Record reflection-driven revisions as last_error and emit tool_result errors. - Synthesize fallback output when only last_error is available. Why: avoid silent incomplete completions when reflection blocks progress. Tests: uv run pytest tests/test_orchestration.py::test_run_action_plan_reflection_blocks_progress tests/test_orchestration.py::test_response_synthesis_helpers -q Dependencies: None
1 parent c9b6769 commit 882cadc

3 files changed

Lines changed: 19 additions & 2 deletions

File tree

packages/meeseeks_core/src/meeseeks_core/action_runner.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def run(self, task_queue: TaskQueue) -> TaskQueue:
103103
continue
104104

105105
if outcome.reflection is not None and outcome.reflection.status != "ok":
106+
reason = outcome.reflection.notes or f"Reflection requested: {outcome.reflection.status}"
107+
self._record_reflection_failure(action_step, reason, task_queue)
108+
self._emit_tool_result(action_step, None, error=reason)
106109
if outcome.reflection.revised_argument:
107110
action_step.action_argument = outcome.reflection.revised_argument
108111
action_step.result = None
@@ -166,7 +169,7 @@ def _ensure_permission(self, action_step: ActionStep) -> bool:
166169
if decision == PermissionDecision.DENY:
167170
mock = get_mock_speaker()
168171
message = (
169-
"Permission denied for " f"{action_step.action_consumer}:{action_step.action_type}."
172+
f"Permission denied for {action_step.action_consumer}:{action_step.action_type}."
170173
)
171174
action_step.result = mock(content=message)
172175
if not decision_logged:
@@ -221,6 +224,14 @@ def _record_failure(self, step: ActionStep, reason: str, task_queue: TaskQueue)
221224
mock = get_mock_speaker()
222225
step.result = mock(content=f"ERROR: {reason}")
223226

227+
def _record_reflection_failure(
228+
self, step: ActionStep, reason: str, task_queue: TaskQueue
229+
) -> None:
230+
note = f"{step.action_consumer} ({step.action_type}) needs revision"
231+
if reason:
232+
note = f"{note}: {reason}"
233+
task_queue.last_error = note
234+
224235
def _emit_tool_result(
225236
self, action_step: ActionStep, result: str | None, *, error: str | None = None
226237
) -> None:

packages/meeseeks_core/src/meeseeks_core/orchestrator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ def _collect_tool_outputs(task_queue: TaskQueue) -> list[str]:
343343
continue
344344
content = getattr(step.result, "content", step.result)
345345
outputs.append(str(content))
346-
return outputs
346+
if outputs or not task_queue.last_error:
347+
return outputs
348+
return [f"ERROR: {task_queue.last_error}"]
347349

348350
@staticmethod
349351
def _should_synthesize_response(task_queue: TaskQueue) -> bool:

tests/test_orchestration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,8 @@ def test_response_synthesis_helpers(monkeypatch):
736736
]
737737
)
738738
assert Orchestrator._collect_tool_outputs(queue) == []
739+
queue.last_error = "tool failed"
740+
assert Orchestrator._collect_tool_outputs(queue) == ["ERROR: tool failed"]
739741
assert Orchestrator._should_synthesize_response(TaskQueue(action_steps=[])) is True
740742

741743
def _fake_model(_inputs):
@@ -1314,6 +1316,8 @@ class DummyReflection:
13141316
model_name="gpt-3.5-turbo",
13151317
)
13161318
assert task_queue.action_steps[0].result is None
1319+
assert task_queue.last_error is not None
1320+
assert "needs revision" in task_queue.last_error
13171321

13181322

13191323
def test_orchestrate_session_auto_compact(monkeypatch, tmp_path):

0 commit comments

Comments
 (0)