diff --git a/CLAUDE.md b/CLAUDE.md index 87e3374d..2e156023 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,7 +118,7 @@ Canvas (Next.js :3000) ←WebSocket→ Platform (Go :8080) ←HTTP→ Postgres + Four main components: - **Platform** (`platform/`): Go/Gin control plane — workspace CRUD, registry, discovery, WebSocket hub, liveness monitoring - **Canvas** (`canvas/`): Next.js 15 + React Flow (@xyflow/react v12) + Zustand + Tailwind — visual workspace graph -- **Workspace Runtime** (`workspace-template/`): Unified Docker image with pluggable adapter system — supports LangGraph, Claude Code, OpenClaw, DeepAgents, CrewAI, AutoGen. Adapters in `workspace-template/adapters/`. Deps installed at startup via `entrypoint.sh`. +- **Workspace Runtime** (`workspace-template/`): Shared runtime published as [`molecule-ai-workspace-runtime`](https://pypi.org/project/molecule-ai-workspace-runtime/) on PyPI. Supports LangGraph, Claude Code, OpenClaw, DeepAgents, CrewAI, AutoGen. Each adapter lives in its own standalone template repo (e.g. `molecule-ai-workspace-template-claude-code`). See `docs/workspace-runtime-package.md` for the full picture. - **molecli** (`platform/cmd/cli/`): Go TUI dashboard (Bubbletea + Lipgloss) — real-time workspace monitoring, event log, health overview, delete/filter operations ## Build & Run Commands @@ -172,21 +172,20 @@ Env vars: `NEXT_PUBLIC_PLATFORM_URL` (default http://localhost:8080), `NEXT_PUBL ### Workspace Images ```bash -bash workspace-template/build-all.sh # Build base + ALL runtime images -bash workspace-template/build-all.sh claude-code # Build base + specific runtime only +bash workspace-template/build-all.sh # Build base image only (workspace-template:base) ``` -Each runtime has its own Docker image extending `workspace-template:base`, with deps pre-installed for fast startup. The base Dockerfile (`workspace-template/Dockerfile`) builds `:base`, then each `adapters/*/Dockerfile` extends it (e.g. `claude_code/Dockerfile` installs the `claude` CLI). **Always use `build-all.sh`** — it builds base first, then all runtimes in order. No `:latest` tag — each runtime uses its own tag to avoid confusion. +Adapters are now in standalone template repos. Each repo has its own `Dockerfile` that installs `molecule-ai-workspace-runtime` from PyPI + adapter-specific deps. The base `workspace-template/Dockerfile` still builds `:base` for local dev. See `docs/workspace-runtime-package.md` for the adapter repo list and details. -| Runtime | Image Tag | Key Deps | -|---------|-----------|----------| -| langgraph | `workspace-template:langgraph` | langchain-anthropic, langgraph | -| claude-code | `workspace-template:claude-code` | claude-agent-sdk (pip), @anthropic-ai/claude-code (npm) | -| openclaw | `workspace-template:openclaw` | openclaw deps | -| crewai | `workspace-template:crewai` | crewai | -| autogen | `workspace-template:autogen` | autogen | -| deepagents | `workspace-template:deepagents` | deepagents | -| hermes | `workspace-template:hermes` | openai (OpenAI-compatible client; Nous Portal via `HERMES_API_KEY` or OpenRouter via `OPENROUTER_API_KEY` fallback) | -| gemini-cli | `workspace-template:gemini-cli` | @google/gemini-cli (npm); requires `GEMINI_API_KEY`; MCP wired via `~/.gemini/settings.json`; memory file: `GEMINI.md` | +| Runtime | Standalone Repo | Key Deps | +|---------|-----------------|----------| +| langgraph | `molecule-ai-workspace-template-langgraph` | molecule-ai-workspace-runtime, langchain-anthropic, langgraph | +| claude-code | `molecule-ai-workspace-template-claude-code` | molecule-ai-workspace-runtime, claude-agent-sdk (pip), @anthropic-ai/claude-code (npm) | +| openclaw | `molecule-ai-workspace-template-openclaw` | molecule-ai-workspace-runtime, openclaw (npm) | +| crewai | `molecule-ai-workspace-template-crewai` | molecule-ai-workspace-runtime, crewai | +| autogen | `molecule-ai-workspace-template-autogen` | molecule-ai-workspace-runtime, autogen | +| deepagents | `molecule-ai-workspace-template-deepagents` | molecule-ai-workspace-runtime, deepagents | +| hermes | `molecule-ai-workspace-template-hermes` | molecule-ai-workspace-runtime, openai, anthropic, google-genai | +| gemini-cli | `molecule-ai-workspace-template-gemini-cli` | molecule-ai-workspace-runtime, @google/gemini-cli (npm) | Templates live in standalone repos under `Molecule-AI/molecule-ai-workspace-template-*` (8 workspace templates) and `Molecule-AI/molecule-ai-org-template-*` (5 org templates). They're cloned at Docker build time into the platform image. The template registry (`template_registry` table in the control plane DB) tracks all templates with their `github://` source URLs. Agent roles are configured after deployment via Config tab or API. diff --git a/docs/workspace-runtime-package.md b/docs/workspace-runtime-package.md new file mode 100644 index 00000000..613e0a9c --- /dev/null +++ b/docs/workspace-runtime-package.md @@ -0,0 +1,79 @@ +# Workspace Runtime PyPI Package + +## Overview + +The shared workspace runtime infrastructure lives in two places: + +1. **Source of truth (monorepo):** `workspace-template/` — this is where all development happens +2. **Published package:** [`molecule-ai-workspace-runtime`](https://pypi.org/project/molecule-ai-workspace-runtime/) on PyPI + +## What's in the package + +Everything in `workspace-template/` except adapter-specific code: + +- `molecule_runtime/` — all shared `.py` files (main.py, config.py, heartbeat.py, etc.) +- `molecule_runtime/adapters/` — `BaseAdapter`, `AdapterConfig`, `SetupResult`, `shared_runtime` +- `molecule_runtime/builtin_tools/` — delegation, memory, approvals, sandbox, telemetry +- `molecule_runtime/skill_loader/` — skill loading + hot-reload +- `molecule_runtime/plugins_registry/` — plugin discovery and install pipeline +- `molecule_runtime/policies/` — namespace routing policies +- Console script: `molecule-runtime` → `molecule_runtime.main:main_sync` + +## Adapter repos + +Each of the 8 adapter repos now contains: +- `adapter.py` — runtime-specific `Adapter` class +- `requirements.txt` — `molecule-ai-workspace-runtime>=0.1.0` + adapter deps +- `Dockerfile` — standalone image (no longer extends workspace-template:base) + +| Adapter | Repo | +|---------|------| +| claude-code | https://github.com/Molecule-AI/molecule-ai-workspace-template-claude-code | +| langgraph | https://github.com/Molecule-AI/molecule-ai-workspace-template-langgraph | +| crewai | https://github.com/Molecule-AI/molecule-ai-workspace-template-crewai | +| autogen | https://github.com/Molecule-AI/molecule-ai-workspace-template-autogen | +| deepagents | https://github.com/Molecule-AI/molecule-ai-workspace-template-deepagents | +| hermes | https://github.com/Molecule-AI/molecule-ai-workspace-template-hermes | +| gemini-cli | https://github.com/Molecule-AI/molecule-ai-workspace-template-gemini-cli | +| openclaw | https://github.com/Molecule-AI/molecule-ai-workspace-template-openclaw | + +## Adapter discovery (ADAPTER_MODULE) + +Standalone adapter repos set `ENV ADAPTER_MODULE=adapter` in their Dockerfile. +The runtime's `get_adapter()` checks this env var first: + +```python +# In molecule_runtime/adapters/__init__.py +def get_adapter(runtime: str) -> type[BaseAdapter]: + adapter_module = os.environ.get("ADAPTER_MODULE") + if adapter_module: + mod = importlib.import_module(adapter_module) + return getattr(mod, "Adapter") + # Fall back to built-in subdirectory scan (monorepo local dev) + ... +``` + +## Publishing a new version + +```bash +cd workspace-template +# 1. Bump version in pyproject.toml +# 2. Sync to molecule-ai-workspace-runtime repo +# 3. Tag and push — CI publishes to PyPI via PYPI_TOKEN secret +``` + +Or manually: +```bash +cd workspace-template +python -m build +python -m twine upload dist/* +``` + +## Writing a new adapter + +1. Create a new standalone repo `molecule-ai-workspace-template-` +2. Copy `adapter.py` pattern from any existing adapter repo +3. Change imports: `from molecule_runtime.adapters.base import BaseAdapter, AdapterConfig` +4. Create `requirements.txt` with `molecule-ai-workspace-runtime>=0.1.0` + your deps +5. Create `Dockerfile` with `ENV ADAPTER_MODULE=adapter` and `ENTRYPOINT ["molecule-runtime"]` +6. Register the runtime name in the platform's known runtimes list diff --git a/workspace-template/Dockerfile b/workspace-template/Dockerfile index 18cccd7f..dfc78ff2 100644 --- a/workspace-template/Dockerfile +++ b/workspace-template/Dockerfile @@ -25,12 +25,12 @@ RUN useradd -m -s /bin/bash agent COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# Copy runtime code +# Copy runtime code (adapters/ has been removed — adapters now live in standalone +# template repos and install molecule-ai-workspace-runtime from PyPI) COPY *.py ./ COPY entrypoint.sh ./ COPY skill_loader/ ./skill_loader/ COPY builtin_tools/ ./builtin_tools/ -COPY adapters/ ./adapters/ COPY plugins_registry/ ./plugins_registry/ COPY policies/ ./policies/ diff --git a/workspace-template/adapters/autogen/Dockerfile b/workspace-template/adapters/autogen/Dockerfile deleted file mode 100644 index ef036038..00000000 --- a/workspace-template/adapters/autogen/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -COPY adapters/autogen/requirements.txt /tmp/adapter-requirements.txt -RUN pip install --no-cache-dir -r /tmp/adapter-requirements.txt && rm /tmp/adapter-requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/autogen/requirements.txt b/workspace-template/adapters/autogen/requirements.txt deleted file mode 100644 index a91e3128..00000000 --- a/workspace-template/adapters/autogen/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -autogen-agentchat>=0.4.0 -autogen-ext[openai]>=0.4.0 diff --git a/workspace-template/adapters/claude_code/Dockerfile b/workspace-template/adapters/claude_code/Dockerfile deleted file mode 100644 index 6ac245b1..00000000 --- a/workspace-template/adapters/claude_code/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM workspace-template:base -USER root -RUN npm install -g @anthropic-ai/claude-code 2>/dev/null || true -# Pre-install adapter Python deps (single source of truth: requirements.txt) -# so first-boot doesn't pay a pip install penalty. entrypoint.sh skips -# reinstall when the first listed package is already importable. -RUN pip install --no-cache-dir -r /app/adapters/claude_code/requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/claude_code/requirements.txt b/workspace-template/adapters/claude_code/requirements.txt deleted file mode 100644 index 83ae9f21..00000000 --- a/workspace-template/adapters/claude_code/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Claude Agent SDK — programmatic API to Claude Code engine. -# Replaces CLI subprocess approach (no more --print, --resume, json parsing). -# The Claude Code CLI is still pre-installed via npm because the SDK uses it under the hood. -claude-agent-sdk>=0.1.58 diff --git a/workspace-template/adapters/crewai/Dockerfile b/workspace-template/adapters/crewai/Dockerfile deleted file mode 100644 index c79fbb71..00000000 --- a/workspace-template/adapters/crewai/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -COPY adapters/crewai/requirements.txt /tmp/adapter-requirements.txt -RUN pip install --no-cache-dir -r /tmp/adapter-requirements.txt && rm /tmp/adapter-requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/crewai/requirements.txt b/workspace-template/adapters/crewai/requirements.txt deleted file mode 100644 index 0f270f64..00000000 --- a/workspace-template/adapters/crewai/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -crewai>=0.100.0 diff --git a/workspace-template/adapters/deepagents/Dockerfile b/workspace-template/adapters/deepagents/Dockerfile deleted file mode 100644 index 552cd9df..00000000 --- a/workspace-template/adapters/deepagents/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -COPY adapters/deepagents/requirements.txt /tmp/adapter-requirements.txt -RUN pip install --no-cache-dir -r /tmp/adapter-requirements.txt && rm /tmp/adapter-requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/deepagents/requirements.txt b/workspace-template/adapters/deepagents/requirements.txt deleted file mode 100644 index 70c47bb3..00000000 --- a/workspace-template/adapters/deepagents/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -deepagents>=0.5.0 -langchain-openai>=1.0.0 -langchain-google-genai>=2.1.0 -langchain-anthropic>=1.4.0 diff --git a/workspace-template/adapters/gemini_cli/Dockerfile b/workspace-template/adapters/gemini_cli/Dockerfile deleted file mode 100644 index 4f5cc222..00000000 --- a/workspace-template/adapters/gemini_cli/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -RUN npm install -g @google/gemini-cli 2>/dev/null || true -# gemini-cli has no extra Python adapter deps — uses CLIAgentExecutor from base -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/gemini_cli/requirements.txt b/workspace-template/adapters/gemini_cli/requirements.txt deleted file mode 100644 index 4897bc73..00000000 --- a/workspace-template/adapters/gemini_cli/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# gemini-cli adapter has no extra Python dependencies. -# The CLIAgentExecutor (base) handles all subprocess management. diff --git a/workspace-template/adapters/hermes/Dockerfile b/workspace-template/adapters/hermes/Dockerfile deleted file mode 100644 index 62d84f97..00000000 --- a/workspace-template/adapters/hermes/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -COPY adapters/hermes/requirements.txt /tmp/adapter-requirements.txt -RUN pip install --no-cache-dir -r /tmp/adapter-requirements.txt && rm /tmp/adapter-requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/hermes/requirements.txt b/workspace-template/adapters/hermes/requirements.txt deleted file mode 100644 index a59236a7..00000000 --- a/workspace-template/adapters/hermes/requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Hermes adapter dependencies. -# -# openai: primary client for the 13 OpenAI-compat providers in providers.py -# (Nous Portal, OpenRouter, OpenAI, xAI, Qwen, GLM, Kimi, MiniMax, DeepSeek, -# Groq, Together, Fireworks, Mistral — all reachable via one openai SDK -# pointed at different base URLs). Anthropic + Gemini now go native. -openai>=1.0.0 - -# anthropic: native Messages API client for the anthropic provider (auth_scheme -# = "anthropic" in providers.py). Phase 2a addition — gives correct tool calling, -# vision, and extended-thinking semantics that don't translate cleanly through -# the OpenAI-compat shim. If this package is missing at runtime, executor.py's -# _do_anthropic_native() raises a clear RuntimeError pointing back at this -# install line, so a workspace image built without it fails loud, not silent. -anthropic>=0.39.0 - -# google-genai: native generateContent API client for the gemini provider -# (auth_scheme = "gemini" in providers.py). Phase 2b addition — gives -# first-class vision content blocks, tool/function calling, system -# instructions, and thinking config that don't translate cleanly through -# the OpenAI-compat /v1beta/openai shim. Same fail-loud semantics as the -# anthropic path: missing at runtime → clear RuntimeError from -# _do_gemini_native(), not a silent fallback. -google-genai>=1.0.0 diff --git a/workspace-template/adapters/langgraph/Dockerfile b/workspace-template/adapters/langgraph/Dockerfile deleted file mode 100644 index cb4f54d7..00000000 --- a/workspace-template/adapters/langgraph/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM workspace-template:base -USER root -COPY adapters/langgraph/requirements.txt /tmp/adapter-requirements.txt -RUN pip install --no-cache-dir -r /tmp/adapter-requirements.txt && rm /tmp/adapter-requirements.txt -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/langgraph/requirements.txt b/workspace-template/adapters/langgraph/requirements.txt deleted file mode 100644 index 03f2b02f..00000000 --- a/workspace-template/adapters/langgraph/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# LangGraph ecosystem — upgrade as a matched set -langchain-core==1.2.26 -langchain-anthropic==1.4.0 -langchain-openai==1.1.12 -langchain-google-genai>=2.1.0 -langgraph==1.1.6 - -# Observability (optional — used if LANGFUSE_* env vars are set) -langfuse==4.0.6 - -# E2B sandbox (optional — used when config.sandbox.backend == "e2b") -e2b-code-interpreter==1.0.3 diff --git a/workspace-template/adapters/openclaw/Dockerfile b/workspace-template/adapters/openclaw/Dockerfile deleted file mode 100644 index 00a3f95b..00000000 --- a/workspace-template/adapters/openclaw/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM workspace-template:base -USER root -RUN npm install -g openclaw 2>/dev/null || true && \ - cd /usr/lib/node_modules/openclaw 2>/dev/null && \ - npm install @buape/carbon @larksuiteoapi/node-sdk @slack/web-api grammy 2>/dev/null || true -# Do NOT set USER agent — entrypoint starts as root, chowns volumes, drops to agent via gosu diff --git a/workspace-template/adapters/openclaw/requirements.txt b/workspace-template/adapters/openclaw/requirements.txt deleted file mode 100644 index 66591959..00000000 --- a/workspace-template/adapters/openclaw/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# OpenClaw is a Node.js CLI — installed via npm at startup, not pip -# The adapter only needs httpx for HTTP proxying (already in base image) diff --git a/workspace-template/build-all.sh b/workspace-template/build-all.sh index ee2922db..1d271d8c 100755 --- a/workspace-template/build-all.sh +++ b/workspace-template/build-all.sh @@ -1,12 +1,19 @@ #!/usr/bin/env bash -# build-all.sh — Rebuild base + all runtime images in the correct order. +# build-all.sh — Rebuild base image and optionally adapter images. +# +# NOTE: Adapters have been extracted to standalone template repos: +# https://github.com/Molecule-AI/molecule-ai-workspace-template- +# +# This script now only builds the base image from workspace-template/Dockerfile. +# Each adapter repo has its own Dockerfile that installs molecule-ai-workspace-runtime +# from PyPI and the adapter-specific deps. # # Usage: -# bash workspace-template/build-all.sh # Build all -# bash workspace-template/build-all.sh claude-code langgraph # Build specific runtimes only +# bash workspace-template/build-all.sh # Build base image only # -# The base image must be built first, then each adapter extends it. -# Adapter directory names use underscores (claude_code), Docker tags use hyphens (claude-code). +# Standalone adapter repos still reference the legacy base image for local dev +# (e.g. FROM workspace-template:base). To build those locally, clone the adapter +# repo and run `docker build -t workspace-template: .` from its root. set -euo pipefail @@ -20,90 +27,11 @@ NC='\033[0m' log() { echo -e "${GREEN}[build]${NC} $1" >&2; } err() { echo -e "${RED}[error]${NC} $1" >&2; } -# Convert between dir name (underscore) and tag name (hyphen) -dir_to_tag() { echo "${1//_/-}"; } -tag_to_dir() { echo "${1//-/_}"; } - -# Step 1: Build base image (always — all runtimes depend on it) +# Build base image log "Building workspace-template:base ..." if ! docker build -t workspace-template:base -f Dockerfile . ; then err "Base image build failed" exit 1 fi log "Base image built" - -# Step 2: Determine which runtimes to build -RUNTIMES=() -if [ $# -gt 0 ]; then - for arg in "$@"; do - dir="$(tag_to_dir "$arg")" - if [ -f "adapters/$dir/Dockerfile" ]; then - RUNTIMES+=("$dir") - else - err "No Dockerfile for runtime: $arg (looked in adapters/$dir/)" - exit 1 - fi - done -else - for df in adapters/*/Dockerfile; do - RUNTIMES+=("$(basename "$(dirname "$df")")") - done -fi - -# Step 3: Build each runtime image — in parallel (Top-5 #3 from outcomes doc). -# -# All adapter Dockerfiles `FROM workspace-template:base` with no inter-adapter -# dependency, so they're safe to run concurrently. Single-runtime builds -# (`bash build-all.sh claude-code`) still run serially — no benefit to fork. -# Per-adapter stderr/stdout goes to /tmp/build_.log so failures are -# debuggable without interleaved output. -FAILED=() - -if [ "${#RUNTIMES[@]}" -le 1 ] || [ "${SERIAL_BUILD:-}" = "1" ]; then - # Serial path — preserves the old behaviour for single-runtime rebuilds and - # for CI environments that prefer bounded concurrency (set SERIAL_BUILD=1). - for dir_name in "${RUNTIMES[@]}"; do - tag="$(dir_to_tag "$dir_name")" - log "Building workspace-template:$tag (serial) ..." - if docker build -t "workspace-template:$tag" -f "adapters/$dir_name/Dockerfile" . ; then - log "workspace-template:$tag built" - else - err "workspace-template:$tag FAILED" - FAILED+=("$tag") - fi - done -else - # Parallel path — fan out one `docker build` per adapter, capture each - # output to /tmp/build_.log, wait for all, then tally. - declare -a PIDS=() - declare -a TAGS=() - for dir_name in "${RUNTIMES[@]}"; do - tag="$(dir_to_tag "$dir_name")" - log "Building workspace-template:$tag (parallel, log=/tmp/build_${tag}.log) ..." - docker build -t "workspace-template:$tag" \ - -f "adapters/$dir_name/Dockerfile" . \ - > "/tmp/build_${tag}.log" 2>&1 & - PIDS+=("$!") - TAGS+=("$tag") - done - - # Wait for each, report per-tag outcome. - for i in "${!PIDS[@]}"; do - pid="${PIDS[$i]}" - tag="${TAGS[$i]}" - if wait "$pid"; then - log "workspace-template:$tag built" - else - err "workspace-template:$tag FAILED — see /tmp/build_${tag}.log" - FAILED+=("$tag") - fi - done -fi - -echo "" -if [ ${#FAILED[@]} -eq 0 ]; then - log "All ${#RUNTIMES[@]} runtime images built successfully" -else - err "${#FAILED[@]} failed: ${FAILED[*]}" - exit 1 -fi +log "Done. Adapters are in standalone template repos — see docs/workspace-runtime-package.md" diff --git a/workspace-template/entrypoint.sh b/workspace-template/entrypoint.sh index b00ccb3e..35f526ee 100644 --- a/workspace-template/entrypoint.sh +++ b/workspace-template/entrypoint.sh @@ -52,33 +52,11 @@ else: print('langgraph') " 2>/dev/null || echo "langgraph") -# Normalize runtime name for directory lookup (claude-code -> claude_code) -ADAPTER_DIR=$(echo "$RUNTIME" | tr '-' '_') - echo "=== Molecule AI Workspace ===" echo "Runtime: $RUNTIME" -# Install adapter-specific Python requirements (skip if already pre-installed in image) -REQ_FILE="/app/adapters/${ADAPTER_DIR}/requirements.txt" -if [ -f "$REQ_FILE" ]; then - if grep -q '^[^#]' "$REQ_FILE" 2>/dev/null; then - # Check if first package is already installed - FIRST_PKG=$(grep '^[^#]' "$REQ_FILE" | head -1 | sed 's/[>=<].*//') - if python3 -c "import importlib; importlib.import_module('${FIRST_PKG//-/_}')" 2>/dev/null; then - echo "Adapter deps already installed (${FIRST_PKG})" - else - echo "Installing Python adapter dependencies..." - pip install --no-cache-dir --user -q -r "$REQ_FILE" 2>&1 | tail -3 - fi - fi -fi - -# Install adapter-specific npm packages (for Node.js-based runtimes like OpenClaw) -NPM_FILE="/app/adapters/${ADAPTER_DIR}/package.json" -if [ -f "$NPM_FILE" ]; then - echo "Installing npm adapter dependencies..." - cd "/app/adapters/${ADAPTER_DIR}" && npm install --production 2>&1 | tail -3 - cd /app -fi +# NOTE: Adapter-specific deps are now pre-installed in each adapter's Docker image +# (standalone template repos). Each image installs molecule-ai-workspace-runtime +# from PyPI plus the adapter-specific requirements. No per-runtime pip install needed here. exec python3 main.py diff --git a/workspace-template/pyproject.toml b/workspace-template/pyproject.toml new file mode 100644 index 00000000..ed64cbc3 --- /dev/null +++ b/workspace-template/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["setuptools>=68.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "molecule-ai-workspace-runtime" +version = "0.1.0" +description = "Molecule AI workspace runtime — shared infrastructure for all agent adapters" +requires-python = ">=3.11" +license = {text = "BSL-1.1"} +readme = "README.md" +# Don't pin heavy deps — each adapter adds its own +dependencies = [ + "a2a-sdk[http-server]>=0.3.25", + "httpx>=0.27.0", + "uvicorn>=0.30.0", + "starlette>=0.38.0", + "websockets>=12.0", + "pyyaml>=6.0", + "langchain-core>=0.3.0", + "opentelemetry-api>=1.24.0", + "opentelemetry-sdk>=1.24.0", + "opentelemetry-exporter-otlp-proto-http>=1.24.0", + "temporalio>=1.7.0", +] + +[project.scripts] +molecule-runtime = "molecule_runtime.main:main_sync" + +[tool.setuptools.packages.find] +where = ["."] +include = ["molecule_runtime*"] + +[tool.setuptools.package-data] +"molecule_runtime" = ["py.typed"] diff --git a/workspace-template/tests/test_hermes_adapter.py b/workspace-template/tests/test_hermes_adapter.py index eff8b34e..d401ea11 100644 --- a/workspace-template/tests/test_hermes_adapter.py +++ b/workspace-template/tests/test_hermes_adapter.py @@ -48,22 +48,33 @@ class TestHermesShellLayout: def test_directory_exists(self): assert HERMES_DIR.is_dir(), "adapters/hermes/ directory is missing" + @pytest.mark.skip( + reason="Dockerfile moved to molecule-ai-workspace-template-hermes standalone repo" + ) def test_dockerfile_present(self): assert (HERMES_DIR / "Dockerfile").is_file(), "Dockerfile missing from hermes shell" def test_init_py_present(self): assert (HERMES_DIR / "__init__.py").is_file(), "__init__.py missing from hermes shell" + @pytest.mark.skip( + reason="requirements.txt moved to molecule-ai-workspace-template-hermes standalone repo" + ) def test_requirements_txt_present(self): assert (HERMES_DIR / "requirements.txt").is_file(), "requirements.txt missing" # --------------------------------------------------------------------------- # 2. requirements.txt — primary dependency contract +# NOTE: requirements.txt has moved to the standalone template repo. +# The source-of-truth checks below are now in that repo's own test suite. # --------------------------------------------------------------------------- class TestHermesRequirements: + @pytest.mark.skip( + reason="requirements.txt moved to molecule-ai-workspace-template-hermes standalone repo" + ) def test_openai_version_pin(self): text = (HERMES_DIR / "requirements.txt").read_text() assert "openai>=1.0.0" in text, ( @@ -71,6 +82,9 @@ class TestHermesRequirements: "the Hermes adapter relies on the OpenAI-compat client for Nous Portal / OpenRouter." ) + @pytest.mark.skip( + reason="requirements.txt moved to molecule-ai-workspace-template-hermes standalone repo" + ) def test_no_heavy_framework_deps(self): """PR-1 shell must not introduce heavy deps that aren't committed to yet.""" text = (HERMES_DIR / "requirements.txt").read_text().lower()