Skip to main content
This guide is for maintainers who build and publish OpenAgentCore. To install Core and Web, use the installation guide. The rules the installer code follows are in Installer design rules; required checks are in CONTRIBUTING.

Build a distribution

A distribution is the matched set of Linux amd64 release assets built from one commit: the control archive (the installer, the oac command, and the Core, Web, gateway and PostgreSQL images), the Runtime image and node artifacts as separate files, and the native installers. Build on Linux x86_64 with a glibc compatible with Debian 12, Docker, the Go version in go.mod, a C compiler (the microsandbox helper is a CGO build), Node, pnpm, Python 3.9 or newer, curl, tar and sha256sum. The source must be clean and committed. First prepare the pinned Codex package and MiniMax Code companion, then build:
prepare-release-runtimes.sh refuses an existing ~/.oac/build/release-inputs; use a fresh build host. The build reuses the Core, Web, Runtime, SDK and helper builders. The manifest records the commit and source tree, image config and OCI manifest digests, the Runtime OCI manifest digest, the microsandbox runtime and firmware hashes, and the size and SHA-256 of every Runtime and node artifact; native installers carry only their SHA-256 in the catalog. Output is the control archive and its .sha256, the optional offline archive, and the versioned Runtime, node and native installer assets. Nothing is published. Rebuilding into a directory that already holds this commit’s distribution is refused. The control archive carries no Runtime image or node execution artifacts; the offline archive carries them. The download contract describes how nodes obtain them. A distribution carries the docs listed in BUNDLED_DOCS in scripts/core-distribution-manifest.py. Links between bundled docs stay relative; every other relative link is rewritten to the same file on GitHub at the bundle’s commit. The build fails when a link or anchor does not resolve, and make check-distribution runs the same check on every tracked Markdown file outside example/. Update the list when you add or move a doc that the installer or its output refers to.

Native installers

Self-hosted machines install oac-daemon from per-platform native installers: Linux amd64, macOS arm64 and Windows amd64. Each is built on its own OS by the native-check workflow (scripts/build-native-installer.mjs, whose pins object fixes the Node.js and Harness versions) and verified on every selected native check. Manual packaging runs and release checks upload oac-native-installer-<OS>-<ARCH>.tar.gz for seven days; ordinary PR and main checks do not upload successful packages. For a local distribution, download the three artifacts from a native-check run on that exact commit (a manual run or the release run; pull-request runs build the merge commit and do not match), then assemble the catalog from that checkout:
The catalog records the commit, the Runtime protocol version, each archive’s SHA-256 and, with a release base, its versioned URL. The control archive and Core image carry only native-installers/catalog.json; the archives become separate oac-native-<commit>-<platform>.tar.gz release assets, and the offline archive holds one copy of each outside the Core image. Without a catalog, Sessions report the install command as unavailable, and the release workflow refuses to publish.

Runtime images and helpers

