feature/0.12.0 #123

Merged
clawdie merged 2 commits from feature/0.12.0 into main 2026-06-23 16:55:44 +02:00
2 changed files with 411 additions and 0 deletions

239
docs/SETUP-USB-TO-MOTHER.md Normal file
View file

@ -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'` |

View file

@ -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'
<private key content>
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 `<agent>/ssh/<keyname>` 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 |