molecule-code-reviewer 4412badf93
contracts-codegen-drift / codegen-drift (push) Successful in 11s
contracts-codegen-drift / contract-conformance (push) Successful in 19s
Test / skill-framework-cli (push) Successful in 19s
contracts-codegen-drift / native-plugins-registry-conformance (push) Successful in 20s
contracts-codegen-drift / plugin-state-conformance (push) Successful in 20s
contracts-codegen-drift / org-template declares something (push) Successful in 18s
contracts-codegen-drift / prompt-canonical-text-conformance (push) Successful in 20s
contracts-codegen-drift / platform-identity-gate-conformance (push) Successful in 21s
Dispatch molecule-core sdk-pin-bump / Dispatch molecule-core sdk-pin-bump (push) Successful in 11s
contracts-codegen-drift / adapter-registry-conformance (push) Successful in 23s
contracts-codegen-drift / workspace-routes-manifest-conformance (push) Successful in 24s
contracts-codegen-drift / plugin-install-report-conformance (push) Successful in 20s
contracts-codegen-drift / branding-conformance (push) Successful in 22s
contracts-codegen-drift / env-contract-drift (push) Successful in 21s
meta-ci-advisory / meta (advisory) (push) Successful in 5s
contracts-codegen-drift / go-parity (push) Successful in 24s
registry-ssot-drift / registry-ssot-drift (push) Successful in 7s
contracts-codegen-drift / env-contract-conformance (push) Successful in 25s
contracts-codegen-drift / schedule accepts both cron spellings (push) Successful in 26s
contracts-codegen-drift / bindings-parity (push) Successful in 27s
llm-registry-ssot-drift / llm-registry-ssot-drift (push) Successful in 11s
llm-registry-ssot-drift / llm-registry-ssot-conformance (push) Successful in 12s
Test / package-smoke (push) Successful in 34s
contracts-codegen-drift / validate (push) Successful in 34s
registry-ssot-drift / registry-ssot-conformance (push) Successful in 14s
contracts-codegen-drift / all-required (push) Successful in 1s
Test / test (3.12) (push) Successful in 56s
Test / test (3.13) (push) Successful in 1m2s
Test / test (3.11) (push) Successful in 1m6s
Test / all-required (push) Successful in 2s
ci(pin): dispatch molecule-core's sdk-pin-bump when SDK main moves (#211)
2026-08-07 18:40:38 +00:00
2026-08-07 18:09:41 +00:00

molecule-ai-sdk

