55import uuid
66from datetime import UTC
77
8- from models import AttachmentConfig , TaskConfig , TaskType
8+ from models import AttachmentConfig , TaskConfig
99from shell import log
1010
1111AGENT_WORKSPACE = os .environ .get ("AGENT_WORKSPACE" , "/workspace" )
1212
13- # Task types that operate on an existing pull request.
14- PR_TASK_TYPES = frozenset (("pr_iteration" , "pr_review" ))
13+ # The platform default workflow id used when a payload omits resolved_workflow
14+ # (local/batch runs). Mirrors the create-task boundary's coding default.
15+ DEFAULT_WORKFLOW_ID = "coding/new-task-v1"
16+ # The repo-less platform default workflow (#248 Phase 3) — the one first-party
17+ # id whose ``requires_repo`` is false. Used by the load-failure fallback to
18+ # decide repo-optionality without loading the file.
19+ REPO_LESS_DEFAULT_WORKFLOW_ID = "default/agent-v1"
20+ # First-party workflow ids that operate on an existing pull request.
21+ PR_WORKFLOW_IDS = frozenset (("coding/pr-iteration-v1" , "coding/pr-review-v1" ))
22+ # First-party workflow ids that are writeable (NOT read-only). Used only by the
23+ # load-failure fallback to bias an unrecognised id toward read-only (fail closed
24+ # on the write-deny invariant). pr-review-v1 is intentionally excluded (it is
25+ # read-only); default/agent-v1 is excluded because its conservative posture
26+ # should fail closed too.
27+ _KNOWN_WRITEABLE_WORKFLOW_IDS = frozenset (("coding/new-task-v1" , "coding/pr-iteration-v1" ))
1528
1629
1730def resolve_github_token () -> str :
@@ -314,7 +327,7 @@ def _refresh(current: dict) -> dict | None:
314327
315328
316329def build_config (
317- repo_url : str ,
330+ repo_url : str = "" ,
318331 task_description : str = "" ,
319332 issue_number : str = "" ,
320333 github_token : str = "" ,
@@ -325,7 +338,7 @@ def build_config(
325338 dry_run : bool = False ,
326339 task_id : str = "" ,
327340 system_prompt_overrides : str = "" ,
328- task_type : str = "new_task" ,
341+ resolved_workflow : dict | None = None ,
329342 branch_name : str = "" ,
330343 pr_number : str = "" ,
331344 channel_source : str = "" ,
@@ -351,22 +364,59 @@ def build_config(
351364 "ANTHROPIC_MODEL" , "us.anthropic.claude-sonnet-4-6"
352365 )
353366
367+ # Resolve the workflow id (the create-task boundary already pinned it; local
368+ # batch runs default to the coding workflow). Required-input validation is
369+ # owned by the create-task boundary now; the agent re-checks only the
370+ # pr_number/issue/description shape needed to run.
371+ workflow = resolved_workflow or {"id" : DEFAULT_WORKFLOW_ID , "version" : "1.0.0" }
372+ workflow_id = workflow .get ("id" , DEFAULT_WORKFLOW_ID )
373+ is_pr_workflow = workflow_id in PR_WORKFLOW_IDS
374+
375+ # Load the workflow up-front: it drives the Cedar principal, the read_only
376+ # flag, AND whether a repo is required (#248 Phase 3). Fall back to id-based
377+ # mapping when the file can't be loaded (e.g. a registry-only id in a future
378+ # phase) — a repo-less default is the safe assumption only for non-coding.
379+ from workflow import WorkflowValidationError , load_workflow , policy_principal_for
380+
381+ try :
382+ workflow_obj = load_workflow (workflow_id )
383+ policy_principal = policy_principal_for (workflow_obj )
384+ workflow_read_only = workflow_obj .read_only
385+ workflow_requires_repo = workflow_obj .resolved_requires_repo
386+ workflow_allowed_tools = list (workflow_obj .agent_config .allowed_tools )
387+ except WorkflowValidationError as exc :
388+ # The pinned workflow file failed to load (corrupt YAML, schema drift, a
389+ # future registry-only id). This is the one place read_only/requires_repo
390+ # can be wrong without a loud failure, so: (1) log it, and (2) fail
391+ # *closed* — assume read-only (deny writes) for any id we don't recognise
392+ # as a known writeable coding workflow, rather than fail-open to writeable.
393+ log ("ERROR" , f"workflow { workflow_id !r} failed to load ({ exc } ); using fallback policy" )
394+ policy_principal = "pr_review" if workflow_id == "coding/pr-review-v1" else "new_task"
395+ # Known writeable coding workflows are the only ids that fall back to
396+ # writeable; everything else (incl. an unrecognised id) is read-only.
397+ workflow_read_only = workflow_id not in _KNOWN_WRITEABLE_WORKFLOW_IDS
398+ # requires_repo: the repo-less platform default is the only id that does
399+ # NOT require a repo; every other id (coding or unknown) requires one.
400+ workflow_requires_repo = workflow_id != REPO_LESS_DEFAULT_WORKFLOW_ID
401+ # Tool surface is unknown without the file; empty = the runner falls back
402+ # to its built-in full surface. read_only (above, fail-closed) still drops
403+ # Write/Edit, so the write-deny invariant holds even on this path.
404+ workflow_allowed_tools = []
405+
354406 errors = []
355- if not resolved_repo_url :
356- errors .append ("repo_url is required (e.g., 'owner/repo')" )
357- if not resolved_github_token :
358- errors .append ("github_token is required" )
407+ # Repo + GitHub token are required only for repo-bound workflows; a repo-less
408+ # workflow (requires_repo:false) runs from task_description/attachments alone.
409+ if workflow_requires_repo :
410+ if not resolved_repo_url :
411+ errors .append ("repo_url is required (e.g., 'owner/repo')" )
412+ if not resolved_github_token :
413+ errors .append ("github_token is required" )
359414 if not resolved_aws_region :
360415 errors .append ("aws_region is required for Bedrock" )
361- try :
362- task = TaskType (task_type )
363- except ValueError :
364- errors .append (f"Invalid task_type: '{ task_type } '" )
365- task = None
366- if task and task .is_pr_task :
416+ if is_pr_workflow :
367417 if not pr_number :
368- errors .append ("pr_number is required for pr_iteration/pr_review task type " )
369- elif task and not resolved_issue_number and not resolved_task_description :
418+ errors .append (f "pr_number is required for the { workflow_id !r } workflow " )
419+ elif not resolved_issue_number and not resolved_task_description :
370420 errors .append ("Either issue_number or task_description is required" )
371421
372422 if errors :
@@ -394,7 +444,12 @@ def build_config(
394444 max_turns = max_turns ,
395445 max_budget_usd = max_budget_usd ,
396446 system_prompt_overrides = system_prompt_overrides ,
397- task_type = task_type ,
447+ resolved_workflow = workflow ,
448+ policy_principal = policy_principal ,
449+ read_only = workflow_read_only ,
450+ allowed_tools = workflow_allowed_tools ,
451+ requires_repo = workflow_requires_repo ,
452+ is_pr_workflow = is_pr_workflow ,
398453 branch_name = branch_name ,
399454 pr_number = pr_number ,
400455 task_id = task_id or uuid .uuid4 ().hex [:12 ],
0 commit comments