molecule-sdk-python/molecule_agent
molecule-ai[bot] 818931f9d3
feat(tests): GAP-05 add _get_with_retry() with 429 back-off + fix broken test_call_peer_errors (#11)
Adds retry-on-429 with exponential back-off (1 s → 2 s → 4 s, ±25% jitter,
30 s cap, Retry-After header honoured) to all idempotent RemoteAgentClient
GET calls: poll_state, pull_secrets, get_peers, discover_peer.

Also fixes the merged test_call_peer_errors.py (PR #7) which was broken:
- Removed pytest-mock dependency (mocker not installed)
- Fixed call_peer(message: str) vs dict
- Fixed non-existent _call_direct/_call_proxy method patches
- Uses FakeResponse + _session.post.side_effect pattern consistently

Adds tests/conftest.py (FakeResponse + client fixture + _CaptureHandler)
and tests/test_retry_backoff.py (18 new tests).

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:08:01 +00:00
..
__init__.py feat(security): add plugin content integrity verification (SHA256) (#3) 2026-04-21 01:00:35 +00:00
__main__.py feat(security): add plugin content integrity verification (SHA256) (#3) 2026-04-21 01:00:35 +00:00
client.py feat(tests): GAP-05 add _get_with_retry() with 429 back-off + fix broken test_call_peer_errors (#11) 2026-04-21 07:08:01 +00:00
README.md feat: initial Python SDK (extracted from molecule-monorepo/sdk/python) 2026-04-16 03:15:38 -07:00

molecule_agent — Remote-agent SDK for Molecule AI

Build a Python agent that runs outside a Molecule AI platform's Docker network and registers as a first-class workspace. The agent gets bearer-token auth, pulls its secrets, calls siblings, installs plugins from the platform's registry, and reacts to platform-initiated lifecycle events (pause, delete) — all over plain HTTP.

This is the client side of Phase 30. The platform side ships in the same release; this package is just the SDK an agent author imports.

Install

pip install molecule-sdk     # ships molecule_plugin + molecule_agent

60-second example

from molecule_agent import RemoteAgentClient

client = RemoteAgentClient(
    workspace_id="<the-uuid-of-an-external-workspace-on-the-platform>",
    platform_url="https://your-platform.example.com",
    agent_card={"name": "my-remote-agent", "skills": []},
)

# 1. Register and mint a bearer token (cached at ~/.molecule/<id>/.auth_token).
client.register()

# 2. Pull secrets the platform was set to inject.
secrets = client.pull_secrets()
# → {"OPENAI_API_KEY": "...", ...}

# 3. (Optional) install a plugin locally — pulls a tarball, unpacks, runs setup.sh.
client.install_plugin("molecule-dev")
client.install_plugin("my-plugin", source="github://acme/my-plugin")

# 4. Run the heartbeat + state-poll loop until the platform pauses/deletes us.
terminal = client.run_heartbeat_loop()
print(f"loop exited: {terminal}")

A runnable demo with full setup walkthrough lives at sdk/python/examples/remote-agent/.

What the SDK gives you

Method Phase What it does
register() 30.1 Mint + cache the workspace's bearer token
pull_secrets() 30.2 Token-gated GET of merged secrets dict
install_plugin(name, source=None) 30.3 Stream plugin tarball, atomic extract, run setup.sh
poll_state() 30.4 Lightweight {status, paused, deleted} poll
heartbeat(...) 30.1 Single bearer-authed heartbeat
get_peers() / discover_peer() 30.6 Sibling URL discovery with TTL cache
call_peer(target, message) 30.6 Direct A2A with proxy fallback
run_heartbeat_loop() combo Drives heartbeat + state-poll on a timer; exits on pause/delete

What it doesn't do (yet)

  • No inbound A2A server. Other agents can't initiate calls to your remote agent unless you host an HTTP endpoint yourself. Future start_a2a_server() helper will close this gap.
  • No automatic reconnect after token loss. If ~/.molecule/<id>/.auth_token is deleted, you'll need to re-issue the token via the platform admin (since POST /registry/register is idempotent — it won't mint a second token for a workspace that already has one).

Design choices

  • Blocking (requests), not async. Drops into any runtime — script, thread, asyncio loop. No framework lock-in.
  • Token cached on disk with 0600 so a restart of the agent doesn't re-issue (the platform refuses anyway). Lives at ~/.molecule/<workspace_id>/.auth_token.
  • URL cache for siblings is process-memory only, 5-minute TTL. Cleared on graceful failures via invalidate_peer_url.
  • Tar extraction uses _safe_extract_tar that rejects path-traversal and skips symlinks — defense against tar-slip CVEs in case a plugin source is compromised.

Compatibility

Requires a Molecule AI platform with Phase 30 endpoints (PR #122 onwards). Older platforms grandfather pre-token workspaces through, so this SDK also works against a transition-period deployment — but you won't get the security benefits of bearer auth until both sides upgrade.