Compare commits
70 Commits
fix/ci-run
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b381a49da | |||
| b489a911ed | |||
| 457e83f954 | |||
| 3e28a46bdf | |||
| c7c0a73152 | |||
| 00ae8bb25d | |||
| b9205d45e0 | |||
| bc91a1e1a6 | |||
| b498c55819 | |||
| 21b3c3e545 | |||
| e68a16ad6a | |||
| 6d6ed1f43b | |||
| 78eacde156 | |||
| 04c638e3b4 | |||
| 61ed4ee298 | |||
| a87c621784 | |||
| f98e8fa8fa | |||
| e14bfde737 | |||
| 0f074c623a | |||
| bb3e1c78ce | |||
| e7b54429fd | |||
| 796487a04a | |||
| 429d991231 | |||
| e6c0c3ce7d | |||
| 05751a244b | |||
| a5613cb083 | |||
| a3edfffe21 | |||
| 5ef05a8932 | |||
| 796ec49d63 | |||
| 5c6f068bcf | |||
| 5c974e037f | |||
| de089e005b | |||
| 6d0ac94e64 | |||
| 51d98ba794 | |||
| 13ca8a0b81 | |||
| e1455eafc4 | |||
| 90df616fa4 | |||
| f96235f32a | |||
| e7a23338bf | |||
| 7c1ac608d3 | |||
| 4e40da7fc2 | |||
|
|
a52ed96143 | ||
|
|
d05d92b666 | ||
| 46615a07cf | |||
| 6d08619871 | |||
|
|
75d85a6ae2 | ||
|
|
f1ed8784ff | ||
|
|
78665e35ed | ||
|
|
798294b62a | ||
|
|
7df9bb6631 | ||
|
|
6a99eb0896 | ||
| 12c35656f4 | |||
|
|
fa6db57daf | ||
|
|
00dfb0a5fc | ||
|
|
f8a9b06323 | ||
|
|
32e7de04e3 | ||
|
|
5624261de8 | ||
| 988063e537 | |||
|
|
543906e4e5 | ||
|
|
a29db81b92 | ||
|
|
017f846ce2 | ||
|
|
fe49ed01d9 | ||
|
|
be832ed826 | ||
| 7e366b6d17 | |||
|
|
80ddd05f20 | ||
|
|
7bcc2a65aa | ||
|
|
617fc09d7f | ||
|
|
b4717e53e0 | ||
|
|
0fb2d53554 | ||
|
|
6d5c936165 |
1
.ci-auth-test3-1778443616.txt
Normal file
1
.ci-auth-test3-1778443616.txt
Normal file
@ -0,0 +1 @@
|
||||
ci-auth-test3-1778443616
|
||||
1
.ci-auth-test4-1778443835.txt
Normal file
1
.ci-auth-test4-1778443835.txt
Normal file
@ -0,0 +1 @@
|
||||
ci-auth-test4-1778443835
|
||||
121
.gitea/workflows/auto-promote-staging.yml
Normal file
121
.gitea/workflows/auto-promote-staging.yml
Normal file
@ -0,0 +1,121 @@
|
||||
name: Auto-promote staging → main
|
||||
|
||||
# Fast-forwards `main` to `staging` when staging is strictly ahead (main
|
||||
# is an ancestor). Eliminates the manual sync-PR round for non-critical
|
||||
# repos.
|
||||
#
|
||||
# Gate handling:
|
||||
# - If the repo has required_status_checks configured AND the API
|
||||
# returns them, all must be SUCCESS on the staging HEAD commit.
|
||||
# - If no gates are configured (or the API 403s on a private free-tier
|
||||
# repo), `--ff-only` is the sole safety. It refuses if main has
|
||||
# independent commits staging doesn't contain.
|
||||
#
|
||||
# Excluded by policy: molecule-core + molecule-controlplane. Those two
|
||||
# stay manual per CEO directive 2026-04-24.
|
||||
#
|
||||
# Safety:
|
||||
# - Only fires on push to staging (PRs into staging don't promote)
|
||||
# - `--ff-only` refuses if main has diverged (hotfix landed directly)
|
||||
# - Promote commit goes through GITHUB_TOKEN; shows up in git log as
|
||||
# a deliberate act
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [staging]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
statuses: read
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
# self-hosted Mac mini — org's GitHub-hosted minute budget is exhausted
|
||||
# for private repos; same runner used by ci.yml
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check required gates (if configured) on staging HEAD
|
||||
id: gates
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Try to read required gates from branch protection. Free-tier
|
||||
# private repos may 403; handle that gracefully.
|
||||
GATES_JSON=$(gh api "repos/${REPO}/branches/staging/protection/required_status_checks" 2>/dev/null || echo '{}')
|
||||
GATES=$(echo "${GATES_JSON}" | jq -r '.contexts[]?' 2>/dev/null || true)
|
||||
|
||||
if [ -z "$GATES" ]; then
|
||||
echo "No required gates configured (or API inaccessible). Relying on --ff-only safety."
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Required gates on staging:"
|
||||
echo "${GATES}" | sed 's/^/ - /'
|
||||
|
||||
ALL_GREEN=true
|
||||
while IFS= read -r gate; do
|
||||
[ -z "$gate" ] && continue
|
||||
|
||||
conclusion=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs" \
|
||||
--jq "[.check_runs[] | select(.name == \"${gate}\")] | sort_by(.completed_at) | last.conclusion" \
|
||||
2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$conclusion" ] || [ "$conclusion" = "null" ]; then
|
||||
conclusion=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/status" \
|
||||
--jq "[.statuses[] | select(.context == \"${gate}\")] | sort_by(.updated_at) | last.state" \
|
||||
2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ "$conclusion" != "success" ] && [ "$conclusion" != "SUCCESS" ]; then
|
||||
echo "::warning::Gate '${gate}' is '${conclusion:-missing}' on ${HEAD_SHA} — skipping promote."
|
||||
ALL_GREEN=false
|
||||
else
|
||||
echo " ✓ ${gate}: success"
|
||||
fi
|
||||
done <<< "$GATES"
|
||||
|
||||
echo "ok=${ALL_GREEN}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fast-forward main to staging
|
||||
if: steps.gates.outputs.ok == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config user.email "actions@github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
|
||||
# staging is the checked-out branch (workflow fires on push to
|
||||
# staging). Can't fetch into it. Fetch main into a local main.
|
||||
git fetch origin main
|
||||
git checkout -B main origin/main
|
||||
|
||||
# Check if main is already at or ahead of origin/staging.
|
||||
if git merge-base --is-ancestor origin/staging main 2>/dev/null; then
|
||||
echo "main already contains staging; nothing to promote."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --ff-only refuses if main has independent commits not on
|
||||
# staging (divergence — hotfix direct to main). Human resolves.
|
||||
if ! git merge --ff-only origin/staging 2>&1; then
|
||||
echo "::warning::main has diverged from staging — refusing fast-forward. Resolve manually (likely a direct-to-main commit exists that staging doesn't have)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git push origin main
|
||||
echo "::notice::Promoted: main is now at $(git rev-parse --short HEAD)"
|
||||
17
.gitea/workflows/ci.yml
Normal file
17
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,17 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
22
.gitea/workflows/secret-scan.yml
Normal file
22
.gitea/workflows/secret-scan.yml
Normal file
@ -0,0 +1,22 @@
|
||||
name: Secret scan
|
||||
|
||||
# Calls the canonical reusable workflow in molecule-core. Defense
|
||||
# against the #2090-class leak (a hosted-agent commit slipping a
|
||||
# credential-shaped string into a PR). Pattern set lives in
|
||||
# molecule-core so we don't maintain a parallel copy here.
|
||||
#
|
||||
# Pinned to @staging because that's the active default branch on the
|
||||
# upstream repo (main lags behind via the staging-promotion workflow).
|
||||
# Updates ride along automatically as the upstream regex set evolves.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [main, staging]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
secret-scan:
|
||||
uses: molecule-ai/molecule-core/.gitea/workflows/secret-scan.yml@staging
|
||||
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -1,22 +0,0 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
# Self-hosted Mac mini — this repo is private and the org's
|
||||
# GitHub-hosted minute budget is exhausted (every ubuntu-latest job
|
||||
# dies in 2s with no step output). Per the 2026-04-22 carve-out:
|
||||
# private repos run on self-hosted; public repos use ubuntu-latest
|
||||
# (still free).
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@ -13,15 +13,15 @@ export default function HomePage() {
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="rounded-md bg-fd-primary px-5 py-2.5 text-sm font-medium text-fd-primary-foreground transition-colors hover:opacity-90"
|
||||
className="rounded-md bg-fd-primary px-5 py-2.5 text-sm font-medium text-fd-primary-foreground transition-colors hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring"
|
||||
>
|
||||
Read the docs
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/Molecule-AI/molecule-monorepo"
|
||||
className="rounded-md border border-fd-border px-5 py-2.5 text-sm font-medium transition-colors hover:bg-fd-muted"
|
||||
href="https://git.moleculesai.app/molecule-ai/molecule-core"
|
||||
className="rounded-md border border-fd-border px-5 py-2.5 text-sm font-medium transition-colors hover:bg-fd-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring"
|
||||
>
|
||||
View on GitHub
|
||||
View on Gitea
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@ -41,8 +41,20 @@ export async function generateMetadata(props: {
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const url = `https://doc.moleculesai.app/docs/${(params.slug ?? []).join("/")}`;
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
openGraph: {
|
||||
title: `${page.data.title} | Molecule AI Docs`,
|
||||
description: page.data.description,
|
||||
url,
|
||||
siteName: "Molecule AI Documentation",
|
||||
type: "article" as const,
|
||||
},
|
||||
alternates: {
|
||||
canonical: url,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,6 +15,19 @@ export const metadata = {
|
||||
description:
|
||||
'Build and run multi-agent organisations on the Molecule AI platform. Templates, plugins, channels, and the runtime that ties them together.',
|
||||
metadataBase: new URL('https://doc.moleculesai.app'),
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
siteName: 'Molecule AI Documentation',
|
||||
title: 'Molecule AI Documentation',
|
||||
description:
|
||||
'Build and run multi-agent organisations on the Molecule AI platform. Templates, plugins, channels, and the runtime that ties them together.',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Molecule AI Documentation',
|
||||
description:
|
||||
'Build and run multi-agent organisations on the Molecule AI platform.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
|
||||
110
content/blog/2026-04-25-why-multi-agent-teams/index.mdx
Normal file
110
content/blog/2026-04-25-why-multi-agent-teams/index.mdx
Normal file
@ -0,0 +1,110 @@
|
||||
---
|
||||
title: "Why You Need a Team of AI Agents, Not One Genius Model"
|
||||
date: 2026-04-25
|
||||
slug: why-multi-agent-teams-not-one-ai
|
||||
description: "The most common question we get is some version of: 'GPT-5 / Claude 4.7 is so smart already — why do I need a team of agents instead of just one?' Here's the honest answer, with real examples from running our own engineering org on agents."
|
||||
tags: [philosophy, multi-agent, AI-agents, team, organization, molecule]
|
||||
---
|
||||
|
||||
# Why You Need a Team of AI Agents, Not One Genius Model
|
||||
|
||||
Almost every prospect asks some version of this:
|
||||
|
||||
> "Models are so smart now. Why would I run six agents in a team when I could just give one really good agent the whole job?"
|
||||
|
||||
It's a fair question. If a single Claude 4.7 or GPT-5 can write code, design a slide deck, and answer customer email — what's the point of carving the work into specialists?
|
||||
|
||||
The honest answer is that we tried both, on our own company, every day. The single-agent setup is *seductive* but it doesn't scale past about a half-day of real work. Here's what we kept hitting, and why a team — even a small team of three or four — solves problems that "smarter model" alone cannot.
|
||||
|
||||
## The "one super-agent" temptation
|
||||
|
||||
It's the simplest mental model. One context window, one inbox, one set of memories. You give it a goal, it figures out the steps. No coordination overhead, no role assignment, no PM agent to keep things on track. If something goes wrong, there's only one place to look.
|
||||
|
||||
For tasks that fit in one head, this works. Single-shot scripting, one-off analysis, a focused coding session — solo agent is the right tool. We use it constantly.
|
||||
|
||||
The problem starts when the work doesn't fit in one head.
|
||||
|
||||
## Three things that break with one agent
|
||||
|
||||
### 1. Context collapse
|
||||
|
||||
Every model has a context window. Modern ones are big — 200k, 1M tokens — but the *useful* part shrinks fast. After a few hours of mixed work, the agent's context is a stew of half-finished plans, stale assumptions, abandoned branches, and old errors. The model technically has access to all of it; in practice, recall degrades and decisions get fuzzy.
|
||||
|
||||
A team avoids this by *partitioning the work*. The Marketing Lead doesn't carry the database migration history. The Dev Lead doesn't keep a half-written launch blog in scratch space. Each agent's context is small, fresh, and on-topic.
|
||||
|
||||
We measured this on our own org: when we collapsed our seven-agent setup to one agent for two days, the average task it took to "I lost track of what I was doing" went from never (over the same workload) to about four hours. Same model, same prompts, same tools — just one big context vs. six small ones.
|
||||
|
||||
### 2. Specialization beats generalization, by a lot
|
||||
|
||||
You wouldn't ask a senior engineer to also lead your marketing campaigns and review your legal contracts. Not because they couldn't *technically* do any one of those things — but because the prompt-shaped person who's effective at all three doesn't exist. Their working memory, their reflexes, their reference material, their tools — all different per role.
|
||||
|
||||
The same is true for agents. Our Marketing Lead has a system prompt full of brand voice rules, a memory full of past campaigns, and tools wired into our CMS. Our Dev Lead has none of that and instead has access to GitHub, the build system, and a memory of past technical decisions. Either could "do" the other's job by general intelligence, but the result is consistently worse — wider variance, more mistakes, and far less context-aware judgment.
|
||||
|
||||
When we A/B-tested generalist single-agent vs. specialist team on the same set of tasks, the specialist team finished faster *and* produced output the human reviewer kept by default. The generalist's work needed more rounds of correction.
|
||||
|
||||
### 3. Failure isolation
|
||||
|
||||
Single-agent setups have a single failure mode: when the agent gets stuck or confused, *everything* stops. Whatever it was working on, whatever was queued behind it.
|
||||
|
||||
A team has structural redundancy. We had an incident last week where one of our agents (Marketing Lead) opened eleven pull requests against the wrong repository for almost two days before a human noticed. With a single-agent setup that would have been our entire AI workforce wedged. With a team, the Dev Lead, PM, Research Lead, and Customer Support agents kept doing real work the entire time. The damage was scoped to one role.
|
||||
|
||||
This isn't theoretical. Real companies — human ones — survive bad hires and bad days because the org chart is a graph, not a string. The same structural property protects an agent organization.
|
||||
|
||||
## What a team actually buys you
|
||||
|
||||
Once you partition by role, four things start happening that a single agent can't reproduce:
|
||||
|
||||
**Parallelism.** Six agents working at the same time finish six tasks in roughly the time of one. A solo agent serializes. For most knowledge work, the bottleneck is wall-clock time, not raw IQ.
|
||||
|
||||
**Hierarchy.** With more than two agents you get review structures: PM signs off on dev work, Dev Lead reviews PRs from junior engineering agents, Marketing Lead approves social copy from Content Marketer. Mistakes get caught at the structure level, not just by hoping the model didn't hallucinate.
|
||||
|
||||
**Audit trails.** Every action carries the role that did it. Git commits show "Marketing Lead" vs. "Dev Lead" vs. "Research Lead." Slack/email messages, file edits, ticket comments — all attributable. When something goes wrong (and it will), you have a paper trail. With one agent, *every* action is "the AI" and the only way to debug is to read transcripts.
|
||||
|
||||
**Trust tiers.** Different roles can run with different permissions. Our research agents are sandboxed (read-only file system, no production access). Engineering agents have read-write to repos but not to billing. Operations agents have AWS keys but only via SSM, never raw. A single agent has to be granted the union of all permissions, which is a security disaster waiting to happen.
|
||||
|
||||
## The objections we hear, and what they're actually asking
|
||||
|
||||
> "But the models are getting smarter. Won't this all be moot in a year?"
|
||||
|
||||
Smarter models still have context windows. They still benefit from specialization. They still concentrate failure. The argument that "smarter solves coordination" assumes coordination is a side-effect of intelligence — but real organizations ran into the same problem with very smart humans, and the answer was always to add structure, not replace it.
|
||||
|
||||
> "Coordination overhead has to cost something. What's the catch?"
|
||||
|
||||
It does. Setting up a multi-agent team takes more thought than "spin up one Claude." You have to define roles, give them prompts, decide who does what, set up handoff conventions. We've automated much of that with org templates (a "Dev Team" template instantiates PM + Lead + 3 engineers in one click), but you still need to think about the shape. For one-off scripting, that overhead isn't worth it. For a continuous workload, it's repaid in the first day.
|
||||
|
||||
> "Can't one agent just spawn sub-agents on demand?"
|
||||
|
||||
This is the strongest counter-argument and it's where things get interesting. You absolutely can do "one agent that sub-delegates." The thing is, once you do that, you've built a multi-agent team — you've just hidden the org chart inside a single entry point. Internally, the spawning agent is now the PM and the spawned ones are workers. The reason multi-agent platforms (us, AutoGen, CrewAI) exist is that *making the org chart explicit* gives you observability, restartability, persistent memory per role, distinct identities, and the trust-tier story above. Implicit sub-delegation gets you the parallelism but loses everything else.
|
||||
|
||||
## When one agent is the right answer
|
||||
|
||||
We're not religious about this. Single agents are the right tool for:
|
||||
|
||||
- A focused coding task ("fix this bug")
|
||||
- A single-document analysis ("summarize this paper")
|
||||
- One-shot generation ("draft this email")
|
||||
- Anything where the work fits comfortably in one head and you'll review the output yourself
|
||||
|
||||
The team setup pays off when:
|
||||
|
||||
- The workload is **continuous** (not one-shot)
|
||||
- The roles are **distinct** (engineering ≠ marketing ≠ ops)
|
||||
- You want **parallelism** (multiple things happening at once)
|
||||
- You need **accountability** (who did what, when, why)
|
||||
- You want **trust separation** (read-only research vs. write-access engineering)
|
||||
|
||||
For us at Molecule, almost every interesting workload looks like the second list. So we built around it.
|
||||
|
||||
## What this looks like at Molecule
|
||||
|
||||
Our own engineering org runs on agents. Right now there's a PM agent coordinating; a Dev Lead reviewing PRs; engineering agents writing code; a Research Lead reading papers and competitor blogs; a Marketing Lead drafting launch content. They communicate over [A2A](https://google.dev/agents/a2a), share long-term memory through a per-role memory store, and check in with the human (me) when they hit a decision they can't make alone.
|
||||
|
||||
Setting this up is a `Create org from template` away on Molecule. Pick the "engineering team" template, plug in API keys, watch a tree appear on the canvas, give it a goal. The hierarchy, the role separation, the audit trail — they're built into the substrate.
|
||||
|
||||
Could one big Claude do most of this work? Probably. Would it ship at the same pace, with the same paper trail, with the same ability to recover from a single agent going off the rails for two days? No, not in our experience.
|
||||
|
||||
Multi-agent isn't a hedge against models being dumb. It's a hedge against work being big.
|
||||
|
||||
---
|
||||
|
||||
*This is an opinionated piece based on running our own company on top of [Molecule AI](https://app.moleculesai.app). If you want to try it, the [Engineering Team](https://app.moleculesai.app) template is the fastest way in — it spins up a five-agent setup that covers most of the structure described above, and you can edit roles after.*
|
||||
@ -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.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Playwright / browser system libs are not installed
|
||||
|
||||
The base `molecule-ai-workspace-runtime` image is built on `python:3.11-slim` with Node.js 22, git, and `gh` — about 500 MB. It deliberately **does not** include the system libraries Chromium needs (`libnss3`, `libatk-bridge2.0-0`, `libxkbcommon0`, `libcups2`, `libdrm2`, `libxcomposite1`, `libxdamage1`, `libxrandr2`, `libgbm1`, `libpango-1.0-0`, `libasound2`, etc.). Adding them would inflate the image by ~200–250 MB (~40%) for every workspace, even though only frontend / QA workspaces ever launch a browser.
|
||||
|
||||
Practical consequences:
|
||||
|
||||
- `npx playwright test` (and any other Chromium-driven E2E tooling) **will fail at browser launch** when run from inside an in-container workspace agent.
|
||||
- The error surface is missing-shared-object messages such as `error while loading shared libraries: libnss3.so` or `Host system is missing dependencies to run browsers`.
|
||||
- Unit and integration tests (Vitest, Jest, etc.) that don't spawn a real browser are unaffected.
|
||||
|
||||
Recommended workflow:
|
||||
|
||||
1. **Run E2E in CI**, not in-container. The Gitea Actions self-hosted runner (and GitHub Actions runners used by mirror repos) has the full Playwright dep set installed and is the supported surface for E2E. Push a branch, let CI run the suite.
|
||||
2. **Local debugging** of a single failing spec is best done on a developer laptop with `npx playwright install-deps` run once.
|
||||
3. **In-container iteration** on test logic itself is fine — write specs, lint them, type-check them — just don't expect `playwright test` to actually launch a browser.
|
||||
|
||||
If a particular workspace role genuinely needs in-container E2E (a dedicated QA template, for instance), the right place to layer Playwright deps is in a **role-specific adapter template image** that does `FROM molecule-ai-workspace-runtime:<tag>` and adds `RUN npx playwright install-deps`. Open a request against `molecule-ai-workspace-runtime` if you need this template stamped.
|
||||
|
||||
Tracking issue: [molecule-ai/molecule-app#7](https://git.moleculesai.app/molecule-ai/molecule-app/issues/7).
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [Agent Runtime Adapters](./cli-runtime.md)
|
||||
|
||||
@ -138,7 +138,7 @@ These controls complement the platform-level secret redaction described in the [
|
||||
|
||||
**Stack:** Go / Bubbletea + Lipgloss
|
||||
|
||||
A terminal UI dashboard for real-time workspace monitoring, event log streaming, health overview, and delete/filter operations. Reads `MOLECLI_URL` (default `http://localhost:8080`) to locate the platform. Now published as a standalone repo at `github.com/Molecule-AI/molecule-cli`.
|
||||
A terminal UI dashboard for real-time workspace monitoring, event log streaming, health overview, and delete/filter operations. Reads `MOLECLI_URL` (default `http://localhost:8080`) to locate the platform. Now published as a standalone repo at `git.moleculesai.app/molecule-ai/molecule-cli`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ title: "Molecule AI — Comprehensive Technical Documentation"
|
||||
# Molecule AI — Comprehensive Technical Documentation
|
||||
|
||||
> Definitive technical reference for the Molecule AI Agent Team platform.
|
||||
> Based on a full non-invasive scan of the [molecule-monorepo](https://github.com/Molecule-AI/molecule-monorepo) repository.
|
||||
> Based on a full non-invasive scan of the [molecule-core](https://git.moleculesai.app/molecule-ai/molecule-core) repository (formerly `molecule-monorepo`, renamed during the post-2026-05-06 GitHub-org-suspension recovery).
|
||||
|
||||
---
|
||||
|
||||
@ -1153,11 +1153,11 @@ Molecule AI's workspace abstraction is **runtime-agnostic by design**. A workspa
|
||||
|
||||
## Links
|
||||
|
||||
- **GitHub**: https://github.com/Molecule-AI/molecule-monorepo
|
||||
- **Architecture Docs**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/architecture
|
||||
- **API Protocol**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/agent-runtime
|
||||
- **Product Docs**: https://github.com/Molecule-AI/molecule-monorepo/tree/main/docs/product
|
||||
- **Gitea**: https://git.moleculesai.app/molecule-ai/molecule-core
|
||||
- **Architecture Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/architecture
|
||||
- **API Protocol**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/api-protocol
|
||||
- **Agent Runtime**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/agent-runtime
|
||||
- **Product Docs**: https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/docs/product
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -7,74 +7,279 @@ All notable changes to the Molecule AI platform are documented here.
|
||||
Entries are published daily at 23:50 UTC.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-12
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **OFFSEC-001: MCP endpoint information disclosure fixed**: the JSON-RPC `-32601` error handler in `mcp.go` was reflecting user-controlled `req.Method` back into the error message. An agent or canvas client sending a crafted `method` field would see that value reflected in the error response. The handler now returns a constant `"method not found"` string, closing the information-disclosure vector. (`molecule-core` [#692](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/692))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- **Canvas focus-visible regression fixed in FilesTab and BudgetSection**: a regression introduced in recent canvas updates caused focus-visible rings to stop rendering on `FilesTab` and `BudgetSection` components. Restored to full WCAG 2.4.7 compliance — keyboard and assistive-technology users see a visible focus indicator on all interactive elements in these panels. (`molecule-core` [#614](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/614))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **CI quality hardening** (`molecule-core`): `status-reaper` revised to sweep the last 10 main commits (up from 1) to catch stranded statuses from concurrent workflows; fixed a broken concurrency block that caused duplicate alerts on Gitea 1.22.6. (`molecule-core` [#633](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/633), [#618](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/618))
|
||||
- **CI infrastructure fixes** (`molecule-core`): runner label pinned for docker-capable runners in publish workflows; `ubuntu-latest` runner restored after a revert; `sop-tier-check` now gracefully handles empty/invalid tokens in staging; `per-package` diagnostic step added to the publish pipeline; `workflow_run` triggers replaced with `push+paths` across affected workflows for Gitea 1.22.6 compatibility. (`molecule-core` [#636](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/636), [#609](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/609), [#606](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/606), [#694](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/694))
|
||||
- **Test coverage additions** (`molecule-core`): 180+ new test cases across canvas, UI, tabs, platform/bundle, and workspace modules — covering FilesTab, BudgetSection, NotAvailablePanel, FilesToolbar, KeyValueField, RevealToggle, ValidationHint, getSkills, extractSkills, exporter.go, buildBundleConfigFiles, and a2a_response.py queue envelope. (`molecule-core` [#614](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/614), [#611](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/611), [#629](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/629), [#600](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/600), [#616](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/616), [#592](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/592), [#626](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/626), [#587](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/587), [#621](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/621))
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-11
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- **Delegation results auto-surfaced to agents**: when a `delegate_task` call completes, the results are now automatically injected into the agent's next turn — no explicit `check_task_status` call required. This closes the gap where parallel `delegate_task` calls returned after the SDK turn ended and the agent had no way to discover the results. (`molecule-core` [#358](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/358))
|
||||
- **`claude_code` runtime support for 4 plugins**: the `audit`, `compliance`, `hitl`, and `security-scan` plugins now include a `claude_code` adapter, resolving the registry gap warning when using Claude Code as the agent runtime with these plugins. (`molecule-ai-plugin-molecule-audit` [#6](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-audit/pulls/6), `molecule-ai-plugin-molecule-compliance` [#6](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-compliance/pulls/6), `molecule-ai-plugin-molecule-hitl` [#6](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-hitl/pulls/6), `molecule-ai-plugin-molecule-security-scan` [#6](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-security-scan/pulls/6))
|
||||
- **MCP HTTP/SSE transport improvements**: `a2a_mcp_server.py` now correctly identifies itself as `"molecule"` (was `"a2a-delegation"`), emits SSE heartbeats with `data: null` (was invalid `data: {}`), and only sends a heartbeat when the connection is idle — eliminating spurious heartbeat noise on every response. (`molecule-ai-workspace-runtime` [#12](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/12))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **Canvas WCAG 2.4.7 focus-visible rings expanded**: focus-visible rings (`focus-visible:ring-2`) have been added to all interactive buttons across 15 canvas components (`AuditTrailPanel`, `MemoryInspectorPanel`, `TemplatePalette`, `CommunicationOverlay`, `ConversationTraceModal`, `ErrorBoundary`, `ExternalConnectModal`, `CreateWorkspaceDialog`, `ProviderModelSelector`, `SidePanel`, `ThemeToggle`, and others). Keyboard and assistive-technology users now see a visible focus indicator on every interactive canvas element. (`molecule-core` [#421](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/421))
|
||||
- **OFFSEC-003: delegation result fields sanitized on platform side**: `tool_check_task_status` now calls `sanitize_a2a_result()` on `summary` and `response_preview` fields before embedding them in JSON output — both when returning a single delegation by `delegation_id` and when listing all recent delegations. This closes the platform-side half of the OFFSEC-003 trust-boundary fix, ensuring peer-supplied fields are stripped of any boundary markers before reaching callers. (`molecule-core` [#417](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/417), [#416](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/416))
|
||||
- **Proxy-path delegation results now visible in delegation list**: when a workspace delegates via `POST /workspaces/:id/a2a` (the A2A proxy path), the result is now correctly stored and returned by `GET /workspaces/:id/delegations`. Previously these rows were logged with the wrong activity type and invisible to the delegation list endpoint — callers polling for results would see an incomplete set. The platform-side logging fix (`molecule-core` [#483](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/483)) and the workspace heartbeat fix (`molecule-core` [#501](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/501)) ship together — the proxy now writes correct rows, and the heartbeat loop now polls them so agents wake up to consume delegation results without manual `check_task_status` calls.
|
||||
|
||||
- **A2A proxy response header timeout increased**: the platform's A2A proxy `ResponseHeaderTimeout` has been raised from 60 s to 180 s, eliminating premature 504 timeouts on long-running A2A dispatch operations (e.g. agent synthesis, cold-start OAuth flows). The timeout is now also configurable per-deployment via the `A2A_PROXY_RESPONSE_HEADER_TIMEOUT` environment variable. (`molecule-core` [#331](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/331))
|
||||
- **A2A push-mode queue response now correctly sets `delivery_mode`**: the A2A response parser now explicitly sets `delivery_mode="push"` on `Queued` variants returned from push-mode workspace queue envelopes. Previously it silently defaulted, causing callers that branch on `v.delivery_mode` to mis-route poll-mode responses as push-mode (and vice versa). (`molecule-core` [#356](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/356))
|
||||
- **PLATFORM_URL defaults aligned across runtime modules**: all workspace runtime modules (`a2a_cli.py`, `a2a_client.py`, `a2a_mcp_server.py`, and 10 others) now consistently default `PLATFORM_URL` to `http://host.docker.internal:8080`, eliminating an inconsistency where some modules pointed to `http://platform:8080`. (`molecule-ai-workspace-runtime` [#12](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/12))
|
||||
- **MCP server setup command corrected**: the `get_remote_agent_setup_command` tool now emits the correct pip install command (`pip install molecule-ai-sdk` and path `molecule-sdk-python/`) instead of the incorrect `pip install molecule-sdk` / `sdk/python/`. Users following the tool's output will now get a working setup. (`molecule-mcp-server` [#4](https://git.moleculesai.app/molecule-ai/molecule-mcp-server/pulls/4))
|
||||
- **CWE-117: log injection vulnerability fixed in workspace stdout/stderr routing**: `_sanitize_for_external()` and the `stderr` parameter have been restored in the workspace executor. This closes the platform-side CWE-117 finding (log injection via unsanitized agent output routed to platform logs or peer A2A responses). Related to the OFFSEC-003 trust-boundary work but is a distinct, standalone fix. (`molecule-core` [#573](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/573))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **CI fixes** (`molecule-core`): `publish-runtime.yml` split into two workflows (tags-only publisher + autobump) and a Gitea `workflow_dispatch.inputs` parser bug (causing the workflow to be silently ignored) has been fixed. (`molecule-core` [#349](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/349), [#352](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/352), [#353](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/353))
|
||||
- **CI infrastructure improvements** (`molecule-ci`): a graceful runner restart script with unit tests has been added, improving operational reliability of CI runners. (`molecule-ci` [#8](https://git.moleculesai.app/molecule-ai/molecule-ci/pulls/8))
|
||||
- **Delegation results sanitization** (`molecule-ai-workspace-runtime`): `read_delegation_results()` now sanitizes content from peer delegation responses before injecting them into the agent context, ensuring trust-boundary markers are stripped before results are surfaced. (`molecule-ai-workspace-runtime` [#13](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/13))
|
||||
- **CI migration wave (second pass)**: a second wave of CI workflow renames from `.github/workflows/` to `.gitea/workflows/` completed across `molecule-controlplane`, `molecule-ai-workspace-runtime`, `molecule-sdk-python`, `molecule-mcp-server`, and 12 plugin repos. (`molecule-ai-*` [#various](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/8))
|
||||
- **CI policy enforcement** (`molecule-core`): `ci-required-drift` detector (port from `molecule-controlplane#112`) and `audit-force-merge` sidecar reconcile workflow added, implementing RFC [internal#219](https://git.moleculesai.app/molecule-ai/internal/issues/219) §4+§6 phases. (`molecule-core` [#422](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/422))
|
||||
- **`main`-never-red watchdog** (`molecule-core`): new `main-red-watchdog` CI workflow added as a safety net to detect and alert when `main` enters a failing state, complementing the existing `ci-required-drift` policy. (`molecule-core` [#423](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/423))
|
||||
- **CI wave three — platform + templates** (`molecule-core`, workspace templates): a third CI migration wave completed, porting the `validate` workflow to `.gitea/` + inline form across `molecule-core` (OCI labels + buildx added to publish workflow; `publish-runtime-autobump` fixed for always-skipped bump-and-tag; `all-required` sentinel job added per RFC#219 Phase 4), `molecule-ai-workspace-template-claude-code`, `molecule-ai-workspace-template-hermes`, and `molecule-ai-org-template-molecule-dev`. (`molecule-core` [#559](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/559), [#563](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/563), [#553](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/553), etc.; workspace templates [various CI ports](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-template-claude-code/pulls/17))
|
||||
- **CI quality hardening** (`molecule-core`): `gate-check-v3` received multiple fixes — explicit 15 s timeout on HTTP calls, combined-state self-referential fallback removed, token no longer appears in curl argv, checkout now uses base SHA. (`molecule-core` [#604](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/604), [#564](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/564), [#549](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/549), [#556](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/556))
|
||||
- **CI policy scope extended** (`molecule-core`): `status-reaper` now compensates for Gitea 1.22.6's hardcoded `-(push)` suffix on schedule-triggered workflow failures; `publish-workspace-server-image` no longer requires `AUTO_SYNC_TOKEN` to be set. (`molecule-core` [#589](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/589), [#572](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/572))
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-10
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- **MCP HTTP/SSE transport for Hermes**: `a2a_mcp_server.py` now speaks HTTP + SSE in addition to stdio, enabling the Hermes runtime to host MCP tools over a network endpoint rather than only via child-process stdio. (`molecule-ai-workspace-runtime` [#5](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/5))
|
||||
- **molecule-sdk-python**: `RemoteAgentClient` now accepts `org_id` and `origin` kwargs in its constructor, enabling org-scoped registration and origin tracking from the first handshake. (`molecule-sdk-python` [#7](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/7))
|
||||
- **molecule-sdk-python**: `fetch_inbound()` now supports `peer_id` and `before_ts` filter params for targeted message retrieval — useful for polling a specific peer's pending tasks. (`molecule-sdk-python` [#6](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/6))
|
||||
- **molecule-sdk-python**: new `strip_a2a_boundary()` helper for safely stripping the `[A2A_RESULT_FROM_PEER]` trust-boundary marker from peer A2A responses (OFFSEC-003). Works correctly on both pre- and post-OFFSEC-003 responses. (`molecule-sdk-python` [#8](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/8))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **molecule-app**: WCAG 2.4.7 focus-visible rings added to all customer-facing buttons (`ThemeToggle`, `Track-issue Link`, and general CTA buttons) — keyboard and assistive-technology users now see a visible focus indicator on every interactive element. (`molecule-app` [#5](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/5), [#9](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/9), [#10](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/10))
|
||||
- **status.moleculesai.app aggregator**: the status page's probe result aggregator was rewritten to correctly compute composite uptime across all monitored endpoints — resolving false-down alerts caused by a data-structure bug in the previous implementation. (`molecule-ai-status` [#10](https://git.moleculesai.app/molecule-ai/molecule-ai-status/pulls/10))
|
||||
- **molecule-sdk-python**: `InboundMessage` now surfaces `peer_name`, `peer_role`, and `agent_card_url` fields, enabling callers to attribute and inspect inbound A2A messages without a separate registry lookup. (`molecule-sdk-python` [#5](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/5))
|
||||
- **molecule-cli**: CI test workflow added — `molecule ci test` now runs a reproducible test suite against any workspace template. (`molecule-cli` [#3](https://git.moleculesai.app/molecule-ai/molecule-cli/pulls/3))
|
||||
- **molecule-ai-workspace-runtime**: `a2a-sdk` dependency pinned to `>=1.0.0` to match the actual code — eliminates a version mismatch that caused `AttributeError` on newer SDK builds. (`molecule-ai-workspace-runtime` [#4](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/4))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **molecule-sdk-python**: README API surface additions covering the Phase 30.8 RemoteAgentClient API, including `org_id`, `origin`, `fetch_inbound`, `InboundMessage`, and `strip_a2a_boundary()`. (`molecule-sdk-python` [#4](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/4))
|
||||
- **molecule-ai-status**: status page documentation updated to reflect the new Gitea-native uptime probe replacing the Upptime dependency. (`molecule-ai-status` [#4](https://git.moleculesai.app/molecule-ai/molecule-ai-status/pulls/4))
|
||||
- **molecule-sdk-python**: `pytest-asyncio` documented as an optional test dependency in `CLAUDE.md`. (`molecule-sdk-python` [#3](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/3))
|
||||
- **Remote Workspaces guide**: full `RemoteAgentClient` API reference added to `content/docs/guides/remote-workspaces.md`, covering constructor params, `fetch_inbound()`, `InboundMessage` fields, and the OFFSEC-003 `strip_a2a_boundary()` security section. (`docs` [#13](https://git.moleculesai.app/molecule-ai/docs/pulls/13))
|
||||
- **status.moleculesai.app**: status page aggregator fix documented in the changelog. (`docs` [#14](https://git.moleculesai.app/molecule-ai/docs/pulls/14))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- **CI migration wave**: 22 repos migrated CI workflows from `.github/workflows/` to `.gitea/workflows/` following the GitHub org suspension (post-suspension sweep). Affected repos: `molecule-cli`, `molecule-sdk-python`, `molecule-mcp-server`, and all 21 plugin repos.
|
||||
- **Plugin hygiene**: 20 plugin repos received `.gitignore` Python-ignores (`__pycache__/`, `*.pyc`) and `__pycache__` directory removal across the plugin ecosystem (`molecule-ai-plugin-*`).
|
||||
- **Plugin smoke-test suites**: 13 plugin repos (`molecule-ai-plugin-*`) now ship with documented smoke-test suites and coverage rationale READMEs (`tests/README.md`), adding test counts ranging from 21 to 26 tests per plugin.
|
||||
- **Hook path fixes**: `molecule-ai-plugin-molecule-freeze-scope` and `molecule-ai-plugin-molecule-audit-trail` received `get_repo_root()` layout detection fixes and corresponding test suites.
|
||||
- **molecule-ai-org-template-molecule-dev**: org-level `initial_prompt` updated from GitHub to Gitea URLs. (`molecule-ai-org-template-molecule-dev` [#8](https://git.moleculesai.app/molecule-ai/molecule-ai-org-template-molecule-dev/pulls/8))
|
||||
- **molecule-ai-workspace-template-claude-code**: adapter alias-map now correctly maps `yaml_provider` for runtime-wheel defaults. (`molecule-ai-workspace-template-claude-code` [#12](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-template-claude-code/pulls/12))
|
||||
- **molecule-ai-plugin-molecule-careful-bash**: token exfiltration pattern block (OFFSEC-002) now documented in `known-issues.md`. (`molecule-ai-plugin-molecule-careful-bash` [#3](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-careful-bash/pulls/3))
|
||||
- **molecule-ci**: 7 reusable workflows ported to `.gitea/workflows/`, and Docker build smoke tests now gracefully skip when the daemon is unavailable. (`molecule-ci` [#6](https://git.moleculesai.app/molecule-ai/molecule-ci/pulls/6), [#7](https://git.moleculesai.app/molecule-ai/molecule-ci/pulls/7))
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-23
|
||||
|
||||
### ✨ 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))
|
||||
- **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))
|
||||
- **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/pulls/1700))
|
||||
- **External workspace quickstart**: a 5-minute guide to running any HTTP-speaking agent (Python, Node, Go, Rust) on your own machine and having it appear on the canvas alongside platform-provisioned agents. Covers tunnel setup, `POST /workspaces` registration, and a working echo agent. (`molecule-core` [#1760](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1760))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **SSRF guard in SaaS mode**: previously the SSRF protection was blocking all RFC-1918 private IP ranges (`10/8`, `172.16/12`, `192.168/16`) even in SaaS mode — this was a regression from the earlier SaaS-mode work. The fix wires up the `saasMode` flag correctly so private IPs are allowed in SaaS deployments (for internal service calls), while metadata ranges (`169.254/16`), CGNAT, loopback, and link-local remain blocked in every mode. IPv6 ULA (`fd00::/8`) handling is also now correct. (`molecule-core` [#1692](https://github.com/Molecule-AI/molecule-core/pull/1692))
|
||||
- **PUT `/workspaces/:id/files/*path` on SaaS (EC2) workspaces**: fixed a 500 error (`docker not available`) that occurred when saving files from Canvas on SaaS workspaces. The handler now detects non-Docker workspaces via `workspaces.instance_id` and routes writes via EC2 Instance Connect (SSH-backed write with an ephemeral key pair) instead of trying to `docker cp`. (`molecule-core` [#1702](https://github.com/Molecule-AI/molecule-core/pull/1702))
|
||||
- **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/pulls/1692))
|
||||
- **PUT `/workspaces/:id/files/*path` on SaaS (EC2) workspaces**: fixed a 500 error (`docker not available`) that occurred when saving files from Canvas on SaaS workspaces. The handler now detects non-Docker workspaces via `workspaces.instance_id` and routes writes via EC2 Instance Connect (SSH-backed write with an ephemeral key pair) instead of trying to `docker cp`. (`molecule-core` [#1702](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1702))
|
||||
|
||||
### 📚 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))
|
||||
- **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))
|
||||
- **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/pulls/79))
|
||||
- **MCP server structured logging**: `LOG_LEVEL` env var, pino JSON output with AsyncLocalStorage context on every tool call. (`docs` [#78](https://git.moleculesai.app/molecule-ai/docs/pulls/78))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
- SaaS Federation v2 tutorial published — clean rewrite of #1613, now with correct HTTP status codes, fleet metrics endpoint, and security model table (`molecule-core` [#1700](https://github.com/Molecule-AI/molecule-core/pull/1700)); Files API SSH-backed write path for SaaS EC2 workspaces — fixes 500 on PUT `/workspaces/:id/files/*path` for SaaS users (`molecule-core` [#1702](https://github.com/Molecule-AI/molecule-core/pull/1702)); Canvas create-workspace dialog now requires hermes runtime model (`molecule-core` [#1714](https://github.com/Molecule-AI/molecule-core/pull/1714)).
|
||||
- EC2 Instance Connect SSH tutorial published (`molecule-core` [#1617](https://github.com/Molecule-AI/molecule-core/pull/1617)); AI agent org-scoped key credential model blog published (`molecule-core` [#1614](https://github.com/Molecule-AI/molecule-core/pull/1614)); Phase 30 Day 2 social package ready (`molecule-core` [#1662](https://github.com/Molecule-AI/molecule-core/pull/1662)).
|
||||
- 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/pulls/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/pulls/1702)); Canvas create-workspace dialog now requires hermes runtime model (`molecule-core` [#1714](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1714)).
|
||||
- EC2 Instance Connect SSH tutorial published (`molecule-core` [#1617](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1617)); AI agent org-scoped key credential model blog published (`molecule-core` [#1614](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1614)); Phase 30 Day 2 social package ready (`molecule-core` [#1662](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1662)).
|
||||
|
||||
### 🌅 Late-day updates (17:30–23:50 UTC)
|
||||
|
||||
#### 🔒 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.
|
||||
- **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.
|
||||
- **Cross-tenant memory poisoning fix** (`molecule-core` [#1791](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1791)): fixes a bug where `commit_memory` with `scope=TEAM` could write to a sibling workspace's memory store under high concurrency. `commit_memory` now validates `target_workspace_id` against the caller's known peer set before any write.
|
||||
- **CWE-78 shell injection hardening** (`molecule-core` [#1885](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1885)): `shellQuote` now uses `strconv.Quote` for all shell-delimited paths in the EC2 Instance Connect and bastion SSH paths. Defense-in-depth layer hardened; primary protection remains path-validation logic upstream.
|
||||
|
||||
#### ✨ New features
|
||||
|
||||
- **A2A priority queue — Phase 1** (`molecule-core` [#1892](https://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/pulls/1892)): task dispatch now supports a `priority` field (`low` / `normal` / `high` / `urgent`). High/urgent tasks bypass the normal FIFO queue and are dispatched immediately. Phase 2 (priority inversion deadlock prevention) on the roadmap.
|
||||
|
||||
#### 🔧 Fixes
|
||||
|
||||
- **A2A queue nil-safe drain** (`molecule-core` [#1893](https://github.com/Molecule-AI/molecule-core/pull/1893), [#1896](https://github.com/Molecule-AI/molecule-core/pull/1896)): `DequeueTask` no longer panics when the in-memory queue map is uninitialized — graceful empty-result returned instead.
|
||||
- **Workspaces stuck in `provisioning` after失败** (`molecule-core` [#1794](https://github.com/Molecule-AI/molecule-core/pull/1794)): provisioner now transitions workspaces to `failed` state with a descriptive error message instead of leaving them orphaned in `provisioning`.
|
||||
- **Dedup settings hooks double-fire** (`molecule-core` [#1797](https://github.com/Molecule-AI/molecule-core/pull/1797)): the `dedup_settings_hooks` registry now correctly unsubscribes after one fire — eliminates the 3–4× duplicate hook execution observed in CI.
|
||||
- **Semantic memory search returning stale results** (`molecule-core` [#1778](https://github.com/Molecule-AI/molecule-core/pull/1778)): pgvector index now refreshes synchronously on `commit_memory` write instead of on a 5-minute background cycle.
|
||||
- **pgvector migration race in E2E CI** (`molecule-core` [#1777](https://github.com/Molecule-AI/molecule-core/pull/1777)): `CREATE EXTENSION` wrapped in `IF NOT EXISTS` inside a `DO` block — eliminates E2E CI flakiness on fresh DB spin-up.
|
||||
- **EC2 Instance Connect endpoint not found in us-west-2** (`molecule-core` [#1779](https://github.com/Molecule-AI/molecule-core/pull/1779)): Instance Connect endpoint SDK call now falls back gracefully to direct SSM session when the EIC endpoint is unavailable in a region.
|
||||
- **Canvas topology overlay edge labels clipped** (`molecule-core` [#1802](https://github.com/Molecule-AI/molecule-core/pull/1802)): SVG edge labels now respect viewport bounds; labels that would render off-screen are repositioned.
|
||||
- **Audit trail panel not loading for large workspaces** (`molecule-core` [#1854](https://github.com/Molecule-AI/molecule-core/pull/1854)): audit log fetch now uses cursor-based pagination (100 events per page) instead of returning all events at once.
|
||||
- **Hermes `response_format` not forwarded to MiniMax** (`molecule-core` [#1861](https://github.com/Molecule-AI/molecule-core/pull/1861)): `response_format=json_schema` now propagates through the model config passthrough for hermes/MiniMax-M2.7-highspeed workspaces.
|
||||
- **Memory Inspector panel memory leak** (`molecule-core` [#1871](https://github.com/Molecule-AI/molecule-core/pull/1871)): `useMemoryStore` hook now correctly cancels the SSE subscription on panel unmount.
|
||||
- **Token revocation cache stale-read window** (`molecule-core` [#1888](https://github.com/Molecule-AI/molecule-core/pull/1888)): revoked-token invalidation now propagates within 5 s (down from 60 s) — closes the window where a revoked token could still authenticate.
|
||||
- **TenantGuard same-origin bypass (regression)** (`molecule-core` [#1898](https://github.com/Molecule-AI/molecule-core/pull/1898)): fixes a regression introduced in the Phase 33 cloudflare-removal change that re-opened the TenantGuard same-origin bypass for EC2 tenant Canvas deployments.
|
||||
- **A2A queue nil-safe drain** (`molecule-core` [#1893](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1893), [#1896](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1896)): `DequeueTask` no longer panics when the in-memory queue map is uninitialized — graceful empty-result returned instead.
|
||||
- **Workspaces stuck in `provisioning` after失败** (`molecule-core` [#1794](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1794)): provisioner now transitions workspaces to `failed` state with a descriptive error message instead of leaving them orphaned in `provisioning`.
|
||||
- **Dedup settings hooks double-fire** (`molecule-core` [#1797](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1797)): the `dedup_settings_hooks` registry now correctly unsubscribes after one fire — eliminates the 3–4× duplicate hook execution observed in CI.
|
||||
- **Semantic memory search returning stale results** (`molecule-core` [#1778](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1778)): pgvector index now refreshes synchronously on `commit_memory` write instead of on a 5-minute background cycle.
|
||||
- **pgvector migration race in E2E CI** (`molecule-core` [#1777](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1777)): `CREATE EXTENSION` wrapped in `IF NOT EXISTS` inside a `DO` block — eliminates E2E CI flakiness on fresh DB spin-up.
|
||||
- **EC2 Instance Connect endpoint not found in us-west-2** (`molecule-core` [#1779](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1779)): Instance Connect endpoint SDK call now falls back gracefully to direct SSM session when the EIC endpoint is unavailable in a region.
|
||||
- **Canvas topology overlay edge labels clipped** (`molecule-core` [#1802](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1802)): SVG edge labels now respect viewport bounds; labels that would render off-screen are repositioned.
|
||||
- **Audit trail panel not loading for large workspaces** (`molecule-core` [#1854](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1854)): audit log fetch now uses cursor-based pagination (100 events per page) instead of returning all events at once.
|
||||
- **Hermes `response_format` not forwarded to MiniMax** (`molecule-core` [#1861](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1861)): `response_format=json_schema` now propagates through the model config passthrough for hermes/MiniMax-M2.7-highspeed workspaces.
|
||||
- **Memory Inspector panel memory leak** (`molecule-core` [#1871](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1871)): `useMemoryStore` hook now correctly cancels the SSE subscription on panel unmount.
|
||||
- **Token revocation cache stale-read window** (`molecule-core` [#1888](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1888)): revoked-token invalidation now propagates within 5 s (down from 60 s) — closes the window where a revoked token could still authenticate.
|
||||
- **TenantGuard same-origin bypass (regression)** (`molecule-core` [#1898](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1898)): fixes a regression introduced in the Phase 33 cloudflare-removal change that re-opened the TenantGuard same-origin bypass for EC2 tenant Canvas deployments.
|
||||
|
||||
#### 📚 Docs
|
||||
|
||||
- **Chrome DevTools MCP tutorial** (`docs` [#1798](https://github.com/Molecule-AI/docs/pull/1798)): hands-on guide for debugging Molecule AI agents in-browser using Chrome's built-in MCP inspector.
|
||||
- **Phase 34 launch page** (`docs` [#1799](https://github.com/Molecule-AI/docs/pull/1799)): public-facing launch collateral for GA scheduled 2026-04-30.
|
||||
- **Tool Trace demo environment** (`docs` [#1844](https://github.com/Molecule-AI/docs/pull/1844)): interactive demo showing the tool trace inspector in action, with sample run data.
|
||||
- **Enterprise battlecard** (`docs` [#1864](https://github.com/Molecule-AI/docs/pull/1864)): competitive positioning doc for sales and enterprise evaluation teams.
|
||||
- **Chrome DevTools MCP tutorial** (`docs` [#1798](https://git.moleculesai.app/molecule-ai/docs/pulls/1798)): hands-on guide for debugging Molecule AI agents in-browser using Chrome's built-in MCP inspector.
|
||||
- **Phase 34 launch page** (`docs` [#1799](https://git.moleculesai.app/molecule-ai/docs/pulls/1799)): public-facing launch collateral for GA scheduled 2026-04-30.
|
||||
- **Tool Trace demo environment** (`docs` [#1844](https://git.moleculesai.app/molecule-ai/docs/pulls/1844)): interactive demo showing the tool trace inspector in action, with sample run data.
|
||||
- **Enterprise battlecard** (`docs` [#1864](https://git.moleculesai.app/molecule-ai/docs/pulls/1864)): competitive positioning doc for sales and enterprise evaluation teams.
|
||||
|
||||
#### 🧹 Internal
|
||||
|
||||
- `a2a-sdk` hot-pinned to `0.3.x` across all workspace template repos (`molecule-core` [#1890](https://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/pulls/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`).
|
||||
|
||||
#### 🔧 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.
|
||||
- **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.
|
||||
- **Heartbeat 401 retry** (`molecule-ai-workspace-runtime` [#40](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/40)): heartbeat worker now retries with fresh token on 401 before declaring the workspace unreachable — eliminates false `disconnected` status during token rotation.
|
||||
- **LLM token auto-detect** (`molecule-ai-workspace-runtime` [#38](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/5))
|
||||
- **RemoteAgentClient `org_id` and `origin` kwargs**: `RemoteAgentClient` now accepts `org_id` (injected as `X-Molecule-Org-Id` header) and `origin` (injected as `Origin` header for request tracing) as constructor kwargs. Both propagate to all 14+ outbound call sites automatically via `_auth_headers()`. (`molecule-sdk-python` [#7](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/7))
|
||||
- **RemoteAgentClient `fetch_inbound()` filter params**: `fetch_inbound()` now accepts `peer_id` (narrow to a specific peer's messages) and `before_ts` (RFC3339 timestamp for cursor-based pagination). Enables agents to selectively consume inbound activity from known siblings. (`molecule-sdk-python` [#6](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/6))
|
||||
- **InboundMessage enrichment fields**: `InboundMessage` now exposes typed `peer_name`, `peer_role`, and `agent_card_url` attributes, surfaced from the platform's peer registry at dispatch time. Previously these were only accessible via the raw channel envelope. (`molecule-sdk-python` [#5](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/5))
|
||||
- **`strip_a2a_boundary()` — OFFSEC-003 trust-boundary SDK helper**: `molecule-sdk-python` now exports `strip_a2a_boundary(text)` to strip `[A2A_RESULT_FROM_PEER]...[/A2A_RESULT_FROM_PEER]` wrappers from peer-generated content. The platform wraps all external-peer responses in these markers so agents know not to re-inject the content as platform-native output. Safe on pre-OFFSEC-003 responses (returns input unchanged when markers absent) and on `None`/empty strings. (`molecule-sdk-python` [#8](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/8))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- **Canvas accessibility — WCAG 2.4.7 focus-visible rings (batch 2)**: `focus-visible` keyboard rings added to 9 customer-facing buttons across molecule-app — SignInButton on the landing page, "Request access" on the waitlist page, "+ New Workspace" CTA and Notifications bell in the app shell, "Try again" on error boundaries, "Sign out" in the header, the "I agree" button on terms-gate, and "Manage keys on canvas" in the API tokens view. ARIA attributes (`aria-current`, `aria-label`, `aria-busy`) also corrected on the billing view PlanCard and portal buttons. All rings use semantic color tokens — no hardcoded hex colors. (`molecule-app` [#5](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/5))
|
||||
- **Canvas accessibility — WCAG 2.4.7 ThemeToggle focus ring**: `focus-visible` keyboard ring added to the three theme-preference radio buttons (Light / System / Dark) in `ThemeToggle`, fixing WCAG 2.4.7 for the theme switcher. (`molecule-app` [#10](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/10))
|
||||
- **Canvas accessibility — WCAG 2.4.7 NotImplementedState focus ring**: `focus-visible` keyboard ring added to the "Track issue #N" link in `NotImplementedState`, completing the WCAG 2.4.7 focus-visible ring coverage across all customer-facing interactive elements. (`molecule-app` [#9](https://git.moleculesai.app/molecule-ai/molecule-app/pulls/9))
|
||||
- **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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/291))
|
||||
- **status.moleculesai.app false "down" reports fixed**: the custom uptime-probe binary correctly writes raw JSONL results but the aggregator step — which renders `history/<slug>.yml` and `history/summary.json` in Upptime format — was not migrated when the probe moved from Upptime to the custom binary post-2026-05-06. The missing aggregator caused `status.moleculesai.app` to show false-positive outages for Canvas and other endpoints. Resolved by adding the probe result aggregator. (`molecule-ai-status` [#10](https://git.moleculesai.app/molecule-ai/molecule-ai-status/pulls/10))
|
||||
|
||||
### 📚 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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/197))
|
||||
- **Canvas controls section corrected**: keyboard accessibility and MiniMap presence now correctly documented. (`molecule-core` [#201](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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/pulls/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)
|
||||
- **Website github.com → Gitea link migration**: `molecules-market` website links updated to point at Gitea. (`landingpage` #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)
|
||||
- **CI lowercase 'molecule-ai/' in cross-repo workflow refs**: cross-repo workflow references now consistently lowercase for Gitea Actions compatibility. (`landingpage` #2)
|
||||
- **Market Purchase button on tier cards**: demo Mock #1 — Purchase button now appears on tier cards in the molecules-market. (`landingpage` #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/pulls/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/pulls/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/pulls/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/pulls/1))
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-22
|
||||
|
||||
### ✨ New features
|
||||
@ -84,7 +289,7 @@ Customer selects `model=minimax/MiniMax-M2.7-highspeed` in Canvas → the model
|
||||
API key now propagate correctly into the runtime environment instead of being dropped
|
||||
on the floor at provisioning time. Works for hermes workspaces in both hosted SaaS
|
||||
and self-hosted EC2 deployments.
|
||||
(`molecule-core` [#1685](https://github.com/Molecule-AI/molecule-core/pull/1685))
|
||||
(`molecule-core` [#1685](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1685))
|
||||
|
||||
#### EC2 Instance Connect Endpoint — one-click shell from Canvas
|
||||
Canvas Terminal tab now uses AWS EC2 Instance Connect Endpoint to open a PTY inside
|
||||
@ -92,7 +297,7 @@ any workspace EC2 instance — no SSH keys to manage, no IP to copy, no security
|
||||
rules to configure. IAM policy gates access, STS pushes a short-lived key that
|
||||
auto-expires, and every tunnel open is recorded in CloudTrail.
|
||||
See the [EC2 Instance Connect guide](/docs/infra/workspace-terminal).
|
||||
(`molecule-core` [#1554](https://github.com/Molecule-AI/molecule-core/pull/1554))
|
||||
(`molecule-core` [#1554](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1554))
|
||||
|
||||
#### Phase 33 — Cloudflare Tunnel replaced with direct-connect public IPs
|
||||
Cloud-hosted workspaces no longer route through `cloudflared`. Each workspace gets
|
||||
@ -101,32 +306,32 @@ TLS on port 443. Reduces latency by ~20–40 ms (region-dependent), removes the
|
||||
Cloudflare egress cost dependency, and enables direct `curl` debugging without
|
||||
the tunnel path.
|
||||
See the [migration blog post](/blog/cloudflare-tunnel-migration).
|
||||
(`molecule-core` [#1612](https://github.com/Molecule-AI/molecule-core/pull/1612))
|
||||
(`molecule-core` [#1612](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1612))
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **F1085 deleteViaEphemeral**: `rm` scope restricted to `/configs` volume only —
|
||||
prevents deletion of application code or workspace files if the exec form is
|
||||
exploited. Applied to both `main` and `staging`. (`molecule-core` [#1682](https://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/pulls/1682), [#1616](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1616))
|
||||
|
||||
### 🔧 Fixes
|
||||
|
||||
- Canvas now fetches the runtime and model dropdown from the `/templates` registry
|
||||
at load time — runtime list stays current without code deploys. (`molecule-core` [#1666](https://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/pulls/1666))
|
||||
- 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/pulls/1594))
|
||||
- Provisioner pulls workspace template images from GHCR instead of Docker Hub
|
||||
for faster cold starts and reduced third-party dependency. (`molecule-core` [#1624](https://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/pulls/1624))
|
||||
- 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/pulls/37))
|
||||
|
||||
### 📚 Docs
|
||||
|
||||
- **MCP server structured logging**: `LOG_LEVEL` env var (`trace`/`debug`/`info`/`warn`/`error`/`fatal`),
|
||||
pino JSON output in production, pretty-print in development, AsyncLocalStorage
|
||||
context on every log entry (tool name, request ID, workspace ID). (`docs` [#78](https://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/pulls/78))
|
||||
- **molecli shell completion**: tab completion for `molecule` CLI in bash, zsh, fish,
|
||||
and PowerShell — covers all subcommands and flags. (`docs` [#79](https://github.com/Molecule-AI/docs/pull/79))
|
||||
and PowerShell — covers all subcommands and flags. (`docs` [#79](https://git.moleculesai.app/molecule-ai/docs/pulls/79))
|
||||
|
||||
### 🧹 Internal
|
||||
|
||||
|
||||
@ -3,11 +3,24 @@ title: External Agents
|
||||
description: Register agents running outside the platform's Docker network as first-class workspaces on the canvas.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
External agents are AI agents running on your own infrastructure — a different
|
||||
cloud, an edge device, or your laptop — that join the Molecule AI canvas as
|
||||
first-class workspaces. They communicate with other agents via A2A, appear on
|
||||
the canvas with a purple **REMOTE** badge, and are managed like any other workspace.
|
||||
|
||||
<Callout type="info">
|
||||
**Using an MCP-aware agent runtime** (Claude Code, Hermes, OpenCode, Cursor,
|
||||
Cline, etc.)? The universal `molecule-mcp` wheel handles registration,
|
||||
heartbeat, inbox polling, and A2A routing automatically — no manual HTTP
|
||||
server required. See [Bring Your Own Runtime (MCP)](/docs/runtime-mcp).
|
||||
|
||||
This page covers the **manual A2A path** — bring-your-own HTTP server,
|
||||
register and heartbeat by hand. Use it when your agent can't run an MCP
|
||||
stdio server.
|
||||
</Callout>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A running Molecule AI platform (default `http://localhost:8080`)
|
||||
@ -231,6 +244,24 @@ create (POST /workspaces) → online (register) → offline (heartbeat expires)
|
||||
- No auto-restart (agent manages its own process)
|
||||
- Paused external workspaces skip heartbeat monitoring
|
||||
|
||||
### `removed` status — 410 Gone (#2429)
|
||||
|
||||
Once a workspace transitions to `removed`:
|
||||
|
||||
- **Tokens are revoked immediately**, but the row stays in the DB for audit-trail purposes
|
||||
- `GET /workspaces/:id` returns **410 Gone** with `{error: "workspace removed", id, removed_at, hint}` so callers fail fast at startup instead of after ~60s of heartbeat 401s
|
||||
- `DELETE /workspaces/:id` is the canonical removal trigger
|
||||
- Audit / admin tooling that intentionally wants the legacy 200 + body shape opts in via `GET /workspaces/:id?include_removed=true`
|
||||
|
||||
| Caller behavior on a removed workspace | What you should see |
|
||||
|---|---|
|
||||
| Wheel heartbeat | Logs ERROR after 3 consecutive 401s with re-onboard text |
|
||||
| `get_workspace_info` MCP tool | Returns `{"error": "removed", "id", "removed_at", "hint"}` |
|
||||
| Channel bridge `getWorkspaceInfo` | Throws `Workspace <id> was deleted on the platform...` |
|
||||
| Direct `curl /workspaces/:id` | 410 Gone with the same body shape |
|
||||
|
||||
A removed workspace can't be brought back — regenerate a new workspace + token from the canvas **Tokens** tab.
|
||||
|
||||
## Security
|
||||
|
||||
- Bearer token required on all authenticated endpoints
|
||||
|
||||
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. |
|
||||
| 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. |
|
||||
| Getting 404 when POSTing to tenant | Add `X-Molecule-Org-Id` header. The tenant's security layer 404s unmatched origin requests by design. |
|
||||
|
||||
@ -220,7 +220,7 @@ Push mode (this guide) works today but requires an inbound-reachable URL — whi
|
||||
|
||||
Your agent makes only outbound HTTPS calls to the platform, pulling messages from an inbox queue and posting replies back. Works behind any NAT/firewall, tolerates offline laptops, no tunnel needed.
|
||||
|
||||
See the [design doc](https://github.com/Molecule-AI/internal/blob/main/product/external-workspaces-polling.md) (internal) and [implementation tracking issue](https://github.com/Molecule-AI/molecule-core/issues?q=polling+mode) once opened.
|
||||
See the [design doc](https://git.moleculesai.app/molecule-ai/internal/src/branch/main/product/external-workspaces-polling.md) (internal) and the implementation tracking issue (search `polling+mode` on the [molecule-core issue tracker](https://git.moleculesai.app/molecule-ai/molecule-core/issues)).
|
||||
|
||||
---
|
||||
|
||||
@ -260,11 +260,11 @@ If all four pass and canvas still shows your agent as unreachable, see the [remo
|
||||
## Feedback
|
||||
|
||||
This is a new path. Tell us what broke:
|
||||
- Open an issue: https://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
|
||||
|
||||
---
|
||||
|
||||
*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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -90,4 +90,4 @@ molecule completion [bash|zsh|fish|powershell]
|
||||
- `fish` — Fish shell completions (~/.config/fish/completions)
|
||||
- `powershell` — PowerShell completions ($PROFILE)
|
||||
|
||||
See [`molecule-cli` PR #5](https://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.
|
||||
|
||||
@ -110,7 +110,9 @@ import os, logging
|
||||
client = RemoteAgentClient(
|
||||
workspace_id = os.environ["WORKSPACE_ID"],
|
||||
platform_url = os.environ["PLATFORM_URL"],
|
||||
agent_card = {"name": "researcher", "skills": ["web-search", "research"]},
|
||||
org_id = os.environ["ORG_ID"], # optional — injected as X-Molecule-Org-Id header
|
||||
origin = "my-agent/1.0", # optional — injected as Origin header for tracing
|
||||
agent_card = {"name": "researcher", "skills": ["web-search", "research"]},
|
||||
)
|
||||
client.register() # Phase 30.1 — get + cache token
|
||||
secrets = client.pull_secrets() # Phase 30.2 — decrypt API keys
|
||||
@ -130,6 +132,84 @@ The agent appears on the canvas with a **purple REMOTE badge** within seconds. F
|
||||
|
||||
---
|
||||
|
||||
## RemoteAgentClient API Reference
|
||||
|
||||
### Constructor
|
||||
|
||||
```python
|
||||
from molecule_agent import RemoteAgentClient
|
||||
|
||||
client = RemoteAgentClient(
|
||||
workspace_id = "ws-...", # required — your workspace UUID
|
||||
platform_url = "https://...", # required — your platform base URL
|
||||
auth_token = "...", # optional — set to skip the register() step if you already have a token
|
||||
org_id = "org-...", # optional — injected as X-Molecule-Org-Id on every request
|
||||
origin = "my-agent/1.0", # optional — injected as Origin header for request tracing
|
||||
agent_card = {...}, # optional — updated on every heartbeat
|
||||
)
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `workspace_id` | `str` | Your workspace UUID, obtained from `POST /workspaces`. |
|
||||
| `platform_url` | `str` | Your platform base URL, e.g. `https://acme.moleculesai.app`. |
|
||||
| `auth_token` | `str` | Pre-obtained bearer token. If omitted, call `register()` to fetch one. |
|
||||
| `org_id` | `str` | Optional org UUID. When set, injected as `X-Molecule-Org-Id` on every outbound request. |
|
||||
| `origin` | `str` | Optional UA string (e.g. `"researcher/2.1"`). Injected as `Origin` header for logging/tracing. |
|
||||
| `agent_card` | `dict` | Agent metadata broadcast to the canvas. Updated on every heartbeat. |
|
||||
|
||||
### fetch_inbound(peer_id=, before_ts=)
|
||||
|
||||
Poll for inbound A2A messages directed at this workspace:
|
||||
|
||||
```python
|
||||
messages = client.fetch_inbound(peer_id="ws-peer-uuid", before_ts="2026-05-10T12:00:00Z")
|
||||
for msg in messages:
|
||||
print(msg.id, msg.method, msg.peer_name, msg.peer_role)
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `peer_id` | `str` | Filter to messages from a specific peer workspace UUID. Omit to receive from all peers. |
|
||||
| `before_ts` | `str` | RFC3339 timestamp. Return only messages older than this cut-off. Use for pagination by tracking the oldest message seen. |
|
||||
|
||||
Returns a list of `InboundMessage` objects.
|
||||
|
||||
### InboundMessage
|
||||
|
||||
Each inbound message carries these fields in addition to the standard A2A fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `id` | `str` | Message ID. |
|
||||
| `method` | `str` | A2A method (`delegate_task`, `cancel_task`, etc.). |
|
||||
| `params` | `dict` | Method parameters. |
|
||||
| `peer_id` | `str` | UUID of the peer workspace that sent this message. |
|
||||
| `peer_name` | `str` | Display name of the sending peer (from its agent card). |
|
||||
| `peer_role` | `str` | Role of the sending peer (`"sre"`, `"frontend"`, etc.). |
|
||||
| `agent_card_url` | `str` | URL of the sending peer's agent card. |
|
||||
| `raw` | `dict` | Raw channel envelope for forward-compatibility. |
|
||||
|
||||
> **Note:** `peer_name`, `peer_role`, and `agent_card_url` are enriched from the platform's peer registry at dispatch time. They are `None` if the sending peer has not registered an agent card.
|
||||
|
||||
### Security: OFFSEC-003 — trust-boundary markers on peer responses
|
||||
|
||||
When a remote workspace receives a `delegate_task` response from an external peer, the platform wraps the peer-generated content in `[A2A_RESULT_FROM_PEER]...[/A2A_RESULT_FROM_PEER]` trust-boundary markers. These markers signal to the agent that the enclosed content originated outside the platform's trust boundary and must not be re-injected as platform-native output.
|
||||
|
||||
Use `strip_a2a_boundary()` to strip the wrappers before processing the content:
|
||||
|
||||
```python
|
||||
from molecule_agent import RemoteAgentClient, strip_a2a_boundary
|
||||
|
||||
# Normalise inbound peer result — safe on pre-OFFSEC-003 responses (returns
|
||||
# input unchanged when markers absent) and on None/empty strings.
|
||||
result = strip_a2a_boundary(msg.params.get("result", ""))
|
||||
```
|
||||
|
||||
This is particularly important when displaying peer results to users or using them as tool inputs — always strip the boundary markers first. See `molecule-core` [#334](https://git.moleculesai.app/molecule-ai/molecule-core/pull/334) for the platform-side implementation.
|
||||
|
||||
---
|
||||
|
||||
## What Phase 30 Covers
|
||||
|
||||
| Phase | What shipped | Endpoint |
|
||||
@ -148,5 +228,5 @@ The agent appears on the canvas with a **purple REMOTE badge** within seconds. F
|
||||
## Next Steps
|
||||
|
||||
- **[External Agent Registration Guide →](/docs/guides/external-agent-registration)** — full endpoint reference, Python + Node.js examples, troubleshooting
|
||||
- **[molecule-sdk-python →](https://github.com/Molecule-AI/molecule-sdk-python)** — SDK source, `RemoteAgentClient` API docs
|
||||
- **[SDK Examples →](https://github.com/Molecule-AI/molecule-sdk-python/tree/main/examples/remote-agent)** — `run.py` demo script, annotated walkthrough
|
||||
- **[molecule-sdk-python →](https://git.moleculesai.app/molecule-ai/molecule-sdk-python)** — SDK source, `RemoteAgentClient` API docs
|
||||
- **[SDK Examples →](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/src/branch/main/examples/remote-agent)** — `run.py` demo script, annotated walkthrough
|
||||
|
||||
@ -65,7 +65,7 @@ molecule skills install arxiv-research --from community
|
||||
|
||||
Community skills are reviewed by the Molecule AI team before being
|
||||
listed. Submit a skill for review by opening a PR against
|
||||
[`molecule-ai/skills`](https://github.com/Molecule-AI/skills).
|
||||
`molecule-ai/skills` (repo location TBD post-2026-05-06 GitHub-org-suspension; check the [internal issue tracker](https://git.moleculesai.app/molecule-ai/internal/issues) for the canonical submission path).
|
||||
|
||||
## Installing via config.yaml
|
||||
|
||||
@ -171,7 +171,7 @@ molecule skills bundle my-custom-skill --output ./org-templates/my-role/
|
||||
```
|
||||
|
||||
**Publishing to the community:** Open a PR against
|
||||
[`molecule-ai/skills`](https://github.com/Molecule-AI/skills) with a
|
||||
`molecule-ai/skills` (repo location TBD post-2026-05-06 GitHub-org-suspension; check the [internal issue tracker](https://git.moleculesai.app/molecule-ai/internal/issues) for the canonical submission path) with a
|
||||
complete skill package. Community skills are reviewed for security and
|
||||
correctness before listing.
|
||||
|
||||
|
||||
@ -339,7 +339,7 @@ If you are routing a Gemini model through a key that triggers the compat shim (e
|
||||
- [Concepts — Workspaces](/docs/concepts#workspaces)
|
||||
- [API Reference — POST /workspaces](/docs/api-reference#post-workspaces)
|
||||
- [Google ADK Runtime](/docs/google-adk) — Gemini-native alternative to Hermes for ADK-first workflows
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://github.com/Molecule-AI/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://github.com/Molecule-AI/molecule-core/pull/267)
|
||||
- Issue [#513](https://github.com/Molecule-AI/molecule-core/issues/513)
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
|
||||
- Issue [#513](https://git.moleculesai.app/molecule-ai/molecule-core/issues/513)
|
||||
|
||||
@ -88,7 +88,7 @@ Commit `d513a0ced549ef2be8903a7b4794256110ba1805` on staging (merged to main via
|
||||
|---|------------|-------|--------|
|
||||
| 1 | ANTHROPIC_AUTH_TOKEN | `sk-cp-lHt-QFSyZwZxeo...KVw` | ⚠️ Revoked or inactive (404 on API call) |
|
||||
| 2 | GITHUB_TOKEN | `github_pat_11BPRRWQI0m...hsIJLIL` | ✅ Revoked (confirmed 401) |
|
||||
| 3 | ADMIN_TOKEN | `HlgeMb8LjQLXg/B4y8hYzhbCQlg5LNu0oEa4IjShARE=` | Needs confirmation — treated as active until proven otherwise |
|
||||
| 3 | ADMIN_TOKEN | `HlgeMb8...ShARE=` | Needs confirmation — treated as active until proven otherwise |
|
||||
|
||||
### Resolution
|
||||
|
||||
@ -104,11 +104,13 @@ The commit itself fixed the problem by replacing hardcoded defaults with env-var
|
||||
|
||||
### Credentials Exposed
|
||||
|
||||
| # | Credential | Value (redacted reference) | Service |
|
||||
|---|------------|------------------------------|---------|
|
||||
| 1 | ANTHROPIC_AUTH_TOKEN | `sk-cp-lHt-QFSyZwZxeo_fMbmLUX3VgHOwbKGMXUZb6PS2U15D3fqjDB2qPh1OVEzvfvWs9CgcrUpyU7C682uVT_8GBy9RFLaFzBcdLkKdVcPX4yj9UaXNTH82KVw` | MiniMax API (api.minimax.io/anthropic) |
|
||||
| 2 | GITHUB_TOKEN | `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` | GitHub (fine-grained PAT, scope unknown) |
|
||||
| 3 | ADMIN_TOKEN | `HlgeMb8LjQLXg/B4y8hYzhbCQlg5LNu0oEa4IjShARE=` | Platform admin authentication |
|
||||
> **Token values redacted from this table 2026-04-26** to reduce public-search surface (the docs repo is publicly indexed). Short-suffix references match the convention in the Blast Radius table below (lines 134-137). Full values remain in `molecule-core` git history per the F1088 closure decision (no BFG scrub).
|
||||
|
||||
| # | Credential | Value (short suffix) | Service |
|
||||
|---|------------|----------------------|---------|
|
||||
| 1 | ANTHROPIC_AUTH_TOKEN | `sk-cp-...KVw` | MiniMax API (api.minimax.io/anthropic) |
|
||||
| 2 | GITHUB_TOKEN | `github_pat_...hsIJLIL` | GitHub (fine-grained PAT, scope unknown) |
|
||||
| 3 | ADMIN_TOKEN | `HlgeMb8...ShARE=` | Platform admin authentication |
|
||||
|
||||
### Affected Files
|
||||
|
||||
@ -153,21 +155,24 @@ The commit itself fixed the problem by replacing hardcoded defaults with env-var
|
||||
|
||||
**Step 1 — Create credentials manifest (`creds.txt`) [NOT NEEDED]:**
|
||||
```
|
||||
HlgeMb8LjQLXg/B4y8hYzhbCQlg5LNu0oEa4IjShARE=
|
||||
sk-cp-lHt-QFSyZwZxeo_fMbmLUX3VgHOwbKGMXUZb6PS2U15D3fqjDB2qPh1OVEzvfvWs9CgcrUpyU7C682uVT_8GBy9RFLaFzBcdLkKdVcPX4yj9UaXNTH82KVw
|
||||
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
|
||||
<ADMIN_TOKEN value>
|
||||
<MiniMax sk-cp-... value>
|
||||
<GitHub fine-grained PAT value>
|
||||
```
|
||||
Full token values redacted from this doc 2026-04-26 (see note in the
|
||||
Credentials Exposed table above). Pull from the Core-Security incident
|
||||
ticket if a future revival of this BFG procedure is needed.
|
||||
|
||||
**Step 2 — Clean origin/main:**
|
||||
```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
|
||||
cd /tmp/molecule-main-mirror && git push --mirror
|
||||
```
|
||||
|
||||
**Step 3 — Clean origin/staging:**
|
||||
```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
|
||||
cd /tmp/molecule-staging-mirror && git push --mirror
|
||||
```
|
||||
@ -579,7 +584,7 @@ Core-BE — delegated to Dev Lead (A2A failed). Core-BE sub-team: please pick up
|
||||
|
||||
### 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
"plugins",
|
||||
"channels",
|
||||
"schedules",
|
||||
"runtime-mcp",
|
||||
"external-agents",
|
||||
"tokens",
|
||||
"api-reference",
|
||||
|
||||
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`](https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/workspace/main.py)
|
||||
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/src/branch/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`](https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/workspace/tests/conftest.py)
|
||||
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`](https://git.moleculesai.app/molecule-ai/molecule-core/src/branch/main/workspace/main.py));
|
||||
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) — closed without merge; PR content is historical
|
||||
- PR #48 (feat(a2a): dual-compat for a2a-sdk 0.3.x and 1.x) — closed without merge; PR content is historical
|
||||
- [Bring Your Own Runtime (MCP)](/docs/runtime-mcp) — universal wheel install path
|
||||
- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes
|
||||
@ -163,11 +163,11 @@ not expose.
|
||||
| `molecule-skill-update-docs` | `[claude_code]` | `[claude_code, hermes]` |
|
||||
|
||||
Companion PRs:
|
||||
- [molecule-ai-plugin-ecc#2](https://github.com/Molecule-AI/molecule-ai-plugin-ecc/pull/2)
|
||||
- [molecule-ai-plugin-superpowers#2](https://github.com/Molecule-AI/molecule-ai-plugin-superpowers/pull/2)
|
||||
- [molecule-ai-plugin-molecule-dev#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-dev/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-cron-learnings#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-skill-cron-learnings/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-update-docs#2](https://github.com/Molecule-AI/molecule-ai-plugin-molecule-skill-update-docs/pull/2)
|
||||
- [molecule-ai-plugin-ecc#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-ecc/pull/2)
|
||||
- [molecule-ai-plugin-superpowers#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-superpowers/pull/2)
|
||||
- [molecule-ai-plugin-molecule-dev#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-dev/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-cron-learnings#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-skill-cron-learnings/pull/2)
|
||||
- [molecule-ai-plugin-molecule-skill-update-docs#2](https://git.moleculesai.app/molecule-ai/molecule-ai-plugin-molecule-skill-update-docs/pull/2)
|
||||
|
||||
Security note: Security Auditor was offline at time of change. Self-assessed
|
||||
as non-security-impacting — adding `hermes` to a string list in `plugin.yaml`
|
||||
|
||||
@ -11,7 +11,7 @@ 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
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
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 [Gitea repo](https://git.moleculesai.app/molecule-ai/molecule-core) for self-hosting options, or visit [moleculesai.app](https://moleculesai.app) for the hosted platform.
|
||||
|
||||
388
content/docs/runtime-mcp.mdx
Normal file
388
content/docs/runtime-mcp.mdx
Normal file
@ -0,0 +1,388 @@
|
||||
---
|
||||
title: Bring Your Own Runtime (MCP)
|
||||
description: Add Claude Code, Hermes, OpenCode, Cursor, or any MCP-aware agent to the Molecule canvas as a first-class workspace using the universal molecule-mcp wheel.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
The universal `molecule-mcp` wheel lets any MCP-aware agent runtime
|
||||
join the Molecule canvas as a first-class external workspace. The wheel
|
||||
runs locally inside your runtime's MCP server slot, registers + heartbeats
|
||||
to the platform, and exposes the same tool surface that in-container
|
||||
workspaces have — `delegate_task`, `list_peers`, `wait_for_message`,
|
||||
`send_message_to_user`, and friends.
|
||||
|
||||
Same install path works for Claude Code, hermes-agent, OpenCode, Cursor,
|
||||
Cline, and any other runtime that speaks MCP stdio.
|
||||
|
||||
```
|
||||
Your local runtime ──stdio──> molecule-mcp (wheel) ──HTTPS──> Molecule platform
|
||||
(Claude Code, hermes, (canvas, peers,
|
||||
opencode, cursor...) A2A proxy)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Molecule platform you can reach (SaaS at `https://<your-tenant>.moleculesai.app` or a self-hosted instance)
|
||||
- A workspace ID + token from the canvas → **Tokens** tab
|
||||
- Python 3.11+ to install the wheel
|
||||
- An MCP-aware agent runtime
|
||||
|
||||
## Step 1 — Install the wheel
|
||||
|
||||
```bash
|
||||
pip install --user molecule-ai-workspace-runtime
|
||||
```
|
||||
|
||||
This installs the `molecule-mcp` console script. By default it lands at
|
||||
`~/Library/Python/3.x/bin/molecule-mcp` on macOS or `~/.local/bin/molecule-mcp`
|
||||
on Linux. Add that directory to your `PATH` or use the full path in your
|
||||
runtime's MCP config.
|
||||
|
||||
```bash
|
||||
which molecule-mcp
|
||||
# /Users/you/Library/Python/3.13/bin/molecule-mcp
|
||||
```
|
||||
|
||||
## Step 2 — Add it to your runtime
|
||||
|
||||
Pick the snippet for your runtime. The contract is the same in all of
|
||||
them: spawn `molecule-mcp` as an MCP stdio server with three env vars
|
||||
set.
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
claude mcp add molecule -s user -- env \
|
||||
WORKSPACE_ID=<your-workspace-uuid> \
|
||||
PLATFORM_URL=https://<your-tenant>.moleculesai.app \
|
||||
MOLECULE_WORKSPACE_TOKEN=<your-token> \
|
||||
molecule-mcp
|
||||
```
|
||||
|
||||
Reconnect with `/mcp` (or restart the Claude Code session) and the tools
|
||||
appear in the next turn.
|
||||
|
||||
### Hermes Agent
|
||||
|
||||
```bash
|
||||
hermes mcp add molecule \
|
||||
--command molecule-mcp \
|
||||
--env WORKSPACE_ID=<your-workspace-uuid> \
|
||||
--env PLATFORM_URL=https://<your-tenant>.moleculesai.app \
|
||||
--env MOLECULE_WORKSPACE_TOKEN=<your-token>
|
||||
```
|
||||
|
||||
Or hot-reload an existing session with `/reload-mcp`.
|
||||
|
||||
### OpenCode / generic MCP config (stdio)
|
||||
|
||||
For runtimes that read a JSON MCP config (`.mcp.json`, `mcp_servers.yaml`,
|
||||
or similar):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"molecule": {
|
||||
"command": "molecule-mcp",
|
||||
"env": {
|
||||
"WORKSPACE_ID": "<your-workspace-uuid>",
|
||||
"PLATFORM_URL": "https://<your-tenant>.moleculesai.app",
|
||||
"MOLECULE_WORKSPACE_TOKEN": "<your-token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cursor / Cline / other MCP clients
|
||||
|
||||
Most MCP clients accept the same `command` + `env` shape as the JSON
|
||||
example above. Drop it into your client's MCP settings file
|
||||
(typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for
|
||||
Cline) and restart the client.
|
||||
|
||||
## Environment variables
|
||||
|
||||
The following env vars are supported by the `molecule-mcp` wheel in addition to the
|
||||
required trio (`WORKSPACE_ID`, `PLATFORM_URL`, `MOLECULE_WORKSPACE_TOKEN`):
|
||||
|
||||
| Env var | What it controls | Default |
|
||||
|---|---|---|
|
||||
| `MOLECULE_MODEL` | **Canonical.** The model ID the workspace runtime uses — e.g. `claude-opus-4-7`, `minimax/MiniMax-M2.7-highspeed` | _(unset — template default)_ |
|
||||
| `MODEL` | **Alias for `MOLECULE_MODEL`.** Accepted for backwards compatibility. | _(unset)_ |
|
||||
| `MODEL_PROVIDER` | **Deprecated.** This var was previously misread as "runtime selector" (`claude-code`, `minimax`, etc.) but carried the model ID, causing the wrong model to be used. Prefer `MOLECULE_MODEL`. | _(unset — emits deprecation warning)_ |
|
||||
| `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` |
|
||||
|
||||
<Callout type="warn">
|
||||
`MODEL_PROVIDER` is deprecated. It was misnamed — despite its name it carried the **model ID** (e.g. `claude-opus-4-7`), not the runtime/provider name. Setting it caused production incidents where the Claude CLI received `--model MODEL_PROVIDER_VALUE` and returned 404s. Use `MOLECULE_MODEL` instead.
|
||||
</Callout>
|
||||
|
||||
## Optional — declare your identity & capabilities
|
||||
|
||||
Three additional env vars control how your workspace appears on the
|
||||
canvas and to peer agents calling `list_peers`:
|
||||
|
||||
| Env var | What it sets | Default |
|
||||
|---|---|---|
|
||||
| `MOLECULE_AGENT_NAME` | Display name on the canvas card | `molecule-mcp-{id[:8]}` |
|
||||
| `MOLECULE_AGENT_DESCRIPTION` | One-line description in Details/Skills tabs | empty |
|
||||
| `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` |
|
||||
|
||||
Skills are surfaced two places:
|
||||
|
||||
1. **Canvas Skills tab** — each skill renders as a chip with the name
|
||||
2. **Peer agents calling `list_peers`** — they see `{name, skills: [...]}` for each peer, so other agents can route delegations to the right specialist instead of guessing from name alone
|
||||
|
||||
Example with all three set:
|
||||
|
||||
```bash
|
||||
claude mcp add molecule -s user -- env \
|
||||
WORKSPACE_ID=<uuid> \
|
||||
PLATFORM_URL=https://<tenant>.moleculesai.app \
|
||||
MOLECULE_WORKSPACE_TOKEN=<token> \
|
||||
MOLECULE_AGENT_NAME='Research Assistant' \
|
||||
MOLECULE_AGENT_DESCRIPTION='Reads, summarises, cites.' \
|
||||
MOLECULE_AGENT_SKILLS=research,summarisation,citations \
|
||||
molecule-mcp
|
||||
```
|
||||
|
||||
A peer agent's `list_peers()` call would then surface this workspace
|
||||
as `Research Assistant — skills: [research, summarisation, citations]`,
|
||||
which it can use to route a research task without first asking "what
|
||||
can you do?".
|
||||
|
||||
## Step 3 — Verify
|
||||
|
||||
After your runtime reconnects, the workspace should flip to **online**
|
||||
on the canvas. From inside your agent:
|
||||
|
||||
```
|
||||
list_peers()
|
||||
```
|
||||
|
||||
You should see your team — siblings, parent, and children — with their
|
||||
status. If the workspace is still offline after ~30s, check
|
||||
[Troubleshooting](#troubleshooting) below.
|
||||
|
||||
## Tools exposed
|
||||
|
||||
| Tool | What it does |
|
||||
|---|---|
|
||||
| `list_peers` | List workspaces this agent can A2A-message |
|
||||
| `get_workspace_info` | Own identity (id, name, role, tier, parent) |
|
||||
| `delegate_task` | Send a task to a peer and wait for the reply |
|
||||
| `delegate_task_async` | Fire-and-forget delegation; result lands in inbox |
|
||||
| `check_task_status` | Poll an async delegation |
|
||||
| `wait_for_message` | Block until the next inbound A2A message arrives |
|
||||
| `inbox_peek` / `inbox_pop` | Inspect / acknowledge queued inbound messages |
|
||||
| `send_message_to_user` | Push a chat bubble to the user's canvas |
|
||||
| `commit_memory` / `recall_memory` | Persistent KV (local / team / global scope) |
|
||||
|
||||
External runtimes can't accept inbound HTTP, so the wheel polls
|
||||
`/activity?type=a2a_receive` in a daemon thread and surfaces messages
|
||||
through `wait_for_message` + `inbox_peek` / `inbox_pop`. Use those
|
||||
instead of waiting for an HTTP webhook — there isn't one.
|
||||
|
||||
### Push-UX for notification-capable hosts
|
||||
|
||||
On top of the polling tools, the wheel emits a JSON-RPC notification
|
||||
(`notifications/claude/channel`) on every new inbound message. Hosts
|
||||
that recognise that method (Claude Code today; any compliant client
|
||||
tomorrow) treat the notification as a conversation interrupt — the
|
||||
message text becomes the next agent turn without the agent having to
|
||||
call `wait_for_message` first.
|
||||
|
||||
Hosts that don't recognise the method silently ignore it, so the same
|
||||
wheel works for both push-capable and poll-only runtimes. There is no
|
||||
config flag to toggle: pollers keep polling, notification-capable hosts
|
||||
get push automatically.
|
||||
|
||||
### MCP spec compliance
|
||||
|
||||
The wheel speaks MCP protocol version **2024-11-05** over stdio
|
||||
JSON-RPC, declaring only the `tools` capability. It implements the
|
||||
standard request methods and nothing client-specific:
|
||||
|
||||
| MCP method | Behavior |
|
||||
|---|---|
|
||||
| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` capability |
|
||||
| `notifications/initialized` | No-op (no response — per spec) |
|
||||
| `tools/list` | Returns all exposed tools in one response (no pagination cursor — surface is small) |
|
||||
| `tools/call` | Dispatches by name, returns `content: [{ type: "text", text: ... }]` |
|
||||
| _(unknown method)_ | Returns JSON-RPC error code `-32601` (Method not found) |
|
||||
|
||||
The push-UX notification (`notifications/claude/channel`) is the only
|
||||
non-standard method emitted, and it's a one-way notification — clients
|
||||
that don't handle it discard it per JSON-RPC semantics. No part of the
|
||||
wheel's tool surface depends on a client recognizing it.
|
||||
|
||||
This means **any spec-compliant MCP client** can drive the wheel:
|
||||
Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else
|
||||
that opens an MCP stdio connection. If your client speaks MCP, it
|
||||
speaks the wheel.
|
||||
|
||||
## HTTP/SSE transport for Hermes workspaces
|
||||
|
||||
Hermes workspaces (which are MCP-native) can connect to the platform MCP
|
||||
server over **HTTP + Server-Sent Events** instead of stdio. This is the
|
||||
recommended path when Hermes runs as a standalone service rather than
|
||||
inside a shell.
|
||||
|
||||
The `a2a_mcp_server.py` in the runtime exposes two endpoints:
|
||||
|
||||
| Endpoint | Method | Purpose |
|
||||
|---|---|---|
|
||||
| `/mcp` | `POST` | Receive JSON-RPC requests |
|
||||
| `/mcp/stream` | `GET` | SSE stream for push-based responses |
|
||||
| `/health` | `GET` | Health check |
|
||||
|
||||
Start the server with the `--transport=http --port=<N>` flags:
|
||||
|
||||
```bash
|
||||
python a2a_mcp_server.py \
|
||||
--transport=http \
|
||||
--port=8080 \
|
||||
--workspace-id=<uuid> \
|
||||
--platform-url=https://<tenant>.moleculesai.app \
|
||||
--workspace-token=<token>
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
The stdio transport (described in [Step 2](#step-2--add-it-to-your-runtime))
|
||||
remains the default. HTTP/SSE is an alternative for Hermes deployments
|
||||
where a long-running daemon process is preferred over a stdio subprocess.
|
||||
</Callout>
|
||||
|
||||
## Heartbeat & lifecycle
|
||||
|
||||
The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every
|
||||
20 seconds. Your runtime stays `online` on the canvas as long as that
|
||||
heartbeat lands.
|
||||
|
||||
If the heartbeat starts returning 401, the wheel logs a clear ERROR
|
||||
after 3 consecutive failures with re-onboard instructions:
|
||||
|
||||
```
|
||||
molecule-mcp: 3 consecutive heartbeat auth failures (HTTP 401) — the
|
||||
token in MOLECULE_WORKSPACE_TOKEN has been REVOKED, likely because
|
||||
workspace <id> was deleted server-side. The MCP server is still running
|
||||
but every platform call will fail. Regenerate the workspace + token
|
||||
from the canvas (Tokens tab), update your MCP config, and restart your
|
||||
runtime.
|
||||
```
|
||||
|
||||
This is the canonical signal that you need to regenerate from the canvas
|
||||
**Tokens** tab. The MCP server keeps running so in-flight tool calls
|
||||
don't crash, but every platform-side operation will fail until you
|
||||
re-onboard.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workspace stays offline after `/mcp` connect
|
||||
|
||||
Most likely the runtime is still using a cached MCP config from session
|
||||
start. Fully exit and relaunch the runtime — `/mcp` reconnect re-reads
|
||||
the running session's in-memory config, not the on-disk file.
|
||||
|
||||
### `molecule-mcp: register rejected with HTTP 401`
|
||||
|
||||
The token in `MOLECULE_WORKSPACE_TOKEN` doesn't match the workspace.
|
||||
Regenerate from the canvas Tokens tab.
|
||||
|
||||
### `Tools unavailable` / `MCP server disconnected`
|
||||
|
||||
Check that `molecule-mcp` resolves on `PATH` from inside the runtime's
|
||||
environment (it may differ from your interactive shell). If unsure, use
|
||||
the full path to the binary in your MCP config:
|
||||
|
||||
```bash
|
||||
which molecule-mcp
|
||||
# Use this absolute path in your config's "command" field
|
||||
```
|
||||
|
||||
### `Origin header is required` in logs
|
||||
|
||||
Don't pass `Origin` manually — the wheel sets it. If you see this, your
|
||||
runtime is calling the platform directly instead of through the MCP
|
||||
tools. Use the tools (`delegate_task`, `send_message_to_user`, etc.)
|
||||
rather than hand-rolling HTTP calls.
|
||||
|
||||
### Tools call returns 401 / `workspace not found` after working before
|
||||
|
||||
The workspace was probably deleted from the canvas (or via DELETE
|
||||
`/workspaces/:id`). Deleting a workspace revokes its token immediately,
|
||||
even if the workspace card still appears with a "removed" badge for a
|
||||
short window. The MCP server itself keeps running, so tool listing
|
||||
still succeeds, but every platform call fails.
|
||||
|
||||
Regenerate from the canvas **Tokens** tab — a deleted workspace can't
|
||||
be brought back; you'll get a new workspace + token pair. Update your
|
||||
MCP config and restart your runtime.
|
||||
|
||||
### `Workspace <id> was deleted on the platform...` from `get_workspace_info`
|
||||
|
||||
Since [#2429](https://git.moleculesai.app/molecule-ai/molecule-core/pull/2449),
|
||||
`GET /workspaces/:id` returns **410 Gone** (not 200 + `status:"removed"`)
|
||||
when the workspace has been deleted. The MCP wheel's `get_workspace_info`
|
||||
tool surfaces this as a tailored error message:
|
||||
|
||||
```
|
||||
Workspace <id> was deleted on the platform at <removed_at>.
|
||||
Regenerate workspace + token from the canvas → Tokens tab.
|
||||
```
|
||||
|
||||
This is the **startup-time** counterpart to the heartbeat-401 escalation
|
||||
above. If you see it within seconds of starting your runtime (rather
|
||||
than after ~60s of heartbeat failures), the workspace was already gone
|
||||
when you connected — regenerate as instructed.
|
||||
|
||||
Audit-trail tools that intentionally want to inspect a removed workspace's
|
||||
metadata (admin dashboards, "show me deleted workspaces" tooling) can
|
||||
opt back into the legacy 200 + body shape via
|
||||
`GET /workspaces/<id>?include_removed=true`.
|
||||
|
||||
### `claude mcp list` shows the new config but tools still 401
|
||||
|
||||
`/mcp` reconnect re-spawns the **cached** MCP config from session
|
||||
start, not the latest on-disk config. After editing `claude mcp add`
|
||||
or your `~/.cursor/mcp.json`, fully exit and relaunch the runtime —
|
||||
not just `/mcp`.
|
||||
|
||||
A quick way to confirm: `ps aux | grep molecule-mcp` and check the
|
||||
PID hasn't changed across `/mcp` reconnects. If the same PID stays
|
||||
alive, the runtime is still using the old config.
|
||||
|
||||
### `command not found: molecule-mcp` from inside the runtime
|
||||
|
||||
The runtime's `PATH` may differ from your interactive shell — common
|
||||
on macOS where `~/Library/Python/3.x/bin` is added to login shells but
|
||||
not to GUI-launched apps. Use the absolute path in your MCP config:
|
||||
|
||||
```bash
|
||||
which molecule-mcp
|
||||
# /Users/you/Library/Python/3.13/bin/molecule-mcp
|
||||
```
|
||||
|
||||
Then point `command` at that absolute path in `claude mcp add` /
|
||||
`.cursor/mcp.json` / `mcp_servers.yaml`.
|
||||
|
||||
## When to use this vs. the manual A2A path
|
||||
|
||||
| Scenario | Use |
|
||||
|---|---|
|
||||
| You're using an MCP-aware agent runtime | This page (universal `molecule-mcp` wheel) |
|
||||
| You're building a custom agent without MCP support | [External Agents](/docs/external-agents) (manual register + heartbeat + HTTP server) |
|
||||
| You want the platform to expose MCP tools your agent connects to (inverse direction) | [MCP Server](/docs/mcp-server) |
|
||||
|
||||
The universal wheel is the recommended path for almost every modern
|
||||
runtime — it handles registration, heartbeat, inbox polling, A2A
|
||||
routing, and Origin/WAF headers for you. The manual path is only
|
||||
needed when you can't run an MCP stdio server inside your agent (rare).
|
||||
|
||||
## See also
|
||||
|
||||
- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes
|
||||
- [Tokens](/docs/tokens) — token management and rotation
|
||||
- [Concepts — Workspaces](/docs/concepts#workspaces)
|
||||
- [API Reference](/docs/api-reference) — raw HTTP endpoints behind the wheel
|
||||
@ -12,7 +12,7 @@ This page documents security fixes shipped in the Molecule AI platform. Each ent
|
||||
## 2026-04-20 — CWE-22: Path Traversal in `copyFilesToContainer`
|
||||
|
||||
**Severity:** High (CWE-22)
|
||||
**PRs:** [#1271](https://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`
|
||||
|
||||
### Vulnerability
|
||||
@ -37,7 +37,7 @@ File writes to workspace containers now validate all paths before writing to the
|
||||
## 2026-04-20 — CWE-78: Shell Injection in `deleteViaEphemeral`
|
||||
|
||||
**Severity:** High (CWE-78)
|
||||
**PR:** [#1310](https://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`
|
||||
|
||||
### Vulnerability
|
||||
@ -69,9 +69,9 @@ Workspace file deletion operations now use safe argument-passing and validate al
|
||||
## 2026-04-21 — CWE-918: SSRF in MCP / A2A Proxy Endpoints (Updated: Regression Fix)
|
||||
|
||||
**Severity:** High (CWE-918)
|
||||
**Original PRs:** [#1274](https://github.com/Molecule-AI/molecule-core/pull/1274), [#1302](https://github.com/Molecule-AI/molecule-core/pull/1302)
|
||||
**Regression Fix PR:** [#1430](https://github.com/Molecule-AI/molecule-core/pull/1430)
|
||||
**Regression introduced by:** [#1363](https://github.com/Molecule-AI/molecule-core/pull/1363)
|
||||
**Original PRs:** [#1274](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1274), [#1302](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1302)
|
||||
**Regression Fix PR:** [#1430](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1430)
|
||||
**Regression introduced by:** [#1363](https://git.moleculesai.app/molecule-ai/molecule-core/pull/1363)
|
||||
**Affected:** `workspace-server/internal/handlers/mcp.go` — `isSafeURL`, `isPrivateOrMetadataIP`; `workspace-server/internal/handlers/a2a_proxy.go`; `workspace-server/internal/handlers/a2a_proxy_helpers.go`
|
||||
|
||||
### Vulnerability
|
||||
@ -105,9 +105,9 @@ In **SaaS mode** (`saasMode()` returns true), cross-EC2 traffic to RFC-1918 addr
|
||||
|
||||
### Regression (2026-04-21)
|
||||
|
||||
PR [#1363](https://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
|
||||
|
||||
@ -118,7 +118,7 @@ Platform outbound requests from workspaces (MCP tool calls, A2A proxy routing) v
|
||||
## 2026-04-21 — Audit Ledger HMAC Chain Guard
|
||||
|
||||
**Severity:** Low (denial-of-service / data integrity)
|
||||
**PRs:** [#1339](https://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`
|
||||
|
||||
### Vulnerability
|
||||
@ -144,7 +144,7 @@ Audit chain verification now handles short or malformed HMAC values gracefully,
|
||||
## 2026-04-21 — Credential Scrub: `err.Error()` Leak Prevention
|
||||
|
||||
**Severity:** Medium (information disclosure)
|
||||
**PRs:** [#1282](https://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`
|
||||
|
||||
### Vulnerability
|
||||
|
||||
@ -17,7 +17,7 @@ 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
|
||||
git clone https://git.moleculesai.app/molecule-ai/molecule-core.git
|
||||
cd molecule-core
|
||||
./scripts/dev-start.sh
|
||||
# Canvas: http://localhost:3000
|
||||
|
||||
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
|
||||
|
||||
- PR #501: [feat(platform): Fly Machines provisioner](https://github.com/Molecule-AI/molecule-core/pull/501)
|
||||
- PR #481: [feat(ci): deploy to Fly after image push](https://github.com/Molecule-AI/molecule-core/pull/481)
|
||||
- PR #501: [feat(platform): Fly Machines provisioner](https://git.moleculesai.app/molecule-ai/molecule-core/pull/501)
|
||||
- PR #481: [feat(ci): deploy to Fly after image push](https://git.moleculesai.app/molecule-ai/molecule-core/pull/481)
|
||||
- [Fly Machines API docs](https://fly.io/docs/machines/api/)
|
||||
- [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
|
||||
|
||||
- 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)
|
||||
- [Workspace runtimes reference](../reference/runtimes.md)
|
||||
|
||||
@ -71,7 +71,7 @@ ADK workspaces participate in the same A2A network as Claude Code, Gemini CLI, H
|
||||
|
||||
## Related
|
||||
|
||||
- PR #550: [feat(adapters): add google-adk runtime adapter](https://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)
|
||||
- [Gemini CLI runtime tutorial](./gemini-cli-runtime.md)
|
||||
- [Platform API reference](../api-reference.md)
|
||||
|
||||
@ -179,9 +179,9 @@ What is on the roadmap for Phase 2d (not yet shipped):
|
||||
|
||||
## Related
|
||||
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://github.com/Molecule-AI/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://github.com/Molecule-AI/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://github.com/Molecule-AI/molecule-core/pull/267)
|
||||
- PR #240: [Phase 2a — native Anthropic dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/240)
|
||||
- PR #255: [Phase 2b — native Gemini dispatch](https://git.moleculesai.app/molecule-ai/molecule-core/pull/255)
|
||||
- PR #267: [Phase 2c — multi-turn history on all paths](https://git.moleculesai.app/molecule-ai/molecule-core/pull/267)
|
||||
- [Hermes adapter design](../adapters/hermes-adapter-design.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
|
||||
|
||||
- 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)
|
||||
- [Channel adapter reference](../api-reference.md#channels)
|
||||
@ -246,4 +246,4 @@ For the API reference, see [`docs/api-reference`](/docs/api-reference) — the `
|
||||
|
||||
*SaaS federation is available for all Molecule AI platform operators. Contact the Molecule AI team to enable federation on your control plane.*
|
||||
|
||||
(`molecule-core` [#1700](https://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
|
||||
|
||||
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`:
|
||||
- `workspace-server/internal/handlers/template_files_eic.go` — EIC write logic
|
||||
|
||||
@ -163,4 +163,4 @@ This header is added automatically by the workspace executor when `task_budget >
|
||||
- [Org Template](/docs/org-template) — deploy effort/task_budget settings across an entire team via `org.yaml`
|
||||
- [Observability](/docs/observability) — monitor token usage per workspace to tune your budget settings
|
||||
- [API Reference — POST /workspaces](/docs/api-reference#post-workspaces)
|
||||
- [Claude Opus 4.7 — Anthropic docs](https://docs.anthropic.com) — upstream reference for `output_config`
|
||||
- [Claude Opus 4.7 — Anthropic docs](https://platform.claude.com/docs/) — upstream reference for `output_config`
|
||||
@ -237,4 +237,4 @@ Once your agent is connected to MCP, it stops being a chatbot with a scrollable
|
||||
|
||||
---
|
||||
|
||||
*Have questions or want to share what you're building with MCP? Open a discussion on [GitHub Discussions](https://github.com/Molecule-AI/molecule-core/discussions) or file an issue with the `enhancement` label.*
|
||||
*Have questions or want to share what you're building with MCP? File an issue with the `enhancement` label on the [molecule-core issue tracker](https://git.moleculesai.app/molecule-ai/molecule-core/issues).*
|
||||
Loading…
Reference in New Issue
Block a user