- Problem
- Solution
- Client Contract
- Request Peeking
- Module Planning
- CLI
- Correctness
- Implementation
- Status
Workspace module loading is now centralized in the engine. This keeps clients simple and gives every client the same schema, but it can be wasteful for one-shot clients.
- Full workspace cost - A workspace with many modules loads every configured module even when the query only touches one.
- One-shot queries know their workload - Commands such as
dagger queryknow the GraphQL request they will send, but the engine currently loads modules before using that request. - Hints duplicate intent - A separate "modules to load" hint would repeat information already present in the GraphQL query.
Add a SingleQuery client metadata flag. When set, the engine may inspect the first /query request body before workspace module loading, derive the top-level GraphQL fields, and load only the workspace modules needed for that request. If the request cannot be safely classified, the engine falls back to current behavior and loads all workspace modules.
SingleQuery is a session contract, not a module-loading flag.
type ClientMetadata struct {
LoadWorkspaceModules bool `json:"load_workspace_modules,omitempty"`
// SingleQuery declares that this client will issue at most one GraphQL
// /query request before disconnecting. The engine may use that request body
// to specialize session setup.
SingleQuery bool `json:"single_query,omitempty"`
}Semantics:
| LoadWorkspaceModules | SingleQuery | Behavior |
|---|---|---|
| false | false/true | Load no workspace modules. |
| true | false | Load all workspace modules. |
| true | true | Inspect the first request and load a subset if safe; otherwise load all. |
If a SingleQuery client sends a second /query request, the engine should return a clear error. This avoids silently serving a schema that was narrowed for a previous request.
dagql owns GraphQL-over-HTTP parsing and leaves the request usable for the real handler.
func PeekRootFields(r *http.Request) (ok bool, fields []string, err error)Responsibilities:
- Decode the GraphQL HTTP request envelope.
- Parse the selected GraphQL operation using the existing
gqlparserparser. - Return actual top-level field names, ignoring aliases.
- Expand top-level fragments when this can be done syntactically.
- Restore
r.Bodybefore returning. - Return
ok=falsewhen it cannot safely produce a complete field list.
dagql does not interpret field names. It does not know about workspaces, modules, currentTypeDefs, or any other Dagger-specific field.
The engine calls dagql.PeekRootFields before module loading, then applies Dagger workspace rules.
ok, fields, err := dagql.PeekRootFields(r)
if err != nil || !ok || requiresFullWorkspaceSchema(fields) {
loadAllWorkspaceModules()
} else {
loadWorkspaceModulesForRootFields(fields)
}Full workspace loading is required when root fields include schema-wide Dagger or GraphQL fields:
__schema
__type
currentTypeDefsFor ordinary root fields:
- If a field matches a workspace module constructor, load that module.
- If a field does not match a constructor but there is exactly one workspace entrypoint, load the entrypoint module.
- If multiple workspace entrypoints or other ambiguity exists, load all modules.
- If all fields are core-only, load no workspace modules.
The query is still validated and executed by the normal GraphQL handler after planning.
dagger query is the first target.
dagger query --doc query.graphqlIt should connect with:
client.Params{
LoadWorkspaceModules: true,
SingleQuery: true,
}Before this can be set honestly, dagger query must avoid preflight GraphQL calls that would violate the single-query contract. Today it uses the generic optional module wrapper, which may issue module/config checks before the user query.
The optimization must be conservative.
- Fallback preserves behavior - Parser failure, unsupported transport shape, ambiguous operation selection, unsupported fragment shape, or uncertain module mapping loads all workspace modules.
- Request body is preserved - Peeking must restore
r.Bodyso the real GraphQL handler receives the original request. - No schema validation during peek - The peek is syntax-only. Validation still happens against the final schema.
- No Dagger semantics in dagql -
dagqlreturns root fields; the engine decides what those fields mean. - Single-query enforcement - A narrowed schema is only safe if the client cannot later ask for a different workspace module.
Likely changes:
- Add
SingleQuerytoengine.ClientMetadata, engine client params, CLI session forwarding, and SDK connection config where needed. - Add
dagql.PeekRootFields(*http.Request). - In
engine/server.serveQuery, call the peek helper forLoadWorkspaceModules && SingleQuerybeforeensureModulesLoaded. - After
ensureWorkspaceLoadedgathersclient.pendingModules, filter those pending modules from the peeked fields. - Track whether a
SingleQueryclient has already served a/queryrequest and reject subsequent requests. - Refactor
dagger queryso it can setSingleQuerywithout issuing preflight GraphQL calls. - Add tests for request body restoration, root-field extraction, fallback cases, workspace constructor matching, entrypoint fallback, and second-query rejection.
Draft proposal.