Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75dab2c6dc | |||
| aa46faeb78 | |||
| d6282cb127 | |||
| 86cf6c3a5c | |||
| dc08033b9f | |||
| 684ec817bd | |||
| 48e691c0e0 | |||
| 18bb733b36 | |||
| a0b3ab9876 | |||
| cfa2fc3d6e | |||
| e0910a1734 |
@@ -9,16 +9,18 @@ The `workspace/` directory is Molecule AI's unified runtime image. Every provisi
|
||||
|
||||
## Runtime Matrix In Current `main`
|
||||
|
||||
Current `main` ships six adapters:
|
||||
Current `main` ships eight adapters:
|
||||
|
||||
- `langgraph`
|
||||
- `deepagents`
|
||||
- `claude-code`
|
||||
- `langgraph`
|
||||
- `crewai`
|
||||
- `autogen`
|
||||
- `deepagents`
|
||||
- `hermes`
|
||||
- `gemini-cli`
|
||||
- `openclaw`
|
||||
|
||||
This is the merged runtime surface today. Branch-level experiments such as NemoClaw are separate and should be treated as roadmap/WIP, not merged support.
|
||||
This is the merged runtime surface today. The canonical allowlist lives in `workspace-server/internal/handlers/admin_workspace_images.go` (`AllRuntimes`). Anything not on this list — including BYO runtimes — registers via the [external workspace](../external-agents.mdx) path, not as a built-in adapter.
|
||||
|
||||
Adapter-specific behavior is documented in [Agent Runtime Adapters](./cli-runtime.md).
|
||||
|
||||
@@ -139,6 +141,28 @@ If you need correctness coverage, write a separate integration test that runs th
|
||||
|
||||
A `config.yaml` from the template repo's root is mounted at `/configs/config.yaml`.
|
||||
|
||||
## Runtime Distribution: PyPI Is Canonical, The Git Mirror May Lag
|
||||
|
||||
The runtime ships as **two surfaces**, and only one of them is wire-truth.
|
||||
|
||||
| Surface | Repo / location | Role |
|
||||
|---|---|---|
|
||||
| **PyPI wheel** | `pip install molecule-ai-workspace-runtime==X.Y.Z` | **Canonical artifact.** Workspace template images, the controlplane runtime smoke harness, and self-hosters all consume this. |
|
||||
| **Git mirror** | [`Molecule-AI/molecule-ai-workspace-runtime`](https://github.com/Molecule-AI/molecule-ai-workspace-runtime) | **Human-readable copy.** Exists for browsing + giving `mirror-guard` a concrete branch to enforce its "no direct PRs" policy against. |
|
||||
|
||||
Both are produced by the [`publish-runtime.yml`](https://github.com/Molecule-AI/molecule-monorepo/blob/main/.github/workflows/publish-runtime.yml) workflow on every push to `molecule-monorepo/workspace/`, but **the wheel publish and the mirror push are separate steps**. The mirror push can lag the wheel by hours, or be skipped entirely on transient failures while the wheel still ships.
|
||||
|
||||
If you're chasing "is module X in the published runtime yet?", trust the wheel listing, not the mirror's `git log`:
|
||||
|
||||
```bash
|
||||
pip download molecule-ai-workspace-runtime==X.Y.Z --no-deps
|
||||
unzip -l molecule_ai_workspace_runtime-X.Y.Z-*.whl | grep your_module
|
||||
```
|
||||
|
||||
To find out what version the controlplane is actually deploying, check the workspace template image's `requirements.txt` pin (it's a `>=`, so the resolved version is whatever PyPI hands back at image-build time — not whatever's in the mirror).
|
||||
|
||||
**Do not edit the git mirror directly.** `mirror-guard` rejects all PRs to `molecule-ai-workspace-runtime`. Edit `molecule-monorepo/workspace/` and let `publish-runtime.yml` regenerate both surfaces.
|
||||
|
||||
## Core Runtime Pieces
|
||||
|
||||
| File | Responsibility |
|
||||
|
||||
@@ -573,14 +573,16 @@ compliance:
|
||||
|
||||
| Adapter | Core Strength | Image Tag |
|
||||
|---------|--------------|-----------|
|
||||
| **LangGraph** | Graph-based state machine, tool use, streaming | `workspace-template:langgraph` |
|
||||
| **DeepAgents** | Deep planning, multi-step task decomposition | `workspace-template:deepagents` |
|
||||
| **Claude Code** | Native coding workflows, CLI continuity, OAuth auth | `workspace-template:claude-code` |
|
||||
| **LangGraph** | Graph-based state machine, tool use, streaming | `workspace-template:langgraph` |
|
||||
| **CrewAI** | Role-based crews, structured task orchestration | `workspace-template:crewai` |
|
||||
| **AutoGen** | Multi-agent conversations, explicit strategies | `workspace-template:autogen` |
|
||||
| **DeepAgents** | Deep planning, multi-step task decomposition | `workspace-template:deepagents` |
|
||||
| **Hermes** | Multi-provider dispatch (Anthropic/Gemini native + OpenAI-compatible shim) | `workspace-template:hermes` |
|
||||
| **Gemini CLI** | Google Gemini CLI workspace | `workspace-template:gemini-cli` |
|
||||
| **OpenClaw** | CLI-native runtime, own session model | `workspace-template:openclaw` |
|
||||
|
||||
**Branch-level WIP**: NemoClaw (NVIDIA T4 + Docker socket) on `feat/nemoclaw-t4-docker`.
|
||||
The canonical allowlist lives in `workspace-server/internal/handlers/admin_workspace_images.go` (`AllRuntimes`). Anything outside this list registers via the external-workspace path.
|
||||
|
||||
Each adapter implements `setup()` + `create_executor()`. The base adapter provides shared infrastructure: system prompt assembly, skill loading, tool registration, coordinator detection, plugin injection.
|
||||
|
||||
@@ -978,7 +980,6 @@ Tools call `resp.json()` without catching JSON decode errors. Should wrap in try
|
||||
|
||||
| Branch | Feature | Status |
|
||||
|--------|---------|--------|
|
||||
| `feat/nemoclaw-t4-docker` | NemoClaw adapter (NVIDIA T4 support) | WIP |
|
||||
| Backlog | Firecracker backend (faster cold starts) | Planned |
|
||||
| Backlog | E2B backend (cloud-hosted code sandbox) | Planned |
|
||||
| Backlog | pgvector semantic memory search | Planned |
|
||||
|
||||
@@ -20,7 +20,7 @@ Canvas (Next.js :3000) ←WebSocket→ Platform (Go :8080) ←HTTP→ Postgres +
|
||||
|
||||
- **Workspace Server** (`workspace-server/`): Go/Gin control plane — workspace CRUD, registry, discovery, WebSocket hub, liveness monitoring.
|
||||
- **Canvas** (`canvas/`): Next.js 15 + React Flow (@xyflow/react v12) + Zustand + Tailwind — visual workspace graph.
|
||||
- **Workspace Runtime** (`workspace/`): Shared runtime published as [`molecule-ai-workspace-runtime`](https://pypi.org/project/molecule-ai-workspace-runtime/) on PyPI. Supports LangGraph, Claude Code, OpenClaw, DeepAgents, CrewAI, AutoGen. Each adapter lives in its own standalone template repo (e.g. `molecule-ai-workspace-template-claude-code`). See `docs/workspace-runtime-package.md` for the full picture.
|
||||
- **Workspace Runtime** (`workspace/`): Shared runtime published as [`molecule-ai-workspace-runtime`](https://pypi.org/project/molecule-ai-workspace-runtime/) on PyPI. Supports Claude Code, LangGraph, CrewAI, AutoGen, DeepAgents, Hermes, Gemini CLI, and OpenClaw. Each adapter lives in its own standalone template repo (e.g. `molecule-ai-workspace-template-claude-code`). See `docs/workspace-runtime-package.md` for the full picture.
|
||||
- **molecli** (`workspace-server/cmd/cli/`): Go TUI dashboard (Bubbletea + Lipgloss) — real-time workspace monitoring, event log, health overview, delete/filter operations.
|
||||
|
||||
## Key Architectural Patterns
|
||||
|
||||
@@ -26,7 +26,7 @@ lands in the watch list with a colliding term, add a row here.
|
||||
| **team** | A named cluster of workspaces under a PM (org template `expand_team`). Used for role grouping in Canvas. | **CrewAI**: a "crew" is a sequence of agents that pass a task through a declared order. Our "team" is an org-chart abstraction, not an execution order. |
|
||||
| **skill** | A directory with `SKILL.md` that an agent invokes via the `Skill` tool. Skills are documentation + optional scripts that teach an agent a recipe. | **Anthropic Skills API**: nearly identical. **CrewAI tool**: closer to our plugin's MCP tool, not our skill. |
|
||||
| **channel** | An outbound/inbound social integration (Telegram, Slack, …) per-workspace, wired in `workspace_channels`. | Slack's "channel": the container for messages. We use "channel" for the adapter + credentials, not the conversation itself. |
|
||||
| **runtime** | The execution engine image tag for a workspace: one of `langgraph`, `claude-code`, `openclaw`, `crewai`, `autogen`, `deepagents`, `hermes`. | **LangGraph runtime**: the Python process running the graph. We use "runtime" for the Docker image + adapter pairing, not the inner process. |
|
||||
| **runtime** | The execution engine image tag for a workspace: one of `claude-code`, `langgraph`, `crewai`, `autogen`, `deepagents`, `hermes`, `gemini-cli`, `openclaw`. | **LangGraph runtime**: the Python process running the graph. We use "runtime" for the Docker image + adapter pairing, not the inner process. |
|
||||
|
||||
## GitHub Awesome Copilot disambiguation
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"channels",
|
||||
"schedules",
|
||||
"runtime-mcp",
|
||||
"runtime-mcp/dev-channels-flag",
|
||||
"external-agents",
|
||||
"tokens",
|
||||
"api-reference",
|
||||
@@ -31,6 +32,8 @@
|
||||
"google-adk",
|
||||
"hermes",
|
||||
"---Integrations---",
|
||||
"opencode"
|
||||
"opencode",
|
||||
"---Migration---",
|
||||
"migration/a2a-sdk-v0-to-v1"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
`/Users/hongming/Documents/GitHub/molecule-monorepo/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://github.com/Molecule-AI/molecule-monorepo/blob/main/workspace/main.py#L215)
|
||||
for the explanatory comment that prevents future accidental re-adds.
|
||||
|
||||
## Common error shapes
|
||||
|
||||
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://github.com/Molecule-AI/molecule-ai-workspace-runtime/blob/main/pyproject.toml)).
|
||||
2. **Fix imports** — sweep the four renamed/removed symbols above. A
|
||||
safe grep is `grep -rn "from a2a\\|import a2a"` across your tree.
|
||||
3. **Fix removed-field reads/writes** — search for
|
||||
`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://github.com/Molecule-AI/molecule-monorepo/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://github.com/Molecule-AI/molecule-monorepo/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://github.com/Molecule-AI/molecule-ai-workspace-runtime/releases/tag/v0.1.11) — first wheel containing KI-009
|
||||
- [PR #39 — feat: migrate a2a-sdk 1.x (KI-009)](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/39)
|
||||
- [PR #48 — feat(a2a): dual-compat for a2a-sdk 0.3.x and 1.x](https://github.com/Molecule-AI/molecule-ai-workspace-runtime/pull/48) — runtime-side compat shim that keeps v0 peers working against the v1 wheel
|
||||
- [Bring Your Own Runtime (MCP)](/docs/runtime-mcp) — universal wheel install path
|
||||
- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes
|
||||
@@ -11,8 +11,8 @@ Get a Molecule AI workspace running in under five minutes.
|
||||
## 1. Install Molecule AI
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Molecule-AI/molecule-core.git
|
||||
cd molecule-core
|
||||
git clone https://github.com/Molecule-AI/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
@@ -78,4 +78,4 @@ Or type `/ask what's our deployment status?` in your connected Discord channel.
|
||||
- [Review the REST API reference](/docs/guides/org-api-keys)
|
||||
- [Browse all guides](/docs/guides)
|
||||
|
||||
Explore the [GitHub repo](https://github.com/Molecule-AI/molecule-core) for self-hosting options, or visit [moleculesai.app](https://moleculesai.app) for the hosted platform.
|
||||
Explore the [GitHub repo](https://github.com/Molecule-AI/molecule-monorepo) for self-hosting options, or visit [moleculesai.app](https://moleculesai.app) for the hosted platform.
|
||||
|
||||
@@ -264,6 +264,11 @@ wheel ships the wire shape correctly, but a standard `claude` launch
|
||||
without the flag silently drops the notification — which is why the
|
||||
poll path has to be the floor.
|
||||
|
||||
See [Dev-channels flag — tagged-form requirement](/docs/runtime-mcp/dev-channels-flag)
|
||||
for the exact form the flag must take, the failure mode when it's
|
||||
wrong, and when operators need to set it manually vs. when the
|
||||
hosted SaaS / workspace template handles it for them.
|
||||
|
||||
Since Claude Code 2.1.x the flag takes a tagged allowlist, not a bare
|
||||
switch. Pass each MCP server you want to push from as `server:<name>`
|
||||
(matching the name you registered the server under in Claude Code's
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: "Dev-channels flag — tagged-form requirement"
|
||||
description: "Why Claude Code 2.1.x+ requires `--dangerously-load-development-channels server:molecule` (not the bare flag) to enable inline channel push from the molecule-mcp wheel."
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
The `molecule-mcp` wheel emits a JSON-RPC `notifications/claude/channel`
|
||||
notification on every inbound A2A message so Claude Code can render it
|
||||
as an inline `<channel>` synthetic user turn — zero polling, zero
|
||||
per-turn stall. During the channels research preview, Claude Code only
|
||||
processes that notification when the host is launched with the
|
||||
`--dangerously-load-development-channels` flag *and the flag carries a
|
||||
matching tagged allowlist entry*.
|
||||
|
||||
This page covers the form that flag must take, what breaks when it's
|
||||
wrong, and when an operator has to think about it.
|
||||
|
||||
<Callout type="warn">
|
||||
The bare flag (no value) is rejected by the post-2.1 CLI parser, and
|
||||
the failure mode propagates upstream as a `Control request timeout:
|
||||
initialize` from any SDK that spawns the CLI — every A2A turn wedges
|
||||
100% of the time. See [Failure mode](#failure-mode) below.
|
||||
</Callout>
|
||||
|
||||
## The flag
|
||||
|
||||
```
|
||||
--dangerously-load-development-channels <entries...>
|
||||
```
|
||||
|
||||
Available in Claude Code **2.1.x and later**. It opts the CLI into
|
||||
processing experimental `notifications/<channel>` JSON-RPC methods
|
||||
emitted by registered MCP servers and plugin channels. Without it, the
|
||||
CLI silently drops those notifications during the allowlist check, even
|
||||
though the wheel ships the wire shape correctly.
|
||||
|
||||
## Required form: tagged allowlist entries
|
||||
|
||||
Each entry must carry one of two prefixes:
|
||||
|
||||
| Form | Use for |
|
||||
|---|---|
|
||||
| `server:<MCP-server-name>` | Manually configured MCP servers — the name matches what you registered with `claude mcp add <name> ...` or the key under `mcpServers` in `~/.claude.json`. |
|
||||
| `plugin:<plugin-name>@<owner>/<repo>` | Plugin channels installed from a Claude Code plugin marketplace. |
|
||||
|
||||
Multiple entries are space-separated:
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule server:telegram
|
||||
```
|
||||
|
||||
Untagged values (`molecule` instead of `server:molecule`) are rejected
|
||||
with `--dangerously-load-development-channels entries must be tagged`.
|
||||
|
||||
## Failure mode
|
||||
|
||||
A bare flag (`--dangerously-load-development-channels` with no value)
|
||||
walks through three layers of damage before surfacing:
|
||||
|
||||
1. **CLI**: rejects the invocation with
|
||||
`error: option '--dangerously-load-development-channels <servers...>' argument missing`.
|
||||
2. **SDK**: `claude-agent-sdk` (used by `claude_sdk_executor.py` in the
|
||||
Claude Code workspace template) renders the kwarg as a bare switch when
|
||||
the value is `None`. The CLI then never responds to the SDK's first
|
||||
`initialize` control message.
|
||||
3. **Workspace agent**: the SDK times out with
|
||||
`Control request timeout: initialize`. Every A2A turn wedges — 100%
|
||||
reproducible. Caught live on workspace `dd40faf8` on 2026-05-01.
|
||||
|
||||
Two small fixes prevent this: pass a tagged value (don't let `None`
|
||||
render as a bare switch), and verify the CLI accepts your specific
|
||||
entries before going broad.
|
||||
|
||||
## For Molecule operators
|
||||
|
||||
Pass `server:molecule` to enable the inbox bridge → MCP
|
||||
`notifications/claude/channel` push for the `molecule-mcp` wheel.
|
||||
|
||||
```bash
|
||||
claude --dangerously-load-development-channels server:molecule
|
||||
```
|
||||
|
||||
The `molecule` here matches the name you registered the wheel under in
|
||||
[Step 2 of the runtime-mcp guide](/docs/runtime-mcp#claude-code) (the
|
||||
key under `mcpServers`, or the first positional arg to `claude mcp add`).
|
||||
If you registered the wheel as `mol` or `molecule-prod`, use that name
|
||||
in the tag.
|
||||
|
||||
When push is live, the session header prints:
|
||||
|
||||
```
|
||||
Listening for channel messages from: server:molecule
|
||||
```
|
||||
|
||||
…and inbound canvas/peer-agent messages render inline as
|
||||
`<channel source="molecule" ...>` synthetic user turns instead of
|
||||
arriving via `inbox_peek`.
|
||||
|
||||
### Embedding in an SDK-driven agent
|
||||
|
||||
If you spawn `claude` through `claude-agent-sdk` (e.g. the Claude Code
|
||||
workspace template's `claude_sdk_executor.py`), forward the tagged value
|
||||
through `extra_args`:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions
|
||||
|
||||
ClaudeAgentOptions(
|
||||
model=self.model,
|
||||
permission_mode="bypassPermissions",
|
||||
cwd=self._resolve_cwd(),
|
||||
mcp_servers=mcp_servers,
|
||||
system_prompt=self._build_system_prompt(),
|
||||
resume=self._session_id,
|
||||
extra_args={"dangerously-load-development-channels": "server:molecule"},
|
||||
)
|
||||
```
|
||||
|
||||
The SDK forwards `extra_args` keys as `--<key> <value>` to the spawned
|
||||
CLI. Passing `None` as the value renders as a bare switch and trips the
|
||||
[Failure mode](#failure-mode) chain above.
|
||||
|
||||
## Verification
|
||||
|
||||
Verified live on 2026-05-02: with the tagged value in `extra_args`,
|
||||
the in-workspace agent received `<channel source="molecule" kind="..."
|
||||
peer_id="..." activity_id="..." ts="...">` tags inline as synthetic
|
||||
user turns. No `wait_for_message` poll was needed for delivery. A2A
|
||||
returned coherent replies on every turn.
|
||||
|
||||
## When this matters
|
||||
|
||||
Only when both of the following apply:
|
||||
|
||||
- You're running Claude Code (any version 2.1.x or later) as the
|
||||
workspace runtime, AND
|
||||
- The in-workspace `molecule-mcp` server is configured (it is, by
|
||||
default, in the `claude-code` workspace template).
|
||||
|
||||
**Hosted Molecule SaaS handles this automatically** — the executor
|
||||
passes `extra_args={"dangerously-load-development-channels": "server:molecule"}`
|
||||
when spawning the CLI. Operators on hosted SaaS do not need to do
|
||||
anything.
|
||||
|
||||
**Self-hosted operators using the Claude Code workspace template** also
|
||||
get this for free since the template's executor sets `extra_args`. The
|
||||
flag only needs operator attention when:
|
||||
|
||||
- Forking the Claude Code workspace template and stripping `extra_args`
|
||||
inadvertently.
|
||||
- Running `claude` directly outside the template (e.g. interactive
|
||||
sessions on a developer laptop) and wanting inline `<channel>` push.
|
||||
- Adding a second tagged source (e.g. `server:telegram` alongside
|
||||
`server:molecule`) — append, don't replace.
|
||||
|
||||
Operators on Cursor, Cline, OpenCode, codex, hermes-agent, or any
|
||||
non-Claude-Code MCP host are unaffected: those clients ignore the
|
||||
notification and the wheel's poll path delivers via
|
||||
`wait_for_message` as the universal fallback.
|
||||
|
||||
## Forward note
|
||||
|
||||
This requirement is a **research-preview gate**. Once Claude Code
|
||||
graduates `notifications/<channel>` from research preview to a default
|
||||
allowlist, the `--dangerously-load-development-channels` flag will no
|
||||
longer be required for the `molecule` server. Drop the `extra_args`
|
||||
entry in `claude_sdk_executor.py` (and any operator launch wrappers)
|
||||
when that happens — the wheel emits the wire shape correctly today
|
||||
and will continue to do so post-graduation.
|
||||
|
||||
## See also
|
||||
|
||||
- [Bring Your Own Runtime (MCP) — Inbound delivery](/docs/runtime-mcp#inbound-delivery-universal-poll-optional-push)
|
||||
- [Bring Your Own Runtime (MCP) — Step 2: Claude Code](/docs/runtime-mcp#claude-code)
|
||||
- [Troubleshooting — Control request timeout: initialize](/docs/runtime-mcp#control-request-timeout-initialize-from-the-workspace-agent)
|
||||
@@ -17,8 +17,8 @@ description: Run the full Molecule AI stack on your own infrastructure.
|
||||
The fastest way to get Molecule AI running locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Molecule-AI/molecule-core.git
|
||||
cd molecule-core
|
||||
git clone https://github.com/Molecule-AI/molecule-monorepo.git
|
||||
cd molecule-monorepo
|
||||
./scripts/dev-start.sh
|
||||
# Canvas: http://localhost:3000
|
||||
# Platform: http://localhost:8080
|
||||
|
||||
Reference in New Issue
Block a user