Skip to main content
Glama

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.

Related MCP server: GitLab MCP Server

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

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stevedevex/orchestrator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server