Simple workflow runner that clones target repos, applies changes via a change agent, and submits PRs.
The system consists of two main workflows:
- Orchestrator Workflow: Manages multi-repo coordination, discovers repos, evaluates relevance, and processes repos in parallel (beta pydantic-graph API)
- Repo Change Workflow: Handles individual repo changes with AI review and CI integration
Parallel Processing: When multiple repositories are provided, the orchestrator processes them concurrently with automatic resource management:
- Relevance evaluation runs in parallel for all discovered repos
- Repo changes (apply, review, submit) are executed in parallel with a configurable concurrency limit
- Default limit: 3 concurrent repos (configurable via
MAX_PARALLEL_REPOSenvironment variable) - Thread-safe state aggregation ensures reliable PR tracking across parallel executions
- Discovery mode (no repos specified) runs sequentially for safety
- Multi-repo rollouts: apply the same change across many repos (dependency bumps, config standardization, lint/format rules, CI updates).
- Safe iteration on the same branch: use
--change-idso reruns target a stable branch name and you can iterate until the PR is clean. - Prompt-config driven automation: store prompts in a separate “prompt config” repo so changes are reviewed/versioned and rerunnable.
- Jira-driven prompts: point at a Jira ticket to build prompts from its summary/description (useful when the ticket is the source of truth).
- Human-in-the-loop or fully automated: use human-written prompts, AI change agents, and optional evaluation steps to skip/stop when not relevant.
Install from PyPI (multi-repo-pr-creator):
pip install -U multi-repo-pr-creator
pr-creator --helpIf you prefer isolated installs:
pipx install multi-repo-pr-creator
pr-creator --helpQuick start (CLI prompts):
export CURSOR_API_KEY=...
export GITHUB_TOKEN=...
pr-creator \
--prompt "Update dependency X to version Y." \
--repo https://github.com/<owner>/<repo> \
--working-dir ~/.pr-creator/reposOr run via Docker (see example below).
- pr-creator:
leonpatmore2/pr-creator:latest(Docker Hub) - cursor-agent:
leonpatmore2/cursor-agent:latest(Docker Hub)
- Either:
- Docker (to run
cursor-agentin a container), or - A local
cursor-agentbinary on your PATH (to run Cursor via CLI)
- Docker (to run
- No git or GitHub CLI needed (Dulwich + GitHub API handle clone/push/PR)
You choose exactly one base prompt source:
- Inline prompts (CLI): pass
--prompt(required) and optionally--relevance-prompt.- If
--relevance-promptis empty/missing, all repos are treated as relevant.
- If
- Prompt config YAML (GitHub): pass
--prompt-config-owner,--prompt-config-repo,--prompt-config-path(and optionally--prompt-config-ref).- The YAML must include
change_promptandrelevance_prompt(and may includechange_id). - The
relevance_promptcomes from the YAML (CLI--relevance-promptis ignored in this mode).
- The YAML must include
- Jira ticket: pass
--jira-ticket(and--jira-base-url/--jira-email/--jira-api-tokenor env fallbacks).- The prompt is built from the ticket summary + description.
- In this mode,
--relevance-promptstill applies (optional).
Notes
- Mutual exclusion: you can’t use Jira (
--jira-ticket) and prompt config (--prompt-config-*) together. - Prompt “tail”: if you use prompt config or Jira and also pass
--prompt, the CLI prompt is treated as higher priority and is placed in a top “Highest priority instructions (CLI)” section. - Exit status: the CLI exits with code 1 if any errors are recorded in
orchestrator_errors(even if some PRs were successfully created).
Target repos in one (or more) of these ways:
- Explicit list: pass
--repomultiple times. Each value can be a full URL, anowner/reposlug, or (withGITHUB_DEFAULT_ORG) a bare repo name. - Datadog discovery: pass
--datadog-teamto discover repos and add them to the list (requiresDATADOG_API_KEY+DATADOG_APP_KEY). - MCP-driven discovery (NEW): if
--mcp-configis provided and no--repois specified, the orchestrator agent will discover the target repository using available MCP tools (e.g., GitHub MCP server) based on the prompt.- If the orchestrator cannot determine which repository is required, it will add an error to the workflow state (
orchestrator_errorslist) instead of proceeding with changes. - If the orchestrator calls
repo_changeand the repo-change workflow fails for a repo (e.g., clone/apply/review/submit error), the error is captured and added toorchestrator_errorsinstead of crashing the entire run. - If MCP tool calls fail (e.g., invalid GitHub search syntax, API errors), the error is captured and added to
orchestrator_errorsinstead of crashing the entire run.
- If the orchestrator cannot determine which repository is required, it will add an error to the workflow state (
Repo processing behavior:
- Dedup + normalization: repo inputs are normalized to GitHub HTTPS URLs and deduplicated.
- Optional relevance filter: if a
relevance_promptis present, each repo is evaluated and only relevant repos get changes applied.- Relevance decisions are cached on disk at
~/.pr-creator/relevance-cache.json, keyed by(repo_identifier, evaluated_sha, prompt_hash), to avoid repeating the same checks across runs.
- Relevance decisions are cached on disk at
- Review before commit: after changes are applied, the Cursor agent reviews the repo's uncommitted state and may request a single follow-up apply pass before committing.
- Repeatable reruns: set
--change-idto use stable branch naming (<change_id>/<slug>) and a stable workspace path under--working-dir(useful for rerunning after fixes).
GitHub auth (required for PR creation)
GITHUB_TOKEN— GitHub token used for clone/push/PR creation (used when--github-tokenis omitted).- If unset, the workflow can still run, but push/PR creation is skipped.
Agent selection
CHANGE_AGENT— choose change agent; defaultcursor.EVALUATE_AGENT— choose evaluate agent; defaultcursor.REVIEW_AGENT— choose review agent; defaultcursor.
Cursor agent runtime (how the change agent is executed)
Common (Docker + CLI):
CURSOR_API_KEY— passed to the cursor agent.CURSOR_RUNNER— how to run cursor-agent;dockerorcli(default:docker).CURSOR_ENV_KEYS— comma-separated env keys forwarded to the agent; defaultCURSOR_API_KEY.CURSOR_MODEL— cursor model to use; defaultgpt-5.2.CURSOR_MODEL_<INTENT>— override model for a specific Cursor usage intent (e.g.CURSOR_MODEL_CHANGEto use a different model just for the change/apply agent). If unset, falls back toCURSOR_MODEL.CURSOR_STREAM_OUTPUT— enable streaming cursor output to console; set totrueto enable verbose streaming (default:false).CURSOR_STREAM_SHOW_THINKING— when streaming output is enabled, include thinking output; set to1|true|yes|onto enable (default: off).PR_CREATOR_CURSOR_OUTPUT_LOG_DIR— directory to write per-run full rawcursor-agentoutput logs (default:~/.pr-creator/cursor-output-logs).
Docker runner only (CURSOR_RUNNER=docker):
CURSOR_IMAGE— docker image for cursor agent; defaultleonpatmore2/cursor-agent:latest.
CLI runner only (CURSOR_RUNNER=cli):
CURSOR_CLI_BIN— cursor-agent binary name/path (default:cursor-agent).CURSOR_WORKSPACE_ROOT— workspace root passed to cursor-agent (default: common path of repo + context roots).CURSOR_AGENT_TIMEOUT_SECONDS— hard timeout for a single cursor-agent run; if exceeded the process is killed. Default:1200(20 minutes). Set to0to disable.CURSOR_AGENT_HEARTBEAT_SECONDS— emit a “still running” message when no output is seen for this many seconds (default:30).
Orchestration
ORCHESTRATOR_MODEL— pydantic-ai model used by the orchestration step; defaultopenai:gpt-5.2.MAX_PARALLEL_REPOS— maximum number of repositories to process concurrently during orchestration; default3. Increase for faster processing of many repos, decrease to reduce resource usage.
LiteLLM (optional)
LITELLM_API_BASE— LiteLLM API base URL (e.g.,http://localhost:4000). If set, pydantic-ai agents (e.g., orchestrator, CI summarizer, naming agent) will use LiteLLM as a proxy to access various LLM providers.LITELLM_API_KEY— LiteLLM API key for authentication. Required whenLITELLM_API_BASEis set.
MCP servers (optional)
MCP_CONFIG— path to MCP servers configuration file (JSON format). Defaults to~/.pr-creator/mcp-servers.jsonif that file exists. If provided (via env or CLI), the orchestrator will load MCP servers as tools, enabling it to access external resources like GitHub repositories for planning context.ORCHESTRATOR_MCP_MAX_RETRIES— max retries for MCP tool calls (default:5). Useful for flaky networks or transient GitHub/MCP failures.
Agent context (optional)
AGENT_CONTEXT_ROOTS— comma-separated absolute paths on your machine to mount read-only into the agent workspace for extra repo context (available under/workspace/context/<n>inside the agent).
Apply safety (optional)
CHANGE_ALLOWED_PATHS— comma-separated glob patterns of paths the change agent is allowed to modify. If set, any changes outside this allowlist are reverted after the change agent runs (useful for strict tasks like “only edit README.md”).
Prompt sources (optional)
PROMPT_CONFIG_OWNER— GitHub owner for prompt config loading when--prompt-config-owneris omitted.JIRA_BASE_URL— Jira base URL (e.g., https://your-org.atlassian.net) when using--jira-ticket.JIRA_EMAIL— Jira account email for API token auth when using--jira-ticket.JIRA_API_TOKEN— Jira API token when using--jira-ticket.
Repo discovery (optional)
DATADOG_API_KEY/DATADOG_APP_KEY— required if using Datadog repo discovery.GITHUB_DEFAULT_ORG— default GitHub org/owner to prepend when repo args are provided without an owner (e.g.,--repo my-repo->github.com/<org>/my-repo.git). Also provided as context to the orchestrator agent when discovering repositories.
PR submission & branch naming
SUBMIT_CHANGE— submitter; defaultgithub.SUBMIT_PR_BASE— target base branch; default repo default.SUBMIT_PR_BODY— PR body prefix; defaultAutomated changes generated by pr-creator.The full PR description will include this prefix followed by the original user prompt (under "## Original Request") and any repo-specific context from the orchestrator (under "## Repository-Specific Context").DEFAULT_BRANCH_PREFIX— branch name prefix used when no change_id is provided; defaultauto/pr.
Naming generation
NAMING_MODEL— pydantic-ai model used to generate branch/PR names (default:openai:gpt-5.2).NAMING_MAX_ATTEMPTS— max number of naming generation attempts per repo (default:4).NAMING_BACKOFF_BASE— exponential backoff base for naming retries (default:3.0).NAMING_BACKOFF_MIN— minimum backoff time in seconds for naming retries (default:5.0).NAMING_BACKOFF_MAX— maximum backoff time in seconds for naming retries (default:60.0).
Relevance evaluation retries
EVALUATE_MAX_ATTEMPTS— max number of relevance evaluation retry attempts per repo (default:4).EVALUATE_BACKOFF_BASE— exponential backoff base for evaluation retries (default:3.0).EVALUATE_BACKOFF_MIN— minimum backoff time in seconds for evaluation retries (default:5.0).EVALUATE_BACKOFF_MAX— maximum backoff time in seconds for evaluation retries (default:60.0).
Change agent (apply step) retries
APPLY_MAX_ATTEMPTS— max number of change agent retry attempts per repo (default:4).APPLY_BACKOFF_BASE— exponential backoff base for change agent retries (default:3.0).APPLY_BACKOFF_MIN— minimum backoff time in seconds for change agent retries (default:5.0).APPLY_BACKOFF_MAX— maximum backoff time in seconds for change agent retries (default:60.0).
Note: The backoff formula is
min * (base^attempt), capped atmax. With the defaults above, retries occur at: 5s → 15s → 45s → 60s.
Review loop
REVIEW_MAX_ATTEMPTS— max number of review→apply retries per repo when the review step returnsCHANGES_REQUIRED(capped at2, default:2).
Review step retries (unexpected errors)
REVIEW_STEP_MAX_ATTEMPTS— max number of in-step retries when the review agent errors (default:4).REVIEW_STEP_BACKOFF_BASE— exponential backoff base for review step retries (default:3.0).REVIEW_STEP_BACKOFF_MIN— minimum backoff time in seconds for review step retries (default:5.0).REVIEW_STEP_BACKOFF_MAX— maximum backoff time in seconds for review step retries (default:60.0).
CI / GitHub Actions (post-submit wait + auto-fix loop)
CI_FIX_MAX_ATTEMPTS— max number of CI-fix→apply retries per repo when checks fail (default:2).CI_WAIT_TIMEOUT_SECONDS— max time to wait for checks per attempt (default:1800).CI_WAIT_POLL_SECONDS— poll interval while waiting (default:15).CI_WAIT_HEARTBEAT_SECONDS— heartbeat log interval while waiting for checks (default:120).CI_PENDING_NO_CHECKS_GRACE_SECONDS— if GitHub reportspendingbut no check-runs or commit-status contexts appear, stop waiting after this many seconds (default:60).CI_ACCEPTABLE_CONCLUSIONS— comma-separated conclusions treated as “passing” (default:success,skipped,neutral).CI_MAX_LOG_BYTES— max bytes to download from a logs archive (default:5000000).CI_MAX_LOG_CHARS— max characters of extracted logs included in the prompt (default:30000).CI_SUMMARY_MODEL— pydantic-ai model used to summarize CI failures (one per failed check) after CI retries are exhausted (default:openai:gpt-5.2).
Logging & git identity
LOG_LEVEL— logging level; defaultINFO.GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL— author/committer; defaults to pr-creator placeholders if unset.
GitHub auth
--github-token— GitHub token used for clone/push/PR creation (overrides envGITHUB_TOKEN).
Prompts
--prompt— main prompt text. Required unless using prompt config.--relevance-prompt— relevance filter prompt. Required unless using prompt config.
Jira prompt source
--jira-ticket— Jira ticket id (e.g., ENG-123) to build the prompt from its summary/description.--jira-base-url— Jira base URL (env fallback:JIRA_BASE_URL).--jira-email— Jira user email for API token auth (env fallback:JIRA_EMAIL).--jira-api-token— Jira API token (env fallback:JIRA_API_TOKEN).- When using
--jira-ticket, the change id is automatically set to the Jira ticket id for stable branch names.
Prompt config (alternative to passing prompts directly)
--prompt-config-owner— GitHub owner of the prompt config repo (env fallback:PROMPT_CONFIG_OWNER). Must be set with--prompt-config-repoand--prompt-config-path.--prompt-config-repo— GitHub repo name containing the prompt config file.--prompt-config-ref— git ref for the prompt config file; defaultmain.--prompt-config-path— path to the YAML prompt config file inside the repo.
Change ID (for static branches)
--change-id— Change ID to use for static branch names. When provided, ensures re-runs use the same branch name (format:{branch_prefix}-{change_id}). Can also be set in prompt config YAML (takes precedence over CLI arg).
Repositories
--repo— repository URL to process. Can be passed multiple times. Optional; if omitted, orchestrator will attempt to discover repos.
Datadog discovery
--datadog-team— Datadog team name to discover repos (requiresDATADOG_API_KEYandDATADOG_APP_KEY).--datadog-site— Datadog API base URL; defaulthttps://api.datadoghq.com.
MCP integration
--mcp-config— path to MCP servers configuration file (JSON format). Defaults to~/.pr-creator/mcp-servers.jsonif that file exists. Enables the orchestrator to:- Access external tools (e.g., GitHub API for reading repos)
- Discover target repositories when
--repois not specified - Gather context from multiple sources before planning changes
- See pydantic-ai MCP docs for config format.
Runtime
--working-dir— where repos are cloned; default~/.pr-creator/repos.--log-level— logging level; defaultINFO.--context-root— host directory to mount (read-only) into the agent workspace for extra context; can be passed multiple times (env equivalent:AGENT_CONTEXT_ROOTS).--secret— forward a secret to the change agent as an env var (KEY=VALUE); can be passed multiple times.--secret-env— forward an env var (by name) from the current process into the change agent; can be passed multiple times.
- Workspaces live under
--working-dir(default~/.pr-creator/repos); directories are auto-created per repo. - When
--change-idis set, the workspace path is deterministic (<repo>-<change_id>) and reused across runs so the same branch can be reapplied. - Without
--change-id, a fresh workspace with a random suffix is created and cleaned up after each repo finishes. - To start fresh, remove the working directory (e.g.,
rm -rf ~/.pr-creator/repos).
By default, pr-creator runs a per-repo orchestration step before applying changes:
- Repo discovery
- For each repo:
- Workspace + relevance check
- Orchestrate: derive a repo-specific prompt (read-only analysis)
- Apply changes + review + submit PR + wait for CI + cleanup Orchestration is always enabled.
docker run --rm \
-e CURSOR_API_KEY \
-e GITHUB_TOKEN \
leonpatmore2/pr-creator:latest \
--prompt-config-owner LeonPatmore \
--prompt-config-repo pr-creator \
--prompt-config-ref main \
--prompt-config-path examples/prompt-config.yaml \
--repo https://github.com/LeonPatmore/cheap-ai-agents-aws \
--working-dir /tmp/repos \
--log-level INFOThe orchestrator can use Model Context Protocol (MCP) servers to access external tools and context. The most common use case is the GitHub MCP server for repository discovery and exploration.
- Automatic repository discovery: Run pr-creator without specifying
--repo, and let the orchestrator find the target repo based on the prompt - Context gathering: Orchestrator can explore repositories, check README files, search for patterns, etc., before planning changes
- Create an MCP configuration file (
mcp-servers.json):
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}- Run pr-creator with MCP enabled:
# Example 1: Let orchestrator discover the repo
pr-creator \
--prompt "Add a health check endpoint to the main API service" \
--mcp-config mcp-servers.json \
--working-dir ~/.pr-creator/repos
# Example 2: Use MCP for context gathering with explicit repo
pr-creator \
--prompt "Update dependencies based on the README requirements" \
--repo https://github.com/owner/repo \
--mcp-config mcp-servers.json \
--working-dir ~/.pr-creator/reposNote: The orchestrator agent will use GitHub MCP tools to search repositories, read files, and gather context before planning changes.
The CLI outputs a JSON summary to stdout with the following structure:
{
"irrelevant_repos": ["https://github.com/owner/repo1"],
"created_prs": [
{
"repo_url": "https://github.com/owner/repo2",
"branch": "auto/pr-update-deps",
"pr_url": "https://github.com/owner/repo2/pull/123",
"pushed_sha": "abc123..."
}
],
"orchestrator_errors": [
"Could not determine target repository from prompt..."
]
}Fields:
irrelevant_repos: List of repository URLs that were filtered out by the relevance checkcreated_prs: List of PR records for repositories where changes were successfully applied (at most one record per repo; retries update/overwrite the record instead of appending duplicates)orchestrator_errors: List of error messages from the orchestrator (e.g., when it cannot determine which repositories to target). Empty list if no errors occurred.
Commands
pipenv run python -m pr_creator.cli --prompt "<prompt>" --relevance-prompt "<relevance>" --repo <repo_url> --working-dir ~/.pr-creator/reposmake test-e2e— run the e2e pytest (requires env vars set; pytest will load repo-root.envif present).make lint— flake8.make format— black (requires Python ≥3.12.6).
