Scope
Three related bodies of work over one substrate (Agent Hub Worker + R2 + catalog):- Part 1 — Publish Agent UI to the Agent Hub (R2 + npm) — make the Agent UI itself a Hub package the way
emailis (it’s anapp-type unit). - Part 2 — In-app Agent Hub + dynamic install — let the running Agent UI browse the Hub and install other published units on demand.
- Multi-component model — generalize the Hub from agents-only to
app · component · agent(see the section below); this reshapes the manifest, catalog, and install paths used by Parts 1–2.
amd-gaia wheel’s release train:
the app, each agent, and the Hub catalog can ship on their own cadence and
SemVer line. A user can update one agent (or the UI shell) without a full GAIA
release, the Worker’s per-version immutable artifacts give every release a
stable, pinnable URL, and min_gaia_version in each manifest lets the catalog
gate compatibility instead of forcing lockstep upgrades. Same machinery that
publishes the email agent independently today — extended to the UI and to
in-app installs.
The Hub is multi-component (app · component · agent)
The Agent Hub is not an agents-only registry. It hosts multiple kinds of publishable units, and the Agent UI is one of them:
This resolves the earlier “is the UI an agent?” tension. The Agent UI is an
app, not an agent — so it does not get co-listed in the same installable-
agent list it renders in-app (which would be circular). It lives in its own Hub
lane. Concretely, the multi-component model adds these requirements on top of
Parts 1–2:
- Manifest needs a
typediscriminator.gaia-agent.yamlis agent-shaped today (id/name/version/language/category/interfaces). Generalize to a top-leveltype: app | component | agent(the manifest/file naming likely generalizes beyondgaia-agent.yaml). Per-type required fields differ — anappcarries installers + an update channel; acomponentcarries a library artifact + a consumption contract (API/version range); anagentcarries the binary/wheel + interfaces it already has. - Catalog (
index.json) needs the type field + lanes.IndexEntrygainstype; the website and in-app Hub filter/segment by it (Apps · Components · Agents) instead of one flat list. - Worker validation forks by type.
manifest.tsvalidates each type’s schema;/publishaccepts app/component artifacts, not just agent binaries. - Install/run semantics differ by type.
app→ run the platform installer;agent→ land + register + run in the host;component→ resolve as a dependency (version range), not a launchable. The in-app “Install” action must branch ontype(you don’t “launch” RAG; you enable/attach it). - Dependency edges between types. Agents/apps can require components (an
agent may need the
ragormemorycomponent at a version range). This is a new dependency-resolution surface the catalog + installer must model — beyond the per-agentrequirements/REQUIRED_CONNECTORSthat exist today.
app (Agent UI) and agent install paths; the
component lane (RAG/memory as independently versioned Hub units) is a third
track that shares the same Worker/R2/catalog substrate and is scoped at the same
altitude here, with its own decisions called out in the inventory (section E).
Extension workstream — making the registry multi-component
The Hub is agent-shaped end-to-end today. Extending it touches two validators, the Worker catalog/storage, the Python hub SDK, and both UIs. Concrete changes, grounded in the current code:-
Manifest schema — add a
typediscriminator (both validators). The canonical validator issrc/gaia/hub/manifest.py(VALID_LANGUAGES, per-language required blocks —cppalready requires acpp.binariesmap, the precedent for per-type required blocks); the Worker’sworkers/agent-hub/src/manifest.tsis a gate-only mirror. Addtype: app | component | agent(defaultagentwhen absent so all 15 existing manifests stay valid) with per-type required sections:app:→ installers per platform + update channel + min OS;component:→ library artifact + a consumption contract (capability id + API/SemVer range itprovides);agent:→ today’spython:/cpp:+interfaces. Decide whether the file staysgaia-agent.yamlor generalizes (e.g.gaia-hub.yaml); keep the old name as an alias for back-compat.
-
Catalog schema —
workers/agent-hub/src/{types,catalog}.ts. AddtypetoIndexEntry; bumpCatalogIndex.schema_version1 → 2 (currently hardcodedschema_version: 1incatalog.ts:150). Keep theagents[]field as a back-compat alias and/or addunits[]; readers default a missingtypetoagent.VersionEntry.artifacts[]already stores multiple artifacts per version (it carries the email agent’s 4 platform binaries today), so the app’s multi-file release reuses that — no new per-version array needed at the manifest layer. -
R2 storage namespace —
workers/agent-hub/src/storage.ts.AGENTS_PREFIX = "agents/"andlistAgentIds()parse an agent-only layout. Decide: (a) keepagents/and add siblingapps/+components/prefixes with a typedlistUnits(), or (b) a singleunits/<type>/<id>/namespace. Either way leave existingagents/<id>/…keys in place (the 15 published agents) and make listing tolerate both. Decision. -
Worker validation + publish —
manifest.ts,publish.ts. Validate per type;/publishaccepts app installers and component libraries, not just wheels/binaries. Server-side SHA-256 + immutability are type-agnostic and carry over unchanged. -
Python hub SDK —
src/gaia/hub/{installer,lifecycle,native_launcher,compatibility,packager,publisher,catalog}.py. These all assume “agent”. Branch by type:installer.py→ app: run the platform installer; agent: land + register + run; component: resolve as a dependency (not launchable).compatibility.py→ add component version-range resolution.packager.py/publisher.py→ learn app / component artifact kinds. -
Consumption / dependency resolution (the new hard problem).
A
componentdeclares what itprovides; an app/agent declares what itrequires(component id + version range) — a layer above today’s per-agentrequirements/REQUIRED_CONNECTORS. The resolver must answer: where the component physically lives (host venv vs. shared store), how conflicts between two agents needing different ranges are handled, and lazy vs. eager install. This is the least-defined contract and the one to design first for thecomponentlane. Decision. -
Surfaces — website + in-app Hub + CLI.
Segment the catalog into Apps · Components · Agents lanes (website
hub/index.astro+ the in-app Hub page); generalize the plannedgaia agent …commands towardgaia hub …so non-agent types are addressable. -
Back-compat + migration.
typedefaults toagent;schema_versionbumps with reader tolerance; existingagents/R2 keys and published manifests are untouched. No re-publish of the current catalog required.
Part 1: Publish Agent UI to the Agent Hub (R2 + npm)
Status: scoping only — no code yet. Goal: makeagent-ui a first-class Agent Hub package the same way email
is — installers published to R2 through the Agent Hub Worker, a thin npm
wrapper that fetches + SHA-verifies artifacts from R2, and R2 as the primary
download + auto-update source.
The gap today
Locked decisions (from scoping):
- Mirror email exactly —
@amd-gaia/agent-uibecomes a thin wrapper that downloads its artifact from R2 and verifies againstbinaries.lock.json. - R2 becomes primary —
hub.amd-gaia.aiis the primary download and the electron-updater feed; GitHub Releases is demoted to (optional) mirror.
The core mismatch to resolve first
The email pattern is “fetch one binary, spawn it as a sidecar.” Agent UI is a GUI desktop app shipped as platform installers — you can’t “spawn an installer as a sidecar,” and a release is N files per platform, not one:win32-x64→…-x64-setup.exe+.exe.blockmap+latest.ymldarwin-arm64→…-arm64.dmg+…-arm64.zip(updater needs the zip) +latest-mac.ymllinux-x64→…-x64.AppImage+…-x64.deb+latest-linux.yml
binaries.lock.jsonis single-file-per-platform. It must be extended to a list of files per platform (artifact + blockmap + update manifest + zip), or the wrapper needs a different manifest shape. Schema change required.- What does the npm wrapper actually do? Recommended: the
gaia-uibin downloads the platform installer from R2, SHA-verifies it against the lock, then hands off to the OS installer (or launches a portable bundle). Decide installer-launch vs. portable-bundle-launch — see Open Questions.
Workstreams
1. Hub manifest + package layout
- Author the manifest for agent-ui (id, version
0.21.x, platforms,type: app, installers/update-channel block — see the multi-component model). Agent-UI is not a Python freeze, so it does not belong inhub/agents/python/. Proposed home:hub/agents/desktop/agent-ui/(manifest + lock + packaging scripts) or colocated withsrc/gaia/apps/webui/. - Do NOT add agent-ui to
setup.py[agents]— that list drivesgaia-agent-*wheel builds (util/list_agent_packages.py). Agent-UI has no wheel; keep it out and document the divergence so the publish-validation tests don’t expect one.
2. Agent Hub Worker (workers/agent-hub/) changes
- Mutable “latest” channel vs. immutability. The Worker guarantees
immutable per-filename artifacts (409 on re-upload). electron-updater needs a
stable, mutable feed URL (
…/latest.yml) that re-points each release. This is a direct conflict with the current model and the single biggest Worker design decision: add a mutable channel pointer (e.g.agents/agent-ui/stable/latest*.ymlrewritten on publish) while keeping the versioned artifacts immutable. - Confirm/extend GET passthrough so
latest.yml,latest-mac.yml,latest-linux.yml, and.blockmapfiles are served at the URLs electron-updater expects. manifest.tsvalidation must accept theappmanifest type (see §E / E8).- New/extended Worker tests for desktop artifacts + channel rewrite.
3. electron-builder / auto-updater repoint
electron-builder.yml: changepublish.provider: github→genericwithurlpointing at the R2 channel path; this bakes the feed intoapp-update.yml.- Ensure
latest*.yml+ blockmaps are generated and uploaded (the macOS.ziptarget already exists for this reason). services/auto-updater.cjsis currently a stub (“Phase F wires up the actual electron-updater logic”) — wiring it to the generic R2 feed is part of this work.- Code signing / notarization: installers fetched from R2 are the same bytes signed in CI today, so Gatekeeper/SmartScreen behavior is unchanged — but verify the signed artifacts (not unsigned intermediates) are what gets POSTed to R2.
4. Release workflow (release_agent_ui.yml)
- New workflow, tag namespace
agent-pkg-agent-ui-*(NOTv*— that fires corepublish.yml/build-installers.yml). - Reuse
build-installers.ymlviaworkflow_callto produce the signed installers + update manifests, then apublishjob:- POST all per-platform files to the Worker
/publish(reusepublish_to_r2.py— needs multi-file-per-platform support). - Regenerate
binaries.lock.jsonwith real hashes (extendgen_binaries_lock.pyfor the list-per-platform schema). - Rewrite the mutable
latest*.ymlchannel pointers. - Fetch-verify every published object before npm publish (email’s atomicity gate).
npm publishthe wrapper (OIDC trusted publishing,--provenance).
- POST all per-platform files to the Worker
agent-publishenvironment +GAIA_HUB_TOKEN, version triple-check (tag ↔package.json↔gaia-agent.yaml), main-ancestry guard — all mirrored fromrelease_agent_email.yml.
5. npm wrapper package — name collision
- “Mirror email exactly” means
@amd-gaia/agent-uibecomes the thin wrapper. But that name is already the published Electron-app package. Resolve:- Recommended: the Electron app stops being npm-published (its output is
installers in R2);
@amd-gaia/agent-uiis re-taken by the wrapper. Thegaia-uibin fetches the installer from R2, verifies vs lock, launches. - Alternative: wrapper takes a new name (
@amd-gaia/agent-ui-launcher) — diverges from “mirror exactly.”
- Recommended: the Electron app stops being npm-published (its output is
installers in R2);
- Wrapper TS (
cli.ts,fetch.ts,platform.ts,lifecycle.ts,errors.ts) templated fromhub/agents/npm/agent-email/, adapted for installer-launch and the multi-file lock.
6. Catalog, website, docs, tests
- agent-ui appears in Hub
index.json; the website/hub download page consumes it. - Docs: update
docs/deployment/ui.mdxand the agent-hub docs; add this plan to nav if desired. - Tests: Worker (publish + channel rewrite), wrapper fetch/integrity, a smoke test that launches the fetched app, and a test asserting the multi-file lock shape.
Open questions / risks
- Mutable auto-update channel vs. Worker immutability — must add a channel
pointer; decide path convention (
stable/, per-channel). - npm name collision on
@amd-gaia/agent-ui— confirm the app stops self-publishing under that name. - Installer-launch vs. portable-bundle-launch for the wrapper’s
gaia-uibin. binaries.lock.jsonmulti-file-per-platform schema extension (touchesgen_binaries_lock.py,fetch.ts, Worker types).- R2 egress / cost — installers are ~100–300 MB each vs. email’s ~90 MB binary.
- Signing/notarization path must point at signed artifacts before R2 upload.
Suggested phasing
- Phase 1 — Worker: serve desktop artifacts + mutable
latest*channel pointer. - Phase 2 — Pipeline:
release_agent_ui.ymlreusesbuild-installers.yml, publishes to R2, regenerates the (extended) lock, fetch-verifies. - Phase 3 — Clients: npm wrapper replaces the app as the published package; electron-updater repointed to the R2 generic feed.
- Phase 4 — Surface: catalog/website integration, docs, tests, baseline.
Part 2: In-app Agent Hub + dynamic install
Three requirements, one dependency chain: a hub page (browse) → an install action (click) → a dynamic-install runtime (import the unit). The front-half UI scaffolding already exists; the back-half install service does not.The install unit and path are not fixed to npm. The Hub is multi-component, so the runtime branches bytype(app/agent/component); for anagentthe choice between a uv-installed wheel and a native-binary sidecar is open (B2/B4). “npm wrapper” below is one candidate, not a settled decision.
What already exists (don’t rebuild)
The gap (confirmed): the in-app install path is a stub —
agent:install / agent:uninstall in agent-process-manager.cjs throw new Error("… not yet implemented"). The Agent UI does not import published npm
agents today.
Feature 1 — Thin app layer that installs agents on demand
Implement theagent:install / agent:uninstall service in the Electron main
process. Pipeline: resolve the unit from the catalog → fetch its artifact from R2
→ integrity-check (binaries.lock.json for the wrapper path) → land in
~/.gaia/agents/<id>/ (or the host venv for a wheel) → register → emit progress
to the renderer.
Key design decisions to settle:
- How to install the npm wrapper without a bundled npm CLI. The app bundles
uv(for Python), not Node/npm. Options: (a) fetch the npm tarball via the registry HTTP API and unpack in-process (no npm needed), or (b) bundle npm. Recommend (a) — a minimal tarball fetcher mirrors how the wrapper already fetches from R2. - Python wheel path vs native-binary path. Published agents currently ship
both a wheel and native frozen binaries. The user wants the npm-wrapper
unit. Decide whether the installed agent runs as a Python wheel (uv-install
into
~/.gaia/venv, register viagaia.agentsentry points) or as a native sidecar (the wrapper’s fetched binary, run byagent-process-manager.cjs). - Sidecar protocol mismatch.
agent-process-manager.cjsmanages stdio JSON-RPC / MCP children, but the frozen agent binaries expose a FastAPIapi_server(/health,/v1/...). Reconcile: either the wrapper exposes an MCP/stdio interface, or the process manager learns to superviseapi_serversidecars (HTTP health + routing). This is the single biggest runtime decision. - Registration after install. Write the
~/.gaia/agent-manifest.jsonnative entry and/or hot-register via the existingregister_from_dirpath so the agent appears in/api/agentswithout a backend restart. - Uninstall must stop any running sidecar, remove
~/.gaia/agents/<id>/, and de-register.
Feature 2 — In-app Hub mirror page
A React page in the renderer that mirrors the public Astro Hub. FetchGET {HUB_BASE_URL}/index.json, render the catalog, merge install state.
- Reuse the contract: port (or share)
website/src/data/catalog.tstypes + helpers (formatBytes,platformLabel,categoryLabel, sort order) and theAgentRow/FeaturedCard/ detail layout so the in-app page matches the site. - Merge installed ↔ catalog:
/api/agents(installed) ∪/index.json(catalog) via the existingagentHub.tshelpers → per-card state (installed / available / update_available), with an updates badge. - CORS (resolve — see B8): the Worker sets no explicit CORS headers today, so
a renderer
fetchofindex.jsonmay be blocked by Chromium. Either addAccess-Control-Allow-Originon the Worker GETs or proxy the fetch through the Electron main process. Cache the last-fetched catalog for offline browsing. - Config: catalog base URL (
hub.amd-gaia.ai) as a setting; honor the same channel/base as Part 1.
Feature 3 — Click an agent → auto-install
Wire the hub card’s Install button and the website’s deep link to the Feature-1 service.- In-app: card Install →
agentStore.installAgent(id)→agent:installIPC → install service → progress events stream intoAgentInstallDialog→ agent appears in the list on success. - Deep link bridge: register the
gaia://protocol (app.setAsDefaultProtocolClient+ single-instance + per-OS handlers) and handlegaia://hub/install/<id>(used by the website’s “Open in GAIA” button) → same install flow. This is what makes website → app one-click work. - Trust gate before install: show the agent’s
security_tier,permissions, andrequirementsand require explicit confirmation; refuse non-verifiedtiers without an override. Don’t silently install.
Cross-cutting dependencies & risks (Part 2)
- Depends on Part 1’s contract: there must be a published
@amd-gaia/agent-*wrapper +binaries.lock.json+ R2 artifacts to install, and the agent must appear inindex.json. Part 2 install is only as real as Part 1’s pipeline. - Sidecar protocol (api_server vs stdio MCP) — see Feature 1.
- No bundled npm — tarball-fetch-in-process vs bundling Node/npm.
- Protocol-handler security —
gaia://hub/install/<id>is web-triggerable; the trust gate (Feature 3) is the guard, not optional. - Disk/footprint — installed native agents are large; surface size from the
catalog (
download_size_bytes) and enforce a confirm. - Versioning/updates —
countUpdatesexists in the UI but the update apply path reuses the same install service; scope update-in-place explicitly.
Suggested phasing (Part 2)
- Phase A — Install service: implement
agent:install/agent:uninstall(tarball fetch → R2 verify → land → register → progress events). Headless-testable. - Phase B — Hub page: in-app catalog browse + install-state merge.
- Phase C — One-click + deep link: wire Install button and register
gaia://. - Phase D — Trust gate, updates, offline cache, uninstall polish.
Part 3: What’s missing for a fast, easy, polished experience
Audit of the current install/UX paths. Several things already exist (streamed bootstrap progress dialog, auto-update IPC, a full hardware scanner) — the gaps below are ranked by user impact, not effort.The #1 gap (underneath Parts 1–2): first use is minutes-long, not seconds
Part 1 can make the Electron installer instant, but the app is unusable until its Python backend finishes a cold bootstrap:backend-installer.cjs runs
uv pip install amd-gaia[ui], which pulls torch/scipy/numpy from PyPI
(~5–10 min), then gaia init downloads a ~4 GB model (~2–5 min). So a
“fast install” still lands the user on a multi-minute wait.
- Highest-leverage fix: ship a frozen/prebuilt backend instead of a
runtime pip install, so first-run is seconds and the PyPI/network failure mode
is gone. Caveat (verify first): the email freeze
(
hub/agents/python/email/packaging/freeze.py) gets to <90 MB by excluding torch/transformers/faiss — but the UI backend can’t, because[ui]directly declaresfaiss-cpu+sentence-transformers+torchandgaia.ui.serverboots faiss + sentence_transformers eagerly (setup.py:143, #845). So this is not a drop-in reuse of the email recipe. Real options, in order of leverage: (a) move embeddings to remote Lemonade so the backend can be slimmed and then frozen small; (b) ship a prebuilt venv / wheelhouse (no PyInstaller) — bigger artifact but avoids freezing torch/faiss native libs, which is notoriously fragile; (c) accept a large (~GB) frozen artifact. Pick before committing — the “<90 MB like email” expectation is wrong as written. - Measure the baseline: the “7–20 min” figure is an estimate, not a measured cold run. Time a real first-run on a clean machine before anchoring the roadmap to it.
- Model setup is silent + non-fatal:
gaia initruns in the background with no in-app visual, and a failure is swallowed (user lands on a terminal hint). Need an in-app model downloader with visible progress + a smaller default model option for the first run.
Hub-fit gaps (install correctness)
- No hardware pre-flight gating. The scanner exists (
/api/system/status: NPU/GPU/memory/disk), and the catalog carriesrequirements(min_memory_gb,npu,gpu_vram_gb,platforms) — but nothing checks one against the other before install/select. Add: block/warn “NPU required, not detected” / “needs 8 GB, you have 6” on the hub card and the install confirm. - No connector flow from install/select. Agents declare
REQUIRED_CONNECTORS(email → Google), and/api/connectorsexposesconfiguredstate — but the user only finds out at runtime via anAuthRequiredError. Add an inline “This agent needs Google — Connect” step in the install/first-run flow that launches the existing OAuth path, instead of a buried Settings page. - Permissions shown but not enforced. Catalog
permissions(e.g.gmail.send) should be surfaced at the trust gate and actually constrained. - Bundled vs. hub dedup + updates. Seeded bundled agents and hub-installed
agents share
~/.gaia/agents/<id>/; reserved IDs exist but the install/update/rollback path (countUpdatesis wired, apply is not) needs an explicit version-in-place + rollback story to avoid clobbering. - Catalog freshness/offline. Cache
index.json(ETag/If-None-Match) for fast, offline-tolerant browsing; show a “last synced” state.
Discoverability & onboarding gaps
- No onboarding wizard.
docs/plans/setup-wizard.mdxis still DRAFT; today first-run is a text hint telling users to rungaia init --profile chatin a terminal — a hard blocker for non-technical users. Replace with a guided first-run (scan hardware → pick/download model → optional connector → done). - Update banner not rendered.
auto-updater.cjsbroadcastsgaia:update:statusand has the native restart dialog, but no renderer component consumes it — wire a non-blocking “Update ready · Restart” banner. - Deep-link when app isn’t installed.
gaia://hub/install/<id>(website “Open in GAIA”) has no fallback — register the protocol (Part 2 Feature 3) and have the website detect “no app” → route to the download page.
Resilience & trust gaps
- Signed/notarized installers fetched from R2. Ensure the signed artifacts (not unsigned intermediates) are what’s published, or macOS Gatekeeper / Windows SmartScreen friction undoes the “easy install” goal.
- Resumable/retryable installs + rollback. Resume interrupted artifact
downloads, retry on transient R2 errors, and roll back a failed agent install
cleanly (don’t leave a half-written
~/.gaia/agents/<id>/). - One-click diagnostics on failure.
gaia diagnosticsalready bundles logs+system info — surface a “Copy diagnostics” button on every failure dialog (install, model, connector) so bug reports are actionable.
Priority order (recommendation)
- Frozen/prebuilt backend (kills the minutes-long first-use wait) — note this does not cleanly reuse the email freeze; pick the approach in C1 first.
- Guided onboarding + in-app model download (replaces the terminal hint).
- Hardware pre-flight + connector-on-install (correct, non-surprising installs).
- Update banner + offline catalog cache + diagnostics button (polish).
- Resumable installs + rollback + signing verification (resilience/trust).
Appendix: Consolidated gap inventory
Every gap surfaced during this investigation, in one checklist. Severity: Critical (blocks the whole goal) · High (blocks a stated requirement) · Medium (needed for a good experience) · Low (operational/polish). “Decision” = a design choice to settle before building, not just missing code.A. Publishing pipeline (Part 1 — R2 + npm)
B. In-app marketplace + dynamic install (Part 2)
C. First-use speed & onboarding (Part 3)
D. Cross-cutting trust, resilience & observability
E. Multi-component Hub model (app · component · agent)
Reference files
- In-app agent runtime:
src/gaia/apps/webui/services/{agent-process-manager,agent-seeder}.cjs,preload.cjs - Renderer agent state:
src/gaia/apps/webui/src/stores/agentStore.ts,src/utils/agentHub.ts,src/components/AgentInstallDialog.tsx - Backend registry + import:
src/gaia/agents/registry.py,src/gaia/ui/routers/agents.py - Catalog API + types:
workers/agent-hub/src/{index,catalog,manifest,types}.ts - Public Hub website (mirror target):
website/src/pages/hub/,website/src/data/catalog.ts,website/src/components/{AgentRow,FeaturedCard}.astro - Agent Hub plan:
docs/plans/agent-hub.mdx - Email release pipeline:
.github/workflows/release_agent_email.yml - Email packaging scripts:
hub/agents/python/email/packaging/{publish_to_r2,gen_binaries_lock,freeze,smoke_test}.py - Email npm wrapper:
hub/agents/npm/agent-email/(binaries.lock.json,src/{cli,fetch,platform,lifecycle}.ts) - Agent Hub Worker:
workers/agent-hub/(src/{index,publish,auth,manifest,catalog,storage}.ts) - Agent UI app:
src/gaia/apps/webui/{package.json,electron-builder.yml,bin/gaia-ui.cjs,services/auto-updater.cjs} - Current installer/publish CI:
.github/workflows/{build-installers,publish}.yml