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>
68 lines
4.6 KiB
Markdown
68 lines
4.6 KiB
Markdown
# 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 `BaseAdapter` from `molecule_runtime.adapter_base`.
|
|
- Override `setup()` and `create_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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
1. Add the runtime name to `KNOWN_RUNTIMES` in `scripts/validate-workspace-template.py`.
|
|
2. Add the runtime + image ref to `RuntimeImages` in `molecule-core/workspace-server/internal/provisioner/provisioner.go`.
|
|
3. Stand up the `molecule-ai-workspace-template-<runtime>` repo from the existing template-of-templates pattern (issue #105 covers this).
|
|
4. Confirm CI green on the new repo before opening it for general use.
|