Compare commits
14 Commits
fix/instal
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ef05a8932 | |||
| 796ec49d63 | |||
| 5c6f068bcf | |||
| 5c974e037f | |||
| de089e005b | |||
| 6d0ac94e64 | |||
| 51d98ba794 | |||
| 13ca8a0b81 | |||
| e1455eafc4 | |||
| 90df616fa4 | |||
| f96235f32a | |||
| e7a23338bf | |||
| 7c1ac608d3 | |||
| 4e40da7fc2 |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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 ~200–250 MB (~40%) for every workspace, even though only frontend / QA workspaces ever launch a browser.
|
||||||
|
|
||||||
|
Practical consequences:
|
||||||
|
|
||||||
|
- `npx playwright test` (and any other Chromium-driven E2E tooling) **will fail at browser launch** when run from inside an in-container workspace agent.
|
||||||
|
- The error surface is missing-shared-object messages such as `error while loading shared libraries: libnss3.so` or `Host system is missing dependencies to run browsers`.
|
||||||
|
- Unit and integration tests (Vitest, Jest, etc.) that don't spawn a real browser are unaffected.
|
||||||
|
|
||||||
|
Recommended workflow:
|
||||||
|
|
||||||
|
1. **Run E2E in CI**, not in-container. The Gitea Actions self-hosted runner (and GitHub Actions runners used by mirror repos) has the full Playwright dep set installed and is the supported surface for E2E. Push a branch, let CI run the suite.
|
||||||
|
2. **Local debugging** of a single failing spec is best done on a developer laptop with `npx playwright install-deps` run once.
|
||||||
|
3. **In-container iteration** on test logic itself is fine — write specs, lint them, type-check them — just don't expect `playwright test` to actually launch a browser.
|
||||||
|
|
||||||
|
If a particular workspace role genuinely needs in-container E2E (a dedicated QA template, for instance), the right place to layer Playwright deps is in a **role-specific adapter template image** that does `FROM molecule-ai-workspace-runtime:<tag>` and adds `RUN npx playwright install-deps`. Open a request against `molecule-ai-workspace-runtime` if you need this template stamped.
|
||||||
|
|
||||||
|
Tracking issue: [molecule-ai/molecule-app#7](https://git.moleculesai.app/molecule-ai/molecule-app/issues/7).
|
||||||
|
|
||||||
## Related Docs
|
## Related Docs
|
||||||
|
|
||||||
- [Agent Runtime Adapters](./cli-runtime.md)
|
- [Agent Runtime Adapters](./cli-runtime.md)
|
||||||
|
|||||||
@ -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:30–23:50 UTC)
|
### 🌅 Late-day updates (17:30–23: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 3–4× 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 3–4× duplicate hook execution observed in CI.
|
||||||
- **Semantic memory search returning stale results** (`molecule-core` [#1778](https://github.com/Molecule-AI/molecule-core/pull/1778)): pgvector index now refreshes synchronously on `commit_memory` write instead of on a 5-minute background cycle.
|
- **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 ~20–40 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
|
||||||
|
|
||||||
|
|||||||
222
content/docs/guides/claude-code-channel-plugin.md
Normal file
222
content/docs/guides/claude-code-channel-plugin.md
Normal 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
|
||||||
@ -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))
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
214
content/docs/migration/a2a-sdk-v0-to-v1.mdx
Normal file
214
content/docs/migration/a2a-sdk-v0-to-v1.mdx
Normal 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
|
||||||
@ -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`
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
284
content/docs/tutorials/aws-ec2-provisioner.md
Normal file
284
content/docs/tutorials/aws-ec2-provisioner.md
Normal 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 86–158](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/cmd/server/main.go#L86-L158)
|
||||||
|
for `os.Getenv` calls inside the `provisioner.NewEC2` block.
|
||||||
|
|
||||||
|
## What happens on `POST /cp/orgs` (tenant provision)
|
||||||
|
|
||||||
|
`OrgsHandler.Create` calls into `(*EC2).Provision(ctx, cfg)`. Roughly:
|
||||||
|
|
||||||
|
1. **Cloudflare cleanup** — `cleanupStaleSlugArtifacts` scrubs any
|
||||||
|
leftover tunnel/DNS rows from a previously-purged org with the same
|
||||||
|
slug, so the slug is reusable.
|
||||||
|
2. **Cloudflare Tunnel + DNS** — `CreateTunnel` → `CreateTunnelDNS`
|
||||||
|
(writes `<slug>.<APP_DOMAIN>` → `<tunnel-id>.cfargotunnel.com`) →
|
||||||
|
`ConfigureTunnelIngress` (registers the hostname on the tunnel's
|
||||||
|
remote config so CF's edge knows to forward). DNS or ingress
|
||||||
|
failures roll back the tunnel and abort the provision — fail-fast
|
||||||
|
behavior added 2026-04-26 after a six-hour outage in which
|
||||||
|
unreachable tenants timed out at 600–900s instead of surfacing the
|
||||||
|
real CF API problem.
|
||||||
|
3. **Bootstrap secrets to AWS Secrets Manager** — the provisioner
|
||||||
|
generates a per-tenant DB password + admin token, packages them with
|
||||||
|
the GHCR pull token, tunnel token, encryption key, and shared
|
||||||
|
secret, and `PutSecret`s them at `awsapi.TenantSecretName(orgID)`.
|
||||||
|
The tenant fetches this bundle at boot via its instance profile —
|
||||||
|
no plaintext secrets in user-data (see #113).
|
||||||
|
4. **Per-tenant SG creation** — `createPerTenantSG` calls
|
||||||
|
`CreateSecurityGroup` with the resolved VPC, the per-org name, and
|
||||||
|
the ingress rules from `tenantIngressRules(vpcCidr, EICEndpointSGID)`.
|
||||||
|
The SG ingress always includes the canvas-terminal EIC `tcp/22`
|
||||||
|
rule sourced from the EIC endpoint's own SG (UserIdGroupPairs, not
|
||||||
|
`0.0.0.0/0` — only AWS EIC's endpoint can use it).
|
||||||
|
5. **`RunInstances`** — `awsClient.RunInstance(ctx, awsapi.LaunchConfig{...})`
|
||||||
|
launches with `InstanceType = TenantInstanceType` (default
|
||||||
|
`t3.small`), the resolved AMI, IAM instance profile, base64-encoded
|
||||||
|
user-data, and tags `OrgID` / `OrgSlug` / `Role=tenant` / `TunnelID`
|
||||||
|
/ `SGID`. Volume size is 30 GB.
|
||||||
|
6. **Audit row** — every CF, SG, Secrets Manager, and EC2 lifecycle
|
||||||
|
event is recorded in the `tenant_resources` audit table (#2343)
|
||||||
|
so the orphan reconciler can diff claims vs live state.
|
||||||
|
|
||||||
|
`Provision` returns a `*Result` whose fields (`FlyMachineID`, `FlyRegion`,
|
||||||
|
`AdminToken`) are still named after Fly. The EC2 provisioner fake-fills
|
||||||
|
them with EC2 equivalents (`InstanceID`, `AWSRegion`); a column-rename
|
||||||
|
migration is on the controlplane backlog.
|
||||||
|
|
||||||
|
## What happens on `POST /workspaces` (workspace provision)
|
||||||
|
|
||||||
|
`workspace-server`'s `POST /workspaces` reaches the control plane via
|
||||||
|
`/cp/workspaces/provision`, which calls
|
||||||
|
`(*EC2).ProvisionWorkspace(ctx, workspaceID, runtime, orgID, tier, platformURL, env)`:
|
||||||
|
|
||||||
|
1. **Resolve tier resources** — `workspaceTierResources(tier)` returns
|
||||||
|
`(instanceType, volumeSize)` per the table below. Hermes runtime
|
||||||
|
floors `volumeSize` to 50 GB regardless of tier (uv + Python venv +
|
||||||
|
Node.js gateway pegs disk at 18–25 GB during install).
|
||||||
|
2. **Resolve AMI** — `resolveWorkspaceAMI` looks up `thin_ami_pins`
|
||||||
|
for the runtime + region. A pin row means the AMI is pre-baked
|
||||||
|
(per `packer/scripts/install-base.sh`) and user-data can skip
|
||||||
|
apt-update + the Python/Node installs (60–140 s saved per
|
||||||
|
provision, RFC #388). Fallback to the static `WorkspaceAMI`.
|
||||||
|
3. **Resolve runtime image** — `resolveRuntimeImage` looks up
|
||||||
|
`runtime_image_pins` and emits the containerized user-data path
|
||||||
|
(docker pull + run) when present. Independent of the AMI gate
|
||||||
|
above; the new path also installs Docker if missing on a thin/stock
|
||||||
|
AMI.
|
||||||
|
4. **Per-workspace SG creation** — same `createPerTenantSG` call with
|
||||||
|
`namePrefix="workspace"`. Workspace SGs get
|
||||||
|
`workspaceIngressRules(EICEndpointSGID)` — currently the EIC
|
||||||
|
`tcp/22` rule and nothing else (workspaces sit behind the
|
||||||
|
Cloudflare Tunnel for HTTP).
|
||||||
|
5. **`RunInstance`** — launches with `wsShort = workspaceID[:12]`
|
||||||
|
prefixed name, the resolved instance type + volume + AMI +
|
||||||
|
user-data, and tags `WorkspaceID` / `Runtime` / `Role=workspace`
|
||||||
|
/ `SGID` / `OrgID`. The `OrgID` tag is what lets
|
||||||
|
`DeprovisionInstance` cascade-terminate workspace EC2s when their
|
||||||
|
tenant is deleted (incident 2026-04-23: ~27 orphaned workspace
|
||||||
|
EC2s pinned staging at the 64 vCPU limit before the tag was
|
||||||
|
added).
|
||||||
|
6. **Audit row** — `tenant_resources` `KindEC2Instance` `StateCreated`
|
||||||
|
with role / runtime / tier / workspace metadata.
|
||||||
|
|
||||||
|
The boot script registers the workspace agent with the platform via
|
||||||
|
`/workspaces/:id/register`, the platform issues an A2A auth token, and
|
||||||
|
the agent comes up ready for `message/send` calls.
|
||||||
|
|
||||||
|
## Tier-based resource sizing
|
||||||
|
|
||||||
|
`workspaceTierResources` is the single source of truth. As of writing,
|
||||||
|
all tiers below T4 are clamped up to T4 (the SaaS floor) and tiers
|
||||||
|
above T4 are also clamped down to T4 (today's max):
|
||||||
|
|
||||||
|
| Tier | Instance type | Volume | Effective use |
|
||||||
|
|------|---------------|--------|---------------|
|
||||||
|
| T1 / T2 | clamped to T4 | clamped to T4 | not in production |
|
||||||
|
| T3 | `t3.medium` | 40 GB | reserved (clamped today) |
|
||||||
|
| T4 | `t3.large` | 80 GB | all production workspaces |
|
||||||
|
|
||||||
|
If you set a tier outside `[3, 4]` the clamp lifts it to T4 — a cheap
|
||||||
|
mis-provision rather than a fall-through to the unset `t3.small`
|
||||||
|
default. The clamp was added in PR #434 follow-up after `tier=5`
|
||||||
|
silently yielded `t3.small`.
|
||||||
|
|
||||||
|
Hermes overrides volume to 50 GB minimum regardless of tier.
|
||||||
|
|
||||||
|
## Lifecycle — stop, restart, redeploy, teardown
|
||||||
|
|
||||||
|
| Operation | Mechanism |
|
||||||
|
|-----------|-----------|
|
||||||
|
| **Stop / start a tenant** | `POST /cp/admin/tenants/:slug/{stop,start}` → `(*EC2).Stop` / `Start` via the EC2 API (no termination) |
|
||||||
|
| **Redeploy a tenant** (in-place new image) | `POST /cp/admin/tenants/:slug/redeploy` → SSM Run Command pulls the latest `TENANT_IMAGE` and recreates the platform container; never reboots EC2 |
|
||||||
|
| **Refresh workspace template images** | `POST /cp/admin/tenants/:slug/workspaces/redeploy` (single-tenant) or `POST /cp/admin/tenants/workspaces/redeploy-fleet` (canary-batched fleet); HTTP-only, no SSM |
|
||||||
|
| **Delete a workspace** | platform `DELETE /workspaces/:id` → CP `DeprovisionInstance(workspaceInstanceID, ...)` terminates the EC2 + cleans DNS + SG |
|
||||||
|
| **Delete a tenant (Art. 17 cascade)** | `DELETE /cp/orgs/:slug` → cascade-terminates all workspace EC2s tagged with this `OrgID`, then terminates the tenant EC2, then deletes the SG, Secrets Manager bundle, CF tunnel + CNAME |
|
||||||
|
| **Orphan recovery** | `tenant_resources` audit table + 30-min reconciler that diffs claims vs live AWS state and exposes orphan counts via `/cp/admin/stats` |
|
||||||
|
|
||||||
|
`DeprovisionInstance` polls termination under its own deadline so a
|
||||||
|
stuck shutdown surfaces as a deprovision failure (and the caller's
|
||||||
|
retry replays the cascade) instead of becoming a silent leak (#263).
|
||||||
|
|
||||||
|
## Why EC2 (vs Fly Machines)
|
||||||
|
|
||||||
|
The control plane has migrated infrastructure twice in April 2026 — both
|
||||||
|
documented in the
|
||||||
|
[molecule-controlplane README "Migration history"](https://git.moleculesai.app/molecule-ai/molecule-controlplane#migration-history):
|
||||||
|
|
||||||
|
- **Apr 2026 — CP host:** Fly (`molecule-cp.fly.dev`) → Railway
|
||||||
|
(`api.moleculesai.app`).
|
||||||
|
- **Apr 2026 — tenant + workspace compute:** Fly Machines → AWS EC2
|
||||||
|
with SSM Run Command for redeploy.
|
||||||
|
|
||||||
|
The drivers were production needs Fly couldn't easily meet:
|
||||||
|
|
||||||
|
- **Region + data-residency control.** EU customers required
|
||||||
|
EU-resident tenant data; AWS regional pinning per tenant is
|
||||||
|
straightforward, Fly's region routing is per-app and harder to
|
||||||
|
guarantee per-tenant.
|
||||||
|
- **AWS-native auth chain for the canvas Terminal.** EC2 Instance
|
||||||
|
Connect lets the platform open SSH tunnels to a tenant box via
|
||||||
|
short-lived (60 s) IAM-signed public keys — no shared SSH keys,
|
||||||
|
no inbound `0.0.0.0/0` rules. The same path powers the Files API
|
||||||
|
EIC writes (see [SaaS file writes via EC2 Instance Connect](./saas-file-writes-eic.md)).
|
||||||
|
- **Secrets Manager + IAM instance profiles** for tenant bootstrap
|
||||||
|
secrets (#113 removed the plaintext user-data path).
|
||||||
|
- **Cloudflare Tunnels** instead of public IPs — no inbound exposure
|
||||||
|
on tenant EC2s; CF edge is the only ingress.
|
||||||
|
- **`tenant_resources` audit table + reconciler** for cascade-cleanup
|
||||||
|
guarantees that Fly's flat machine list couldn't enforce.
|
||||||
|
|
||||||
|
Old `internal/flyapi/` and `internal/provisioner/fly.go` files remain
|
||||||
|
in the controlplane tree as legacy code awaiting cleanup; they are not
|
||||||
|
wired in `cmd/server/main.go`.
|
||||||
|
|
||||||
|
## Operating notes
|
||||||
|
|
||||||
|
- **Schema names still say "fly".** The `org_instances` columns
|
||||||
|
`fly_app` / `fly_machine_id` / `fly_region` are fake-filled with EC2
|
||||||
|
equivalents; a rename migration is on the controlplane backlog
|
||||||
|
(`PLAN.md`).
|
||||||
|
- **`SECRETS_ENCRYPTION_KEY` gates the whole provisioner.** The crypto
|
||||||
|
envelope is required even when only AWS creds are present; without
|
||||||
|
it, `tenant provisioner: DISABLED` is logged and `POST /cp/orgs`
|
||||||
|
accepts the row but never spins a tenant.
|
||||||
|
- **Per-tenant SG creation needs `EC2_VPC_ID`.** If you only set
|
||||||
|
`EC2_SECURITY_GROUP` (the legacy shared-SG fallback), every tenant
|
||||||
|
shares one SG — caught the bug in PR #434 review. Production must
|
||||||
|
set `EC2_VPC_ID`.
|
||||||
|
- **`EIC_ENDPOINT_SG_ID` is silently load-bearing.** If unset, the
|
||||||
|
canvas Terminal hangs with `failed to open EIC tunnel` and the
|
||||||
|
Files API EIC write path returns 500 — the EC2 boots fine, the
|
||||||
|
symptom only shows when an operator opens the canvas Terminal tab.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [`molecule-controlplane/internal/provisioner/ec2.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/internal/provisioner/ec2.go) — provisioner source
|
||||||
|
- [`molecule-controlplane/cmd/server/main.go`](https://git.moleculesai.app/molecule-ai/molecule-controlplane/blob/main/cmd/server/main.go) — env-var wiring
|
||||||
|
- [`molecule-controlplane` README "Migration history"](https://git.moleculesai.app/molecule-ai/molecule-controlplane#migration-history) — canonical record
|
||||||
|
- [AWS EC2 Instance Connect endpoints](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-endpoint.html)
|
||||||
|
- [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)
|
||||||
|
- [SaaS file writes via EC2 Instance Connect](./saas-file-writes-eic.md) — EIC is also the Files API write channel
|
||||||
|
- [Fly Machines provisioner (DEPRECATED)](./fly-machines-provisioner.md) — previous backend, retained for migration history
|
||||||
@ -88,8 +88,8 @@ Fly Machines start in milliseconds and run in 35+ regions. Provisioning agent wo
|
|||||||
|
|
||||||
## Related
|
## 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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
@ -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))
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user