1 file changed
@@ -197,8 +197,11 @@ def record_result(self, stage: str, result: str = "", | |||
| 197 | 197 | if stage == "reflection": | |
| 198 | 198 | self._post_reflection_hook() | |
| 199 | 199 | ||
| 200 | - next_stage = self._get_next_stage(stage, result, score) | ||
| 201 | - self.ws.update_stage(next_stage) | ||
| 200 | + next_stage, new_iteration = self._get_next_stage(stage, result, score) | ||
| 201 | + if new_iteration is not None: | ||
| 202 | + self.ws.update_stage_and_iteration(next_stage, new_iteration) | ||
| 203 | + else: | ||
| 204 | + self.ws.update_stage(next_stage) | ||
| 202 | 205 | ||
| 203 | 206 | if score is not None: | |
| 204 | 207 | self.ws.write_file( | |
@@ -740,8 +743,12 @@ def _parse_quality_gate_params(self) -> tuple[float, float, int]: | |||
| 740 | 743 | return score, threshold, max_iters | |
| 741 | 744 | ||
| 742 | 745 | def _get_next_stage(self, current_stage: str, result: str = "", | |
| 743 | - score: float | None = None) -> str: | ||
| 744 | - """Determine the next stage based on current stage and result.""" | ||
| 746 | + score: float | None = None) -> tuple[str, int | None]: | ||
| 747 | + """Determine the next stage based on current stage and result. | ||
| 748 | + | ||
| 749 | + Returns (next_stage, new_iteration). new_iteration is non-None only | ||
| 750 | + when the quality gate loops back for a new iteration. | ||
| 751 | + """ | ||
| 745 | 752 | # experiment_decision: PIVOT loops back to idea_debate | |
| 746 | 753 | if current_stage == "experiment_decision": | |
| 747 | 754 | decision = self.ws.read_file("supervisor/experiment_analysis.md") | |
@@ -756,7 +763,7 @@ def _get_next_stage(self, current_stage: str, result: str = "", | |||
| 756 | 763 | f"logs/idea_exp_cycle_{cycle + 1}.marker", | |
| 757 | 764 | f"PIVOT at iteration {iteration}", | |
| 758 | 765 | ) | |
| 759 | - return "idea_debate" | ||
| 766 | + return ("idea_debate", None) | ||
| 760 | 767 | else: | |
| 761 | 768 | self.ws.add_error( | |
| 762 | 769 | f"PIVOT requested but cycle limit reached ({cycle}/{self.config.idea_exp_cycles})" | |
@@ -782,15 +789,15 @@ def _get_next_stage(self, current_stage: str, result: str = "", | |||
| 782 | 789 | f"writing/critique/revision_round_{revision_rounds + 1}.marker", | |
| 783 | 790 | f"Revision round {revision_rounds + 1}, score={review_score}", | |
| 784 | 791 | ) | |
| 785 | - return "writing_integrate" | ||
| 792 | + return ("writing_integrate", None) | ||
| 786 | 793 | ||
| 787 | 794 | # init is transient: always advance to literature_search | |
| 788 | 795 | if current_stage == "init": | |
| 789 | - return "literature_search" | ||
| 796 | + return ("literature_search", None) | ||
| 790 | 797 | ||
| 791 | 798 | # lark stages: skip if lark disabled | |
| 792 | 799 | if current_stage == "reflection" and not self.config.lark_enabled: | |
| 793 | - return "quality_gate" | ||
| 800 | + return ("quality_gate", None) | ||
| 794 | 801 | ||
| 795 | 802 | # quality_gate: execute side effects and determine next stage | |
| 796 | 803 | if current_stage == "quality_gate": | |
@@ -804,7 +811,7 @@ def _get_next_stage(self, current_stage: str, result: str = "", | |||
| 804 | 811 | if self.config.evolution_enabled: | |
| 805 | 812 | from sibyl.evolution import EvolutionEngine | |
| 806 | 813 | EvolutionEngine().run_cross_project_evolution() | |
| 807 | - return "done" | ||
| 814 | + return ("done", None) | ||
| 808 | 815 | else: | |
| 809 | 816 | # Tag end of iteration, archive artifacts, advance counter | |
| 810 | 817 | self.ws.git_tag( | |
@@ -817,18 +824,17 @@ def _get_next_stage(self, current_stage: str, result: str = "", | |||
| 817 | 824 | self.ws.add_error(f"Archive failed for iteration {iteration}: {e}") | |
| 818 | 825 | # Clear stale artifacts that would pollute the next iteration | |
| 819 | 826 | self._clear_iteration_artifacts() | |
| 820 | - # Update iteration counter; stage will be set by record_result | ||
| 821 | - self.ws.update_iteration(iteration + 1) | ||
| 822 | - return "literature_search" # start new iteration | ||
| 827 | + # Iteration counter update is atomic with stage in record_result | ||
| 828 | + return ("literature_search", iteration + 1) | ||
| 823 | 829 | ||
| 824 | 830 | try: | |
| 825 | 831 | idx = self.STAGES.index(current_stage) | |
| 826 | 832 | if idx + 1 < len(self.STAGES): | |
| 827 | - return self.STAGES[idx + 1] | ||
| 833 | + return (self.STAGES[idx + 1], None) | ||
| 828 | 834 | except ValueError: | |
| 829 | 835 | self.ws.add_error(f"Unknown stage '{current_stage}', forcing done") | |
| 830 | - return "done" | ||
| 831 | - return current_stage | ||
| 836 | + return ("done", None) | ||
| 837 | + return (current_stage, None) | ||
| 832 | 838 | ||
| 833 | 839 | def _clear_iteration_artifacts(self): | |
| 834 | 840 | """Clear stale working-directory artifacts between iterations. | |
0 commit comments