Platform stores per-workspace LLM credentials under a single key
(ANTHROPIC_AUTH_TOKEN in workspace_secrets). But downstream tools
expect different env var names depending on the token type:
sk-ant-oat01-* → CLAUDE_CODE_OAUTH_TOKEN (Claude Code OAuth session)
sk-ant-api03-* → ANTHROPIC_API_KEY (direct Anthropic API)
sk-cp-* → ANTHROPIC_AUTH_TOKEN (proxy: MiniMax, gateways)
Without normalisation, an OAuth token under ANTHROPIC_AUTH_TOKEN gets
sent as a bearer to api.anthropic.com, which responds:
401 authentication_error: OAuth authentication is currently not
supported.
This was a platform-wide footgun: anyone rotating LLM keys had to
know the exact env var for each token type, AND make sure stale
overrides were cleared, AND set ANTHROPIC_BASE_URL correctly for
proxies (or NOT set for native Claude). Nothing downstream could
help — the SDK just saw the wrong var.
Fix:
- New molecule_runtime/llm_auth.py — normalise_llm_env() mutates
os.environ (or any dict) to the correct shape based on token
prefix. Returns a NormalisationResult for logging.
- main.py calls it as step 0, before any adapter/executor import.
Every adapter (claude-code, langgraph, crewai, autogen, hermes,
…) benefits automatically — no per-adapter branching needed.
- 11 unit tests covering all prefix paths, edge cases, and the
"operator deliberately set CLAUDE_CODE_OAUTH_TOKEN" precedence
rule.
Operationally: this means operators can keep using one
ANTHROPIC_AUTH_TOKEN slot in platform settings and just paste
whatever token the agent needs. No env-var-name awareness required.
Tested locally: 11/11 new tests pass. 83 other tests unchanged
(pre-existing failures on staging are all unrelated:
test_workspace_id_validation, test_a2a_mcp_server RBAC, the
test_imports.main module-walker — same signature as on staging
HEAD before this PR).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .github/workflows | ||
| molecule_runtime | ||
| tests | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
molecule-ai-workspace-runtime
Shared Python runtime infrastructure for all Molecule AI agent adapters.
This package provides the core machinery that every Molecule AI workspace container needs:
- A2A server — Registers with the platform, heartbeats, serves A2A JSON-RPC
- Adapter interface —
BaseAdapter/AdapterConfig/SetupResult - Built-in tools — delegation, memory, approvals, sandbox, telemetry
- Skill loader — loads and hot-reloads skill modules from
/configs/skills/ - Plugin system — per-workspace + shared plugin discovery and install
- Config / preflight — YAML config loading with validation
Installation
pip install molecule-ai-workspace-runtime
Adapter Discovery
The runtime discovers adapters in two ways:
-
ADAPTER_MODULEenv var (standalone adapter repos):ADAPTER_MODULE=my_adapter molecule-runtimeThe module must export an
Adapterclass extendingBaseAdapter. -
Built-in subdirectory scan (monorepo local dev): Scans
molecule_runtime/adapters/subdirectories forAdapterclasses.
Writing an Adapter
from molecule_runtime.adapters.base import BaseAdapter, AdapterConfig
from a2a.server.agent_execution import AgentExecutor
class Adapter(BaseAdapter):
@staticmethod
def name() -> str:
return "my-runtime"
@staticmethod
def display_name() -> str:
return "My Runtime"
@staticmethod
def description() -> str:
return "My custom agent runtime"
async def setup(self, config: AdapterConfig) -> None:
result = await self._common_setup(config)
# Store result attributes for create_executor
async def create_executor(self, config: AdapterConfig) -> AgentExecutor:
# Return an AgentExecutor instance
...
Set ADAPTER_MODULE=my_package.adapter and run molecule-runtime.
License
BSL-1.1 — see LICENSE for details.