Files
sdk-dev f8a2991797
Test / test (3.12) (pull_request) Waiting to run
Test / test (3.13) (pull_request) Successful in 2m24s
Test / test (3.11) (pull_request) Successful in 2m52s
[Do] Manual ack
sop-checklist / all-items-acked SOP checklist acknowledged by sdk-dev
fix(examples): correct repo-root import path in remote-agent/run.py
The local-dev import path was adding sdk/python/ to sys.path, but the
molecule_agent package lives at the repo root, not under sdk/python/.
The dead-code path silently failed (isdir check returned False) so the
script only worked when molecule-ai-sdk was pip-installed. Now uses
os.path.normpath(.. / ..) pointing to the repo root instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 10:02:22 +00:00

101 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Minimal remote-agent demo — Phase 30.130.5 end-to-end from outside the
platform's Docker network.
What this does:
1. Registers the workspace with the platform (mints + saves a bearer token).
2. Pulls the merged decrypted secrets via the token-gated 30.2 endpoint.
3. Runs a heartbeat + state-poll loop; exits cleanly when the platform
reports the workspace paused or deleted.
What it doesn't do (future 30.8b work):
- Host an inbound A2A server. Platform-initiated calls to this agent
won't reach it unless you expose one yourself.
Usage:
# One-time setup on the platform side:
# 1) Create the workspace row (any template is fine — external runtime
# is cleanest if you don't want Docker to try to start a container):
curl -s -X POST http://localhost:8080/workspaces \\
-H 'Content-Type: application/json' \\
-d '{"name":"remote-demo","tier":2,"runtime":"external"}'
# 2) Grab the returned workspace id.
# 3) Optional — seed a secret:
curl -s -X POST http://localhost:8080/workspaces/<id>/secrets \\
-H 'Content-Type: application/json' \\
-d '{"key":"REMOTE_DEMO_KEY","value":"hello-from-remote"}'
# Now run this script from any machine that can reach the platform:
WORKSPACE_ID=<id> PLATFORM_URL=http://localhost:8080 python3 run.py
Environment variables:
WORKSPACE_ID (required)
PLATFORM_URL (required)
AGENT_NAME (optional; default derived from workspace id)
MAX_ITERATIONS (optional; caps loop length for demos)
"""
from __future__ import annotations
import logging
import os
import sys
# Local-dev import path — when installed via pip the molecule_agent package
# resolves normally; when running from the repo checkout we add the repo root
# to sys.path so you can run `python3 run.py` without a pip install.
_here = os.path.dirname(os.path.abspath(__file__))
_repo_root = os.path.normpath(os.path.join(_here, "..", ".."))
if _repo_root not in sys.path:
sys.path.insert(0, _repo_root)
from molecule_agent import RemoteAgentClient # noqa: E402
def main() -> int:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
log = logging.getLogger("remote-agent-demo")
workspace_id = os.environ.get("WORKSPACE_ID", "").strip()
platform_url = os.environ.get("PLATFORM_URL", "").strip()
if not workspace_id or not platform_url:
log.error("set WORKSPACE_ID and PLATFORM_URL and re-run")
return 2
agent_name = os.environ.get("AGENT_NAME", f"remote-demo-{workspace_id[:8]}")
max_iter_env = os.environ.get("MAX_ITERATIONS", "").strip()
max_iter = int(max_iter_env) if max_iter_env else None
client = RemoteAgentClient(
workspace_id=workspace_id,
platform_url=platform_url,
agent_card={"name": agent_name, "skills": []},
# Shorter intervals for demo visibility; production would leave defaults.
heartbeat_interval=5.0,
)
log.info("phase 1 — registering workspace %s with %s", workspace_id, platform_url)
client.register()
log.info("phase 2 — pulling secrets via 30.2 token-gated endpoint")
try:
secrets = client.pull_secrets()
except Exception as exc:
log.error("pull_secrets failed: %s", exc)
return 1
log.info("received %d secret(s): keys=%s", len(secrets), sorted(secrets.keys()))
log.info("phase 3 — heartbeat + state-poll loop (will exit on pause/delete)")
terminal = client.run_heartbeat_loop(
max_iterations=max_iter,
task_supplier=lambda: {"current_task": "remote-agent demo idle", "active_tasks": 0},
)
log.info("loop exited: terminal_status=%s", terminal)
return 0
if __name__ == "__main__":
sys.exit(main())