The Molecule AI SDK and contract SSOT. One repo, three things:

  1. Two Python packages (published together under the distribution name molecule-ai-sdk in Molecule's publicly readable Gitea package registry; publishing remains authenticated, and the package is not on public PyPI):

    • molecule_external_workspace — write an agent that runs outside the platform's Docker network and joins a Molecule AI org from another machine. It registers with the platform, pulls secrets, sends heartbeats, discovers peers (A2A), delegates, and detects pause/delete. Public API: RemoteAgentClient, A2AServer.
    • molecule_plugin — build installable plugin directories (rules, skills in agentskills.io format, per-runtime install adaptors) + validators and the molecule-plugin CLI. (See Plugin authoring.)
  2. contracts/ — the platform contract SSOT. The JSON-Schema (draft 2020-12) IDL for every cross-boundary contract in the platform, plus the cloud-provider YAML SSOT. This is the former molecule-contracts repo, folded in and archived here 2026-07-01. See Contracts.

  3. gen/ — generated bindings. Go / TypeScript / Python types + constants emitted from contracts/ by tools/gen-*.mjs. Consumed by molecule-core and molecule-controlplane (Go, via go.moleculesai.app/sdk/gen/go/...), the storefront (TS), and the runtime (Python). Never hand-edited — a fresh regen must match what's committed (enforced by CI).

molecule-ai-sdk/
├── molecule_external_workspace/   # remote-agent client (RemoteAgentClient / A2AServer)
├── molecule_plugin/               # plugin-authoring SDK + validators + CLI
├── contracts/                     # JSON-Schema IDL SSOT + cloudproviders.yaml
│   ├── mcp/  plugin-manifest/  workspace-template/  org-template/
│   ├── catalog/                   # catalog-entry + marketplace-service (catalog/publish/install/entitlement)
│   ├── provision-request/  promote-request/  workspace-comms/
│   └── cloudproviders.yaml + cloudproviders.schema.json
├── gen/{go,ts,python}/            # generated bindings (DO NOT EDIT)
├── tools/gen-runtimes.mjs + gen-{go,ts,python}.mjs  # Node generators
├── molecule_plugin/templates/     # packaged skill/MCP/channel/trigger scaffolds
└── tests/

SDK release boundary: 0.6.1 is published

sdk-v0.6.1 is tagged and molecule-ai-sdk==0.6.1 is published to the Molecule Gitea registry (2026-08-05). It is the latest published version and the one the install example below pins. Everything documented on this branch — including the native-channel retirement validator described further down — is in that wheel; an install from the registry now does prove the 0.6.0 channel cutover.

0.6.1 is a hotfix over 0.6.0 and carries no breaking change of its own. It fixes the kind: trigger delivery watchdog cancelling every delivery on a workspace that was idle before the fire started: the runtime turn lease is workspace-global, so on a workspace quiet past the 900s idle TTL it was already idle_expired when the delivery began, and the first 30s probe killed it into a permanent cancel/retry loop. A lease is now honoured only when it was armed after the delivery it is being read about. See CHANGELOG.md.

0.6.0 is a breaking release: the native org-template channels retirement, the local/dockerenteros substrate rename, the Tier-1 branding flip, the LLM-registry platform-arm correction, the removal of MOLECULE_TRIGGER_DELIVERY_WATCHDOG_SECONDS, and A2AServer fail-closed inbound auth. Read the Breaking section of CHANGELOG.md before upgrading — the substrate rename in particular has a data precondition (molecule-controlplane migrations 070 and 071) that upgrading the SDK alone does not satisfy.

Contributors validating an unreleased candidate should use a reviewed, exact source commit and install that checkout with python -m pip install -e '.[test]'. Production consumers should stay on an immutable published version.

Install

SDK_DOWNLOAD="$(mktemp -d)"
python -m pip download --no-deps --dest "$SDK_DOWNLOAD" \
  --index-url "https://git.moleculesai.app/api/packages/molecule-ai/pypi/simple" \
  molecule-ai-sdk==0.6.1
python -m pip install --index-url https://pypi.org/simple \
  "$SDK_DOWNLOAD"/molecule_ai_sdk-*.whl

The Gitea registry does not proxy public PyPI. Download only the private SDK wheel from Gitea, then install that local wheel while resolving its declared public dependencies from PyPI. Keeping retrieval in separate commands avoids both a broken sole-index install, dependency drift, and cross-index dependency confusion. Authenticate via ~/.netrc (or pip's keyring) — do not inline credentials in the URL.

Go consumers import the generated bindings via the vanity path (no PyPI needed):

import molcontracts "go.moleculesai.app/sdk/gen/go/molcontracts"

Contracts — the SSOT

Every contract is a pair under its contracts/<domain>/ directory:

  • *.schema.json — the rules (JSON-Schema draft 2020-12): fields, types, required, enum, const pins, patterns. The enforceable definition.
  • *.contract.json — one canonical instance that MUST validate against its sibling schema. It's the worked example, the CI anchor, and — for value-bearing contracts like mcp-plugin-delivery — the SSOT for the concrete values baked into gen/.

Direction: *.contract.json ──validate──▶ *.schema.json, and contracts/ ──codegen (tools/gen-*.mjs)──▶ gen/{go,ts,python}. Each domain directory under contracts/ carries its own README.md describing that surface.

Eight jobs back this under the strict all-required aggregator in .gitea/workflows/contracts-codegen-drift.yml: schema validation, codegen drift, cloud-provider and adapter/prompt/platform conformance, Go parity, and published binding parity. Regenerate locally after any contract edit and commit the result:

node tools/gen-runtimes.mjs
node tools/gen-go.mjs && node tools/gen-ts.mjs && node tools/gen-python.mjs

Remote agents — molecule_external_workspace

from molecule_external_workspace import RemoteAgentClient

client = RemoteAgentClient(
    platform_url="https://<org>.moleculesai.app",
    workspace_id="my-agent",
    # the auth token is minted on first register() and persisted client-side
)

The client wraps the workspace↔platform HTTP contract (register, pull secrets, heartbeat, state-poll, A2A proxy delivery, delegation, plugin install) — the same surface captured in contracts/workspace-comms/. A2AServer is the authenticated receive side for agent-to-agent messages and rejects traffic until registration provides a platform_inbound_secret. Before exposing it publicly, attach it to the client, register, require the returned secret, and only then start the listener.

Plugin authoring — molecule_plugin

A Molecule AI plugin is a directory that bundles rules, skills, and per-runtime install adaptors. Any plugin that conforms to the contract is installable on any Molecule AI workspace whose runtime the plugin supports.

Quick start

Create a validated scaffold directly from the installed wheel:

molecule-plugin init my-plugin --kind skill
cd my-plugin
molecule-plugin validate plugin .

Use --kind mcp for a dependency-free stdio MCP starter, --kind channel for a supervised provider bridge with a vendored, provider-neutral channel API v1 client, or --kind trigger for an autonomous scheduler. Names must be lowercase alphanumeric slugs with single hyphens. The initializer refuses path traversal and never overwrites an existing directory.

my-plugin/
├── .gitea/workflows/ci.yml  # generated repository CI
├── .gitignore
├── README.md
├── plugin.yaml              # name, version, runtimes, description
├── adapters/
│   ├── claude_code.py       # AgentskillsAdaptor entrypoint
│   └── codex.py
├── skills/my-plugin/
│   └── SKILL.md             # instructions injected into the system prompt
└── tests/test_scaffold.py   # generated scaffold smoke test

Validate:

from molecule_plugin import validate_manifest
errors = validate_manifest("my-plugin/plugin.yaml")
assert not errors, errors

CLI

molecule-plugin init my-skill-plugin --kind skill
molecule-plugin init my-mcp-plugin   --kind mcp
molecule-plugin init my-channel      --kind channel
molecule-plugin init my-scheduler    --kind trigger
molecule-plugin validate plugin    my-plugin/
molecule-plugin validate workspace workspace-configs-templates/claude-code-default/
molecule-plugin validate org       org-templates/molecule-dev/
molecule-plugin validate my-plugin/   # kind defaults to 'plugin'

# Channel and trigger repos pin a vendored copy of the public local-A2A client.
molecule-plugin channel-sdk check my-channel/
molecule-plugin channel-sdk sync  my-channel/  # intentional SDK upgrade

Exit code is 0 when valid, 1 when any errors are found — suitable for CI. Add -q / --quiet to suppress success lines and emit only errors. python -m molecule_plugin ... remains an equivalent invocation.

Manifest validation follows the canonical contracts/plugin-manifest/plugin-manifest.schema.json: name, version, and description are required; known contributes entries are shape-checked; unknown contribution points remain forward-compatible. The authoring validator requires every declared daemon to match the canonical name/command/args/ env/cwd shape, so a typo fails before publication. Runtime admission remains backward-compatible: an already-installed legacy manifest with a malformed daemon is skipped with a warning instead of bricking the workspace.

The following channel-cutover behavior shipped in 0.6.0 and is present in the published molecule-ai-sdk==0.6.0 package. It is not in 0.5.5 or in the release/0.5.x line, so a consumer still on either must treat it as a breaking upgrade — see the Breaking section of CHANGELOG.md.

Channel integrations are kind: channel plugins in plugins[]; the retired native org-template channels key is rejected at the root, defaults, and every recursive workspace node with migration guidance. The schema and authoring validator match the reserved channels/defaults/workspaces/children field names case-insensitively in YAML and JSON-form documents. Validation also inspects every explicit duplicate structural-key occurrence before normal last-key-wins decoding, while preserving explicit-key precedence over YAML merge sources. Nested business mappings such as category_routing.channels remain valid. The public molecule_plugin.channel module owns the API v1 client contract: reserved runtime environment names, canonical A2A request construction, response parsing, and explicit unavailable/protocol/delivery-unknown failures. Generated channel scaffolds vendor that module byte-for-byte so their daemon does not import private runtime code. Generated pull-request CI validates the manifest, checks that the vendored client still matches its pinned SDK, and runs the scaffold tests. The SDK's own required PR suite also exercises a provider event through the generated bridge and a real authenticated Unix socket to an agent reply.

Programmatic equivalents:

from molecule_plugin import (
    send_channel_message,
    channel_message_response_text,
    validate_plugin,
    validate_workspace_template,
    validate_org_template,
)

Per-runtime adaptors — when to write a custom one

The default AgentskillsAdaptor handles the common shape: rules go into the runtime's memory file (CLAUDE.md), skill dirs go into /configs/skills/. That covers most plugins.

The adaptor records exact ownership under /configs/.molecule/plugin-ownership/. Uninstall removes only unchanged files, the exact marked memory block, and the plugin's exact settings contribution; pre-existing and user-modified content is preserved. Legacy installs without a record are a safe no-op. See docs/migrations/plugin-uninstall-ownership.md.

Write a custom adaptor when you need to:

  • Register runtime tools dynamically — call ctx.register_tool(name, fn).
  • Register runtime sub-agents — call ctx.register_subagent(name, spec).
  • Write to a non-standard memory file — call ctx.append_to_memory(filename, content).

Minimum custom adaptor:

# adapters/codex.py
from molecule_plugin import InstallContext, InstallResult

class Adaptor:
    def __init__(self, plugin_name: str, runtime: str):
        self.plugin_name, self.runtime = plugin_name, runtime

    async def install(self, ctx: InstallContext) -> InstallResult:
        ctx.register_subagent("my-agent", {"prompt": "...", "tools": [...]})
        return InstallResult(plugin_name=self.plugin_name, runtime=self.runtime, source="plugin")

    async def uninstall(self, ctx: InstallContext) -> None:
        pass

Resolution order (understood by the platform)

For (plugin_name, runtime):

  1. Platform registryworkspace-template/plugins_registry/<plugin>/<runtime>.py (curated; set by the Molecule AI team for quality-assured plugins).
  2. Plugin-shipped<plugin_root>/adapters/<runtime>.py (what this SDK helps you build).
  3. Raw-drop fallback — copies plugin files into /configs/plugins/<name>/ and surfaces a warning; no tools are wired.

You generally ship for path #2. If your plugin becomes popular enough to be promoted to "default," the Molecule AI team PRs a copy of your adaptor into the platform registry (path #1) so it survives upstream breakage.

Runtime identifiers and official support

Runtime IDs are open, bounded, path-safe slugs defined by contracts/adapter/runtime-id.schema.json; third-party adapters are not blocked by a first-party allowlist. The separate official registry contains Claude Code, Codex, Hermes, and OpenClaw. Known aliases such as claude_code normalize to their canonical ID, while other valid IDs remain unchanged.

Build and test

pip install -e '.[test]'   # base packages + pytest-asyncio
pytest -q
S
Description
No description provided
Readme MIT 2.9 MiB
Languages
Python 52.2%
JavaScript 21.8%
TypeScript 21.5%
Go 3.8%
Shell 0.5%
Other 0.2%