feat(mother): Phase 1a — machine_id + local LLM schema
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled

Schema (mother_schema.sql):
- Added machine_id TEXT UNIQUE column to hive_nodes
- Idempotent migration ALTER TABLE + constraint
- Extended derive_capabilities(): ollama_available, llama_cpp_available
  with model lists, plus inference_tier (local-fast/slow/remote-only)

MCP (node-register-mcp):
- Accepts optional machine_id argument (null for unsecured nodes)
- Passes through to INSERT/UPSERT with NULLIF for empty strings
- COALESCE preserves existing machine_id on conflict (don't lose
  stable identity if hostname changes)

Sam & Hermes
This commit is contained in:
123kupola 2026-06-27 13:19:19 +02:00
parent c35bbd273a
commit 14697a07dd
2 changed files with 44 additions and 4 deletions

View file

@ -22,6 +22,9 @@ END $$;
CREATE TABLE IF NOT EXISTS hive_nodes (
id SERIAL PRIMARY KEY,
hostname TEXT NOT NULL UNIQUE,
-- Stable identity across reboots (generated on first password set).
-- NULL for unsecured nodes; used for UPSERT deduplication.
machine_id TEXT UNIQUE,
-- Provisioning medium: live-usb | disk | vps | mother | unknown.
node_type TEXT NOT NULL DEFAULT 'unknown',
last_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
@ -35,6 +38,13 @@ CREATE TABLE IF NOT EXISTS hive_nodes (
);
-- Add node_type to tables migrated from the old usb_nodes definition.
ALTER TABLE hive_nodes ADD COLUMN IF NOT EXISTS node_type TEXT NOT NULL DEFAULT 'unknown';
ALTER TABLE hive_nodes ADD COLUMN IF NOT EXISTS machine_id TEXT;
-- Make unique if not already; ignore duplicate-key errors from pre-existing NULLs.
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'hive_nodes_machine_id_key') THEN
ALTER TABLE hive_nodes ADD CONSTRAINT hive_nodes_machine_id_key UNIQUE (machine_id);
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_nodes_status ON hive_nodes (status);
CREATE INDEX IF NOT EXISTS idx_nodes_last_seen ON hive_nodes (last_seen DESC);
@ -102,6 +112,29 @@ BEGIN
IF wifi IS NOT NULL AND jsonb_array_length(wifi) > 0 THEN
caps := caps || '{"has_wifi": true}'::JSONB;
END IF;
-- Local LLM detection (ollama + llama.cpp probes from hw-probe).
IF NEW.hw_profile->'local_llm' IS NOT NULL THEN
IF NEW.hw_profile->'local_llm'->'ollama'->>'available' = 'true' THEN
caps := caps || jsonb_build_object(
'ollama_available', true,
'ollama_models', NEW.hw_profile->'local_llm'->'ollama'->'models'
);
END IF;
IF NEW.hw_profile->'local_llm'->'llama_cpp'->>'available' = 'true' THEN
caps := caps || jsonb_build_object(
'llama_cpp_available', true,
'llama_cpp_models', NEW.hw_profile->'local_llm'->'llama_cpp'->'models'
);
END IF;
END IF;
-- Inference tier: what this node can run locally.
IF caps->>'has_gpu' = 'true' AND (caps->>'ollama_available' = 'true' OR caps->>'llama_cpp_available' = 'true') THEN
caps := caps || '{"inference_tier": "local-fast"}'::JSONB;
ELSIF caps->>'can_run_local_llm' = 'true' OR caps->>'ollama_available' = 'true' OR caps->>'llama_cpp_available' = 'true' THEN
caps := caps || '{"inference_tier": "local-slow"}'::JSONB;
ELSE
caps := caps || '{"inference_tier": "remote-only"}'::JSONB;
END IF;
NEW.capabilities := caps;
NEW.last_cap_sync := now();
RETURN NEW;

View file

@ -8,11 +8,15 @@
# can_run_local_llm, has_wifi, etc. on INSERT/UPDATE.
#
# Expected input:
# {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"node_register","arguments":{"hostname":"clawdie-node","node_type":"live-usb","hw_profile":{...}}}}
# {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"node_register","arguments":{"hostname":"clawdie-node","node_type":"live-usb","machine_id":"a1b2...","hw_profile":{...}}}}
#
# Output on success:
# {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"registered\":true,\"hostname\":\"clawdie-node\",\"capabilities\":{...}}"}]}}
#
# machine_id is optional (null for unsecured nodes). When present, it provides
# stable identity across hostname changes and is passed through to the
# hive_nodes.machine_id UNIQUE column.
#
# Security: psql :'variable' substitution expands to a safely single-quoted SQL
# string literal — no shell interpolation touches the JSON blobs.
#
@ -31,6 +35,7 @@ INPUT=$(cat)
ID=$(echo "$INPUT" | jq -r '.id // "1"')
HOSTNAME=$(echo "$INPUT" | jq -r '.params.arguments.hostname // ""')
NODE_TYPE=$(echo "$INPUT" | jq -r '.params.arguments.node_type // "unknown"')
MACHINE_ID=$(echo "$INPUT" | jq -r '.params.arguments.machine_id // ""')
HW_PROFILE=$(echo "$INPUT" | jq -c '.params.arguments.hw_profile // {}')
# node_type is a small enum; validate so a bad value can't land an odd row.
@ -59,12 +64,14 @@ fi
# would continue past a failed statement and exit 0, hiding failures. stderr is
# folded into RESULT so the error branch can report what went wrong.
RESULT=$(psql -d "$DB" -tA -v ON_ERROR_STOP=1 \
-v hostname="$HOSTNAME" -v node_type="$NODE_TYPE" -v hw_profile="$HW_PROFILE" 2>&1 <<'PSQL'
-v hostname="$HOSTNAME" -v node_type="$NODE_TYPE" \
-v machine_id="$MACHINE_ID" -v hw_profile="$HW_PROFILE" 2>&1 <<'PSQL'
BEGIN;
INSERT INTO hive_nodes (hostname, node_type, hw_profile, status, last_seen)
VALUES (:'hostname', :'node_type', (:'hw_profile')::jsonb, 'online', now())
INSERT INTO hive_nodes (hostname, node_type, machine_id, hw_profile, status, last_seen)
VALUES (:'hostname', :'node_type', NULLIF(:'machine_id', ''), (:'hw_profile')::jsonb, 'online', now())
ON CONFLICT (hostname) DO UPDATE
SET node_type = EXCLUDED.node_type,
machine_id = COALESCE(NULLIF(EXCLUDED.machine_id, ''), hive_nodes.machine_id),
hw_profile = EXCLUDED.hw_profile,
status = 'online',
last_seen = now();