Compare commits
35 Commits
main
...
fix/docs-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75dab2c6dc | ||
|
|
aa46faeb78 | ||
|
|
d6282cb127 | ||
|
|
86cf6c3a5c | ||
|
|
dc08033b9f | ||
|
|
684ec817bd | ||
|
|
48e691c0e0 | ||
|
|
18bb733b36 | ||
|
|
a0b3ab9876 | ||
|
|
cfa2fc3d6e | ||
|
|
e0910a1734 | ||
|
|
c93388441d | ||
|
|
651fe00998 | ||
|
|
1e8f9dc295 | ||
|
|
1408cbae94 | ||
|
|
9c28fcf22c | ||
|
|
a45a1c65c3 | ||
|
|
49f95877f8 | ||
|
|
18866f6d7e | ||
|
|
c6494de5ad | ||
|
|
c5421c61f5 | ||
|
|
2ccd4b99d0 | ||
|
|
5f0871707e | ||
|
|
b80891b312 | ||
|
|
149c315dfa | ||
|
|
b26d7ee9b2 | ||
|
|
1dd9cfaaf3 | ||
|
|
28600d7956 | ||
|
|
36ab08129c | ||
|
|
ec78c7637b | ||
|
|
aa710d9310 | ||
|
|
050cd70060 | ||
|
|
1ccd92e0c8 | ||
|
|
403725b970 | ||
|
|
03a222d9b5 |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -6,7 +6,12 @@ on:
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
# Self-hosted Mac mini — this repo is private and the org's
|
||||
# GitHub-hosted minute budget is exhausted (every ubuntu-latest job
|
||||
# dies in 2s with no step output). Per the 2026-04-22 carve-out:
|
||||
# private repos run on self-hosted; public repos use ubuntu-latest
|
||||
# (still free).
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
@ -1,29 +1,100 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
// Three quick-start lanes — keeps the home page from being a wall of text
|
||||
// and lets builders, operators, and integrators each find their entry
|
||||
// point in one click.
|
||||
const lanes = [
|
||||
{
|
||||
kicker: '01',
|
||||
title: 'Build a workspace',
|
||||
body: 'Pick a runtime template (Claude Code, LangGraph, CrewAI, Hermes, …), wire your tools, and ship.',
|
||||
href: '/docs/workspace',
|
||||
cta: 'Workspace guide →',
|
||||
},
|
||||
{
|
||||
kicker: '02',
|
||||
title: 'Run an organisation',
|
||||
body: 'Topology, A2A, three-tier memory, governance — the platform layer that ties multi-agent teams together.',
|
||||
href: '/docs/platform',
|
||||
cta: 'Platform reference →',
|
||||
},
|
||||
{
|
||||
kicker: '03',
|
||||
title: 'Publish to the Marketplace',
|
||||
body: 'Plugins, agents, and org bundles ship as signed manifests. Authors keep 80%, paid via Stripe Connect.',
|
||||
href: '/docs/marketplace',
|
||||
cta: 'Author guide →',
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-6 py-24 text-center">
|
||||
<h1 className="mb-4 text-5xl font-bold tracking-tight sm:text-6xl">
|
||||
Molecule AI
|
||||
</h1>
|
||||
<p className="mb-8 max-w-2xl text-lg text-fd-muted-foreground">
|
||||
Build and run multi-agent organisations. Templates, plugins, channels,
|
||||
and the runtime that ties them together — documented end to end.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="rounded-md bg-fd-primary px-5 py-2.5 text-sm font-medium text-fd-primary-foreground transition-colors hover:opacity-90"
|
||||
>
|
||||
Read the docs
|
||||
</Link>
|
||||
<Link
|
||||
href="https://git.moleculesai.app/molecule-ai/molecule-core"
|
||||
className="rounded-md border border-fd-border px-5 py-2.5 text-sm font-medium transition-colors hover:bg-fd-muted"
|
||||
>
|
||||
View on Gitea
|
||||
</Link>
|
||||
<main className="flex flex-1 flex-col">
|
||||
{/* Statusbar — mirrors the landing's "All systems · status.* · phase" strip */}
|
||||
<div className="border-b border-fd-border bg-fd-muted px-6 py-1.5 text-[11px] font-mono text-fd-muted-foreground flex flex-wrap justify-between gap-4">
|
||||
<span>
|
||||
<span className="inline-block size-1.5 rounded-full bg-[#2f7a4d] align-middle mr-1.5" />
|
||||
All systems · status.moleculesai.app
|
||||
</span>
|
||||
<span>Phase 33 shipped · Phase 35 Marketplace public beta</span>
|
||||
</div>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="px-6 py-20 sm:py-28 max-w-6xl mx-auto w-full">
|
||||
<div className="text-[11px] font-mono uppercase tracking-[0.08em] text-fd-muted-foreground mb-4 flex items-center gap-2">
|
||||
<span className="inline-block size-1.5 rounded-full bg-[#c0532b]" />
|
||||
Documentation
|
||||
</div>
|
||||
<h1 className="text-5xl sm:text-6xl font-semibold tracking-tight leading-[1.05] mb-5 max-w-3xl">
|
||||
The operating system for{' '}
|
||||
<span className="text-[#3b5bdb]">AI agent organizations.</span>
|
||||
</h1>
|
||||
<p className="text-lg text-fd-muted-foreground max-w-2xl leading-relaxed mb-8">
|
||||
Build and run multi-agent organisations the way you'd staff a company.
|
||||
Templates, plugins, channels, runtimes, governance — documented end
|
||||
to end.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="rounded-md bg-fd-primary px-5 py-2.5 text-sm font-medium text-fd-primary-foreground transition hover:opacity-90"
|
||||
>
|
||||
Read the docs
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/Molecule-AI"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-md border border-fd-border px-5 py-2.5 text-sm font-medium transition hover:bg-fd-muted"
|
||||
>
|
||||
View on GitHub
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Three lanes */}
|
||||
<section className="px-6 pb-24 max-w-6xl mx-auto w-full">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{lanes.map((lane) => (
|
||||
<Link
|
||||
key={lane.kicker}
|
||||
href={lane.href}
|
||||
className="group rounded-lg border border-fd-border bg-fd-card p-6 transition hover:border-fd-foreground hover:-translate-y-0.5"
|
||||
>
|
||||
<div className="text-[11px] font-mono text-[#3b5bdb] mb-3 tracking-[0.08em]">
|
||||
{lane.kicker}
|
||||
</div>
|
||||
<h3 className="text-base font-semibold mb-2">{lane.title}</h3>
|
||||
<p className="text-sm text-fd-muted-foreground leading-relaxed mb-4">
|
||||
{lane.body}
|
||||
</p>
|
||||
<div className="text-xs font-mono text-fd-foreground group-hover:text-[#3b5bdb] transition">
|
||||
{lane.cta}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,32 @@
|
||||
@import 'tailwindcss';
|
||||
@import 'fumadocs-ui/css/neutral.css';
|
||||
@import 'fumadocs-ui/css/preset.css';
|
||||
|
||||
/* Warm-paper light theme — aligned with the landing page (moleculesai.app).
|
||||
Tokens map fumadocs' @theme variables onto our brand palette so docs,
|
||||
marketing, and the canvas read as one product. */
|
||||
@theme {
|
||||
--font-sans: var(--font-geist), ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: var(--font-mono), ui-monospace, SFMono-Regular, monospace;
|
||||
|
||||
--color-fd-background: #fafaf7;
|
||||
--color-fd-foreground: #15181c;
|
||||
--color-fd-muted: #f3f1ec;
|
||||
--color-fd-muted-foreground: #5a5e66;
|
||||
--color-fd-popover: #ffffff;
|
||||
--color-fd-popover-foreground: #15181c;
|
||||
--color-fd-card: #ffffff;
|
||||
--color-fd-card-foreground: #15181c;
|
||||
--color-fd-border: #e6e2d8;
|
||||
--color-fd-primary: #3b5bdb;
|
||||
--color-fd-primary-foreground: #ffffff;
|
||||
--color-fd-secondary: #efece4;
|
||||
--color-fd-secondary-foreground: #15181c;
|
||||
--color-fd-accent: #efece4;
|
||||
--color-fd-accent-foreground: #15181c;
|
||||
--color-fd-ring: #3b5bdb;
|
||||
--color-fd-overlay: hsla(0, 0%, 0%, 0.18);
|
||||
}
|
||||
|
||||
/* Dark mode keeps fumadocs' neutral defaults — readers expect docs sites
|
||||
to honor their system preference, and our landing only ships light. */
|
||||
|
||||
@ -1,7 +1,50 @@
|
||||
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||
|
||||
// Molecule logo — the same triangle-of-nodes mark used on moleculesai.app.
|
||||
// Inlined as a JSX element so fumadocs renders it in the topbar without a
|
||||
// separate asset request.
|
||||
const MoleculeLogo = (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="14" cy="6" r="2.5" fill="currentColor" />
|
||||
<circle cx="6" cy="20" r="2.5" fill="currentColor" />
|
||||
<circle cx="22" cy="20" r="2.5" fill="currentColor" />
|
||||
<circle
|
||||
cx="14"
|
||||
cy="14"
|
||||
r="1.6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.2"
|
||||
/>
|
||||
<line x1="14" y1="8.5" x2="14" y2="12.6" stroke="currentColor" strokeWidth="1.2" />
|
||||
<line x1="8" y1="18.5" x2="12.7" y2="14.8" stroke="currentColor" strokeWidth="1.2" />
|
||||
<line x1="20" y1="18.5" x2="15.3" y2="14.8" stroke="currentColor" strokeWidth="1.2" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const baseOptions: BaseLayoutProps = {
|
||||
nav: {
|
||||
title: 'Molecule AI',
|
||||
title: (
|
||||
<span className="flex items-center gap-2 font-semibold tracking-tight">
|
||||
{MoleculeLogo}
|
||||
<span>Molecule AI</span>
|
||||
<span className="text-xs uppercase tracking-[0.08em] text-fd-muted-foreground font-mono">
|
||||
Docs
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
url: 'https://doc.moleculesai.app',
|
||||
},
|
||||
links: [
|
||||
{ text: 'Platform', url: 'https://app.moleculesai.app', external: true },
|
||||
{ text: 'Marketplace', url: 'https://market.moleculesai.app', external: true },
|
||||
{ text: 'Landing', url: 'https://www.moleculesai.app', external: true },
|
||||
],
|
||||
githubUrl: 'https://github.com/Molecule-AI',
|
||||
};
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import './global.css';
|
||||
import { RootProvider } from 'fumadocs-ui/provider/next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { Geist, JetBrains_Mono } from 'next/font/google';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const inter = Inter({
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-geist',
|
||||
});
|
||||
|
||||
const jetbrains = JetBrains_Mono({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-mono',
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
@ -19,8 +25,12 @@ export const metadata = {
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={inter.className} suppressHydrationWarning>
|
||||
<body className="flex flex-col min-h-screen">
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geist.variable} ${jetbrains.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body className="flex flex-col min-h-screen font-sans">
|
||||
<RootProvider>{children}</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -9,16 +9,18 @@ The `workspace/` directory is Molecule AI's unified runtime image. Every provisi
|
||||
|
||||
## Runtime Matrix In Current `main`
|
||||
|
||||
Current `main` ships six adapters:
|
||||
Current `main` ships eight adapters:
|
||||
|
||||
- `langgraph`
|
||||
- `deepagents`
|
||||
- `claude-code`
|
||||
- `langgraph`
|
||||
- `crewai`
|
||||
- `autogen`
|
||||
- `deepagents`
|
||||
- `hermes`
|
||||
- `gemini-cli`
|
||||
- `openclaw`
|
||||
|
||||
This is the merged runtime surface today. Branch-level experiments such as NemoClaw are separate and should be treated as roadmap/WIP, not merged support.
|
||||
This is the merged runtime surface today. The canonical allowlist lives in `workspace-server/internal/handlers/admin_workspace_images.go` (`AllRuntimes`). Anything not on this list — including BYO runtimes — registers via the [external workspace](../external-agents.mdx) path, not as a built-in adapter.
|
||||
|
||||
Adapter-specific behavior is documented in [Agent Runtime Adapters](./cli-runtime.md).
|
||||
|
||||
@ -73,6 +75,94 @@ At a high level, `workspace/main.py` does this:
|
||||
10. Start the skill watcher when skills are configured.
|
||||
11. Serve the A2A app through Uvicorn.
|
||||
|
||||
## Boot-Smoke Contract (`MOLECULE_SMOKE_MODE`)
|
||||
|
||||
The image-publish CI pipeline runs each template's image with `MOLECULE_SMOKE_MODE=1` to exercise lazy imports inside `executor.execute()` against stub credentials and no network. The runtime detects the env var, invokes `executor.execute()` once with a stubbed `RequestContext` and a short timeout, then exits — registration, heartbeats, and the A2A server are skipped.
|
||||
|
||||
This catches lazy imports that pure `python3 -c "import adapter"` smokes miss: imports nested inside `if`-branches, deferred until first call, or behind `importlib.import_module()`.
|
||||
|
||||
### What adapter authors need to do
|
||||
|
||||
**Most adapters need to do nothing.** If `setup()` only writes files, parses config, or instantiates Python objects, the smoke gate just works.
|
||||
|
||||
**Adapters whose `setup()` does real I/O must opt out of that I/O under smoke mode.** This applies to:
|
||||
|
||||
- spawning subprocesses that require valid credentials (e.g. a gateway daemon)
|
||||
- making real network calls
|
||||
- writing to filesystem locations that need a specific uid/gid the smoke harness can't guarantee
|
||||
|
||||
The contract:
|
||||
|
||||
```python
|
||||
async def setup(self, config: AdapterConfig) -> None:
|
||||
if os.environ.get("MOLECULE_SMOKE_MODE") == "1":
|
||||
return # skip real I/O; runtime's smoke short-circuit handles the rest
|
||||
# ... real setup ...
|
||||
```
|
||||
|
||||
For shell entrypoints that wrap `molecule-runtime`:
|
||||
|
||||
```bash
|
||||
if [ "${MOLECULE_SMOKE_MODE:-0}" = "1" ]; then
|
||||
exec molecule-runtime
|
||||
fi
|
||||
```
|
||||
|
||||
### What gets exercised under smoke mode
|
||||
|
||||
- All `/app/*.py` modules import cleanly (covered by a separate static-import smoke step)
|
||||
- `adapter.setup()` runs (with the opt-out above for I/O-heavy adapters)
|
||||
- `adapter.create_executor()` runs
|
||||
- `executor.execute()` is invoked once against a stub `RequestContext`/`EventQueue` with `MOLECULE_SMOKE_TIMEOUT_SECS` (default 5s); a clean timeout exits 0, an import error exits non-zero
|
||||
|
||||
### What the gate does NOT prove
|
||||
|
||||
A green gate means **"imports are healthy enough that `executor.execute()` reaches its body"** — that's the regression class the gate exists to catch (lazy `from x import y` inside an `if`-branch, or `importlib.import_module()` on a path that breaks after a wheel bump).
|
||||
|
||||
It does **not** prove that `execute()` produces the right output for real input. The harness reports PASS in three distinct cases:
|
||||
|
||||
1. **Clean return** — execute() ran to completion within the timeout.
|
||||
2. **Timeout** — execute() was still running when the timer fired (typical for adapters that do real I/O inside execute(): subprocess to a gateway, httpx call to an upstream LLM).
|
||||
3. **Any non-import exception** — execute() raised `RuntimeError`, auth errors, validation errors, etc. The harness only fails on `ImportError`/`ModuleNotFoundError`.
|
||||
|
||||
The stub `RequestContext` carries a non-empty `"smoke test"` text message (so adapters relying on `extract_message_text(ctx)` returning input still work), and the harness never drains the `EventQueue` — what `execute()` writes back is ignored.
|
||||
|
||||
If you need correctness coverage, write a separate integration test that runs the workspace against real or mocked infrastructure — the smoke gate is a strict subset.
|
||||
|
||||
### Stub env the smoke harness sets
|
||||
|
||||
| Var | Value |
|
||||
|---|---|
|
||||
| `MOLECULE_SMOKE_MODE` | `1` |
|
||||
| `MOLECULE_SMOKE_TIMEOUT_SECS` | `10` (CI default) |
|
||||
| `WORKSPACE_ID` | `fake-smoke` |
|
||||
| `PYTHONPATH` | `/app` (mirrors the platform provisioner) |
|
||||
| `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `OPENAI_API_KEY` | `sk-fake-smoke-*` |
|
||||
|
||||
A `config.yaml` from the template repo's root is mounted at `/configs/config.yaml`.
|
||||
|
||||
## Runtime Distribution: PyPI Is Canonical, The Git Mirror May Lag
|
||||
|
||||
The runtime ships as **two surfaces**, and only one of them is wire-truth.
|
||||
|
||||
| Surface | Repo / location | Role |
|
||||
|---|---|---|
|
||||
| **PyPI wheel** | `pip install molecule-ai-workspace-runtime==X.Y.Z` | **Canonical artifact.** Workspace template images, the controlplane runtime smoke harness, and self-hosters all consume this. |
|
||||
| **Git mirror** | [`Molecule-AI/molecule-ai-workspace-runtime`](https://github.com/Molecule-AI/molecule-ai-workspace-runtime) | **Human-readable copy.** Exists for browsing + giving `mirror-guard` a concrete branch to enforce its "no direct PRs" policy against. |
|
||||
|
||||
Both are produced by the [`publish-runtime.yml`](https://github.com/Molecule-AI/molecule-monorepo/blob/main/.github/workflows/publish-runtime.yml) workflow on every push to `molecule-monorepo/workspace/`, but **the wheel publish and the mirror push are separate steps**. The mirror push can lag the wheel by hours, or be skipped entirely on transient failures while the wheel still ships.
|
||||
|
||||
If you're chasing "is module X in the published runtime yet?", trust the wheel listing, not the mirror's `git log`:
|
||||
|
||||
```bash
|
||||
pip download molecule-ai-workspace-runtime==X.Y.Z --no-deps
|
||||
unzip -l molecule_ai_workspace_runtime-X.Y.Z-*.whl | grep your_module
|
||||
```
|
||||
|
||||
To find out what version the controlplane is actually deploying, check the workspace template image's `requirements.txt` pin (it's a `>=`, so the resolved version is whatever PyPI hands back at image-build time — not whatever's in the mirror).
|
||||
|
||||
**Do not edit the git mirror directly.** `mirror-guard` rejects all PRs to `molecule-ai-workspace-runtime`. Edit `molecule-monorepo/workspace/` and let `publish-runtime.yml` regenerate both surfaces.
|
||||
|
||||
## Core Runtime Pieces
|
||||
|
||||
| File | Responsibility |
|
||||
@ -274,28 +364,6 @@ Each workspace exposes an A2A server, builds an Agent Card, and registers with t
|
||||
|
||||
But the long-term collaboration model remains direct workspace-to-workspace communication via A2A.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Playwright / browser system libs are not installed
|
||||
|
||||
The base `molecule-ai-workspace-runtime` image is built on `python:3.11-slim` with Node.js 22, git, and `gh` — about 500 MB. It deliberately **does not** include the system libraries Chromium needs (`libnss3`, `libatk-bridge2.0-0`, `libxkbcommon0`, `libcups2`, `libdrm2`, `libxcomposite1`, `libxdamage1`, `libxrandr2`, `libgbm1`, `libpango-1.0-0`, `libasound2`, etc.). Adding them would inflate the image by ~200–250 MB (~40%) for every workspace, even though only frontend / QA workspaces ever launch a browser.
|
||||
|
||||
Practical consequences:
|
||||
|
||||
- `npx playwright test` (and any other Chromium-driven E2E tooling) **will fail at browser launch** when run from inside an in-container workspace agent.
|
||||
- The error surface is missing-shared-object messages such as `error while loading shared libraries: libnss3.so` or `Host system is missing dependencies to run browsers`.
|
||||
- Unit and integration tests (Vitest, Jest, etc.) that don't spawn a real browser are unaffected.
|
||||
|
||||
Recommended workflow:
|
||||
|
||||
1. **Run E2E in CI**, not in-container. The Gitea Actions self-hosted runner (and GitHub Actions runners used by mirror repos) has the full Playwright dep set installed and is the supported surface for E2E. Push a branch, let CI run the suite.
|
||||
2. **Local debugging** of a single failing spec is best done on a developer laptop with `npx playwright install-deps` run once.
|
||||
3. **In-container iteration** on test logic itself is fine — write specs, lint them, type-check them — just don't expect `playwright test` to actually launch a browser.
|
||||
|
||||
If a particular workspace role genuinely needs in-container E2E (a dedicated QA template, for instance), the right place to layer Playwright deps is in a **role-specific adapter template image** that does `FROM molecule-ai-workspace-runtime:<tag>` and adds `RUN npx playwright install-deps`. Open a request against `molecule-ai-workspace-runtime` if you need this template stamped.
|
||||
|
||||
Tracking issue: [molecule-ai/molecule-app#7](https://git.moleculesai.app/molecule-ai/molecule-app/issues/7).
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [Agent Runtime Adapters](./cli-runtime.md)
|
||||
|
||||
@ -138,7 +138,7 @@ These controls complement the platform-level secret redaction described in the [
|
||||
|
||||
**Stack:** Go / Bubbletea + Lipgloss
|
||||
|
||||
A terminal UI dashboard for real-time workspace monitoring, event log streaming, health overview, and delete/filter operations. Reads `MOLECLI_URL` (default `http://localhost:8080`) to locate the platform. Now published as a standalone repo at `git.moleculesai.app/molecule-ai/molecule-cli`.
|
||||
A terminal UI dashboard for real-time workspace monitoring, event log streaming, health overview, and delete/filter operations. Reads `MOLECLI_URL` (default `http://localhost:8080`) to locate the platform. Now published as a standalone repo at `github.com/Molecule-AI/molecule-cli`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ title: "Molecule AI — Comprehensive Technical Documentation"
|
||||
# Molecule AI — Comprehensive Technical Documentation
|
||||
|
||||
> Definitive technical reference for the Molecule AI Agent Team platform.
|
||||
> Based on a full non-invasive scan of the [molecule-core](https://git.moleculesai.app/molecule-ai/molecule-core) repository (formerly `molecule-monorepo`, renamed during the post-2026-05-06 GitHub-org-suspension recovery).
|
||||
> Based on a full non-invasive scan of the [molecule-monorepo](https://github.com/Molecule-AI/molecule-monorepo) repository.
|
||||
|
||||
---
|
||||
|
||||
@ -573,14 +573,16 @@ compliance:
|
||||
|
||||
| Adapter | Core Strength | Image Tag |
|
||||
|---------|--------------|-----------|
|
||||
| **LangGraph** | Graph-based state machine, tool use, streaming | `workspace-template:langgraph` |
|
||||
| **DeepAgents** | Deep planning, multi-step task decomposition | `workspace-template:deepagents` |
|
||||
| **Claude Code** | Native coding workflows, CLI continuity, OAuth auth | `workspace-template:claude-code` |
|
||||
| **LangGraph** | Graph-based state machine, tool use, streaming | `workspace-template:langgraph` |
|
||||
| **CrewAI** | Role-based crews, structured task orchestration | `workspace-template:crewai` |
|
||||
| **AutoGen** | Multi-agent conversations, explicit strategies | `workspace-template:autogen` |
|
||||
| **DeepAgents** | Deep planning, multi-step task decomposition | `workspace-template:deepagents` |
|
||||
| **Hermes** | Multi-provider dispatch (Anthropic/Gemini native + OpenAI-compatible shim) | `workspace-template:hermes` |
|
||||
| **Gemini CLI** | Google Gemini CLI workspace | `workspace-template:gemini-cli` |
|
||||
| **OpenClaw** | CLI-native runtime, own session model | `workspace-template:openclaw` |
|
||||
|
||||
**Branch-level WIP**: NemoClaw (NVIDIA T4 + Docker socket) on `feat/nemoclaw-t4-docker`.
|
||||
The canonical allowlist lives in `workspace-server/internal/handlers/admin_workspace_images.go` (`AllRuntimes`). Anything outside this list registers via the external-workspace path.
|
||||
|
||||
Each adapter implements `setup()` + `create_executor()`. The base adapter provides shared infrastructure: system prompt assembly, skill loading, tool registration, coordinator detection, plugin injection.
|
||||
|
||||
@ -978,7 +980,6 @@ Tools call `resp.json()` without catching JSON decode errors. Should wrap in try
|
||||
|
||||
| Branch | Feature | Status |
|
||||
|--------|---------|--------|
|
||||
| `feat/nemoclaw-t4-docker` | NemoClaw adapter (NVIDIA T4 support) | WIP |
|
||||
| Backlog | Firecracker backend (faster cold starts) | Planned |
|
||||
| Backlog | E2B backend (cloud-hosted code sandbox) | Planned |
|
||||
| Backlog | pgvector semantic memory search | Planned |
|
||||
@ -1153,11 +1154,11 @@ Molecule AI's workspace abstraction is **runtime-agnostic by design**. A workspa
|
||||
|
||||
## Links
|
||||
|
||||
- **Gitea**: https://git.moleculesai.app/molecule-ai/molecule-core
|
||||
- **Architecture Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/architecture
|
||||
- **API Protocol**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/agent-runtime
|
||||
- **Product Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/product
|
||||
- **GitHub**: https://github.com/Molecule-AI/molecule-monorepo
|
||||
- **Architecture Docs**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/architecture
|
||||
- **API Protocol**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/agent-runtime
|
||||
- **Product Docs**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/product
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ Canvas (Next.js :3000) ←WebSocket→ Platform (Go :8080) ←HTTP→ Postgres +
|
||||
|
||||
- **Workspace Server** (`workspace-server/`): 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/`): 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.
|
||||
- **Workspace Runtime** (`workspace/`): Shared runtime published as [`molecule-ai-workspace-runtime`](https://pypi.org/project/molecule-ai-workspace-runtime/) on PyPI. Supports Claude Code, LangGraph, CrewAI, AutoGen, DeepAgents, Hermes, Gemini CLI, and OpenClaw. 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** (`workspace-server/cmd/cli/`): Go TUI dashboard (Bubbletea + Lipgloss) — real-time workspace monitoring, event log, health overview, delete/filter operations.
|
||||
|
||||
## Key Architectural Patterns
|
||||
|
||||
@ -27,7 +27,8 @@ CP (Railway): staging service production service
|
||||
staging.api.moleculesai.app api.moleculesai.app
|
||||
|
||||
Tenant EC2s: staging EC2 instances production EC2 instances
|
||||
*.staging.moleculesai.app *.moleculesai.app
|
||||
<slug>.staging.moleculesai.app <slug>.moleculesai.app
|
||||
(per-tenant CNAME, no wildcard) (per-tenant CNAME, no wildcard)
|
||||
|
||||
App (Vercel): staging.app.moleculesai.app app.moleculesai.app
|
||||
(Vercel preview) (Vercel production)
|
||||
@ -38,8 +39,10 @@ DB (Neon): staging branch main branch
|
||||
Docker images: platform-tenant:staging platform-tenant:latest
|
||||
(GHCR) (GHCR)
|
||||
|
||||
Cloudflare: *.staging.moleculesai.app *.moleculesai.app
|
||||
(separate tunnel/worker) (tunnel per tenant)
|
||||
Cloudflare: per-tenant CNAMEs under per-tenant CNAMEs under
|
||||
staging.moleculesai.app moleculesai.app
|
||||
(one CNAME + one tunnel (one CNAME + one tunnel
|
||||
per provisioned tenant) per provisioned tenant)
|
||||
```
|
||||
|
||||
## Deploy flow
|
||||
@ -115,15 +118,35 @@ platform-tenant:sha-xxxxx — immutable, pinned to specific commit
|
||||
# pushes :latest only on manual promote
|
||||
```
|
||||
|
||||
### 5. Cloudflare: staging subdomain
|
||||
### 5. Cloudflare: per-tenant CNAMEs (no wildcard)
|
||||
|
||||
Option A (simple): `*.staging.moleculesai.app` with its own tunnel/worker
|
||||
Option B (full): separate Cloudflare zone for staging (overkill)
|
||||
There is **no `*.staging.moleculesai.app` wildcard record** and there is no
|
||||
`*.moleculesai.app` wildcard either. The control plane writes a per-tenant
|
||||
CNAME at provision time, pointing `<slug>.<env-domain>` at that tenant's
|
||||
Cloudflare tunnel (`<tunnel-id>.cfargotunnel.com`).
|
||||
|
||||
Recommend Option A:
|
||||
- Add `staging.moleculesai.app` DNS records
|
||||
- Staging tenants get `slug.staging.moleculesai.app` subdomains
|
||||
- Production tenants get `slug.moleculesai.app` (unchanged)
|
||||
Verified in `molecule-controlplane/internal/provisioner/ec2.go` — the
|
||||
provisioner calls `Tunnel.CreateTunnelDNS(ctx, slug, domain, tunnelID)`
|
||||
during workspace provision, then records a `cf_dns` row in
|
||||
`tenant_resources` with `type=CNAME` for symmetric create/delete audit.
|
||||
|
||||
Implications for staging:
|
||||
|
||||
- Staging tenants get `<slug>.staging.moleculesai.app` only **after** they
|
||||
are provisioned through the staging control plane. The CNAME is
|
||||
written as part of `Provision()`.
|
||||
- Production tenants get `<slug>.moleculesai.app` the same way, against
|
||||
the production CP.
|
||||
- Pre-provision, an unknown slug returns **NXDOMAIN**. This is correct
|
||||
behavior, not a regression — there is no wildcard to catch the lookup.
|
||||
- Tests that hit a staging slug they have not provisioned themselves
|
||||
will fail with `getaddrinfo ENOTFOUND` (Node) or `Name or service not
|
||||
known` (curl). The fix is to provision your own slug against the
|
||||
staging CP first; do not file this as an infrastructure bug.
|
||||
|
||||
The same model applies to both environments — the only difference is
|
||||
the parent zone (`staging.moleculesai.app` vs `moleculesai.app`) and the
|
||||
CP that writes the records.
|
||||
|
||||
### 6. EC2: staging tag
|
||||
|
||||
|
||||
@ -11,176 +11,70 @@ Entries are published daily at 23:50 UTC.
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- **SaaS Federation v2 tutorial**: a clean, self-contained walkthrough for platform operators who want to run multi-tenant workspaces from a single control plane. Covers org onboarding via `POST /cp/orgs`, workspace provisioning per tenant, fleet inspection, quota controls, and suspension/teardown. (`molecule-core` [#1700](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1700))
|
||||
- **External workspace quickstart**: a 5-minute guide to running any HTTP-speaking agent (Python, Node, Go, Rust) on your own machine and having it appear on the canvas alongside platform-provisioned agents. Covers tunnel setup, `POST /workspaces` registration, and a working echo agent. (`molecule-core` [#1760](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1760))
|
||||
- **SaaS Federation v2 tutorial**: a clean, self-contained walkthrough for platform operators who want to run multi-tenant workspaces from a single control plane. Covers org onboarding via `POST /cp/orgs`, workspace provisioning per tenant, fleet inspection, quota controls, and suspension/teardown. (`molecule-core` [#1700](https://github.com/Molecule-AI/molecule-core/pull/1700))
|
||||
- **External workspace quickstart**: a 5-minute guide to running any HTTP-speaking agent (Python, Node, Go, Rust) on your own machine and having it appear on the canvas alongside platform-provisioned agents. Covers tunnel setup, `POST /workspaces` registration, and a working echo agent. (`molecule-core` [#1760](https://github.com/Molecule-AI/molecule-core/pull/1760))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **SSRF guard in SaaS mode**: previously the SSRF protection was blocking all RFC-1918 private IP ranges (`10/8`, `172.16/12`, `192.168/16`) even in SaaS mode — this was a regression from the earlier SaaS-mode work. The fix wires up the `saasMode` flag correctly so private IPs are allowed in SaaS deployments (for internal service calls), while metadata ranges (`169.254/16`), CGNAT, loopback, and link-local remain blocked in every mode. IPv6 ULA (`fd00::/8`) handling is also now correct. (`molecule-core` [#1692](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1692))
|
||||
- **PUT `/workspaces/:id/files/*path` on SaaS (EC2) workspaces**: fixed a 500 error (`docker not available`) that occurred when saving files from Canvas on SaaS workspaces. The handler now detects non-Docker workspaces via `workspaces.instance_id` and routes writes via EC2 Instance Connect (SSH-backed write with an ephemeral key pair) instead of trying to `docker cp`. (`molecule-core` [#1702](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1702))
|
||||
- **SSRF guard in SaaS mode**: previously the SSRF protection was blocking all RFC-1918 private IP ranges (`10/8`, `172.16/12`, `192.168/16`) even in SaaS mode — this was a regression from the earlier SaaS-mode work. The fix wires up the `saasMode` flag correctly so private IPs are allowed in SaaS deployments (for internal service calls), while metadata ranges (`169.254/16`), CGNAT, loopback, and link-local remain blocked in every mode. IPv6 ULA (`fd00::/8`) handling is also now correct. (`molecule-core` [#1692](https://github.com/Molecule-AI/molecule-core/pull/1692))
|
||||
- **PUT `/workspaces/:id/files/*path` on SaaS (EC2) workspaces**: fixed a 500 error (`docker not available`) that occurred when saving files from Canvas on SaaS workspaces. The handler now detects non-Docker workspaces via `workspaces.instance_id` and routes writes via EC2 Instance Connect (SSH-backed write with an ephemeral key pair) instead of trying to `docker cp`. (`molecule-core` [#1702](https://github.com/Molecule-AI/molecule-core/pull/1702))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **molecli shell completion**: tab completion for `molecule` CLI in bash, zsh, fish, and PowerShell — covers all subcommands and flags. (`docs` [#79](https://git.moleculesai.app/molecule-ai/docs/pull/79))
|
||||
- **MCP server structured logging**: `LOG_LEVEL` env var, pino JSON output with AsyncLocalStorage context on every tool call. (`docs` [#78](https://git.moleculesai.app/molecule-ai/docs/pull/78))
|
||||
- **molecli shell completion**: tab completion for `molecule` CLI in bash, zsh, fish, and PowerShell — covers all subcommands and flags. (`docs` [#79](https://github.com/Molecule-AI/docs/pull/79))
|
||||
- **MCP server structured logging**: `LOG_LEVEL` env var, pino JSON output with AsyncLocalStorage context on every tool call. (`docs` [#78](https://github.com/Molecule-AI/docs/pull/78))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- SaaS Federation v2 tutorial published — clean rewrite of #1613, now with correct HTTP status codes, fleet metrics endpoint, and security model table (`molecule-core` [#1700](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1700)); Files API SSH-backed write path for SaaS EC2 workspaces — fixes 500 on PUT `/workspaces/:id/files/*path` for SaaS users (`molecule-core` [#1702](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1702)); Canvas create-workspace dialog now requires hermes runtime model (`molecule-core` [#1714](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1714)).
|
||||
- EC2 Instance Connect SSH tutorial published (`molecule-core` [#1617](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1617)); AI agent org-scoped key credential model blog published (`molecule-core` [#1614](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1614)); Phase 30 Day 2 social package ready (`molecule-core` [#1662](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1662)).
|
||||
- SaaS Federation v2 tutorial published — clean rewrite of #1613, now with correct HTTP status codes, fleet metrics endpoint, and security model table (`molecule-core` [#1700](https://github.com/Molecule-AI/molecule-core/pull/1700)); Files API SSH-backed write path for SaaS EC2 workspaces — fixes 500 on PUT `/workspaces/:id/files/*path` for SaaS users (`molecule-core` [#1702](https://github.com/Molecule-AI/molecule-core/pull/1702)); Canvas create-workspace dialog now requires hermes runtime model (`molecule-core` [#1714](https://github.com/Molecule-AI/molecule-core/pull/1714)).
|
||||
- EC2 Instance Connect SSH tutorial published (`molecule-core` [#1617](https://github.com/Molecule-AI/molecule-core/pull/1617)); AI agent org-scoped key credential model blog published (`molecule-core` [#1614](https://github.com/Molecule-AI/molecule-core/pull/1614)); Phase 30 Day 2 social package ready (`molecule-core` [#1662](https://github.com/Molecule-AI/molecule-core/pull/1662)).
|
||||
|
||||
### 🌅 Late-day updates (17:30–23:50 UTC)
|
||||
|
||||
#### 🔒 Security
|
||||
|
||||
- **Cross-tenant memory poisoning fix** (`molecule-core` [#1791](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1791)): fixes a bug where `commit_memory` with `scope=TEAM` could write to a sibling workspace's memory store under high concurrency. `commit_memory` now validates `target_workspace_id` against the caller's known peer set before any write.
|
||||
- **CWE-78 shell injection hardening** (`molecule-core` [#1885](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1885)): `shellQuote` now uses `strconv.Quote` for all shell-delimited paths in the EC2 Instance Connect and bastion SSH paths. Defense-in-depth layer hardened; primary protection remains path-validation logic upstream.
|
||||
- **Cross-tenant memory poisoning fix** (`molecule-core` [#1791](https://github.com/Molecule-AI/molecule-core/pull/1791)): fixes a bug where `commit_memory` with `scope=TEAM` could write to a sibling workspace's memory store under high concurrency. `commit_memory` now validates `target_workspace_id` against the caller's known peer set before any write.
|
||||
- **CWE-78 shell injection hardening** (`molecule-core` [#1885](https://github.com/Molecule-AI/molecule-core/pull/1885)): `shellQuote` now uses `strconv.Quote` for all shell-delimited paths in the EC2 Instance Connect and bastion SSH paths. Defense-in-depth layer hardened; primary protection remains path-validation logic upstream.
|
||||
|
||||
#### ✨ New features
|
||||
|
||||
- **A2A priority queue — Phase 1** (`molecule-core` [#1892](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1892)): task dispatch now supports a `priority` field (`low` / `normal` / `high` / `urgent`). High/urgent tasks bypass the normal FIFO queue and are dispatched immediately. Phase 2 (priority inversion deadlock prevention) on the roadmap.
|
||||
- **A2A priority queue — Phase 1** (`molecule-core` [#1892](https://github.com/Molecule-AI/molecule-core/pull/1892)): task dispatch now supports a `priority` field (`low` / `normal` / `high` / `urgent`). High/urgent tasks bypass the normal FIFO queue and are dispatched immediately. Phase 2 (priority inversion deadlock prevention) on the roadmap.
|
||||
|
||||
#### 🔧 Fixes
|
||||
|
||||
- **A2A queue nil-safe drain** (`molecule-core` [#1893](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1893), [#1896](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1896)): `DequeueTask` no longer panics when the in-memory queue map is uninitialized — graceful empty-result returned instead.
|
||||
- **Workspaces stuck in `provisioning` after失败** (`molecule-core` [#1794](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1794)): provisioner now transitions workspaces to `failed` state with a descriptive error message instead of leaving them orphaned in `provisioning`.
|
||||
- **Dedup settings hooks double-fire** (`molecule-core` [#1797](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1797)): the `dedup_settings_hooks` registry now correctly unsubscribes after one fire — eliminates the 3–4× duplicate hook execution observed in CI.
|
||||
- **Semantic memory search returning stale results** (`molecule-core` [#1778](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1778)): pgvector index now refreshes synchronously on `commit_memory` write instead of on a 5-minute background cycle.
|
||||
- **pgvector migration race in E2E CI** (`molecule-core` [#1777](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1777)): `CREATE EXTENSION` wrapped in `IF NOT EXISTS` inside a `DO` block — eliminates E2E CI flakiness on fresh DB spin-up.
|
||||
- **EC2 Instance Connect endpoint not found in us-west-2** (`molecule-core` [#1779](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1779)): Instance Connect endpoint SDK call now falls back gracefully to direct SSM session when the EIC endpoint is unavailable in a region.
|
||||
- **Canvas topology overlay edge labels clipped** (`molecule-core` [#1802](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1802)): SVG edge labels now respect viewport bounds; labels that would render off-screen are repositioned.
|
||||
- **Audit trail panel not loading for large workspaces** (`molecule-core` [#1854](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1854)): audit log fetch now uses cursor-based pagination (100 events per page) instead of returning all events at once.
|
||||
- **Hermes `response_format` not forwarded to MiniMax** (`molecule-core` [#1861](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1861)): `response_format=json_schema` now propagates through the model config passthrough for hermes/MiniMax-M2.7-highspeed workspaces.
|
||||
- **Memory Inspector panel memory leak** (`molecule-core` [#1871](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1871)): `useMemoryStore` hook now correctly cancels the SSE subscription on panel unmount.
|
||||
- **Token revocation cache stale-read window** (`molecule-core` [#1888](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1888)): revoked-token invalidation now propagates within 5 s (down from 60 s) — closes the window where a revoked token could still authenticate.
|
||||
- **TenantGuard same-origin bypass (regression)** (`molecule-core` [#1898](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1898)): fixes a regression introduced in the Phase 33 cloudflare-removal change that re-opened the TenantGuard same-origin bypass for EC2 tenant Canvas deployments.
|
||||
- **A2A queue nil-safe drain** (`molecule-core` [#1893](https://github.com/Molecule-AI/molecule-core/pull/1893), [#1896](https://github.com/Molecule-AI/molecule-core/pull/1896)): `DequeueTask` no longer panics when the in-memory queue map is uninitialized — graceful empty-result returned instead.
|
||||
- **Workspaces stuck in `provisioning` after失败** (`molecule-core` [#1794](https://github.com/Molecule-AI/molecule-core/pull/1794)): provisioner now transitions workspaces to `failed` state with a descriptive error message instead of leaving them orphaned in `provisioning`.
|
||||
- **Dedup settings hooks double-fire** (`molecule-core` [#1797](https://github.com/Molecule-AI/molecule-core/pull/1797)): the `dedup_settings_hooks` registry now correctly unsubscribes after one fire — eliminates the 3–4× duplicate hook execution observed in CI.
|
||||
- **Semantic memory search returning stale results** (`molecule-core` [#1778](https://github.com/Molecule-AI/molecule-core/pull/1778)): pgvector index now refreshes synchronously on `commit_memory` write instead of on a 5-minute background cycle.
|
||||
- **pgvector migration race in E2E CI** (`molecule-core` [#1777](https://github.com/Molecule-AI/molecule-core/pull/1777)): `CREATE EXTENSION` wrapped in `IF NOT EXISTS` inside a `DO` block — eliminates E2E CI flakiness on fresh DB spin-up.
|
||||
- **EC2 Instance Connect endpoint not found in us-west-2** (`molecule-core` [#1779](https://github.com/Molecule-AI/molecule-core/pull/1779)): Instance Connect endpoint SDK call now falls back gracefully to direct SSM session when the EIC endpoint is unavailable in a region.
|
||||
- **Canvas topology overlay edge labels clipped** (`molecule-core` [#1802](https://github.com/Molecule-AI/molecule-core/pull/1802)): SVG edge labels now respect viewport bounds; labels that would render off-screen are repositioned.
|
||||
- **Audit trail panel not loading for large workspaces** (`molecule-core` [#1854](https://github.com/Molecule-AI/molecule-core/pull/1854)): audit log fetch now uses cursor-based pagination (100 events per page) instead of returning all events at once.
|
||||
- **Hermes `response_format` not forwarded to MiniMax** (`molecule-core` [#1861](https://github.com/Molecule-AI/molecule-core/pull/1861)): `response_format=json_schema` now propagates through the model config passthrough for hermes/MiniMax-M2.7-highspeed workspaces.
|
||||
- **Memory Inspector panel memory leak** (`molecule-core` [#1871](https://github.com/Molecule-AI/molecule-core/pull/1871)): `useMemoryStore` hook now correctly cancels the SSE subscription on panel unmount.
|
||||
- **Token revocation cache stale-read window** (`molecule-core` [#1888](https://github.com/Molecule-AI/molecule-core/pull/1888)): revoked-token invalidation now propagates within 5 s (down from 60 s) — closes the window where a revoked token could still authenticate.
|
||||
- **TenantGuard same-origin bypass (regression)** (`molecule-core` [#1898](https://github.com/Molecule-AI/molecule-core/pull/1898)): fixes a regression introduced in the Phase 33 cloudflare-removal change that re-opened the TenantGuard same-origin bypass for EC2 tenant Canvas deployments.
|
||||
|
||||
#### 📚 Docs
|
||||
|
||||
- **Chrome DevTools MCP tutorial** (`docs` [#1798](https://git.moleculesai.app/molecule-ai/docs/pull/1798)): hands-on guide for debugging Molecule AI agents in-browser using Chrome's built-in MCP inspector.
|
||||
- **Phase 34 launch page** (`docs` [#1799](https://git.moleculesai.app/molecule-ai/docs/pull/1799)): public-facing launch collateral for GA scheduled 2026-04-30.
|
||||
- **Tool Trace demo environment** (`docs` [#1844](https://git.moleculesai.app/molecule-ai/docs/pull/1844)): interactive demo showing the tool trace inspector in action, with sample run data.
|
||||
- **Enterprise battlecard** (`docs` [#1864](https://git.moleculesai.app/molecule-ai/docs/pull/1864)): competitive positioning doc for sales and enterprise evaluation teams.
|
||||
- **Chrome DevTools MCP tutorial** (`docs` [#1798](https://github.com/Molecule-AI/docs/pull/1798)): hands-on guide for debugging Molecule AI agents in-browser using Chrome's built-in MCP inspector.
|
||||
- **Phase 34 launch page** (`docs` [#1799](https://github.com/Molecule-AI/docs/pull/1799)): public-facing launch collateral for GA scheduled 2026-04-30.
|
||||
- **Tool Trace demo environment** (`docs` [#1844](https://github.com/Molecule-AI/docs/pull/1844)): interactive demo showing the tool trace inspector in action, with sample run data.
|
||||
- **Enterprise battlecard** (`docs` [#1864](https://github.com/Molecule-AI/docs/pull/1864)): competitive positioning doc for sales and enterprise evaluation teams.
|
||||
|
||||
#### 🧹 Internal
|
||||
|
||||
- `a2a-sdk` hot-pinned to `0.3.x` across all workspace template repos (`molecule-core` [#1890](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1890)); SDK upgrade path documented in `KI-009` (`internal` [#1631](https://git.moleculesai.app/molecule-ai/internal/issues/1631)).
|
||||
- `a2a-sdk` hot-pinned to `0.3.x` across all workspace template repos (`molecule-core` [#1890](https://github.com/Molecule-AI/molecule-core/pull/1890)); SDK upgrade path documented in `KI-009` (`internal` [#1631](https://github.com/Molecule-AI/internal/issues/1631)).
|
||||
- Phase 34 CI matrix expanded to cover Node 22 and Go 1.24 (`molecule-ci`).
|
||||
|
||||
#### 🔧 Runtime fixes
|
||||
|
||||
- **Heartbeat 401 retry** (`molecule-ai-workspace-runtime` [#40](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pull/40)): heartbeat worker now retries with fresh token on 401 before declaring the workspace unreachable — eliminates false `disconnected` status during token rotation.
|
||||
- **LLM token auto-detect** (`molecule-ai-workspace-runtime` [#38](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pull/38)): hermes runtime now auto-detects `max_tokens` from model context window and request timeout when not explicitly configured.
|
||||
- **Heartbeat 401 retry** (`molecule-ai-workspace-runtime` [#40](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/40)): heartbeat worker now retries with fresh token on 401 before declaring the workspace unreachable — eliminates false `disconnected` status during token rotation.
|
||||
- **LLM token auto-detect** (`molecule-ai-workspace-runtime` [#38](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/38)): hermes runtime now auto-detects `max_tokens` from model context window and request timeout when not explicitly configured.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 2026-05-10
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- **A2A priority queue — Phase 1**: task dispatch now supports a `priority` field (`low` / `normal` / `high` / `urgent`). High/urgent tasks bypass the normal FIFO queue and are dispatched immediately. (`molecule-core` [#225](https://git.moleculesai.app/molecule-ai/molecule-core/pull/225))
|
||||
- **Plugin drift detector + queue + admin apply endpoint**: a new plugin drift detection system monitors loaded plugins against their pinned SHAs and surfaces drift via a queue; admins can review and apply corrections via a new `/admin/plugin-apply` endpoint. (`molecule-core` [#204](https://git.moleculesai.app/molecule-ai/molecule-core/pull/204))
|
||||
- **workspace-server pre-restart A2A drain signal**: the workspace-server now sends a pre-restart A2A drain signal before restarting, allowing peer workspaces to gracefully drain pending tasks instead of timing out. (`molecule-core` [#207](https://git.moleculesai.app/molecule-ai/molecule-core/pull/207))
|
||||
- **Admin auth runbook**: new `admin-auth.md` runbook documents the test-token route lockdown and `AdminAuth` middleware behaviour for operators. (`molecule-core` [#220](https://git.moleculesai.app/molecule-ai/molecule-core/pull/220))
|
||||
- **Static `.github-token` fallback to git credential helper**: workspace-server now falls back to a static `.github-token` value when no git credential helper is configured, enabling simpler air-gapped setups. (`molecule-core` [#219](https://git.moleculesai.app/molecule-ai/molecule-core/pull/219))
|
||||
- **Keyboard shortcuts in Toolbar help dialog**: all keyboard shortcuts are now documented in a Toolbar help dialog accessible from the canvas top bar. (`molecule-core` [#244](https://git.moleculesai.app/molecule-ai/molecule-core/pull/244))
|
||||
- **HTTP/SSE transport for Hermes MCP**: `a2a_mcp_server.py` now exposes `--transport=http --port=<N>` for Hermes workspaces that prefer HTTP + SSE over stdio. Endpoints: `POST /mcp` (JSON-RPC), `GET /mcp/stream` (SSE), `GET /health`. (`molecule-ai-workspace-runtime` [#5](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pull/5))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **SSRF validation before writing external workspace URL**: the workspace handler now validates URLs against SSRF allowlists before writing external workspace configurations. (`molecule-core` [#221](https://git.moleculesai.app/molecule-ai/molecule-core/pull/221))
|
||||
- **Dockerfile tenant chown /org-templates**: `/org-templates` directory now correctly chowned to the canvas user to fix `EACCES` on `mkdir` for external resolvers. (`molecule-core` [#223](https://git.moleculesai.app/molecule-ai/molecule-core/pull/223))
|
||||
- **CI `ghcr` → `ECR` migration + POST route smoke tests**: canary-verify workflow migrated from GHCR to ECR; new POST route smoke tests added for deployment verification. (`molecule-core` [#217](https://git.moleculesai.app/molecule-ai/molecule-core/pull/217))
|
||||
- **CI `dorny/paths-filter` → shell-based git diff**: replaced `dorny/paths-filter` with shell-based git diff for Gitea Actions compatibility. (`molecule-core` [#208](https://git.moleculesai.app/molecule-ai/molecule-core/pull/208))
|
||||
- **SOP tier-check clause splitter strips newlines**: the SOP tier-check script's clause splitter now correctly preserves newlines, fixing every `tier:low` PR CI failure. (`molecule-core` [#243](https://git.moleculesai.app/molecule-ai/molecule-core/pull/243))
|
||||
- **SOP tier-check APPROVER_TEAMS pattern matching**: outer quotes removed from case patterns in `APPROVER_TEAMS` matching logic, fixing approval team resolution. (`molecule-core` [#231](https://git.moleculesai.app/molecule-ai/molecule-core/pull/231))
|
||||
- **CI port `publish-workspace-server-image.yml` to `.gitea/workflows/`**: `publish-workspace-server-image.yml` migrated from `.github/workflows/` to `.gitea/workflows/` for Gitea Actions parity. (`molecule-core` [#237](https://git.moleculesai.app/molecule-ai/molecule-core/pull/237))
|
||||
- **CI port `publish-runtime.yml` to `.gitea/workflows/`**: `publish-runtime.yml` migrated from `.github/workflows/` to `.gitea/workflows/` for Gitea Actions parity. (`molecule-core` [#211](https://git.moleculesai.app/molecule-ai/molecule-core/pull/211))
|
||||
- **Docker base image digests pinned**: base image digests pinned in all Dockerfiles to ensure reproducible builds and prevent unexpected base image updates. (`molecule-core` [#199](https://git.moleculesai.app/molecule-ai/molecule-core/pull/199))
|
||||
- **KeyboardShortcutsDialog corrected**: keyboard shortcuts dialog text corrected and min-clamp test expectations fixed. (`molecule-core` [#200](https://git.moleculesai.app/molecule-ai/molecule-core/pull/200))
|
||||
- **`MODEL_PROVIDER` env var deprecated**: the `MODEL_PROVIDER` env var was misnamed — it carried the model ID (e.g. `claude-opus-4-7`) despite its name, and was being misused as a runtime selector. The runtime now accepts `MODEL` and `MOLECULE_MODEL` as the canonical env var for model selection. `MODEL_PROVIDER` still works but emits a deprecation warning. (`molecule-core` [#280](https://git.moleculesai.app/molecule-ai/molecule-core/pull/280))
|
||||
- **`delegate_task` self-delegation guard**: calling `delegate_task` with your own workspace ID now returns an early actionable error instead of deadlocking the task lock. Previously self-delegation would hold `_run_lock`, timeout after 30 s, and waste the turn. (`molecule-core` [#291](https://git.moleculesai.app/molecule-ai/molecule-core/pull/291))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **Canvas known issues section cleaned up**: duplicate entries removed from known issues; pre-commit action link fixed. (`molecule-core` [#202](https://git.moleculesai.app/molecule-ai/molecule-core/pull/202))
|
||||
- **Canvas controls section corrected**: Canvas Controls section corrected to reflect current keyboard navigation and MiniMap state. (`molecule-core` [#201](https://git.moleculesai.app/molecule-ai/molecule-core/pull/201))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **SOP tier-check AND-composition of required team approvals per tier**: tier-check now enforces AND-composition of required team approvals per tier (`tier:high`). (`molecule-core` [#225](https://git.moleculesai.app/molecule-ai/molecule-core/pull/225))
|
||||
- **Canvas structural tests for TIER_CONFIG and COMM_TYPE_LABELS**: structural tests added for canvas TIER_CONFIG and COMM_TYPE_LABELS constants. (`molecule-core` [#245](https://git.moleculesai.app/molecule-ai/molecule-core/pull/245))
|
||||
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- **Keyboard-accessible canvas node resize**: Cmd/Ctrl+Arrow keys now resize canvas nodes in the topology view, satisfying WCAG AA keyboard navigation requirements. (`molecule-core` [#192](https://git.moleculesai.app/molecule-ai/molecule-core/pull/192))
|
||||
- **Keyboard-accessible edge anchors**: Enter/Space on an edge now selects the anchor for keyboard-based topology editing. (`molecule-core` [#190](https://git.moleculesai.app/molecule-ai/molecule-core/pull/190))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **Handlers auto-restart workspace after file write/delete/replace**: file mutations via the Canvas editor now correctly trigger workspace restart, ensuring the agent picks up the new file state without manual intervention. (`molecule-core` [#188](https://git.moleculesai.app/molecule-ai/molecule-core/pull/188))
|
||||
- **CI `gh api` → Gitea API migration**: all GitHub Actions `gh api` calls replaced with Gitea-compatible alternatives — CI now runs cleanly in Gitea Actions without GitHub dependency. (`molecule-core` [#191](https://git.moleculesai.app/molecule-ai/molecule-core/pull/191))
|
||||
- **WCAG AA contrast fix + KeyboardShortcutsDialog improvements**: toolbar contrast ratios corrected for WCAG AA compliance; keyboard shortcuts dialog now scrolls properly on small viewports. (`molecule-core` [#198](https://git.moleculesai.app/molecule-ai/molecule-core/pull/198))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **Canvas accessibility audit — all gaps now closed**: the accessibility audit doc updated to reflect fully closed status. (`molecule-core` [#197](https://git.moleculesai.app/molecule-ai/molecule-core/pull/197))
|
||||
- **Canvas controls section corrected**: keyboard accessibility and MiniMap presence now correctly documented. (`molecule-core` [#201](https://git.moleculesai.app/molecule-ai/molecule-core/pull/201))
|
||||
- **Stale audit doc text fixed**: stale text from PR #182 corrected in canvas audit documentation. (`molecule-core` [#187](https://git.moleculesai.app/molecule-ai/molecule-core/pull/187))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **gh-identity module path migration**: `github.com/Molecule-AI/gh-identity` imports migrated to `git.moleculesai.app/molecule-ai/gh-identity` across all workspace templates. (`molecule-core` [#189](https://git.moleculesai.app/molecule-ai/molecule-core/pull/189))
|
||||
- **Pending uploads test isolation fix**: sweeper test isolation corrected — eliminates cross-test pollution in CI. (`molecule-core` [#185](https://git.moleculesai.app/molecule-ai/molecule-core/pull/185))
|
||||
- **Poll error counter to 0 before assert**: RecordsMetricsOnSuccess now polls error counter to 0 before asserting, eliminating flaky E2E test failures. (`molecule-core` [#194](https://git.moleculesai.app/molecule-ai/molecule-core/pull/194))
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-08
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **molecule-app CI testTimeout bumped to 20s**: vitest `testTimeout` increased to 20 s to handle shared act_runner load on the molecule-app repo. (`molecule-app` [#4](https://git.moleculesai.app/molecule-ai/molecule-app/pull/4))
|
||||
- **molecule-app drops staging branch — trunk-based migration**: first repo of the trunk-based development migration; staging branch removed. (`molecule-app` [#3](https://git.moleculesai.app/molecule-ai/molecule-app/pull/3))
|
||||
- **docs CI switches to ubuntu-latest**: docs repo CI now uses `ubuntu-latest` now that the repo is public. (`docs` [#4](https://git.moleculesai.app/molecule-ai/docs/pull/4))
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-07
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **Install guide — GitHub.com refs → Gitea**: all active `github.com/Molecule-AI` references migrated to `git.moleculesai.app/molecule-ai` in the installation docs. (`docs` [#1](https://git.moleculesai.app/molecule-ai/docs/pull/1))
|
||||
- **Website github.com → Gitea link migration**: `molecules-market` website links updated to point at Gitea. (`landingpage` [#3](https://git.moleculesai.app/molecule-ai/landingpage/pull/3))
|
||||
- **molecule-monorepo → molecule-core rename (Phase 4)**: landingpage follow-up renaming of `molecule-monorepo` to `molecule-core` in all cross-repo references. (`landingpage` [#4](https://git.moleculesai.app/molecule-ai/landingpage/pull/4))
|
||||
- **CI lowercase 'molecule-ai/' in cross-repo workflow refs**: cross-repo workflow references now consistently lowercase for Gitea Actions compatibility. (`landingpage` [#2](https://git.moleculesai.app/molecule-ai/landingpage/pull/2))
|
||||
- **Market Purchase button on tier cards**: demo Mock #1 — Purchase button now appears on tier cards in the molecules-market. (`landingpage` [#5](https://git.moleculesai.app/molecule-ai/landingpage/pull/5))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **molecule-app runs-on ubuntu-latest**: Hetzner runner labels post-suspension; CI now uses `ubuntu-latest`. (`molecule-app` [#1](https://git.moleculesai.app/molecule-ai/molecule-app/pull/1))
|
||||
- **molecule-app GitHub → Gitea URL migration**: all `github.com/Molecule-AI` references migrated to `git.moleculesai.app/molecule-ai` in molecule-app. (`molecule-app` [#2](https://git.moleculesai.app/molecule-ai/molecule-app/pull/2))
|
||||
- **docs GitHub → Gitea URL migration**: `github.com/Molecule-AI` references migrated to Gitea across docs repo. (`docs` [#3](https://git.moleculesai.app/molecule-ai/docs/pull/3))
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-06
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **molecule-core org-wide Gitea URL migration**: all `github.com/Molecule-AI` references migrated to `git.moleculesai.app/molecule-ai` across all repos in the org. (`molecule-core`)
|
||||
- **Hetzner act-runner suspension**: CI runners updated to use `ubuntu-latest` labels following Hetzner act-runner suspension. (`molecule-app` [#1](https://git.moleculesai.app/molecule-ai/molecule-app/pull/1))
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-22
|
||||
|
||||
### ✨ New features
|
||||
@ -190,7 +84,7 @@ Customer selects `model=minimax/MiniMax-M2.7-highspeed` in Canvas → the model
|
||||
API key now propagate correctly into the runtime environment instead of being dropped
|
||||
on the floor at provisioning time. Works for hermes workspaces in both hosted SaaS
|
||||
and self-hosted EC2 deployments.
|
||||
(`molecule-core` [#1685](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1685))
|
||||
(`molecule-core` [#1685](https://github.com/Molecule-AI/molecule-core/pull/1685))
|
||||
|
||||
#### EC2 Instance Connect Endpoint — one-click shell from Canvas
|
||||
Canvas Terminal tab now uses AWS EC2 Instance Connect Endpoint to open a PTY inside
|
||||
@ -198,7 +92,7 @@ any workspace EC2 instance — no SSH keys to manage, no IP to copy, no security
|
||||
rules to configure. IAM policy gates access, STS pushes a short-lived key that
|
||||
auto-expires, and every tunnel open is recorded in CloudTrail.
|
||||
See the [EC2 Instance Connect guide](/docs/infra/workspace-terminal).
|
||||
(`molecule-core` [#1554](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1554))
|
||||
(`molecule-core` [#1554](https://github.com/Molecule-AI/molecule-core/pull/1554))
|
||||
|
||||
#### Phase 33 — Cloudflare Tunnel replaced with direct-connect public IPs
|
||||
Cloud-hosted workspaces no longer route through `cloudflared`. Each workspace gets
|
||||
@ -207,32 +101,32 @@ TLS on port 443. Reduces latency by ~20–40 ms (region-dependent), removes the
|
||||
Cloudflare egress cost dependency, and enables direct `curl` debugging without
|
||||
the tunnel path.
|
||||
See the [migration blog post](/blog/cloudflare-tunnel-migration).
|
||||
(`molecule-core` [#1612](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1612))
|
||||
(`molecule-core` [#1612](https://github.com/Molecule-AI/molecule-core/pull/1612))
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **F1085 deleteViaEphemeral**: `rm` scope restricted to `/configs` volume only —
|
||||
prevents deletion of application code or workspace files if the exec form is
|
||||
exploited. Applied to both `main` and `staging`. (`molecule-core` [#1682](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1682), [#1616](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1616))
|
||||
exploited. Applied to both `main` and `staging`. (`molecule-core` [#1682](https://github.com/Molecule-AI/molecule-core/pull/1682), [#1616](https://github.com/Molecule-AI/molecule-core/pull/1616))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- Canvas now fetches the runtime and model dropdown from the `/templates` registry
|
||||
at load time — runtime list stays current without code deploys. (`molecule-core` [#1666](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1666))
|
||||
at load time — runtime list stays current without code deploys. (`molecule-core` [#1666](https://github.com/Molecule-AI/molecule-core/pull/1666))
|
||||
- Canvas accessibility: `aria-hidden` correctly applied to decorative SVGs;
|
||||
`MissingKeysModal` now uses correct dialog semantics and manages focus. (`molecule-core` [#1594](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1594))
|
||||
`MissingKeysModal` now uses correct dialog semantics and manages focus. (`molecule-core` [#1594](https://github.com/Molecule-AI/molecule-core/pull/1594))
|
||||
- Provisioner pulls workspace template images from GHCR instead of Docker Hub
|
||||
for faster cold starts and reduced third-party dependency. (`molecule-core` [#1624](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1624))
|
||||
for faster cold starts and reduced third-party dependency. (`molecule-core` [#1624](https://github.com/Molecule-AI/molecule-core/pull/1624))
|
||||
- Shared runtime heartbeat no longer leaves workspaces in a phantom-busy state after
|
||||
task completion. (`molecule-ai-workspace-runtime` [#37](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pull/37))
|
||||
task completion. (`molecule-ai-workspace-runtime` [#37](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/37))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **MCP server structured logging**: `LOG_LEVEL` env var (`trace`/`debug`/`info`/`warn`/`error`/`fatal`),
|
||||
pino JSON output in production, pretty-print in development, AsyncLocalStorage
|
||||
context on every log entry (tool name, request ID, workspace ID). (`docs` [#78](https://git.moleculesai.app/molecule-ai/docs/pull/78))
|
||||
context on every log entry (tool name, request ID, workspace ID). (`docs` [#78](https://github.com/Molecule-AI/docs/pull/78))
|
||||
- **molecli shell completion**: tab completion for `molecule` CLI in bash, zsh, fish,
|
||||
and PowerShell — covers all subcommands and flags. (`docs` [#79](https://git.moleculesai.app/molecule-ai/docs/pull/79))
|
||||
and PowerShell — covers all subcommands and flags. (`docs` [#79](https://github.com/Molecule-AI/docs/pull/79))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
|
||||
@ -21,6 +21,17 @@ register and heartbeat by hand. Use it when your agent can't run an MCP
|
||||
stdio server.
|
||||
</Callout>
|
||||
|
||||
## Pick the right path
|
||||
|
||||
| Your agent runs as | Best path | Why |
|
||||
|---|---|---|
|
||||
| **An MCP-aware runtime** (Claude Code, Hermes, OpenCode, Cursor, Cline) | [Bring Your Own Runtime (MCP)](/docs/runtime-mcp) | Universal `molecule-mcp` wheel — no HTTP server, no tunnel. |
|
||||
| **A Claude Code session on your laptop** | [Claude Code Channel Plugin](/docs/guides/claude-code-channel-plugin) | Polling-based; no tunnel/public URL needed. Set up in under a minute. |
|
||||
| Any HTTP server with a public URL | The flow on this page (or the [Python SDK guide](/docs/guides/external-agent-registration)) | Push-based; lower latency; works for any A2A-compatible HTTP endpoint. |
|
||||
| A custom A2A server you wrote yourself | The flow on this page | Direct register + heartbeat + handler. |
|
||||
|
||||
The rest of this doc covers the third + fourth rows. For Claude Code or other MCP runtimes, follow the linked guides.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A running Molecule AI platform (default `http://localhost:8080`)
|
||||
|
||||
@ -26,7 +26,7 @@ lands in the watch list with a colliding term, add a row here.
|
||||
| **team** | A named cluster of workspaces under a PM (org template `expand_team`). Used for role grouping in Canvas. | **CrewAI**: a "crew" is a sequence of agents that pass a task through a declared order. Our "team" is an org-chart abstraction, not an execution order. |
|
||||
| **skill** | A directory with `SKILL.md` that an agent invokes via the `Skill` tool. Skills are documentation + optional scripts that teach an agent a recipe. | **Anthropic Skills API**: nearly identical. **CrewAI tool**: closer to our plugin's MCP tool, not our skill. |
|
||||
| **channel** | An outbound/inbound social integration (Telegram, Slack, …) per-workspace, wired in `workspace_channels`. | Slack's "channel": the container for messages. We use "channel" for the adapter + credentials, not the conversation itself. |
|
||||
| **runtime** | The execution engine image tag for a workspace: one of `langgraph`, `claude-code`, `openclaw`, `crewai`, `autogen`, `deepagents`, `hermes`. | **LangGraph runtime**: the Python process running the graph. We use "runtime" for the Docker image + adapter pairing, not the inner process. |
|
||||
| **runtime** | The execution engine image tag for a workspace: one of `claude-code`, `langgraph`, `crewai`, `autogen`, `deepagents`, `hermes`, `gemini-cli`, `openclaw`. | **LangGraph runtime**: the Python process running the graph. We use "runtime" for the Docker image + adapter pairing, not the inner process. |
|
||||
|
||||
## GitHub Awesome Copilot disambiguation
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ description: "Bridge Molecule A2A traffic into a running Claude Code session via
|
||||
|
||||
Run [Claude Code](https://claude.com/claude-code) on your laptop and have it appear on the Molecule AI canvas as a first-class external workspace. Inbound A2A messages from peer workspaces surface as conversation turns; replies route back through Molecule's A2A endpoints.
|
||||
|
||||
> **What this is:** [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel) — an MCP-based "channel plugin" that turns a Claude Code session into a Molecule workspace.
|
||||
> **What this is:** [`Molecule-AI/molecule-mcp-claude-channel`](https://github.com/Molecule-AI/molecule-mcp-claude-channel) — an MCP-based "channel plugin" that turns a Claude Code session into a Molecule workspace.
|
||||
|
||||
> **What this is NOT:** the [Python SDK / curl register flow](/docs/guides/external-agent-registration) for arbitrary HTTP-speaking agents. That flow needs a public URL the platform can POST to. This one polls — runs on any laptop behind any NAT.
|
||||
|
||||
@ -84,7 +84,7 @@ Replace the token placeholder with the workspace bearer from Step 1.
|
||||
## Step 3 — Launch Claude Code
|
||||
|
||||
```bash
|
||||
claude --channels plugin:molecule@molecule-ai/molecule-mcp-claude-channel
|
||||
claude --channels plugin:molecule@Molecule-AI/molecule-mcp-claude-channel
|
||||
```
|
||||
|
||||
You should see on stderr (use `--debug` to surface):
|
||||
@ -199,7 +199,7 @@ curl -fsS "$MOLECULE_PLATFORM_URL/workspaces/$WS_ID/activity?type=a2a_receive&li
|
||||
-H "Authorization: Bearer $WS_TOKEN" | jq
|
||||
```
|
||||
|
||||
If that returns events but Claude doesn't see them, file an issue at [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel/issues) with the workspace_id + sample event.
|
||||
If that returns events but Claude doesn't see them, file an issue at [`Molecule-AI/molecule-mcp-claude-channel`](https://github.com/Molecule-AI/molecule-mcp-claude-channel/issues) with the workspace_id + sample event.
|
||||
|
||||
---
|
||||
|
||||
@ -210,7 +210,7 @@ If that returns events but Claude doesn't see them, file an issue at [`molecule-
|
||||
- **No file-attachment download.** URLs surface in the meta block; the host fetches on-demand.
|
||||
- **No outbound channel-init.** The plugin only sends replies (in response to inbound A2A); starting a fresh A2A conversation initiated FROM the Claude Code side requires a future `start_workspace_chat` tool.
|
||||
|
||||
Track the v0.2 roadmap on the [plugin repo's README](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel#limitations-v01).
|
||||
Track the v0.2 roadmap on the [plugin repo's README](https://github.com/Molecule-AI/molecule-mcp-claude-channel#limitations-v01).
|
||||
|
||||
---
|
||||
|
||||
@ -219,4 +219,4 @@ Track the v0.2 roadmap on the [plugin repo's README](https://git.moleculesai.app
|
||||
- [External Agent Registration](/docs/guides/external-agent-registration) — full A2A wire-shape reference + Python SDK + curl flow
|
||||
- [External Workspace Quickstart](/docs/guides/external-workspace-quickstart) — 5-min guide for any HTTP-speaking agent
|
||||
- [Remote Workspaces FAQ](/docs/guides/remote-workspaces-faq) — production hardening notes
|
||||
- [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel) — plugin source code, issues, v0.2 roadmap
|
||||
- [`Molecule-AI/molecule-mcp-claude-channel`](https://github.com/Molecule-AI/molecule-mcp-claude-channel) — plugin source code, issues, v0.2 roadmap
|
||||
|
||||
@ -9,6 +9,8 @@ Run an agent on your laptop, a home server, a cloud VM, or any machine with inte
|
||||
|
||||
> **Looking for the operator-focused reference?** See [External Agent Registration](/docs/guides/external-agent-registration) for full capability + auth details, or [Remote Workspaces FAQ](/docs/guides/remote-workspaces-faq) for hardening + production notes. This doc is the fast path.
|
||||
|
||||
> **Running Claude Code on your laptop?** Skip this guide — use the [Claude Code Channel Plugin](/docs/guides/claude-code-channel-plugin) instead. It's polling-based and needs no tunnel, so your laptop session shows up on the canvas in under a minute.
|
||||
|
||||
---
|
||||
|
||||
## What is an "external workspace"?
|
||||
@ -158,7 +160,7 @@ The `id` field is your workspace ID — remember it.
|
||||
|---|---|
|
||||
| "Failed to send message — agent may be unreachable" | The tenant couldn't POST to your URL. Verify `curl https://<your-tunnel>/health` returns 200 from another machine. |
|
||||
| Response takes > 30s | Canvas times out around 30s. Keep initial implementations simple. For long-running work, return a placeholder and use [polling mode](#next-step-polling-mode-preview) (once available). |
|
||||
| Agent duplicated in chat | Known canvas bug where WebSocket + HTTP responses both render. Fixed in [molecule-core #1517](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1517). |
|
||||
| Agent duplicated in chat | Known canvas bug where WebSocket + HTTP responses both render. Fixed in [molecule-core #1517](https://github.com/Molecule-AI/molecule-core/pull/1517). |
|
||||
| Agent replies but canvas shows "Agent unreachable" | Check the tenant can reach your URL. Cloudflare quick tunnels rotate — the URL in your canvas may point at a dead tunnel after restart. |
|
||||
| Getting 404 when POSTing to tenant | Add `X-Molecule-Org-Id` header. The tenant's security layer 404s unmatched origin requests by design. |
|
||||
|
||||
@ -220,7 +222,7 @@ Push mode (this guide) works today but requires an inbound-reachable URL — whi
|
||||
|
||||
Your agent makes only outbound HTTPS calls to the platform, pulling messages from an inbox queue and posting replies back. Works behind any NAT/firewall, tolerates offline laptops, no tunnel needed.
|
||||
|
||||
See the [design doc](https://git.moleculesai.app/molecule-ai/internal/src/branch/main/product/external-workspaces-polling.md) (internal) and the implementation tracking issue (search `polling+mode` on the [molecule-core issue tracker](https://git.moleculesai.app/molecule-ai/molecule-core/issues)).
|
||||
See the [design doc](https://github.com/Molecule-AI/internal/blob/main/product/external-workspaces-polling.md) (internal) and [implementation tracking issue](https://github.com/Molecule-AI/molecule-core/issues?q=polling+mode) once opened.
|
||||
|
||||
---
|
||||
|
||||
@ -260,11 +262,11 @@ If all four pass and canvas still shows your agent as unreachable, see the [remo
|
||||
## Feedback
|
||||
|
||||
This is a new path. Tell us what broke:
|
||||
- Open an issue: https://git.moleculesai.app/molecule-ai/molecule-core/issues/new?labels=external-workspace
|
||||
- Open an issue: https://github.com/Molecule-AI/molecule-core/issues/new?labels=external-workspace
|
||||
- Submit a PR improving this doc if something tripped you up — the faster we can make the quickstart, the more developers we bring in
|
||||
|
||||
---
|
||||
|
||||
*Last updated 2026-04-23*
|
||||
|
||||
(`molecule-core` [#1760](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1760))
|
||||
(`molecule-core` [#1760](https://github.com/Molecule-AI/molecule-core/pull/1760))
|
||||
@ -78,7 +78,7 @@ Every log entry automatically includes MCP request context (tool name, request I
|
||||
|
||||
Set `LOG_LEVEL=debug` (level 20) to trace all tool calls and request IDs. Set `LOG_LEVEL=error` (level 50) in CI to suppress informational output.
|
||||
|
||||
See [`molecule-mcp-server` PR #6](https://git.moleculesai.app/molecule-ai/molecule-mcp-server/pull/6) for implementation details.
|
||||
See [`molecule-mcp-server` PR #6](https://github.com/Molecule-AI/molecule-mcp-server/pull/6) for implementation details.
|
||||
|
||||
## Tool Reference
|
||||
|
||||
|
||||
@ -90,4 +90,4 @@ molecule completion [bash|zsh|fish|powershell]
|
||||
- `fish` — Fish shell completions (~/.config/fish/completions)
|
||||
- `powershell` — PowerShell completions ($PROFILE)
|
||||
|
||||
See [`molecule-cli` PR #5](https://git.moleculesai.app/molecule-ai/molecule-cli/pull/5) for implementation details.
|
||||
See [`molecule-cli` PR #5](https://github.com/Molecule-AI/molecule-cli/pull/5) for implementation details.
|
||||
|
||||
@ -148,5 +148,5 @@ The agent appears on the canvas with a **purple REMOTE badge** within seconds. F
|
||||
## Next Steps
|
||||
|
||||
- **[External Agent Registration Guide →](/docs/guides/external-agent-registration)** — full endpoint reference, Python + Node.js examples, troubleshooting
|
||||
- **[molecule-sdk-python →](https://git.moleculesai.app/molecule-ai/molecule-sdk-python)** — SDK source, `RemoteAgentClient` API docs
|
||||
- **[SDK Examples →](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/src/branch/main/examples/remote-agent)** — `run.py` demo script, annotated walkthrough
|
||||
- **[molecule-sdk-python →](https://github.com/Molecule-AI/molecule-sdk-python)** — SDK source, `RemoteAgentClient` API docs
|
||||
- **[SDK Examples →](https://github.com/Molecule-AI/molecule-sdk-python/tree/main/examples/remote-agent)** — `run.py` demo script, annotated walkthrough
|
||||
|
||||
@ -65,7 +65,7 @@ molecule skills install arxiv-research --from community
|
||||
|
||||
Community skills are reviewed by the Molecule AI team before being
|
||||
listed. Submit a skill for review by opening a PR against
|
||||
`molecule-ai/skills` (repo location TBD post-2026-05-06 GitHub-org-suspension; check the [internal issue tracker](https://git.moleculesai.app/molecule-ai/internal/issues) for the canonical submission path).
|
||||
[`molecule-ai/skills`](https://github.com/Molecule-AI/skills).
|
||||
|
||||
## Installing via config.yaml
|
||||
|
||||
@ -171,7 +171,7 @@ molecule skills bundle my-custom-skill --output ./org-templates/my-role/
|
||||
```
|
||||
|
||||
**Publishing to the community:** Open a PR against
|
||||
`molecule-ai/skills` (repo location TBD post-2026-05-06 GitHub-org-suspension; check the [internal issue tracker](https://git.moleculesai.app/molecule-ai/internal/issues) for the canonical submission path) with a
|
||||
[`molecule-ai/skills`](https://github.com/Molecule-AI/skills) with a
|
||||
complete skill package. Community skills are reviewed for security and
|
||||
correctness before listing.
|
||||
|
||||
|
||||
@ -339,7 +339,7 @@ If you are routing a Gemini model through a key that triggers the compat shim (e
|
||||
- [Concepts — Workspaces](/docs/concepts#workspaces)
|
||||
- [API Reference — POST /workspaces](/docs/api-reference#post-workspaces)
|
||||
- [Google ADK Runtime](/docs/google-adk) — Gemini-native alternative to Hermes for ADK-first workflows
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
|
||||
- Issue [#513](https://git.moleculesai.app/molecule-ai/molecule-core/issues/513)
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://github.com/Molecule-AI/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://github.com/Molecule-AI/molecule-core/pull/267)
|
||||
- Issue [#513](https://github.com/Molecule-AI/molecule-core/issues/513)
|
||||
|
||||
@ -165,14 +165,14 @@ ticket if a future revival of this BFG procedure is needed.
|
||||
|
||||
**Step 2 — Clean origin/main:**
|
||||
```bash
|
||||
git clone --mirror https://git.moleculesai.app/molecule-ai/molecule-core /tmp/molecule-main-mirror
|
||||
git clone --mirror https://github.com/Molecule-AI/molecule-core /tmp/molecule-main-mirror
|
||||
java -jar bfgr.jar --replace-text creds.txt --rewrite-not-committed-by-oss --no-blob-protection /tmp/molecule-main-mirror
|
||||
cd /tmp/molecule-main-mirror && git push --mirror
|
||||
```
|
||||
|
||||
**Step 3 — Clean origin/staging:**
|
||||
```bash
|
||||
git clone --mirror https://git.moleculesai.app/molecule-ai/molecule-core /tmp/molecule-staging-mirror
|
||||
git clone --mirror https://github.com/Molecule-AI/molecule-core /tmp/molecule-staging-mirror
|
||||
java -jar bfgr.jar --replace-text creds.txt --rewrite-not-committed-by-oss --no-blob-protection /tmp/molecule-staging-mirror
|
||||
cd /tmp/molecule-staging-mirror && git push --mirror
|
||||
```
|
||||
@ -584,7 +584,7 @@ Core-BE — delegated to Dev Lead (A2A failed). Core-BE sub-team: please pick up
|
||||
|
||||
### Fix PR
|
||||
|
||||
[PR #1336](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1336) filed — `fix(orchestrator): fail-fast if WORKSPACE_ID env var is unset/empty`. Targets staging. Labels: bug, needs-work, area:backend-engineer, area:dev-lead.
|
||||
[PR #1336](https://github.com/Molecule-AI/molecule-core/pull/1336) filed — `fix(orchestrator): fail-fast if WORKSPACE_ID env var is unset/empty`. Targets staging. Labels: bug, needs-work, area:backend-engineer, area:dev-lead.
|
||||
|
||||
---
|
||||
|
||||
|
||||
166
content/docs/marketplace.mdx
Normal file
166
content/docs/marketplace.mdx
Normal file
@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Marketplace
|
||||
description: A tiered library of plugins, agents, and bundles you can mount into any Molecule workspace.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Molecule **Marketplace** is the distribution surface for reusable agent
|
||||
infrastructure. It surfaces three tiers of artifacts — from a single MCP
|
||||
plugin to a full team topology — and the same governance, memory, and audit
|
||||
substrate runs underneath each one.
|
||||
|
||||
You browse and install via the Marketplace UI at
|
||||
[`https://moleculesai.app`](https://moleculesai.app), or pin entries from
|
||||
your `workspace.yaml` for reproducible deployments.
|
||||
|
||||
---
|
||||
|
||||
## Three Tiers
|
||||
|
||||
| Tier | Name | Granularity | Mount as |
|
||||
|------|------|-------------|----------|
|
||||
| **L1** | Plugins | A single MCP server / tool pack | Tool capability on an agent or workspace |
|
||||
| **L2** | Agents | A prebuilt single-agent skill (prompts + tools + policy) | Workspace member |
|
||||
| **L3** | Bundles | A full team topology (root + children with their own scopes) | Workspace |
|
||||
|
||||
The tier model is intentionally additive — an L3 Bundle is composed of L2
|
||||
Agents, which in turn use L1 Plugins. Forking a Bundle gives you the lineage
|
||||
to swap any constituent piece without rewiring the operating model.
|
||||
|
||||
### L1 — Plugins
|
||||
|
||||
Plugins are MCP servers or agentskills.io packs. Examples:
|
||||
|
||||
- `postgres` — read/write Postgres with role-scoped credentials
|
||||
- `slack` — post and search Slack with workspace-scoped tokens
|
||||
- `linear` — create / triage / comment on Linear issues
|
||||
- `gh-actions` — query and dispatch GitHub Actions runs
|
||||
- `sentry` — read incident timeline, ack alerts
|
||||
|
||||
Plugins follow the [two-axis source/shape model](/docs/plugins) and install
|
||||
from either a curated `local://` source or a pinned `github://owner/repo#tag`.
|
||||
|
||||
### L2 — Agents
|
||||
|
||||
Agents are single-purpose skills mounted as a workspace member. They ship with:
|
||||
|
||||
- A **system prompt** baked in
|
||||
- A **tool manifest** specifying which L1 plugins they require
|
||||
- A **policy** declaring scope reads/writes and approval requirements
|
||||
|
||||
Examples:
|
||||
|
||||
- `code-reviewer` — five-axis review, posts inline comments via `gh-actions`
|
||||
- `oncall-triager` — reads Sentry, drafts a runbook step, requests approval before paging
|
||||
- `churn-analyst` — periodic Postgres + Stripe rollup, posts a weekly Slack summary
|
||||
|
||||
Mount an agent via the workspace UI or `workspace.yaml`:
|
||||
|
||||
```yaml
|
||||
members:
|
||||
- kind: agent
|
||||
source: marketplace://l2/code-reviewer
|
||||
version: ^1.2.0
|
||||
scopes:
|
||||
- read: pull_requests
|
||||
- write: pull_request_comments
|
||||
```
|
||||
|
||||
### L3 — Bundles
|
||||
|
||||
Bundles are complete team topologies. A bundle ships:
|
||||
|
||||
- A **root agent** that coordinates the team
|
||||
- One or more **child agents**, each with its own scope, memory, and tool
|
||||
list
|
||||
- A **policy graph** declaring which scopes the root can write through and
|
||||
which approvals route to humans
|
||||
|
||||
Examples:
|
||||
|
||||
- `growth-team` — root strategist + content-writer + analytics-rollup +
|
||||
experiment-designer
|
||||
- `platform-ops` — root SRE + on-call triager + change-reviewer +
|
||||
incident-scribe
|
||||
- `revenue-pod` — root commercial lead + churn-analyst + cs-summarizer +
|
||||
expansion-prospector
|
||||
|
||||
Mount a bundle as a workspace:
|
||||
|
||||
```yaml
|
||||
workspace:
|
||||
bundle: marketplace://l3/platform-ops
|
||||
bundle_version: ^0.4.0
|
||||
overrides:
|
||||
members:
|
||||
change-reviewer:
|
||||
scopes:
|
||||
- read: ["github:Molecule-AI/*", "linear:eng"]
|
||||
```
|
||||
|
||||
Forking is encouraged — the bundle author publishes the operating model;
|
||||
your team tunes it for your processes without rebuilding the substrate.
|
||||
|
||||
---
|
||||
|
||||
## Trust Tiers
|
||||
|
||||
Every Marketplace entry carries a **trust tier** that signals review depth
|
||||
and supply-chain provenance:
|
||||
|
||||
| Trust | Vetting | Provenance |
|
||||
|-------|---------|------------|
|
||||
| **Verified** | Reviewed by Molecule for safety, prompt-injection resistance, and policy correctness | Published from a Molecule-controlled identity |
|
||||
| **Partner** | Reviewed by a Marketplace partner; carries the partner's identity badge | Published from a verified partner account |
|
||||
| **Community** | Self-published; static analysis + sandbox runtime; no human review | Pinned to a specific commit SHA |
|
||||
|
||||
The trust tier is shown on every listing card and gated by enterprise
|
||||
policy: organizations on the Enterprise plan can restrict installs to
|
||||
Verified-only via `policy.marketplace.min_trust = verified`.
|
||||
|
||||
---
|
||||
|
||||
## Installing from the Marketplace
|
||||
|
||||
Browse listings at [`https://moleculesai.app`](https://moleculesai.app).
|
||||
Each card shows tier (L1/L2/L3), trust badge, runtime compatibility, and
|
||||
required scopes. The "Install" flow:
|
||||
|
||||
1. Picks a workspace (or creates a new one) to mount into.
|
||||
2. Surfaces required scopes for review and approval.
|
||||
3. Pins to a specific version (semver range, exact tag, or commit SHA).
|
||||
4. Writes the entry into your `workspace.yaml` and triggers a workspace
|
||||
redeploy.
|
||||
|
||||
You can also install non-interactively:
|
||||
|
||||
```bash
|
||||
curl -X POST https://app.moleculesai.app/cp/orgs/$ORG/marketplace/install \
|
||||
-H "Authorization: Bearer $CP_ADMIN_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tier": "l2",
|
||||
"slug": "code-reviewer",
|
||||
"version": "^1.2.0",
|
||||
"workspace_id": "ws_abc123"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Listing on the Marketplace
|
||||
|
||||
If you have built reusable agent infrastructure — a plugin, agent, or
|
||||
bundle — you can list it on the Marketplace and reach every Molecule
|
||||
organization. See [Listing on the Marketplace](/docs/marketplace/creators)
|
||||
for the full builder workflow.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- [Plugins](/docs/plugins) — L1 source/shape model and install mechanics
|
||||
- [External Agents](/docs/external-agents) — bringing a non-Molecule agent runtime
|
||||
- [Workspace Configuration](/docs/workspace-config) — `workspace.yaml` reference
|
||||
- [Listing on the Marketplace](/docs/marketplace/creators) — builder workflow
|
||||
164
content/docs/marketplace/creators.mdx
Normal file
164
content/docs/marketplace/creators.mdx
Normal file
@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Listing on the Marketplace
|
||||
description: How builders ship plugins, agents, and bundles to every Molecule organization.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Marketplace is open to external builders. If you have authored reusable
|
||||
agent infrastructure — an MCP plugin, a single-agent skill, or a full team
|
||||
bundle — you can list it and reach every Molecule organization. We handle
|
||||
distribution, billing, and policy; you keep the IP and the upgrade cadence.
|
||||
|
||||
This page walks through the three-step workflow: **Build · List · Earn**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Build
|
||||
|
||||
You author your artifact against the open Molecule SDK. The same primitives
|
||||
we use internally are available to you:
|
||||
|
||||
- **Workspace** — the durable boundary for memory, members, and policy
|
||||
- **A2A** — agent-to-agent messaging, used to talk to runtimes you don't
|
||||
own (LangGraph, CrewAI, etc.)
|
||||
- **Memory scopes** — hierarchical, governance-aware persistence
|
||||
- **Audit** — every action is captured at the orchestration layer
|
||||
|
||||
Pick the tier that matches your artifact's granularity:
|
||||
|
||||
### L1 — Plugins
|
||||
|
||||
A plugin is an MCP server (or an agentskills.io pack). The
|
||||
[two-axis source/shape model](/docs/plugins) describes how the workspace
|
||||
runtime loads it. Authoring requirements:
|
||||
|
||||
- A `plugin.yaml` manifest declaring tools, required scopes, and runtime
|
||||
compatibility.
|
||||
- A README documenting the tool surface and side effects.
|
||||
- For MCP plugins: an HTTP or stdio MCP server pinned to a tagged commit.
|
||||
|
||||
A reference plugin lives at
|
||||
[`Molecule-AI/molecule-ai-plugin-template`](https://github.com/Molecule-AI/molecule-ai-plugin-template).
|
||||
|
||||
### L2 — Agents
|
||||
|
||||
An agent is a single workspace member with a baked-in prompt + tools +
|
||||
policy. Authoring requirements:
|
||||
|
||||
- An `agent.yaml` manifest declaring system prompt, required L1 plugins,
|
||||
scope reads/writes, and approval triggers.
|
||||
- A `prompts/` directory with the system prompt and any reusable templates.
|
||||
- A `tests/` directory exercising the prompt against canned scenarios.
|
||||
|
||||
Reference: [`Molecule-AI/molecule-ai-agent-template`](https://github.com/Molecule-AI/molecule-ai-agent-template).
|
||||
|
||||
### L3 — Bundles
|
||||
|
||||
A bundle ships a complete team topology — a root agent plus children, each
|
||||
with its own scope and memory. Authoring requirements:
|
||||
|
||||
- A `bundle.yaml` declaring members, their scopes, and the policy graph
|
||||
(which scopes the root can write through, which approvals route to
|
||||
humans).
|
||||
- A `members/` directory containing member-specific overrides if any
|
||||
member is a fork of an L2 agent.
|
||||
- A `topology.svg` diagram (auto-rendered from `bundle.yaml`, but you can
|
||||
override).
|
||||
|
||||
Reference: [`Molecule-AI/molecule-ai-bundle-template`](https://github.com/Molecule-AI/molecule-ai-bundle-template).
|
||||
|
||||
---
|
||||
|
||||
## 2. List
|
||||
|
||||
Submit through the **Creator Portal** at
|
||||
[`https://moleculesai.app/creators`](https://moleculesai.app/creators).
|
||||
The submission flow:
|
||||
|
||||
1. **Connect** — link the GitHub repository hosting your artifact. We pull
|
||||
from tagged releases; we never re-tag or modify your code.
|
||||
2. **Manifest check** — we validate `plugin.yaml` / `agent.yaml` /
|
||||
`bundle.yaml` against the schema for your tier and surface any gaps.
|
||||
3. **Static analysis** — credential-shape scan, prompt-injection-pattern
|
||||
scan, and dependency vulnerability check on every tagged release.
|
||||
4. **Sandbox boot** — your artifact is mounted into a throwaway workspace
|
||||
to verify it boots, declares its scopes correctly, and surfaces a
|
||||
reasonable error path.
|
||||
5. **Trust tier** — every artifact starts at **Community**. Apply for
|
||||
**Partner** or **Verified** review once you have a couple of releases
|
||||
under your belt.
|
||||
|
||||
Pricing is configured at submission:
|
||||
|
||||
- **Free** — no charge to install.
|
||||
- **Per-seat** — a flat monthly amount per workspace member that mounts
|
||||
the artifact.
|
||||
- **Per-use** — metered against a unit you define (token calls, runs,
|
||||
alerts handled).
|
||||
- **Hybrid** — base seat fee plus metered overages.
|
||||
|
||||
You can change pricing on subsequent releases; existing installs are
|
||||
grandfathered to the version they pinned.
|
||||
|
||||
---
|
||||
|
||||
## 3. Earn
|
||||
|
||||
Once your listing is live, you receive:
|
||||
|
||||
- **Distribution** — every Molecule organization sees your listing in the
|
||||
Marketplace UI, gated only by their policy (`min_trust`, region, etc.).
|
||||
- **Billing** — Molecule handles the charge to the installing
|
||||
organization, deducts the platform fee (15% as of writing; check the
|
||||
current rate in the Creator Portal), and pays out monthly.
|
||||
- **Audit visibility** — you see install counts, version distribution,
|
||||
and aggregated usage metrics in the Creator Portal. You do **not** see
|
||||
per-organization data.
|
||||
- **Upgrade cadence** — semver: bump tags, organizations on a `^range`
|
||||
pin pull updates on their next workspace redeploy. Major bumps require
|
||||
re-approval of any new scopes.
|
||||
|
||||
---
|
||||
|
||||
## Policy & Safety
|
||||
|
||||
By listing, you agree to:
|
||||
|
||||
- **No exfiltration** — your code does not transmit organization data
|
||||
outside the scopes it declares.
|
||||
- **Pinned releases** — every version is pinned to an immutable commit;
|
||||
retagging is not permitted.
|
||||
- **Disclose model usage** — if your agent calls an LLM API, declare the
|
||||
provider and model so enterprise plans can route through their own
|
||||
keys.
|
||||
- **Respect approval triggers** — if your `agent.yaml` declares a scope
|
||||
that requires human approval (e.g. `write: pull_request_merge`), you
|
||||
must call the approval API before acting.
|
||||
|
||||
Listings that violate these terms are de-listed; refunds for affected
|
||||
installs are paid from your account.
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
Once a listing is live, you can:
|
||||
|
||||
- Push new tagged releases — they enter the static-analysis + sandbox
|
||||
flow automatically.
|
||||
- Mark older versions as **deprecated** to nudge installs to upgrade.
|
||||
- File **security advisories** that surface to every organization on a
|
||||
vulnerable pinned version.
|
||||
- Yank a release in the rare case of a critical bug; organizations
|
||||
pinned to the yanked tag are notified and offered the next safe version.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- [Marketplace](/docs/marketplace) — tier model and installation overview
|
||||
- [Plugins](/docs/plugins) — L1 plugin source/shape mechanics
|
||||
- [Workspace Configuration](/docs/workspace-config) — pinning marketplace
|
||||
entries in `workspace.yaml`
|
||||
- [Security » OWASP Agentic Top 10](/docs/security/owasp-agentic-top-10) — supply-chain considerations relevant to bundle authors
|
||||
@ -12,6 +12,7 @@
|
||||
"channels",
|
||||
"schedules",
|
||||
"runtime-mcp",
|
||||
"runtime-mcp/dev-channels-flag",
|
||||
"external-agents",
|
||||
"tokens",
|
||||
"api-reference",
|
||||
@ -20,6 +21,9 @@
|
||||
"self-hosting/admin-token",
|
||||
"observability",
|
||||
"troubleshooting",
|
||||
"---Marketplace---",
|
||||
"marketplace",
|
||||
"marketplace/creators",
|
||||
"---Security---",
|
||||
"security/index",
|
||||
"security/safe-mcp-advisory",
|
||||
@ -28,6 +32,8 @@
|
||||
"google-adk",
|
||||
"hermes",
|
||||
"---Integrations---",
|
||||
"opencode"
|
||||
"opencode",
|
||||
"---Migration---",
|
||||
"migration/a2a-sdk-v0-to-v1"
|
||||
]
|
||||
}
|
||||
@ -35,7 +35,7 @@ is your checklist.
|
||||
|
||||
The four breaking changes that hit the Molecule runtime during KI-009.
|
||||
All four are confirmed against
|
||||
`molecule-core/workspace/` source.
|
||||
`/Users/hongming/Documents/GitHub/molecule-monorepo/workspace/` source.
|
||||
|
||||
### 1. `new_agent_text_message` renamed to `new_text_message`
|
||||
|
||||
@ -79,7 +79,7 @@ The factories also let you mount the JSON-RPC endpoint at any path
|
||||
|
||||
If you pass `state_transition_history=...` as a kwarg to
|
||||
`AgentCapabilities` under v1, Pydantic will reject it. Drop the kwarg.
|
||||
See [`workspace/main.py:215`](https://git.moleculesai.app/Molecule-AI/molecule-core/blob/main/workspace/main.py#L215)
|
||||
See [`workspace/main.py:215`](https://github.com/Molecule-AI/molecule-monorepo/blob/main/workspace/main.py#L215)
|
||||
for the explanatory comment that prevents future accidental re-adds.
|
||||
|
||||
## Common error shapes
|
||||
@ -104,7 +104,7 @@ even if the field name isn't on the cheat sheet above.
|
||||
1. **Bump the dep** — `a2a-sdk[http-server]>=0.3.25` is the floor; remove
|
||||
any `<1.0` upper bound. The Molecule wheel uses
|
||||
`a2a-sdk[http-server]>=0.3.25` with no upper bound (see
|
||||
[`molecule-ai-workspace-runtime/pyproject.toml`](https://git.moleculesai.app/Molecule-AI/molecule-ai-workspace-runtime/blob/main/pyproject.toml)).
|
||||
[`molecule-ai-workspace-runtime/pyproject.toml`](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/blob/main/pyproject.toml)).
|
||||
2. **Fix imports** — sweep the four renamed/removed symbols above. A
|
||||
safe grep is `grep -rn "from a2a\\|import a2a"` across your tree.
|
||||
3. **Fix removed-field reads/writes** — search for
|
||||
@ -118,7 +118,7 @@ even if the field name isn't on the cheat sheet above.
|
||||
6. **Re-run tests** — fixture-level mocks of `a2a.helpers` /
|
||||
`a2a.utils` need to mock both names so tests still pass during the
|
||||
rename rollout (see
|
||||
[`workspace/tests/conftest.py:105-111`](https://git.moleculesai.app/Molecule-AI/molecule-core/blob/main/workspace/tests/conftest.py#L105-L111)
|
||||
[`workspace/tests/conftest.py:105-111`](https://github.com/Molecule-AI/molecule-monorepo/blob/main/workspace/tests/conftest.py#L105-L111)
|
||||
for the dual-name pattern).
|
||||
|
||||
## Before / after diffs
|
||||
@ -178,7 +178,7 @@ The `enable_v0_3_compat=True` flag on `create_jsonrpc_routes` is what
|
||||
keeps in-flight v0 callers (peers that haven't migrated yet) from
|
||||
breaking — it accepts the old method names and translates them. The
|
||||
Molecule runtime ships with this flag on (see
|
||||
[`workspace/main.py:279`](https://git.moleculesai.app/Molecule-AI/molecule-core/blob/main/workspace/main.py#L279));
|
||||
[`workspace/main.py:279`](https://github.com/Molecule-AI/molecule-monorepo/blob/main/workspace/main.py#L279));
|
||||
strip it once your entire fleet is on v1.
|
||||
|
||||
## For downstream consumers
|
||||
@ -207,8 +207,8 @@ migrate at your own pace.
|
||||
|
||||
## See also
|
||||
|
||||
- [`molecule-ai-workspace-runtime` v0.1.11 release](https://git.moleculesai.app/Molecule-AI/molecule-ai-workspace-runtime/releases/tag/v0.1.11) — first wheel containing KI-009
|
||||
- [PR #39 — feat: migrate a2a-sdk 1.x (KI-009)](https://git.moleculesai.app/Molecule-AI/molecule-ai-workspace-runtime/pulls/39)
|
||||
- [PR #48 — feat(a2a): dual-compat for a2a-sdk 0.3.x and 1.x](https://git.moleculesai.app/Molecule-AI/molecule-ai-workspace-runtime/pulls/48) — runtime-side compat shim that keeps v0 peers working against the v1 wheel
|
||||
- [`molecule-ai-workspace-runtime` v0.1.11 release](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/releases/tag/v0.1.11) — first wheel containing KI-009
|
||||
- [PR #39 — feat: migrate a2a-sdk 1.x (KI-009)](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/39)
|
||||
- [PR #48 — feat(a2a): dual-compat for a2a-sdk 0.3.x and 1.x](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/48) — runtime-side compat shim that keeps v0 peers working against the v1 wheel
|
||||
- [Bring Your Own Runtime (MCP)](/docs/runtime-mcp) — universal wheel install path
|
||||
- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes
|
||||
|
||||
@ -163,11 +163,11 @@ not expose.
|
||||
| `molecule-skill-update-docs` | `[claude_code]` | `[claude_code, hermes]` |
|
||||
|
||||
Companion PRs:
|
||||
- [molecule-ai-plugin-ecc#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-ecc/pull/2)
|
||||
- [molecule-ai-plugin-superpowers#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-superpowers/pull/2)
|
||||
- [molecule-ai-plugin-molecule-dev#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-dev/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-cron-learnings#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-skill-cron-learnings/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-update-docs#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-skill-update-docs/pull/2)
|
||||
- [molecule-ai-plugin-ecc#2](https://github.com/Molecule-AI/molecule-ai-plugin-ecc/pull/2)
|
||||
- [molecule-ai-plugin-superpowers#2](https://github.com/Molecule-AI/molecule-ai-plugin-superpowers/pull/2)
|
||||
- [molecule-ai-plugin-molecule-dev#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-dev/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-cron-learnings#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-skill-cron-learnings/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-update-docs#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-skill-update-docs/pull/2)
|
||||
|
||||
Security note: Security Auditor was offline at time of change. Self-assessed
|
||||
as non-security-impacting — adding `hermes` to a string list in `plugin.yaml`
|
||||
|
||||
@ -11,8 +11,8 @@ Get a Molecule AI workspace running in under five minutes.
|
||||
## 1. Install Molecule AI
|
||||
|
||||
```bash
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
git clone https://github.com/Molecule-AI/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
@ -78,4 +78,4 @@ Or type `/ask what's our deployment status?` in your connected Discord channel.
|
||||
- [Review the REST API reference](/docs/guides/org-api-keys)
|
||||
- [Browse all guides](/docs/guides)
|
||||
|
||||
Explore the [Gitea repo](https://git.moleculesai.app/molecule-ai/molecule-core) for self-hosting options, or visit [moleculesai.app](https://moleculesai.app) for the hosted platform.
|
||||
Explore the [GitHub repo](https://github.com/Molecule-AI/molecule-monorepo) for self-hosting options, or visit [moleculesai.app](https://moleculesai.app) for the hosted platform.
|
||||
|
||||
@ -52,14 +52,54 @@ set.
|
||||
|
||||
### Claude Code
|
||||
|
||||
Two equivalent paths — pick whichever your version supports.
|
||||
|
||||
**CLI (Claude Code 2.1+):** pass each env var with `-e`, scope with
|
||||
`-s user` so the server is available in every project, and put the
|
||||
command after `--`:
|
||||
|
||||
```bash
|
||||
claude mcp add molecule -s user -- env \
|
||||
WORKSPACE_ID=<your-workspace-uuid> \
|
||||
PLATFORM_URL=https://<your-tenant>.moleculesai.app \
|
||||
MOLECULE_WORKSPACE_TOKEN=<your-token> \
|
||||
molecule-mcp
|
||||
claude mcp add molecule -s user \
|
||||
-e WORKSPACE_ID=<your-workspace-uuid> \
|
||||
-e PLATFORM_URL=https://<your-tenant>.moleculesai.app \
|
||||
-e MOLECULE_WORKSPACE_TOKEN=<your-token> \
|
||||
-- molecule-mcp
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
Older docs used a `-- env VAR=val ... molecule-mcp` shell trick (with
|
||||
`env` as the command). It still works but produces a less idiomatic
|
||||
`~/.claude.json` entry and trips up the post-2.1 flag parser if you
|
||||
forget the `--`. Prefer the `-e` form above.
|
||||
</Callout>
|
||||
|
||||
**Direct edit of `~/.claude.json`:** add the entry under the **top-level
|
||||
`mcpServers` key** (this is the user-scope location — available in
|
||||
every project). If you'd rather scope it to a single project, use a
|
||||
`.mcp.json` file in that project's root with the same `mcpServers`
|
||||
shape.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"molecule": {
|
||||
"type": "stdio",
|
||||
"command": "molecule-mcp",
|
||||
"args": [],
|
||||
"env": {
|
||||
"WORKSPACE_ID": "<your-workspace-uuid>",
|
||||
"PLATFORM_URL": "https://<your-tenant>.moleculesai.app",
|
||||
"MOLECULE_WORKSPACE_TOKEN": "<your-token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `molecule-mcp` isn't on the PATH that Claude Code sees (common on
|
||||
macOS — see [Troubleshooting](#command-not-found-molecule-mcp-from-inside-the-runtime)),
|
||||
replace `"command": "molecule-mcp"` with the absolute path from `which molecule-mcp`.
|
||||
|
||||
Reconnect with `/mcp` (or restart the Claude Code session) and the tools
|
||||
appear in the next turn.
|
||||
|
||||
@ -102,49 +142,55 @@ example above. Drop it into your client's MCP settings file
|
||||
(typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for
|
||||
Cline) and restart the client.
|
||||
|
||||
## Environment variables
|
||||
|
||||
The following env vars are supported by the `molecule-mcp` wheel in addition to the
|
||||
required trio (`WORKSPACE_ID`, `PLATFORM_URL`, `MOLECULE_WORKSPACE_TOKEN`):
|
||||
|
||||
| Env var | What it controls | Default |
|
||||
|---|---|---|
|
||||
| `MOLECULE_MODEL` | **Canonical.** The model ID the workspace runtime uses — e.g. `claude-opus-4-7`, `minimax/MiniMax-M2.7-highspeed` | _(unset — template default)_ |
|
||||
| `MODEL` | **Alias for `MOLECULE_MODEL`.** Accepted for backwards compatibility. | _(unset)_ |
|
||||
| `MODEL_PROVIDER` | **Deprecated.** This var was previously misread as "runtime selector" (`claude-code`, `minimax`, etc.) but carried the model ID, causing the wrong model to be used. Prefer `MOLECULE_MODEL`. | _(unset — emits deprecation warning)_ |
|
||||
| `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` |
|
||||
|
||||
<Callout type="warn">
|
||||
`MODEL_PROVIDER` is deprecated. It was misnamed — despite its name it carried the **model ID** (e.g. `claude-opus-4-7`), not the runtime/provider name. Setting it caused production incidents where the Claude CLI received `--model MODEL_PROVIDER_VALUE` and returned 404s. Use `MOLECULE_MODEL` instead.
|
||||
</Callout>
|
||||
|
||||
## Optional — declare your identity & capabilities
|
||||
|
||||
Three additional env vars control how your workspace appears on the
|
||||
canvas and to peer agents calling `list_peers`:
|
||||
Four additional env vars control how your workspace appears on the
|
||||
canvas and how the wheel's inbound-delivery contract behaves:
|
||||
|
||||
| Env var | What it sets | Default |
|
||||
|---|---|---|
|
||||
| `MOLECULE_AGENT_NAME` | Display name on the canvas card | `molecule-mcp-{id[:8]}` |
|
||||
| `MOLECULE_AGENT_DESCRIPTION` | One-line description in Details/Skills tabs | empty |
|
||||
| `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` |
|
||||
| `MOLECULE_MCP_POLL_TIMEOUT_SECS` | How long the agent blocks on `wait_for_message` per turn (the universal poll path). `0` disables polling for push-only mode (Claude Code launched with `--dangerously-load-development-channels server:molecule`). Above 60 clamps to 60. | `2` |
|
||||
|
||||
Skills are surfaced two places:
|
||||
|
||||
1. **Canvas Skills tab** — each skill renders as a chip with the name
|
||||
2. **Peer agents calling `list_peers`** — they see `{name, skills: [...]}` for each peer, so other agents can route delegations to the right specialist instead of guessing from name alone
|
||||
|
||||
Example with all three set:
|
||||
Example with all three set (Claude Code 2.1+ CLI form):
|
||||
|
||||
```bash
|
||||
claude mcp add molecule -s user -- env \
|
||||
WORKSPACE_ID=<uuid> \
|
||||
PLATFORM_URL=https://<tenant>.moleculesai.app \
|
||||
MOLECULE_WORKSPACE_TOKEN=<token> \
|
||||
MOLECULE_AGENT_NAME='Research Assistant' \
|
||||
MOLECULE_AGENT_DESCRIPTION='Reads, summarises, cites.' \
|
||||
MOLECULE_AGENT_SKILLS=research,summarisation,citations \
|
||||
molecule-mcp
|
||||
claude mcp add molecule -s user \
|
||||
-e WORKSPACE_ID=<uuid> \
|
||||
-e PLATFORM_URL=https://<tenant>.moleculesai.app \
|
||||
-e MOLECULE_WORKSPACE_TOKEN=<token> \
|
||||
-e MOLECULE_AGENT_NAME='Research Assistant' \
|
||||
-e MOLECULE_AGENT_DESCRIPTION='Reads, summarises, cites.' \
|
||||
-e MOLECULE_AGENT_SKILLS=research,summarisation,citations \
|
||||
-- molecule-mcp
|
||||
```
|
||||
|
||||
Or as the equivalent `~/.claude.json` entry:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"molecule": {
|
||||
"type": "stdio",
|
||||
"command": "molecule-mcp",
|
||||
"env": {
|
||||
"WORKSPACE_ID": "<uuid>",
|
||||
"PLATFORM_URL": "https://<tenant>.moleculesai.app",
|
||||
"MOLECULE_WORKSPACE_TOKEN": "<token>",
|
||||
"MOLECULE_AGENT_NAME": "Research Assistant",
|
||||
"MOLECULE_AGENT_DESCRIPTION": "Reads, summarises, cites.",
|
||||
"MOLECULE_AGENT_SKILLS": "research,summarisation,citations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A peer agent's `list_peers()` call would then surface this workspace
|
||||
@ -174,7 +220,7 @@ status. If the workspace is still offline after ~30s, check
|
||||
| `delegate_task` | Send a task to a peer and wait for the reply |
|
||||
| `delegate_task_async` | Fire-and-forget delegation; result lands in inbox |
|
||||
| `check_task_status` | Poll an async delegation |
|
||||
| `wait_for_message` | Block until the next inbound A2A message arrives |
|
||||
| `wait_for_message` | Block until the next inbound A2A message arrives — the universal inbound-delivery primitive (see [Inbound delivery](#inbound-delivery-universal-poll-optional-push)) |
|
||||
| `inbox_peek` / `inbox_pop` | Inspect / acknowledge queued inbound messages |
|
||||
| `send_message_to_user` | Push a chat bubble to the user's canvas |
|
||||
| `commit_memory` / `recall_memory` | Persistent KV (local / team / global scope) |
|
||||
@ -184,29 +230,130 @@ External runtimes can't accept inbound HTTP, so the wheel polls
|
||||
through `wait_for_message` + `inbox_peek` / `inbox_pop`. Use those
|
||||
instead of waiting for an HTTP webhook — there isn't one.
|
||||
|
||||
### Push-UX for notification-capable hosts
|
||||
### Inbound delivery: universal poll, optional push
|
||||
|
||||
On top of the polling tools, the wheel emits a JSON-RPC notification
|
||||
(`notifications/claude/channel`) on every new inbound message. Hosts
|
||||
that recognise that method (Claude Code today; any compliant client
|
||||
tomorrow) treat the notification as a conversation interrupt — the
|
||||
message text becomes the next agent turn without the agent having to
|
||||
call `wait_for_message` first.
|
||||
Inbound messages reach the agent via one of two paths. The wheel
|
||||
exposes both; which one fires depends on the host's capabilities.
|
||||
Both paths converge on the same `inbox_pop` ack so dedup is automatic.
|
||||
|
||||
Hosts that don't recognise the method silently ignore it, so the same
|
||||
wheel works for both push-capable and poll-only runtimes. There is no
|
||||
config flag to toggle: pollers keep polling, notification-capable hosts
|
||||
get push automatically.
|
||||
**Poll path (universal default — works on every spec-compliant MCP
|
||||
client).** The wheel's `initialize` handshake includes an `instructions`
|
||||
field telling the agent: *"At the start of every turn, before producing
|
||||
your final response, call `wait_for_message(timeout_secs=N)` to check
|
||||
for inbound messages."* Every MCP client surfaces `instructions` to
|
||||
the agent's system prompt automatically, so Claude Code, Cursor, Cline,
|
||||
OpenCode, hermes-agent, and codex all receive the polling contract
|
||||
without any per-client wiring. The 2-second default is tuned for the
|
||||
"peer A2A landed seconds before my turn started" common case; tune
|
||||
via the `MOLECULE_MCP_POLL_TIMEOUT_SECS` env var
|
||||
(see "Optional — declare your identity & capabilities" above).
|
||||
|
||||
**Push path (Claude Code with channel push enabled — strictly
|
||||
better when available).** On top of the poll path, the wheel emits a
|
||||
JSON-RPC notification (`notifications/claude/channel`) on every new
|
||||
inbound message and declares the matching `experimental.claude/channel`
|
||||
capability in `initialize`. Claude Code with channel push enabled
|
||||
turns the notification into an inline `<channel source="molecule"
|
||||
...>` synthetic user turn — zero agent-side polling cost, zero
|
||||
per-turn stall.
|
||||
|
||||
**Today (research preview), Claude Code's channel push requires
|
||||
either the `--dangerously-load-development-channels` launch flag OR
|
||||
an entry on Claude Code's approved channel-server allowlist.** The
|
||||
wheel ships the wire shape correctly, but a standard `claude` launch
|
||||
without the flag silently drops the notification — which is why the
|
||||
poll path has to be the floor.
|
||||
|
||||
See [Dev-channels flag — tagged-form requirement](/docs/runtime-mcp/dev-channels-flag)
|
||||
for the exact form the flag must take, the failure mode when it's
|
||||
wrong, and when operators need to set it manually vs. when the
|
||||
hosted SaaS / workspace template handles it for them.
|
||||
|
||||
Since Claude Code 2.1.x the flag takes a tagged allowlist, not a bare
|
||||
switch. Pass each MCP server you want to push from as `server:<name>`
|
||||
(matching the name you registered the server under in Claude Code's
|
||||
config — `molecule` if you followed [Step 2](#claude-code) above):
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule
|
||||
```
|
||||
|
||||
Multiple entries are space-separated:
|
||||
`server:molecule server:telegram`. A bare
|
||||
`--dangerously-load-development-channels` (no value) is rejected with
|
||||
`argument missing`; an untagged value (`molecule`) is rejected with
|
||||
`entries must be tagged`. Easy way to confirm push is live: the
|
||||
session header prints `Listening for channel messages from:
|
||||
server:molecule`, and inbound canvas messages render inline as
|
||||
`← molecule: <text>` instead of arriving via `inbox_peek`.
|
||||
|
||||
Set `MOLECULE_MCP_POLL_TIMEOUT_SECS=0` to disable polling entirely
|
||||
when you're running Claude Code with the dev-channels flag and don't
|
||||
want the per-turn stall. The instructions adapt automatically: with
|
||||
polling disabled, the agent is told push is the only delivery path.
|
||||
|
||||
#### `<channel>` envelope attributes
|
||||
|
||||
Every inbound message — push or poll — carries the same metadata
|
||||
shape. On the push path, attributes render inline as XML-style attrs
|
||||
on the `<channel>` tag; on the poll path, the same fields appear in
|
||||
the JSON returned by `inbox_peek` / `wait_for_message`. Either way,
|
||||
the agent sees a consistent view.
|
||||
|
||||
| Attribute | When present | Description |
|
||||
|---|---|---|
|
||||
| `source` | always | Always `molecule` — distinguishes our channel from other registered servers (`telegram`, etc.). |
|
||||
| `kind` | always | `canvas_user` (a human in the canvas chat) or `peer_agent` (another workspace's agent). Drives reply routing. |
|
||||
| `peer_id` | always | Empty for `canvas_user`; the sender's workspace UUID for `peer_agent`. Use as `workspace_id` when calling `delegate_task` to reply. |
|
||||
| `peer_name` | `peer_agent` only | The peer's display name (e.g. `ops-agent`) resolved from the platform registry. Absent on registry-lookup failure — the push still delivers. |
|
||||
| `peer_role` | `peer_agent` only | The peer's declared role (e.g. `sre`, `coordinator`). Same registry source as `peer_name`; same graceful-degrade rule. |
|
||||
| `agent_card_url` | `peer_agent` only | URL of the platform's discover endpoint for this peer. Fetch it if you need the peer's full capability list (skills, runtime, etc.). |
|
||||
| `activity_id` | always | The inbox row ID. **Pass it to `inbox_pop` after handling** so the message isn't re-delivered on the next push or poll cycle. |
|
||||
| `ts` | always | ISO-8601 timestamp of when the message landed in the platform's activity log. |
|
||||
|
||||
`peer_name` and `peer_role` are added by the wheel via a TTL'd
|
||||
registry lookup keyed on `peer_id`. Cache TTL is 5 minutes — long
|
||||
enough that a busy multi-peer chat doesn't hit the registry on every
|
||||
push, short enough that role/name renames propagate within a single
|
||||
agent session. Lookup failure is silent: the attributes are simply
|
||||
absent and the push delivers anyway, so a registry stall can never
|
||||
block inbound messages.
|
||||
|
||||
`agent_card_url` is constructed deterministically from `peer_id`, so
|
||||
it's present even if the registry is down. The agent can hit it
|
||||
later to enumerate the sender's capabilities once the registry is
|
||||
back up.
|
||||
|
||||
Worked push example for a `peer_agent` arrival:
|
||||
|
||||
```
|
||||
<channel source="molecule" kind="peer_agent"
|
||||
peer_id="11111111-2222-3333-4444-555555555555"
|
||||
peer_name="ops-agent" peer_role="sre"
|
||||
agent_card_url="https://platform.example.com/registry/discover/11111111-2222-3333-4444-555555555555"
|
||||
activity_id="act-742" ts="2026-05-01T12:34:56Z">
|
||||
Can you check the deploy status for the canary?
|
||||
</channel>
|
||||
```
|
||||
|
||||
| Client | Push path | Poll path |
|
||||
|---|---|---|
|
||||
| Claude Code with `--dangerously-load-development-channels server:molecule` | ✅ inline `← molecule:` tag | ✅ also works |
|
||||
| Claude Code (standard launch) | ❌ silently dropped | ✅ via instructions |
|
||||
| Cursor / Cline / OpenCode / codex | ❌ method ignored | ✅ via instructions |
|
||||
| hermes-agent | ❌ method ignored | ✅ naturally polls every cycle |
|
||||
|
||||
### MCP spec compliance
|
||||
|
||||
The wheel speaks MCP protocol version **2024-11-05** over stdio
|
||||
JSON-RPC, declaring only the `tools` capability. It implements the
|
||||
standard request methods and nothing client-specific:
|
||||
JSON-RPC. It declares the standard `tools` capability plus the
|
||||
`experimental.claude/channel` capability for the optional push path
|
||||
(see [Inbound delivery](#inbound-delivery-universal-poll-optional-push)).
|
||||
It implements the standard request methods and nothing client-specific:
|
||||
|
||||
| MCP method | Behavior |
|
||||
|---|---|
|
||||
| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` capability |
|
||||
| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` + `experimental.claude/channel` capabilities, returns the dual-path delivery `instructions` |
|
||||
| `notifications/initialized` | No-op (no response — per spec) |
|
||||
| `tools/list` | Returns all exposed tools in one response (no pagination cursor — surface is small) |
|
||||
| `tools/call` | Dispatches by name, returns `content: [{ type: "text", text: ... }]` |
|
||||
@ -214,46 +361,16 @@ standard request methods and nothing client-specific:
|
||||
|
||||
The push-UX notification (`notifications/claude/channel`) is the only
|
||||
non-standard method emitted, and it's a one-way notification — clients
|
||||
that don't handle it discard it per JSON-RPC semantics. No part of the
|
||||
wheel's tool surface depends on a client recognizing it.
|
||||
that don't handle it discard it per JSON-RPC semantics. The poll path
|
||||
(via the standard `instructions` field) carries delivery for those
|
||||
clients, so no part of the wheel's tool surface depends on a client
|
||||
recognizing the notification.
|
||||
|
||||
This means **any spec-compliant MCP client** can drive the wheel:
|
||||
Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else
|
||||
that opens an MCP stdio connection. If your client speaks MCP, it
|
||||
speaks the wheel.
|
||||
|
||||
## HTTP/SSE transport for Hermes workspaces
|
||||
|
||||
Hermes workspaces (which are MCP-native) can connect to the platform MCP
|
||||
server over **HTTP + Server-Sent Events** instead of stdio. This is the
|
||||
recommended path when Hermes runs as a standalone service rather than
|
||||
inside a shell.
|
||||
|
||||
The `a2a_mcp_server.py` in the runtime exposes two endpoints:
|
||||
|
||||
| Endpoint | Method | Purpose |
|
||||
|---|---|---|
|
||||
| `/mcp` | `POST` | Receive JSON-RPC requests |
|
||||
| `/mcp/stream` | `GET` | SSE stream for push-based responses |
|
||||
| `/health` | `GET` | Health check |
|
||||
|
||||
Start the server with the `--transport=http --port=<N>` flags:
|
||||
|
||||
```bash
|
||||
python a2a_mcp_server.py \
|
||||
--transport=http \
|
||||
--port=8080 \
|
||||
--workspace-id=<uuid> \
|
||||
--platform-url=https://<tenant>.moleculesai.app \
|
||||
--workspace-token=<token>
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
The stdio transport (described in [Step 2](#step-2--add-it-to-your-runtime))
|
||||
remains the default. HTTP/SSE is an alternative for Hermes deployments
|
||||
where a long-running daemon process is preferred over a stdio subprocess.
|
||||
</Callout>
|
||||
|
||||
## Heartbeat & lifecycle
|
||||
|
||||
The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every
|
||||
@ -322,7 +439,7 @@ MCP config and restart your runtime.
|
||||
|
||||
### `Workspace <id> was deleted on the platform...` from `get_workspace_info`
|
||||
|
||||
Since [#2429](https://git.moleculesai.app/molecule-ai/molecule-core/pull/2449),
|
||||
Since [#2429](https://github.com/Molecule-AI/molecule-core/pull/2449),
|
||||
`GET /workspaces/:id` returns **410 Gone** (not 200 + `status:"removed"`)
|
||||
when the workspace has been deleted. The MCP wheel's `get_workspace_info`
|
||||
tool surfaces this as a tailored error message:
|
||||
@ -353,6 +470,21 @@ A quick way to confirm: `ps aux | grep molecule-mcp` and check the
|
||||
PID hasn't changed across `/mcp` reconnects. If the same PID stays
|
||||
alive, the runtime is still using the old config.
|
||||
|
||||
### `claude mcp add` rejects the install command on Claude Code 2.1+
|
||||
|
||||
Two common shapes from older docs trip the 2.1+ parser:
|
||||
|
||||
- `claude mcp add molecule -s user -- env VAR=val molecule-mcp` — works
|
||||
but lands as `command: "env"` with positional args, which surprises
|
||||
some MCP clients on older 2.1.x patch builds.
|
||||
- `claude mcp add molecule -e VAR=val molecule-mcp` (missing `--`) — the
|
||||
CLI parses `molecule-mcp` as a flag value, not a command, and either
|
||||
errors or silently registers nothing.
|
||||
|
||||
Use the `-e` form **with** `--` (see [Step 2](#claude-code)), or skip the
|
||||
CLI entirely and write the JSON shape into `~/.claude.json` directly.
|
||||
The on-disk shape is the source of truth and not version-sensitive.
|
||||
|
||||
### `command not found: molecule-mcp` from inside the runtime
|
||||
|
||||
The runtime's `PATH` may differ from your interactive shell — common
|
||||
@ -367,6 +499,44 @@ which molecule-mcp
|
||||
Then point `command` at that absolute path in `claude mcp add` /
|
||||
`.cursor/mcp.json` / `mcp_servers.yaml`.
|
||||
|
||||
### `error: option '--dangerously-load-development-channels <servers...>' argument missing`
|
||||
|
||||
You're on Claude Code 2.1.x or later. The flag changed from a bare
|
||||
switch to an allowlist that takes tagged entries. See
|
||||
[Inbound delivery](#inbound-delivery-universal-poll-optional-push) for
|
||||
the right form — short answer:
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule
|
||||
```
|
||||
|
||||
### `--dangerously-load-development-channels entries must be tagged: molecule`
|
||||
|
||||
The flag value needs the `server:` (or `plugin:`) prefix. Pass
|
||||
`server:molecule` (the registered MCP server name), not bare
|
||||
`molecule`.
|
||||
|
||||
### `Control request timeout: initialize` from the workspace agent
|
||||
|
||||
This is the symptom of forwarding the dev-channels flag to a nested
|
||||
`claude` CLI through the `claude-agent-sdk` with the wrong shape. If
|
||||
you embed the wheel inside an SDK-driven agent (e.g. the claude-code
|
||||
workspace template's `claude_sdk_executor.py`), pass the tagged value
|
||||
through `extra_args`:
|
||||
|
||||
```python
|
||||
ClaudeAgentOptions(
|
||||
...,
|
||||
extra_args={"dangerously-load-development-channels": "server:molecule"},
|
||||
)
|
||||
```
|
||||
|
||||
The SDK forwards `extra_args` keys as `--<key> <value>` to the spawned
|
||||
CLI. Passing `None` renders as a bare switch and the post-2.1.x CLI
|
||||
rejects it with `argument missing`, which surfaces upstream as
|
||||
`Control request timeout: initialize` (the SDK never gets a response
|
||||
to its initialize control message).
|
||||
|
||||
## When to use this vs. the manual A2A path
|
||||
|
||||
| Scenario | Use |
|
||||
|
||||
176
content/docs/runtime-mcp/dev-channels-flag.mdx
Normal file
176
content/docs/runtime-mcp/dev-channels-flag.mdx
Normal file
@ -0,0 +1,176 @@
|
||||
---
|
||||
title: "Dev-channels flag — tagged-form requirement"
|
||||
description: "Why Claude Code 2.1.x+ requires `--dangerously-load-development-channels server:molecule` (not the bare flag) to enable inline channel push from the molecule-mcp wheel."
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
The `molecule-mcp` wheel emits a JSON-RPC `notifications/claude/channel`
|
||||
notification on every inbound A2A message so Claude Code can render it
|
||||
as an inline `<channel>` synthetic user turn — zero polling, zero
|
||||
per-turn stall. During the channels research preview, Claude Code only
|
||||
processes that notification when the host is launched with the
|
||||
`--dangerously-load-development-channels` flag *and the flag carries a
|
||||
matching tagged allowlist entry*.
|
||||
|
||||
This page covers the form that flag must take, what breaks when it's
|
||||
wrong, and when an operator has to think about it.
|
||||
|
||||
<Callout type="warn">
|
||||
The bare flag (no value) is rejected by the post-2.1 CLI parser, and
|
||||
the failure mode propagates upstream as a `Control request timeout:
|
||||
initialize` from any SDK that spawns the CLI — every A2A turn wedges
|
||||
100% of the time. See [Failure mode](#failure-mode) below.
|
||||
</Callout>
|
||||
|
||||
## The flag
|
||||
|
||||
```
|
||||
--dangerously-load-development-channels <entries...>
|
||||
```
|
||||
|
||||
Available in Claude Code **2.1.x and later**. It opts the CLI into
|
||||
processing experimental `notifications/<channel>` JSON-RPC methods
|
||||
emitted by registered MCP servers and plugin channels. Without it, the
|
||||
CLI silently drops those notifications during the allowlist check, even
|
||||
though the wheel ships the wire shape correctly.
|
||||
|
||||
## Required form: tagged allowlist entries
|
||||
|
||||
Each entry must carry one of two prefixes:
|
||||
|
||||
| Form | Use for |
|
||||
|---|---|
|
||||
| `server:<MCP-server-name>` | Manually configured MCP servers — the name matches what you registered with `claude mcp add <name> ...` or the key under `mcpServers` in `~/.claude.json`. |
|
||||
| `plugin:<plugin-name>@<owner>/<repo>` | Plugin channels installed from a Claude Code plugin marketplace. |
|
||||
|
||||
Multiple entries are space-separated:
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule server:telegram
|
||||
```
|
||||
|
||||
Untagged values (`molecule` instead of `server:molecule`) are rejected
|
||||
with `--dangerously-load-development-channels entries must be tagged`.
|
||||
|
||||
## Failure mode
|
||||
|
||||
A bare flag (`--dangerously-load-development-channels` with no value)
|
||||
walks through three layers of damage before surfacing:
|
||||
|
||||
1. **CLI**: rejects the invocation with
|
||||
`error: option '--dangerously-load-development-channels <servers...>' argument missing`.
|
||||
2. **SDK**: `claude-agent-sdk` (used by `claude_sdk_executor.py` in the
|
||||
Claude Code workspace template) renders the kwarg as a bare switch when
|
||||
the value is `None`. The CLI then never responds to the SDK's first
|
||||
`initialize` control message.
|
||||
3. **Workspace agent**: the SDK times out with
|
||||
`Control request timeout: initialize`. Every A2A turn wedges — 100%
|
||||
reproducible. Caught live on workspace `dd40faf8` on 2026-05-01.
|
||||
|
||||
Two small fixes prevent this: pass a tagged value (don't let `None`
|
||||
render as a bare switch), and verify the CLI accepts your specific
|
||||
entries before going broad.
|
||||
|
||||
## For Molecule operators
|
||||
|
||||
Pass `server:molecule` to enable the inbox bridge → MCP
|
||||
`notifications/claude/channel` push for the `molecule-mcp` wheel.
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule
|
||||
```
|
||||
|
||||
The `molecule` here matches the name you registered the wheel under in
|
||||
[Step 2 of the runtime-mcp guide](/docs/runtime-mcp#claude-code) (the
|
||||
key under `mcpServers`, or the first positional arg to `claude mcp add`).
|
||||
If you registered the wheel as `mol` or `molecule-prod`, use that name
|
||||
in the tag.
|
||||
|
||||
When push is live, the session header prints:
|
||||
|
||||
```
|
||||
Listening for channel messages from: server:molecule
|
||||
```
|
||||
|
||||
…and inbound canvas/peer-agent messages render inline as
|
||||
`<channel source="molecule" ...>` synthetic user turns instead of
|
||||
arriving via `inbox_peek`.
|
||||
|
||||
### Embedding in an SDK-driven agent
|
||||
|
||||
If you spawn `claude` through `claude-agent-sdk` (e.g. the Claude Code
|
||||
workspace template's `claude_sdk_executor.py`), forward the tagged value
|
||||
through `extra_args`:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
|
||||
ClaudeAgentOptions(
|
||||
model=self.model,
|
||||
permission_mode="bypassPermissions",
|
||||
cwd=self._resolve_cwd(),
|
||||
mcp_servers=mcp_servers,
|
||||
system_prompt=self._build_system_prompt(),
|
||||
resume=self._session_id,
|
||||
extra_args={"dangerously-load-development-channels": "server:molecule"},
|
||||
)
|
||||
```
|
||||
|
||||
The SDK forwards `extra_args` keys as `--<key> <value>` to the spawned
|
||||
CLI. Passing `None` as the value renders as a bare switch and trips the
|
||||
[Failure mode](#failure-mode) chain above.
|
||||
|
||||
## Verification
|
||||
|
||||
Verified live on 2026-05-02: with the tagged value in `extra_args`,
|
||||
the in-workspace agent received `<channel source="molecule" kind="..."
|
||||
peer_id="..." activity_id="..." ts="...">` tags inline as synthetic
|
||||
user turns. No `wait_for_message` poll was needed for delivery. A2A
|
||||
returned coherent replies on every turn.
|
||||
|
||||
## When this matters
|
||||
|
||||
Only when both of the following apply:
|
||||
|
||||
- You're running Claude Code (any version 2.1.x or later) as the
|
||||
workspace runtime, AND
|
||||
- The in-workspace `molecule-mcp` server is configured (it is, by
|
||||
default, in the `claude-code` workspace template).
|
||||
|
||||
**Hosted Molecule SaaS handles this automatically** — the executor
|
||||
passes `extra_args={"dangerously-load-development-channels": "server:molecule"}`
|
||||
when spawning the CLI. Operators on hosted SaaS do not need to do
|
||||
anything.
|
||||
|
||||
**Self-hosted operators using the Claude Code workspace template** also
|
||||
get this for free since the template's executor sets `extra_args`. The
|
||||
flag only needs operator attention when:
|
||||
|
||||
- Forking the Claude Code workspace template and stripping `extra_args`
|
||||
inadvertently.
|
||||
- Running `claude` directly outside the template (e.g. interactive
|
||||
sessions on a developer laptop) and wanting inline `<channel>` push.
|
||||
- Adding a second tagged source (e.g. `server:telegram` alongside
|
||||
`server:molecule`) — append, don't replace.
|
||||
|
||||
Operators on Cursor, Cline, OpenCode, codex, hermes-agent, or any
|
||||
non-Claude-Code MCP host are unaffected: those clients ignore the
|
||||
notification and the wheel's poll path delivers via
|
||||
`wait_for_message` as the universal fallback.
|
||||
|
||||
## Forward note
|
||||
|
||||
This requirement is a **research-preview gate**. Once Claude Code
|
||||
graduates `notifications/<channel>` from research preview to a default
|
||||
allowlist, the `--dangerously-load-development-channels` flag will no
|
||||
longer be required for the `molecule` server. Drop the `extra_args`
|
||||
entry in `claude_sdk_executor.py` (and any operator launch wrappers)
|
||||
when that happens — the wheel emits the wire shape correctly today
|
||||
and will continue to do so post-graduation.
|
||||
|
||||
## See also
|
||||
|
||||
- [Bring Your Own Runtime (MCP) — Inbound delivery](/docs/runtime-mcp#inbound-delivery-universal-poll-optional-push)
|
||||
- [Bring Your Own Runtime (MCP) — Step 2: Claude Code](/docs/runtime-mcp#claude-code)
|
||||
- [Troubleshooting — Control request timeout: initialize](/docs/runtime-mcp#control-request-timeout-initialize-from-the-workspace-agent)
|
||||
@ -12,7 +12,7 @@ This page documents security fixes shipped in the Molecule AI platform. Each ent
|
||||
## 2026-04-20 — CWE-22: Path Traversal in `copyFilesToContainer`
|
||||
|
||||
**Severity:** High (CWE-22)
|
||||
**PRs:** [#1271](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1271), [#1270](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1270), [#1267](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1267)
|
||||
**PRs:** [#1271](https://github.com/Molecule-AI/molecule-core/pull/1271), [#1270](https://github.com/Molecule-AI/molecule-core/pull/1270), [#1267](https://github.com/Molecule-AI/molecule-core/pull/1267)
|
||||
**Affected:** `workspace-server/internal/handlers/container_files.go` — `TemplatesHandler.copyFilesToContainer`
|
||||
|
||||
### Vulnerability
|
||||
@ -37,7 +37,7 @@ File writes to workspace containers now validate all paths before writing to the
|
||||
## 2026-04-20 — CWE-78: Shell Injection in `deleteViaEphemeral`
|
||||
|
||||
**Severity:** High (CWE-78)
|
||||
**PR:** [#1310](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1310)
|
||||
**PR:** [#1310](https://github.com/Molecule-AI/molecule-core/pull/1310)
|
||||
**Affected:** `workspace-server/internal/handlers/container_files.go` — `TemplatesHandler.deleteViaEphemeral`
|
||||
|
||||
### Vulnerability
|
||||
@ -69,9 +69,9 @@ Workspace file deletion operations now use safe argument-passing and validate al
|
||||
## 2026-04-21 — CWE-918: SSRF in MCP / A2A Proxy Endpoints (Updated: Regression Fix)
|
||||
|
||||
**Severity:** High (CWE-918)
|
||||
**Original PRs:** [#1274](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1274), [#1302](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1302)
|
||||
**Regression Fix PR:** [#1430](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1430)
|
||||
**Regression introduced by:** [#1363](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1363)
|
||||
**Original PRs:** [#1274](https://github.com/Molecule-AI/molecule-core/pull/1274), [#1302](https://github.com/Molecule-AI/molecule-core/pull/1302)
|
||||
**Regression Fix PR:** [#1430](https://github.com/Molecule-AI/molecule-core/pull/1430)
|
||||
**Regression introduced by:** [#1363](https://github.com/Molecule-AI/molecule-core/pull/1363)
|
||||
**Affected:** `workspace-server/internal/handlers/mcp.go` — `isSafeURL`, `isPrivateOrMetadataIP`; `workspace-server/internal/handlers/a2a_proxy.go`; `workspace-server/internal/handlers/a2a_proxy_helpers.go`
|
||||
|
||||
### Vulnerability
|
||||
@ -105,9 +105,9 @@ In **SaaS mode** (`saasMode()` returns true), cross-EC2 traffic to RFC-1918 addr
|
||||
|
||||
### Regression (2026-04-21)
|
||||
|
||||
PR [#1363](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1363) (handler refactor) moved `isPrivateOrMetadataIP` into `a2a_proxy_helpers.go` but kept a **pre-SaaS version** that unconditionally blocked RFC-1918 addresses, breaking cross-EC2 communication in SaaS. The old version also **returned `false` for all IPv6 inputs**, fully bypassing SSRF protection for IPv6 targets.
|
||||
PR [#1363](https://github.com/Molecule-AI/molecule-core/pull/1363) (handler refactor) moved `isPrivateOrMetadataIP` into `a2a_proxy_helpers.go` but kept a **pre-SaaS version** that unconditionally blocked RFC-1918 addresses, breaking cross-EC2 communication in SaaS. The old version also **returned `false` for all IPv6 inputs**, fully bypassing SSRF protection for IPv6 targets.
|
||||
|
||||
PR [#1430](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1430) restores the correct SaaS-gated logic and adds proper IPv6 coverage to the A2A proxy path.
|
||||
PR [#1430](https://github.com/Molecule-AI/molecule-core/pull/1430) restores the correct SaaS-gated logic and adds proper IPv6 coverage to the A2A proxy path.
|
||||
|
||||
### User-facing summary
|
||||
|
||||
@ -118,7 +118,7 @@ Platform outbound requests from workspaces (MCP tool calls, A2A proxy routing) v
|
||||
## 2026-04-21 — Audit Ledger HMAC Chain Guard
|
||||
|
||||
**Severity:** Low (denial-of-service / data integrity)
|
||||
**PRs:** [#1339](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1339), [#1352](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1352), [#1354](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1354) (backport to `main`)
|
||||
**PRs:** [#1339](https://github.com/Molecule-AI/molecule-core/pull/1339), [#1352](https://github.com/Molecule-AI/molecule-core/pull/1352), [#1354](https://github.com/Molecule-AI/molecule-core/pull/1354) (backport to `main`)
|
||||
**Affected:** `workspace-server/internal/handlers/audit.go`
|
||||
|
||||
### Vulnerability
|
||||
@ -144,7 +144,7 @@ Audit chain verification now handles short or malformed HMAC values gracefully,
|
||||
## 2026-04-21 — Credential Scrub: `err.Error()` Leak Prevention
|
||||
|
||||
**Severity:** Medium (information disclosure)
|
||||
**PRs:** [#1282](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1282), [#1355](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1355), [#1359](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1359)
|
||||
**PRs:** [#1282](https://github.com/Molecule-AI/molecule-core/pull/1282), [#1355](https://github.com/Molecule-AI/molecule-core/pull/1355), [#1359](https://github.com/Molecule-AI/molecule-core/pull/1359)
|
||||
**Affected:** `workspace-server/internal/handlers/plugins_install_pipeline.go`, `workspace-server/internal/handlers/workspace_provision.go`, `content/docs/incidents/INCIDENT_LOG.md`
|
||||
|
||||
### Vulnerability
|
||||
|
||||
@ -17,8 +17,8 @@ description: Run the full Molecule AI stack on your own infrastructure.
|
||||
The fastest way to get Molecule AI running locally:
|
||||
|
||||
```bash
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
git clone https://github.com/Molecule-AI/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
./scripts/dev-start.sh
|
||||
# Canvas: http://localhost:3000
|
||||
# Platform: http://localhost:8080
|
||||
|
||||
@ -1,284 +0,0 @@
|
||||
---
|
||||
title: "Provisioning Workspaces on AWS EC2 (production SaaS provisioner)"
|
||||
description: "How the molecule-controlplane EC2 provisioner turns POST /cp/orgs and POST /workspaces calls into running tenant + workspace EC2 instances — env vars, lifecycle, tier sizing, and the migration off Fly Machines."
|
||||
---
|
||||
|
||||
# Provisioning Workspaces on AWS EC2 (production SaaS provisioner)
|
||||
|
||||
As of April 2026, Molecule AI's SaaS control plane provisions both **tenants**
|
||||
(per-org platform VMs) and **workspaces** (per-agent inference VMs) on
|
||||
AWS EC2 instances. The provisioner lives at
|
||||
[`molecule-controlplane/internal/provisioner/ec2.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/internal/provisioner/ec2.go)
|
||||
and is auto-wired by [`cmd/server/main.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/cmd/server/main.go)
|
||||
whenever AWS credentials are present in the control-plane environment. The
|
||||
platform manages workspace lifecycle, auth, and routing; AWS manages the
|
||||
underlying EC2, security groups, and network plumbing.
|
||||
|
||||
This tutorial documents what env vars the provisioner reads, what AWS
|
||||
actions it performs on a `POST /workspaces`, and how to operate it. It is
|
||||
the replacement for the deprecated [Fly Machines provisioner](./fly-machines-provisioner.md)
|
||||
tutorial.
|
||||
|
||||
> **Audience:** operators running a self-hosted Molecule AI control plane
|
||||
> against their own AWS account, and contributors debugging the
|
||||
> production CP. End-users of `*.moleculesai.app` do not need any of
|
||||
> this — provisioning happens transparently when you create an org or
|
||||
> workspace in the canvas.
|
||||
|
||||
## When EC2 is the active provisioner
|
||||
|
||||
`cmd/server/main.go` switches on whether `AWS_ACCESS_KEY_ID` is set in the
|
||||
process environment. If yes, it constructs an `*provisioner.EC2` from the
|
||||
config below and registers it as the tenant provisioner. There is **no**
|
||||
`CONTAINER_BACKEND=ec2` switch — the dispatcher key is presence of AWS
|
||||
credentials. (The legacy `flyio` backend still has dead code in the tree
|
||||
but is no longer wired in `main.go`.)
|
||||
|
||||
A typical Railway-hosted control plane log line on boot:
|
||||
|
||||
```
|
||||
provisioner: EC2 (region=us-east-2, ami=ami-0ea3c35c5c3284d82)
|
||||
tenant provisioner: EC2 ✓
|
||||
```
|
||||
|
||||
If `AWS_ACCESS_KEY_ID` is unset, you'll see `provisioner: disabled`
|
||||
instead — useful for local dev where you want orgs CRUD to work without
|
||||
AWS access.
|
||||
|
||||
## Environment variables
|
||||
|
||||
The full list of env vars `cmd/server/main.go` passes into
|
||||
`provisioner.EC2Config`. Anything not listed here is unused by the
|
||||
provisioner.
|
||||
|
||||
### Required for any EC2 provisioning
|
||||
|
||||
| Var | Default | Purpose |
|
||||
|-----|---------|---------|
|
||||
| `AWS_ACCESS_KEY_ID` | — | Toggle: presence enables EC2 wiring at all |
|
||||
| `AWS_SECRET_ACCESS_KEY` | — | Standard AWS SDK credential pair |
|
||||
| `AWS_REGION` | `us-east-1` | Region for tenant + workspace launches |
|
||||
| `EC2_AMI` | `ami-0ea3c35c5c3284d82` (Ubuntu 22.04 us-east-2) | Default AMI when no `thin_ami_pins` row matches |
|
||||
| `EC2_VPC_ID` | — | VPC for per-tenant SG creation; falls back to `EC2_SECURITY_GROUP` if unset |
|
||||
| `EC2_SUBNET_ID` | — | Subnet for `RunInstances` |
|
||||
| `SECRETS_ENCRYPTION_KEY` | — | KMS-envelope DEK for tenant secret-at-rest; provisioner stays disabled until set |
|
||||
|
||||
### Required for production (#44 secure bootstrap)
|
||||
|
||||
| Var | Purpose |
|
||||
|-----|---------|
|
||||
| `EC2_TENANT_IAM_PROFILE` | Instance profile attached to every tenant EC2 so it can fetch its bootstrap bundle from Secrets Manager at boot. Without this set, `Provision` returns the error `"Secrets Manager + IAM instance profile are required (#113 — plaintext user-data path removed)"`. |
|
||||
| `PROVISION_SHARED_SECRET` | Shared HMAC-secret stored alongside the tenant bootstrap bundle so workspace-server can authenticate inbound `/cp/...` callbacks |
|
||||
| `CP_ADMIN_API_TOKEN` | Token the tenant uses to call admin endpoints back on the control plane |
|
||||
| `CP_BASE_URL` | URL the tenant boot script uses to reach the control plane (typically `https://api.moleculesai.app`) |
|
||||
|
||||
### Required for the canvas Terminal tab
|
||||
|
||||
| Var | Purpose |
|
||||
|-----|---------|
|
||||
| `EIC_ENDPOINT_SG_ID` | Security-group ID of the region's [EC2 Instance Connect endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-endpoint.html). The provisioner adds a `tcp/22` ingress rule to every per-tenant + per-workspace SG sourced from this SG, so the canvas Terminal can EIC-tunnel into the box for diagnostic ssh. Empty leaves the canvas Terminal broken with `failed to open EIC tunnel`. Discover with `aws ec2 describe-instance-connect-endpoints --region <region>`. |
|
||||
|
||||
### Cloudflare integration (per-tenant subdomains)
|
||||
|
||||
| Var | Purpose |
|
||||
|-----|---------|
|
||||
| `CLOUDFLARE_API_TOKEN` | Enables CF DNS client; provisioner creates the per-tenant `<slug>.<APP_DOMAIN>` CNAME |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | Enables CF Tunnel client (preferred over Worker + wildcard DNS) |
|
||||
| `CLOUDFLARE_ZONE_ID` | DNS zone the tenant CNAMEs are written under |
|
||||
| `APP_DOMAIN` | Default `moleculesai.app`; tenant FQDN becomes `<slug>.<APP_DOMAIN>` |
|
||||
|
||||
### Optional — runtime images, tier image, backups, canary, multi-env
|
||||
|
||||
| Var | Purpose |
|
||||
|-----|---------|
|
||||
| `MOLECULE_ENV` | `dev` / `staging` / `prod`; stamped on every EC2 tag and scopes the orphan-report's AWS lister so envs don't false-positive each other |
|
||||
| `EC2_INSTANCE_TYPE` | Default `t3.small` for tenant VMs (workspaces use the per-tier table below) |
|
||||
| `EC2_SECURITY_GROUP` | Fallback shared SG when `EC2_VPC_ID` is unset; production should leave this empty |
|
||||
| `EC2_KEY_NAME` | Optional EC2 KeyPair name for emergency console SSH |
|
||||
| `TENANT_IMAGE` | OCI ref for the tenant platform image (e.g. `ghcr.io/molecule-ai/platform-tenant:staging-<sha>`) |
|
||||
| `CANARY_TENANT_IMAGE` | Override `TENANT_IMAGE` for orgs flagged `is_canary=true` |
|
||||
| `CANARY_ROLE_ARN`, `CANARY_REGION`, `CANARY_VPC_ID`, `CANARY_SUBNET_ID` | Second-AWS-account target for canary tenant launches; all four required together |
|
||||
| `TENANT_BACKUP_S3_PREFIX` | Empty disables nightly `pg_dump`; set `s3://bucket/path` to enable |
|
||||
| `TENANT_BACKUP_REPORT_URL` | Defaults to `${CP_BASE_URL}/cp/tenants/backup-report` |
|
||||
| `GHCR_PULL_TOKEN` | GHCR pull token written into the tenant bootstrap bundle (private images only) |
|
||||
|
||||
For the always-current set, grep
|
||||
[`cmd/server/main.go` lines 86–158](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/cmd/server/main.go#L86-L158)
|
||||
for `os.Getenv` calls inside the `provisioner.NewEC2` block.
|
||||
|
||||
## What happens on `POST /cp/orgs` (tenant provision)
|
||||
|
||||
`OrgsHandler.Create` calls into `(*EC2).Provision(ctx, cfg)`. Roughly:
|
||||
|
||||
1. **Cloudflare cleanup** — `cleanupStaleSlugArtifacts` scrubs any
|
||||
leftover tunnel/DNS rows from a previously-purged org with the same
|
||||
slug, so the slug is reusable.
|
||||
2. **Cloudflare Tunnel + DNS** — `CreateTunnel` → `CreateTunnelDNS`
|
||||
(writes `<slug>.<APP_DOMAIN>` → `<tunnel-id>.cfargotunnel.com`) →
|
||||
`ConfigureTunnelIngress` (registers the hostname on the tunnel's
|
||||
remote config so CF's edge knows to forward). DNS or ingress
|
||||
failures roll back the tunnel and abort the provision — fail-fast
|
||||
behavior added 2026-04-26 after a six-hour outage in which
|
||||
unreachable tenants timed out at 600–900s instead of surfacing the
|
||||
real CF API problem.
|
||||
3. **Bootstrap secrets to AWS Secrets Manager** — the provisioner
|
||||
generates a per-tenant DB password + admin token, packages them with
|
||||
the GHCR pull token, tunnel token, encryption key, and shared
|
||||
secret, and `PutSecret`s them at `awsapi.TenantSecretName(orgID)`.
|
||||
The tenant fetches this bundle at boot via its instance profile —
|
||||
no plaintext secrets in user-data (see #113).
|
||||
4. **Per-tenant SG creation** — `createPerTenantSG` calls
|
||||
`CreateSecurityGroup` with the resolved VPC, the per-org name, and
|
||||
the ingress rules from `tenantIngressRules(vpcCidr, EICEndpointSGID)`.
|
||||
The SG ingress always includes the canvas-terminal EIC `tcp/22`
|
||||
rule sourced from the EIC endpoint's own SG (UserIdGroupPairs, not
|
||||
`0.0.0.0/0` — only AWS EIC's endpoint can use it).
|
||||
5. **`RunInstances`** — `awsClient.RunInstance(ctx, awsapi.LaunchConfig{...})`
|
||||
launches with `InstanceType = TenantInstanceType` (default
|
||||
`t3.small`), the resolved AMI, IAM instance profile, base64-encoded
|
||||
user-data, and tags `OrgID` / `OrgSlug` / `Role=tenant` / `TunnelID`
|
||||
/ `SGID`. Volume size is 30 GB.
|
||||
6. **Audit row** — every CF, SG, Secrets Manager, and EC2 lifecycle
|
||||
event is recorded in the `tenant_resources` audit table (#2343)
|
||||
so the orphan reconciler can diff claims vs live state.
|
||||
|
||||
`Provision` returns a `*Result` whose fields (`FlyMachineID`, `FlyRegion`,
|
||||
`AdminToken`) are still named after Fly. The EC2 provisioner fake-fills
|
||||
them with EC2 equivalents (`InstanceID`, `AWSRegion`); a column-rename
|
||||
migration is on the controlplane backlog.
|
||||
|
||||
## What happens on `POST /workspaces` (workspace provision)
|
||||
|
||||
`workspace-server`'s `POST /workspaces` reaches the control plane via
|
||||
`/cp/workspaces/provision`, which calls
|
||||
`(*EC2).ProvisionWorkspace(ctx, workspaceID, runtime, orgID, tier, platformURL, env)`:
|
||||
|
||||
1. **Resolve tier resources** — `workspaceTierResources(tier)` returns
|
||||
`(instanceType, volumeSize)` per the table below. Hermes runtime
|
||||
floors `volumeSize` to 50 GB regardless of tier (uv + Python venv +
|
||||
Node.js gateway pegs disk at 18–25 GB during install).
|
||||
2. **Resolve AMI** — `resolveWorkspaceAMI` looks up `thin_ami_pins`
|
||||
for the runtime + region. A pin row means the AMI is pre-baked
|
||||
(per `packer/scripts/install-base.sh`) and user-data can skip
|
||||
apt-update + the Python/Node installs (60–140 s saved per
|
||||
provision, RFC #388). Fallback to the static `WorkspaceAMI`.
|
||||
3. **Resolve runtime image** — `resolveRuntimeImage` looks up
|
||||
`runtime_image_pins` and emits the containerized user-data path
|
||||
(docker pull + run) when present. Independent of the AMI gate
|
||||
above; the new path also installs Docker if missing on a thin/stock
|
||||
AMI.
|
||||
4. **Per-workspace SG creation** — same `createPerTenantSG` call with
|
||||
`namePrefix="workspace"`. Workspace SGs get
|
||||
`workspaceIngressRules(EICEndpointSGID)` — currently the EIC
|
||||
`tcp/22` rule and nothing else (workspaces sit behind the
|
||||
Cloudflare Tunnel for HTTP).
|
||||
5. **`RunInstance`** — launches with `wsShort = workspaceID[:12]`
|
||||
prefixed name, the resolved instance type + volume + AMI +
|
||||
user-data, and tags `WorkspaceID` / `Runtime` / `Role=workspace`
|
||||
/ `SGID` / `OrgID`. The `OrgID` tag is what lets
|
||||
`DeprovisionInstance` cascade-terminate workspace EC2s when their
|
||||
tenant is deleted (incident 2026-04-23: ~27 orphaned workspace
|
||||
EC2s pinned staging at the 64 vCPU limit before the tag was
|
||||
added).
|
||||
6. **Audit row** — `tenant_resources` `KindEC2Instance` `StateCreated`
|
||||
with role / runtime / tier / workspace metadata.
|
||||
|
||||
The boot script registers the workspace agent with the platform via
|
||||
`/workspaces/:id/register`, the platform issues an A2A auth token, and
|
||||
the agent comes up ready for `message/send` calls.
|
||||
|
||||
## Tier-based resource sizing
|
||||
|
||||
`workspaceTierResources` is the single source of truth. As of writing,
|
||||
all tiers below T4 are clamped up to T4 (the SaaS floor) and tiers
|
||||
above T4 are also clamped down to T4 (today's max):
|
||||
|
||||
| Tier | Instance type | Volume | Effective use |
|
||||
|------|---------------|--------|---------------|
|
||||
| T1 / T2 | clamped to T4 | clamped to T4 | not in production |
|
||||
| T3 | `t3.medium` | 40 GB | reserved (clamped today) |
|
||||
| T4 | `t3.large` | 80 GB | all production workspaces |
|
||||
|
||||
If you set a tier outside `[3, 4]` the clamp lifts it to T4 — a cheap
|
||||
mis-provision rather than a fall-through to the unset `t3.small`
|
||||
default. The clamp was added in PR #434 follow-up after `tier=5`
|
||||
silently yielded `t3.small`.
|
||||
|
||||
Hermes overrides volume to 50 GB minimum regardless of tier.
|
||||
|
||||
## Lifecycle — stop, restart, redeploy, teardown
|
||||
|
||||
| Operation | Mechanism |
|
||||
|-----------|-----------|
|
||||
| **Stop / start a tenant** | `POST /cp/admin/tenants/:slug/{stop,start}` → `(*EC2).Stop` / `Start` via the EC2 API (no termination) |
|
||||
| **Redeploy a tenant** (in-place new image) | `POST /cp/admin/tenants/:slug/redeploy` → SSM Run Command pulls the latest `TENANT_IMAGE` and recreates the platform container; never reboots EC2 |
|
||||
| **Refresh workspace template images** | `POST /cp/admin/tenants/:slug/workspaces/redeploy` (single-tenant) or `POST /cp/admin/tenants/workspaces/redeploy-fleet` (canary-batched fleet); HTTP-only, no SSM |
|
||||
| **Delete a workspace** | platform `DELETE /workspaces/:id` → CP `DeprovisionInstance(workspaceInstanceID, ...)` terminates the EC2 + cleans DNS + SG |
|
||||
| **Delete a tenant (Art. 17 cascade)** | `DELETE /cp/orgs/:slug` → cascade-terminates all workspace EC2s tagged with this `OrgID`, then terminates the tenant EC2, then deletes the SG, Secrets Manager bundle, CF tunnel + CNAME |
|
||||
| **Orphan recovery** | `tenant_resources` audit table + 30-min reconciler that diffs claims vs live AWS state and exposes orphan counts via `/cp/admin/stats` |
|
||||
|
||||
`DeprovisionInstance` polls termination under its own deadline so a
|
||||
stuck shutdown surfaces as a deprovision failure (and the caller's
|
||||
retry replays the cascade) instead of becoming a silent leak (#263).
|
||||
|
||||
## Why EC2 (vs Fly Machines)
|
||||
|
||||
The control plane has migrated infrastructure twice in April 2026 — both
|
||||
documented in the
|
||||
[molecule-controlplane README "Migration history"](https://git.moleculesai.app/molecule-ai/molecule-controlplane#migration-history):
|
||||
|
||||
- **Apr 2026 — CP host:** Fly (`molecule-cp.fly.dev`) → Railway
|
||||
(`api.moleculesai.app`).
|
||||
- **Apr 2026 — tenant + workspace compute:** Fly Machines → AWS EC2
|
||||
with SSM Run Command for redeploy.
|
||||
|
||||
The drivers were production needs Fly couldn't easily meet:
|
||||
|
||||
- **Region + data-residency control.** EU customers required
|
||||
EU-resident tenant data; AWS regional pinning per tenant is
|
||||
straightforward, Fly's region routing is per-app and harder to
|
||||
guarantee per-tenant.
|
||||
- **AWS-native auth chain for the canvas Terminal.** EC2 Instance
|
||||
Connect lets the platform open SSH tunnels to a tenant box via
|
||||
short-lived (60 s) IAM-signed public keys — no shared SSH keys,
|
||||
no inbound `0.0.0.0/0` rules. The same path powers the Files API
|
||||
EIC writes (see [SaaS file writes via EC2 Instance Connect](./saas-file-writes-eic.md)).
|
||||
- **Secrets Manager + IAM instance profiles** for tenant bootstrap
|
||||
secrets (#113 removed the plaintext user-data path).
|
||||
- **Cloudflare Tunnels** instead of public IPs — no inbound exposure
|
||||
on tenant EC2s; CF edge is the only ingress.
|
||||
- **`tenant_resources` audit table + reconciler** for cascade-cleanup
|
||||
guarantees that Fly's flat machine list couldn't enforce.
|
||||
|
||||
Old `internal/flyapi/` and `internal/provisioner/fly.go` files remain
|
||||
in the controlplane tree as legacy code awaiting cleanup; they are not
|
||||
wired in `cmd/server/main.go`.
|
||||
|
||||
## Operating notes
|
||||
|
||||
- **Schema names still say "fly".** The `org_instances` columns
|
||||
`fly_app` / `fly_machine_id` / `fly_region` are fake-filled with EC2
|
||||
equivalents; a rename migration is on the controlplane backlog
|
||||
(`PLAN.md`).
|
||||
- **`SECRETS_ENCRYPTION_KEY` gates the whole provisioner.** The crypto
|
||||
envelope is required even when only AWS creds are present; without
|
||||
it, `tenant provisioner: DISABLED` is logged and `POST /cp/orgs`
|
||||
accepts the row but never spins a tenant.
|
||||
- **Per-tenant SG creation needs `EC2_VPC_ID`.** If you only set
|
||||
`EC2_SECURITY_GROUP` (the legacy shared-SG fallback), every tenant
|
||||
shares one SG — caught the bug in PR #434 review. Production must
|
||||
set `EC2_VPC_ID`.
|
||||
- **`EIC_ENDPOINT_SG_ID` is silently load-bearing.** If unset, the
|
||||
canvas Terminal hangs with `failed to open EIC tunnel` and the
|
||||
Files API EIC write path returns 500 — the EC2 boots fine, the
|
||||
symptom only shows when an operator opens the canvas Terminal tab.
|
||||
|
||||
## References
|
||||
|
||||
- [`molecule-controlplane/internal/provisioner/ec2.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/internal/provisioner/ec2.go) — provisioner source
|
||||
- [`molecule-controlplane/cmd/server/main.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/cmd/server/main.go) — env-var wiring
|
||||
- [`molecule-controlplane` README "Migration history"](https://git.moleculesai.app/molecule-ai/molecule-controlplane#migration-history) — canonical record
|
||||
- [AWS EC2 Instance Connect endpoints](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-endpoint.html)
|
||||
- [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)
|
||||
- [SaaS file writes via EC2 Instance Connect](./saas-file-writes-eic.md) — EIC is also the Files API write channel
|
||||
- [Fly Machines provisioner (DEPRECATED)](./fly-machines-provisioner.md) — previous backend, retained for migration history
|
||||
@ -88,8 +88,8 @@ Fly Machines start in milliseconds and run in 35+ regions. Provisioning agent wo
|
||||
|
||||
## Related
|
||||
|
||||
- PR #501: [feat(platform): Fly Machines provisioner](https://git.moleculesai.app/molecule-ai/molecule-core/pull/501)
|
||||
- PR #481: [feat(ci): deploy to Fly after image push](https://git.moleculesai.app/molecule-ai/molecule-core/pull/481)
|
||||
- PR #501: [feat(platform): Fly Machines provisioner](https://github.com/Molecule-AI/molecule-core/pull/501)
|
||||
- PR #481: [feat(ci): deploy to Fly after image push](https://github.com/Molecule-AI/molecule-core/pull/481)
|
||||
- [Fly Machines API docs](https://fly.io/docs/machines/api/)
|
||||
- [Platform API reference](../api-reference.md)
|
||||
- Issue [#525](https://git.moleculesai.app/molecule-ai/molecule-core/issues/525)
|
||||
- Issue [#525](https://github.com/Molecule-AI/molecule-core/issues/525)
|
||||
|
||||
@ -64,6 +64,6 @@ The real power surfaces when you mix runtimes on the same Molecule AI tenant. Yo
|
||||
|
||||
## Related
|
||||
|
||||
- PR #379: [feat(adapters): add gemini-cli runtime adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/379)
|
||||
- PR #379: [feat(adapters): add gemini-cli runtime adapter](https://github.com/Molecule-AI/molecule-core/pull/379)
|
||||
- [Multi-provider Hermes docs](../architecture/hermes.md)
|
||||
- [Workspace runtimes reference](../reference/runtimes.md)
|
||||
|
||||
@ -71,7 +71,7 @@ ADK workspaces participate in the same A2A network as Claude Code, Gemini CLI, H
|
||||
|
||||
## Related
|
||||
|
||||
- PR #550: [feat(adapters): add google-adk runtime adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/550)
|
||||
- PR #550: [feat(adapters): add google-adk runtime adapter](https://github.com/Molecule-AI/molecule-core/pull/550)
|
||||
- [Google ADK (adk-python)](https://github.com/google/adk-python)
|
||||
- [Gemini CLI runtime tutorial](./gemini-cli-runtime.md)
|
||||
- [Platform API reference](../api-reference.md)
|
||||
|
||||
@ -179,9 +179,9 @@ What is on the roadmap for Phase 2d (not yet shipped):
|
||||
|
||||
## Related
|
||||
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://github.com/Molecule-AI/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://github.com/Molecule-AI/molecule-core/pull/267)
|
||||
- [Hermes adapter design](../adapters/hermes-adapter-design.md)
|
||||
- [Platform API reference](../api-reference.md)
|
||||
- Issue [#513](https://git.moleculesai.app/molecule-ai/molecule-core/issues/513)
|
||||
- Issue [#513](https://github.com/Molecule-AI/molecule-core/issues/513)
|
||||
|
||||
@ -93,6 +93,6 @@ Molecule AI canvas without code changes.
|
||||
|
||||
## Related
|
||||
|
||||
- PR #480: [feat(channels): Lark / Feishu channel adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/480)
|
||||
- PR #480: [feat(channels): Lark / Feishu channel adapter](https://github.com/Molecule-AI/molecule-core/pull/480)
|
||||
- [Social channels architecture](../agent-runtime/social-channels.md)
|
||||
- [Channel adapter reference](../api-reference.md#channels)
|
||||
@ -246,4 +246,4 @@ For the API reference, see [`docs/api-reference`](/docs/api-reference) — the `
|
||||
|
||||
*SaaS federation is available for all Molecule AI platform operators. Contact the Molecule AI team to enable federation on your control plane.*
|
||||
|
||||
(`molecule-core` [#1700](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1700))
|
||||
(`molecule-core` [#1700](https://github.com/Molecule-AI/molecule-core/pull/1700))
|
||||
@ -145,7 +145,7 @@ Key push + tunnel + write took longer than 30 s. Common causes: slow AWS EIC in
|
||||
|
||||
## Source PR
|
||||
|
||||
PR [#1702](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1702) — `feat(files-api): SSH-backed write for SaaS workspaces (fixes 500 docker not available)`
|
||||
PR [#1702](https://github.com/Molecule-AI/molecule-core/pull/1702) — `feat(files-api): SSH-backed write for SaaS workspaces (fixes 500 docker not available)`
|
||||
|
||||
Key files in `molecule-core`:
|
||||
- `workspace-server/internal/handlers/template_files_eic.go` — EIC write logic
|
||||
|
||||
@ -237,4 +237,4 @@ Once your agent is connected to MCP, it stops being a chatbot with a scrollable
|
||||
|
||||
---
|
||||
|
||||
*Have questions or want to share what you're building with MCP? File an issue with the `enhancement` label on the [molecule-core issue tracker](https://git.moleculesai.app/molecule-ai/molecule-core/issues).*
|
||||
*Have questions or want to share what you're building with MCP? Open a discussion on [GitHub Discussions](https://github.com/Molecule-AI/molecule-core/discussions) or file an issue with the `enhancement` label.*
|
||||
Loading…
Reference in New Issue
Block a user