Skip to content

Release Process ​

This document covers the CI/CD pipeline, versioning policy, the Changesets-based release workflow, and the procedure for adding a new package to the workspace.


CI/CD & Release Flow ​

GitLab CI with stages: build → test → release → deploy.

Pipeline runs on MRs and main. Jobs include: build, test:unit, typecheck, lint, and the sharded integration jobs described below.

Integration test sharding ​

The old monolithic test:integration job (~45 min) is replaced by three parallel jobs that all inherit from .integration_base (full service set: Redis + Postgres + real Temporal, pinned to tags: [amd64]):

  • test:integ:e2e — @helix-agents/e2e, 4 vitest shards. The e2e shard runner (packages/e2e/scripts/run-both-vitest.mjs) forwards --shard to both of its vitest configs.
  • test:integ:dbos — @helix-agents/runtime-dbos, 2 shards (runs serially via singleFork).
  • test:integ:rest — everything else, using turbo negative filters (--filter='!@helix-agents/e2e' --filter='!@helix-agents/runtime-dbos') so no package is dropped.

Each shard runs in its own pod. A test failure isolates to that shard; re-running one ~12-minute shard is far cheaper than re-running the old ~45-minute monolith. The old global TURBO_CONCURRENCY cap is gone.

test:integration:nightly-matrix (schedule-only, runs the full 9-backend parity matrix) is unchanged.

format:check (disabled) ​

A format:check job exists in the pipeline but is currently disabled (when: never) pending a repo-wide pnpm run format pass to clear existing prettier drift. To enable it: remove the when: never line and uncomment the two trigger rules.

Release ​

The old two-step flow (release:version opened a release/next MR; release:publish fired when CHANGELOG changed on merge) is replaced by a single manual release job in the main pipeline.

On a green main pipeline, click release. The job:

  1. Runs changeset version to consume changesets and bump package versions.
  2. Runs changeset publish to publish packages to the GitLab npm registry. Changesets detects pnpm and publishes with pnpm publish, which rewrites every internal workspace:^ dependency to ^<current version> in the published package.json.
  3. Commits chore: version packages [skip ci] and pushes the bump + tags straight to main.

Publish runs before the push, so a publish failure leaves main untouched and the job is safely re-runnable (changeset publish skips already-published versions).

One-time setup required: pushing to the protected main branch requires a project access token with write_repository scope, added to main's "Allowed to push" list and stored as the masked CI/CD variable RELEASE_PUSH_TOKEN. CI_JOB_TOKEN cannot push to a protected branch; the job's push step is inert until that variable exists.

Versioning policy (CC25, round 30) ​

Helix Agents is pre-1.0 and follows a "MINOR = observable behavior change" policy — stricter than the loose "pre-1.0 breaking changes are OK" convention many projects use:

  • MAJOR: not used for 0.x packages. A package that is already at >= 1.0.0 (several are, through Changesets' peer-dependency cascade: ai-sdk, agent-server and the runtime-* packages) takes major for a breaking change, as precedent changesets did (e.g. the runtime-cloudflare sandbox subpath move).

  • MINOR: bumped for BOTH type-signature additions AND observable behavior changes (including runtime-rejection of previously-valid inputs, changed default semantics, removed reference-identity on returned objects, and metric/log schema changes). Consumers must read the changeset body before upgrading a minor version — "MINOR" does NOT imply "fully backwards compatible."

  • PATCH: bug fixes, doc-only changes, purely internal refactors with no consumer-observable effect.

Peer-dependency cascade. .changeset/config.json sets ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange: true. A package whose peer dependency is bumped is bumped itself only when the peer's new version falls outside the declared range. Without the option, Changesets gave every peer dependent of a bumped package a major, even for an in-range minor; that is how ai-sdk and the runtime-* packages reached their current major versions. With it:

  • A peer at >= 1.0.0 that takes a minor or patch stays inside ^x.y.z, so its peer dependents get no forced bump (only the updateInternalDependencies patch, when they also depend on it directly).
  • A peer at 0.x that takes a minor leaves a caret range (^0.13.0 excludes 0.14.0), so a dependent that declares the peer as workspace:^ or ^0.x still takes a major. An open range such as >=0.13.0 (what runtime-js and runtime-dbos declare for their store peers) stays in range. A 0.x patch stays in range either way.
  • A peer's major leaves a caret range, so a workspace:^ / ^x dependent still takes a major.
  • An open range absorbs even a peer's major: >=0.13.0 accepts 1.0.0, so runtime-js / runtime-dbos get no bump when a store they peer-depend on ships a breaking change. The author of that change must add an explicit changeset for the open-range dependents.

Run pnpm changeset status --verbose before merging a changeset that bumps a peer-depended-on package, and state any resulting major in the changeset body and the MR description. The option lives under ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH, so a @changesets/cli upgrade may rename or drop it: re-run changeset status after bumping Changesets and confirm the cascade still behaves as described here.

The rationale: pre-1.0 we'd rather surface behavior changes via changeset body than force a MAJOR bump cadence that dilutes the signal when a truly breaking API change lands post-1.0. Each changeset's "Observable behavior changes" section is load-bearing — consumers who skim CHANGELOG.md for version numbers alone will miss these.

Contributors: if your changeset adds runtime rejection of input that used to be accepted, changes a default value, or alters the content/shape of a log event or metric, use minor and describe the change explicitly under an "Observable behavior changes" section in the changeset body.

Release Process (Changesets) ​

Releases use Changesets. The release flow is:

  1. Contributors add changesets — Run pnpm changeset in your MR, select affected packages, choose bump type (patch/minor/major), describe changes. This creates a .changeset/*.md file. Only published packages are versioned: private packages (@helix-agents/e2e and the examples) are skipped (privatePackages.version: false), and a changeset must not name a private package together with a published one; Changesets rejects that mix. The changesets:check MR job (pnpm run changeset:check) catches it before merge.

  2. Changesets accumulate — Multiple MRs can add changesets before a release.

  3. Maintainer triggers release — On the green main pipeline, manually click the release job (see CI/CD section above).

Local Release Flow (Faster) ​

Running the release locally skips waiting for CI:

bash
# 1. Ensure you're on latest main
git checkout main && git pull origin main

# 2. Consume changesets and bump versions
pnpm changeset version

# 3. Publish packages to the registry
pnpm changeset publish

# 4. Commit and push the bump + tags
git add .changeset/ packages/*/CHANGELOG.md packages/*/package.json
git commit -m "chore: version packages [skip ci]"
git push origin main --follow-tags

See .gitlab-ci.yml and the files it includes under ci/ for the full pipeline configuration.

Adding a Package ​

  1. Create packages/<name>/
  2. Add package.json with name @helix-agents/<name>, version 0.0.1
  3. Add publishConfig pointing to GitLab registry
  4. Add tsconfig.json and tsup.config.ts (copy from existing package)
  5. Depend on other workspace packages with "@helix-agents/<pkg>": "workspace:^" (never a bare version or * — pnpm only links workspace packages through the workspace: protocol), then run pnpm install and commit the updated pnpm-lock.yaml.
  6. Add a changeset for the new package: pnpm changeset and select the new package as minor. The Changesets CLI is the only supported way to ship a version bump for a new package; do not edit package.json versions by hand. See the Release Process (Changesets) section above.

Released under the MIT License.