From 548b7c9e4503b73296d249767cd107ca200c6d93 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 24 Feb 2026 17:12:39 -0500 Subject: [PATCH 1/5] issue/planning-agents-turn-budgets: Right-size turn budgets to 30 for planning agents Replace DEFAULT_AGENT_MAX_TURNS (150) with hardcoded 30 in all 4 planning agent functions (run_product_manager, run_architect, run_tech_lead, run_sprint_planner). Preserves import statement for potential future use. Reduces token usage and latency for planning phase. --- swe_af/reasoners/pipeline.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swe_af/reasoners/pipeline.py b/swe_af/reasoners/pipeline.py index 001cf14..18b8170 100644 --- a/swe_af/reasoners/pipeline.py +++ b/swe_af/reasoners/pipeline.py @@ -166,7 +166,7 @@ async def run_product_manager( artifacts_dir: str = ".artifacts", additional_context: str = "", model: str = "sonnet", - max_turns: int = DEFAULT_AGENT_MAX_TURNS, + max_turns: int = 30, permission_mode: str = "", ai_provider: str = "claude", ) -> dict: @@ -212,7 +212,7 @@ async def run_architect( artifacts_dir: str = ".artifacts", feedback: str = "", model: str = "sonnet", - max_turns: int = DEFAULT_AGENT_MAX_TURNS, + max_turns: int = 30, permission_mode: str = "", ai_provider: str = "claude", ) -> dict: @@ -260,7 +260,7 @@ async def run_tech_lead( artifacts_dir: str = ".artifacts", revision_number: int = 0, model: str = "sonnet", - max_turns: int = DEFAULT_AGENT_MAX_TURNS, + max_turns: int = 30, permission_mode: str = "", ai_provider: str = "claude", ) -> dict: @@ -310,7 +310,7 @@ async def run_sprint_planner( repo_path: str, artifacts_dir: str = ".artifacts", model: str = "sonnet", - max_turns: int = DEFAULT_AGENT_MAX_TURNS, + max_turns: int = 30, permission_mode: str = "", ai_provider: str = "claude", ) -> dict: From 4dcff37d788812645779c5ca9127d146f4fd751c Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 24 Feb 2026 17:13:14 -0500 Subject: [PATCH 2/5] issue/haiku-model-defaults: Set haiku model defaults for utility roles Added haiku model overrides for retry_advisor_model, git_model, and merger_model in the claude_code runtime configuration. These low-complexity utility roles benefit from haiku's speed advantage while downstream verification protects correctness. --- swe_af/execution/schemas.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swe_af/execution/schemas.py b/swe_af/execution/schemas.py index f8653b4..2903a1d 100644 --- a/swe_af/execution/schemas.py +++ b/swe_af/execution/schemas.py @@ -374,6 +374,9 @@ class QASynthesisResult(BaseModel): "claude_code": { **{field: "sonnet" for field in ALL_MODEL_FIELDS}, "qa_synthesizer_model": "haiku", + "retry_advisor_model": "haiku", + "git_model": "haiku", + "merger_model": "haiku", }, "open_code": { **{field: "minimax/minimax-m2.5" for field in ALL_MODEL_FIELDS}, From 5711ee63480a3f6ab221fc6f0ac5ed5686442d40 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 24 Feb 2026 17:15:37 -0500 Subject: [PATCH 3/5] issue/execution-agents-turn-budgets: Replace DEFAULT_AGENT_MAX_TURNS with role-specific turn budgets Replaced generic DEFAULT_AGENT_MAX_TURNS with explicit turn budgets tailored to each agent's complexity: - run_coder: 50 turns (full implementation work) - run_issue_advisor, run_replanner, run_verifier, run_integration_tester, generate_fix_issues: 30 turns (advisory/analysis roles) - run_retry_advisor, run_issue_writer, run_qa, run_code_reviewer: 20 turns (review/synthesis roles) - run_git_init, run_workspace_setup, run_merger, run_workspace_cleanup, run_qa_synthesizer, run_repo_finalize, run_github_pr: 10 turns (git/workspace operations) All 17 agents now use explicit turn budgets. DEFAULT_AGENT_MAX_TURNS import preserved for schema defaults. --- swe_af/reasoners/execution_agents.py | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/swe_af/reasoners/execution_agents.py b/swe_af/reasoners/execution_agents.py index d0f57ab..4086998 100644 --- a/swe_af/reasoners/execution_agents.py +++ b/swe_af/reasoners/execution_agents.py @@ -133,7 +133,7 @@ async def run_retry_advisor( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=20, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -215,7 +215,7 @@ async def run_issue_advisor( model=model, provider=ai_provider, cwd=cwd, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=30, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -293,7 +293,7 @@ async def run_replanner( model=replan_model, provider=ai_provider, cwd=state.repo_path or ".", - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=30, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -407,7 +407,7 @@ class IssueWriterOutput(BaseModel): model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=20, allowed_tools=[Tool.READ, Tool.WRITE, Tool.GLOB, Tool.GREP], permission_mode=permission_mode or None, )) @@ -472,7 +472,7 @@ async def run_verifier( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=30, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -558,7 +558,7 @@ async def run_git_init( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH], permission_mode=permission_mode or None, )) @@ -636,7 +636,7 @@ class WorkspaceSetupResult(BaseModel): model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH], permission_mode=permission_mode or None, )) @@ -703,7 +703,7 @@ async def run_merger( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH, Tool.READ, Tool.GLOB, Tool.GREP], permission_mode=permission_mode or None, )) @@ -777,7 +777,7 @@ async def run_integration_tester( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=30, allowed_tools=[Tool.BASH, Tool.READ, Tool.WRITE, Tool.GLOB, Tool.GREP], permission_mode=permission_mode or None, )) @@ -848,7 +848,7 @@ class WorkspaceCleanupResult(BaseModel): model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH], permission_mode=permission_mode or None, )) @@ -922,7 +922,7 @@ async def run_coder( model=model, provider=ai_provider, cwd=worktree_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=50, allowed_tools=[ Tool.READ, Tool.WRITE, Tool.EDIT, Tool.BASH, Tool.GLOB, Tool.GREP, @@ -999,7 +999,7 @@ async def run_qa( model=model, provider=ai_provider, cwd=worktree_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=20, allowed_tools=[ Tool.READ, Tool.WRITE, Tool.EDIT, Tool.BASH, Tool.GLOB, Tool.GREP, @@ -1079,7 +1079,7 @@ async def run_code_reviewer( model=model, provider=ai_provider, cwd=worktree_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=20, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -1155,7 +1155,7 @@ async def run_qa_synthesizer( model=model, provider=ai_provider, cwd=worktree_path or ".", - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[], permission_mode=permission_mode or None, )) @@ -1251,7 +1251,7 @@ class FixGeneratorOutput(BaseModel): model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=30, allowed_tools=[Tool.READ, Tool.GLOB, Tool.GREP, Tool.BASH], permission_mode=permission_mode or None, )) @@ -1320,7 +1320,7 @@ async def run_repo_finalize( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH, Tool.READ, Tool.GLOB, Tool.GREP], permission_mode=permission_mode or None, )) @@ -1396,7 +1396,7 @@ async def run_github_pr( model=model, provider=ai_provider, cwd=repo_path, - max_turns=DEFAULT_AGENT_MAX_TURNS, + max_turns=10, allowed_tools=[Tool.BASH], permission_mode=permission_mode or None, )) From dbdcbd4cab3beb70ecc4d5a95c384253b0773273 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 24 Feb 2026 17:16:51 -0500 Subject: [PATCH 4/5] issue/fast-path-logging: Add fast-path exit logging to coding_loop.py Add explicit log message when first-iteration coding succeeds to make the fast-path behavior observable. This adds a 'FAST-PATH EXIT' message with a 'fast_path' tag when iteration==1 approval occurs, before the existing 'Coding loop APPROVED' message. The fast-path (first-iteration approval) is the most common happy path and a key performance indicator. This change makes it easily queryable in logs. --- swe_af/execution/coding_loop.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/swe_af/execution/coding_loop.py b/swe_af/execution/coding_loop.py index 009b9c8..7bd9704 100644 --- a/swe_af/execution/coding_loop.py +++ b/swe_af/execution/coding_loop.py @@ -699,6 +699,12 @@ async def run_coding_loop( # --- 4. BRANCH ON ACTION --- if action == "approve": + if iteration == 1: + if note_fn: + note_fn( + f"FAST-PATH EXIT: {issue_name} approved on first iteration", + tags=["coding_loop", "fast_path", "complete", issue_name], + ) if note_fn: note_fn( f"Coding loop APPROVED: {issue_name} after {iteration} iteration(s)", From a959127154636175f27f93153a222dc5c2a30b30 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 24 Feb 2026 17:17:59 -0500 Subject: [PATCH 5/5] issue/haiku-model-defaults: Fix test to include new haiku model fields Updated test_claude_code_defaults to correctly verify that retry_advisor_model, git_model, and merger_model are set to 'haiku' in addition to qa_synthesizer_model, while all other fields remain 'sonnet'. --- tests/test_model_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_model_config.py b/tests/test_model_config.py index 9f01798..5222f06 100644 --- a/tests/test_model_config.py +++ b/tests/test_model_config.py @@ -16,11 +16,12 @@ class TestResolveRuntimeModels(unittest.TestCase): def test_claude_code_defaults(self) -> None: resolved = resolve_runtime_models(runtime="claude_code", models=None) + haiku_fields = {"qa_synthesizer_model", "retry_advisor_model", "git_model", "merger_model"} for field in ALL_MODEL_FIELDS: - if field == "qa_synthesizer_model": - continue - self.assertEqual(resolved[field], "sonnet") - self.assertEqual(resolved["qa_synthesizer_model"], "haiku") + if field in haiku_fields: + self.assertEqual(resolved[field], "haiku") + else: + self.assertEqual(resolved[field], "sonnet") def test_open_code_defaults(self) -> None: resolved = resolve_runtime_models(runtime="open_code", models=None)