feat(plugin-devenv): ship devenv as a cdylib plugin, not a builtin - #419
Open
raphaelvigee wants to merge 3 commits into
Open
feat(plugin-devenv): ship devenv as a cdylib plugin, not a builtin#419raphaelvigee wants to merge 3 commits into
raphaelvigee wants to merge 3 commits into
Conversation
raphaelvigee
marked this pull request as ready for review
August 23, 2026 13:23
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 23, 2026 14:27
a136555 to
f0a7755
Compare
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 23, 2026 15:56
f0a7755 to
2f71585
Compare
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 23, 2026 16:40
2f71585 to
f408839
Compare
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 23, 2026 17:55
f408839 to
4248073
Compare
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 23, 2026 22:46
4248073 to
c1eb51b
Compare
`devenv` was asked for as a real loadable plugin and shipped as a builtin: `crates/plugin-devenv` had no `[lib] crate-type`, and `bootstrap.rs` registered both halves unconditionally into the host binary. It is now a cdylib like go, gha and oci — `crates/plugin-devenv-cdylib`, loaded from a `heph-devenv-plugin.json` manifest, built and published for all three targets. Nothing about devenv is compiled into heph any more. One driver serves both halves. It builds the environment artifact, and it answers the exec-runner lane that opens a session from it — so the runner is the party that starts the process: `prepare_spec` is per target, so one `devenv shell` is opened once inside the plugin and every target's exec is routed into it. The lane rides new `DriverMethod` ids on the existing `drivers` export, so no `PluginComponents` layout change and no ABI_SEMVER bump. `SessionSupport` moves into the plugin, where it belongs: `current_exe()` in a cdylib is the *host* binary, which is exactly the agent and per-target client a session needs, and `home`/`root` come from `CreateConfig`. The host no longer assembles it. - `install-devenv-plugin`, mirroring `install-gha-plugin`. - Release pipeline: one more package in the single `cargo build` (splitting it would serialize a fourth LTO pass), staged into `dist/`, version-slot patched, manifest generated, published. `pattern: "heph*"` and `files: dist/*` pick it up with no further change. - `qualityCrates` gains the crate — a new crate missing there lints as green locally and red in CI. Two bin-e2e tests, which is the only place this seam exists: the shipped cdylib is `dlopen`ed and its `devenv` driver answers across the ABI, and `devenv` no longer resolves with no plugin configured. The second is the one that matters — without it the conversion half-lands, the builtin keeps answering, every fixture passes and the cdylib is never actually loaded. plugin-devenv 10, engine 545, heph 128, plugin-sdk 36. lint clean.
`example/exec_runner/BUILD` uses `driver = "devenv"`, which stopped resolving the moment the driver left the binary. `gen-example` installs the plugin and `.hephconfig2` loads it by manifest, the same way it already does for go.
Follows the ABI change below: a runner's session now crosses as an object, so `Runner` implements `ExecRunner` and nothing else. The 55-line `ExecRunnerPlugin` adapter, the `Mutex<HashMap<String, Arc<dyn ExecSession>>>` it kept sessions in, and the `AtomicU64` that minted ids all existed to answer the host by id. The host holds the session itself now, so none of them have a caller. `Runner` is back to a single field. The devenv shell, its socket and its pid are where they always should have been — inside the session object, invisible to the host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RC5ykWRMPjRKGk1Tz1KTja
raphaelvigee
force-pushed
the
raphaelvigee/exec-runners-devenv-cdylib
branch
from
August 24, 2026 08:21
c1eb51b to
decb5b2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On top of the generic-runner PR.
devenvis a real cdylib plugin now, not abuiltin.
It was asked for as a loadable plugin during design and shipped as a builtin:
crates/plugin-devenvhad no[lib] crate-type, andbootstrap.rsregisteredboth halves unconditionally into the host binary. My miss.
What changed
crates/plugin-devenv-cdylib—crate-type = ["cdylib"], loaded from aheph-devenv-plugin.jsonmanifest, exactly like go/gha/oci. The host binary nolonger links
plugin-devenvat all.Two components, both named
devenv: a driver that builds the environmentartifact, and an exec runner (#418) that serves sessions from it. Separate
components, because a runner is not a driver — it has no schema, parses nothing,
builds nothing.
Being the runner is what makes this plugin the party that decides how the
process starts:
ExecSession::prepareis per target, so onedevenv shellisopened once inside the plugin and every target's exec is routed into it.
Since #418 hands the host a live session object rather than an id, that shell —
its socket, its pid — simply lives on the session.
Runneris a single fieldagain: the
Mutex<HashMap<String, Arc<dyn ExecSession>>>and theAtomicU64that minted ids existed only to answer the host by a name it had invented, and
both are deleted here.
SessionSupportmoves into the plugin, where it belongs:current_exe()in acdylib is the host binary, which is exactly the agent and per-target client a
session needs, and
home/rootcome fromCreateConfig. The host no longerassembles it.
Packaging
Ships as a release artifact on all three targets, same flow as the others:
cargo build— splitting it would serialize afourth LTO tail
dist/,macos-portable.sh'd, version-slot patched, manifestgenerated by
tools/pluginmanifest, publishedpattern: "heph*"andfiles: dist/*pick it up with no further changeinstall-devenv-pluginfor local use;gen-exampleruns it, sinceexample/exec_runner/BUILDusesdriver = "devenv"qualityCratesgains the crate — a new crate missing there lints greenlocally and red in CI, which has already cost time once on this stack
Tests
Two in
bin-e2e, which is the only place this seam exists — an in-process testconstructs the driver through generics and never crosses it:
dlopens and itsdevenvdriver answers across the ABIdevenvno longer resolves with no plugin configuredThe second is the one that matters. Without it the conversion half-lands: the
builtin keeps answering, every fixture passes, and nobody notices the cdylib is
dead weight that is never loaded.
Verified against real staged release artifacts —
plugin_dylib12/12.exec-runner 37, plugin-devenv 10, engine 545, heph 128, e2e/exec_runner 12.
lintclean.Note for reviewers
e2eis a devenv script and is baked at shell start, so an existing shellstill stages the old three-artifact list and this suite fails on a missing
heph-devenv-plugin.dylib. Re-enter the shell (ordirenv reload) beforerunning
e2elocally. CI is unaffected — it starts a fresh shell.Stack created with GitHub Stacks CLI • Give Feedback 💬