The v1 tag exists in this repo but README + docs still showed @main in the caller-pattern examples. Followers of the docs were copy-pasting unstable @main pins. Fix: update all 6 example references to @v1 across: - README.md (4 examples) - docs/template-contract.md (1 example) - .github/workflows/auto-promote-staging-pr.yml header comment (1 example, just shipped in PR #25) Operational note: v1 is meant to track the latest stable patch within the v1 major. Cutting a new v1.X.Y or breaking-change v2 requires moving the v1 tag forward — same convention as actions/checkout@v4 etc. Doesn't migrate any consumer repo. Consumer migration from @main to @v1 is a per-repo follow-up; this PR ships the docs that guide that migration. Closes task #133. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.6 KiB
Workspace Template Contract
Hard rules every molecule-ai-workspace-template-* repo must satisfy. Enforced by scripts/validate-workspace-template.py on every CI run via the reusable validate-workspace-template.yml workflow.
The contract exists because the 8 template repos were extracted from a single monolithic Dockerfile pre-#87, and have drifted as each was edited piecemeal since. Without this gate, a 28-line cascade-friendly Dockerfile in one repo silently regresses to a 25-line non-cache-friendly one in another, and the next runtime publish ships the previous wheel from a stale layer (cache trap observed five times in a row on 2026-04-27).
Dockerfile
| Rule | Why |
|---|---|
FROM python:3.11-slim |
Single base everywhere — keeps apt + pip behaviour identical and lets us reason about CVE patches on one base. |
ARG RUNTIME_VERSION= declared |
The arg invalidates the pip-install layer's cache key whenever the cascade publishes a new wheel. Without it the cache hit replays the previous runtime. |
${RUNTIME_VERSION} referenced in a RUN |
Just declaring the ARG isn't enough — it has to be in the layer's command line so docker hashes it. Pattern: if [ -n "${RUNTIME_VERSION}" ]; then pip install --no-cache-dir --upgrade "molecule-ai-workspace-runtime==${RUNTIME_VERSION}"; fi |
RUN useradd -u 1000 -m -s /bin/bash agent |
The runtime drops to uid 1000 before exec'ing the SDK. Claude Code refuses --dangerously-skip-permissions as root for safety. The /workspace volume is also chown'd to 1000 by the platform provisioner. |
ENTRYPOINT ["molecule-runtime"] or a wrapper script that exec's molecule-runtime |
Single entrypoint means the platform's container-restart contract is uniform across templates. Wrapper scripts are allowed (claude-code has entrypoint.sh for gosu drop-priv; hermes has start.sh to boot the hermes-agent daemon first). |
molecule-ai-workspace-runtime listed in requirements.txt (or installed in the Dockerfile directly) |
The runtime wheel is the contract — without it the container has no A2A server, no heartbeat, no MCP bridge. |
config.yaml
| Required key | Type | Notes |
|---|---|---|
name |
str | Human-readable; appears on the canvas card. |
runtime |
str | Must be one of: langgraph, claude-code, crewai, autogen, deepagents, hermes, gemini-cli, openclaw. Custom runtimes warn but are allowed. |
template_schema_version |
int | Currently 1. Bump when adding a key that changes how the platform consumes config.yaml. Must be int, not string — a quoted "1" will fail validation. |
| Optional key | Notes |
|---|---|
description |
Free text, surfaces on canvas. |
version, tier |
int, controls platform-side rollout gating. |
model, models |
Either a single model id or a list of model ids the agent may use. |
runtime_config |
Nested block of runtime-specific settings (used by claude-code, gemini-cli, hermes). |
env, skills, tools, a2a, delegation, prompt_files, bridge, governance |
Optional feature blocks. Add new keys to OPTIONAL_KEYS in the validator when introducing them. |
Unknown top-level keys produce a warning (not an error) so accidental drift is visible without blocking.
adapter.py
Optional. When present, adapter.py should:
- Import
BaseAdapterfrommolecule_runtime.adapter_base. - Override
setup()andcreate_executor()for the runtime's specific entry point.
The pre-#87 import path (molecule_ai) produces a warning if it appears.
requirements.txt
Must declare molecule-ai-workspace-runtime (with a version pin or floor).
CI
Every template repo's .github/workflows/ci.yml should be a one-liner that calls the canonical reusable workflow:
name: CI
on: [push, pull_request]
jobs:
validate:
uses: Molecule-AI/molecule-ci/.github/workflows/validate-workspace-template.yml@v1
The reusable workflow checks out molecule-ci itself (into .molecule-ci-canonical) and runs the canonical validate-workspace-template.py from there — so no per-repo vendoring of the script is needed. The legacy .molecule-ci/scripts/ directory in each template repo is being phased out.
Adding a new runtime
- Add the runtime name to
KNOWN_RUNTIMESinscripts/validate-workspace-template.py. - Add the runtime + image ref to
RuntimeImagesinmolecule-core/workspace-server/internal/provisioner/provisioner.go. - Stand up the
molecule-ai-workspace-template-<runtime>repo from the existing template-of-templates pattern (issue #105 covers this). - Confirm CI green on the new repo before opening it for general use.