make build-core-distribution builds all of these. Build one on its own to test a Harness image or a helper. Run every command from the repository root; default outputs go under ${OAC_DEV_HOME:-$HOME/.oac}/build. Codex Runtime image. Extract the official npm package @openai/codex@0.153.4-linux-x64 under ~/.oac (for example with npm pack --ignore-scripts and tar -xzf), then:
The script checks the package version, builds oac-daemon for Linux amd64 and prepares a context with only the daemon, the unmodified native executable, its resources and services/core/deploy/codex/Dockerfile. Claude Code Runtime image. Node 20 or newer and pnpm are required.
The first step exports the adapter with the pinned Claude Agent SDK (packages/claude-sdk-adapter/package.json) as a checksummed archive for the host platform; the second verifies it and adds the daemon. The image step needs the linux-x64-glibc archive, so build both on Linux x86_64 with glibc. Keep the exported archive unchanged. MiniMax Code Runtime image. Build the companion from a checkout of the revision pinned in packages/mcode-harness/source.json, with the @minimax-ai/code npm package of the same version for native dependencies. The companion build runs on Linux x86_64 or macOS arm64 into a new directory; for the Linux Runtime image, build it on Linux x86_64 (macOS arm64 serves only the native installer):
scripts/prepare-release-runtimes.sh runs the companion build from the pins. The distribution combines the three Harness images into one Runtime image (deploy/distribution/Runtime.Dockerfile): the MiniMax Code image, which carries the daemon, with the Codex executable and resources and the Claude SDK bundle copied in. It verifies that each image carries the daemon built from the same commit. E2B helper.
Docker builds the Linux amd64 helper with the pinned CPython and Debian 12 image. The Python dependency closure, including PyInstaller, is hash-locked in services/core/tools/e2b-provider/requirements.lock; no E2B account key is needed. Set E2B_PROVIDER_BUILD_DIR for another output directory and E2B_SOURCE_REVISION when building from an exported source tree. The output is oac-e2b-provider-linux-amd64.tar.gz with its .sha256; it extracts to oac-e2b-provider/ with the executable, _internal/, licenses/, requirements.lock and manifest.json. The Core image and native Core use the same tree; the host needs a compatible glibc and CA certificates, not Python. microsandbox helper. Linux only, with a C compiler:
The helper is written to ~/.oac/build/microsandbox-provider/oac-microsandbox-provider. Its separate Go module pins the microsandbox Go SDK v0.7.2 and embeds the matching FFI library; never build production with the SDK’s microsandbox_ffi_path tag. Core itself stays a CGO-disabled build. The helper needs glibc and runs only on nodes. microsandbox runtime. The distribution uses the official v0.7.2 release archive microsandbox-linux-x86_64.tar.gz, SHA256 47c223e3ef5298abf05f47ed9f87981106e400d99bb3f1d042d4d6881346b18b (RUNTIME_ARCHIVE_SHA256 in scripts/core-distribution-manifest.py). The build verifies the checksum before extracting msb and libkrunfw.so.5.6.1 and records both files’ hashes. The helper checks those hashes on every call and never installs or upgrades them.

Standalone Core builds

make build-core builds oac-core, oac-core-migrate, oac-core-device, oac-core-environment-key and oac-node into ${OAC_DEV_HOME:-$HOME/.oac}/build/oac-core (OAC_DEV_CORE_BUILD_DIR selects another absolute directory). The build copies only the source set listed in scripts/build-core.sh (the Core service, its contracts, the shared packages it needs and the root Go module files) into a temporary context and builds with CGO disabled, read-only modules and trimmed paths. It needs no Node, Docker or other application. When Core gains a shared dependency, add that package to the list; never copy the whole repository to make it compile. make docker-build-core builds the image oac-core:dev (OAC_DEV_CORE_IMAGE selects another name) from those five commands and the E2B helper. The base is the digest-pinned debian:bookworm-slim with CA certificates and the glibc runtime the helper needs; the default user is UID/GID 65532 and Core listens on :8091. The image is Linux amd64 only and is not pushed to a registry. Changes to the image or its build need make check-core-container in addition to the relevant source checks: it runs the official-client suite against the image with a read-only root filesystem and needs Linux Docker, a non-root user, and the test database and pinned SDK of the service checks (OAC_TEST_DATABASE_URL naming an oac_*_tests database with the migrations applied, and OAC_TEST_OFFICIAL_SDK_PYTHON).

Publish a version

