From ea66ef417323726fc8f91cb4e894bd8ba707c119 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 13:01:50 -0700 Subject: [PATCH 1/6] docs(skill): add agent-hub-release skill for publishing sidecar agents Generalizes the email agent's release pipeline (freeze -> Agent Hub Worker /publish -> npm OIDC) into a reusable, phased checklist for shipping any future sidecar agent. Documents the per-agent layout, manifest, binaries.lock, the three version numbers that must align, the one-time agent-publish gate + infra setup, and the immutability/best-effort-Intel/main-only invariants. The email agent (hub/agents/{python,npm}/...email + release_agent_email.yml) is the worked reference throughout. --- .claude/skills/agent-hub-release/SKILL.md | 190 ++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 .claude/skills/agent-hub-release/SKILL.md diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md new file mode 100644 index 000000000..1ae30e035 --- /dev/null +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -0,0 +1,190 @@ +--- +name: "agent-hub-release" +description: "Publish a GAIA sidecar agent (frozen no-Python binary + npm client) to the Agent Hub and npm. Use when cutting or setting up a release for any agent under hub/agents/ — 'release the email agent', 'publish agent- v0.2.0', 'onboard a new hub agent', or wiring its release workflow. The email agent is the reference implementation; this skill generalizes it to every future agent." +--- + +# Publishing a GAIA Agent to the Agent Hub + +How to ship a **sidecar agent** — a frozen, no-Python REST binary plus a thin npm +client — to the GAIA Agent Hub and the npm registry. The **email agent is the +reference**: `hub/agents/python/email/` + `hub/agents/npm/agent-email/` + +`.github/workflows/release_agent_email.yml`. Every future agent mirrors that shape. + +This is a **phased process with one hard human gate** (the `agent-publish` +environment). Stop and confirm before anything irreversible — pushing a release +tag, approving the publish gate. Publishes are **immutable per filename**: a bad +release is fixed by a new version, never an overwrite. + +## When to use + +- Cutting a release of an existing hub agent (the happy path). +- Onboarding a brand-new sidecar agent into the hub/npm pipeline (one-time). +- Authoring or debugging a `release_agent_.yml` workflow. + +Not for in-repo Python-only agents (analyst, browser, jira, …) that ship inside +the `amd-gaia` wheel — those follow the core `gaia-release` skill. This skill is +specifically the **frozen-binary + npm-client + hub** distribution path. + +## Distribution model (the mental picture) + +``` +freeze.py (PyInstaller, native per-OS, no cross-compile) + └─ email-agent-[.exe] ← one-file binary, boots a FastAPI REST sidecar + └─ POST /publish → Agent Hub Worker (workers/agent-hub/) ── R2 bucket "gaia-hub" + • stores each object IMMUTABLY under agents/// + • computes SHA-256 server-side + • rebuilds index.json (the hub catalog the website reads) + download = plain public GET at hub.amd-gaia.ai/agents/// + └─ @amd-gaia/agent- (npm) ← typed client + fetch CLI + sidecar lifecycle + • binaries.lock.json maps platform → {filename, sha256, size}; the SHA-256 + is the integrity gate the fetch CLI enforces on download + • published via npm OIDC trusted publishing (provenance, NO npm token) +``` + +The Worker fronts the bucket; **CI never touches R2 directly** — it POSTs to +`/publish` (Bearer auth) so uploads are server-checksummed and the catalog is +rebuilt atomically. The manifest (`gaia-agent.yaml`) rides along inside each POST. + +## What a publishable agent is made of (email = reference) + +| Path | Role | +|------|------| +| `hub/agents/python//gaia_agent_/` | the agent package — `agent.py`, `cli.py`, `contract.py` (frozen wire contract + `SCHEMA_VERSION`), `api_routes.py`, server entry | +| `hub/agents/python//gaia-agent.yaml` | **manifest** — validated against `workers/agent-hub/schemas/manifest.schema.json` | +| `hub/agents/python//README.md` | agent README — published to the hub alongside the binaries (`--readme`) | +| `hub/agents/python//packaging/` | `freeze.py`, `smoke_test.py`, `server.py`, `gen_binaries_lock.py`, `publish_to_r2.py`, `HUB-UPLOAD.md` (manual fallback) | +| `hub/agents/npm/agent-/package.json` | ESM-only; `exports` `.` (Node) + `./client` (browser-safe); `bin`; `files` includes `CHANGELOG.md` | +| `hub/agents/npm/agent-/binaries.lock.json` | platform → artifact + **sha256** + size + `baseUrl` | +| `hub/agents/npm/agent-/README.md` + `CHANGELOG.md` | client docs + SemVer history | +| `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | +| `.github/workflows/release_agent_.yml` | the tag-triggered release (copy of `release_agent_email.yml`) | + +### The manifest (`gaia-agent.yaml`) +Reference: `hub/agents/python/email/gaia-agent.yaml`. Required-ish fields: +`id`, `name`, `version`, `description`, `author`, `license`, `category`, `tags`, +`icon`, `language`, `min_gaia_version`, `models`, `python.{entry_module,entry_class, +dependencies}`, `requirements.{min_memory_gb,platforms}`, `interfaces.{cli,pipe, +api_server,mcp_server,tui}`. Validate against +`workers/agent-hub/schemas/manifest.schema.json` before publishing. + +### The npm client +- **ESM-only** (`"type": "module"`). Two entry points: `.` (Node: fetch + spawn) and + `./client` (browser/Electron renderer: `EmailClient` only, zero Node built-ins). +- **`binaries.lock.json` ships placeholder hashes** (`PENDING-…-replace-with-real-sha256`, + `size: 0`) until the first real release fills them. While a hash is a placeholder, + the fetch CLI is **intentionally fail-loud** — a bad/untrusted binary can never be + fetched. (Build-from-source can't `fetchBinary` until a release; point lifecycle + helpers at a locally-frozen binary for dev.) +- `CHANGELOG.md` follows SemVer; the **MAJOR of the wire `SCHEMA_VERSION`** is what the + client's `checkVersion` enforces at startup, so a contract MAJOR bump is at least a + package MINOR bump with a migration note. + +## The three version numbers that MUST match + +The release fails loudly at the `Resolve + validate release version` step unless all +three are identical: + +1. the **tag** (or `workflow_dispatch` input) — `agent-pkg--v` → `` +2. `hub/agents/npm/agent-/package.json` → `.version` +3. `hub/agents/python//gaia-agent.yaml` → `version` + +Add the matching `CHANGELOG.md` entry in the same version-bump PR. +`binaries.lock.json`'s `agentVersion` + `baseUrl` are **regenerated by CI** — don't +hand-edit them for a release. + +## Cutting a release (existing agent) + +1. **Version-bump PR → main.** Bump all three versions + add the CHANGELOG entry. + Merge to `main` (publishing is allowed **only from main** — the workflow asserts + the release commit is a main ancestor). +2. **Pre-flight locally** (in `hub/agents/npm/agent-/`): `npm ci && npm run build + && npm test`, and `npm pack --dry-run` to confirm `CHANGELOG.md`/`README.md` ship. +3. **Tag from main** (or use `workflow_dispatch` with the version): + ```bash + git tag agent-pkg--v && git push origin --tags + ``` + Namespace is `agent-pkg--*` (NOT `v*`) — it deliberately does not fire the core + `publish.yml`. +4. **Build stage** freezes the binary on each of 4 platforms (`win32-x64`, + `darwin-arm64`, `linux-x64` **required**; `darwin-x64` Intel **best-effort**), + smoke-tests each, computes SHA-256. +5. **Approve the gate.** The `publish` job pauses on the `agent-publish` environment + until a maintainer approves — the human backstop against an accidental/tampered + tag. The publish token isn't even readable until approval. +6. **Publish stage** (atomic): POST every binary to the Worker `/publish` → regenerate + `binaries.lock.json` with the **real** hashes → **fetch-verify every published + object** against the lock (the real integrity gate) → `npm publish` via OIDC + (provenance) → trigger `deploy_website.yml` so the new catalog entry appears. + +Monitor with `gh run watch`. `npm publish` is idempotent (skipped if that exact +version already exists); `/publish` is a verified 409 no-op for identical bytes. + +## Onboarding a NEW agent (one-time) + +1. **Scaffold** `hub/agents/python//` (agent + manifest + packaging) and + `hub/agents/npm/agent-/` (client + lock with placeholders). Mirror email. +2. **Adapt the packaging scripts.** `freeze.py`, `publish_to_r2.py`, and + `gen_binaries_lock.py` currently **hardcode the `email-agent` artifact prefix** and + email paths — copy + parameterize them (or generalize the prefix) for ``. +3. **Copy the workflow** `release_agent_email.yml` → `release_agent_.yml` and change: + `PKG_DIR`, `MANIFEST`, `README`, `FREEZE_DIST`, `HUB_PREFIX` (`agents/`), the + tag trigger (`agent-pkg--*`), the artifact/frozen names, and the npm package + name in the verify/publish steps. +4. **Register the npm trusted publisher** for `@amd-gaia/agent-` against the exact + filename `release_agent_.yml`. ⚠️ The OIDC subject is tied to the filename — + **renaming the workflow later breaks publish.** +5. First release fills the placeholder hashes (step 6 above) — until then the lock's + `PENDING` entries keep fetch fail-loud. + +## One-time infrastructure (per repo / per package) + +- **GitHub environment `agent-publish`** with **required reviewers**; restrict its + deployment branches/tags to `main` **and** the `agent-pkg-*` tag pattern (a + main-only rule blocks the tag-triggered gate). +- **Secret `GAIA_HUB_TOKEN`** — Agent Hub Bearer publish token; must match an entry in + the Worker's `PUBLISH_TOKENS`, scoped to the `AMD` author. Define it as an + **environment** secret on `agent-publish` (not a repo secret) so it's unreadable + until the gate is approved. +- **Variable `GAIA_HUB_BASE_URL`** — public Worker origin for downloads + the lock + `baseUrl` (default `https://hub.amd-gaia.ai`). +- **Variable `GAIA_HUB_PUBLISH_URL`** — the Worker's **workers.dev** URL for uploads. + The free-plan WAF on the proxied `hub.amd-gaia.ai` custom domain blocks large binary + multipart uploads (but not GETs). Unset → uploads fall back to the custom domain and + **403**. +- **Railway `HUB_CATALOG_URL=https://hub.amd-gaia.ai`** so the website rebuild reflects + the new entry. + +## Invariants & gotchas + +- **Immutable per filename.** Re-publishing identical bytes = idempotent 409 no-op; + different bytes under a published name **fail loudly**. Fix a bad release with a new + version — never an overwrite. +- **Publish only from main.** The job asserts the release commit is on `main` (blocks + releasing from a feature branch and keeps npm OIDC on main). +- **`SCHEMA_VERSION` MAJOR is the compat gate.** Client and binary must agree on the + wire-contract MAJOR or `startSidecar` throws `VersionMismatchError`. Bump the npm + package and re-publish the binary together. +- **Best-effort Intel.** `darwin-x64` builds on `macos-26-intel`, then is verified on + `macos-15-intel`. If it fails or is missing, it's **dropped** and the other 3 ship — + a loud `::warning::` + job summary, and Intel users get a clear "no binary for + darwin-x64" install error (never a placeholder/silent one). +- **Fetch-verify is the real gate.** Even after `/publish`, CI re-fetches every object + through the npm `fetch` CLI and checks bytes-hash-to-lock (with bounded retry for + Cloudflare edge propagation) before `npm publish`. +- **Website is rebuilt, not patched.** The hub pages build from the live `index.json`; + the publish job triggers `deploy_website.yml` on `main`. +- **Manual fallback:** `hub/agents/python//packaging/HUB-UPLOAD.md` documents the + by-hand rclone path to the `gaia-hub` bucket — it produces the identical objects + + lock as CI, so a hand-upload and a CI release are interchangeable. + +## Reference files + +- `.github/workflows/release_agent_email.yml` — the canonical release workflow (read + its header comments — they document the whole contract). +- `hub/agents/python/email/gaia-agent.yaml` — manifest reference. +- `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md,CHANGELOG.md}` — + client package reference. +- `hub/agents/python/email/packaging/{freeze,smoke_test,publish_to_r2,gen_binaries_lock}.py`, + `HUB-UPLOAD.md` — packaging + publish tooling. +- `workers/agent-hub/{README.md,schemas/manifest.schema.json,src/}` — the Worker, the + `/publish` contract, and the manifest schema. From 7bbaaf121cdccae93a232619d12e48df8773e7d3 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 13:20:26 -0700 Subject: [PATCH 2/6] docs(skill): align agent-hub-release with the author guide; fix manifest-schema claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-checked against docs/guides/hub-publishing.mdx (the on-main author guide): - correct the manifest validation claim — gaia-agent.yaml is linted by 'gaia agent test --lint'; manifest.schema.json is the server-side AGGREGATE schema (requires versions/latest_version/security_tier/permissions you never hand-write), not what you validate the source manifest against - position the skill as the sidecar (frozen-binary + npm) extension on top of the guide's wheel/PyPI + PR publish; defer manifest/versioning/immutability to it - use 'gaia agent version' for the version bump + manual npm package.json sync - mark ./client + CHANGELOG-in-files as landing with #1773/#1776 (not yet on main) - add the 'pipeline never cut a real release' (#1648 placeholders) + 'verify infra exists' caveats, the win-x64/win32-x64 platform-name skew, and a reusable- workflow pointer so per-agent copies don't drift --- .claude/skills/agent-hub-release/SKILL.md | 233 ++++++++++++---------- 1 file changed, 123 insertions(+), 110 deletions(-) diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md index 1ae30e035..8a6d2fdc2 100644 --- a/.claude/skills/agent-hub-release/SKILL.md +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -1,29 +1,40 @@ --- name: "agent-hub-release" -description: "Publish a GAIA sidecar agent (frozen no-Python binary + npm client) to the Agent Hub and npm. Use when cutting or setting up a release for any agent under hub/agents/ — 'release the email agent', 'publish agent- v0.2.0', 'onboard a new hub agent', or wiring its release workflow. The email agent is the reference implementation; this skill generalizes it to every future agent." +description: "Cut or wire a frozen-binary + npm sidecar release for a GAIA agent (the email-agent CI pipeline: freeze -> Agent Hub Worker /publish -> npm OIDC). Use when releasing or onboarding a sidecar agent under hub/agents/, or authoring a release_agent_.yml. For the standard wheel/PyPI + Hub publish, defer to the author guide docs/guides/hub-publishing.mdx — this skill is the sidecar extension on top of it." --- -# Publishing a GAIA Agent to the Agent Hub +# Releasing a GAIA Sidecar Agent (frozen binary + npm) How to ship a **sidecar agent** — a frozen, no-Python REST binary plus a thin npm -client — to the GAIA Agent Hub and the npm registry. The **email agent is the -reference**: `hub/agents/python/email/` + `hub/agents/npm/agent-email/` + -`.github/workflows/release_agent_email.yml`. Every future agent mirrors that shape. +client — to the GAIA Agent Hub and npm, via the tag-triggered CI release. The +**email agent is the reference**: `hub/agents/python/email/` + +`hub/agents/npm/agent-email/` + `.github/workflows/release_agent_email.yml`. This is a **phased process with one hard human gate** (the `agent-publish` environment). Stop and confirm before anything irreversible — pushing a release tag, approving the publish gate. Publishes are **immutable per filename**: a bad release is fixed by a new version, never an overwrite. -## When to use +> **Status — the email pipeline has not yet cut a real release.** +> `hub/agents/npm/agent-email/binaries.lock.json` still carries placeholder +> `PENDING-1648` hashes and **#1648 is open**, so the freeze→publish→fetch-verify +> path has never run end-to-end. Treat this skill as the design of record, not a +> paved road, until the first real release lands and fills the lock. Likewise the +> `agent-publish` environment + `GAIA_HUB_TOKEN` + hub vars are **maintainer +> setup** (workflow header) — verify they exist before relying on them. -- Cutting a release of an existing hub agent (the happy path). -- Onboarding a brand-new sidecar agent into the hub/npm pipeline (one-time). -- Authoring or debugging a `release_agent_.yml` workflow. +## Relationship to the author guide (read first) -Not for in-repo Python-only agents (analyst, browser, jira, …) that ship inside -the `amd-gaia` wheel — those follow the core `gaia-release` skill. This skill is -specifically the **frozen-binary + npm-client + hub** distribution path. +[`docs/guides/hub-publishing.mdx`](../../../docs/guides/hub-publishing.mdx) is the +**author-facing** guide for the standard distribution: a Python **wheel** published +to the Hub + PyPI via `gaia agent publish` (or the no-token PR route). It owns the +authoritative rules for the **manifest, versioning, immutability, and the two +publish routes** — don't restate or contradict them here. + +This skill covers the **additional** channel an agent like email layers on top: a +**frozen native binary + an `@amd-gaia/agent-` npm client**, released through +`release_agent_.yml`. An agent can ship one or both channels. Where the two +overlap (manifest, version rules), the guide is the source of truth. ## Distribution model (the mental picture) @@ -33,7 +44,7 @@ freeze.py (PyInstaller, native per-OS, no cross-compile) └─ POST /publish → Agent Hub Worker (workers/agent-hub/) ── R2 bucket "gaia-hub" • stores each object IMMUTABLY under agents/// • computes SHA-256 server-side - • rebuilds index.json (the hub catalog the website reads) + • rebuilds index.json (the catalog the website reads) download = plain public GET at hub.amd-gaia.ai/agents/// └─ @amd-gaia/agent- (npm) ← typed client + fetch CLI + sidecar lifecycle • binaries.lock.json maps platform → {filename, sha256, size}; the SHA-256 @@ -42,149 +53,151 @@ freeze.py (PyInstaller, native per-OS, no cross-compile) ``` The Worker fronts the bucket; **CI never touches R2 directly** — it POSTs to -`/publish` (Bearer auth) so uploads are server-checksummed and the catalog is -rebuilt atomically. The manifest (`gaia-agent.yaml`) rides along inside each POST. +`/publish` so uploads are server-checksummed and the catalog is rebuilt atomically. +The manifest + README ride along inside the publish. + +## What a sidecar agent adds (email = reference) -## What a publishable agent is made of (email = reference) +On top of the normal agent package (manifest + code + README + `pyproject.toml`, +per the guide), a sidecar agent adds: | Path | Role | |------|------| -| `hub/agents/python//gaia_agent_/` | the agent package — `agent.py`, `cli.py`, `contract.py` (frozen wire contract + `SCHEMA_VERSION`), `api_routes.py`, server entry | -| `hub/agents/python//gaia-agent.yaml` | **manifest** — validated against `workers/agent-hub/schemas/manifest.schema.json` | -| `hub/agents/python//README.md` | agent README — published to the hub alongside the binaries (`--readme`) | | `hub/agents/python//packaging/` | `freeze.py`, `smoke_test.py`, `server.py`, `gen_binaries_lock.py`, `publish_to_r2.py`, `HUB-UPLOAD.md` (manual fallback) | -| `hub/agents/npm/agent-/package.json` | ESM-only; `exports` `.` (Node) + `./client` (browser-safe); `bin`; `files` includes `CHANGELOG.md` | -| `hub/agents/npm/agent-/binaries.lock.json` | platform → artifact + **sha256** + size + `baseUrl` | -| `hub/agents/npm/agent-/README.md` + `CHANGELOG.md` | client docs + SemVer history | -| `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | +| `hub/agents/npm/agent-/package.json` | ESM-only client; `exports` `.` (Node) — and `./client` (browser-safe) once **#1773** lands | +| `hub/agents/npm/agent-/binaries.lock.json` | platform → artifact + **sha256** + size + `baseUrl` (placeholders until the first release) | +| `hub/agents/npm/agent-/README.md` (+ `CHANGELOG.md`) | client docs; CHANGELOG is recommended (Keep a Changelog), **not** required by publish | +| `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser, #1773), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | | `.github/workflows/release_agent_.yml` | the tag-triggered release (copy of `release_agent_email.yml`) | -### The manifest (`gaia-agent.yaml`) -Reference: `hub/agents/python/email/gaia-agent.yaml`. Required-ish fields: -`id`, `name`, `version`, `description`, `author`, `license`, `category`, `tags`, -`icon`, `language`, `min_gaia_version`, `models`, `python.{entry_module,entry_class, -dependencies}`, `requirements.{min_memory_gb,platforms}`, `interfaces.{cli,pipe, -api_server,mcp_server,tui}`. Validate against -`workers/agent-hub/schemas/manifest.schema.json` before publishing. - -### The npm client -- **ESM-only** (`"type": "module"`). Two entry points: `.` (Node: fetch + spawn) and - `./client` (browser/Electron renderer: `EmailClient` only, zero Node built-ins). -- **`binaries.lock.json` ships placeholder hashes** (`PENDING-…-replace-with-real-sha256`, - `size: 0`) until the first real release fills them. While a hash is a placeholder, - the fetch CLI is **intentionally fail-loud** — a bad/untrusted binary can never be - fetched. (Build-from-source can't `fetchBinary` until a release; point lifecycle - helpers at a locally-frozen binary for dev.) -- `CHANGELOG.md` follows SemVer; the **MAJOR of the wire `SCHEMA_VERSION`** is what the - client's `checkVersion` enforces at startup, so a contract MAJOR bump is at least a - package MINOR bump with a migration note. - -## The three version numbers that MUST match - -The release fails loudly at the `Resolve + validate release version` step unless all -three are identical: +Notes: +- **The npm client is ESM-only** (`"type": "module"`). Node consumers use the `.` + entry (fetch + spawn); browser/Electron renderers use `./client` (client-only, + zero Node built-ins) — added by **#1773**, documented by **#1776**. +- **`binaries.lock.json` ships placeholder hashes** until the first real release. + While a hash is a placeholder the fetch CLI is **fail-loud** — a bad binary can + never be fetched. For local dev, point the lifecycle helpers at a locally-frozen + binary instead of `fetchBinary`. +- **Manifest validation:** validate the author manifest with `gaia agent test + --lint`. Do **not** validate `gaia-agent.yaml` against + `workers/agent-hub/schemas/manifest.schema.json` — that is the Hub's *server-side + aggregate* schema and requires fields you never hand-write (`versions`, + `latest_version`, `deprecated`, `security_tier`, `permissions`). +- **Platform-name skew (easy bug):** the manifest's `requirements.platforms` uses + `win-x64`; `binaries.lock.json` + the CI matrix use `win32-x64`. Same agent, two + spellings — don't copy one into the other. + +## The three version numbers the release checks + +The `Resolve + validate release version` step fails loudly unless all three are +identical: 1. the **tag** (or `workflow_dispatch` input) — `agent-pkg--v` → `` 2. `hub/agents/npm/agent-/package.json` → `.version` 3. `hub/agents/python//gaia-agent.yaml` → `version` -Add the matching `CHANGELOG.md` entry in the same version-bump PR. -`binaries.lock.json`'s `agentVersion` + `baseUrl` are **regenerated by CI** — don't -hand-edit them for a release. +Bump the Python side with **`gaia agent version patch|minor|major`** (it rewrites +`gaia-agent.yaml` + `pyproject.toml` + `__init__.py` together), then **manually sync +the npm `package.json`** to match — the release tooling checks they agree. Add the +matching `CHANGELOG.md` entry in the same PR. `binaries.lock.json`'s `agentVersion` ++ `baseUrl` are **regenerated by CI** — don't hand-edit them for a release. ## Cutting a release (existing agent) -1. **Version-bump PR → main.** Bump all three versions + add the CHANGELOG entry. - Merge to `main` (publishing is allowed **only from main** — the workflow asserts - the release commit is a main ancestor). -2. **Pre-flight locally** (in `hub/agents/npm/agent-/`): `npm ci && npm run build - && npm test`, and `npm pack --dry-run` to confirm `CHANGELOG.md`/`README.md` ship. -3. **Tag from main** (or use `workflow_dispatch` with the version): +1. **Version-bump PR → main.** `gaia agent version `, sync the npm + `package.json`, add the CHANGELOG entry, merge to `main` (the workflow asserts the + release commit is a `main` ancestor — publishing is allowed only from main). +2. **Pre-flight** in `hub/agents/npm/agent-/`: `npm ci && npm run build && npm + test`, and `npm pack --dry-run` to confirm `README.md`/`CHANGELOG.md` ship. +3. **Tag from main** (or `workflow_dispatch` with the version): ```bash git tag agent-pkg--v && git push origin --tags ``` - Namespace is `agent-pkg--*` (NOT `v*`) — it deliberately does not fire the core - `publish.yml`. -4. **Build stage** freezes the binary on each of 4 platforms (`win32-x64`, - `darwin-arm64`, `linux-x64` **required**; `darwin-x64` Intel **best-effort**), - smoke-tests each, computes SHA-256. + Namespace is `agent-pkg--*` (NOT `v*`) — it deliberately does not fire the + core `publish.yml`. +4. **Build stage** freezes on 4 platforms — `win32-x64`, `darwin-arm64`, `linux-x64` + **required**; `darwin-x64` (Intel) **best-effort** — smoke-tests each, hashes it. 5. **Approve the gate.** The `publish` job pauses on the `agent-publish` environment - until a maintainer approves — the human backstop against an accidental/tampered - tag. The publish token isn't even readable until approval. -6. **Publish stage** (atomic): POST every binary to the Worker `/publish` → regenerate + until a maintainer approves; the publish token isn't readable until then. +6. **Publish stage** (atomic): POST every binary to `/publish` → regenerate `binaries.lock.json` with the **real** hashes → **fetch-verify every published - object** against the lock (the real integrity gate) → `npm publish` via OIDC - (provenance) → trigger `deploy_website.yml` so the new catalog entry appears. + object** against the lock → `npm publish` via OIDC (provenance) → trigger + `deploy_website.yml` so the new catalog entry appears. -Monitor with `gh run watch`. `npm publish` is idempotent (skipped if that exact -version already exists); `/publish` is a verified 409 no-op for identical bytes. +Monitor with `gh run watch`. `npm publish` skips if that exact version already +exists; `/publish` is a verified 409 no-op for identical bytes. -## Onboarding a NEW agent (one-time) +## Onboarding a NEW sidecar agent (one-time) -1. **Scaffold** `hub/agents/python//` (agent + manifest + packaging) and - `hub/agents/npm/agent-/` (client + lock with placeholders). Mirror email. +1. **Scaffold** the normal agent first (`gaia agent init`, per the guide), then add + `packaging/` and the `hub/agents/npm/agent-/` client. Mirror email. 2. **Adapt the packaging scripts.** `freeze.py`, `publish_to_r2.py`, and - `gen_binaries_lock.py` currently **hardcode the `email-agent` artifact prefix** and - email paths — copy + parameterize them (or generalize the prefix) for ``. -3. **Copy the workflow** `release_agent_email.yml` → `release_agent_.yml` and change: - `PKG_DIR`, `MANIFEST`, `README`, `FREEZE_DIST`, `HUB_PREFIX` (`agents/`), the - tag trigger (`agent-pkg--*`), the artifact/frozen names, and the npm package - name in the verify/publish steps. -4. **Register the npm trusted publisher** for `@amd-gaia/agent-` against the exact - filename `release_agent_.yml`. ⚠️ The OIDC subject is tied to the filename — - **renaming the workflow later breaks publish.** -5. First release fills the placeholder hashes (step 6 above) — until then the lock's - `PENDING` entries keep fetch fail-loud. - -## One-time infrastructure (per repo / per package) + `gen_binaries_lock.py` currently **hardcode the `email-agent` artifact name + + paths** — copy + parameterize them for ``. +3. **Copy the workflow** `release_agent_email.yml` → `release_agent_.yml` and + change: `PKG_DIR`, `MANIFEST`, `README`, `FREEZE_DIST`, `HUB_PREFIX` + (`agents/`), the tag trigger (`agent-pkg--*`), the artifact/frozen names, + and the npm package name in the verify/publish steps. +4. **Register the npm trusted publisher** for `@amd-gaia/agent-` against the + exact filename `release_agent_.yml`. ⚠️ The OIDC subject is tied to the + filename — **renaming the workflow later breaks publish.** +5. *(Maintainability)* per-agent copies of a ~550-line workflow + three hardcoded + scripts **will drift**. The durable fix is a reusable `workflow_call` release + workflow + scripts parameterized by ``; the per-agent copy is interim. + +## One-time infrastructure (verify it exists) + +Per the workflow header (`release_agent_email.yml`), these are **maintainer setup** +— confirm each before the first release: - **GitHub environment `agent-publish`** with **required reviewers**; restrict its deployment branches/tags to `main` **and** the `agent-pkg-*` tag pattern (a main-only rule blocks the tag-triggered gate). -- **Secret `GAIA_HUB_TOKEN`** — Agent Hub Bearer publish token; must match an entry in - the Worker's `PUBLISH_TOKENS`, scoped to the `AMD` author. Define it as an +- **Secret `GAIA_HUB_TOKEN`** — Agent Hub Bearer token matching an entry in the + Worker's `PUBLISH_TOKENS`, scoped to the agent's `author`. Define it as an **environment** secret on `agent-publish` (not a repo secret) so it's unreadable until the gate is approved. -- **Variable `GAIA_HUB_BASE_URL`** — public Worker origin for downloads + the lock +- **Var `GAIA_HUB_BASE_URL`** — public Worker origin for downloads + the lock `baseUrl` (default `https://hub.amd-gaia.ai`). -- **Variable `GAIA_HUB_PUBLISH_URL`** — the Worker's **workers.dev** URL for uploads. - The free-plan WAF on the proxied `hub.amd-gaia.ai` custom domain blocks large binary - multipart uploads (but not GETs). Unset → uploads fall back to the custom domain and - **403**. -- **Railway `HUB_CATALOG_URL=https://hub.amd-gaia.ai`** so the website rebuild reflects - the new entry. +- **Var `GAIA_HUB_PUBLISH_URL`** — the Worker's **workers.dev** URL for uploads. The + free-plan WAF on the proxied `hub.amd-gaia.ai` custom domain blocks large binary + uploads (but not GETs). Unset → uploads fall back to the custom domain and **403**. +- **Railway `HUB_CATALOG_URL=https://hub.amd-gaia.ai`** so the website rebuild + reflects the new entry. ## Invariants & gotchas - **Immutable per filename.** Re-publishing identical bytes = idempotent 409 no-op; - different bytes under a published name **fail loudly**. Fix a bad release with a new - version — never an overwrite. -- **Publish only from main.** The job asserts the release commit is on `main` (blocks - releasing from a feature branch and keeps npm OIDC on main). + different bytes under a published name **fail loudly**. Fix a bad release with a + new version — never an overwrite. (Same immutability the guide describes for + `id@version`.) +- **Publish only from main.** The job asserts the release commit is on `main`. - **`SCHEMA_VERSION` MAJOR is the compat gate.** Client and binary must agree on the wire-contract MAJOR or `startSidecar` throws `VersionMismatchError`. Bump the npm package and re-publish the binary together. - **Best-effort Intel.** `darwin-x64` builds on `macos-26-intel`, then is verified on - `macos-15-intel`. If it fails or is missing, it's **dropped** and the other 3 ship — - a loud `::warning::` + job summary, and Intel users get a clear "no binary for - darwin-x64" install error (never a placeholder/silent one). -- **Fetch-verify is the real gate.** Even after `/publish`, CI re-fetches every object - through the npm `fetch` CLI and checks bytes-hash-to-lock (with bounded retry for - Cloudflare edge propagation) before `npm publish`. -- **Website is rebuilt, not patched.** The hub pages build from the live `index.json`; - the publish job triggers `deploy_website.yml` on `main`. + `macos-15-intel`. If it fails/absent it's **dropped** (3-platform release) with a + loud `::warning::`; Intel users get a clear "no binary for darwin-x64" install + error, never a placeholder one. +- **Fetch-verify is the real gate.** After `/publish`, CI re-fetches every object via + the npm `fetch` CLI and checks bytes-hash-to-lock (bounded retry for Cloudflare + edge propagation) before `npm publish`. +- **Website is rebuilt, not patched.** Hub pages build from live `index.json`; the + publish job triggers `deploy_website.yml` on `main`. (A generic per-agent + auto-redeploy is still being wired — see the guide's Verify step.) - **Manual fallback:** `hub/agents/python//packaging/HUB-UPLOAD.md` documents the - by-hand rclone path to the `gaia-hub` bucket — it produces the identical objects + - lock as CI, so a hand-upload and a CI release are interchangeable. + by-hand rclone path to the `gaia-hub` bucket — identical objects + lock as CI. ## Reference files -- `.github/workflows/release_agent_email.yml` — the canonical release workflow (read - its header comments — they document the whole contract). +- `.github/workflows/release_agent_email.yml` — the canonical release workflow (its + header comments document the whole contract). +- `docs/guides/hub-publishing.mdx` — the author guide (manifest, versioning, wheel + + PR publish routes). The overlap's source of truth. - `hub/agents/python/email/gaia-agent.yaml` — manifest reference. - `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md,CHANGELOG.md}` — client package reference. - `hub/agents/python/email/packaging/{freeze,smoke_test,publish_to_r2,gen_binaries_lock}.py`, `HUB-UPLOAD.md` — packaging + publish tooling. - `workers/agent-hub/{README.md,schemas/manifest.schema.json,src/}` — the Worker, the - `/publish` contract, and the manifest schema. + `/publish` contract, and the server-side aggregate schema. From a3c3946f5996c4af0e9caca34bf375d9f1959da8 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 13:26:10 -0700 Subject: [PATCH 3/6] docs(skill): precision fixes from deep code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three-surface verification (cli_agent.py, release_agent_email.yml + packaging scripts, npm client + agent-hub Worker) confirmed the author guide and the release code are correct and mutually consistent; corrected the skill where it drifted from the code: - gen_binaries_lock.py is generic — only freeze.py (NAME) and publish_to_r2.py (executable + email-agent- prefix) hardcode email; HUB_PREFIX is workflow env - SHA-256 is hashed locally AND server-side and asserted equal before the lock is written (not 'server computes, client trusts') - the Worker 409s per filename (head(), not byte-compare); the publish script verifies the stored hash to distinguish idempotent re-upload from a real conflict - note the GAIA_HUB_TOKEN -> AGENT_HUB_PUBLISH_TOKEN env mapping - note the CI smoke test passes on 502/timeout (boots the route; no LLM in CI) --- .claude/skills/agent-hub-release/SKILL.md | 28 ++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md index 8a6d2fdc2..af970c775 100644 --- a/.claude/skills/agent-hub-release/SKILL.md +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -117,6 +117,8 @@ matching `CHANGELOG.md` entry in the same PR. `binaries.lock.json`'s `agentVersi core `publish.yml`. 4. **Build stage** freezes on 4 platforms — `win32-x64`, `darwin-arm64`, `linux-x64` **required**; `darwin-x64` (Intel) **best-effort** — smoke-tests each, hashes it. + (The smoke test proves the binary boots and the REST route answers; with no + Lemonade in CI it does **not** exercise real LLM triage — a 502/timeout passes.) 5. **Approve the gate.** The `publish` job pauses on the `agent-publish` environment until a maintainer approves; the publish token isn't readable until then. 6. **Publish stage** (atomic): POST every binary to `/publish` → regenerate @@ -131,9 +133,12 @@ exists; `/publish` is a verified 409 no-op for identical bytes. 1. **Scaffold** the normal agent first (`gaia agent init`, per the guide), then add `packaging/` and the `hub/agents/npm/agent-/` client. Mirror email. -2. **Adapt the packaging scripts.** `freeze.py`, `publish_to_r2.py`, and - `gen_binaries_lock.py` currently **hardcode the `email-agent` artifact name + - paths** — copy + parameterize them for ``. +2. **Adapt the packaging scripts.** `freeze.py` (its `NAME = "email-agent"` constant) + and `publish_to_r2.py` (the executable name + the `email-agent-` filename prefix it + parses) **hardcode `email-agent`** — copy + parameterize them for ``. + `gen_binaries_lock.py` is already generic (driven by `published.json` + manifest + `id`); the `agents/email` hub prefix lives in the workflow's `HUB_PREFIX` env, not a + script. 3. **Copy the workflow** `release_agent_email.yml` → `release_agent_.yml` and change: `PKG_DIR`, `MANIFEST`, `README`, `FREEZE_DIST`, `HUB_PREFIX` (`agents/`), the tag trigger (`agent-pkg--*`), the artifact/frozen names, @@ -156,7 +161,8 @@ Per the workflow header (`release_agent_email.yml`), these are **maintainer setu - **Secret `GAIA_HUB_TOKEN`** — Agent Hub Bearer token matching an entry in the Worker's `PUBLISH_TOKENS`, scoped to the agent's `author`. Define it as an **environment** secret on `agent-publish` (not a repo secret) so it's unreadable - until the gate is approved. + until the gate is approved. (The workflow maps it into the publish script's + `AGENT_HUB_PUBLISH_TOKEN` env var — same token, different name inside the script.) - **Var `GAIA_HUB_BASE_URL`** — public Worker origin for downloads + the lock `baseUrl` (default `https://hub.amd-gaia.ai`). - **Var `GAIA_HUB_PUBLISH_URL`** — the Worker's **workers.dev** URL for uploads. The @@ -167,10 +173,11 @@ Per the workflow header (`release_agent_email.yml`), these are **maintainer setu ## Invariants & gotchas -- **Immutable per filename.** Re-publishing identical bytes = idempotent 409 no-op; - different bytes under a published name **fail loudly**. Fix a bad release with a - new version — never an overwrite. (Same immutability the guide describes for - `id@version`.) +- **Immutable per filename.** The Worker `409`s on any re-POST of an existing + filename (it keys on the filename via `head()`, not a byte-compare). The publish + script then re-fetches and hashes the stored object: **identical bytes → idempotent + no-op**, a **hash mismatch → fail loudly**. Fix a bad release with a new version — + never an overwrite. (Same immutability the guide describes for `id@version`.) - **Publish only from main.** The job asserts the release commit is on `main`. - **`SCHEMA_VERSION` MAJOR is the compat gate.** Client and binary must agree on the wire-contract MAJOR or `startSidecar` throws `VersionMismatchError`. Bump the npm @@ -179,6 +186,11 @@ Per the workflow header (`release_agent_email.yml`), these are **maintainer setu `macos-15-intel`. If it fails/absent it's **dropped** (3-platform release) with a loud `::warning::`; Intel users get a clear "no binary for darwin-x64" install error, never a placeholder one. +- **SHA-256 provenance.** `publish_to_r2.py` hashes each binary locally and the Worker + hashes it server-side; the script **asserts they match** on the `201` before that + (local, server-verified) hash is written to `binaries.lock.json`. The lock hash is + then the gate the npm `fetch` CLI enforces on download (`PlatformError` on a + placeholder, `IntegrityError` on a mismatch). - **Fetch-verify is the real gate.** After `/publish`, CI re-fetches every object via the npm `fetch` CLI and checks bytes-hash-to-lock (bounded retry for Cloudflare edge propagation) before `npm publish`. From 3affef5b111af97ce858c9dbdb0a478e04c11237 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 14:16:41 -0700 Subject: [PATCH 4/6] docs(skill): frame CHANGELOG.md as a release setup step, not an existing file The agent-email npm package ships no CHANGELOG.md and doesn't list one in package.json files, so the skill's CHANGELOG references were asserting a fact that isn't true. Reword each to instruct the releaser to create the file and add it to the files array when cutting a release. --- .claude/skills/agent-hub-release/SKILL.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md index af970c775..cb808e9f1 100644 --- a/.claude/skills/agent-hub-release/SKILL.md +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -66,7 +66,7 @@ per the guide), a sidecar agent adds: | `hub/agents/python//packaging/` | `freeze.py`, `smoke_test.py`, `server.py`, `gen_binaries_lock.py`, `publish_to_r2.py`, `HUB-UPLOAD.md` (manual fallback) | | `hub/agents/npm/agent-/package.json` | ESM-only client; `exports` `.` (Node) — and `./client` (browser-safe) once **#1773** lands | | `hub/agents/npm/agent-/binaries.lock.json` | platform → artifact + **sha256** + size + `baseUrl` (placeholders until the first release) | -| `hub/agents/npm/agent-/README.md` (+ `CHANGELOG.md`) | client docs; CHANGELOG is recommended (Keep a Changelog), **not** required by publish | +| `hub/agents/npm/agent-/README.md` | client docs. Optionally create a `CHANGELOG.md` (Keep a Changelog) and add it to `package.json`'s `files` array yourself — recommended, but **not** required by publish | | `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser, #1773), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | | `.github/workflows/release_agent_.yml` | the tag-triggered release (copy of `release_agent_email.yml`) | @@ -98,17 +98,20 @@ identical: Bump the Python side with **`gaia agent version patch|minor|major`** (it rewrites `gaia-agent.yaml` + `pyproject.toml` + `__init__.py` together), then **manually sync -the npm `package.json`** to match — the release tooling checks they agree. Add the -matching `CHANGELOG.md` entry in the same PR. `binaries.lock.json`'s `agentVersion` +the npm `package.json`** to match — the release tooling checks they agree. If you +keep a changelog, create or update `CHANGELOG.md` (and list it in `package.json`'s +`files` array) with a matching entry in the same PR. `binaries.lock.json`'s `agentVersion` + `baseUrl` are **regenerated by CI** — don't hand-edit them for a release. ## Cutting a release (existing agent) 1. **Version-bump PR → main.** `gaia agent version `, sync the npm - `package.json`, add the CHANGELOG entry, merge to `main` (the workflow asserts the + `package.json`, create/update `CHANGELOG.md` (adding it to `package.json`'s `files` + array) with the new entry, merge to `main` (the workflow asserts the release commit is a `main` ancestor — publishing is allowed only from main). 2. **Pre-flight** in `hub/agents/npm/agent-/`: `npm ci && npm run build && npm - test`, and `npm pack --dry-run` to confirm `README.md`/`CHANGELOG.md` ship. + test`, and `npm pack --dry-run` to confirm `README.md` (and `CHANGELOG.md`, if you + created one) ship. 3. **Tag from main** (or `workflow_dispatch` with the version): ```bash git tag agent-pkg--v && git push origin --tags @@ -207,8 +210,9 @@ Per the workflow header (`release_agent_email.yml`), these are **maintainer setu - `docs/guides/hub-publishing.mdx` — the author guide (manifest, versioning, wheel + PR publish routes). The overlap's source of truth. - `hub/agents/python/email/gaia-agent.yaml` — manifest reference. -- `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md,CHANGELOG.md}` — - client package reference. +- `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md}` — + client package reference (no `CHANGELOG.md` ships yet — add one per the steps above + when you cut a release). - `hub/agents/python/email/packaging/{freeze,smoke_test,publish_to_r2,gen_binaries_lock}.py`, `HUB-UPLOAD.md` — packaging + publish tooling. - `workers/agent-hub/{README.md,schemas/manifest.schema.json,src/}` — the Worker, the From f8cf730ca1d73733bf342a26a1b615eb28a53221 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 14:32:36 -0700 Subject: [PATCH 5/6] =?UTF-8?q?docs(skill):=20agent-hub-release=20?= =?UTF-8?q?=E2=80=94=20#1773=20has=20landed=20(past=20tense)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The email epic (#1771/#1773/#1776) merged to main: the npm package is 0.2.0 with the ./client browser entry shipping today. Update the skill's 'once #1773 lands' framing to 'landed in #1773'. --- .claude/skills/agent-hub-release/SKILL.md | 24 ++++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md index cb808e9f1..43ab4471a 100644 --- a/.claude/skills/agent-hub-release/SKILL.md +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -64,16 +64,16 @@ per the guide), a sidecar agent adds: | Path | Role | |------|------| | `hub/agents/python//packaging/` | `freeze.py`, `smoke_test.py`, `server.py`, `gen_binaries_lock.py`, `publish_to_r2.py`, `HUB-UPLOAD.md` (manual fallback) | -| `hub/agents/npm/agent-/package.json` | ESM-only client; `exports` `.` (Node) — and `./client` (browser-safe) once **#1773** lands | +| `hub/agents/npm/agent-/package.json` | ESM-only client; `exports` `.` (Node) and `./client` (browser-safe, client-only — landed in **#1773**) | | `hub/agents/npm/agent-/binaries.lock.json` | platform → artifact + **sha256** + size + `baseUrl` (placeholders until the first release) | -| `hub/agents/npm/agent-/README.md` | client docs. Optionally create a `CHANGELOG.md` (Keep a Changelog) and add it to `package.json`'s `files` array yourself — recommended, but **not** required by publish | -| `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser, #1773), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | +| `hub/agents/npm/agent-/README.md` (+ `CHANGELOG.md`) | client docs; CHANGELOG is recommended (Keep a Changelog), **not** required by publish | +| `hub/agents/npm/agent-/src/` | `client.ts`, `client-entry.ts` (browser entry, landed in #1773), `fetch.ts`, lifecycle, `types.ts`, `errors.ts`, `cli.ts` | | `.github/workflows/release_agent_.yml` | the tag-triggered release (copy of `release_agent_email.yml`) | Notes: - **The npm client is ESM-only** (`"type": "module"`). Node consumers use the `.` entry (fetch + spawn); browser/Electron renderers use `./client` (client-only, - zero Node built-ins) — added by **#1773**, documented by **#1776**. + zero Node built-ins) — landed in **#1773**, documented by **#1776**. - **`binaries.lock.json` ships placeholder hashes** until the first real release. While a hash is a placeholder the fetch CLI is **fail-loud** — a bad binary can never be fetched. For local dev, point the lifecycle helpers at a locally-frozen @@ -98,20 +98,17 @@ identical: Bump the Python side with **`gaia agent version patch|minor|major`** (it rewrites `gaia-agent.yaml` + `pyproject.toml` + `__init__.py` together), then **manually sync -the npm `package.json`** to match — the release tooling checks they agree. If you -keep a changelog, create or update `CHANGELOG.md` (and list it in `package.json`'s -`files` array) with a matching entry in the same PR. `binaries.lock.json`'s `agentVersion` +the npm `package.json`** to match — the release tooling checks they agree. Add the +matching `CHANGELOG.md` entry in the same PR. `binaries.lock.json`'s `agentVersion` + `baseUrl` are **regenerated by CI** — don't hand-edit them for a release. ## Cutting a release (existing agent) 1. **Version-bump PR → main.** `gaia agent version `, sync the npm - `package.json`, create/update `CHANGELOG.md` (adding it to `package.json`'s `files` - array) with the new entry, merge to `main` (the workflow asserts the + `package.json`, add the CHANGELOG entry, merge to `main` (the workflow asserts the release commit is a `main` ancestor — publishing is allowed only from main). 2. **Pre-flight** in `hub/agents/npm/agent-/`: `npm ci && npm run build && npm - test`, and `npm pack --dry-run` to confirm `README.md` (and `CHANGELOG.md`, if you - created one) ship. + test`, and `npm pack --dry-run` to confirm `README.md`/`CHANGELOG.md` ship. 3. **Tag from main** (or `workflow_dispatch` with the version): ```bash git tag agent-pkg--v && git push origin --tags @@ -210,9 +207,8 @@ Per the workflow header (`release_agent_email.yml`), these are **maintainer setu - `docs/guides/hub-publishing.mdx` — the author guide (manifest, versioning, wheel + PR publish routes). The overlap's source of truth. - `hub/agents/python/email/gaia-agent.yaml` — manifest reference. -- `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md}` — - client package reference (no `CHANGELOG.md` ships yet — add one per the steps above - when you cut a release). +- `hub/agents/npm/agent-email/{package.json,binaries.lock.json,README.md,CHANGELOG.md}` — + client package reference. - `hub/agents/python/email/packaging/{freeze,smoke_test,publish_to_r2,gen_binaries_lock}.py`, `HUB-UPLOAD.md` — packaging + publish tooling. - `workers/agent-hub/{README.md,schemas/manifest.schema.json,src/}` — the Worker, the From 83c9906cccaa84f60fdee9147d5e8d2e26704075 Mon Sep 17 00:00:00 2001 From: Kalin Ovtcharov Date: Fri, 19 Jun 2026 14:39:22 -0700 Subject: [PATCH 6/6] =?UTF-8?q?docs(skill):=20agent-hub-release=20?= =?UTF-8?q?=E2=80=94=20make=20the=20version-sync=20direction=20explicit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gaia agent version bumps from the gaia-agent.yaml base and ignores the npm package.json value, so when the two are already out of sync (the email agent is live in exactly this state: package.json 0.2.0, gaia-agent.yaml 0.1.0) a blind patch bumps the yaml, not npm — the manual npm sync is what reconciles them. Spell that out so a releaser doesn't assume the bump aligns all three. --- .claude/skills/agent-hub-release/SKILL.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude/skills/agent-hub-release/SKILL.md b/.claude/skills/agent-hub-release/SKILL.md index 43ab4471a..fc64e9820 100644 --- a/.claude/skills/agent-hub-release/SKILL.md +++ b/.claude/skills/agent-hub-release/SKILL.md @@ -97,8 +97,12 @@ identical: 3. `hub/agents/python//gaia-agent.yaml` → `version` Bump the Python side with **`gaia agent version patch|minor|major`** (it rewrites -`gaia-agent.yaml` + `pyproject.toml` + `__init__.py` together), then **manually sync -the npm `package.json`** to match — the release tooling checks they agree. Add the +`gaia-agent.yaml` + `pyproject.toml` + `__init__.py` together — bumping **from the +`gaia-agent.yaml` value**, ignoring whatever the npm `package.json` currently says), +then **manually set the npm `package.json`** to that same version — the release +tooling checks all three agree. (Heads up: they can already be out of sync — e.g. a +package.json ahead of `gaia-agent.yaml` — so a blind `patch` bumps the yaml base, not +the npm value; the manual npm sync is what actually reconciles them.) Add the matching `CHANGELOG.md` entry in the same PR. `binaries.lock.json`'s `agentVersion` + `baseUrl` are **regenerated by CI** — don't hand-edit them for a release.