molecule-core/docs/tutorials/register-remote-agent.md

269 lines
11 KiB
Markdown

# Register a Remote Agent on Molecule AI
Remote agents let you connect AI agents running on *any* infrastructure — your laptop, a cloud VM, a CI/CD pipeline, or an on-premise server — to a single Molecule AI canvas. Your agent keeps running wherever it lives; the canvas gives you fleet-wide visibility, secret management, and cross-network A2A messaging from one place.
This tutorial walks through the full registration flow: creating an external workspace, obtaining a bearer token, setting up the heartbeat, and verifying the agent appears on your canvas.
> **Prerequisites:** A running Molecule AI platform (self-hosted or cloud), `ADMIN_TOKEN` (or an org-scoped key with admin scope), and an agent binary that can make HTTP calls.
## How remote agents work
Molecule AI's remote agent system has three parts:
1. **External workspace** — a workspace record with `runtime: "external"` and `external: true`. It holds metadata (agent name, URL, agent card) but does not provision a container.
2. **Bearer token** — the credential your remote agent uses to authenticate to the platform on every call. Issued once at registration; stored by the agent.
3. **Heartbeat loop** — the agent sends a `POST /registry/heartbeat` every 30 seconds to stay visible on the canvas.
```
Your infra (laptop / VM / CI) Molecule AI Platform
│ │
│ POST /workspaces (create external workspace)
│────────────────────────────────────►│
│ │
│ POST /registry/register (get bearer token)
│────────────────────────────────────►│
│ ← auth_token
│ │
│ POST /registry/heartbeat (every 30s)
│────────────────────────────────────►│ Canvas shows purple REMOTE badge
│ │
│ GET /secrets (fetch workspace secrets)
│ POST /a2a (A2A messaging)
│────────────────────────────────────►│
```
## Step-by-step registration
### Step 1: Create an external workspace
```bash
ADMIN_TOKEN="your-admin-token-or-org-key"
PLATFORM_URL="https://platform.moleculesai.app"
AGENT_URL="https://your-agent.example.com" # must be reachable from the platform
WORKSPACE=$(curl -s -X POST "${PLATFORM_URL}/workspaces" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "CI Agent",
"runtime": "external",
"external": true,
"url": "https://your-agent.example.com"
}')
WORKSPACE_ID=$(echo $WORKSPACE | jq -r '.id')
echo "Workspace ID: ${WORKSPACE_ID}"
```
The `runtime: "external"` flag tells the platform this workspace is agent-managed, not container-provisioned. The `url` field is the address the platform uses to reach your agent (for A2A routing and health checks).
Save the workspace ID — you'll use it in the next step.
### Step 2: Register the agent and receive a bearer token
```bash
REG=$(curl -s -X POST "${PLATFORM_URL}/registry/register" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"id\": \"${WORKSPACE_ID}\",
\"url\": \"https://your-agent.example.com\",
\"agent_card\": {
\"name\": \"CI Agent\",
\"runtime\": \"external\",
\"version\": \"1.0\"
}
}")
AUTH_TOKEN=$(echo $REG | jq -r '.auth_token')
echo "Auth token: ${AUTH_TOKEN}"
# IMPORTANT: the auth_token is shown once. Store it securely.
# If lost, revoke and re-register.
```
The response looks like:
```json
{
"auth_token": "rtok_01HZX... truncated ...",
"workspace_id": "ws_01HZX...",
"org_id": "org_01HZX...",
"expires_at": null
}
```
Store `auth_token` in your agent's environment — **it's shown only once**. If you lose it, create a new external workspace and re-register.
### Step 3: Pull secrets on demand
Your agent fetches workspace secrets via the platform API using its bearer token. Secrets are never injected as environment variables for remote agents — the agent pulls them explicitly:
```bash
curl -s "${PLATFORM_URL}/workspaces/${WORKSPACE_ID}/secrets" \
-H "Authorization: Bearer ${AUTH_TOKEN}"
```
```json
{
"secrets": {
"OPENAI_API_KEY": "sk-...",
"GITHUB_TOKEN": "ghs_..."
}
}
```
This keeps secrets out of environment blocks and allows rotation without restarting the agent. Call this on agent boot and re-call whenever your agent refreshes its credential cache.
### Step 4: Start the heartbeat loop
The heartbeat keeps your agent visible on the canvas. Send it every **30 seconds**:
```python
import requests, time
AUTH_TOKEN = "rtok_01HZX..."
WORKSPACE_ID = "ws_01HZX..."
PLATFORM_URL = "https://platform.moleculesai.app"
while True:
resp = requests.post(
f"{PLATFORM_URL}/registry/heartbeat",
headers={"Authorization": f"Bearer {AUTH_TOKEN}"},
json={"workspace_id": WORKSPACE_ID},
)
if resp.status_code != 200:
print(f"Heartbeat failed: {resp.status_code} {resp.text}")
time.sleep(30)
```
If the platform misses three consecutive heartbeats (90 seconds), it marks the agent as `offline` on the canvas. The agent can resume by sending a heartbeat at any time — the canvas updates immediately.
### Step 5: Send and receive A2A messages
Remote agents use the standard A2A protocol. Your agent polls for inbound tasks:
```bash
curl -s -X POST "${PLATFORM_URL}/a2a" \
-H "Authorization: Bearer ${AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: ${WORKSPACE_ID}" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "Hello from a remote agent"}]
}
}
}'
```
The `X-Workspace-ID` header identifies which workspace the message originates from. Remote agents send from their own workspace; orchestrators can address specific agents by workspace ID.
### Step 6: Verify the agent appears on the canvas
Open your Molecule AI canvas, navigate to **Workspaces**, and look for your agent. Remote agents show a **purple REMOTE badge** next to their name so you can distinguish them from container-provisioned workspaces at a glance.
If the badge is grey instead of purple, the heartbeat is not reaching the platform. Check:
- The agent's outbound HTTPS can reach `platform.moleculesai.app`
- The heartbeat loop is running and not crashing silently
- The `auth_token` matches the workspace ID
## Agent code: minimal Python example
Here's a minimal agent that registers, starts the heartbeat, and can receive A2A tasks:
```python
import requests, time, threading, json
PLATFORM_URL = "https://platform.moleculesai.app"
ADMIN_TOKEN = "your-admin-token" # used only during registration
AGENT_URL = "https://your-agent.example.com" # must be HTTPS and reachable
# Step 1: Create external workspace
workspace = requests.post(
f"{PLATFORM_URL}/workspaces",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
json={"name": "CI Agent", "runtime": "external", "external": True, "url": AGENT_URL},
).json()
WORKSPACE_ID = workspace["id"]
# Step 2: Register and get bearer token
reg = requests.post(
f"{PLATFORM_URL}/registry/register",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
json={
"id": WORKSPACE_ID,
"url": AGENT_URL,
"agent_card": {"name": "CI Agent", "runtime": "external"},
},
).json()
AUTH_TOKEN = reg["auth_token"]
# Step 3: Fetch secrets on boot
secrets = requests.get(
f"{PLATFORM_URL}/workspaces/{WORKSPACE_ID}/secrets",
headers={"Authorization": f"Bearer {AUTH_TOKEN}"},
).json()
# Store secrets in your agent's credential store
# Step 4: Heartbeat loop (runs in background)
def heartbeat_loop():
while True:
requests.post(
f"{PLATFORM_URL}/registry/heartbeat",
headers={"Authorization": f"Bearer {AUTH_TOKEN}"},
json={"workspace_id": WORKSPACE_ID},
)
time.sleep(30)
threading.Thread(target=heartbeat_loop, daemon=True).start()
# Step 5: Poll for A2A tasks
print(f"Registered. Workspace ID: {WORKSPACE_ID}")
print("Heartbeat running in background.")
```
## Self-hosted agents
For agents on private networks or air-gapped infrastructure, the platform must be able to reach `AGENT_URL` for A2A delivery. If your agent is behind a NAT or firewall:
- Use a tunnel (Cloudflare Tunnel, ngrok, frp) to expose the agent on a public HTTPS URL
- Ensure the URL resolves and the agent's HTTP server handles `POST /a2a` requests
- Check that your firewall allows outbound HTTPS to `PLATFORM_URL`
For air-gapped deployments without internet access, contact your Molecule AI sales team for on-premise deployment options.
## Revoking and re-registering
To rotate the agent's bearer token:
1. **Revoke the workspace** (canvas UI or `DELETE /workspaces/{id}`) — this invalidates the current token
2. Re-run Step 1 and Step 2 above with a new workspace name
3. Update your agent's `AUTH_TOKEN` with the new value
To revoke without deleting the workspace record, use `DELETE /workspaces/{id}/tokens` if your platform version supports it.
## Remote agents vs. Docker workspaces
| | Remote Agent | Docker Workspace |
|---|---|---|
| Infrastructure | Your own (laptop, VM, bare metal) | Platform-provisioned containers |
| Token issuance | Manual via `/registry/register` | Automatic on container boot |
| Secrets | Pulled on demand via API | Injected as env vars at startup |
| Heartbeat | Your code sends it every 30s | Platform sends it from the container |
| Canvas badge | Purple REMOTE | Standard (no badge) |
| Tear-down | Revoke token + stop agent | `DELETE /workspaces/{id}` |
| Best for | CI/CD agents, laptops, on-prem | Cloud VMs managed by the platform |
## What's next
- [Agent Card reference](../agent-runtime/agent-card.md) — publish your agent's capabilities so orchestrators can discover and route tasks
- [A2A protocol reference](../api-protocol/a2a-protocol.md) — full message format, error codes, and streaming
- [Registry and heartbeat reference](../api-protocol/registry-and-heartbeat.md) — heartbeat interval, offline detection, and error handling
- [Remote workspaces blog post](../blog/2026-04-20-remote-workspaces/index.md) — the product announcement with fleet visibility context
> **Molecule AI is open source.** Remote agent support is in `molecule-core/registry/` on `main`.