Agent Provisioning Process
Each step of a hands-off agent provision, across Solomon and deploy.enteracloud.com. The two sides run interleaved in time; the numbers below reflect the temporal order observed end-to-end on agent210 (clone → online → chat-ready in ~3 min).
POST /api/provision/agents
Operator (UI or API caller) submits the new-agent request to Solomon. Required body fields: name. Optional: friendly_name, ip_mode (default static), static_ip, model (default zai/glm-5.1), and any vCenter overrides (host/datastore/folder/network/template_name/cpu/ram_mb/disk_gb).
POST /api/provision/agents
{ "name": "clw-agent213",
"ip_mode": "static",
"static_ip": "10.8.4.213" }Mint records: Agent + Server + BootstrapToken
Solomon creates the Agent row (status=OFFLINE, gateway_token = random 32-byte URL-safe), a placeholder Server row (ip_address starts empty), and a one-time BootstrapToken (30-minute TTL) that the new VM will redeem to fetch its install script.
Call deploy.enteracloud.com (clean: post_script="")
Solomon calls the external deploy API with all vCenter parameters and an EMPTY post_script. Empty post_script avoids racing with vCenter guest customization (the step that applies the static IP); bootstrap is done from Solomon afterward instead.
host=192.168.8.101 ds=101-nvme1 folder=clw-804 network=DPortGroup804 template=tpl-linux ip_mode=static static_ip=<x> static_gateway=10.8.4.1 static_dns="1.1.1.1, 8.8.8.8" post_script=""
Return to caller; spawn hub-bootstrap thread
Solomon responds 200 { agent_id, deploy_id, vm_name, bootstrap_id } and starts _hub_bootstrap_vm on a background thread. Subsequent steps happen asynchronously.
Clone template on vCenter
Deploy connects to vCenter, locates the template (tpl-linux) and clones it to the new VM on the target host (192.168.8.101) and datastore (101-nvme1), placed in folder clw-804 on DPortGroup804.
Apply guest customization (static IP + DNS)
Deploy prepares and applies vCenter guest customization: static IP, mask 255.255.255.0, gateway, DNS. Customization runs at VM first boot via VMware tools; it may include a network restart or guest reboot.
VM boots; vmware-tools applies the static IP
VM powers on. cloud-init / vmware-tools applies the network config inside the guest. When the interface (ens33) gets its static IP, the VM is ARP-reachable on the LAN. This is the step that intermittently flakes in our environment — see Known failure modes below.
Install SSH key via proxy (guest-ops)
Deploy reaches the VM via its proxy (VMware guest operations) and installs an SSH key for admin1. With post_script empty this is the deploy's only intervention on the new VM. Marks deployment complete.
hub-bootstrap thread polls SSH at static_ip
Solomon's _hub_bootstrap_vm thread polls SSH on the new VM's static IP (admin1@<static_ip>) every 15s for up to 10 minutes. Once a session opens cleanly, it proceeds.
Fetch install.sh via the one-time bootstrap URL
Hub SSHes in and runs curl against /api/provision/bootstrap/<bootstrap_id>/install.sh, then pipes the script to sudo bash. Fetching the URL consumes the token (it is then 410 Gone for any subsequent fetch).
curl -fsSL --connect-timeout 15 --max-time 600 \ https://solomon/api/provision/bootstrap/<id>/install.sh \ -o /tmp/sa-install.sh sudo bash /tmp/sa-install.sh
install.sh: prereqs → clone → venv → daemon
On the VM: apt install python3-venv git curl, clone solomon-agent (falling back to the hub-served tarball if the GitLab clone fails), python venv + pip install requirements, write /etc/solomon-agent/agent.env (with SOLOMON_URL + SOLOMON_TOKEN + OPENCLAW_BIN), install the solomon CLI to /usr/local/bin/solomon, install + enable + start the solomon-agent.service systemd unit.
Daemon dials home (WebSocket)
solomon_agent.py connects to ws://10.8.4.220:8000/api/agents/ws?token=<gateway_token>. The hub's ConnectionManager accepts it and flips agent.status to ONLINE.
_configure_openclaw_vm: install Node + openclaw
Hub SSHes in again (idempotent) and installs Node 22 from NodeSource if missing, then npm install -g openclaw --prefix /home/admin1/.npm-global (matching OPENCLAW_BIN).
openclaw onboard --non-interactive --accept-risk --mode local
Creates ~/.openclaw/openclaw.json with baseline config, the workspace, sessions folder, and the default "main" agent that the solomon-agent daemon dispatches into.
openclaw config patch (per-provider, from OPENCLAW_PROVIDERS)
Hub builds a JSON5 patch from the OPENCLAW_PROVIDERS registry based on the agent's model (e.g. zai/glm-5.1 → zai provider def + agents.defaults.model.primary). It is delivered to the VM via base64 and applied with openclaw config patch --file.
{ models: { mode: "merge",
providers: { zai: { baseUrl, api, models } } },
agents: { defaults: { model: { primary: "zai/glm-5.1" } } } }openclaw models auth paste-token (key via stdin)
Hub reads the provider API key from Solomon's vault (e.g. api/zai.api_key) and pipes it into paste-token over the SSH stdin. The key never appears in argv, server logs, or the JSON config in cleartext.
openclaw models auth paste-token \ --provider zai --profile-id zai:default # token read from stdin
openclaw config validate
Final sanity check on the assembled config; returns Config valid: ~/.openclaw/openclaw.json.
Chat-ready: openclaw agent --agent main returns model output
A turn dispatched from Solomon flows: hub → daemon WS → solomon_agent.run_openclaw_turn → openclaw agent --agent main --message <prompt> --json → provider (e.g. zai) → JSON result with finalAssistantVisibleText. End-to-end demonstrated on clw-agent210 hands-off (~3 min from POST to first model response).
Known failure modes
Endpoint reference
OPENCLAW_PROVIDERS in provision.py. Vault paths: api/<provider>.api_key.