Conversation
Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
The first release that runs Dang entrypoints and serves serveModule. Signed-off-by: Yves Brissaud <yves@dagger.io>
The SDK files may import nothing generated, so module support selects the few fields it needs by name, with the arguments the bindings send. Signed-off-by: Yves Brissaud <yves@dagger.io>
A generated client carries a descriptor, not a connection: its session serves the target once, through serveModule, before the first query. Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
It keeps dag.container() and dagger.Container working while a module migrates to the clients, behind a flag. Signed-off-by: Yves Brissaud <yves@dagger.io>
The package no longer imports the one-file bindings. dagger.dag is the default Session, or the global client when one is generated. Signed-off-by: Yves Brissaud <yves@dagger.io>
A module marks itself first, so it never provisions one in its own container. Signed-off-by: Yves Brissaud <yves@dagger.io>
The SDK owns only its entries in a scope's pyproject.toml; the rest is the user's and must come back exactly as written. Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
A scope vendors nothing: the SDK files, core and each client are uv workspace members, installed when the project depends on them. Signed-off-by: Yves Brissaud <yves@dagger.io>
The workspace the engine hands over is the caller's. It holds the module only when the module sits in it, not when it was loaded from git. Signed-off-by: Yves Brissaud <yves@dagger.io>
A client is generated inside the scope that uses it, as a workspace member, so a module's manifest no longer lists its clients. Signed-off-by: Yves Brissaud <yves@dagger.io>
The floor runs entrypoints and follows one over a runtime, so the [runtime] table this SDK wrote went unread, and its engineVersion misled. Signed-off-by: Yves Brissaud <yves@dagger.io>
The module's code runs in an exec with no module context, so it cannot resolve a local client itself. An ID is a capability, and the module is third-party code, so the workspace stays with the caller. Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
engine-e2e existed because released engines lacked serveModule. The floor has it, so its checks join the suite, and a floor job asserts that release. Signed-off-by: Yves Brissaud <yves@dagger.io>
Signed-off-by: Yves Brissaud <yves@dagger.io>
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.
Draft, for review of the approach as much as the code.
One generated client artifact per module, used by a module and by a plain Python program alike. It replaces the per-dependency bindings and
[[dependencies]].The design is committed with the code:
hack/designs/2026-09-15-unified-clients.md.What a scope looks like
Any directory that uses clients — a module's own directory, or a plain project:
Each client is a PEP 420 namespace package under
dagger_clients, a uv workspace member with no path of its own, so the same artifact is byte-identical in two different scopes.What works
Verified by hand with the released CLI, outside the check harness.
hack/try-unified-clients.shbuilds the whole walkthrough from nothing in a temporary workspace.v1.0.0-beta.14, throughQuery.serveModuleand under both entrypoint forms.[project]table at all, calling a client throughdagger.connection().dag.container()anddagger.Containerkeep working behind[tool.dagger] global-client = true, and clearing the flag gives back exactly the tree of a module that never had one.What is open, and not fixable here
A changed client target does not invalidate its caller's cached result.
[[dependencies]]used to put the target into the caller's identity; a unified client records a path and loads the module at run time, so nothing about the target reaches the caller's cache key. Change the target, call the caller with the same arguments, and the old answer comes back; call it with a different argument and the change appears.This reproduces on a released engine and on
284cd849alike, so it is a property of loading a module at run time rather than of the new field. Every SDK that drops[[dependencies]]inherits it. Written up language-neutrally, with the measured behaviour kept separate from the inferred mechanism:hack/designs/2026-09-18-serve-module-cache.md.A module driven by a Dang entrypoint cannot reach a second hop. In a chain
demo → lib → leaf,lib's entrypoint is handed the caller's container-derived workspace rescoped to/.dagger/modules/lib, solibcannot load its own local client. One hop works; two do not.core/sdk/entrypoint/entrypoint.go,Workspace()at beta.14.The SDK that generates a module must be the one that runs it. A generated manifest names the published shared entrypoint, which predates this layout and refuses the module. An entrypoint source may name only a git ref or a path inside the module, so a checkout under development cannot point at its own. Written up in
hack/designs/2026-09-18-workspace-sdk-runtime.md. Before release, tagentrypoint/v1.xfrom this branch.Smaller, also engine-side:
dagger module client add ../librecords./.dagger/modules/lib, butdagger module client rm ../librefuses the same argument, becausewithoutClientdoes not normalise the address the waywithClientdoes.To try this branch today:
hack/try-unified-clients.shbuilds the walkthrough from nothing and copies this checkout's entrypoint into each module, which is what makes a development checkout runnable.Modules run on a Dang entrypoint, and are handed only their clients
Generated manifests carry no
[runtime]: a module runs through the shared Dang entrypoint, or the one--static-entrypointgenerates inside it. The floor is released v1.0.0-beta.14, which drives entrypoints and hasserveModule; the load path for engines without it is gone.That exposed a defect worth recording. A Dang entrypoint runs the module's Python in an ordinary nested exec, and the engine attaches module context only to execs it starts itself — so the process reports its own container as its workspace and no
currentModuleat all. Whatever module context the code needs, the entrypoint must hand over.What it hands over is the least thing that works: one module source per declared client, built from the files the engine already loaded, detached from the workspace they came from, re-read from the caller's config at every call. Not the workspace, and not a handle that leads back to one — both were tried and both leaked, the second because a
ModuleSourcefromWorkspace.moduleSourcekeeps its origin andwithIncludes("../…")climbs back out.A probe check runs module code that walks every ID the loader holds and every
ModuleSourceroute that could rebuild a directory — at every climb depth, distinguishing an absent field from a refused capability — and reads nothing: 4,305 routes,reads=[],absent=0, under both entrypoint forms. Against the leaking shape the same probe reads the caller's file, so the check has a negative control.The rule most of the review time went into
Generation writes into a directory the user also owns, so it must touch only what it made. Making that true took one critical defect and eleven more:
[tool.dagger] generatedmarker is read as TOML, never as text, and onlyclient,coreandruntimecount. A regex over raw text once deleted a user's directory because a multi-line string in it happened to quote a marker.clients/corestops generation rather than being replaced.[tool.uv.workspace] membersand[project] dependenciesare parsed withtomllibin the SDK's pinned image. A hand-written reader failed this twice in opposite directions: first too generous, then refusing"tomli; python_version < \"3.11\""as "not an array of strings".Checks
sdk: 546 passed.helpers/pyproject: 39 Go tests.hack/e2e-local.shruns them against your engine;hack/e2e-floor.shpins the floor release and asserts it before any check runs.Phases
This is phase 1. Phase 2 publishes the SDK files and ends the migration: the
sdk/copy leaves the scope, and the global client is removed after a deprecation period.