Skip to content

Agent / Orchestrator #3

Agent / Orchestrator

Agent / Orchestrator #3

name: Agent / Orchestrator
on:
workflow_dispatch:
inputs:
source_action:
description: "Agent action that handed back to the orchestrator"
required: true
source_conclusion:
description: "Normalized source action conclusion"
required: true
source_recommended_next_step:
description: "Optional source action recommended next step"
required: false
default: ""
source_run_id:
description: "Workflow run ID of the source action, used for handoff dedupe"
required: false
default: ""
target_number:
description: "Issue or pull request number currently being handled"
required: true
target_kind:
description: "Issue or pull request kind currently being handled"
required: false
default: ""
author_association:
description: "Original requester author association from router context"
required: false
default: ""
access_policy:
description: "Route access policy JSON used by router authorization"
required: false
default: ""
repository_private:
description: "Repository visibility flag for access policy defaults"
required: false
default: ""
next_target_number:
description: "Optional next target number produced by the source action"
required: false
default: ""
source_handoff_context:
description: "Optional action-oriented context derived by the source action"
required: false
default: ""
requested_by:
description: "GitHub login that requested the original run"
required: false
request_text:
description: "Original user request text forwarded from the source action"
required: false
session_bundle_mode:
description: "Session bundle persistence mode"
required: false
default: ""
base_branch:
description: "Branch to pass to agent-implement when dispatching an implementation"
required: false
default: ""
base_pr:
description: "Open PR number whose head branch agent-implement should stack on"
required: false
default: ""
automation_mode:
description: "Post-action orchestration mode (disabled, heuristics, agent)"
required: false
default: "disabled"
automation_current_round:
description: "Current automation handoff round"
required: false
default: "1"
automation_max_rounds:
description: "Maximum automation handoff rounds"
required: false
default: "12"
permissions:
actions: write
contents: read
issues: write
pull-requests: read
id-token: write # required for GitHub Actions OIDC broker exchange
concurrency:
group: agent-orchestrator-${{ github.repository }}-${{ inputs.target_number }}-${{ inputs.source_action }}-${{ inputs.automation_current_round }}
cancel-in-progress: false
jobs:
orchestrate:
if: vars.AGENT_ENABLED != 'false'
runs-on: ${{ fromJson(vars.AGENT_RUNS_ON || '["ubuntu-latest"]') }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
token: ${{ github.token }}
- name: Resolve GitHub auth
id: auth
uses: ./.github/actions/resolve-github-auth
with:
app_id: ${{ secrets.AGENT_APP_ID }}
app_private_key: ${{ secrets.AGENT_APP_PRIVATE_KEY }}
pat: ${{ secrets.AGENT_PAT }}
fallback_token: ${{ github.token }}
- name: Setup agent runtime
uses: ./.github/actions/setup-agent-runtime
- name: Check handoff preflight
id: preflight
env:
AUTOMATION_CURRENT_ROUND: ${{ inputs.automation_current_round }}
AUTOMATION_MAX_ROUNDS: ${{ inputs.automation_max_rounds }}
AUTOMATION_MODE: ${{ inputs.automation_mode }}
SOURCE_ACTION: ${{ inputs.source_action }}
SOURCE_CONCLUSION: ${{ inputs.source_conclusion }}
TARGET_KIND: ${{ inputs.target_kind || (inputs.source_action == 'implement' && 'issue' || 'pull_request') }}
AGENT_ALLOW_SELF_APPROVE: ${{ vars.AGENT_ALLOW_SELF_APPROVE || 'false' }}
AGENT_ALLOW_SELF_MERGE: ${{ vars.AGENT_ALLOW_SELF_MERGE || 'false' }}
AUTHOR_ASSOCIATION: ${{ inputs.author_association }}
ACCESS_POLICY: ${{ inputs.access_policy }}
REPOSITORY_PRIVATE: ${{ inputs.repository_private || (github.event.repository.private && 'true' || 'false') }}
run: node .agent/dist/cli/orchestrator-preflight.js
- name: Resolve orchestrator provider
id: provider
if: ${{ steps.preflight.outputs.planner_enabled == 'true' }}
uses: ./.github/actions/resolve-agent-provider
with:
route: orchestrator
default_provider: ${{ vars.AGENT_DEFAULT_PROVIDER || 'auto' }}
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model_policy: ${{ vars.AGENT_MODEL_POLICY || '' }}
- name: Install orchestrator provider
if: ${{ steps.preflight.outputs.planner_enabled == 'true' }}
uses: ./.github/actions/setup-agent-runtime
with:
install_codex: ${{ steps.provider.outputs.install_codex }}
install_claude: ${{ steps.provider.outputs.install_claude }}
- name: Resolve task timeout
if: ${{ steps.preflight.outputs.planner_enabled == 'true' }}
id: task_timeout
env:
AGENT_TASK_TIMEOUT_POLICY: ${{ vars.AGENT_TASK_TIMEOUT_POLICY || '' }}
ROUTE: orchestrator
run: node .agent/dist/cli/resolve-task-timeout.js
- name: Plan next action with agent
id: planner
if: ${{ steps.preflight.outputs.planner_enabled == 'true' }}
continue-on-error: true
timeout-minutes: ${{ fromJson(steps.task_timeout.outputs.minutes || '30') }}
uses: ./.github/actions/run-agent-task
env:
ORCHESTRATOR_SOURCE_ACTION: ${{ inputs.source_action }}
ORCHESTRATOR_SOURCE_CONCLUSION: ${{ inputs.source_conclusion }}
ORCHESTRATOR_SOURCE_RECOMMENDED_NEXT_STEP: ${{ inputs.source_recommended_next_step }}
ORCHESTRATOR_SOURCE_RUN_ID: ${{ inputs.source_run_id || github.run_id }}
ORCHESTRATOR_NEXT_TARGET_NUMBER: ${{ inputs.next_target_number }}
ORCHESTRATOR_SOURCE_HANDOFF_CONTEXT: ${{ inputs.source_handoff_context }}
ORCHESTRATOR_SELF_APPROVE_ENABLED: ${{ vars.AGENT_ALLOW_SELF_APPROVE || 'false' }}
ORCHESTRATOR_SELF_MERGE_ENABLED: ${{ vars.AGENT_ALLOW_SELF_MERGE || 'false' }}
ORCHESTRATOR_CURRENT_ROUND: ${{ inputs.automation_current_round }}
ORCHESTRATOR_MAX_ROUNDS: ${{ inputs.automation_max_rounds }}
with:
agent: ${{ steps.provider.outputs.provider }}
github_token: ${{ steps.auth.outputs.token }}
secondary_github_token: ${{ secrets.AGENT_SECONDARY_GITHUB_TOKEN }}
openai_api_key: ${{ secrets.OPENAI_API_KEY }}
claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: ${{ steps.provider.outputs.model }}
display_model: ${{ vars.AGENT_DISPLAY_MODEL || '' }}
permission_mode: approve-all
prompt: orchestrator
reasoning_effort: ${{ steps.provider.outputs.reasoning_effort || 'high' }}
lane: planner
memory_mode_override: read-only
memory_ref: ${{ vars.AGENT_MEMORY_REF || 'agent/memory' }}
memory_policy: ${{ vars.AGENT_MEMORY_POLICY || '' }}
rubrics_ref: ${{ vars.AGENT_RUBRICS_REF || 'agent/rubrics' }}
rubrics_policy: ${{ vars.AGENT_RUBRICS_POLICY || '' }}
rubrics_mode_override: read-only
rubrics_limit: ${{ vars.AGENT_RUBRICS_LIMIT || '10' }}
session_bundle_mode: ${{ inputs.session_bundle_mode || vars.AGENT_SESSION_BUNDLE_MODE || 'auto' }}
session_policy: resume-best-effort
request_text: ${{ inputs.request_text }}
requested_by: ${{ inputs.requested_by || github.actor }}
route: orchestrator
source_kind: workflow_dispatch
target_kind: ${{ inputs.target_kind || (inputs.source_action == 'implement' && 'issue' || 'pull_request') }}
target_number: ${{ inputs.target_number }}
target_url: ${{ (inputs.target_kind || (inputs.source_action == 'implement' && 'issue' || 'pull_request')) == 'issue' && format('{0}/{1}/issues/{2}', github.server_url, github.repository, inputs.target_number) || format('{0}/{1}/pull/{2}', github.server_url, github.repository, inputs.target_number) }}
workflow: agent-orchestrator.yml
- name: Decide and dispatch next action
env:
AUTOMATION_CURRENT_ROUND: ${{ inputs.automation_current_round }}
AUTOMATION_MAX_ROUNDS: ${{ inputs.automation_max_rounds }}
AUTOMATION_MODE: ${{ inputs.automation_mode }}
AGENT_COLLAPSE_OLD_REVIEWS: ${{ vars.AGENT_COLLAPSE_OLD_REVIEWS }}
BASE_BRANCH: ${{ inputs.base_branch }}
BASE_PR: ${{ inputs.base_pr }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ steps.auth.outputs.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
NEXT_TARGET_NUMBER: ${{ inputs.next_target_number }}
REQUESTED_BY: ${{ inputs.requested_by || github.actor }}
REQUEST_TEXT: ${{ inputs.request_text }}
SESSION_BUNDLE_MODE: ${{ inputs.session_bundle_mode || vars.AGENT_SESSION_BUNDLE_MODE || 'auto' }}
SOURCE_ACTION: ${{ inputs.source_action }}
SOURCE_CONCLUSION: ${{ inputs.source_conclusion }}
SOURCE_RECOMMENDED_NEXT_STEP: ${{ inputs.source_recommended_next_step }}
SOURCE_HANDOFF_CONTEXT: ${{ inputs.source_handoff_context }}
SOURCE_RUN_ID: ${{ inputs.source_run_id || github.run_id }}
AGENT_ALLOW_SELF_APPROVE: ${{ vars.AGENT_ALLOW_SELF_APPROVE || 'false' }}
AGENT_ALLOW_SELF_MERGE: ${{ vars.AGENT_ALLOW_SELF_MERGE || 'false' }}
AUTHOR_ASSOCIATION: ${{ inputs.author_association }}
ACCESS_POLICY: ${{ inputs.access_policy }}
REPOSITORY_PRIVATE: ${{ inputs.repository_private || (github.event.repository.private && 'true' || 'false') }}
TARGET_KIND: ${{ inputs.target_kind || (inputs.source_action == 'implement' && 'issue' || 'pull_request') }}
TARGET_NUMBER: ${{ inputs.target_number }}
PLANNER_RESPONSE_FILE: ${{ steps.planner.outputs.response_file }}
run: node .agent/dist/cli/orchestrate-handoff.js