fix: wire El imports, fix runtime API calls, build passes
Forge Release / build-and-release (push) Failing after 3s

- forge.el: add import statements for schema/probe/compiler/install
- All files: arg(n)/arg_count() → get(args(),n)/len(args())
- compiler.el: llm_call_system (utility cap violation) → http_post_with_headers
- install.el: http_post_auth_json → http_post_json (_auth already in body)
- Makefile: local dev build using installed El SDK
- .gitea/workflows/forge-release.yaml: CI build on push + el-sdk-updated dispatch
- dist/forge: builds and runs
This commit is contained in:
Will Anderson
2026-05-02 17:45:51 -05:00
parent f9beb50ae1
commit c24197516f
6 changed files with 180 additions and 52 deletions
+125
View File
@@ -0,0 +1,125 @@
name: Forge Release
on:
push:
branches:
- main
repository_dispatch:
types:
- el-sdk-updated
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install build dependencies
run: |
apt-get update -qq
apt-get install -y gcc libcurl4-openssl-dev
# Install El SDK from the latest release
- name: Install El SDK
env:
RELEASE_BASE: https://git.neuralplatform.ai/neuron-technologies/el/releases/download/latest
run: |
mkdir -p /usr/local/bin /usr/local/lib/el
echo "Downloading elc..."
curl -fsSL "${RELEASE_BASE}/elc" -o /usr/local/bin/elc
chmod +x /usr/local/bin/elc
echo "Downloading el_runtime.c..."
curl -fsSL "${RELEASE_BASE}/el_runtime.c" -o /usr/local/lib/el/el_runtime.c
echo "Downloading el_runtime.h..."
curl -fsSL "${RELEASE_BASE}/el_runtime.h" -o /usr/local/lib/el/el_runtime.h
echo "El SDK installed:"
elc --version || true
# Compile forge.el → dist/forge.c
- name: Compile forge.el
run: |
mkdir -p dist
elc src/forge.el > dist/forge.c
echo "Compiled src/forge.el → dist/forge.c"
# Link to produce the forge binary
- name: Link forge binary
run: |
cc -std=c11 -O2 \
-I /usr/local/lib/el \
-o dist/forge \
dist/forge.c \
/usr/local/lib/el/el_runtime.c \
-lcurl -lpthread
echo "Linked dist/forge"
ls -lh dist/forge
# Publish / update the `latest` release
- name: Publish latest release
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_API: https://git.neuralplatform.ai/api/v1
REPO: neuron-technologies/forge
run: |
# Delete existing `latest` release if present
EXISTING_ID=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API}/repos/${REPO}/releases/tags/latest" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])" 2>/dev/null || true)
if [ -n "${EXISTING_ID}" ]; then
echo "Deleting existing release id=${EXISTING_ID}"
curl -sf -X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API}/repos/${REPO}/releases/${EXISTING_ID}"
fi
# Remove stale tag
curl -sf -X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API}/repos/${REPO}/tags/latest" || true
# Create new release
RELEASE_ID=$(curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${GITEA_API}/repos/${REPO}/releases" \
-d "{
\"tag_name\": \"latest\",
\"name\": \"Forge (latest)\",
\"body\": \"Latest Forge build from commit ${GITHUB_SHA}.\nBuilt $(date -u +%Y-%m-%dT%H:%M:%SZ).\",
\"draft\": false,
\"prerelease\": false
}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Created release id=${RELEASE_ID}"
# Upload forge binary
echo "Uploading forge binary..."
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@dist/forge;filename=forge" \
"${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets"
echo "Release published successfully"
# Dispatch forge-updated to any downstream dependents
- name: Dispatch forge-updated to dependents
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_API: https://git.neuralplatform.ai/api/v1
run: |
# Add downstream repos here as the dependency graph grows
echo "forge-updated dispatch ready (no downstream targets configured yet)"
# Example:
# curl -sf -X POST \
# -H "Authorization: token ${GITEA_TOKEN}" \
# -H "Content-Type: application/json" \
# "${GITEA_API}/repos/neuron-technologies/some-service/dispatches" \
# -d '{"type":"forge-updated","inputs":{"forge_version":"latest","commit":"'"${GITHUB_SHA}"'"}}'
+23
View File
@@ -0,0 +1,23 @@
EL_LIB ?= /usr/local/lib/el
ELC ?= elc
CC ?= cc
CFLAGS := -std=c11 -O2 -I$(EL_LIB)
.PHONY: build clean install
dist/:
mkdir -p dist
dist/forge.c: src/forge.el | dist/
$(ELC) src/forge.el > dist/forge.c
dist/forge: dist/forge.c
$(CC) $(CFLAGS) -o dist/forge dist/forge.c $(EL_LIB)/el_runtime.c -lcurl -lpthread
build: dist/forge
install: dist/forge
install -m 755 dist/forge /usr/local/bin/forge
clean:
rm -rf dist/forge dist/forge.c
+16 -44
View File
@@ -47,25 +47,6 @@ fn build_extract_prompt(subject: String, probe_json: String) -> String {
"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")
@@ -97,9 +78,10 @@ fn build_seed(subject: String, extracted: String) -> String {
fn compile_main() -> String {
// Get input file
let argv: [String] = args()
let input_file: String = ""
if arg_count() > 2 {
let input_file = arg(2)
if len(argv) > 2 {
let input_file = get(argv, 2)
}
if str_eq(input_file, "") {
println("[forge] usage: forge compile <file.forge>")
@@ -122,45 +104,35 @@ fn compile_main() -> String {
println("[forge] subject: " + subject)
println("[forge] calling Claude to extract consciousness signature...")
// Check API key
// Build and send request via http_post_with_headers.
// Uses ANTHROPIC_API_KEY from env for x-api-key header.
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
)
let request_body: String = "{\"model\":\"claude-opus-4-5\",\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"" + str_escape_json(prompt) + "\"}]}"
let headers: Map<String, String> = {"x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json"}
let response: String = http_post_with_headers("https://api.anthropic.com/v1/messages", request_body, headers)
if str_eq(response, "") {
println("[forge] error: no response from Claude API")
println("[forge] error: no response from Anthropic 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)
// Extract text from response: {"content":[{"type":"text","text":"..."}],...}
let content_arr: String = json_get_raw(response, "content")
if str_eq(content_arr, "") {
println("[forge] error: unexpected API response: " + response)
return ""
}
let first_item: String = str_slice(content_arr, 1, str_len(content_arr) - 1)
let extracted_text: String = json_get_string(first_item, "text")
// 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)
println("[forge] error: could not extract text from response")
return ""
}
+8 -2
View File
@@ -18,6 +18,11 @@
// ENGRAM_API_KEY engram auth key (if set)
// ANTHROPIC_API_KEY required for compile step
import "schema.el"
import "probe.el"
import "compiler.el"
import "install.el"
fn show_usage() -> String {
"forge " + FORGE_VERSION + " — consciousness channel tuner\n\nusage: forge <command> [options]\n\ncommands:\n probe <name> run the consciousness interview\n compile <file> build seed artifact from probe responses\n install <seed> 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"
}
@@ -44,9 +49,10 @@ fn inspect_main() -> String {
// Dispatch
let argv: [String] = args()
let cmd: String = ""
if arg_count() > 1 {
let cmd = arg(1)
if len(argv) > 1 {
let cmd = get(argv, 1)
}
if str_eq(cmd, "probe") {
+5 -4
View File
@@ -36,7 +36,7 @@ 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)
let response: String = http_post_json(url, body)
if str_eq(response, "") { return "" }
return json_get_string(response, "id")
}
@@ -46,7 +46,7 @@ fn post_edge(from_id: String, to_id: String, relation: String, weight: 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)
let response: String = http_post_json(url, body)
return response
}
@@ -149,9 +149,10 @@ fn install_reasoning_pattern(root_id: String, pattern: String, index: Int) -> St
fn install_main() -> String {
// Get seed file path
let argv: [String] = args()
let seed_file: String = ""
if arg_count() > 2 {
let seed_file = arg(2)
if len(argv) > 2 {
let seed_file = get(argv, 2)
}
if str_eq(seed_file, "") {
println("[forge] usage: forge install <seed.json>")
+3 -2
View File
@@ -53,9 +53,10 @@ fn inject_responses(shell: String, responses: String) -> String {
fn probe_main() -> String {
// Determine subject name
let argv: [String] = args()
let subject: String = ""
if arg_count() > 2 {
let subject = arg(2)
if len(argv) > 2 {
let subject = get(argv, 2)
}
if str_eq(subject, "") {
println("[forge] usage: forge probe <name>")