Skip to content

Download prebuilt binaries via pel package manager (Phase A part 2) - #31

Open
JeroenSoeters wants to merge 21 commits into
mcp-prebuilt-distributionfrom
mcp-orbital-provisioning
Open

Download prebuilt binaries via pel package manager (Phase A part 2)#31
JeroenSoeters wants to merge 21 commits into
mcp-prebuilt-distributionfrom
mcp-orbital-provisioning

Conversation

@JeroenSoeters

@JeroenSoeters JeroenSoeters commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

What

Phase A part 2 — the "download, don't build" provisioning layer. The plugin now downloads a prebuilt formae-mcp and a matched formae via the pel package manager into a dedicated user tree ~/.formae-ai/opt (sudo-free) instead of compiling on the user's machine, publishes the formae-mcp opkg on a version tag, and makes /formae:upgrade pull the newer binary on version skew.

Stacked on #30 (mcp-prebuilt-distribution, the Go execution-context/formaebin/skew foundation). Base is that branch so this diff shows only the part-2 work; retarget to main once #30 merges. Neither merges to main until Phase A's definition-of-done — including the real install test below — is met, so main stays releasable.

Changes

  • scripts/provision.shprovision_pkg <pkg> <channel>: installs a pel package into ~/.formae-ai/opt with no sudo. Pre-creates ~/.formae-ai (works around orbital's one-level privilege walk), never passes a leading -- to pelmgr, idempotent fast-path.
  • justfile + Makefile — opkg build/publish targets mirroring the formae repo (ops opkg buildops publish --repo pel --channel {dev|stable}; channel routed by tag shape).
  • Opkgfile — package manifest (name = formae-mcp, version from the CI VERSION env).
  • .github/workflows/release.yml + package.yml — publish the opkg on a [0-9]* tag, 4-platform matrix, OIDC assume-role github-pel.
  • scripts/start-mcp.sh — rewritten to provision + exec; go build survives only under FORMAE_MCP_DEV=1. Retires compile-on-first-launch.
  • skills/upgrade/SKILL.md/formae:upgrade does a channel-aware, non-silent pull (managed tree via provision_pkg; classic /opt/pel handed back to the user).

Target layout

~/.formae-ai/opt/bin/{formae-mcp,formae}     downloaded, sudo-free
~/.formae-ai/opt/formae/plugins/             system/managed plugins

Testing

make test green; workflows YAML-validated; provision.sh/start-mcp.sh sh -n + dry-run verified (mkdir mitigation, no--- rule, idempotent fast-path, dev/normal branches). Reviewed task-by-task plus a whole-branch pass (one Critical caught and fixed: the missing Opkgfile).

Remaining (human-gated, before this can be considered done)

End-to-end verification that can't run without touching the prod hub / a dev agent:

  1. Push a -dev.N tag → confirm the formae-mcp opkg publishes to the pel dev channel (real check of Opkgfile + workflow + role).
  2. Install via the real harness command on a clean machine (dev channel) → confirm no build, binaries land in ~/.formae-ai/opt/bin, check_health works against a dev agent, and skew → /formae:upgrade pulls the newer binary.

Follow-ups (tracked)

  • pel-manager user-space install works today (PLA-450); the orbital one-level privilege-walk is mitigated here by pre-creating the parent (a proper orbital fix — walk to nearest existing ancestor — is optional).

Client identity

Commands issued through the MCP now send the CLI's client ID (~/.pel/formae/cli_client_id) as the Client-ID header instead of a fixed formae-mcp value, so the agent attributes them to the same client as the user's own formae runs. When the file does not exist yet (fresh install via the prebuilt download, formae never executed), the MCP runs formae --version once so formae creates it, and falls back to the old formae-mcp identity if it still cannot be read. Note: this is a one-time attribution shift; commands submitted before the upgrade remain recorded under formae-mcp.

…ged formae by path

- start-mcp.sh: read plugin version from .claude-plugin/plugin.json and store
  it in ~/.formae-ai/opt/.formae-mcp.plugin-version; force-reprovision formae-mcp
  when the marker is missing or differs from the current version, so a new plugin
  release replaces the stale binary instead of hitting the existence fast-path.
  The formae binary is intentionally left on the original fast-path (explicit
  upgrade only, pinned-CLI policy).
- skills/upgrade/SKILL.md: classify the install by checking the managed path
  (~/.formae-ai/opt/bin/formae) directly before falling back to which formae.
  The managed binary is exposed via FORMAE_BUNDLED_BIN and is not on PATH, so
  which formae fails to find it even when the managed tree is active.
… startup timeout

Codex starts sessions without waiting for MCP servers unless the server is
marked required, so on first launch (while the launcher downloads the
binaries) the formae tools are silently missing from the session. Make the
config.toml block the primary registration path and include required = true
plus startup_timeout_sec = 120; codex mcp add cannot set either field.
Each spins a clean container, follows the harness INSTALL doc (clone, skills
symlink, register scripts/start-mcp.sh), and proves the harness spawns the
launcher and the MCP connects: Codex via a non-interactive exec turn that
must complete a search_hub_plugins tool call (agentless, hub-backed), and
OpenCode via 'opencode mcp list' showing the server connected plus a direct
JSON-RPC tools/list through the launcher. Both assert the binaries were
downloaded, not built (no Go toolchain in the image).
@JeroenSoeters

Copy link
Copy Markdown
Contributor Author

Codex + OpenCode install docs are now verified in clean containers (dev channel), via the two new test scripts:

  • OpenCode (test/clean-install-opencode.sh): follows .opencode/INSTALL.md verbatim; opencode mcp list cold-spawns start-mcp.sh, waits out the first-run binary download, and reports formae connected; a direct JSON-RPC tools/list through the launcher serves 50 tools. No doc changes needed.
  • Codex (test/clean-install-codex.sh): a non-interactive codex exec turn completes a search_hub_plugins tool call against the hub (agentless). This surfaced a real gap: Codex does not wait for MCP servers at session start unless the server is marked required = true, so the first session (during the one-time download) silently had no formae tools; codex mcp add cannot set that field. .codex/INSTALL.md now makes the config.toml block (with required = true + startup_timeout_sec = 120) the primary registration path. Reproduced deterministically with an artificially slow server: default config -> tools missing; with the two settings -> tool call succeeds.

Both tests assert the binaries were downloaded, not built (no Go toolchain in the image). Still open: the agent-backed check_health + interactive skew/upgrade test needs a live dev formae agent (cut-over pre-checklist).

Codex CLI >=0.148 installs plugins from Claude-format marketplaces:
`codex plugin marketplace add` reads .claude-plugin/marketplace.json, and
plugin manifests are discovered at .codex-plugin/plugin.json (falling back to
.claude-plugin/plugin.json).

Add .codex-plugin/plugin.json declaring the skills dir and pointing at
.codex-plugin/mcp.json for the MCP server config. The command is a relative
path with an explicit cwd rather than ${CLAUDE_PLUGIN_ROOT}/${PLUGIN_ROOT}
interpolation, which is currently broken upstream — a relative command
resolves fine once cwd is set to the plugin's own root. required=true plus a
120s startup_timeout_sec make the first session wait for the one-time prebuilt
binary download instead of silently starting without the formae tools.

The MCP config lives inside .codex-plugin/ (not a root .mcp.json) so Claude
Code never auto-loads it as its own server.
Codex CLI >=0.148 can install the formae plugin the same way it installs
any Claude-format marketplace plugin:

    codex plugin marketplace add platform-engineering-labs/formae-marketplace
    codex plugin add formae@formae-marketplace

No clone, no symlink, no config.toml editing, nothing on PATH. The plugin
manifest carries required=true and a 120s startup_timeout_sec, so the first
session waits for the one-time prebuilt-binary download instead of silently
starting without formae tools; later sessions start instantly.

Keep the old clone + symlink + config.toml flow as a documented fallback for
Codex versions that predate plugin marketplace support.
Rewrite the clean-container Codex test to install the plugin the way an end
user now does: build a local marketplace naming the plugin "formae" sourced
from a clone of this repo, then run the same two commands documented in
.codex/INSTALL.md (marketplace add + plugin add). Pin the test channel by
injecting FORMAE_MCP_CHANNEL into the cloned plugin's .codex-plugin/mcp.json
before installing, mirroring how the other clean-install scripts avoid
touching stable.

Proves the install end to end: no Go toolchain in the image, codex plugin list
shows formae installed, a cold codex exec turn drives a real
search_hub_plugins tool call through the spawned MCP server, and the
downloaded formae-mcp/formae binaries are executable in ~/.formae-ai/opt.

Also exercises marketplace upgrade + remove/add as a non-fatal probe of the
update flow, to decide whether INSTALL.md can document it as working.
@JeroenSoeters

Copy link
Copy Markdown
Contributor Author

Codex plugin install now works end to end

Codex CLI >=0.148 reads Claude-format marketplaces (.claude-plugin/marketplace.json) and discovers a plugin's manifest at .codex-plugin/plugin.json. This adds that manifest shape so installing the formae plugin in Codex is the same two commands as Claude Code:

codex plugin marketplace add platform-engineering-labs/formae-marketplace
codex plugin add formae@formae-marketplace

What changed and why

  • .codex-plugin/plugin.json + .codex-plugin/mcp.json: the MCP server command is a relative path (./scripts/start-mcp.sh) with an explicit "cwd": "./", not ${CLAUDE_PLUGIN_ROOT}/${PLUGIN_ROOT} interpolation, which is currently broken upstream. Codex resolves a relative cwd against the plugin's own cache root, so this works without any variable substitution.
  • required: true + startup_timeout_sec: 120: without required, Codex starts a session without waiting for a slow-starting MCP server, so the very first session (while the prebuilt formae-mcp/formae binaries download) would silently have no formae tools. The 120s budget covers that one-time download; warm starts are instant.
  • The MCP config lives inside .codex-plugin/ (not a root .mcp.json) so Claude Code never auto-loads it as its own server.
  • .codex/INSTALL.md rewritten to the two-command flow as primary, with the old clone + symlink + config.toml flow kept as a labeled fallback for older Codex versions.
  • test/clean-install-codex.sh rewritten to install the plugin the same way a user does (local marketplace -> marketplace add -> plugin add), then proves connectivity with a real codex exec tool call.

Test evidence (clean container, cold)

codex version: codex-cli 0.149.0
...
Added marketplace `formae-marketplace` from /root/mkt.
Added plugin `formae` from marketplace `formae-marketplace`.

PLUGIN                     STATUS              VERSION  PATH
formae@formae-marketplace  installed, enabled  0.8.0    /root/mkt/formae

--- codex exec: cold-spawn the MCP and call search_hub_plugins ---
{"type":"item.completed","item":{"id":"item_1","type":"mcp_tool_call","server":"formae","tool":"search_hub_plugins","arguments":{"query":"aws"},"result":{...},"error":null,"status":"completed"}}
cold codex exec wall-clock: 23s

PASS: Codex installed the plugin from the marketplace, spawned start-mcp.sh,
binaries downloaded (no build, no sudo), formae tool call completed
-rwxr-xr-x 1 root root 138879936 Aug 21 13:40 formae
-rwxr-xr-x 1 root root  11921092 Aug 21 13:40 formae-mcp

Also exercised the update flow in the same run (codex plugin marketplace upgrade + remove + add, then a warm probe): all steps succeeded and the warm probe completed another real search_hub_plugins call, so .codex/INSTALL.md documents marketplace upgrade as the primary update path. One caveat: because the test marketplace is a local directory rather than a real git-tracked one, marketplace upgrade itself reported "No configured Git marketplaces to upgrade" (a no-op for that source type) rather than exercising an actual version bump — the meaningfully verified part of the update flow here is remove+add plus the warm connectivity probe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant