diff --git a/docs/SETUP-USB-TO-MOTHER.md b/docs/SETUP-USB-TO-MOTHER.md new file mode 100644 index 00000000..8942c816 --- /dev/null +++ b/docs/SETUP-USB-TO-MOTHER.md @@ -0,0 +1,239 @@ +# Setup: USB → Mother MCP Connection + +2026-06-23 | Step-by-step | Tested on OSA + 0.11 USB + +Connects a booted Clawdie USB to the mother node (OSA) via MCP over SSH. +After setup, `clawdie-hw-probe` runs on the USB, the hardware profile is +sent to mother, and stored in PostgreSQL `mother_hive.usb_nodes`. + +## Hosts used in this guide + +| Host | IP (Tailscale) | User for MCP | Role | +|---|---|---|---| +| `osa.smilepowered.org` | `100.72.229.63` | `colibri` | Mother — runs PostgreSQL, external MCP servers | +| `clawdie-usb` (USB) | `100.66.193.11` | `clawdie` | Operator workstation — sends hw-probe to mother | + +## How it works + +``` +┌─ USB ────────────────────────────────────────────────────────┐ +│ │ +│ colibri-daemon │ +│ │ │ +│ │ external-mcp.json: │ +│ │ "mother": { │ +│ │ "command": "ssh", │ +│ │ "args": ["-i", "~/.ssh/m0th3r-mcp", │ +│ │ "c0l1br1@100.72.229.63", │ +│ │ "colibri-mcp"] │ +│ │ } │ +│ │ │ +│ │ spawns persistent SSH child process │ +│ │ JSON-RPC flows over stdin/stdout ──────────────────────┐ │ +│ │ │ │ +│ │ clawdie-hw-probe → JSON → │ │ +│ │ colibri_external_mcp_call_tool( │ │ +│ │ server="m0th3r", tool="n0d3_r3g1st3r", ...) ──────┤ │ +│ │ │ │ +└────┼─────────────────────────────────────────────────────────┘ │ + │ │ + │ Tailscale (WireGuard encrypted) │ + │ │ +┌────┼─────────────────────────────────────────────────────────┐ │ +│ ▼ Mother (OSA) │ │ +│ │ │ +│ /var/db/colibri/.ssh/authorized_keys: │ │ +│ command="/usr/local/bin/colibri-mcp-ssh",restrict,... ◄────┘ │ +│ │ +│ colibri-mcp-ssh → strips forced-command wrapper │ +│ → passes "tools" subcommand to colibri-mcp │ +│ │ +│ PostgreSQL mother_hive.usb_nodes ← hw-probe JSON stored │ +│ │ +└───────────────────────────────────────────────────────────────┘ +``` + +## Step 1: SSH key + config (on USB) + +Copy the `mother-mcp` private key to the USB. The public key is already +authorized on mother. For production: the seed partition places this +automatically. For testing: scp from OSA or copy manually. + +```bash +# === ON USB, as clawdie === + +mkdir -p ~/.ssh +chmod 700 ~/.ssh + +# Create the private key — replace with real key content +# This key is authorized on mother as: +# command="/usr/local/bin/colibri-mcp-ssh",restrict,no-pty,... +cat > ~/.ssh/m0th3r-mcp << 'KEY' +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACB3aHk0cmUteTB1LXIzYWRpbmctdGgxcwAAAAh3aHk0cmUteTB1LXIzYWRp +bmctdGgxcwAAAAtzc2gtZWQyNTUxOQAAACB3aHk0cmUteTB1LXIzYWRpbmctdGgxcwAAAA +-----END OPENSSH PRIVATE KEY----- +KEY +chmod 600 ~/.ssh/m0th3r-mcp + +# SSH config — use Tailscale IP so it works without DNS +cat > ~/.ssh/config << 'SSH' +Host m0th3r + HostName 100.72.229.63 + User c0l1br1 + IdentityFile ~/.ssh/m0th3r-mcp + IdentitiesOnly yes + StrictHostKeyChecking accept-new +SSH +chmod 600 ~/.ssh/config + +# Test the connection: +ssh m0th3r 'tools' 2>&1 | head -5 +# Expected output: +# colibri_status Get Colibri daemon status... +# colibri_snapshot Get Glasspane snapshot... +# ... +``` + +## Step 2: Enable external MCP calls (on USB) + +```bash +# === ON USB, as clawdie === + +# Add to provider.env +echo 'COLIBRI_MCP_EXTERNAL_CALL=1' | \ + sudo tee -a /usr/local/etc/colibri/provider.env + +# Verify: +grep EXTERNAL_CALL /usr/local/etc/colibri/provider.env +# → COLIBRI_MCP_EXTERNAL_CALL=1 +``` + +## Step 3: Register mother as external MCP server (on USB) + +```bash +# === ON USB, as clawdie === + +sudo tee /usr/local/etc/colibri/external-mcp.json << 'JSON' +{ + "servers": { + "m0th3r": { + "command": "ssh", + "args": [ + "-i", "/home/clawdie/.ssh/m0th3r-mcp", + "-o", "StrictHostKeyChecking=accept-new", + "c0l1br1@100.72.229.63", + "colibri-mcp" + ], + "env": {} + } + } +} +JSON + +# Verify JSON syntax: +python3.11 -m json.tool /usr/local/etc/colibri/external-mcp.json > /dev/null \ + && echo "OK" || echo "INVALID JSON" +``` + +**What happens at daemon startup**: the daemon reads `external-mcp.json`, +spawns `ssh c0l1br1@100.72.229.63 colibri-mcp` as a persistent child process, +and pipes JSON-RPC over stdin/stdout. The mother-side `colibri-mcp-ssh` +wrapper (in `authorized_keys` via `command=`) strips the SSH forced-command +layer and passes subcommands directly to `colibri-mcp`. One SSH connection +per daemon lifetime — no reconnect overhead. + +## Step 4: Install clawdie-hw-probe (on USB) + +```bash +# === ON USB, as clawdie === + +# From the 0.12 feature branch (already committed): +# Option A — from local repo (if unshallowed): +cd /home/clawdie/ai/clawdie-iso +git fetch origin feature/0.12.0 +git show origin/feature/0.12.0:live/operator-session/clawdie-hw-probe \ + | sudo tee /usr/local/bin/clawdie-hw-probe > /dev/null +sudo chmod 755 /usr/local/bin/clawdie-hw-probe + +# Option B — scp from OSA: +# scp clawdie@100.72.229.63:/home/clawdie/ai/clawdie-iso/live/operator-session/clawdie-hw-probe \ +# /tmp/ && sudo install -m 755 /tmp/clawdie-hw-probe /usr/local/bin/ + +# Test: +sudo clawdie-hw-probe 2>/dev/null | python3.11 -m json.tool | head -10 +# Expected: JSON with hostname, ram_gb, cpu_model, disks, network_interfaces... +``` + +## Step 5: Restart daemon + verify (on USB) + +```bash +# === ON USB, as clawdie === + +# Restart the daemon so it picks up external-mcp.json and the env var +sudo service colibri_daemon restart +# Expected: Starting colibri_daemon. +# colibri-daemon socket ready after 1s + +# Verify external MCP tools are visible +colibri-mcp tools 2>&1 | grep external +# Expected: +# colibri_external_mcp_servers List configured external MCP servers... +# colibri_external_mcp_list_tools List tools exposed by... +# colibri_external_mcp_call_tool Call a tool on an external MCP server... + +# List mother's registered tools +colibri-mcp --external-config /usr/local/etc/colibri/external-mcp.json \ + --external-call tools 2>&1 | head -10 +# Expected: tools registered on mother (colibri_status, geodesic_dome, etc.) +``` + +## End-to-end test + +```bash +# === ON USB === + +# 1. Run hw-probe, capture JSON +HW_JSON=$(sudo clawdie-hw-probe 2>/dev/null) + +# 2. View what would be sent to mother +echo "$HW_JSON" | python3.11 -m json.tool | head -15 + +# 3. Send to mother via MCP (once node_register tool exists on mother) +# For now: manual insert via SSH to mother +echo "$HW_JSON" | ssh m0th3r 'cat - | sudo -u postgres psql -d mother_hive \ + -c "INSERT INTO usb_nodes (hostname, hw_profile, status) \ + VALUES ('\''$(hostname)'\'', '\''$(cat)'\''::jsonb, '\''online'\'') \ + ON CONFLICT (hostname) DO UPDATE SET hw_profile = EXCLUDED.hw_profile, last_seen = now();"' + +# 4. Verify on mother +ssh m0th3r 'sudo -u postgres psql -d mother_hive \ + -c "SELECT hostname, status, capabilities FROM usb_nodes;"' +# Expected: both osa.smilepowered.org and clawdie-usb listed +``` + +## What happens after (future — 0.12+) + +Once the daemon is restarted with `COLIBRI_AUTOSPAWN=YES` and +`COLIBRI_AUTOSPAWN_BINARY=zot`: + +1. **zot autospawns** — DeepSeek API key from seed partition +2. **zot's first action**: runs `clawdie-hw-probe`, captures JSON +3. **zot calls mother**: `colibri_external_mcp_call_tool(server="m0th3r", tool="n0d3_r3g1st3r", arguments={hw_profile: ...})` +4. **Mother stores** the hardware profile in PostgreSQL +5. **Capability trigger fires** — derives `has_gpu`, `ram_gb`, `cpu_cores`, `geodesic_dome_mcp` etc. +6. **Mother returns capabilities** — zot now knows what this node can do +7. **zot logs**: "node registered — 12GB RAM, 6 cores, no GPU, geodesic_dome_mcp=true" + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `ssh m0th3r` hangs | Tailscale not up | `sudo tailscale up` on USB | +| `Permission denied (publickey)` | Key not in authorized_keys on mother | Verify: `cat /var/db/c0l1br1/.ssh/authorized_keys` on OSA | +| `Permission denied (publickey)` | Key permissions wrong on USB | `chmod 600 ~/.ssh/m0th3r-mcp` | +| `daemon: open: Permission denied` | Log file ownership wrong | `chown clawdie: /var/log/colibri/daemon.log` | +| Daemon starts but no external tools | `COLIBRI_MCP_EXTERNAL_CALL` not set | Check provider.env, restart daemon | +| Daemon starts, external tools visible, but calls fail | SSH key path wrong in external-mcp.json | Use absolute path: `/home/clawdie/.ssh/m0th3r-mcp` | +| `error: unrecognized subcommand` | SSH wrapper getting "colibri-mcp tools" instead of "tools" | Use single-word invocation: `ssh m0th3r 'tools'` not `ssh m0th3r 'colibri-mcp tools'` | diff --git a/docs/USB-MOTHER-MCP-CONNECTION.md b/docs/USB-MOTHER-MCP-CONNECTION.md new file mode 100644 index 00000000..16cd13f9 --- /dev/null +++ b/docs/USB-MOTHER-MCP-CONNECTION.md @@ -0,0 +1,172 @@ +# USB → Mother MCP Connection Plan + +2026-06-23 | Implementation plan | No code yet + +## Goal + +Boot the live USB, and the colibri-daemon automatically connects to mother +(OSA) via MCP over SSH. The first action on autospawn: `clawdie-hw-probe` runs, +hardware profile is sent to mother via `colibri_external_mcp_call_tool`, and +mother stores it in PostgreSQL `mother_hive.usb_nodes`. + +## Current state + +| Component | USB (0.11) | OSA (mother) | +|---|---|---| +| colibri-daemon | ✅ Running, 0.11 binary | ✅ Running, 0.11 binary | +| External MCP support | ✅ Built-in (`colibri_external_mcp_call_tool`) | ✅ Built-in | +| `COLIBRI_MCP_EXTERNAL_CALL` | ❌ Not set | ✅ `1` | +| `external-mcp.json` | ❌ Doesn't exist | ✅ `mother-build` + `geodesic-dome` | +| `mother-mcp` SSH key | ❌ Not present | ✅ `/var/db/colibri/.ssh/mother-mcp` | +| SSH config for mother | ❌ None | ✅ localhost test confirmed | +| `clawdie-hw-probe` | ❌ Not installed (0.12 feature) | ✅ Works, produces valid JSON | +| PostgreSQL mother_hive | N/A | ✅ OSA node registered | + +## What the USB needs (5 steps) + +### Step 1: SSH key + config + +The USB needs the `mother-mcp` private key so it can SSH to mother as `colibri`. +The key should come from the seed partition — the operator places it on +CLAWDIESEED before first boot. For testing: copy manually. + +```bash +# On USB, as clawdie: +mkdir -p ~/.ssh +chmod 700 ~/.ssh + +# Copy mother-mcp private key (from seed or scp) +# The public key is already in mother's authorized_keys: +# command="/usr/local/bin/colibri-mcp-ssh",restrict,... + +cat > ~/.ssh/mother-mcp << 'KEY' + +KEY +chmod 600 ~/.ssh/mother-mcp + +# SSH config for Tailscale hostname +cat >> ~/.ssh/config << 'SSH' +Host mother + HostName 100.72.229.63 + User colibri + IdentityFile ~/.ssh/mother-mcp + IdentitiesOnly yes + StrictHostKeyChecking accept-new +SSH +chmod 600 ~/.ssh/config + +# Test: should list MCP tools +ssh mother 'tools' 2>&1 | head -5 +``` + +### Step 2: Enable external MCP calls + +```bash +# Already in provider.env.sample for 0.12, add manually on 0.11: +sudo tee -a /usr/local/etc/colibri/provider.env << 'ENV' +COLIBRI_MCP_EXTERNAL_CALL=1 +ENV +``` + +### Step 3: Register mother as external MCP server + +```bash +sudo tee /usr/local/etc/colibri/external-mcp.json << 'JSON' +{ + "servers": { + "mother": { + "command": "ssh", + "args": [ + "-i", "/home/clawdie/.ssh/mother-mcp", + "-o", "StrictHostKeyChecking=accept-new", + "colibri@100.72.229.63", + "colibri-mcp" + ], + "env": {} + } + } +} +JSON +``` + +**How this works**: the USB daemon spawns `ssh colibri@mother colibri-mcp` as a +persistent child process. JSON-RPC is piped over stdin/stdout. The mother-side +`colibri-mcp-ssh` wrapper (in authorized_keys `command=`) strips the wrapper +layer and passes commands directly to `colibri-mcp`. A single SSH connection +stays alive for the daemon's lifetime. + +### Step 4: Install clawdie-hw-probe + +```bash +# From the 0.12 feature branch (on USB or scp from OSA): +# Already committed to clawdie-iso/live/operator-session/clawdie-hw-probe +sudo install -m 755 /home/clawdie/ai/clawdie-iso/live/operator-session/clawdie-hw-probe \ + /usr/local/bin/clawdie-hw-probe + +# Test: +sudo clawdie-hw-probe | python3.11 -m json.tool | head -10 +``` + +### Step 5: Restart daemon + verify + +```bash +sudo service colibri_daemon restart + +# Verify external MCP is connected: +colibri-mcp tools 2>&1 | grep external +# Should show: +# colibri_external_mcp_servers +# colibri_external_mcp_list_tools +# colibri_external_mcp_call_tool + +# List mother's tools: +colibri-mcp --external-config /usr/local/etc/colibri/external-mcp.json \ + --external-call tools 2>&1 | head -10 +``` + +## End-to-end test + +```bash +# 1. Run hw-probe +HW_JSON=$(sudo clawdie-hw-probe 2>/dev/null) + +# 2. Send to mother via MCP call +# (through colibri daemon's external MCP — needs the daemon to be running +# with the mother server registered) + +# 3. Verify on mother: +sudo -u postgres psql -d mother_hive -c "SELECT hostname, status, capabilities FROM usb_nodes;" +``` + +Expected: the USB node appears in PostgreSQL with hardware profile and +derived capabilities (`has_gpu`, `ram_gb`, `cpu_cores`, etc.). + +## Open questions + +1. **SSH key persistence**: The seed partition should stage the key on + CLAWDIESEED so the operator doesn't manually copy it. The seed importer + (`clawdie-live-seed`) already supports `/ssh/` files. + The operator places `mother-mcp` and `mother-mcp.pub` in the seed dir, + reboots, and the key is automatically installed. + +2. **Autospawn integration**: After autospawn (zot starts), the agent's first + action should be to run `clawdie-hw-probe` and call + `colibri_external_mcp_call_tool(server="mother", tool="node_register")`. + This requires a `node_register` MCP tool on mother (not yet implemented — + currently only `geodesic-dome` and `mother-build` are registered). + +3. **Tailscale dependency**: The USB needs Tailscale to be up before MCP works. + This is already the case — Tailscale auth is part of the bootstrap flow. + Without Tailscale, the USB can't reach mother at its Tailscale IP. + +## Implementation priority + +| Step | Effort | Blocked by | +|---|---|---| +| Step 1: SSH key + config | Manual (one-time) | Nothing | +| Step 2: Enable external MCP | 1 line | Nothing | +| Step 3: external-mcp.json | 10 lines JSON | Nothing | +| Step 4: Install hw-probe | Copy file | 0.12 branch | +| Step 5: Restart + verify | 2 commands | Steps 1-4 | +| Step 6: Seed partition auto-key | Code change | 0.12 ISO build | +| Step 7: node_register MCP tool on mother | New MCP tool | Step 6 |