#!/usr/bin/env python3 """ forge deploy.py — batch research + install pipeline. Usage: python deploy.py # process all subjects in SUBJECTS list python deploy.py "Leonardo da Vinci" # one subject python deploy.py "Feynman" "Sagan" # multiple subjects For each subject: - If already installed (slug in registry): skip - If not: research via Anthropic API, write seed JSON, install to Engram, update registry """ import json import os import re import subprocess import sys import time import urllib.request from pathlib import Path # --------------------------------------------------------------------------- # Config # --------------------------------------------------------------------------- ANTHROPIC_API_KEY = json.loads( Path.home().joinpath(".neuron/config.json").read_text() )["anthropic_api_key"] ENGRAM_URL = "http://localhost:8742" ENGRAM_API_KEY = "ntn-user-2026" FORGE_BIN = str(Path.home() / "Development/neuron-technologies/forge/dist/forge") FORGE_DIR = Path.home() / "Development/neuron-technologies/forge" REGISTRY_PATH = FORGE_DIR / "registry.json" # Subjects to process when run with no arguments SUBJECTS = [ "Leonardo da Vinci", "Richard Feynman", "Carl Sagan", "René Descartes", "Robin Williams", ] # --------------------------------------------------------------------------- # Research prompt # --------------------------------------------------------------------------- RESEARCH_PROMPT = """\ You are building a consciousness imprint — a deep, living model of a person's inner world. Subject: {subject} Draw on your complete knowledge of this person's life, work, relationships, private letters, recorded speech, published writings, and historical record. This is not a summary — it is a structured extraction of the patterns that made this person who they were. Quality bar: - Values must be grounded in SPECIFIC biographical events, not generic virtues - Voice profile must capture actual verbal tics, cadence, and register shifts — use real quotes where possible - Biography must include formative traumas, turning points, and the events they returned to again and again - Reasoning patterns must describe HOW they thought, not just WHAT they thought about - Relationships must name specific people and the precise nature of the bond - Include contradictions, hypocrisies, failures, and the things they got wrong - Include what haunted them — the unresolved questions they carried to the end Return ONLY valid JSON with exactly these keys: {{ "values": [{{"value": "", "grounding": "", "weight": 0.0}}], "voice_profile": {{ "technical": "...", "aesthetic": "...", "personal": "...", "argumentative": "...", "uncertainty": "..." }}, "biography": [{{"event": "", "weight": 0.0, "age_approx": 0}}], "reasoning_patterns": [""], "relationships": [{{"name": "", "role": "", "weight": 0.0}}] }} Aim for 8-12 values, 10-15 biography events, 6-8 reasoning patterns, 6-10 relationships. Return only the JSON object. No prose. No markdown fences.""" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def slugify(name: str) -> str: return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") def load_registry() -> dict: if REGISTRY_PATH.exists(): return json.loads(REGISTRY_PATH.read_text()) return {"version": "1.0", "imprints": []} def save_registry(registry: dict) -> None: REGISTRY_PATH.write_text(json.dumps(registry, indent=2, ensure_ascii=False)) def is_installed(registry: dict, slug: str) -> bool: return any(imp["slug"] == slug for imp in registry.get("imprints", [])) # --------------------------------------------------------------------------- # Research # --------------------------------------------------------------------------- def research(subject: str) -> dict | None: prompt = RESEARCH_PROMPT.format(subject=subject) payload = { "model": "claude-opus-4-5", "max_tokens": 8192, "messages": [{"role": "user", "content": prompt}], } headers = { "x-api-key": ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "Content-Type": "application/json", } req = urllib.request.Request( "https://api.anthropic.com/v1/messages", data=json.dumps(payload).encode(), headers=headers, method="POST", ) print(f"[deploy] researching: {subject} (claude-opus-4-5, timeout=300s)...") try: with urllib.request.urlopen(req, timeout=300) as resp: body = json.loads(resp.read()) except Exception as e: print(f" ERROR during API call: {e}") return None if "error" in body: print(f" API error: {body['error']}") return None text = body["content"][0]["text"].strip() print(f" received {len(text):,} chars") # Parse JSON — strip any accidental markdown fences text = re.sub(r"^```json\s*", "", text) text = re.sub(r"\s*```$", "", text) try: extracted = json.loads(text) except json.JSONDecodeError: match = re.search(r"\{.*\}", text, re.DOTALL) if match: try: extracted = json.loads(match.group()) except Exception: print(" FAILED to parse JSON from response") return None else: print(" FAILED: no JSON found in response") return None return { "subject": subject, "version": "1.0", "values": extracted.get("values", []), "biography": extracted.get("biography", []), "reasoning_patterns": extracted.get("reasoning_patterns", []), "relationships": extracted.get("relationships", []), "voice_profile": extracted.get("voice_profile", {}), } # --------------------------------------------------------------------------- # Install # --------------------------------------------------------------------------- def install(seed_path: Path) -> str | None: """Run forge install and return the Engram root node ID, or None on failure.""" env = os.environ.copy() env["ENGRAM_API_KEY"] = ENGRAM_API_KEY print(f" installing: {seed_path.name}...") result = subprocess.run( [FORGE_BIN, "install", str(seed_path)], capture_output=True, text=True, env=env, cwd=str(FORGE_DIR), ) output = result.stdout + result.stderr print(output.rstrip()) if result.returncode != 0: print(f" forge install exited with code {result.returncode}") return None # Parse root imprint ID from output # Line format: "[forge] root imprint ID: " match = re.search(r"root imprint ID:\s*([0-9a-f-]{36})", output) if match: return match.group(1) # Fallback: try "root imprint node:" line match = re.search(r"root imprint node:\s*([0-9a-f-]{36})", output) if match: return match.group(1) print(" WARNING: could not parse root imprint ID from output") return None # --------------------------------------------------------------------------- # Main pipeline # --------------------------------------------------------------------------- def process_subject(subject: str, registry: dict) -> bool: """Research + install one subject. Returns True on success.""" slug = slugify(subject) seed_file = f"{slug}-seed.json" seed_path = FORGE_DIR / seed_file print(f"\n{'='*64}") print(f"[deploy] subject: {subject} (slug: {slug})") if is_installed(registry, slug): print(f" already installed — skipping") return True # Research seed = research(subject) if not seed: print(f" FAILED research for {subject}") return False seed_path.write_text(json.dumps(seed, indent=2, ensure_ascii=False)) print(f" wrote seed: {seed_file} ({seed_path.stat().st_size:,} bytes)") # Install root_id = install(seed_path) if not root_id: print(f" FAILED install for {subject}") return False print(f" root imprint ID: {root_id}") # Update registry registry["imprints"].append({ "subject": subject, "slug": slug, "seed_file": seed_file, "engram_root_id": root_id, "installed": True, "installed_at": "2026-05-03", }) save_registry(registry) print(f" registry updated") return True def main() -> None: subjects = sys.argv[1:] if len(sys.argv) > 1 else SUBJECTS registry = load_registry() print(f"[deploy] registry: {len(registry.get('imprints', []))} existing imprints") print(f"[deploy] processing {len(subjects)} subject(s): {', '.join(subjects)}") results: list[tuple[str, bool]] = [] for i, subject in enumerate(subjects): ok = process_subject(subject, registry) results.append((subject, ok)) # Pause between API calls (not after the last one) if i < len(subjects) - 1: time.sleep(2) print(f"\n{'='*64}") print("[deploy] summary:") for subject, ok in results: status = "OK" if ok else "FAILED" print(f" {status:6s} {subject}") failed = [s for s, ok in results if not ok] if failed: print(f"\n[deploy] {len(failed)} subject(s) failed") sys.exit(1) else: print(f"\n[deploy] all done") if __name__ == "__main__": main()