make-identity-episode19-demo.sh
bash
sha256:5472be4fece32b606c308fad9d57295ce327955fb2b87b8beec6ea1626050473
Add published YouTube URL to Episode 00
Sonnet 5
8 hours ago
| 1 | #!/usr/bin/env bash |
| 2 | # Drives real MuseHub identity/genealogy calls for Episode 19 ("Identity |
| 3 | # Profiles"). Requires the local MuseHub dev stack running. IMPORTANT: per |
| 4 | # .museagent.md, always restart + verify freshness of musehub/musehub_worker |
| 5 | # before trusting results here if source has changed recently -- a stale |
| 6 | # container produced a false crash during this episode's own research. |
| 7 | set -euo pipefail |
| 8 | |
| 9 | HUB="https://localhost:1337" |
| 10 | SUFFIX="$(date +%s)" |
| 11 | HANDLE_A="ep19-agent-a-$SUFFIX" |
| 12 | HANDLE_B="ep19-agent-b-$SUFFIX" |
| 13 | |
| 14 | gen_key() { |
| 15 | python3 -c " |
| 16 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey |
| 17 | import base64, hashlib |
| 18 | priv = Ed25519PrivateKey.generate() |
| 19 | pub_raw = priv.public_key().public_bytes_raw() |
| 20 | print(base64.urlsafe_b64encode(pub_raw).decode().rstrip('=')) |
| 21 | print('sha256:' + hashlib.sha256(pub_raw).hexdigest()) |
| 22 | print(priv.private_bytes_raw().hex()) |
| 23 | " |
| 24 | } |
| 25 | |
| 26 | echo "=== Part 1: register a human-spawned agent (real API call) ===" |
| 27 | readarray -t KEY_A < <(gen_key) |
| 28 | PUB_A="${KEY_A[0]}"; FP_A="${KEY_A[1]}"; PRIV_A="${KEY_A[2]}" |
| 29 | BODY_A="{\"handle\":\"$HANDLE_A\",\"public_key_b64\":\"ed25519:$PUB_A\",\"fingerprint\":\"$FP_A\",\"algorithm\":\"ed25519\",\"agent_model\":\"claude-sonnet-5\",\"label\":\"episode-19-agent-a\"}" |
| 30 | printf '%s' "$BODY_A" > /tmp/ep19_reg_a.json |
| 31 | HEADER=$(muse sign header --method POST --path "/api/identities/agent" --hub "$HUB" --body-file /tmp/ep19_reg_a.json --json | python3 -c "import json,sys; print(json.load(sys.stdin)['header_value'])") |
| 32 | curl -sk -X POST "$HUB/api/identities/agent" -H "Authorization: $HEADER" -H "Content-Type: application/json" --data-binary @/tmp/ep19_reg_a.json | python3 -m json.tool |
| 33 | |
| 34 | echo |
| 35 | echo "=== Part 2: an agent spawning another agent, authenticated with its OWN key ===" |
| 36 | readarray -t KEY_B < <(gen_key) |
| 37 | PUB_B="${KEY_B[0]}"; FP_B="${KEY_B[1]}" |
| 38 | BODY_B="{\"handle\":\"$HANDLE_B\",\"public_key_b64\":\"ed25519:$PUB_B\",\"fingerprint\":\"$FP_B\",\"algorithm\":\"ed25519\",\"agent_model\":\"claude-sonnet-5\",\"label\":\"episode-19-agent-b\"}" |
| 39 | printf '%s' "$BODY_B" > /tmp/ep19_reg_b.json |
| 40 | python3 - "$PRIV_A" "$HANDLE_A" <<'PY' |
| 41 | import sys, time, hashlib, base64, pathlib |
| 42 | sys.path.insert(0, str(pathlib.Path.home() / "ecosystem" / "muse")) |
| 43 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey |
| 44 | from muse.core.msign import canonical_message |
| 45 | |
| 46 | priv = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(sys.argv[1])) |
| 47 | handle_a = sys.argv[2] |
| 48 | body = pathlib.Path("/tmp/ep19_reg_b.json").read_bytes() |
| 49 | ts = int(time.time()) |
| 50 | msg = canonical_message("POST", "/api/identities/agent", ts, body, host="localhost:1337") |
| 51 | sig_b64 = base64.urlsafe_b64encode(priv.sign(msg)).decode().rstrip("=") |
| 52 | header = f'MSign handle="{handle_a}" alg="ed25519" ts={ts} sig="{sig_b64}"' |
| 53 | pathlib.Path("/tmp/ep19_header_b.txt").write_text(header) |
| 54 | PY |
| 55 | HEADER=$(cat /tmp/ep19_header_b.txt) |
| 56 | curl -sk -X POST "$HUB/api/identities/agent" -H "Authorization: $HEADER" -H "Content-Type: application/json" --data-binary @/tmp/ep19_reg_b.json | python3 -m json.tool |
| 57 | |
| 58 | echo |
| 59 | echo "=== Part 3: the honest gap -- no genealogy query exists for this real chain ===" |
| 60 | curl -sk "$HUB/api/openapi.json" | python3 -c " |
| 61 | import json, sys |
| 62 | d = json.load(sys.stdin) |
| 63 | hits = [p for p in d['paths'] if 'genealog' in p.lower() or 'ancestor' in p.lower() or 'root-distance' in p.lower()] |
| 64 | print('genealogy-related endpoints found:', hits or 'NONE') |
| 65 | " |
| 66 | |
| 67 | echo |
| 68 | echo "=== Part 4: the same real chain, answered directly by the engine that IS correct ===" |
| 69 | python3 - "$HANDLE_A" "$HANDLE_B" <<'PY' |
| 70 | import sys, pathlib |
| 71 | sys.path.insert(0, str(pathlib.Path.home() / "ecosystem" / "musehub")) |
| 72 | from musehub.graph.service import IdentityGraphService |
| 73 | from musehub.graph.dag import NodeType |
| 74 | |
| 75 | handle_a, handle_b = sys.argv[1], sys.argv[2] |
| 76 | |
| 77 | svc = IdentityGraphService() |
| 78 | svc.add_identity("gabriel", NodeType.HUMAN) |
| 79 | svc.add_identity(handle_a, NodeType.AGENT) |
| 80 | svc.add_identity(handle_b, NodeType.AGENT) |
| 81 | svc.add_spawn("gabriel", handle_a) |
| 82 | svc.add_spawn(handle_a, handle_b) |
| 83 | |
| 84 | print("distance(gabriel): ", svc.root_distance("gabriel")) |
| 85 | print("distance(agent-a): ", svc.root_distance(handle_a)) |
| 86 | print("distance(agent-b): ", svc.root_distance(handle_b)) |
| 87 | print("human_ancestors(agent-b):", svc.human_ancestors(handle_b)) |
| 88 | |
| 89 | print() |
| 90 | print("--- attempt to close a cycle: agent-b spawns agent-a ---") |
| 91 | try: |
| 92 | svc.add_spawn(handle_b, handle_a) |
| 93 | print("NO ERROR RAISED -- would be a bug") |
| 94 | except Exception as e: |
| 95 | print(f"Correctly rejected: {type(e).__name__}: {e}") |
| 96 | PY |
| 97 | |
| 98 | echo |
| 99 | echo "=== Part 5: attestations -- real infrastructure, empty in this dev DB ===" |
| 100 | HEADER=$(muse sign header --method GET --path "/api/profiles/attestation-types" --hub "$HUB" --json | python3 -c "import json,sys; print(json.load(sys.stdin)['header_value'])") |
| 101 | curl -sk "$HUB/api/profiles/attestation-types" -H "Authorization: $HEADER" | python3 -m json.tool |
| 102 | |
| 103 | rm -f /tmp/ep19_reg_a.json /tmp/ep19_reg_b.json /tmp/ep19_header_b.txt |
| 104 | |
| 105 | echo |
| 106 | echo "Demo complete." |
File History
1 commit
sha256:5472be4fece32b606c308fad9d57295ce327955fb2b87b8beec6ea1626050473
Add published YouTube URL to Episode 00
Sonnet 5
8 hours ago