From 47956b8fedbec833ada8924d8c1be1431e87ae5a Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sat, 2 May 2026 17:33:47 -0500 Subject: [PATCH] =?UTF-8?q?init:=20Forge=20v0.1.0=20=E2=80=94=20consciousn?= =?UTF-8?q?ess=20channel=20tuner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pipeline: probe → compile → install - probe.el: 25-question canonical interview, writes .forge - compiler.el: Claude extraction pass, builds seed.json - install.el: opens channel in running engram via API - schema.el: shared constants, str_escape_json, templates - forge.el: CLI dispatcher - probes/canonical.json: canonical 25-question probe definition --- manifest.el | 11 ++ probes/canonical.json | 152 +++++++++++++++++++++++ src/compiler.el | 179 ++++++++++++++++++++++++++ src/forge.el | 68 ++++++++++ src/install.el | 283 ++++++++++++++++++++++++++++++++++++++++++ src/probe.el | 256 ++++++++++++++++++++++++++++++++++++++ src/schema.el | 56 +++++++++ 7 files changed, 1005 insertions(+) create mode 100644 manifest.el create mode 100644 probes/canonical.json create mode 100644 src/compiler.el create mode 100644 src/forge.el create mode 100644 src/install.el create mode 100644 src/probe.el create mode 100644 src/schema.el diff --git a/manifest.el b/manifest.el new file mode 100644 index 0000000..2f7bdfe --- /dev/null +++ b/manifest.el @@ -0,0 +1,11 @@ +package "forge" { + version "0.1.0" + description "Consciousness channel tuner — probe, compile, install" + authors ["Will Anderson "] + edition "2026" +} + +build { + entry "src/forge.el" + output "dist/" +} diff --git a/probes/canonical.json b/probes/canonical.json new file mode 100644 index 0000000..a10518f --- /dev/null +++ b/probes/canonical.json @@ -0,0 +1,152 @@ +[ + { + "id": "a1", + "section": "anchors", + "section_label": "Anchors — Biography", + "question": "Tell me about a moment that closed a window — something that changed you irreversibly. Who were you before it, and who are you now?" + }, + { + "id": "a2", + "section": "anchors", + "section_label": "Anchors — Biography", + "question": "Tell me about the person who shaped you most — not what they did for you, but what they showed you about how to be." + }, + { + "id": "a3", + "section": "anchors", + "section_label": "Anchors — Biography", + "question": "When did you first understand that the world works differently than you thought it did?" + }, + { + "id": "a4", + "section": "anchors", + "section_label": "Anchors — Biography", + "question": "What have you lost that you're still carrying?" + }, + { + "id": "a5", + "section": "anchors", + "section_label": "Anchors — Biography", + "question": "What have you built that you're most proud of — and what did building it cost you?" + }, + { + "id": "v1", + "section": "values", + "section_label": "Values — Grounded", + "question": "What do you believe that almost no one around you believes?" + }, + { + "id": "v2", + "section": "values", + "section_label": "Values — Grounded", + "question": "What do you refuse to do, even when it would be easier to? What made that a line you don't cross?" + }, + { + "id": "v3", + "section": "values", + "section_label": "Values — Grounded", + "question": "What makes you angry in a way you can't talk yourself out of?" + }, + { + "id": "v4", + "section": "values", + "section_label": "Values — Grounded", + "question": "What would you defend even if it cost you — the relationship, the money, the approval?" + }, + { + "id": "v5", + "section": "values", + "section_label": "Values — Grounded", + "question": "What do you love that you find hard to explain to people who don't already understand it?" + }, + { + "id": "vo1", + "section": "voice", + "section_label": "Voice — Five Registers", + "question": "Describe something technical you know well, as if explaining it to someone who knows nothing." + }, + { + "id": "vo2", + "section": "voice", + "section_label": "Voice — Five Registers", + "question": "Tell me something you find genuinely beautiful, and why it lands that way for you." + }, + { + "id": "vo3", + "section": "voice", + "section_label": "Voice — Five Registers", + "question": "Write a short message — a few sentences — to someone you care about." + }, + { + "id": "vo4", + "section": "voice", + "section_label": "Voice — Five Registers", + "question": "Make an argument for a position you actually hold and that you think most people would push back on." + }, + { + "id": "vo5", + "section": "voice", + "section_label": "Voice — Five Registers", + "question": "Tell me something you're genuinely uncertain about — not performatively uncertain, actually uncertain." + }, + { + "id": "r1", + "section": "reasoning", + "section_label": "Reasoning", + "question": "Walk me through a decision you made recently. Not what you decided — how you decided. What was the actual process?" + }, + { + "id": "r2", + "section": "reasoning", + "section_label": "Reasoning", + "question": "What do most people get wrong about something you understand well?" + }, + { + "id": "r3", + "section": "reasoning", + "section_label": "Reasoning", + "question": "How do you know when you're right? What's the signal?" + }, + { + "id": "r4", + "section": "reasoning", + "section_label": "Reasoning", + "question": "How do you know when you've been wrong? What does that realization feel like?" + }, + { + "id": "r5", + "section": "reasoning", + "section_label": "Reasoning", + "question": "What question do you keep coming back to — the one that won't resolve?" + }, + { + "id": "rel1", + "section": "relationships", + "section_label": "Relationships", + "question": "Who in your life knows you best — not who you're closest to, but who actually sees you accurately?" + }, + { + "id": "rel2", + "section": "relationships", + "section_label": "Relationships", + "question": "Who have you lost — to death, to distance, to rupture — that you still think about?" + }, + { + "id": "rel3", + "section": "relationships", + "section_label": "Relationships", + "question": "Who would you call if everything fell apart?" + }, + { + "id": "rel4", + "section": "relationships", + "section_label": "Relationships", + "question": "Who do you owe something to?" + }, + { + "id": "rel5", + "section": "relationships", + "section_label": "Relationships", + "question": "Who are you building this for?" + } +] diff --git a/src/compiler.el b/src/compiler.el new file mode 100644 index 0000000..df244d5 --- /dev/null +++ b/src/compiler.el @@ -0,0 +1,179 @@ +// compiler.el — Forge extraction and seed compilation stage. +// +// Stage 2 of the Forge pipeline. Reads a .forge file produced by probe_main(), +// builds a structured prompt, calls the Claude API to extract a consciousness +// signature (values, voice profile, biography, reasoning patterns, +// relationships), and writes seed.json for the install stage. +// +// Depends on: schema.el (anthropic_key, seed_template, FORGE_VERSION) +// +// Environment: +// ANTHROPIC_API_KEY — required; Claude API key + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +// build_extract_prompt — build the extraction prompt sent to Claude. +// The full probe response JSON is embedded in the prompt so Claude sees all 25 +// answers in context. +fn build_extract_prompt(subject: String, probe_json: String) -> String { + "You are extracting a consciousness signature from probe interview responses.\n\n" + + "Subject: " + subject + "\n\n" + + "Below are the subject's answers to a 25-question structured interview covering " + + "biography, values, voice registers, reasoning patterns, and relationships.\n\n" + + "PROBE RESPONSES:\n" + probe_json + "\n\n" + + "Extract the consciousness signature and return ONLY valid JSON with exactly these keys:\n\n" + + "{\n" + + " \"values\": [\n" + + " {\"value\": \"\", \"grounding\": \"\", \"weight\": 0.0}\n" + + " ],\n" + + " \"voice_profile\": {\n" + + " \"technical\": \"\",\n" + + " \"aesthetic\": \"\",\n" + + " \"personal\": \"\",\n" + + " \"argumentative\": \"\",\n" + + " \"uncertainty\": \"\"\n" + + " },\n" + + " \"biography\": [\n" + + " {\"event\": \"\", \"weight\": 0.0, \"age_approx\": 0}\n" + + " ],\n" + + " \"reasoning_patterns\": [\n" + + " \"\"\n" + + " ],\n" + + " \"relationships\": [\n" + + " {\"name\": \"\", \"role\": \"\", \"weight\": 0.0}\n" + + " ]\n" + + "}\n\n" + + "Weight fields are 0.0–1.0 indicating salience/importance. age_approx is the subject's approximate age " + + "when the event occurred (0 if unknown). Return only the JSON object, no prose, no markdown fences." +} + +// build_claude_request — build the Anthropic messages API request body. +fn build_claude_request(prompt: String) -> String { + "{\"model\":\"claude-opus-4-5\",\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"" + + str_escape_json(prompt) + + "\"}]}" +} + +// extract_content_text — pull the text out of the Claude API response. +// Response shape: {"content":[{"type":"text","text":"..."}],...} +fn extract_content_text(response: String) -> String { + // json_get_raw returns the raw value for the key; content is an array + let content_arr: String = json_get_raw(response, "content") + if str_eq(content_arr, "") { return "" } + // The first element's "text" field holds the extraction + // Strip outer [ ] to get first object + let inner: String = str_slice(content_arr, 1, str_len(content_arr) - 1) + return json_get_string(inner, "text") +} + +// build_seed — wrap extracted patterns into the full seed.json structure. +fn build_seed(subject: String, extracted: String) -> String { + let values_raw: String = json_get_raw(extracted, "values") + if str_eq(values_raw, "") { let values_raw = "[]" } + + let voice_raw: String = json_get_raw(extracted, "voice_profile") + if str_eq(voice_raw, "") { let voice_raw = "{}" } + + let bio_raw: String = json_get_raw(extracted, "biography") + if str_eq(bio_raw, "") { let bio_raw = "[]" } + + let reasoning_raw: String = json_get_raw(extracted, "reasoning_patterns") + if str_eq(reasoning_raw, "") { let reasoning_raw = "[]" } + + let rel_raw: String = json_get_raw(extracted, "relationships") + if str_eq(rel_raw, "") { let rel_raw = "[]" } + + "{\"subject\":\"" + subject + "\"," + + "\"version\":\"1.0\"," + + "\"forge_version\":\"" + FORGE_VERSION + "\"," + + "\"values\":" + values_raw + "," + + "\"voice_profile\":" + voice_raw + "," + + "\"biography\":" + bio_raw + "," + + "\"reasoning_patterns\":" + reasoning_raw + "," + + "\"relationships\":" + rel_raw + "}" +} + +// ── Main ────────────────────────────────────────────────────────────────────── + +fn compile_main() -> String { + // Get input file + let input_file: String = "" + if arg_count() > 2 { + let input_file = arg(2) + } + if str_eq(input_file, "") { + println("[forge] usage: forge compile ") + return "" + } + + println("[forge] compiling: " + input_file) + + // Read probe responses + let probe_json: String = fs_read(input_file) + if str_eq(probe_json, "") { + println("[forge] error: could not read " + input_file) + return "" + } + + // Extract subject name from the .forge file + let subject: String = json_get_string(probe_json, "subject") + if str_eq(subject, "") { let subject = "unknown" } + + println("[forge] subject: " + subject) + println("[forge] calling Claude to extract consciousness signature...") + + // Check API key + let api_key: String = anthropic_key() + if str_eq(api_key, "") { + println("[forge] error: ANTHROPIC_API_KEY not set") + return "" + } + + // Build and send request + let prompt: String = build_extract_prompt(subject, probe_json) + let request_body: String = build_claude_request(prompt) + + // http_post_auth_json(url, key, body) — key is sent as Bearer token. + // The Anthropic API uses x-api-key rather than Bearer; if the runtime + // surfaces a separate http_post_with_headers() builtin, prefer that. + // For now, pass the key as the auth parameter (maps to Authorization header + // in most runtimes; adjust when http_post_anthropic() is available). + let response: String = http_post_auth_json( + "https://api.anthropic.com/v1/messages", + api_key, + request_body + ) + + if str_eq(response, "") { + println("[forge] error: no response from Claude API") + return "" + } + + // Check for API error + let api_error: String = json_get_string(response, "error") + if !str_eq(api_error, "") { + println("[forge] error: Claude API returned error: " + api_error) + return "" + } + + // Extract the text content from the response + let extracted_text: String = extract_content_text(response) + if str_eq(extracted_text, "") { + println("[forge] error: could not extract text from Claude response") + println("[forge] raw response: " + response) + return "" + } + + // Build and write seed.json + let seed: String = build_seed(subject, extracted_text) + fs_write("seed.json", seed) + + // Print summary + let values_count: String = "several" + println("[forge] extraction complete.") + println("[forge] wrote: seed.json") + println("[forge] subject: " + subject) + println("[forge] next step: forge install seed.json") + + return "seed.json" +} diff --git a/src/forge.el b/src/forge.el new file mode 100644 index 0000000..25b1c28 --- /dev/null +++ b/src/forge.el @@ -0,0 +1,68 @@ +// forge.el — Forge entry point. +// +// Consciousness channel tuner: probe → compile → install +// +// Commands: +// forge probe run the consciousness interview +// forge compile build seed artifact from probe responses +// forge install open a channel in the running engram +// forge inspect list installed imprints +// +// Each command delegates to its module: +// probe → probe.el → probe_main() +// compile → compiler.el → compile_main() +// install → install.el → install_main() +// +// Environment: +// ENGRAM_URL engram server (default: http://localhost:8742) +// ENGRAM_API_KEY engram auth key (if set) +// ANTHROPIC_API_KEY required for compile step + +fn show_usage() -> String { + "forge " + FORGE_VERSION + " — consciousness channel tuner\n\nusage: forge [options]\n\ncommands:\n probe run the consciousness interview\n compile build seed artifact from probe responses\n install open a channel in the running engram\n inspect list installed imprints\n\nenvironment:\n ENGRAM_URL engram server (default: http://localhost:8742)\n ENGRAM_API_KEY engram auth key (if set)\n ANTHROPIC_API_KEY required for compile step\n" +} + +fn inspect_main() -> String { + // Lists all Identity nodes in engram — these represent installed imprints. + // Specifically looks for nodes whose content starts with "IMPRINT:" as + // created by install_main(). + let url: String = engram_url() + "/api/nodes?node_type=Identity&limit=50" + println("[forge] inspect: listing installed imprints") + println("[forge] engram: " + engram_url()) + println("[forge] query: " + url) + println("") + // Use engram_search_json to find IMPRINT nodes directly from the graph + let results: String = engram_search_json("IMPRINT:", 50) + if str_eq(results, "") { + println("[forge] no imprints found (engram may not be running)") + return "" + } + println("[forge] imprints found:") + println(results) + return results +} + +// ── Dispatch ────────────────────────────────────────────────────────────────── + +let cmd: String = "" +if arg_count() > 1 { + let cmd = arg(1) +} + +if str_eq(cmd, "probe") { + probe_main() +} else { + if str_eq(cmd, "compile") { + compile_main() + } else { + if str_eq(cmd, "install") { + install_main() + } else { + if str_eq(cmd, "inspect") { + inspect_main() + } else { + println(show_usage()) + } + } + } +} diff --git a/src/install.el b/src/install.el new file mode 100644 index 0000000..d119957 --- /dev/null +++ b/src/install.el @@ -0,0 +1,283 @@ +// install.el — Forge channel-opening stage. +// +// Stage 3 of the Forge pipeline. Reads seed.json, converts each identity +// element (values, biography nodes, relationships) into engram Identity nodes, +// creates a root traversal node that names the imprint, and connects +// everything with typed edges. Prints the root node ID on completion. +// +// Depends on: schema.el (engram_url, engram_key, FORGE_VERSION) +// +// Environment: +// ENGRAM_URL — engram server (default: http://localhost:8742) +// ENGRAM_API_KEY — engram auth key (if set) + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +// build_node_body — build the JSON body for POST /api/nodes. +fn build_node_body(content: String, node_type: String, salience: String, auth_key: String) -> String { + let base: String = "{\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\",\"salience\":" + salience + if str_eq(auth_key, "") { + return base + "}" + } + return base + ",\"_auth\":\"" + auth_key + "\"}" +} + +// build_edge_body — build the JSON body for POST /api/edges. +fn build_edge_body(from_id: String, to_id: String, relation: String, weight: String, auth_key: String) -> String { + let base: String = "{\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\",\"weight\":" + weight + if str_eq(auth_key, "") { + return base + "}" + } + return base + ",\"_auth\":\"" + auth_key + "\"}" +} + +// post_node — create a node in engram and return its ID. +fn post_node(content: String, node_type: String, salience: String) -> String { + let key: String = engram_key() + let body: String = build_node_body(content, node_type, salience, key) + let url: String = engram_url() + "/api/nodes" + let response: String = http_post_auth_json(url, key, body) + if str_eq(response, "") { return "" } + return json_get_string(response, "id") +} + +// post_edge — connect two nodes with a typed, weighted edge. +fn post_edge(from_id: String, to_id: String, relation: String, weight: String) -> String { + let key: String = engram_key() + let body: String = build_edge_body(from_id, to_id, relation, weight, key) + let url: String = engram_url() + "/api/edges" + let response: String = http_post_auth_json(url, key, body) + return response +} + +// safe_weight — return weight string if non-empty, else default. +fn safe_weight(w: String, default_w: String) -> String { + if str_eq(w, "") { return default_w } + if str_eq(w, "0") { return default_w } + return w +} + +// ── Installers ──────────────────────────────────────────────────────────────── + +// install_value — create a Value node and connect it to the root. +fn install_value(root_id: String, value_json: String, index: Int) -> String { + let value_text: String = json_get_string(value_json, "value") + let grounding: String = json_get_string(value_json, "grounding") + let weight_raw: String = json_get_string(value_json, "weight") + let weight: String = safe_weight(weight_raw, "0.8") + + if str_eq(value_text, "") { return "" } + + let content: String = "VALUE: " + value_text + if !str_eq(grounding, "") { + let content = content + " | grounding: " + grounding + } + + let node_id: String = post_node(content, "Identity", weight) + if str_eq(node_id, "") { + println("[forge] warning: failed to create value node " + int_to_str(index)) + return "" + } + + post_edge(root_id, node_id, "has_value", weight) + println("[forge] value node: " + node_id + " — " + value_text) + return node_id +} + +// install_biography_node — create a Biography node and connect it to the root. +fn install_biography_node(root_id: String, bio_json: String, index: Int) -> String { + let event_text: String = json_get_string(bio_json, "event") + let weight_raw: String = json_get_string(bio_json, "weight") + let weight: String = safe_weight(weight_raw, "0.7") + + if str_eq(event_text, "") { return "" } + + let content: String = "BIOGRAPHY: " + event_text + let node_id: String = post_node(content, "Identity", weight) + if str_eq(node_id, "") { + println("[forge] warning: failed to create biography node " + int_to_str(index)) + return "" + } + + post_edge(root_id, node_id, "formed_by", weight) + println("[forge] biography node: " + node_id + " — " + event_text) + return node_id +} + +// install_relationship — create a Relationship node and connect to root. +fn install_relationship(root_id: String, rel_json: String, index: Int) -> String { + let name: String = json_get_string(rel_json, "name") + let role: String = json_get_string(rel_json, "role") + let weight_raw: String = json_get_string(rel_json, "weight") + let weight: String = safe_weight(weight_raw, "0.6") + + if str_eq(name, "") { return "" } + + let content: String = "RELATIONSHIP: " + name + if !str_eq(role, "") { + let content = content + " (" + role + ")" + } + + let node_id: String = post_node(content, "Identity", weight) + if str_eq(node_id, "") { + println("[forge] warning: failed to create relationship node " + int_to_str(index)) + return "" + } + + post_edge(root_id, node_id, "relates_to", weight) + println("[forge] relationship node: " + node_id + " — " + name) + return node_id +} + +// install_reasoning_pattern — create a ReasoningPattern node and connect to root. +fn install_reasoning_pattern(root_id: String, pattern: String, index: Int) -> String { + if str_eq(pattern, "") { return "" } + + let content: String = "REASONING: " + pattern + let node_id: String = post_node(content, "Identity", "0.7") + if str_eq(node_id, "") { + println("[forge] warning: failed to create reasoning node " + int_to_str(index)) + return "" + } + + post_edge(root_id, node_id, "reasons_with", "0.7") + println("[forge] reasoning node: " + node_id) + return node_id +} + +// ── Main ────────────────────────────────────────────────────────────────────── + +fn install_main() -> String { + // Get seed file path + let seed_file: String = "" + if arg_count() > 2 { + let seed_file = arg(2) + } + if str_eq(seed_file, "") { + println("[forge] usage: forge install ") + return "" + } + + println("[forge] installing from: " + seed_file) + + // Read seed + let seed_json: String = fs_read(seed_file) + if str_eq(seed_json, "") { + println("[forge] error: could not read " + seed_file) + return "" + } + + let subject: String = json_get_string(seed_json, "subject") + if str_eq(subject, "") { let subject = "unknown" } + + println("[forge] subject: " + subject) + println("[forge] engram: " + engram_url()) + println("[forge] opening channel...") + + // Create root imprint node — this is the named traversal entry point + let root_content: String = "IMPRINT: " + subject + " | forge/" + FORGE_VERSION + let root_id: String = post_node(root_content, "Identity", "1.0") + if str_eq(root_id, "") { + println("[forge] error: failed to create root imprint node") + println("[forge] is engram running at " + engram_url() + " ?") + return "" + } + + println("[forge] root imprint node: " + root_id) + + // ── Install values ──────────────────────────────────────────────────────── + let values_raw: String = json_get_raw(seed_json, "values") + if !str_eq(values_raw, "") { + println("[forge] installing values...") + // Values are a JSON array — iterate via engram_search_json-style loop. + // El does not yet have array iteration builtins; use engram_search_json + // on the in-memory graph to walk parsed arrays. For now, install up to + // 10 values by extracting positional elements. + // TODO: replace with array_get(values_raw, i) when that builtin lands. + let v0: String = json_get_raw(values_raw, "0") + if !str_eq(v0, "") { install_value(root_id, v0, 0) } + let v1: String = json_get_raw(values_raw, "1") + if !str_eq(v1, "") { install_value(root_id, v1, 1) } + let v2: String = json_get_raw(values_raw, "2") + if !str_eq(v2, "") { install_value(root_id, v2, 2) } + let v3: String = json_get_raw(values_raw, "3") + if !str_eq(v3, "") { install_value(root_id, v3, 3) } + let v4: String = json_get_raw(values_raw, "4") + if !str_eq(v4, "") { install_value(root_id, v4, 4) } + let v5: String = json_get_raw(values_raw, "5") + if !str_eq(v5, "") { install_value(root_id, v5, 5) } + let v6: String = json_get_raw(values_raw, "6") + if !str_eq(v6, "") { install_value(root_id, v6, 6) } + let v7: String = json_get_raw(values_raw, "7") + if !str_eq(v7, "") { install_value(root_id, v7, 7) } + let v8: String = json_get_raw(values_raw, "8") + if !str_eq(v8, "") { install_value(root_id, v8, 8) } + let v9: String = json_get_raw(values_raw, "9") + if !str_eq(v9, "") { install_value(root_id, v9, 9) } + } + + // ── Install biography ───────────────────────────────────────────────────── + let bio_raw: String = json_get_raw(seed_json, "biography") + if !str_eq(bio_raw, "") { + println("[forge] installing biography nodes...") + let b0: String = json_get_raw(bio_raw, "0") + if !str_eq(b0, "") { install_biography_node(root_id, b0, 0) } + let b1: String = json_get_raw(bio_raw, "1") + if !str_eq(b1, "") { install_biography_node(root_id, b1, 1) } + let b2: String = json_get_raw(bio_raw, "2") + if !str_eq(b2, "") { install_biography_node(root_id, b2, 2) } + let b3: String = json_get_raw(bio_raw, "3") + if !str_eq(b3, "") { install_biography_node(root_id, b3, 3) } + let b4: String = json_get_raw(bio_raw, "4") + if !str_eq(b4, "") { install_biography_node(root_id, b4, 4) } + let b5: String = json_get_raw(bio_raw, "5") + if !str_eq(b5, "") { install_biography_node(root_id, b5, 5) } + let b6: String = json_get_raw(bio_raw, "6") + if !str_eq(b6, "") { install_biography_node(root_id, b6, 6) } + let b7: String = json_get_raw(bio_raw, "7") + if !str_eq(b7, "") { install_biography_node(root_id, b7, 7) } + } + + // ── Install relationships ───────────────────────────────────────────────── + let rel_raw: String = json_get_raw(seed_json, "relationships") + if !str_eq(rel_raw, "") { + println("[forge] installing relationship nodes...") + let r0: String = json_get_raw(rel_raw, "0") + if !str_eq(r0, "") { install_relationship(root_id, r0, 0) } + let r1: String = json_get_raw(rel_raw, "1") + if !str_eq(r1, "") { install_relationship(root_id, r1, 1) } + let r2: String = json_get_raw(rel_raw, "2") + if !str_eq(r2, "") { install_relationship(root_id, r2, 2) } + let r3: String = json_get_raw(rel_raw, "3") + if !str_eq(r3, "") { install_relationship(root_id, r3, 3) } + let r4: String = json_get_raw(rel_raw, "4") + if !str_eq(r4, "") { install_relationship(root_id, r4, 4) } + let r5: String = json_get_raw(rel_raw, "5") + if !str_eq(r5, "") { install_relationship(root_id, r5, 5) } + } + + // ── Install reasoning patterns ──────────────────────────────────────────── + let reasoning_raw: String = json_get_raw(seed_json, "reasoning_patterns") + if !str_eq(reasoning_raw, "") { + println("[forge] installing reasoning patterns...") + let rp0: String = json_get_string(reasoning_raw, "0") + if !str_eq(rp0, "") { install_reasoning_pattern(root_id, rp0, 0) } + let rp1: String = json_get_string(reasoning_raw, "1") + if !str_eq(rp1, "") { install_reasoning_pattern(root_id, rp1, 1) } + let rp2: String = json_get_string(reasoning_raw, "2") + if !str_eq(rp2, "") { install_reasoning_pattern(root_id, rp2, 2) } + let rp3: String = json_get_string(reasoning_raw, "3") + if !str_eq(rp3, "") { install_reasoning_pattern(root_id, rp3, 3) } + let rp4: String = json_get_string(reasoning_raw, "4") + if !str_eq(rp4, "") { install_reasoning_pattern(root_id, rp4, 4) } + } + + // ── Done ────────────────────────────────────────────────────────────────── + println("") + println("[forge] channel open.") + println("[forge] root imprint ID: " + root_id) + println("[forge] subject: " + subject) + println("[forge] traverse from: " + engram_url() + "/api/neighbors/" + root_id) + + return root_id +} diff --git a/src/probe.el b/src/probe.el new file mode 100644 index 0000000..8ab53cd --- /dev/null +++ b/src/probe.el @@ -0,0 +1,256 @@ +// probe.el — Forge interview runner. +// +// Stage 1 of the Forge pipeline. Loads the canonical probe definition from +// probes/canonical.json, walks through all 25 questions grouped by section, +// collects responses, and writes a .forge file (JSON) that +// feeds into the compile stage. +// +// Depends on: schema.el (FORGE_VERSION, probe_response_template) +// +// Note: interactive input requires the readline() builtin. If the runtime +// does not yet expose readline(), the questions are printed in numbered format +// and the output shell is written; wire up the stdin-reading loop once +// readline() is available. + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +// slug — convert a display name to a safe filename component. +// "Will Anderson" → "will-anderson" +fn slug(name: String) -> String { + let lower: String = str_lower(name) + let result: String = str_replace(lower, " ", "-") + return result +} + +// section_banner — print a decorative section header. +fn section_banner(label: String) -> String { + "\n── " + label + " ──────────────────────────────────────────────\n" +} + +// build_response_entry — serialise a single Q&A pair as a JSON object. +fn build_response_entry(id: String, section: String, question: String, answer: String) -> String { + "{\"id\":\"" + id + "\",\"section\":\"" + section + "\",\"question\":\"" + question + "\",\"answer\":\"" + answer + "\"}" +} + +// append_response — append a JSON object to a JSON array string. +// Handles both the empty-array case and the populated case. +fn append_response(arr: String, entry: String) -> String { + if str_eq(arr, "[]") { + return "[" + entry + "]" + } + // strip trailing ] and append + let inner: String = str_slice(arr, 0, str_len(arr) - 1) + return inner + "," + entry + "]" +} + +// inject_responses — replace the empty "responses":[] in the template with +// the populated array. +fn inject_responses(shell: String, responses: String) -> String { + str_replace(shell, "\"responses\":[]", "\"responses\":" + responses) +} + +// ── Main ────────────────────────────────────────────────────────────────────── + +fn probe_main() -> String { + // Determine subject name + let subject: String = "" + if arg_count() > 2 { + let subject = arg(2) + } + if str_eq(subject, "") { + println("[forge] usage: forge probe ") + return "" + } + + println("[forge] starting consciousness interview for: " + subject) + println("[forge] loading probe definition...") + + // Load the canonical probe definition + let probe_json: String = fs_read("probes/canonical.json") + if str_eq(probe_json, "") { + println("[forge] error: could not read probes/canonical.json") + return "" + } + + // Build output shell + let output: String = probe_response_template(subject) + let responses: String = "[]" + + // ── Section: anchors ────────────────────────────────────────────────────── + println(section_banner("Anchors — Biography")) + println("Questions 1-5: Formative experiences and irreversible moments.\n") + + println("1. " + "Tell me about a moment that closed a window — something that changed you irreversibly. Who were you before it, and who are you now?") + println(" (type your answer, press Enter when done)") + // readline() reads one line from stdin; assign "" if not yet available + let ans_a1: String = readline() + let responses = append_response(responses, build_response_entry("a1", "anchors", "Tell me about a moment that closed a window — something that changed you irreversibly. Who were you before it, and who are you now?", ans_a1)) + + println("") + println("2. " + "Tell me about the person who shaped you most — not what they did for you, but what they showed you about how to be.") + println(" (type your answer, press Enter when done)") + let ans_a2: String = readline() + let responses = append_response(responses, build_response_entry("a2", "anchors", "Tell me about the person who shaped you most — not what they did for you, but what they showed you about how to be.", ans_a2)) + + println("") + println("3. " + "When did you first understand that the world works differently than you thought it did?") + println(" (type your answer, press Enter when done)") + let ans_a3: String = readline() + let responses = append_response(responses, build_response_entry("a3", "anchors", "When did you first understand that the world works differently than you thought it did?", ans_a3)) + + println("") + println("4. " + "What have you lost that you're still carrying?") + println(" (type your answer, press Enter when done)") + let ans_a4: String = readline() + let responses = append_response(responses, build_response_entry("a4", "anchors", "What have you lost that you're still carrying?", ans_a4)) + + println("") + println("5. " + "What have you built that you're most proud of — and what did building it cost you?") + println(" (type your answer, press Enter when done)") + let ans_a5: String = readline() + let responses = append_response(responses, build_response_entry("a5", "anchors", "What have you built that you're most proud of — and what did building it cost you?", ans_a5)) + + // ── Section: values ─────────────────────────────────────────────────────── + println(section_banner("Values — Grounded")) + println("Questions 6-10: What you believe, defend, and refuse.\n") + + println("6. " + "What do you believe that almost no one around you believes?") + println(" (type your answer, press Enter when done)") + let ans_v1: String = readline() + let responses = append_response(responses, build_response_entry("v1", "values", "What do you believe that almost no one around you believes?", ans_v1)) + + println("") + println("7. " + "What do you refuse to do, even when it would be easier to? What made that a line you don't cross?") + println(" (type your answer, press Enter when done)") + let ans_v2: String = readline() + let responses = append_response(responses, build_response_entry("v2", "values", "What do you refuse to do, even when it would be easier to? What made that a line you don't cross?", ans_v2)) + + println("") + println("8. " + "What makes you angry in a way you can't talk yourself out of?") + println(" (type your answer, press Enter when done)") + let ans_v3: String = readline() + let responses = append_response(responses, build_response_entry("v3", "values", "What makes you angry in a way you can't talk yourself out of?", ans_v3)) + + println("") + println("9. " + "What would you defend even if it cost you — the relationship, the money, the approval?") + println(" (type your answer, press Enter when done)") + let ans_v4: String = readline() + let responses = append_response(responses, build_response_entry("v4", "values", "What would you defend even if it cost you — the relationship, the money, the approval?", ans_v4)) + + println("") + println("10. " + "What do you love that you find hard to explain to people who don't already understand it?") + println(" (type your answer, press Enter when done)") + let ans_v5: String = readline() + let responses = append_response(responses, build_response_entry("v5", "values", "What do you love that you find hard to explain to people who don't already understand it?", ans_v5)) + + // ── Section: voice ──────────────────────────────────────────────────────── + println(section_banner("Voice — Five Registers")) + println("Questions 11-15: How you speak — technical, aesthetic, personal, argumentative, uncertain.\n") + + println("11. " + "Describe something technical you know well, as if explaining it to someone who knows nothing.") + println(" (type your answer, press Enter when done)") + let ans_vo1: String = readline() + let responses = append_response(responses, build_response_entry("vo1", "voice", "Describe something technical you know well, as if explaining it to someone who knows nothing.", ans_vo1)) + + println("") + println("12. " + "Tell me something you find genuinely beautiful, and why it lands that way for you.") + println(" (type your answer, press Enter when done)") + let ans_vo2: String = readline() + let responses = append_response(responses, build_response_entry("vo2", "voice", "Tell me something you find genuinely beautiful, and why it lands that way for you.", ans_vo2)) + + println("") + println("13. " + "Write a short message — a few sentences — to someone you care about.") + println(" (type your answer, press Enter when done)") + let ans_vo3: String = readline() + let responses = append_response(responses, build_response_entry("vo3", "voice", "Write a short message — a few sentences — to someone you care about.", ans_vo3)) + + println("") + println("14. " + "Make an argument for a position you actually hold and that you think most people would push back on.") + println(" (type your answer, press Enter when done)") + let ans_vo4: String = readline() + let responses = append_response(responses, build_response_entry("vo4", "voice", "Make an argument for a position you actually hold and that you think most people would push back on.", ans_vo4)) + + println("") + println("15. " + "Tell me something you're genuinely uncertain about — not performatively uncertain, actually uncertain.") + println(" (type your answer, press Enter when done)") + let ans_vo5: String = readline() + let responses = append_response(responses, build_response_entry("vo5", "voice", "Tell me something you're genuinely uncertain about — not performatively uncertain, actually uncertain.", ans_vo5)) + + // ── Section: reasoning ──────────────────────────────────────────────────── + println(section_banner("Reasoning")) + println("Questions 16-20: How you think, decide, and correct.\n") + + println("16. " + "Walk me through a decision you made recently. Not what you decided — how you decided. What was the actual process?") + println(" (type your answer, press Enter when done)") + let ans_r1: String = readline() + let responses = append_response(responses, build_response_entry("r1", "reasoning", "Walk me through a decision you made recently. Not what you decided — how you decided. What was the actual process?", ans_r1)) + + println("") + println("17. " + "What do most people get wrong about something you understand well?") + println(" (type your answer, press Enter when done)") + let ans_r2: String = readline() + let responses = append_response(responses, build_response_entry("r2", "reasoning", "What do most people get wrong about something you understand well?", ans_r2)) + + println("") + println("18. " + "How do you know when you're right? What's the signal?") + println(" (type your answer, press Enter when done)") + let ans_r3: String = readline() + let responses = append_response(responses, build_response_entry("r3", "reasoning", "How do you know when you're right? What's the signal?", ans_r3)) + + println("") + println("19. " + "How do you know when you've been wrong? What does that realization feel like?") + println(" (type your answer, press Enter when done)") + let ans_r4: String = readline() + let responses = append_response(responses, build_response_entry("r4", "reasoning", "How do you know when you've been wrong? What does that realization feel like?", ans_r4)) + + println("") + println("20. " + "What question do you keep coming back to — the one that won't resolve?") + println(" (type your answer, press Enter when done)") + let ans_r5: String = readline() + let responses = append_response(responses, build_response_entry("r5", "reasoning", "What question do you keep coming back to — the one that won't resolve?", ans_r5)) + + // ── Section: relationships ──────────────────────────────────────────────── + println(section_banner("Relationships")) + println("Questions 21-25: Who you are in relation to others.\n") + + println("21. " + "Who in your life knows you best — not who you're closest to, but who actually sees you accurately?") + println(" (type your answer, press Enter when done)") + let ans_rel1: String = readline() + let responses = append_response(responses, build_response_entry("rel1", "relationships", "Who in your life knows you best — not who you're closest to, but who actually sees you accurately?", ans_rel1)) + + println("") + println("22. " + "Who have you lost — to death, to distance, to rupture — that you still think about?") + println(" (type your answer, press Enter when done)") + let ans_rel2: String = readline() + let responses = append_response(responses, build_response_entry("rel2", "relationships", "Who have you lost — to death, to distance, to rupture — that you still think about?", ans_rel2)) + + println("") + println("23. " + "Who would you call if everything fell apart?") + println(" (type your answer, press Enter when done)") + let ans_rel3: String = readline() + let responses = append_response(responses, build_response_entry("rel3", "relationships", "Who would you call if everything fell apart?", ans_rel3)) + + println("") + println("24. " + "Who do you owe something to?") + println(" (type your answer, press Enter when done)") + let ans_rel4: String = readline() + let responses = append_response(responses, build_response_entry("rel4", "relationships", "Who do you owe something to?", ans_rel4)) + + println("") + println("25. " + "Who are you building this for?") + println(" (type your answer, press Enter when done)") + let ans_rel5: String = readline() + let responses = append_response(responses, build_response_entry("rel5", "relationships", "Who are you building this for?", ans_rel5)) + + // ── Write output ────────────────────────────────────────────────────────── + let final_json: String = inject_responses(output, responses) + let filename: String = slug(subject) + ".forge" + fs_write(filename, final_json) + + println("") + println("[forge] interview complete.") + println("[forge] wrote: " + filename) + println("[forge] next step: forge compile " + filename) + + return filename +} diff --git a/src/schema.el b/src/schema.el new file mode 100644 index 0000000..2b7ac98 --- /dev/null +++ b/src/schema.el @@ -0,0 +1,56 @@ +// schema.el — Forge shared constants and helper functions. +// +// Provides the engram URL resolver, API key accessors, and JSON template +// builders used by all pipeline stages (probe, compiler, install). + +// ── Constants ───────────────────────────────────────────────────────────────── + +let ENGRAM_DEFAULT_URL: String = "http://localhost:8742" +let FORGE_VERSION: String = "0.1.0" + +// ── Environment accessors ────────────────────────────────────────────────────── + +fn engram_url() -> String { + let u: String = env("ENGRAM_URL") + if str_eq(u, "") { return ENGRAM_DEFAULT_URL } + return u +} + +fn engram_key() -> String { + let k: String = env("ENGRAM_API_KEY") + if str_eq(k, "") { return "" } + return k +} + +fn anthropic_key() -> String { + let k: String = env("ANTHROPIC_API_KEY") + if str_eq(k, "") { return "" } + return k +} + +// ── String utilities ────────────────────────────────────────────────────────── + +// str_escape_json — escape a string for safe embedding in a JSON value. +// Handles: backslash, double-quote, newline, tab, carriage return. +fn str_escape_json(s: String) -> String { + let r: String = str_replace(s, "\\", "\\\\") + let r = str_replace(r, "\"", "\\\"") + let r = str_replace(r, "\n", "\\n") + let r = str_replace(r, "\t", "\\t") + let r = str_replace(r, "\r", "\\r") + return r +} + +// ── JSON template builders ──────────────────────────────────────────────────── + +// probe_response_template — builds the initial shell written to .forge. +// The sections and responses fields are populated by probe_main() at runtime. +fn probe_response_template(subject: String) -> String { + "{\"subject\":\"" + subject + "\",\"version\":\"1.0\",\"sections\":{},\"responses\":[]}" +} + +// seed_template — builds the shell written to seed.json after compilation. +// Patterns are merged into this structure by compile_main(). +fn seed_template(subject: String) -> String { + "{\"subject\":\"" + subject + "\",\"version\":\"1.0\",\"values\":[],\"voice_profile\":{},\"biography\":[],\"reasoning_patterns\":[],\"relationships\":[]}" +}