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

Available Tools

12 tools
get_manifestC

The release manifest YAML — the declarative source of truth for the repo topology.

Inputs: no named parameters. Returns: result

list_releasesA

Every release known to the state store, newest first, with a progress summary.

Use this to discover coordinates when the user doesn't name one ("what's in flight?", "what was our last release?").

Inputs: no named parameters. Returns: result

release_approveB

Clear a manual gate (e.g. 'qa-signoff') and advance the nodes it was blocking.

Inputs: gate (required), coordinate (required). Returns: object

release_bumpB

After a fix landed on a node's release branch: cut the next iterated tag and build it.

Inputs: node (required), coordinate (required). Returns: object

release_explainA

Explain why a node's pipeline failed (advisory; reads the failed job's log).

Inputs: node (required), coordinate (required). Returns: result

release_planA

Dry-run of release_start: exactly what would be created — branches, MRs, predicted tags (computed from the tags that exist right now), file edits with resolved values, and pipeline variables. Strictly read-only; nothing is touched.

ALWAYS call this before release_start with the same arguments and show the user the plan; only call release_start after they confirm.

Inputs: env (required), inputs (required), coordinate (required), rfc. Returns: object

release_reconcileA

One idempotent tick: read actual GitLab state and advance whatever is ready.

Safe to call any time; does nothing if pipelines are still running.

Inputs: coordinate (required). Returns: object

release_reportA

The release report (markdown): inputs, per-node tags, pipelines, produced versions, and values captured from job logs (e.g. Sonar report URLs).

With publish=true the report is also committed to the state repo (reports/.md).

Inputs: coordinate (required), publish. Returns: result

release_startA

Start a release: build run state from the manifest and fire every ready node.

inputs are the manifest-declared release inputs (e.g. library versions). Returns immediately; pipelines run in GitLab on their own time. Call release_plan first and show the user what will happen.

Inputs: env (required), inputs (required), coordinate (required), rfc. Returns: object

release_statusB

Read-only view of the release DAG: node states, tags, pipeline URLs, gates.

Inputs: coordinate (required). Returns: object

release_tagsA

A node's repository tags, newest first, read LIVE from GitLab — no run state needed.

Use this to answer "what was the last released version of ?" from the repo itself rather than from a release's state or the manifest.

Inputs: node (required), limit. Returns: object

validate_manifestA

Validate a release manifest YAML without touching anything.

Returns 'valid' with a topology summary, or the exact validation error. Use before committing manifest changes to the state repo.

Inputs: manifest_yaml (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 12 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: manifest retrieval, release listing, approval, bumping, explanation, planning, reconciliation, reporting, starting, status, tags, and validation. No two tools overlap in intent.

    Naming Consistency3/5

    Most tools follow the 'release_verb' pattern, but three (get_manifest, list_releases, validate_manifest) lack the prefix, creating inconsistency. The naming is still descriptive but not uniform.

    Tool Count5/5

    12 tools is well-scoped for a release management server. Each tool serves a necessary step in the release lifecycle without unnecessary duplication.

    Completeness5/5

    The tool set covers the full release workflow: planning, starting, approving, bumping, reconciling, reporting, status checking, and tag fetching. Validation and explanation tools add robustness. No obvious gaps for common release operations.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to interact with GitLab repositories, manage merge requests, review code diffs, post comments, and handle issues directly through natural language.
      125 npm
      MIT
    • F
      license
      Not graded
      quality
      Not graded
      maintenance
      Connects AI assistants to GitLab, enabling natural language queries for merge requests, reviews, discussions, pipeline tests, and job logs with the ability to respond to comments and resolve discussions.
      -