forked from molecule-ai/molecule-core
feat(provision): propagate workspace model into runtime env
Tenant's workspace provisioner now forwards payload.Model (set by
canvas Config tab when a user picks a model) through to the
workspace's runtime env as HERMES_DEFAULT_MODEL, so install.sh /
start.sh in the template can seed the right ~/.hermes/config.yaml
without any post-provision manual step.
Helper applyRuntimeModelEnv() is runtime-switched so each template
owns its own env contract — hermes uses HERMES_DEFAULT_MODEL, future
runtimes with different config schemas register their own cases.
Runtimes that read model from /configs/config.yaml instead (langgraph,
claude-code, deepagents) are unaffected: the switch has no case for
them, so this is a no-op in those paths.
Applied in both the Docker provisioner path (provisionWorkspaceOpts)
and the SaaS/CP path (provisionWorkspaceCP) so local dev and
production behave identically.
Combined with:
- molecule-controlplane#231 (/opt/adapter/install.sh hook)
- molecule-ai-workspace-template-hermes#8 (install.sh for bare-host)
- molecule-ai-workspace-template-hermes#9 (derive-provider.sh)
this completes the MVP flow: customer creates a hermes workspace
in canvas with model = minimax/MiniMax-M2.7-highspeed + secret
MINIMAX_API_KEY = sk-cp-…, clicks Save, workspace provisions with
the MiniMax Token Plan hermes-agent gateway up and ready for the
first chat — no ops touch.
Foundation this builds on:
- env injection works for every runtime
- secret passthrough is generic (already via workspace_secrets)
- per-runtime env-var contract encoded once (applyRuntimeModelEnv)
- canvas Save button for later-edit remains a Files-API-over-EIC
concern (tracked separately)
See internal/product/designs/workspace-backends.md for the broader
architectural direction this fits into.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
41316eea54
commit
7e3cd043c8
@ -94,6 +94,7 @@ func (h *WorkspaceHandler) provisionWorkspaceOpts(workspaceID, templatePath stri
|
||||
// Runs after secret loads so an operator can still override via a
|
||||
// workspace_secret named GIT_AUTHOR_NAME if they want custom identity.
|
||||
applyAgentGitIdentity(envVars, payload.Name)
|
||||
applyRuntimeModelEnv(envVars, payload.Runtime, payload.Model)
|
||||
|
||||
// Plugin extension point: run any registered EnvMutators (e.g.
|
||||
// github-app-auth, vault-secrets) AFTER built-in identity injection so
|
||||
@ -544,6 +545,37 @@ func (h *WorkspaceHandler) ensureDefaultConfig(workspaceID string, payload model
|
||||
return files
|
||||
}
|
||||
|
||||
// applyRuntimeModelEnv exposes the workspace's selected model via an
|
||||
// env var the target runtime's install.sh / start.sh knows to read.
|
||||
// Each runtime owns its own env-var contract — the tenant just plumbs
|
||||
// the value through so CP can bake it into user-data.
|
||||
//
|
||||
// Why per-runtime rather than a generic MOLECULE_MODEL: each runtime
|
||||
// installer has its own config schema and naming (hermes writes to
|
||||
// ~/.hermes/config.yaml with `model.default`; langgraph reads from
|
||||
// /configs/config.yaml directly; future IoT/robotics targets may have
|
||||
// firmware manifests). Keeping the contract owned by the runtime
|
||||
// template means adding a new runtime doesn't require edits on the
|
||||
// tenant side for each one.
|
||||
//
|
||||
// For runtimes with no env-based model override (langgraph etc. read
|
||||
// model from /configs/config.yaml which CP user-data generates from
|
||||
// payload.Model at boot), this is a no-op — no harm in the switch
|
||||
// being empty for those cases.
|
||||
func applyRuntimeModelEnv(envVars map[string]string, runtime, model string) {
|
||||
if model == "" {
|
||||
return
|
||||
}
|
||||
switch runtime {
|
||||
case "hermes":
|
||||
// template-hermes install.sh reads this into ~/.hermes/config.yaml's
|
||||
// model.default field; derives HERMES_INFERENCE_PROVIDER from the
|
||||
// slug prefix (minimax/…, anthropic/…, openai/…, etc.) when the
|
||||
// provider isn't explicitly set.
|
||||
envVars["HERMES_DEFAULT_MODEL"] = model
|
||||
}
|
||||
}
|
||||
|
||||
// loadWorkspaceSecrets loads global + workspace-specific secrets into a map.
|
||||
// Returns nil map + error string on decrypt failure. Shared by both Docker
|
||||
// and control plane provisioning paths to avoid duplication.
|
||||
@ -600,6 +632,7 @@ func (h *WorkspaceHandler) provisionWorkspaceCP(workspaceID, templatePath string
|
||||
}
|
||||
|
||||
applyAgentGitIdentity(envVars, payload.Name)
|
||||
applyRuntimeModelEnv(envVars, payload.Runtime, payload.Model)
|
||||
if err := h.envMutators.Run(ctx, workspaceID, envVars); err != nil {
|
||||
log.Printf("CPProvisioner: env mutator failed for %s: %v", workspaceID, err)
|
||||
// F1086 / #1206: env mutator errors (missing tokens, vault paths) must not
|
||||
|
||||
Loading…
Reference in New Issue
Block a user