3131import memory as agent_memory
3232import task_state
3333from observability import task_span
34+ from prompts import get_system_prompt
3435from system_prompt import SYSTEM_PROMPT
3536
3637# ---------------------------------------------------------------------------
@@ -77,6 +78,9 @@ def build_config(
7778 dry_run : bool = False ,
7879 task_id : str = "" ,
7980 system_prompt_overrides : str = "" ,
81+ task_type : str = "new_task" ,
82+ branch_name : str = "" ,
83+ pr_number : str = "" ,
8084) -> dict :
8185 """Build and validate configuration from explicit parameters.
8286
@@ -94,6 +98,9 @@ def build_config(
9498 "max_turns" : max_turns ,
9599 "max_budget_usd" : max_budget_usd ,
96100 "system_prompt_overrides" : system_prompt_overrides ,
101+ "task_type" : task_type ,
102+ "branch_name" : branch_name ,
103+ "pr_number" : pr_number ,
97104 }
98105
99106 errors = []
@@ -103,7 +110,10 @@ def build_config(
103110 errors .append ("github_token is required" )
104111 if not config ["aws_region" ]:
105112 errors .append ("aws_region is required for Bedrock" )
106- if not config ["issue_number" ] and not config ["task_description" ]:
113+ if config ["task_type" ] == "pr_iteration" :
114+ if not config ["pr_number" ]:
115+ errors .append ("pr_number is required for pr_iteration task type" )
116+ elif not config ["issue_number" ] and not config ["task_description" ]:
107117 errors .append ("Either issue_number or task_description is required" )
108118
109119 if errors :
@@ -303,15 +313,19 @@ def setup_repo(config: dict) -> dict:
303313 repo_dir = f"{ AGENT_WORKSPACE } /{ config ['task_id' ]} "
304314 setup : dict [str , str | list [str ] | bool ] = {"repo_dir" : repo_dir , "notes" : []}
305315
306- # Derive branch slug from issue title or task description
307- title = ""
308- if config .get ("issue" ):
309- title = config ["issue" ]["title" ]
310- if not title :
311- title = config ["task_description" ]
312- slug = slugify (title )
313- branch = f"bgagent/{ config ['task_id' ]} /{ slug } "
314- setup ["branch" ] = branch
316+ if config .get ("task_type" ) == "pr_iteration" and config .get ("branch_name" ):
317+ branch = config ["branch_name" ]
318+ setup ["branch" ] = branch
319+ else :
320+ # Derive branch slug from issue title or task description
321+ title = ""
322+ if config .get ("issue" ):
323+ title = config ["issue" ]["title" ]
324+ if not title :
325+ title = config ["task_description" ]
326+ slug = slugify (title )
327+ branch = f"bgagent/{ config ['task_id' ]} /{ slug } "
328+ setup ["branch" ] = branch
315329
316330 # Mark the repo directory as safe for git. On persistent session storage
317331 # the mount may be owned by a different UID than the container user,
@@ -343,9 +357,22 @@ def setup_repo(config: dict) -> dict:
343357 cwd = repo_dir ,
344358 )
345359
346- # Create branch
347- log ("SETUP" , f"Creating branch: { branch } " )
348- run_cmd (["git" , "checkout" , "-b" , branch ], label = "create-branch" , cwd = repo_dir )
360+ # Branch setup
361+ if config .get ("task_type" ) == "pr_iteration" and config .get ("branch_name" ):
362+ log ("SETUP" , f"Checking out existing PR branch: { branch } " )
363+ run_cmd (
364+ ["git" , "fetch" , "origin" , branch ],
365+ label = "fetch-pr-branch" ,
366+ cwd = repo_dir ,
367+ )
368+ run_cmd (
369+ ["git" , "checkout" , "-b" , branch , f"origin/{ branch } " ],
370+ label = "checkout-pr-branch" ,
371+ cwd = repo_dir ,
372+ )
373+ else :
374+ log ("SETUP" , f"Creating branch: { branch } " )
375+ run_cmd (["git" , "checkout" , "-b" , branch ], label = "create-branch" , cwd = repo_dir )
349376
350377 # Trust mise config files in the cloned repo (required before mise install)
351378 run_cmd (
@@ -402,7 +429,11 @@ def setup_repo(config: dict) -> dict:
402429 setup ["lint_before" ] = True
403430
404431 # Detect default branch
405- setup ["default_branch" ] = detect_default_branch (config ["repo_url" ], repo_dir )
432+ # For PR iteration: use base_branch from orchestrator if available
433+ if config .get ("task_type" ) == "pr_iteration" and config .get ("base_branch" ):
434+ setup ["default_branch" ] = config ["base_branch" ]
435+ else :
436+ setup ["default_branch" ] = detect_default_branch (config ["repo_url" ], repo_dir )
406437
407438 # Install prepare-commit-msg hook for code attribution
408439 _install_commit_hook (repo_dir )
@@ -628,6 +659,37 @@ def ensure_pr(
628659 branch = setup ["branch" ]
629660 default_branch = setup .get ("default_branch" , "main" )
630661
662+ # PR iteration: skip PR creation — just push and return existing PR URL
663+ if config .get ("task_type" ) == "pr_iteration" :
664+ if not ensure_pushed (repo_dir , branch ):
665+ log ("WARN" , "Failed to push commits before resolving PR URL" )
666+ log ("POST" , "PR iteration — returning existing PR URL" )
667+ result = subprocess .run (
668+ [
669+ "gh" ,
670+ "pr" ,
671+ "view" ,
672+ branch ,
673+ "--repo" ,
674+ config ["repo_url" ],
675+ "--json" ,
676+ "url" ,
677+ "-q" ,
678+ ".url" ,
679+ ],
680+ cwd = repo_dir ,
681+ capture_output = True ,
682+ text = True ,
683+ timeout = 60 ,
684+ )
685+ if result .returncode == 0 and result .stdout .strip ():
686+ pr_url = result .stdout .strip ()
687+ log ("POST" , f"Existing PR: { pr_url } " )
688+ return pr_url
689+ stderr_msg = result .stderr .strip () if result .stderr else "(no stderr)"
690+ log ("WARN" , f"Could not resolve existing PR URL (rc={ result .returncode } ): { stderr_msg } " )
691+ return None
692+
631693 # Check if the agent already created a PR for this branch
632694 log ("POST" , "Checking for existing PR..." )
633695 result = subprocess .run (
@@ -1482,7 +1544,13 @@ def _build_system_prompt(
14821544 overrides : str ,
14831545) -> str :
14841546 """Assemble the system prompt with task-specific values and memory context."""
1485- system_prompt = SYSTEM_PROMPT .replace ("{repo_url}" , config ["repo_url" ])
1547+ task_type = config .get ("task_type" , "new_task" )
1548+ try :
1549+ system_prompt = get_system_prompt (task_type )
1550+ except ValueError :
1551+ log ("ERROR" , f"Unknown task_type { task_type !r} — falling back to default system prompt" )
1552+ system_prompt = SYSTEM_PROMPT
1553+ system_prompt = system_prompt .replace ("{repo_url}" , config ["repo_url" ])
14861554 system_prompt = system_prompt .replace ("{task_id}" , config ["task_id" ])
14871555 system_prompt = system_prompt .replace ("{workspace}" , AGENT_WORKSPACE )
14881556 system_prompt = system_prompt .replace ("{branch_name}" , setup ["branch" ])
@@ -1513,6 +1581,14 @@ def _build_system_prompt(
15131581 memory_context_text = "\n " .join (mc_parts )
15141582 system_prompt = system_prompt .replace ("{memory_context}" , memory_context_text )
15151583
1584+ # Substitute PR-specific placeholders
1585+ pr_number_val = config .get ("pr_number" , "" )
1586+ if pr_number_val :
1587+ system_prompt = system_prompt .replace ("{pr_number}" , str (pr_number_val ))
1588+ elif "{pr_number}" in system_prompt :
1589+ log ("WARN" , "System prompt contains {pr_number} placeholder but no pr_number in config" )
1590+ system_prompt = system_prompt .replace ("{pr_number}" , "(unknown)" )
1591+
15161592 # Append Blueprint system_prompt_overrides after all placeholder
15171593 # substitutions (avoids double-substitution if overrides contain
15181594 # template placeholders like {repo_url}).
@@ -1628,6 +1704,9 @@ def run_task(
16281704 system_prompt_overrides : str = "" ,
16291705 prompt_version : str = "" ,
16301706 memory_id : str = "" ,
1707+ task_type : str = "new_task" ,
1708+ branch_name : str = "" ,
1709+ pr_number : str = "" ,
16311710) -> dict :
16321711 """Run the full agent pipeline and return a result dict.
16331712
@@ -1652,6 +1731,9 @@ def run_task(
16521731 aws_region = aws_region ,
16531732 task_id = task_id ,
16541733 system_prompt_overrides = system_prompt_overrides ,
1734+ task_type = task_type ,
1735+ branch_name = branch_name ,
1736+ pr_number = pr_number ,
16551737 )
16561738
16571739 log ("TASK" , f"Task ID: { config ['task_id' ]} " )
@@ -1678,6 +1760,8 @@ def run_task(
16781760 prompt = hydrated_context ["user_prompt" ]
16791761 if hydrated_context .get ("issue" ):
16801762 config ["issue" ] = hydrated_context ["issue" ]
1763+ if hydrated_context .get ("resolved_base_branch" ):
1764+ config ["base_branch" ] = hydrated_context ["resolved_base_branch" ]
16811765 if hydrated_context .get ("truncated" ):
16821766 log ("WARN" , "Context was truncated by orchestrator token budget" )
16831767 else :
0 commit comments