* feat(release): add OMA-powered release bot * fix(release): harden bot execution boundaries * chore(release): schedule bot for Friday evening
13 KiB
Releasing
This repository publishes three packages from one npm workspaces root. A fourth
private workspace, @open-multi-agent/release-bot, prepares and executes the
repository-specific workflow but is never published. This file records what
ships, in what order, and what CI proves at each stage. The day-to-day
contribution flow is in CONTRIBUTING.md.
What ships
| Package | Version track | Tagged | Published |
|---|---|---|---|
@open-multi-agent/core |
The trunk. Git tags and GitHub Releases track this version. | vX.Y.Z |
Every release |
@open-multi-agent/otel |
Independent. Depends on core through a semver range, so a core release does not force a republish. | No tag | Only when it changes |
create-oma-app |
Independent. Its templates pin core exactly, so it ships alongside every core release. | No tag | Every core release |
The root package.json is private and is never published. Each published
package sets publishConfig.access to public, so no extra access flag is
needed at publish time.
Release bot and authority boundary
packages/release-bot uses OMA itself to
prepare a release. Its fixed runTasks() graph runs change analysis and
compatibility review in parallel, then a planner and an independent reviewer.
All four agents use DeepSeek V4 Flash. Only the two evidence roles receive
narrow, read-only tools: immutable evidence, the release contract, and one
deterministic risk-ranked, size-bounded diff bundle whose paths are selected by
code rather than the model. The planner and reviewer consume the immutable
summary and structured dependency reports. Model output is restricted to
version bump classes and changelog prose; deterministic code owns concrete
versions, the allowed file set, template pins, validation, Git/GitHub
mutations, package order, registry checks, tags, and GitHub Releases.
Each DAG task has one task-level attempt; malformed structured output may use
OMA's single in-run correction, but the whole role is not restarted. Per-role
turn and output budgets bound model work, and the complete planning DAG aborts
after ten minutes. When core changes but packages/create-oma-app does not,
deterministic policy forces a create-oma-app patch bump for the template pin.
The planner's scaffolder bump class is authoritative only when that workspace
has merged changes of its own; normalization occurs before independent review.
The weekly workflow creates a ready release PR automatically. A maintainer
reviews and merges it manually. Merge is the release approval, but it is not a
model tool approval: publication happens in a separate deterministic workflow
only after CI succeeds on the exact merged release commit.
Repository content is treated as untrusted evidence. Agents never receive
bash, write tools, GitHub credentials, npm credentials, or publish tools. A
rejected or invalid structured plan fails closed without creating a branch.
One-time activation
The workflows require all of the following external configuration:
- Install a repository-scoped GitHub App with Contents: read/write and
Pull requests: read/write. Set its client ID as the repository variable
RELEASE_BOT_APP_CLIENT_IDand private key as the secretRELEASE_BOT_APP_PRIVATE_KEY. - Add
DEEPSEEK_API_KEYas a repository Actions secret. - Create a GitHub environment named
npm-release. Environment reviewers are optional; merging the release PR is already the required human approval. - For each of the three npm packages, configure a GitHub Actions trusted
publisher for organization
open-multi-agent, repositoryopen-multi-agent, workflow filenamepublish.yml, environmentnpm-release, and thenpm publishaction.
The GitHub App token is required rather than the repository GITHUB_TOKEN so
the automatically created PR starts normal CI and the published GitHub Release
starts release-smoke.yml. npm publication uses short-lived OIDC credentials;
there is no NPM_TOKEN. Trusted publishing currently requires a GitHub-hosted
runner, Node 22.14 or newer, npm 11.5.1 or newer, and id-token: write.
Versioning and tags
- Only core is tagged. Tags are
vX.Y.Zmatching the core version, lightweight rather than annotated, pointing at the release commit. @open-multi-agent/otelandcreate-oma-appcarry their own version numbers and are not tagged.CHANGELOG.mdis a single root file keyed by core version, with an## Unreleasedsection at the top.
Breaking changes
Decide whether a release contains one before the release commit, not while writing release notes. A change is breaking when it can stop working for a caller who did nothing but upgrade:
- the
enginesfloor rises - a published direct dependency crosses a major version
- input that an earlier release accepted is now rejected
- a public export is removed, renamed, or has its signature narrowed
A conventional-commits ! marker on a merged commit signals that one landed,
but it is not a substitute for the steps below, because nothing carries it
through to a reader of the release.
When a release contains a breaking change:
CHANGELOG.mdopens that version with a### Breaking changessection above### Added, naming what breaks and what the caller has to do.- The GitHub Release repeats it as its first section, not as a note near the end.
- Weigh the version number against how far the change reaches. 1.14.0 raised the
enginesfloor to Node 20 and movedopenaifrom v4 to v6 while shipping as a minor, so every^1.xcaller on Node 18 received it automatically. npm treats anenginesmismatch as anEBADENGINEwarning rather than an install failure, which means those projects install successfully and fail later at run time.
The release commit
Version bumps land on main through a normal pull request before anything is
published. A release commit contains:
- the version bump in
packages/core/package.json - the version bump in
packages/otel/package.json, when otel is part of this release - the version bump in
packages/create-oma-app/package.json - the new core version pinned in every create-oma-app template manifest:
packages/create-oma-app/templates/demo/package.jsonpackages/create-oma-app/templates/pr-review/package.jsonpackages/create-oma-app/templates/security/package.jsonpackages/create-oma-app/template/package.json, the shared base
- the
CHANGELOG.mdentry, moved out of## Unreleased - the regenerated
package-lock.json
The three package manifests are what users receive, and their package versions
remain independent as described above. The package job in ci.yml asserts
that every templates/*/package.json overlay pins the current core version; it
does not require the otel or create-oma-app package version to equal core. The
base template manifest is not covered by that assertion and is not user-facing
either, because every overlay ships its own package.json that overwrites the
base copy at scaffold time. Keep it in sync anyway so local tooling and the base
layer do not disagree with the release, but a stale pin there does not reach a
generated project. See
packages/create-oma-app/AGENTS.md for
the full set of template traps.
Order
- Prepare a release PR.
release-bot.ymlruns each Friday at 10:00 UTC (18:00 Asia/Taipei) and is also manually dispatchable. It exits successfully without calling a model when a release PR is already open, no commits exist after the latest core tag, orpackages/coredid not change. Otherwise it plans the release, updates the known manifests, pins, changelog, and lockfile, runs root lint, test, and build validation, pushes arelease-bot/core-vX.Y.Zbranch, and opens a ready PR. - Review and merge the release commit into
main. Branch protection and the normal PR CI remain authoritative. Never merge a release proposal merely because its model reviewer approved it. - Wait for
CIon the exact merged release commit. A successfulCIworkflow run onmaintriggerspublish.yml. The publisher verifies that both core and create-oma-app versions increased relative to that commit's first parent; later unrelated commits cannot accidentally publish. - Publish to npm in order:
core, thenotelwhen its declared version is not already live, thencreate-oma-app. The publisher checks the public registry before every action and waits for each new version to resolve before continuing. Rerunning the exact release commit resumes a partial publication rather than attempting to republish an immutable npm version. - Tag the release commit
vX.Y.Zand push the tag. The lightweight tag is still created only after every expected package is visible. An existing tag must point to the exact release commit, and a tag with missing packages is a hard failure. - Publish the GitHub Release last. The GitHub App event triggers
release-smoke.yml, which resolves the real registry bytes. An existing matching Release is treated as an idempotent resume.
For recovery, manually dispatch publish.yml with the exact merged release
commit SHA. The workflow verifies that the SHA is reachable from main and has
an already successful CI run; it does not accept a branch name, failed CI, or
an arbitrary working tree.
Release notes are not a copy of the changelog
The two are rendered under different rules. CHANGELOG.md is
hard-wrapped at 80 columns, which is correct for a repository file because GitHub
joins a single newline into a space there. A release body is rendered with GFM
hard line breaks, where every newline becomes a <br>, so pasted wrapped text
turns into a column of lines that look truncated at 80 characters.
Unwrap each paragraph and list item onto a single line before publishing, and check the draft first:
jq -Rs '{text: ., mode: "gfm"}' < notes.md | gh api /markdown --input - | grep -c '<br>'
That endpoint matches what the release page renders. A correct release body
returns 0.
What CI proves
Before merge
ci.yml runs on every push and pull request targeting main.
packagebuilds every workspace, then:- imports each core entry point, runs the CLI help, and exercises the evaluation gate on both its pass and fail paths
- asserts the core, otel, and create-oma-app tarballs ship exactly the expected files
- packs core, installs it, and smoke-tests the installed
omabin - asserts
templates/*/package.jsonpin the current core version, and typechecks the template against core - resolves the lowest core version allowed by otel's dependency range, packs it from npm, and installs the real core and otel tarballs into clean consumers
scaffold-e2erunsnpm run test:scaffold: pack, scaffold, install, and run, all from local tarballs.lint,testacross Node 20/22/24, andcoveragecover the rest.
Running npm pack --dry-run inside a package locally mirrors the tarball
assertion CI performs.
After the GitHub Release
release-smoke.yml triggers on release: published and runs two independent
jobs against the real registry.
npx-scaffold proves the published bytes work from a user's point of view:
npx create-oma-app@latest <project> --template pr-review --provider cloud- the generated
package.jsonpins@open-multi-agent/coreat exactly the release tag without its leadingv npm installresolves that same core versionnpm run demosucceeds with no API key set, driving the real scheduler and report generation from scripted responsesreports/contains Markdown, JSON, and HTML output carrying the expected demo-mode markers
The whole chain retries up to three times with a delay, so a stale npm cache or CDN propagation lag becomes a retry rather than a failure.
otel-consumer covers @open-multi-agent/otel, which has no tag of its own
and would otherwise get no post-publish coverage:
- reads the otel version from
packages/otel/package.jsonat the release commit, since the release tag only names the core version - installs that otel version together with the released core and pinned OpenTelemetry packages into a clean consumer
- asserts the resolved core is the released one, which catches a core release that has outgrown otel's dependency range (npm would quietly resolve an older core rather than fail the install)
- emits a span record through
createOtelTraceSinkand asserts it reaches anInMemorySpanExporter
When otel did not change in a given release, its version is already live from an earlier one and this job still proves the pairing holds against the new core.
This is a post-publish alarm, not a gate. The release is already out when either job runs. A red run means the published bytes are broken and a fix release is needed; it does not block anything.