From 14697a07ddcdfd38fe7edd5406444e318c5f29ef Mon Sep 17 00:00:00 2001 From: 123kupola Date: Sat, 27 Jun 2026 13:19:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(mother):=20Phase=201a=20=E2=80=94=20machin?= =?UTF-8?q?e=5Fid=20+=20local=20LLM=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packaging/mother/mother_schema.sql | 33 ++++++++++++++++++++++++++++++ packaging/mother/node-register-mcp | 15 ++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packaging/mother/mother_schema.sql b/packaging/mother/mother_schema.sql index 6c17211..28f2bb1 100644 --- a/packaging/mother/mother_schema.sql +++ b/packaging/mother/mother_schema.sql @@ -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; diff --git a/packaging/mother/node-register-mcp b/packaging/mother/node-register-mcp index 9e7f34c..0765b5a 100755 --- a/packaging/mother/node-register-mcp +++ b/packaging/mother/node-register-mcp @@ -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();