Push a version tag on the reviewed commit to run the core-release workflow:
Tags use vMAJOR.MINOR.PATCH, optionally with a prerelease suffix such as -rc.1 and build metadata such as +build.1. A prerelease suffix creates a GitHub prerelease. Pushing the tag is the release decision. Automated checks establish build and test results, not real-model qualification: assess live execution evidence before you push the tag. Model credentials and private certificate authorities never enter CI or release inputs, including acceptance images that contain them. The workflow runs check on the tagged commit, including the full local gate, official-client and image acceptance, and the native matrix with its packaging artifacts enabled. build starts after check succeeds and reuses those native artifacts. build prepares the pinned Runtime inputs, assembles the native catalog and builds the distribution with the offline archive, and adds deploy/install-release.sh as install.sh with its checksum. The release job runs only after check and build succeed. It is the only job with contents: write. It verifies the archive checksums and the native installer checksums against the catalog, resolves the repository’s current name from GitHub before any write (Actions can keep an old name after a rename), refuses an existing Release or draft for the tag, uploads everything to a new draft on uploads.github.com bound to that draft’s ID without retrying failed uploads, confirms the tag still points at the built commit, and publishes that draft by its ID. Images ship as archives; no registry is pushed. Downloads are anonymous. install.sh resolves the latest stable release once, or the release named by --version, verifies the control archive and runs that bundle’s installer; the installation guide covers its use. Go check and build jobs share Go module and compiler-cache directories under ~/.oac/cache/, keyed by runner OS and architecture, all Go module files, the check/build partition and the commit. Partitioned keys prevent concurrent jobs from saving different compiler subsets under one key. Release builds can seed their cache from backend checks as well as earlier release builds. An older cache only seeds downloads and compilation; every check still runs. New keys are saved only after a successful job. Never move a release tag or overwrite published assets. If the release job fails, inspect the Release first: publication may have completed despite a lost response. Leave a complete published Release as it is. For an incomplete draft, delete that draft (the job refuses any existing Release or draft for the tag), then rerun the failed release job, which reuses the original Actions artifact. Do not rerun the build or recreate the tag to recover a failed upload.

Build a candidate without publishing

A manual run takes a full commit SHA, runs the same checks and builds, defaults to the offline archive, and never publishes:
With draft_release=true the result is an unpublished build-<full SHA> draft Release; with draft_release=false the files stay in the Actions artifact. Use the exact matched asset set; never mix builds or resolve components through latest.

Continuous integration

