Plugins: the UHP 2026-09-12 sub-protocol in the gateway, runner and console - #175
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…side 2026-08-11 The gateway now lists both versions, echoes the one asked for, and reports the plugins capability with the Agent Plugins manifest schema it installs. A harness accepts plugins on create and update: each package is read the way the Agent Plugins client conformance section says (closed manifest, components from their fixed locations, an invalid component recorded in skipped rather than fatal, a missing or invalid manifest refused), stored out of line, and recorded as manifest, mcpServers, skills and skipped. The harness's own mcpServers and skills are never rewritten by a plugin, which is what keeps every 2026-08-11 client working. Component names are unique across the harness and its enabled plugins; a collision is a 409 plugin_conflict at write time. A stdio server on a base whose runner cannot launch one (pi, dsh) is refused with unsupported_transport. Two endpoints on both the org and the public surface: the package files, and the harness exported as a package with credentials omitted and recorded. Every turn folds enabled plugins in: their skills take the ordinary skill path, their remote servers the ordinary MCP path, and their stdio servers travel with the whole package so the runner has a root to run them from. The runner materialises each package at .harness/plugins/<name>/ with a writable .harness/plugin-data/<name>/, and turns each stdio server into a launcher script that exports PLUGIN_ROOT and PLUGIN_DATA and the entry's env, expands the two placeholders once, resolves a ./ command against the root, confines cwd to the root or the data directory, and execs the command with its args as argv. Every backend writer therefore sees one shape, command plus args; codex, hermes, cline and omp gain that entry beside the URL entry they had. A package that also carries .claude-plugin/plugin.json still reaches Claude Code through --plugin-dir. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Install a package from a folder (the picker reads every file under it, paths relative to the folder, and names the row from plugin.json before the save; the server validates on save and answers with what it derived and what it skipped), see each installed plugin's tools, Skills and what did not load, enable or disable it, remove it, and download the Harness's own tools and Skills as a package zipped in the browser. The harness type and save body carry `plugins`; fflate zips the export. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lugin The starter kits now carry their Skills as an Agent Plugins package at <kit>/plugin/. Launch installs it, so the Harness records the kit as a named, versioned plugin whose skills derive from the package, and can export it. A kit baked before that layout still carries its skills directly through the path that read them before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…API reference Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…der the name Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Gateway. Packages are stored under the org that installed them and a handle is checked for shape and ownership, so a leaked handle cannot read another org's package through a PUT. Everything is validated before any package is stored, and a write that drops a plugin deletes its package; deleting a harness deletes them all. The stdio gate normalises the base the way the write does, so no spelling of pi or dsh slips a process past it. Two plugin names, or a plugin skill and a harness skill, that the runner would fold into one directory are refused as one name. A remote server this deployment's network policy refuses is refused at write time and written into skipped, not dropped with a log line at turn time. File entries are checked in full: content or content_b64 but not both, base64 that decodes, no duplicate paths, no control characters; a manifest with no $schema is invalid rather than an unsupported version; env names must be environment variable names; the frontmatter reader handles block scalars and trailing comments; the derived record is bounded so the harness write cannot exceed the property cap. The export never fails on what an earlier write accepted and strips credentials from URLs. A disabled own skill no longer suppresses a plugin's skill of that name. The kit catalog names what a package installs; launch reserves the names of the hosted entries it attaches; a declared package that is missing is logged. Cloud upload carries plugins whole when the hosted side reports the capability and as their skills and remote servers when it does not. A harness's base is immutable on update, as the spec says. Runner. The plugin root and data directory are checked as the paths the session could have replaced with links, before anything is written through them; roots are rebuilt every turn and stale roots removed; files are written beside and renamed over so a running server's binary is replaced rather than truncated; a package's own ./ command is made runnable; the launcher is written without following links; codex's TOML escapes control characters; skills this runner wrote on an earlier turn and does not write now are removed; a package that is also a Claude Code plugin is not folded into the skills directory a second time. Console. Errors and notices from installing or downloading a package appear in the Plugins section; a folder over the package limit or for a manifest version the server does not install is refused before it is read into the draft; a save that leaves a plugin with parts not loaded stays on the page and opens that row; the download is disabled while the draft is unsaved and reports what the package left out; the open row is keyed by name; copy and wrapping at phone width. Docs. The guide's install example is a create, and the update path says what PUT replaces; the API reference lists the plugins field. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7c5db37 to
aa894a3
Compare
…the kit shipped one Launch on an existing kit Harness handled the database tool and the media entry but never the package, so the three kit Harnesses on hr-oss-test launched before their kits carried packages stayed without one through a relaunch while a fresh launch installed it (2026-09-14). The existing path now installs the kit's current package, replaces an older version and leaves a matching one alone, the kit's loose skills giving way to the package's, as on a fresh launch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
Plugin matrix on hr-oss-test, 2026-09-14Every base, both package shapes, one base at a time with
gemini's first attempt hit a connection timeout from this machine and its second found no provider: the box's provider store had been left holding one column's integration after a matrix column earlier in the day (the restore failed on a route to a key only its owner can re-add). With the store restored, gemini passed both shapes. Starter kits as packagesRelaunch of each kit on hr-oss-test with the four packages from starter-kit PR #23 baked into the image, then one real task per kit asking the agent for the SKILL.md files it can see.
The first relaunch installed a package only for Slides: the three kit Harnesses launched before their kits shipped packages kept no package through a relaunch, because the existing-Harness path of Branch rebased onto main (goose TMPDIR revert included); gateway 291, runner 333 in the 3.12 venv. 🤖 Generated with Claude Code |
The four kits end to end on hr-oss-test (2026-09-14, image plugins-0e3618c, starter-kit #23 merged as 361cae3)Each kit opened in the console as the console user, created from a prompt in its own app, and the result read off the rendered page with Playwright.
No kit Harness carries loose skills or a package server: the Skills all derive from the package, and the only MCP entries are the two launch provisions (a database connection record and the deployment's media server), which bind this deployment's data and credential and so cannot travel inside a portable package. Dashboards needed a database: the connection recorded on the box pointed at a container that no longer exists, so a seeded PostgreSQL now runs on the VM ( One follow-up for starter-kit, not blocking: every kit SKILL.md points its validator at 🤖 Generated with Claude Code |
…e version The version check alone let a kit whose Skills changed without a bump (the 2026-09-14 Skill fix shipped as 1.0.0 twice) leave launched Harnesses on the old bytes. Launch now compares the stored package with the one on disk, path by path, and installs when they differ; a matching version and matching files stay untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
Follow-up closed: the kit Skills find their validators (2026-09-14)
Deployed as 🤖 Generated with Claude Code |
…, and downloads the plugin The version rendered as a full-width bordered box (the row's block-span rule outranked the pill), the details were a wall of text beside floating actions, the header wrapped at 390, and "Download as plugin" exported the Harness's own tools and Skills, which for a kit Harness is an empty package. Each installed plugin now has its own Download (the package as installed, zipped in the browser), an inline version pill, a two-line description, a facts line and a details panel with Skills, tools, what was not loaded and links. The Harness export moved to a footer that says what it packages and appears only when the Harness has tools or Skills of its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
8ad4deb to
90488e4
Compare
`POST /v1/kits/{kit}/launch {"harness": <id>}` binds the kit to that Harness: the kit's app then
talks to it, it receives the kit's package and launch entries, and the previous kit Harness keeps
its sessions and package but is no longer the one the app finds. A Harness running another kit is
not taken over (409 kit_conflict). This is how a package migrated to another Harness takes the kit
with it, which the plugin round trip needs to be a closed loop.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
Plugin migration, closed loop, all four kits (2026-09-14, image plugins-fdad224, kits 1.0.2)For each kit: take the kit Harness's installed package exactly as the server holds it (the bytes the console's Download zips), install it on a fresh Harness of the same base, point the kit at that Harness, drive the kit's own app end to end with Playwright, then point the kit back and delete the copy.
Every session the app created landed on the copy, not on the original. What the first pass found. On the copy, Slides turned the deck into a PowerPoint and deleted deck.json. The package round-tripped, but the rule "deck.json is the deliverable the app reads" lived in the kit Harness's system prompt, which a package does not carry (Plugins chapter 5, by design). The portable home for a rule the app depends on is the Skill: starter-kit #26 moves each kit's operative rules into its SKILL.md and trims the kit prompt to the persona plus "read the Skill first" (packages 1.0.2). With that in place the copy behaves like the original. New on this branch
🤖 Generated with Claude Code |
…n narrow Four buttons in one line took the width the description needed. Wide: a two by two block on the right, the copy keeps the rest. Narrow: one per row under the copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
… viewport The settings panel is about 430px wide on a 1024 screen, so a viewport breakpoint left the copy squeezed to 140px beside four buttons. The list is now a size container: beside the copy, two per row; under 680px the actions drop below the copy; under 420px one per row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
…acks on a phone Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
Plugin row layout (0e7f441)The four actions no longer take a whole line from the description. The row follows the width of its list, not the viewport (the settings panel is about 410 px wide on a 1024 screen):
The export footer's button no longer wraps at any width. Measured with Playwright at 1440, 1024, 768 and 390, no sideways scroll; screenshots in the handoff evidence. 🤖 Generated with Claude Code |
…the Harness export footer is gone Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
The title's flex rule lost to the row's block rule for strong, so the pill touched the name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q96VFkSEJZdTydczxxJHMD
richard-epsilla
left a comment
There was a problem hiding this comment.
Measured on hr-oss-test as plugins-705bb5c: plugin matrix 22 of 22 on every base, the four kits end to end, plugin migration closed loop on all four kits, console Plugins section verified at 1440, 1024, 768 and 390. Tables and evidence in the comments.
The reference implementation now serves UHP 2026-09-12 beside 2026-08-11 and implements the Plugins chapter end to end. Ten of ten plugin conformance checks pass against it (P-01 to P-10, plus the F-series they lean on), so the next remeasure reads full 2026-09-12.
Gateway. Both versions from one code path, the requested one echoed. A harness accepts
pluginson create and update: each package is read the way the Agent Plugins client conformance section says (closed manifest, components from their fixed locations, an invalid component recorded inskippedrather than fatal, a missing or invalid manifest refused), stored out of line, and recorded asmanifest,mcpServers,skillsandskipped. The harness's ownmcpServersandskillsare never rewritten by a plugin, which is what keeps every 2026-08-11 client working. Names are unique across the harness and its enabled plugins (409plugin_conflict); a stdio server on a base whose runner cannot launch one is refused withunsupported_transport. Two endpoints on the org and public surfaces: the package files, and the harness exported as a package with credentials omitted and recorded. Every turn folds enabled plugins in.Runner. Each package lands whole at
.harness/plugins/<name>/with a writable.harness/plugin-data/<name>/. A stdio server becomes a launcher that exports PLUGIN_ROOT and PLUGIN_DATA and the entry's env, expands the two placeholders once, resolves a./command against the root, confines cwd, and execs the command with its args as argv, so every backend writer sees one shape; codex, hermes, cline and omp gain that entry. A package that is also a Claude Code plugin still reaches--plugin-dir.Console. A Plugins section on the Harness page: install from a folder, see each plugin's tools, Skills and what did not load, enable, remove, and download the Harness's own tools and Skills as a zipped package.
Starter kits. Launch installs a kit's plugin package when the kit ships one (HarnessRouter/starter-kit#23 makes all four ship one), so a launched Harness shows the kit as a named, versioned plugin it can export. A kit baked before that layout still carries its skills directly.
Docs: the self-hosting guide and the console API reference. Tests: gateway
test_plugins.py, runnertest_uhp_plugins.py. Verified on hr-test with a derived image and the conformance suite; the console at four widths.🤖 Generated with Claude Code
Verified live on hr-test (derived image
plugins-33cabcfon 0.17.1). The full conformance suite againsthttps://20-98-237-6.sslip.io/api/harness, class full, suite 2026.9.12: 74 of 74 passed, none skipped, "CONFORMANT UHP 2026-09-12 (full)". The Slides kit relaunched from the kits-as-plugins layout recordsharnessrouter-slides 1.0.0with its one Skill derived and nothing skipped; the package files and the harness export read back; a real hermes task on that harness listed.harness/plugins/harnessrouter_slides/skills/slide-design/SKILL.mdand the Skill mounted under the CLI's own skills directory. The console's Plugins section renders the installed kit plugin at 1440, 1024, 768 and 390 with no horizontal overflow.Review pass (commit 7c5db37). An adversarial review over the branch raised 54 findings; the real ones are fixed in one commit: org-bound package storage with handle ownership checks, validation before any package is stored and deletion of dropped packages, the stdio gate normalising the base, name collisions compared the way the runner keys directories, write-time network policy for plugin servers, full file entry validation, symlink-safe runner writes with stale roots and skills removed per turn, a runnable
./command, TOML control-character escaping, cloud upload carrying plugins, an immutable base on update, section-local console notices, and a guide example that no longer blanks a harness. Redeployed to hr-test and re-measured: 74 of 74, "CONFORMANT UHP 2026-09-12 (full)", and the Slides kit's Skill again reaches the agent from the plugin root.