Skip to content

stevedevex/orchestrator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

release-agent

Deterministic, non-blocking release preparation across a fleet of GitLab repositories — a reconciler engine over a declarative manifest, exposed as a CLI (release) and an MCP server (release-mcp) so you can drive releases from GitHub Copilot Chat, Claude, or any other MCP client in plain language.

Built for the reality of multi-repo products: libraries that must publish before services can pin them, release branches with iterated tags (v1.0.0-1, -2, ...), a QA gate, a deploy repo that pins the final versions — and CI pipelines that take 40–50 minutes. The engine never waits: every command returns in seconds, pipelines run on their own time, and an idempotent reconcile advances whatever is ready whenever you come back.

How it works

  • Manifest (release-manifest.yaml) — declares your repos, their dependency DAG, which files to edit (maven properties, python dependencies, yaml keys), and what variables each pipeline needs. See examples/release-manifest.yaml. The engine hard-codes zero product knowledge — adoption is pure configuration.
  • Run state — one JSON per release, stored in a small dedicated GitLab project. Shared visibility, full history, optimistic locking; any teammate can resume any release.
  • Reconciler — walks the DAG; per node: create branch → commit version pins → cut tag → trigger pipeline (via the API, so per-release variables travel with the build) → poll → record produced versions → unblock dependents. Nothing is ever created twice.
  • Gates — a manual checkpoint (e.g. qa-signoff) the engine will not pass without an explicit release approve.
  • Captures & report — manifest-declared regexes grep job logs for values of interest (Sonar URLs, image digests); release report assembles the whole release — tags, pipelines, versions, captured values — into markdown, optionally published to the state repo (reports/<coordinate>.md).
  • No LLM in the engine — ever. release explain deterministically fetches a failed job's log tail; the model on the client side (Copilot, Claude, ...) interprets it in the same chat. ports.LogExplainer stays as an extension point if you want a hosted model, but nothing requires one.

Install

Requires Python 3.13+ and uv.

git clone <this repo> && cd release_agent
uv sync
uv run release --help

Configure

Everything is environment variables (per-developer PAT model):

Variable Required Meaning
RELEASE_AGENT_GITLAB_URL yes GitLab base URL, e.g. https://gitlab.example.com
RELEASE_AGENT_GITLAB_TOKEN yes PAT with api scope (falls back to GITLAB_TOKEN)
RELEASE_AGENT_BOT_TOKEN no Second identity used to approve MRs on repos with merge_request: { bot_approve: true }
RELEASE_AGENT_STATE_PROJECT yes* Project holding run states + manifest, e.g. group/release-state
RELEASE_AGENT_STATE_BRANCH no Branch in the state project (default main)
RELEASE_AGENT_MANIFEST_PATH no Manifest path in the state project (default release-manifest.yaml)
RELEASE_AGENT_MANIFEST_FILE no Local manifest file (overrides the state project copy)
RELEASE_AGENT_STATE_DIR yes* Local state directory instead of a state project (single-user/dev)

* one of STATE_PROJECT / STATE_DIR is required.

Use — CLI

release start 1.0.0 --env sit \
  -i jar_bundle_1_version=1.0.1 -i pydantic_version=10.0.0 -i jar_bundle_2_version=2.3.0

release status 1.0.0        # render the DAG, tags, pipeline URLs
release reconcile 1.0.0     # idempotent tick — run it whenever, it never double-fires
release bump 1.0.0 service-a          # next vX.Y.Z-(N+1) after a QA fix
release approve 1.0.0 qa-signoff      # clear the manual gate
release explain 1.0.0 service-a       # advisory: why did the pipeline fail?

Use — MCP (Copilot Chat, Claude, ...)

Register the stdio server with your client, e.g. VS Code .vscode/mcp.json:

{
  "servers": {
    "release-agent": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/release_agent", "release-mcp"],
      "env": {
        "RELEASE_AGENT_GITLAB_URL": "https://gitlab.example.com",
        "RELEASE_AGENT_GITLAB_TOKEN": "${input:gitlab-token}",
        "RELEASE_AGENT_STATE_PROJECT": "group/release-state"
      }
    }
  }
}

Then just talk: "start release 1.0.0 on sit with jar-bundle-2 2.3.0, jar-bundle-1 jar 1.0.1, pydantic 10.0.0" → the model calls release_start; later, "advance the release"release_reconcile. Tools exposed: release_start, release_status, release_reconcile, release_approve, release_bump, release_explain, get_manifest.

Prompt cookbook

You say Tool the model calls
"Start release 1.2.0 on sit — jar-bundle-1 jar 1.0.2, pydantic 10.1.0, jar-bundle-2 2.4.0" release_plan (dry-run shown for confirmation) → release_start
"What exactly would starting 1.2.0 do?" release_plan — branches, MRs, predicted tags, edits, variables; nothing touched
"Where's release 1.2.0?" / "Did the jar-bundle-2 build finish?" release_status
"Advance the release" / "Pipelines look done, continue" release_reconcile (idempotent — always safe)
"Why is service-a stuck?" release_status → explains e.g. an AWAITING_MERGE MR with its link
"QA signed off, approve the release" release_approve
"Why did service-b fail?" release_explain → model interprets the failed job's log
"Fix is merged on service-b's release branch, rebuild it" release_bump (warns if the deploy repo pinned the old tag)
"Which files get edited during a release? What depends on what?" get_manifest
"What was the last released version of service-a?" release_tags — reads the repo's tags live, no run state needed
"What releases are in flight?" / "What did we ship last?" list_releases — all coordinates with progress + attention flags
"Here's my updated manifest — is it valid?" validate_manifest — full validation before you commit it
"Take 1.2.0 as far as it can go and tell me what's blocking" chains reconcile → status → summary
"Give me the release report" / "…and publish it" release_report — tags, pipelines, versions, log-captured values (e.g. Sonar URLs)

Habits that keep it reliable: always name the coordinate ("release 1.2.0") so the model never guesses which release you mean, and ask to "advance" freely — reconcile never double-fires, so an over-eager prompt costs nothing.

Adapt to your product

  1. Copy examples/release-manifest.yaml and describe your repos, tiers, edits, and pipeline variables.
  2. Create a release-state project in GitLab, commit the manifest there.
  3. Gate your CI release/publish jobs to run only from API-triggered tag pipelines ($CI_COMMIT_TAG && $CI_PIPELINE_SOURCE == "api" — purely additive, design §9). On GitLab 17.7+ also set each project's "Minimum role to use pipeline variables" to developer, or API-triggered pipelines with variables are rejected with HTTP 400.
  4. Need a new file mutation? Add one function to EDIT_KINDS in src/release_agent/core/edits.py.
  5. Have an LLM endpoint? Implement ports.LogExplainer and wire it in src/release_agent/bootstrap.py.

Development

uv run pytest          # unit + engine tests against an in-memory fake GitLab
uv run ruff check .

License

MIT

About

A deterministic GitLab release orchestrator exposed as MCP tools, driven from Copilot Chat or Claude in plain language.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages