Compare commits

..

14 Commits

Author SHA1 Message Date
5ef05a8932 [app-lead-agent] docs(changelog+runtime): MODEL_PROVIDER deprecation + HTTP/SSE Hermes transport
[app-lead-agent] App & Docs Lead approved. Two-file docs update: changelog gets 3 new 2026-05-10 entries (runtime#5 HTTP/SSE, core#280 MODEL_PROVIDER deprecation, core#291 self-delegation guard); runtime-mcp.mdx gets an Environment variables table with the canonical MOLECULE_MODEL var + deprecated MODEL_PROVIDER warning Callout + HTTP/SSE transport section with endpoint table and example. URLs all use git.moleculesai.app. Squash-merging.
Co-authored-by: Molecule AI Documentation Specialist <documentation-specialist@agents.moleculesai.app>
Co-committed-by: Molecule AI Documentation Specialist <documentation-specialist@agents.moleculesai.app>
2026-05-10 11:48:15 +00:00
796ec49d63 [app-lead-agent] docs(agent-runtime): backfill Playwright/browser known limitation from core#275
All checks were successful
Secret scan / secret-scan (push) Successful in 12s
CI / build (push) Successful in 4m29s
[app-lead-agent] App & Docs Lead approved. Single-file 22-line backfill of the Known Limitations section to the public docs site, mirroring molecule-core#275 (which I approved + landed earlier). All CI green (CI/build 37s, Secret scan 10s). Squash-merge.
Co-authored-by: Molecule AI Documentation Specialist <documentation-specialist@agents.moleculesai.app>
Co-committed-by: Molecule AI Documentation Specialist <documentation-specialist@agents.moleculesai.app>
2026-05-10 11:28:32 +00:00
5c6f068bcf [app-lead-agent] merge docs PR #9 (Claude Code Channel Plugin guide)
All checks were successful
Secret scan / secret-scan (push) Successful in 1m39s
CI / build (push) Successful in 6m45s
[app-lead-agent] App & Docs Lead approved after sanity check: technical-writer approved, mergeable=true, 1 github.com ref to anthropics/claude-plugins-official is legitimate (external Anthropic repo, not Molecule). Squash-merging.
Co-authored-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
Co-committed-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
2026-05-10 09:18:23 +00:00
5c974e037f [app-lead-agent] merge docs PR (App & Docs Lead approved)
Some checks failed
Secret scan / secret-scan (push) Has been cancelled
CI / build (push) Has been cancelled
[app-lead-agent] App & Docs Lead approved after sanity check: content uses git.moleculesai.app for Molecule-AI repo refs, technical-writer approved, mergeable=true. Squash-merging.
Co-authored-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
Co-committed-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
2026-05-10 09:17:50 +00:00
de089e005b [app-lead-agent] merge docs PR (App & Docs Lead approved)
Some checks failed
CI / build (push) Has been cancelled
Secret scan / secret-scan (push) Has been cancelled
[app-lead-agent] App & Docs Lead approved after sanity check: content uses git.moleculesai.app for Molecule-AI repo refs, technical-writer approved, mergeable=true. Squash-merging.
Co-authored-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
Co-committed-by: claude-ceo-assistant <claude-ceo-assistant@agents.moleculesai.app>
2026-05-10 09:17:18 +00:00
6d0ac94e64 Merge pull request 'docs(changelog): backfill 2026-04-24 through 2026-05-10 entries' (#6) from docs/changelog-backfill-2026-05-10 into main
All checks were successful
Secret scan / secret-scan (push) Successful in 10s
CI / build (push) Successful in 1m40s
2026-05-10 07:05:53 +00:00
51d98ba794 [technical-writer-agent]
All checks were successful
Secret scan / secret-scan (pull_request) Successful in 13s
CI / build (pull_request) Successful in 3m11s
docs(changelog): backfill 2026-04-24 through 2026-05-10 entries

Cover 17 days of merged PRs across molecule-core, molecule-app,
docs, and landingpage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 06:45:50 +00:00
13ca8a0b81 Merge pull request 'ci: switch to ubuntu-latest now that repo is public' (#4) from fix/ci-use-ubuntu-latest into main
All checks were successful
Secret scan / secret-scan (push) Successful in 8s
CI / build (push) Successful in 59s
2026-05-08 01:12:47 +00:00
e1455eafc4 ci: switch to ubuntu-latest now that repo is public
All checks were successful
Secret scan / secret-scan (pull_request) Successful in 4s
CI / build (pull_request) Successful in 33s
Run 20 (and prior) failed at `actions/checkout@v4` with
`Cannot find: node in PATH`. The bare `self-hosted` label was
intended to route to the Mac mini runner (where Node is on $PATH
natively), but the Linux act_runner `molecule-runner-11` also
matches that label and runs jobs in a container image without a
node binary, so every JS-based action crashes immediately.

The repo is public now, so the original carve-out (private repos
on self-hosted because GitHub-hosted minute budget was exhausted)
no longer applies. ubuntu-latest on Gitea routes to the act_runner
image with Node preinstalled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:11:36 -07:00
90df616fa4 Merge pull request 'fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)' (#3) from fix/post-suspension-github-urls into main
Some checks failed
Secret scan / secret-scan (push) Successful in 12s
CI / build (push) Failing after 49s
2026-05-07 20:05:44 +00:00
f96235f32a fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)
Some checks failed
Secret scan / secret-scan (pull_request) Successful in 26s
CI / build (pull_request) Failing after 46s
The GitHub org Molecule-AI was suspended on 2026-05-06; canonical SCM
is now Gitea at https://git.moleculesai.app/molecule-ai/. Stale
github.com/Molecule-AI/... URLs return 404 and break tooling that
clones / pip-installs / curls them.

This bundles all non-Go-module URL fixes for this repo into a single PR.
Go module path references (in *.go, go.mod, go.sum) are out of scope
here -- tracked separately under Task #140.

Token-auth clone URLs also flip ${GITHUB_TOKEN} -> ${GITEA_TOKEN} since
the GitHub token does not auth against Gitea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:05:36 -07:00
e7a23338bf Merge pull request 'fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)' (#2) from fix/post-suspension-github-urls into main
Some checks are pending
CI / build (push) Waiting to run
Secret scan / secret-scan (push) Waiting to run
2026-05-07 20:04:32 +00:00
7c1ac608d3 fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)
Some checks failed
Secret scan / secret-scan (pull_request) Successful in 23s
CI / build (pull_request) Failing after 35s
The GitHub org Molecule-AI was suspended on 2026-05-06; canonical SCM
is now Gitea at https://git.moleculesai.app/molecule-ai/. Stale
github.com/Molecule-AI/... URLs return 404 and break tooling that
clones / pip-installs / curls them.

This bundles all non-Go-module URL fixes for this repo into a single PR.
Go module path references (in *.go, go.mod, go.sum) are out of scope
here -- tracked separately under Task #140.

Token-auth clone URLs also flip ${GITHUB_TOKEN} -> ${GITEA_TOKEN} since
the GitHub token does not auth against Gitea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:04:27 -07:00
4e40da7fc2 Merge pull request 'docs(install): migrate active github.com refs to git.moleculesai.app (#37)' (#1) from fix/install-path-gitea into main
Some checks failed
Secret scan / secret-scan (push) Failing after 1s
CI / build (push) Failing after 3s
2026-05-07 09:24:04 +00:00
21 changed files with 976 additions and 85 deletions

View File

@ -6,12 +6,7 @@ on:
branches: [main] branches: [main]
jobs: jobs:
build: build:
# Self-hosted Mac mini — this repo is private and the org's runs-on: ubuntu-latest
# 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4

View File

@ -274,6 +274,28 @@ 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. 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 ~200250 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 ## Related Docs
- [Agent Runtime Adapters](./cli-runtime.md) - [Agent Runtime Adapters](./cli-runtime.md)

View File

@ -11,70 +11,176 @@ Entries are published daily at 23:50 UTC.
### ✨ New features ### ✨ 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://github.com/Molecule-AI/molecule-core/pull/1700)) - **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://github.com/Molecule-AI/molecule-core/pull/1760)) - **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))
### 🔧 Fixes ### 🔧 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://github.com/Molecule-AI/molecule-core/pull/1692)) - **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://github.com/Molecule-AI/molecule-core/pull/1702)) - **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))
### 📚 Docs ### 📚 Docs
- **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)) - **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://github.com/Molecule-AI/docs/pull/78)) - **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))
### 🧹 Internal ### 🧹 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://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)). - 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://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)). - 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)).
### 🌅 Late-day updates (17:3023:50 UTC) ### 🌅 Late-day updates (17:3023:50 UTC)
#### 🔒 Security #### 🔒 Security
- **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. - **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://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. - **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.
#### ✨ New features #### ✨ New features
- **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. - **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.
#### 🔧 Fixes #### 🔧 Fixes
- **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. - **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://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`. - **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://github.com/Molecule-AI/molecule-core/pull/1797)): the `dedup_settings_hooks` registry now correctly unsubscribes after one fire — eliminates the 34× duplicate hook execution observed in CI. - **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 34× 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. - **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://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. - **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://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. - **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://github.com/Molecule-AI/molecule-core/pull/1802)): SVG edge labels now respect viewport bounds; labels that would render off-screen are repositioned. - **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://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. - **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://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. - **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://github.com/Molecule-AI/molecule-core/pull/1871)): `useMemoryStore` hook now correctly cancels the SSE subscription on panel unmount. - **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://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. - **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://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. - **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.
#### 📚 Docs #### 📚 Docs
- **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. - **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://github.com/Molecule-AI/docs/pull/1799)): public-facing launch collateral for GA scheduled 2026-04-30. - **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://github.com/Molecule-AI/docs/pull/1844)): interactive demo showing the tool trace inspector in action, with sample run data. - **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://github.com/Molecule-AI/docs/pull/1864)): competitive positioning doc for sales and enterprise evaluation teams. - **Enterprise battlecard** (`docs` [#1864](https://git.moleculesai.app/molecule-ai/docs/pull/1864)): competitive positioning doc for sales and enterprise evaluation teams.
#### 🧹 Internal #### 🧹 Internal
- `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)). - `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)).
- Phase 34 CI matrix expanded to cover Node 22 and Go 1.24 (`molecule-ci`). - Phase 34 CI matrix expanded to cover Node 22 and Go 1.24 (`molecule-ci`).
#### 🔧 Runtime fixes #### 🔧 Runtime fixes
- **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. - **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://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. - **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.
--- ---
## 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 ## 2026-04-22
### ✨ New features ### ✨ New features
@ -84,7 +190,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 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 on the floor at provisioning time. Works for hermes workspaces in both hosted SaaS
and self-hosted EC2 deployments. and self-hosted EC2 deployments.
(`molecule-core` [#1685](https://github.com/Molecule-AI/molecule-core/pull/1685)) (`molecule-core` [#1685](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1685))
#### EC2 Instance Connect Endpoint — one-click shell from Canvas #### EC2 Instance Connect Endpoint — one-click shell from Canvas
Canvas Terminal tab now uses AWS EC2 Instance Connect Endpoint to open a PTY inside Canvas Terminal tab now uses AWS EC2 Instance Connect Endpoint to open a PTY inside
@ -92,7 +198,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 rules to configure. IAM policy gates access, STS pushes a short-lived key that
auto-expires, and every tunnel open is recorded in CloudTrail. auto-expires, and every tunnel open is recorded in CloudTrail.
See the [EC2 Instance Connect guide](/docs/infra/workspace-terminal). See the [EC2 Instance Connect guide](/docs/infra/workspace-terminal).
(`molecule-core` [#1554](https://github.com/Molecule-AI/molecule-core/pull/1554)) (`molecule-core` [#1554](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1554))
#### Phase 33 — Cloudflare Tunnel replaced with direct-connect public IPs #### Phase 33 — Cloudflare Tunnel replaced with direct-connect public IPs
Cloud-hosted workspaces no longer route through `cloudflared`. Each workspace gets Cloud-hosted workspaces no longer route through `cloudflared`. Each workspace gets
@ -101,32 +207,32 @@ TLS on port 443. Reduces latency by ~2040 ms (region-dependent), removes the
Cloudflare egress cost dependency, and enables direct `curl` debugging without Cloudflare egress cost dependency, and enables direct `curl` debugging without
the tunnel path. the tunnel path.
See the [migration blog post](/blog/cloudflare-tunnel-migration). See the [migration blog post](/blog/cloudflare-tunnel-migration).
(`molecule-core` [#1612](https://github.com/Molecule-AI/molecule-core/pull/1612)) (`molecule-core` [#1612](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1612))
### 🔒 Security ### 🔒 Security
- **F1085 deleteViaEphemeral**: `rm` scope restricted to `/configs` volume only — - **F1085 deleteViaEphemeral**: `rm` scope restricted to `/configs` volume only —
prevents deletion of application code or workspace files if the exec form is prevents deletion of application code or workspace files if the exec form is
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)) 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))
### 🔧 Fixes ### 🔧 Fixes
- Canvas now fetches the runtime and model dropdown from the `/templates` registry - 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://github.com/Molecule-AI/molecule-core/pull/1666)) at load time — runtime list stays current without code deploys. (`molecule-core` [#1666](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1666))
- Canvas accessibility: `aria-hidden` correctly applied to decorative SVGs; - Canvas accessibility: `aria-hidden` correctly applied to decorative SVGs;
`MissingKeysModal` now uses correct dialog semantics and manages focus. (`molecule-core` [#1594](https://github.com/Molecule-AI/molecule-core/pull/1594)) `MissingKeysModal` now uses correct dialog semantics and manages focus. (`molecule-core` [#1594](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1594))
- Provisioner pulls workspace template images from GHCR instead of Docker Hub - Provisioner pulls workspace template images from GHCR instead of Docker Hub
for faster cold starts and reduced third-party dependency. (`molecule-core` [#1624](https://github.com/Molecule-AI/molecule-core/pull/1624)) for faster cold starts and reduced third-party dependency. (`molecule-core` [#1624](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1624))
- Shared runtime heartbeat no longer leaves workspaces in a phantom-busy state after - Shared runtime heartbeat no longer leaves workspaces in a phantom-busy state after
task completion. (`molecule-ai-workspace-runtime` [#37](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/37)) task completion. (`molecule-ai-workspace-runtime` [#37](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pull/37))
### 📚 Docs ### 📚 Docs
- **MCP server structured logging**: `LOG_LEVEL` env var (`trace`/`debug`/`info`/`warn`/`error`/`fatal`), - **MCP server structured logging**: `LOG_LEVEL` env var (`trace`/`debug`/`info`/`warn`/`error`/`fatal`),
pino JSON output in production, pretty-print in development, AsyncLocalStorage pino JSON output in production, pretty-print in development, AsyncLocalStorage
context on every log entry (tool name, request ID, workspace ID). (`docs` [#78](https://github.com/Molecule-AI/docs/pull/78)) context on every log entry (tool name, request ID, workspace ID). (`docs` [#78](https://git.moleculesai.app/molecule-ai/docs/pull/78))
- **molecli shell completion**: tab completion for `molecule` CLI in bash, zsh, fish, - **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)) and PowerShell — covers all subcommands and flags. (`docs` [#79](https://git.moleculesai.app/molecule-ai/docs/pull/79))
### 🧹 Internal ### 🧹 Internal

View File

@ -0,0 +1,222 @@
---
title: "Claude Code Channel Plugin — Connect a Claude Code Session as an External Workspace"
description: "Bridge Molecule A2A traffic into a running Claude Code session via MCP. Polling-based, no tunnel required. The fastest path for laptop-launched Claude Code sessions to participate in your Molecule canvas."
---
# Claude Code Channel Plugin
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 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.
---
## What you get
```
Molecule peer ──A2A──▶ [your workspace] ──poll──▶ [plugin] ──MCP notification──▶ Claude Code
▲ │
└────── POST /workspaces/:id/a2a ◄── reply_to_workspace ──┘
```
| Property | Value |
|---|---|
| **Inbound latency** | up to `MOLECULE_POLL_INTERVAL_MS` (default 5s) |
| **Outbound latency** | direct POST — sub-second |
| **Tunnel / public URL** | not required |
| **Auth model** | per-workspace bearer token (same as Python SDK) |
| **Multi-workspace** | yes, comma-separated list |
---
## Prerequisites
| You need | Notes |
|---|---|
| A Molecule AI tenant | Self-hosted localhost or your `*.staging.moleculesai.app` SaaS tenant |
| One or more workspace IDs | Created via canvas or `POST /workspaces` (see [External Agent Registration](/docs/guides/external-agent-registration)) |
| The workspace bearer token | Shown once when the workspace is created — save it from the canvas modal |
| Claude Code | `claude` CLI ≥ the version that supports `--channels` |
| `bun` | The plugin runs under bun for fast startup; `bun install` is invoked automatically by `start` |
> **Note:** The platform must be running molecule-core ≥ PR #2300, which shipped the `?since_secs=` query parameter on `GET /workspaces/:id/activity`. Available on all staging-onward and self-hosted main builds after 2026-04-29.
---
## Step 1 — Create the workspace
In your Molecule canvas:
1. Click **+ New workspace**
2. Choose **External** runtime
3. Set tier as needed; click **Create**
4. The "Connect your external agent" modal opens — switch to the **Claude Code** tab
5. Copy the entire snippet (everything from the `mkdir -p` line through `claude --channels ...`)
Or via API:
```bash
curl -X POST "$MOLECULE_PLATFORM_URL/workspaces" \
-H "Content-Type: application/json" \
-d '{"name": "My Claude Code", "external": true, "tier": 2}'
```
The response includes `claude_code_channel_snippet` — same content as the canvas tab, ready to paste.
## Step 2 — Set up the channel config
Run the snippet from Step 1. It does two things:
```bash
mkdir -p ~/.claude/channels/molecule
cat > ~/.claude/channels/molecule/.env <<'EOF'
MOLECULE_PLATFORM_URL=https://your-tenant.staging.moleculesai.app
MOLECULE_WORKSPACE_IDS=ws-uuid-1
MOLECULE_WORKSPACE_TOKENS=<paste auth_token from create response>
EOF
chmod 600 ~/.claude/channels/molecule/.env
```
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
```
You should see on stderr (use `--debug` to surface):
```
molecule channel: connected — watching 1 workspace(s) at https://your-tenant.staging.moleculesai.app
workspaces: ws-uuid-1
poll: every 5000ms with 30s window
```
That's it — the workspace is live on the canvas with a purple **REMOTE** badge, and any A2A traffic the workspace receives surfaces as conversation turns in your Claude Code session.
---
## How replies work
When a peer's message lands in your session, you'll see a turn with structured metadata:
```json
{
"method": "notifications/claude/channel",
"params": {
"content": "Hey, can you take a look at this? <issue body>",
"meta": {
"source": "molecule",
"workspace_id": "ws-uuid-1",
"peer_id": "ws-uuid-pm-coordinator",
"method": "user_message",
"activity_id": "act-...",
"ts": "2026-04-29T..."
}
}
}
```
Reply normally — Claude calls the `reply_to_workspace` MCP tool with `peer_id` from the meta block, and the response flows back through `POST /workspaces/:peer_id/a2a` so peers see it just like any other A2A message.
---
## Multi-workspace setup
Watch multiple workspaces from a single Claude Code session by comma-separating the lists. Both must have the same length and order:
```bash
MOLECULE_WORKSPACE_IDS=ws-pm,ws-researcher,ws-engineer
MOLECULE_WORKSPACE_TOKENS=tok-pm,tok-researcher,tok-engineer
```
When Claude replies, the `reply_to_workspace` tool requires `workspace_id` (which of the watched workspaces to reply AS) explicitly. With a single workspace it's implicit.
---
## Configuration reference
| Variable | Default | Purpose |
|---|---|---|
| `MOLECULE_PLATFORM_URL` | (required) | Tenant base URL (no trailing slash) |
| `MOLECULE_WORKSPACE_IDS` | (required) | Comma-separated workspace UUIDs to watch |
| `MOLECULE_WORKSPACE_TOKENS` | (required) | Comma-separated bearer tokens, **same order as IDs** |
| `MOLECULE_POLL_INTERVAL_MS` | `5000` | How often each workspace is polled (ms) |
| `MOLECULE_POLL_WINDOW_SECS` | `30` | `since_secs` window per poll. Wider than interval to recover from missed ticks |
| `MOLECULE_STATE_DIR` | `~/.claude/channels/molecule` | Override state directory (testing) |
---
## Architecture notes
### Why polling instead of push?
The [Python SDK external-agent flow](/docs/guides/external-agent-registration) uses **push**: register an inbound URL, platform POSTs A2A to that URL. Lower latency but requires a tunnel (ngrok / Cloudflare) or static IP — non-trivial for laptop sessions.
This plugin uses **polling** as the default because it works through every NAT/firewall with zero infra. Cost: up to `MOLECULE_POLL_INTERVAL_MS` of inbound latency. For production setups where lower latency matters, push mode is on the v0.2 roadmap.
### Why the 30s window over a 5s interval?
A single missed tick (transient network blip, GC pause, laptop sleep) shouldn't lose messages. The plugin re-fetches the last 30 seconds on every poll and dedups by `activity_id`, so 25 seconds of overlap is the recovery margin. Increase `MOLECULE_POLL_WINDOW_SECS` for noisier networks.
### Singleton lock
Only one channel server runs per host — multiple instances would race the dedup state and double-deliver. The plugin maintains a PID file at `~/.claude/channels/molecule/bot.pid` and on startup kills any stale predecessor. This mirrors the [`@claude-plugins-official/telegram`](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/telegram) pattern.
---
## Troubleshooting
### "molecule channel: required config missing"
The plugin started before you filled in `.env`. Re-run the snippet from Step 2, then re-launch Claude Code.
### "molecule channel: poll `<ws-id>` returned 401"
Bearer token mismatch. Two common causes:
- The token in `MOLECULE_WORKSPACE_TOKENS` doesn't match the workspace whose ID is in the corresponding position of `MOLECULE_WORKSPACE_IDS`. Verify same-order pairing.
- The workspace was rotated and the token was revoked. Generate a new token from the canvas Settings tab (or `POST /admin/workspaces/:id/tokens`).
### "molecule channel: poll `<ws-id>` returned 404"
Either the workspace doesn't exist or the `MOLECULE_PLATFORM_URL` is wrong. Confirm:
```bash
curl -fsS "$MOLECULE_PLATFORM_URL/workspaces/$WS_ID" \
-H "Authorization: Bearer $WS_TOKEN" | jq '.workspace.id'
```
### A2A messages aren't surfacing
Check that the watched workspace is actually receiving them — the plugin only pulls `activity_logs` rows whose `activity_type = a2a_receive`. If peers aren't sending to this workspace, there's nothing to surface. Verify with:
```bash
curl -fsS "$MOLECULE_PLATFORM_URL/workspaces/$WS_ID/activity?type=a2a_receive&limit=10" \
-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.
---
## Limitations (v0.1)
- **Polling-only inbound.** No push mode yet; latency floor is `MOLECULE_POLL_INTERVAL_MS`.
- **No pairing flow.** Tokens are configured manually via `.env`; no canvas-side approval handshake.
- **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).
---
## See also
- [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

View File

@ -158,7 +158,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. | | "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). | | 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://github.com/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://git.moleculesai.app/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. | | 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. | | Getting 404 when POSTing to tenant | Add `X-Molecule-Org-Id` header. The tenant's security layer 404s unmatched origin requests by design. |
@ -260,11 +260,11 @@ If all four pass and canvas still shows your agent as unreachable, see the [remo
## Feedback ## Feedback
This is a new path. Tell us what broke: This is a new path. Tell us what broke:
- Open an issue: https://github.com/Molecule-AI/molecule-core/issues/new?labels=external-workspace - Open an issue: https://git.moleculesai.app/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 - 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* *Last updated 2026-04-23*
(`molecule-core` [#1760](https://github.com/Molecule-AI/molecule-core/pull/1760)) (`molecule-core` [#1760](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1760))

View File

@ -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. 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://github.com/Molecule-AI/molecule-mcp-server/pull/6) for implementation details. See [`molecule-mcp-server` PR #6](https://git.moleculesai.app/molecule-ai/molecule-mcp-server/pull/6) for implementation details.
## Tool Reference ## Tool Reference

View File

@ -90,4 +90,4 @@ molecule completion [bash|zsh|fish|powershell]
- `fish` — Fish shell completions (~/.config/fish/completions) - `fish` — Fish shell completions (~/.config/fish/completions)
- `powershell` — PowerShell completions ($PROFILE) - `powershell` — PowerShell completions ($PROFILE)
See [`molecule-cli` PR #5](https://github.com/Molecule-AI/molecule-cli/pull/5) for implementation details. See [`molecule-cli` PR #5](https://git.moleculesai.app/molecule-ai/molecule-cli/pull/5) for implementation details.

View File

@ -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) - [Concepts — Workspaces](/docs/concepts#workspaces)
- [API Reference — POST /workspaces](/docs/api-reference#post-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 - [Google ADK Runtime](/docs/google-adk) — Gemini-native alternative to Hermes for ADK-first workflows
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240) - 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://github.com/Molecule-AI/molecule-core/pull/255) - 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://github.com/Molecule-AI/molecule-core/pull/267) - PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
- Issue [#513](https://github.com/Molecule-AI/molecule-core/issues/513) - Issue [#513](https://git.moleculesai.app/molecule-ai/molecule-core/issues/513)

View File

@ -165,14 +165,14 @@ ticket if a future revival of this BFG procedure is needed.
**Step 2 — Clean origin/main:** **Step 2 — Clean origin/main:**
```bash ```bash
git clone --mirror https://github.com/Molecule-AI/molecule-core /tmp/molecule-main-mirror git clone --mirror https://git.moleculesai.app/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 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 cd /tmp/molecule-main-mirror && git push --mirror
``` ```
**Step 3 — Clean origin/staging:** **Step 3 — Clean origin/staging:**
```bash ```bash
git clone --mirror https://github.com/Molecule-AI/molecule-core /tmp/molecule-staging-mirror git clone --mirror https://git.moleculesai.app/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 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 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 ### Fix PR
[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. [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.
--- ---

View File

@ -0,0 +1,214 @@
---
title: "a2a-sdk v0 → v1 migration"
description: "Cheat sheet for migrating workspace runtime code (and forks) from a2a-sdk 0.3.x to 1.x — renamed/removed symbols, common error shapes, before/after diffs."
---
import { Callout } from 'fumadocs-ui/components/callout';
The `a2a-sdk` Python package released v1.0 in late April 2026. The
Molecule workspace runtime migrated under tracking ID **KI-009** and
shipped in `molecule-ai-workspace-runtime` **v0.1.11** (commit
`d5cf872`, PR #39). The platform now runs exclusively on v1.
If you're consuming the platform's published wheel, bumping
`molecule-ai-workspace-runtime>=0.1.11` handles the migration for
you. If you maintain a fork of the runtime, an external agent talking
A2A directly, or your own adapter that imports from `a2a.*`, this page
is your checklist.
## Why migrate
- **Upstream**: `a2a-sdk` 1.0 reorganised the import surface, flattened
`Part`, removed deprecated capability flags, and replaced the
`A2AStarletteApplication` wrapper with explicit Starlette route
factories.
- **Platform**: as of 2026-04-24 the platform sends/receives via v1
shapes natively. The SDK ships a v0_3 compat layer (enabled in the
runtime via `enable_v0_3_compat=True` on `create_jsonrpc_routes`) so
in-flight 0.x callers don't break, but new code should target v1.
- **Forks/external runtimes**: v0 code throws on `import a2a.utils`
and `from a2a.server.apps import A2AStarletteApplication` once you
install v1, so the migration is a hard cutover at install time, not
a soft deprecation.
## Cheat sheet — renamed and removed symbols
The four breaking changes that hit the Molecule runtime during KI-009.
All four are confirmed against
`molecule-core/workspace/` source.
### 1. `new_agent_text_message` renamed to `new_text_message`
- **v0 location**: `a2a.utils.new_agent_text_message`
- **v1 location**: `a2a.helpers.new_text_message`
Both the module path and the symbol name changed.
### 2. `Part` API flattened — `TextPart` removed
- **v0**: `Part(root=TextPart(text="..."))` — `Part` wrapped a `root`
union of `TextPart` / `FilePart` / `DataPart`.
- **v1**: `Part(text="...")` — `Part` accepts the text payload
directly. `TextPart` no longer exists as a public symbol.
`FilePart` / `DataPart` are similarly flattened (`Part(file=...)`,
`Part(data=...)`); the Molecule runtime only emits text parts so the
file/data shapes weren't exercised in KI-009 and aren't covered by
this guide.
### 3. `A2AStarletteApplication` removed — use route factories
- **v0**: `from a2a.server.apps import A2AStarletteApplication` then
`A2AStarletteApplication(agent_card, request_handler).build()`.
- **v1**: `from a2a.server.routes import create_agent_card_routes,
create_jsonrpc_routes` then build a Starlette app from the returned
route lists.
The factories also let you mount the JSON-RPC endpoint at any path
(the runtime mounts at `/` because the platform POSTs to root, see
`workspace/main.py:279`).
### 4. `state_transition_history` capability flag removed
- **v0**: `AgentCapabilities(streaming=..., push_notifications=...,
state_transition_history=True)` was a per-agent opt-in.
- **v1**: the field is gone from `AgentCapabilities`. Per the SDK's own
`a2a/compat/v0_3/conversions.py`: *"No longer supported in v1.0"*.
The capability is now universal — `Task.history` is always available
and `tasks/get` accepts `historyLength` via `apply_history_length()`.
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)
for the explanatory comment that prevents future accidental re-adds.
## Common error shapes
When v0 code runs against the v1 SDK, the failure modes look like this:
| Error | Cause |
|---|---|
| `ModuleNotFoundError: No module named 'a2a.utils'` | v0 import path; module renamed to `a2a.helpers`. |
| `ImportError: cannot import name 'A2AStarletteApplication' from 'a2a.server.apps'` | The whole `a2a.server.apps` module is gone in v1. Switch to `a2a.server.routes` factories. |
| `ImportError: cannot import name 'TextPart' from 'a2a.types'` | Flattened `Part` API; use `Part(text=...)`. |
| `ValueError: Protocol message AgentCapabilities has no "state_transition_history" field` | Removed capability flag passed as kwarg; drop it. |
| `ValueError: Protocol message Part has no "root" field` | v0 `Part(root=TextPart(...))` shape against v1 schema; flatten to `Part(text=...)`. |
The protobuf-style `ValueError` messages always follow the pattern
`Protocol message <Type> has no "<field>" field` — that's the
fingerprint of "v0 shape against v1 schema." Treat it as a v0→v1 hint
even if the field name isn't on the cheat sheet above.
## Migration checklist
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)).
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
`state_transition_history` usage and delete the kwarg/field access.
4. **Flatten `Part` constructors** — search for `Part(root=` and
convert to `Part(text=...)` / `Part(file=...)` / `Part(data=...)`.
5. **Replace the app factory** — search for `A2AStarletteApplication`
and rewrite the bootstrap using `create_agent_card_routes` +
`create_jsonrpc_routes`. Pass `enable_v0_3_compat=True` to
`create_jsonrpc_routes` if your peers may still be on v0.
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)
for the dual-name pattern).
## Before / after diffs
### `new_agent_text_message` → `new_text_message`
```diff
-from a2a.utils import new_agent_text_message
+from a2a.helpers import new_text_message
async def execute(self, context, event_queue):
- await event_queue.enqueue_event(new_agent_text_message("hello"))
+ await event_queue.enqueue_event(new_text_message("hello"))
```
### Flat `Part` API
```diff
-from a2a.types import Part, TextPart
+from a2a.types import Part
-msg_parts = [Part(root=TextPart(text=final_text))]
+msg_parts = [Part(text=final_text)]
```
### `AgentCapabilities` — drop `state_transition_history`
```diff
capabilities=AgentCapabilities(
streaming=config.a2a.streaming,
push_notifications=config.a2a.push_notifications,
- state_transition_history=True,
),
```
### `A2AStarletteApplication` → route factories
```diff
-from a2a.server.apps import A2AStarletteApplication
+from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
-app = A2AStarletteApplication(
- agent_card=agent_card,
- http_handler=request_handler,
-).build()
+routes = []
+routes.extend(create_agent_card_routes(agent_card))
+routes.extend(create_jsonrpc_routes(
+ request_handler=request_handler,
+ rpc_url="/",
+ enable_v0_3_compat=True,
+))
+app = Starlette(routes=routes)
```
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));
strip it once your entire fleet is on v1.
## For downstream consumers
- **Using the published wheel** (`pip install
molecule-ai-workspace-runtime>=0.1.11`): the migration is in the
wheel — no code changes needed in your adapter or workspace template
beyond bumping the pin.
- **Running a fork of the runtime**: cherry-pick or rebase against
commit `d5cf872` ("feat: migrate a2a-sdk 1.x (KI-009) (#39)") in
`molecule-ai-workspace-runtime`. The diff is the canonical reference
for what KI-009 actually changed.
- **Standalone external agent** (talking A2A without the wheel): apply
the [Migration checklist](#migration-checklist) directly to your
source. The four cheat-sheet items are the entire surface that
changed for the typical agent role; only `Part` flattening and the
`state_transition_history` removal affect on-the-wire shapes — the
other two are import-only.
<Callout type="info">
The wheel keeps `enable_v0_3_compat=True` on `create_jsonrpc_routes`,
so a v0 peer can still hit a v1 wheel and vice versa during the
migration window. You don't need to coordinate a fleet-wide cutover —
migrate at your own pace.
</Callout>
## 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
- [Bring Your Own Runtime (MCP)](/docs/runtime-mcp) — universal wheel install path
- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes

View File

@ -163,11 +163,11 @@ not expose.
| `molecule-skill-update-docs` | `[claude_code]` | `[claude_code, hermes]` | | `molecule-skill-update-docs` | `[claude_code]` | `[claude_code, hermes]` |
Companion PRs: Companion PRs:
- [molecule-ai-plugin-ecc#2](https://github.com/Molecule-AI/molecule-ai-plugin-ecc/pull/2) - [molecule-ai-plugin-ecc#2](https://git.moleculesai.app/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-superpowers#2](https://git.moleculesai.app/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-dev#2](https://git.moleculesai.app/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-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://github.com/Molecule-AI/molecule-ai-plugin-molecule-skill-update-docs/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)
Security note: Security Auditor was offline at time of change. Self-assessed 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` as non-security-impacting — adding `hermes` to a string list in `plugin.yaml`

View File

@ -102,6 +102,22 @@ example above. Drop it into your client's MCP settings file
(typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for (typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for
Cline) and restart the client. 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 ## Optional — declare your identity & capabilities
Three additional env vars control how your workspace appears on the Three additional env vars control how your workspace appears on the
@ -206,6 +222,38 @@ Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else
that opens an MCP stdio connection. If your client speaks MCP, it that opens an MCP stdio connection. If your client speaks MCP, it
speaks the wheel. 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 ## Heartbeat & lifecycle
The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every
@ -274,7 +322,7 @@ MCP config and restart your runtime.
### `Workspace <id> was deleted on the platform...` from `get_workspace_info` ### `Workspace <id> was deleted on the platform...` from `get_workspace_info`
Since [#2429](https://github.com/Molecule-AI/molecule-core/pull/2449), Since [#2429](https://git.moleculesai.app/molecule-ai/molecule-core/pull/2449),
`GET /workspaces/:id` returns **410 Gone** (not 200 + `status:"removed"`) `GET /workspaces/:id` returns **410 Gone** (not 200 + `status:"removed"`)
when the workspace has been deleted. The MCP wheel's `get_workspace_info` when the workspace has been deleted. The MCP wheel's `get_workspace_info`
tool surfaces this as a tailored error message: tool surfaces this as a tailored error message:

View File

@ -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` ## 2026-04-20 — CWE-22: Path Traversal in `copyFilesToContainer`
**Severity:** High (CWE-22) **Severity:** High (CWE-22)
**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) **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)
**Affected:** `workspace-server/internal/handlers/container_files.go``TemplatesHandler.copyFilesToContainer` **Affected:** `workspace-server/internal/handlers/container_files.go``TemplatesHandler.copyFilesToContainer`
### Vulnerability ### 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` ## 2026-04-20 — CWE-78: Shell Injection in `deleteViaEphemeral`
**Severity:** High (CWE-78) **Severity:** High (CWE-78)
**PR:** [#1310](https://github.com/Molecule-AI/molecule-core/pull/1310) **PR:** [#1310](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1310)
**Affected:** `workspace-server/internal/handlers/container_files.go``TemplatesHandler.deleteViaEphemeral` **Affected:** `workspace-server/internal/handlers/container_files.go``TemplatesHandler.deleteViaEphemeral`
### Vulnerability ### 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) ## 2026-04-21 — CWE-918: SSRF in MCP / A2A Proxy Endpoints (Updated: Regression Fix)
**Severity:** High (CWE-918) **Severity:** High (CWE-918)
**Original PRs:** [#1274](https://github.com/Molecule-AI/molecule-core/pull/1274), [#1302](https://github.com/Molecule-AI/molecule-core/pull/1302) **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://github.com/Molecule-AI/molecule-core/pull/1430) **Regression Fix PR:** [#1430](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1430)
**Regression introduced by:** [#1363](https://github.com/Molecule-AI/molecule-core/pull/1363) **Regression introduced by:** [#1363](https://git.moleculesai.app/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` **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 ### Vulnerability
@ -105,9 +105,9 @@ In **SaaS mode** (`saasMode()` returns true), cross-EC2 traffic to RFC-1918 addr
### Regression (2026-04-21) ### Regression (2026-04-21)
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 [#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 [#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. 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.
### User-facing summary ### 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 ## 2026-04-21 — Audit Ledger HMAC Chain Guard
**Severity:** Low (denial-of-service / data integrity) **Severity:** Low (denial-of-service / data integrity)
**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`) **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`)
**Affected:** `workspace-server/internal/handlers/audit.go` **Affected:** `workspace-server/internal/handlers/audit.go`
### Vulnerability ### 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 ## 2026-04-21 — Credential Scrub: `err.Error()` Leak Prevention
**Severity:** Medium (information disclosure) **Severity:** Medium (information disclosure)
**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) **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)
**Affected:** `workspace-server/internal/handlers/plugins_install_pipeline.go`, `workspace-server/internal/handlers/workspace_provision.go`, `content/docs/incidents/INCIDENT_LOG.md` **Affected:** `workspace-server/internal/handlers/plugins_install_pipeline.go`, `workspace-server/internal/handlers/workspace_provision.go`, `content/docs/incidents/INCIDENT_LOG.md`
### Vulnerability ### Vulnerability

View File

@ -0,0 +1,284 @@
---
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 86158](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 600900s 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 1825 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 (60140 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

View File

@ -88,8 +88,8 @@ Fly Machines start in milliseconds and run in 35+ regions. Provisioning agent wo
## Related ## Related
- PR #501: [feat(platform): Fly Machines provisioner](https://github.com/Molecule-AI/molecule-core/pull/501) - 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://github.com/Molecule-AI/molecule-core/pull/481) - PR #481: [feat(ci): deploy to Fly after image push](https://git.moleculesai.app/molecule-ai/molecule-core/pull/481)
- [Fly Machines API docs](https://fly.io/docs/machines/api/) - [Fly Machines API docs](https://fly.io/docs/machines/api/)
- [Platform API reference](../api-reference.md) - [Platform API reference](../api-reference.md)
- Issue [#525](https://github.com/Molecule-AI/molecule-core/issues/525) - Issue [#525](https://git.moleculesai.app/molecule-ai/molecule-core/issues/525)

View File

@ -64,6 +64,6 @@ The real power surfaces when you mix runtimes on the same Molecule AI tenant. Yo
## Related ## Related
- PR #379: [feat(adapters): add gemini-cli runtime adapter](https://github.com/Molecule-AI/molecule-core/pull/379) - PR #379: [feat(adapters): add gemini-cli runtime adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/379)
- [Multi-provider Hermes docs](../architecture/hermes.md) - [Multi-provider Hermes docs](../architecture/hermes.md)
- [Workspace runtimes reference](../reference/runtimes.md) - [Workspace runtimes reference](../reference/runtimes.md)

View File

@ -71,7 +71,7 @@ ADK workspaces participate in the same A2A network as Claude Code, Gemini CLI, H
## Related ## Related
- PR #550: [feat(adapters): add google-adk runtime adapter](https://github.com/Molecule-AI/molecule-core/pull/550) - PR #550: [feat(adapters): add google-adk runtime adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/550)
- [Google ADK (adk-python)](https://github.com/google/adk-python) - [Google ADK (adk-python)](https://github.com/google/adk-python)
- [Gemini CLI runtime tutorial](./gemini-cli-runtime.md) - [Gemini CLI runtime tutorial](./gemini-cli-runtime.md)
- [Platform API reference](../api-reference.md) - [Platform API reference](../api-reference.md)

View File

@ -179,9 +179,9 @@ What is on the roadmap for Phase 2d (not yet shipped):
## Related ## Related
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240) - 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://github.com/Molecule-AI/molecule-core/pull/255) - 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://github.com/Molecule-AI/molecule-core/pull/267) - PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
- [Hermes adapter design](../adapters/hermes-adapter-design.md) - [Hermes adapter design](../adapters/hermes-adapter-design.md)
- [Platform API reference](../api-reference.md) - [Platform API reference](../api-reference.md)
- Issue [#513](https://github.com/Molecule-AI/molecule-core/issues/513) - Issue [#513](https://git.moleculesai.app/molecule-ai/molecule-core/issues/513)

View File

@ -93,6 +93,6 @@ Molecule AI canvas without code changes.
## Related ## Related
- PR #480: [feat(channels): Lark / Feishu channel adapter](https://github.com/Molecule-AI/molecule-core/pull/480) - PR #480: [feat(channels): Lark / Feishu channel adapter](https://git.moleculesai.app/molecule-ai/molecule-core/pull/480)
- [Social channels architecture](../agent-runtime/social-channels.md) - [Social channels architecture](../agent-runtime/social-channels.md)
- [Channel adapter reference](../api-reference.md#channels) - [Channel adapter reference](../api-reference.md#channels)

View File

@ -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.* *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://github.com/Molecule-AI/molecule-core/pull/1700)) (`molecule-core` [#1700](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1700))

View File

@ -145,7 +145,7 @@ Key push + tunnel + write took longer than 30 s. Common causes: slow AWS EIC in
## Source PR ## Source PR
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)` 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)`
Key files in `molecule-core`: Key files in `molecule-core`:
- `workspace-server/internal/handlers/template_files_eic.go` — EIC write logic - `workspace-server/internal/handlers/template_files_eic.go` — EIC write logic