diff --git a/app-instance/backend/beaver/skills/learning/service.py b/app-instance/backend/beaver/skills/learning/service.py index 3fdf7e2..83d80c6 100644 --- a/app-instance/backend/beaver/skills/learning/service.py +++ b/app-instance/backend/beaver/skills/learning/service.py @@ -462,7 +462,15 @@ class SkillLearningService: @staticmethod def _representative_task_text(runs: list[RunRecord], *, fallback: str = "") -> str: - ordered = sorted(runs, key=lambda item: (item.attempt_index, item.started_at, item.run_id)) + ordered = sorted( + runs, + key=lambda item: ( + item.attempt_index is None, + item.attempt_index if item.attempt_index is not None else 0, + item.started_at, + item.run_id, + ), + ) for record in ordered: text = record.task_text.strip() if text: diff --git a/app-instance/backend/tests/unit/test_phase5_skills_runtime.py b/app-instance/backend/tests/unit/test_phase5_skills_runtime.py index 4d45e66..22c1041 100644 --- a/app-instance/backend/tests/unit/test_phase5_skills_runtime.py +++ b/app-instance/backend/tests/unit/test_phase5_skills_runtime.py @@ -591,6 +591,51 @@ def test_skill_learning_service_uses_original_task_text_for_new_skill_theme(tmp_ assert candidates[0].evidence["task_text"] == "Compare direct production restart with staging rollout" +def test_skill_learning_service_handles_team_runs_without_attempt_index(tmp_path: Path) -> None: + store = SkillSpecStore(tmp_path) + run_store = RunMemoryStore(tmp_path / "memory" / "runs") + learning_store = SkillLearningStore(tmp_path / "memory" / "skills") + service = SkillLearningService( + run_store=run_store, + learning_store=learning_store, + draft_service=DraftService(store), + evidence_selector=EvidenceSelector(run_store), + ) + now = datetime.now(timezone.utc).isoformat() + run_store.append_run_record( + RunRecord( + run_id="team-run", + session_id="session-task:team:research", + task_id="task-1", + attempt_index=None, + task_text="Research one product", + started_at=now, + ended_at=now, + success=True, + finish_reason="stop", + ) + ) + run_store.append_run_record( + RunRecord( + run_id="main-run", + session_id="session-task", + task_id="task-1", + attempt_index=1, + task_text="Compare two products and email the report", + started_at=now, + ended_at=now, + success=True, + finish_reason="stop", + feedback={"acceptance_type": "accept"}, + ) + ) + + candidates = service.build_learning_candidates_for_task("task-1", final_accepted_run_id="main-run") + + assert [candidate.candidate_id for candidate in candidates] == ["new:task:task-1"] + assert candidates[0].evidence["task_text"] == "Compare two products and email the report" + + def test_task_theme_uses_first_sentence_for_chinese_text() -> None: assert ( SkillLearningService._task_theme(