Go to file
molecule-ai[bot] 4e289e3004
tests: add GAP-01 tar security + GAP-02 SHA256 verification suites (#8)
* tests: add GAP-01 tar security and GAP-02 SHA256 verification test suites

GAP-01 (test_safe_extract.py):
- CWE-22 traversal via ../ in tar header names (3 cases)
- Absolute path rejection in tar entries (2 cases)
- Symlink hardlink skip (2 cases each)
- Hardlink skip
- Deep traversal rejection
- Deep valid path extraction
- Empty tar noop
- Normal operation smoke test
- zipfile placeholder (documents no zip hardening yet)

GAP-02 (test_sha256_verification.py):
- _is_hex validation (4 cases)
- _sha256_file empty/small/large/binary/not-found (5 cases)
- _walk_files excludes dirs/deterministic/set equality (3 cases)
- verify_plugin_sha256 empty plugin/excludes plugin.yaml/invalid format (3 cases)
- compute_plugin_sha256 stable/deterministic order/content changes exclusion (4 cases)
- CLI verify-sha256 exit zero/nonzero/file-not-dir/error message (4 cases)
- Round-trip compute→verify (1 case)
- Mismatch returns False (1 case)

Total: 37 new test cases, all passing.
180 passed / 1 skipped across full suite (excluding broken conftest import in test_call_peer_errors.py).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add KI-007 (_is_hex TypeError gap) and KI-008 (test_call_peer_errors conftest)

KI-007: _is_hex raises TypeError on non-strings instead of returning False;
guard with isinstance(value, str) check.

KI-008: test_call_peer_errors.py imports tests.conftest which doesn't exist;
fix import or create conftest.py stub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Molecule AI SDK Lead <sdk-lead@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:17:42 +00:00
.claude docs: add CLAUDE.md, known-issues.md, and .claude/settings.json (#2) 2026-04-20 23:10:37 +00:00
.github/workflows ci: add PyPI publish workflow (tag v* → upload) 2026-04-16 03:50:17 -07:00
examples/remote-agent feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00
molecule_agent feat(security): add plugin content integrity verification (SHA256) (#3) 2026-04-21 01:00:35 +00:00
molecule_plugin fix(tests): add pytest-asyncio markers to async adaptor tests (#4) 2026-04-21 00:54:07 +00:00
template feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00
tests tests: add GAP-01 tar security + GAP-02 SHA256 verification suites (#8) 2026-04-21 06:17:42 +00:00
.DS_Store feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00
.gitignore chore: gitignore credentials for molecule-sdk-python 2026-04-16 09:19:08 -07:00
CLAUDE.md feat(security): add plugin content integrity verification (SHA256) (#3) 2026-04-21 01:00:35 +00:00
known-issues.md tests: add GAP-01 tar security + GAP-02 SHA256 verification suites (#8) 2026-04-21 06:17:42 +00:00
pr-description-draft.md feat(security): add plugin content integrity verification (SHA256) (#3) 2026-04-21 01:00:35 +00:00
pyproject.toml fix(tests): add pytest-asyncio markers to async adaptor tests (#4) 2026-04-21 00:54:07 +00:00
pytest.ini feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00
README.md feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00

molecule_plugin — Python SDK for building Molecule AI plugins

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

Quick start

Copy template/ to a new directory and edit:

my-plugin/
├── plugin.yaml              # name, version, runtimes, description
├── rules/my-rule.md         # optional — appended to CLAUDE.md at install
├── skills/my-skill/
│   ├── SKILL.md             # instructions injected into the system prompt
│   └── tools/do_thing.py    # optional LangChain @tool functions
└── adapters/
    ├── claude_code.py       # one-liner: `from molecule_plugin import AgentskillsAdaptor as Adaptor`
    └── deepagents.py        # same

Validate:

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

CLI

The SDK ships a CLI for validating Molecule AI artifacts before publishing:

python -m molecule_plugin validate plugin    my-plugin/
python -m molecule_plugin validate workspace workspace-configs-templates/claude-code-default/
python -m molecule_plugin validate org       org-templates/molecule-dev/
python -m molecule_plugin validate channel   channels.yaml
python -m molecule_plugin validate my-plugin/   # kind defaults to 'plugin'

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.

Programmatic equivalents:

from molecule_plugin import (
    validate_plugin,
    validate_workspace_template,
    validate_org_template,
    validate_channel_file,
    validate_channel_config,
)

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.

Write a custom adaptor when you need to:

  • Register runtime tools dynamically — call ctx.register_tool(name, fn).
  • Register DeepAgents 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/deepagents.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.

Testing locally

The SDK ships AgentskillsAdaptor as a standalone, unit-testable class:

import asyncio
from pathlib import Path
from molecule_plugin import AgentskillsAdaptor, InstallContext

ctx = InstallContext(
    configs_dir=Path("/tmp/configs"),
    workspace_id="local",
    runtime="claude_code",
    plugin_root=Path("./my-plugin"),
)
asyncio.run(AgentskillsAdaptor("my-plugin", "claude_code").install(ctx))
# check /tmp/configs/CLAUDE.md, /tmp/configs/skills/

Publishing

A plugin is just a directory. Push it to any Git host. Installation via POST /plugins/install {git_url} is on the roadmap — see the platform's PLAN.md under "Install-from-GitHub-URL flow." Until then, plugins are bundled into the platform by dropping them into plugins/ at deploy time.

Supported runtimes

As of 2026-Q2: claude_code, deepagents, langgraph, crewai, autogen, openclaw. See the live list with:

curl $PLATFORM_URL/plugins