Every PR runs core-check and reports the required status check. scripts/ci_plan.py owns the only path-to-check map. The planner compares the PR event’s tested merge commit with its verified first parent, using NUL-delimited Git output with rename detection disabled so both old and new paths count. Its JSON plan and reasons appear in the run summary. Missing or inconsistent merge parents, unavailable diffs, empty changes, unknown files, changes to the planner or orchestration/release workflows, and shared build inputs select the full gate. Deletions and mixed changes retain all affected groups. Main pushes and release calls always select every group. Known workflow changes select their consumers: the CI review and actionlint workflows run hygiene and lint; native workflow changes add native checks; API acceptance workflow changes add API checks with container acceptance enabled. The shared Node action selects every job that uses it plus lint. A new or unclassified workflow/action selects the full gate until its consumers are declared in the planner. Planner tests and CI measurement scripts run hygiene; changing the planner itself runs the full gate. Go module and workspace inputs select backend, API (including the container), native and distribution checks. Node manifests, lockfiles and package-manager configuration select Harness, example, Web, Web acceptance and native checks. The root TypeScript configuration selects Web and example checks; the adapter TypeScript configuration retains the Node consumer group. Each selected set includes hygiene. Mixed changes accumulate their consumers, and every job reads the same plan instead of maintaining its own path list. For example, a notification-only PR skips database, browser and native jobs, while a notification plus Core change adds backend and API checks. Ordinary documentation runs hygiene only; generated catalog files and configuration reference sections retain their distribution freshness checks. Installer changes add distribution checks. Web changes add Web checks and both browser shards; Core/DB changes add backend and official-client acceptance. Shared contracts, SDKs, Runtime inputs and dependencies propagate to their consumers according to the planner. Generated catalog and protocol inputs include the installer, client and UI consumers. Do not duplicate path lists in reusable workflows or put a paths filter on the required workflow. The final check runs even when planning or a dependency fails. It requires a successful, valid plan, every selected job to be successful, and every unselected job to be skipped. Failure, cancellation, a missing job, an unexpected skip or an unexpected execution fails the gate. API/native reusable workflows are direct dependencies of this gate. A newer run on the same PR cancels its predecessor. Release checks run at their requested immutable ref; native packaging executes once inside those checks, and the distribution build waits for them. Browser jobs own separate fixtures and servers; increasing workers against the shared mutable fixture is unsafe. Failed browser jobs retain reports/traces for seven days. Native failure phase summaries are retained for seven days and detailed output stays in the Actions logs; credentials and temporary installation trees are not uploaded. Successful native archives are uploaded only for explicit manual packaging or releases, without recompressing the compressed archive. Release distribution artifacts retain their existing recovery policy; failed publication can reuse the original build as described above. The local Node composite action installs the pinned pnpm and caches its package store by lockfile, OS, architecture, Node version and pnpm version. It caches downloaded packages, not node_modules; installs remain frozen. Go partitions retain the existing module/compiler caches described under publication. Cache hits seed work and never replace tests. The native concurrency limit reduces peak demand; it does not imply a reduction in total machine time. For a local change, inspect the selected groups and run their Makefile targets from the table and workflows:
make check remains the full local entry point with an unsharded Web suite and an unsharded store package. make check-web-unit and make check-web-acceptance OAC_WEB_TEST_SHARD=1/2 expose the Web parts; make check-core-packages and make check-core-store OAC_CORE_STORE_SHARD=1/3 expose the Core parts, with store tests assigned to shards by a stable hash of their names. The selection tests cover mixed changes, shared consumers, renames/deletions, unknown inputs, shallow merge checkouts and failed/cancelled/missing results. Changes to the map or workflow graph also require actionlint and replay of representative PR diffs; exercise real documentation, installer, Web and Core runs before relying on new selection rules. Measure completed runs with python3 scripts/ci_metrics.py RUN_ID .... It reports the latest attempt’s summed runner minutes, elapsed time and initial queue delay from that attempt’s start, peak concurrent jobs, platform breakdown and job outcomes/failure fraction. Only jobs assigned a runner in that attempt contribute machine time and execution concurrency; jobs cancelled while queued retain their outcome and wall time. Earlier attempts are not included. Failed-job reruns can carry earlier successful results: their outcomes appear separately and their old execution time is excluded. A missing rerun start timestamp stops measurement because reused jobs cannot be separated reliably. Keep run/head/attempt identities with comparisons, and report cancellations and unfinished runs separately. Raw runner minutes are not billed minutes; use each platform’s published conversion and allowance rules before estimating cost. A small successful sample is not a long-term failure-rate estimate. Main impact selection or scheduled full runs are outside this policy.

CI runners and free allowance

Linux jobs use Blacksmith’s 2-vCPU Ubuntu 22.04 or 24.04 runners; native Windows uses its 2-vCPU Windows 2025 runner. Blacksmith has no 2-vCPU macOS runner, so native macOS uses the standard GitHub macos-15 ARM64 runner. Release building and publication also use 2-vCPU Blacksmith runners. Set the repository Actions variable OAC_USE_GITHUB_RUNNERS to true to run all jobs on standard GitHub-hosted runners instead. Linux keeps its matching Ubuntu version, Windows uses windows-2025, and macOS continues using macos-15. Remove the variable or set it to false to return to Blacksmith’s 2-vCPU defaults. For example, maintainers can switch when the organization’s free allowance is used up, then restore Blacksmith after the allowance resets:
This is an explicit operator switch, not an automatic billing balance probe. Runner selection applies to newly scheduled runs. Check current allowance and platform conversion rates in Blacksmith’s runner documentation before treating 2-vCPU usage as free; Windows minutes consume more allowance than Linux minutes. Standard GitHub runner usage follows the repository’s visibility and GitHub plan. These workflows request no Blacksmith runner larger than 2 vCPU and no paid cache add-on.