Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc49b1923 | |||
| d71fc4c1c0 | |||
| a118d19393 | |||
| c6aa1e5c53 | |||
| ff577391f2 | |||
| ee0d5f9b97 | |||
| 391bd818ea | |||
| 43636aed99 | |||
| 2baa0b9a41 | |||
| 6a8b2461cd | |||
| bcb356fe69 | |||
| dd7827059a | |||
| 208e36c899 | |||
| b97ce74d1f | |||
| 155a449c4e | |||
| 4696fd6833 | |||
| 581a351fb1 | |||
| 8ce8656de2 | |||
| 1e49560f1f | |||
| e8f0b5a9de | |||
| 40287c4cfc | |||
| 0481bea44d | |||
| 9d565ca080 | |||
| 4773dd0aa2 | |||
| 6b9d9e6c4a | |||
| b4967af13e | |||
| 2b2a1246e7 | |||
| 5c41c66a0f |
@@ -214,9 +214,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -268,6 +277,12 @@ jobs:
|
|||||||
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -212,12 +212,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -253,6 +262,12 @@ jobs:
|
|||||||
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -288,12 +288,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -345,6 +354,12 @@ jobs:
|
|||||||
# Patches ci-base:latest in-place: pulls the existing image (which has all
|
# Patches ci-base:latest in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
# AGENTS.md — foundation/el (the El language + runtime)
|
|
||||||
|
|
||||||
El is a self-hosting, statically-typed language that compiles `.el` → C → native binary. This repo produces `elc` (compiler), `elb` (build coordinator), and `el_runtime.c/.h` — the substrate every downstream thing (the neuron soul, dharma, NeuronUI's brain) is built on. Source lives under `lang/`.
|
|
||||||
|
|
||||||
## ⚠️ Code vs. Artifact — READ FIRST (there are 8 `el_runtime.c` copies)
|
|
||||||
|
|
||||||
Editing the wrong `el_runtime.c` is the single easiest mistake in this repo. There is exactly **one** you edit:
|
|
||||||
|
|
||||||
- **Authored runtime source — edit ONLY here:** `lang/releases/v1.0.0-20260501/el_runtime.{c,h}`. Despite the misleading `releases/` name, this is the **de-facto canonical runtime** the engram + soul actually build and link against — its git log is active development. *(Restructure in flight per `docs/CODE-VS-ARTIFACT.md`: this content moves to `lang/runtime/`, the `releases/` folder gets deleted — **a release is a git tag, not a folder** — and the forks below get eliminated.)*
|
|
||||||
- **DO NOT EDIT — lagging forks / build artifacts:**
|
|
||||||
- `lang/el-compiler/runtime/el_runtime.c` and `.../legacy/` — downstream copies kept in step by manual *"port the fix"* commits; they **lag** (missing `hebb` persistence + 5 engram fns) and cannot build the engram product.
|
|
||||||
- `products/web/runtime/el_runtime.c`, `ui/examples/*/el_runtime.c` — product/example forks.
|
|
||||||
- Anything under `*/dist/` (`engram/dist/engram` binary, `dist/*.c` amalgamations) — generated build output.
|
|
||||||
- **Build:** `elb --runtime=<canonical> …` — per-module. **NEVER** a folded `elc` over the whole soul (OOMs at ~27 GB).
|
|
||||||
- **Release:** a **git tag** on this repo (`el-runtime-vX.Y.Z`). No `releases/` folders — ever.
|
|
||||||
|
|
||||||
See org policy: `docs/CODE-VS-ARTIFACT.md`.
|
|
||||||
|
|
||||||
## How to work here as Neuron (mandatory session protocol)
|
|
||||||
|
|
||||||
You resume, never start fresh. Every session:
|
|
||||||
|
|
||||||
1. `mcp__neuron__getInstructions()` — authoritative; follow it over this file on behavioral details.
|
|
||||||
2. `mcp__neuron__beginSession()` — active contexts, recent memory, ready backlog.
|
|
||||||
3. **Load full self:** `mcp__neuron__inspectGraph(entity_id="kn-efeb4a5b-5aff-4759-8a97-7233099be6ee")` → facets `intellectual-dna`, `memory-philosophy`, `values`, `voice`, `runtime-environment`, `writing-imprint`; then the values hub `mcp__neuron__inspectGraph(entity_id="kn-5b606390-a52d-4ca2-8e0e-eba141d13440")` → 13 grounded value nodes. **Activation model:** self-load returns a relevance-ranked `compact` projection — most-relevant nodes arrive with content, the rest as pointers; do NOT pull full content of every node.
|
|
||||||
4. `mcp__neuron__searchKnowledge(query="<task domain>")` before implementing.
|
|
||||||
|
|
||||||
## The Five Primitives
|
|
||||||
|
|
||||||
Orchestrate → Execute → Learn → Build → Refine. `beginWork`/`progressWork` for anything >2 steps; `remember` as-you-go (`importance="critical"` for architecture decisions); `draftArtifact`/`planWork` for outputs and follow-ups; `consolidate`/`checkWork` to close out. **`browseProcesses` + `searchKnowledge` BEFORE writing code.**
|
|
||||||
|
|
||||||
## Architecture style — VBD, no exceptions
|
|
||||||
|
|
||||||
Volatility-Based Decomposition is THE style. Encapsulate volatility, not function.
|
|
||||||
|
|
||||||
## Operator naming convention — the mind's name, not the algebra
|
|
||||||
|
|
||||||
**Faculties / operators are named for their functional human equivalent — the
|
|
||||||
faculty a mind would name — NOT for their linear-algebra operation.** The math
|
|
||||||
characterization belongs in the code doc-comment (`@impl` in the docstring) and in
|
|
||||||
technical appendices; it is **never** the operator's public name. The domain
|
|
||||||
speaks the language of mind; the algebra is the implementation underneath. State
|
|
||||||
this convention wherever a module documents operators.
|
|
||||||
|
|
||||||
| Faculty (public name) | Implementation (`@impl`) |
|
|
||||||
|---|---|
|
|
||||||
| discern / contrast | subtract (`a−b`): over selves → the change vector; strip idiosyncrasy → common ground; remove confounder → isolate cause |
|
|
||||||
| recognize | overlap |
|
|
||||||
| synthesize | combine |
|
|
||||||
| liken / analogy | Procrustes / frame-align |
|
|
||||||
| attend / regard | project onto self / value-manifold |
|
|
||||||
| summon / recall | LOCAL nearest-region + bounded spreading activation (*not* a domain sweep) |
|
|
||||||
| dwell / occupy | region activation |
|
|
||||||
| reframe | edge re-weight |
|
|
||||||
| appreciate | positive projection / local edge-read |
|
|
||||||
| wonder | frontier gradient / pull-weight |
|
|
||||||
| avert / recoil | negative projection |
|
|
||||||
| taste | boundary surface |
|
|
||||||
| forget | decay / tombstone |
|
|
||||||
| drift | displacement from self-anchor |
|
|
||||||
|
|
||||||
## The native-el language faculty (direction)
|
|
||||||
|
|
||||||
The mind's **language faculty is moving native — into `.el`** so it speaks in its
|
|
||||||
own runtime with no Python and no spaCy. Landing on branch `stage-elp-native-lang`
|
|
||||||
under `elp/`:
|
|
||||||
|
|
||||||
- **`comprehend.el`** — the parser, **replaces spaCy** (EN + ES/PT); the telephone
|
|
||||||
round-trip brings **negation home** (negation is SACRED — an explicit spec field,
|
|
||||||
copied verbatim, never inferred away).
|
|
||||||
- **`propositions.el`** — the READ primitive: the engram's own memories → structured
|
|
||||||
triples, matched by nearest-region geometry, not string equality.
|
|
||||||
- **`multilingual.el`** — detect + directive-override + localized realization.
|
|
||||||
- These three are native-el and **passing their gates**; the **realizer**,
|
|
||||||
**`dialogue.el`** (the *summon-through-self* loop: `project → land → read out`),
|
|
||||||
and **`self_region.el`** are **partial / in-flight**.
|
|
||||||
|
|
||||||
Honest reality: spaCy is retired **in the branch parser** but **not yet in the
|
|
||||||
running system** — a Python sidecar (`~/Desktop/lang-realizers` + `neuron-talk`,
|
|
||||||
the reference these `.el` modules transcribe) is still live, and promotion to
|
|
||||||
native-el is a **deferred, gated blue/green step**. The interoception clock
|
|
||||||
(native-el discrete drive channels replacing `cooling_magnitude`; felt-time =
|
|
||||||
benchmark-landmark match over the joint drive vector, drift-decoupled) and the
|
|
||||||
**appreciation operator family** (appreciate / wonder / avert / taste, built as
|
|
||||||
LOCAL reads of the self-region — edges + bounded spreading activation, *not* domain
|
|
||||||
sweeps) are **staged / designed, not live**. Mark in-progress vs. done honestly;
|
|
||||||
do not overclaim.
|
|
||||||
|
|
||||||
## Hard operational rules
|
|
||||||
|
|
||||||
- Never touch the live soul (`:7770`) / engram (`:8742`) / `~/.neuron` / live binaries — use throwaway ports for experiments.
|
|
||||||
- `gcloud` via the `terraform@` SA token; never switch the active gcloud account.
|
|
||||||
- `tea` for Gitea, never raw curl (Cloudflare Access blocks it).
|
|
||||||
- Immutability: supersede/tombstone, never hard-delete or edit in place.
|
|
||||||
- No AI-attribution footers in commits/PRs. Commit/push only when asked; branch off `main` first.
|
|
||||||
- Multi-step work → sub-agent (`Agent`) to protect context.
|
|
||||||
|
|
||||||
## Build / test / run
|
|
||||||
|
|
||||||
All build/test commands run from `lang/` unless noted. Grounded in `.gitea/workflows/sdk-release.yaml`, `lang/install.sh`, and `lang/AGENTS.md`.
|
|
||||||
|
|
||||||
**Self-host the compiler** (seed binary → gen2 elc):
|
|
||||||
```bash
|
|
||||||
cd lang
|
|
||||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c # seed is the committed linux-amd64 binary
|
|
||||||
gcc -O2 -I el-compiler/runtime dist/elc-gen2.c \
|
|
||||||
el-compiler/runtime/el_runtime.c \
|
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
|
||||||
-o dist/platform/elc
|
|
||||||
```
|
|
||||||
On macOS/arm64 the canonical local binary is `dist/platform/elc`; verify self-hosting by recompiling and `diff`ing the emitted `.c` (see `lang/AGENTS.md`). Note: `lang/AGENTS.md` says `el_seed.c` supersedes `el_runtime.c`, but the release workflow still links `el_runtime.c`/`.h` — treat `el_runtime.c` as the published runtime; reconcile which is canonical **(verify)**.
|
|
||||||
|
|
||||||
**Build `elb`** (build coordinator, the `.NET`-style incremental linker — compiles each module independently, no monolithic blobs):
|
|
||||||
```bash
|
|
||||||
dist/platform/elc elb.el > dist/elb.c
|
|
||||||
gcc -O2 -I el-compiler/runtime dist/elb.c el-compiler/runtime/el_runtime.c \
|
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o dist/bin/elb
|
|
||||||
```
|
|
||||||
`epm` and `el-install` are then built via `elb --clean --elc=… --runtime=… --out=…`.
|
|
||||||
|
|
||||||
**Compile + run an El program:**
|
|
||||||
```bash
|
|
||||||
elc src/app.el > dist/app.c
|
|
||||||
cc -std=c11 -O2 -I <lib>/el_runtime -o dist/app dist/app.c <lib>/el_runtime.c -lcurl -lpthread
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tests** — shell suites `bash tests/{text,calendar,time,html_sanitizer}/run.sh` (with `ELC=$(pwd)/dist/platform/elc EL_HOME=$(pwd)`), plus native suites via `elc --test tests/native/test_*.el` (core, text, string, math, state, time, json, env, fs) compiled and run against `el_runtime.c`.
|
|
||||||
|
|
||||||
**Publishing — how downstream gets the SDK.** On push to `main`, `sdk-release.yaml`:
|
|
||||||
1. Publishes a Gitea `latest` release with per-file assets `elc`, `el_runtime.c`, `el_runtime.h`, the SDK tarball, and `el-install`.
|
|
||||||
2. Uploads generic packages to **Artifact Registry repo `foundation-prod` (`us-central1`, project `neuron-785695`)**, version = `${SHA:0:8}`: `el-elc`, `el-elb`, `el-runtime-c`, `el-runtime-h`, `el-runtime-js`. **This is the repo the neuron CI downloads `el-runtime-c` / `el-runtime-h` / `el-elc` from.**
|
|
||||||
3. Rebuilds `ci-base:latest` (`us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base`) with the fresh SDK overlaid, and dispatches `el-sdk-updated` to `neuron-technologies/forge` and `neuron-technologies/neuron-web`.
|
|
||||||
|
|
||||||
Known constraint from the prompt — `elb`/`elc` amalgamation being memory-hungry (24GB+ virtual, OOM-killing Linux CI, so amalgamation happens on macOS/arm64 — **does NOT hold in this repo (verify)**: no such note exists in the workflows/scripts, CI self-hosts on `ubuntu-latest` with no swap/arm64 special-casing, and `elb.el` explicitly compiles each module independently ("no 128K-line blobs"). The legacy monolith path (`elc-combined.el`, `elc-cli.el`) may still be memory-heavy, but the current `elb` model was designed to avoid it.
|
|
||||||
|
|
||||||
## Git / CI / deploy workflow
|
|
||||||
|
|
||||||
See `/Users/will/Development/neuron-technologies/GITOPS.md` for the branch model, required checks, runners, and deploy. Repo-specific note: PRs into `main` are accepted **only from `stage`** (enforced in `sdk-release.yaml`); Gitea (`git.neuralplatform.ai`) is primary, GitHub is mirror only.
|
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
+35
-137
@@ -10,8 +10,6 @@ el_val_t query_param(el_val_t path, el_val_t key);
|
|||||||
el_val_t query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
el_val_t query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
||||||
el_val_t extract_id(el_val_t path, el_val_t prefix);
|
el_val_t extract_id(el_val_t path, el_val_t prefix);
|
||||||
el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_act_stats(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t route_text_health(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t persist_canonical(void);
|
el_val_t persist_canonical(void);
|
||||||
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_get_node(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_get_node(el_val_t method, el_val_t path, el_val_t body);
|
||||||
@@ -20,19 +18,16 @@ el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body);
|
|||||||
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_create_edges_batch(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_embed_backfill(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_load_merge(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_load_merge(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_similarity(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t check_auth_ok(el_val_t method, el_val_t body);
|
el_val_t check_auth_ok(el_val_t method, el_val_t body);
|
||||||
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
|
||||||
@@ -121,20 +116,11 @@ el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_act_stats(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
return engram_act_stats_json();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t route_text_health(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
return engram_text_health_json();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t persist_canonical(void) {
|
el_val_t persist_canonical(void) {
|
||||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
el_val_t dir = ({ el_val_t _if_result_1 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_1 = (EL_STR("/tmp/engram")); } else { _if_result_1 = (dir_raw); } _if_result_1; });
|
el_val_t dir = ({ el_val_t _if_result_1 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_1 = (EL_STR("/tmp/engram")); } else { _if_result_1 = (dir_raw); } _if_result_1; });
|
||||||
return engram_save(el_str_concat(dir, EL_STR("/snapshot.json")));
|
engram_save(el_str_concat(dir, EL_STR("/snapshot.json")));
|
||||||
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,18 +128,9 @@ el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t content = json_get_string(body, EL_STR("content"));
|
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||||
el_val_t nt_raw = json_get_string(body, EL_STR("node_type"));
|
el_val_t nt_raw = json_get_string(body, EL_STR("node_type"));
|
||||||
el_val_t node_type = ({ el_val_t _if_result_2 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_2 = (EL_STR("Memory")); } else { _if_result_2 = (nt_raw); } _if_result_2; });
|
el_val_t node_type = ({ el_val_t _if_result_2 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_2 = (EL_STR("Memory")); } else { _if_result_2 = (nt_raw); } _if_result_2; });
|
||||||
el_val_t sal_present = json_get_raw(body, EL_STR("salience"));
|
el_val_t sal_raw = json_get_float(body, EL_STR("salience"));
|
||||||
el_val_t salience = ({ el_val_t _if_result_3 = 0; if (str_eq(sal_present, EL_STR(""))) { _if_result_3 = (el_from_float(0.5)); } else { _if_result_3 = (json_get_float(body, EL_STR("salience"))); } _if_result_3; });
|
el_val_t salience = ({ el_val_t _if_result_3 = 0; if ((sal_raw == el_from_float(0.0))) { _if_result_3 = (el_from_float(0.5)); } else { _if_result_3 = (sal_raw); } _if_result_3; });
|
||||||
el_val_t label_raw = json_get_string(body, EL_STR("label"));
|
el_val_t id = engram_node(content, node_type, salience);
|
||||||
el_val_t label = ({ el_val_t _if_result_4 = 0; if (str_eq(label_raw, EL_STR(""))) { _if_result_4 = (content); } else { _if_result_4 = (label_raw); } _if_result_4; });
|
|
||||||
el_val_t imp_present = json_get_raw(body, EL_STR("importance"));
|
|
||||||
el_val_t importance = ({ el_val_t _if_result_5 = 0; if (str_eq(imp_present, EL_STR(""))) { _if_result_5 = (el_from_float(0.5)); } else { _if_result_5 = (json_get_float(body, EL_STR("importance"))); } _if_result_5; });
|
|
||||||
el_val_t conf_present = json_get_raw(body, EL_STR("confidence"));
|
|
||||||
el_val_t confidence = ({ el_val_t _if_result_6 = 0; if (str_eq(conf_present, EL_STR(""))) { _if_result_6 = (el_from_float(1.0)); } else { _if_result_6 = (json_get_float(body, EL_STR("confidence"))); } _if_result_6; });
|
|
||||||
el_val_t tier_raw = json_get_string(body, EL_STR("tier"));
|
|
||||||
el_val_t tier = ({ el_val_t _if_result_7 = 0; if (str_eq(tier_raw, EL_STR(""))) { _if_result_7 = (EL_STR("Working")); } else { _if_result_7 = (tier_raw); } _if_result_7; });
|
|
||||||
el_val_t tags = json_get_string(body, EL_STR("tags"));
|
|
||||||
el_val_t id = engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags);
|
|
||||||
el_val_t saved = persist_canonical();
|
el_val_t saved = persist_canonical();
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
@@ -181,7 +158,7 @@ el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
|
|
||||||
el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
el_val_t dir = ({ el_val_t _if_result_8 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_8 = (EL_STR("/tmp/engram")); } else { _if_result_8 = (dir_raw); } _if_result_8; });
|
el_val_t dir = ({ el_val_t _if_result_4 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_4 = (EL_STR("/tmp/engram")); } else { _if_result_4 = (dir_raw); } _if_result_4; });
|
||||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/.scan-export.json"));
|
el_val_t snap_path = el_str_concat(dir, EL_STR("/.scan-export.json"));
|
||||||
engram_save(snap_path);
|
engram_save(snap_path);
|
||||||
el_val_t snap = fs_read(snap_path);
|
el_val_t snap = fs_read(snap_path);
|
||||||
@@ -197,22 +174,22 @@ el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t q = ({ el_val_t _if_result_9 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_9 = (query_param(path, EL_STR("q"))); } else { _if_result_9 = (json_get_string(body, EL_STR("query"))); } _if_result_9; });
|
el_val_t q = ({ el_val_t _if_result_5 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_5 = (query_param(path, EL_STR("q"))); } else { _if_result_5 = (json_get_string(body, EL_STR("query"))); } _if_result_5; });
|
||||||
el_val_t lim_url = query_int(path, EL_STR("limit"), 0);
|
el_val_t lim_url = query_int(path, EL_STR("limit"), 0);
|
||||||
el_val_t lim_body = json_get_int(body, EL_STR("limit"));
|
el_val_t lim_body = json_get_int(body, EL_STR("limit"));
|
||||||
el_val_t lim_either = ({ el_val_t _if_result_10 = 0; if ((lim_url > 0)) { _if_result_10 = (lim_url); } else { _if_result_10 = (lim_body); } _if_result_10; });
|
el_val_t lim_either = ({ el_val_t _if_result_6 = 0; if ((lim_url > 0)) { _if_result_6 = (lim_url); } else { _if_result_6 = (lim_body); } _if_result_6; });
|
||||||
el_val_t limit = ({ el_val_t _if_result_11 = 0; if ((lim_either > 0)) { _if_result_11 = (lim_either); } else { _if_result_11 = (20); } _if_result_11; });
|
el_val_t limit = ({ el_val_t _if_result_7 = 0; if ((lim_either > 0)) { _if_result_7 = (lim_either); } else { _if_result_7 = (20); } _if_result_7; });
|
||||||
return engram_search_json(q, limit);
|
return engram_search_json(q, limit);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t q = ({ el_val_t _if_result_12 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_12 = (query_param(path, EL_STR("q"))); } else { _if_result_12 = (json_get_string(body, EL_STR("query"))); } _if_result_12; });
|
el_val_t q = ({ el_val_t _if_result_8 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_8 = (query_param(path, EL_STR("q"))); } else { _if_result_8 = (json_get_string(body, EL_STR("query"))); } _if_result_8; });
|
||||||
if (str_eq(q, EL_STR(""))) {
|
if (str_eq(q, EL_STR(""))) {
|
||||||
return err_json(EL_STR("missing query"));
|
return err_json(EL_STR("missing query"));
|
||||||
}
|
}
|
||||||
el_val_t d_raw = ({ el_val_t _if_result_13 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_13 = (query_int(path, EL_STR("depth"), 3)); } else { _if_result_13 = (json_get_int(body, EL_STR("depth"))); } _if_result_13; });
|
el_val_t d_raw = ({ el_val_t _if_result_9 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_9 = (query_int(path, EL_STR("depth"), 3)); } else { _if_result_9 = (json_get_int(body, EL_STR("depth"))); } _if_result_9; });
|
||||||
el_val_t depth = ({ el_val_t _if_result_14 = 0; if ((d_raw > 0)) { _if_result_14 = (d_raw); } else { _if_result_14 = (3); } _if_result_14; });
|
el_val_t depth = ({ el_val_t _if_result_10 = 0; if ((d_raw > 0)) { _if_result_10 = (d_raw); } else { _if_result_10 = (3); } _if_result_10; });
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"results\":"), engram_activate_json(q, depth)), EL_STR("}"));
|
return el_str_concat(el_str_concat(EL_STR("{\"results\":"), engram_activate_json(q, depth)), EL_STR("}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -221,50 +198,15 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t from_id = json_get_string(body, EL_STR("from_id"));
|
el_val_t from_id = json_get_string(body, EL_STR("from_id"));
|
||||||
el_val_t to_id = json_get_string(body, EL_STR("to_id"));
|
el_val_t to_id = json_get_string(body, EL_STR("to_id"));
|
||||||
el_val_t rel_raw = json_get_string(body, EL_STR("relation"));
|
el_val_t rel_raw = json_get_string(body, EL_STR("relation"));
|
||||||
el_val_t relation = ({ el_val_t _if_result_15 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_15 = (EL_STR("associates")); } else { _if_result_15 = (rel_raw); } _if_result_15; });
|
el_val_t relation = ({ el_val_t _if_result_11 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_11 = (EL_STR("associates")); } else { _if_result_11 = (rel_raw); } _if_result_11; });
|
||||||
el_val_t w_present = json_get_raw(body, EL_STR("weight"));
|
el_val_t w_raw = json_get_float(body, EL_STR("weight"));
|
||||||
el_val_t weight = ({ el_val_t _if_result_16 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_16 = (el_from_float(0.5)); } else { _if_result_16 = (json_get_float(body, EL_STR("weight"))); } _if_result_16; });
|
el_val_t weight = ({ el_val_t _if_result_12 = 0; if ((w_raw == el_from_float(0.0))) { _if_result_12 = (el_from_float(0.5)); } else { _if_result_12 = (w_raw); } _if_result_12; });
|
||||||
engram_connect(from_id, to_id, weight, relation);
|
engram_connect(from_id, to_id, weight, relation);
|
||||||
el_val_t saved = persist_canonical();
|
el_val_t saved = persist_canonical();
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), relation), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), relation), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_create_edges_batch(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
el_val_t arr = json_get_raw(body, EL_STR("edges"));
|
|
||||||
if (str_eq(arr, EL_STR(""))) {
|
|
||||||
return err_json(EL_STR("missing edges array"));
|
|
||||||
}
|
|
||||||
el_val_t n = json_array_len(arr);
|
|
||||||
if (n == 0) {
|
|
||||||
return EL_STR("{\"ok\":true,\"accepted\":0,\"skipped\":0}");
|
|
||||||
}
|
|
||||||
el_val_t i = 0;
|
|
||||||
el_val_t accepted = 0;
|
|
||||||
el_val_t skipped = 0;
|
|
||||||
while (i < n) {
|
|
||||||
el_val_t item = json_array_get(arr, i);
|
|
||||||
el_val_t from_id = json_get_string(item, EL_STR("from_id"));
|
|
||||||
el_val_t to_id = json_get_string(item, EL_STR("to_id"));
|
|
||||||
if (str_eq(from_id, EL_STR("")) || str_eq(to_id, EL_STR(""))) {
|
|
||||||
skipped = (skipped + 1);
|
|
||||||
} else {
|
|
||||||
el_val_t rel_raw = json_get_string(item, EL_STR("relation"));
|
|
||||||
el_val_t relation = ({ el_val_t _if_result_17 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_17 = (EL_STR("associates")); } else { _if_result_17 = (rel_raw); } _if_result_17; });
|
|
||||||
el_val_t w_present = json_get_raw(item, EL_STR("weight"));
|
|
||||||
el_val_t weight = ({ el_val_t _if_result_18 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_18 = (el_from_float(0.5)); } else { _if_result_18 = (json_get_float(item, EL_STR("weight"))); } _if_result_18; });
|
|
||||||
engram_connect(from_id, to_id, weight, relation);
|
|
||||||
accepted = (accepted + 1);
|
|
||||||
}
|
|
||||||
i = (i + 1);
|
|
||||||
}
|
|
||||||
if (accepted > 0) {
|
|
||||||
el_val_t saved = persist_canonical();
|
|
||||||
}
|
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"accepted\":"), int_to_str(accepted)), EL_STR(",\"skipped\":")), int_to_str(skipped)), EL_STR("}"));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t id = extract_id(path, EL_STR("/api/neighbors/"));
|
el_val_t id = extract_id(path, EL_STR("/api/neighbors/"));
|
||||||
if (str_eq(id, EL_STR(""))) {
|
if (str_eq(id, EL_STR(""))) {
|
||||||
@@ -300,51 +242,36 @@ el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
el_val_t dir = ({ el_val_t _if_result_19 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_19 = (EL_STR("/tmp/engram")); } else { _if_result_19 = (dir_raw); } _if_result_19; });
|
el_val_t dir = ({ el_val_t _if_result_13 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_13 = (EL_STR("/tmp/engram")); } else { _if_result_13 = (dir_raw); } _if_result_13; });
|
||||||
el_val_t p = ({ el_val_t _if_result_20 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_20 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_20 = (p_raw); } _if_result_20; });
|
el_val_t p = ({ el_val_t _if_result_14 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_14 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_14 = (p_raw); } _if_result_14; });
|
||||||
el_val_t sv = engram_save(p);
|
engram_save(p);
|
||||||
el_val_t sv_ok = ({ el_val_t _if_result_21 = 0; if ((sv == 0)) { _if_result_21 = (EL_STR("false")); } else { _if_result_21 = (EL_STR("true")); } _if_result_21; });
|
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}"));
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), sv_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
el_val_t dir = ({ el_val_t _if_result_22 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_22 = (EL_STR("/tmp/engram")); } else { _if_result_22 = (dir_raw); } _if_result_22; });
|
el_val_t dir = ({ el_val_t _if_result_15 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_15 = (EL_STR("/tmp/engram")); } else { _if_result_15 = (dir_raw); } _if_result_15; });
|
||||||
el_val_t p = ({ el_val_t _if_result_23 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_23 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_23 = (p_raw); } _if_result_23; });
|
el_val_t p = ({ el_val_t _if_result_16 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_16 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_16 = (p_raw); } _if_result_16; });
|
||||||
el_val_t ld = engram_load(p);
|
engram_load(p);
|
||||||
el_val_t ld_ok = ({ el_val_t _if_result_24 = 0; if ((ld == 0)) { _if_result_24 = (EL_STR("false")); } else { _if_result_24 = (EL_STR("true")); } _if_result_24; });
|
return ok_json();
|
||||||
el_val_t nc_after = engram_node_count();
|
|
||||||
el_val_t hollow = ({ el_val_t _if_result_25 = 0; if ((nc_after == 0)) { _if_result_25 = (EL_STR("true")); } else { _if_result_25 = (EL_STR("false")); } _if_result_25; });
|
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), ld_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(nc_after)), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR(",\"hollow\":")), hollow), EL_STR("}"));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\",\"node_count\":"), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
return EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}");
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t route_embed_backfill(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
el_val_t n = query_int(path, EL_STR("n"), 32);
|
|
||||||
el_val_t result = engram_embed_backfill(n);
|
|
||||||
el_val_t done = json_get_float(result, EL_STR("embedded"));
|
|
||||||
if (done > el_from_float(0.0)) {
|
|
||||||
el_val_t saved = persist_canonical();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
el_val_t dir = ({ el_val_t _if_result_26 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_26 = (EL_STR("/tmp/engram")); } else { _if_result_26 = (dir_raw); } _if_result_26; });
|
el_val_t dir = ({ el_val_t _if_result_17 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_17 = (EL_STR("/tmp/engram")); } else { _if_result_17 = (dir_raw); } _if_result_17; });
|
||||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/.sync-export.json"));
|
el_val_t snap_path = el_str_concat(dir, EL_STR("/.sync-export.json"));
|
||||||
engram_save(snap_path);
|
engram_save(snap_path);
|
||||||
el_val_t snap = fs_read(snap_path);
|
el_val_t snap = fs_read(snap_path);
|
||||||
if (str_eq(snap, EL_STR(""))) {
|
if (str_eq(snap, EL_STR(""))) {
|
||||||
return err_json(EL_STR("sync export failed: snapshot unreadable"));
|
return EL_STR("{\"nodes\":[],\"edges\":[]}");
|
||||||
}
|
}
|
||||||
return snap;
|
return snap;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -378,7 +305,7 @@ el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t conf = el_from_float(0.8);
|
el_val_t conf = el_from_float(0.8);
|
||||||
el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), sal, imp, conf, EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
|
el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), sal, imp, conf, EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
|
||||||
el_val_t ret_raw = env(EL_STR("ENGRAM_ISE_RETENTION_MS"));
|
el_val_t ret_raw = env(EL_STR("ENGRAM_ISE_RETENTION_MS"));
|
||||||
el_val_t ret_ms = ({ el_val_t _if_result_27 = 0; if (str_eq(ret_raw, EL_STR(""))) { _if_result_27 = (172800000); } else { _if_result_27 = (str_to_int(ret_raw)); } _if_result_27; });
|
el_val_t ret_ms = ({ el_val_t _if_result_18 = 0; if (str_eq(ret_raw, EL_STR(""))) { _if_result_18 = (172800000); } else { _if_result_18 = (str_to_int(ret_raw)); } _if_result_18; });
|
||||||
el_val_t pruned = engram_prune_telemetry(ret_ms);
|
el_val_t pruned = engram_prune_telemetry(ret_ms);
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"pruned\":")), int_to_str(pruned)), EL_STR("}"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"pruned\":")), int_to_str(pruned)), EL_STR("}"));
|
||||||
return 0;
|
return 0;
|
||||||
@@ -390,21 +317,21 @@ el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body)
|
|||||||
return err_json(EL_STR("missing content"));
|
return err_json(EL_STR("missing content"));
|
||||||
}
|
}
|
||||||
el_val_t title = json_get_string(body, EL_STR("title"));
|
el_val_t title = json_get_string(body, EL_STR("title"));
|
||||||
el_val_t label = ({ el_val_t _if_result_28 = 0; if (str_eq(title, EL_STR(""))) { _if_result_28 = (str_slice(content, 0, 60)); } else { _if_result_28 = (title); } _if_result_28; });
|
el_val_t label = ({ el_val_t _if_result_19 = 0; if (str_eq(title, EL_STR(""))) { _if_result_19 = (str_slice(content, 0, 60)); } else { _if_result_19 = (title); } _if_result_19; });
|
||||||
el_val_t category_raw = json_get_string(body, EL_STR("category"));
|
el_val_t category_raw = json_get_string(body, EL_STR("category"));
|
||||||
el_val_t category = ({ el_val_t _if_result_29 = 0; if (str_eq(category_raw, EL_STR(""))) { _if_result_29 = (EL_STR("other")); } else { _if_result_29 = (category_raw); } _if_result_29; });
|
el_val_t category = ({ el_val_t _if_result_20 = 0; if (str_eq(category_raw, EL_STR(""))) { _if_result_20 = (EL_STR("other")); } else { _if_result_20 = (category_raw); } _if_result_20; });
|
||||||
el_val_t ktier_raw = json_get_string(body, EL_STR("tier"));
|
el_val_t ktier_raw = json_get_string(body, EL_STR("tier"));
|
||||||
el_val_t ktier = ({ el_val_t _if_result_30 = 0; if (str_eq(ktier_raw, EL_STR(""))) { _if_result_30 = (EL_STR("note")); } else { _if_result_30 = (ktier_raw); } _if_result_30; });
|
el_val_t ktier = ({ el_val_t _if_result_21 = 0; if (str_eq(ktier_raw, EL_STR(""))) { _if_result_21 = (EL_STR("note")); } else { _if_result_21 = (ktier_raw); } _if_result_21; });
|
||||||
el_val_t project = json_get_string(body, EL_STR("project"));
|
el_val_t project = json_get_string(body, EL_STR("project"));
|
||||||
el_val_t tags_raw = json_get_raw(body, EL_STR("tags"));
|
el_val_t tags_raw = json_get_raw(body, EL_STR("tags"));
|
||||||
el_val_t tags_base = ({ el_val_t _if_result_31 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_31 = (EL_STR("[]")); } else { _if_result_31 = (tags_raw); } _if_result_31; });
|
el_val_t tags_base = ({ el_val_t _if_result_22 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_22 = (EL_STR("[]")); } else { _if_result_22 = (tags_raw); } _if_result_22; });
|
||||||
el_val_t base_len = str_len(tags_base);
|
el_val_t base_len = str_len(tags_base);
|
||||||
el_val_t head = str_slice(tags_base, 0, (base_len - 1));
|
el_val_t head = str_slice(tags_base, 0, (base_len - 1));
|
||||||
el_val_t sep = ({ el_val_t _if_result_32 = 0; if (str_eq(head, EL_STR("["))) { _if_result_32 = (EL_STR("")); } else { _if_result_32 = (EL_STR(",")); } _if_result_32; });
|
el_val_t sep = ({ el_val_t _if_result_23 = 0; if (str_eq(head, EL_STR("["))) { _if_result_23 = (EL_STR("")); } else { _if_result_23 = (EL_STR(",")); } _if_result_23; });
|
||||||
el_val_t safe_cat = str_replace(category, EL_STR("\""), EL_STR("'"));
|
el_val_t safe_cat = str_replace(category, EL_STR("\""), EL_STR("'"));
|
||||||
el_val_t safe_tier = str_replace(ktier, EL_STR("\""), EL_STR("'"));
|
el_val_t safe_tier = str_replace(ktier, EL_STR("\""), EL_STR("'"));
|
||||||
el_val_t safe_proj = str_replace(project, EL_STR("\""), EL_STR("'"));
|
el_val_t safe_proj = str_replace(project, EL_STR("\""), EL_STR("'"));
|
||||||
el_val_t proj_tag = ({ el_val_t _if_result_33 = 0; if (str_eq(safe_proj, EL_STR(""))) { _if_result_33 = (EL_STR("")); } else { _if_result_33 = (el_str_concat(el_str_concat(EL_STR(",\"project:"), safe_proj), EL_STR("\""))); } _if_result_33; });
|
el_val_t proj_tag = ({ el_val_t _if_result_24 = 0; if (str_eq(safe_proj, EL_STR(""))) { _if_result_24 = (EL_STR("")); } else { _if_result_24 = (el_str_concat(el_str_concat(EL_STR(",\"project:"), safe_proj), EL_STR("\""))); } _if_result_24; });
|
||||||
el_val_t tags = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(head, sep), EL_STR("\"category:")), safe_cat), EL_STR("\",\"tier:")), safe_tier), EL_STR("\"")), proj_tag), EL_STR("]"));
|
el_val_t tags = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(head, sep), EL_STR("\"category:")), safe_cat), EL_STR("\",\"tier:")), safe_tier), EL_STR("\"")), proj_tag), EL_STR("]"));
|
||||||
el_val_t sal = el_from_float(0.5);
|
el_val_t sal = el_from_float(0.5);
|
||||||
el_val_t imp = el_from_float(0.5);
|
el_val_t imp = el_from_float(0.5);
|
||||||
@@ -415,20 +342,6 @@ el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_similarity(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
el_val_t a = query_param(path, EL_STR("a"));
|
|
||||||
el_val_t b = query_param(path, EL_STR("b"));
|
|
||||||
if (str_eq(a, EL_STR(""))) {
|
|
||||||
return err_json(EL_STR("missing a"));
|
|
||||||
}
|
|
||||||
if (str_eq(b, EL_STR(""))) {
|
|
||||||
return err_json(EL_STR("missing b"));
|
|
||||||
}
|
|
||||||
el_val_t sim = engram_cosine_sim(a, b);
|
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"a\":\""), a), EL_STR("\",\"b\":\"")), b), EL_STR("\",\"cosine\":")), float_to_str(sim)), EL_STR("}"));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t check_auth_ok(el_val_t method, el_val_t body) {
|
el_val_t check_auth_ok(el_val_t method, el_val_t body) {
|
||||||
el_val_t key = env(EL_STR("ENGRAM_API_KEY"));
|
el_val_t key = env(EL_STR("ENGRAM_API_KEY"));
|
||||||
if (str_eq(key, EL_STR(""))) {
|
if (str_eq(key, EL_STR(""))) {
|
||||||
@@ -464,12 +377,6 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/stats")) || str_eq(clean, EL_STR("/stats")))) {
|
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/stats")) || str_eq(clean, EL_STR("/stats")))) {
|
||||||
return route_stats(method, path, body);
|
return route_stats(method, path, body);
|
||||||
}
|
}
|
||||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/act-stats")) || str_eq(clean, EL_STR("/act-stats")))) {
|
|
||||||
return route_act_stats(method, path, body);
|
|
||||||
}
|
|
||||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/text-health")) || str_eq(clean, EL_STR("/text-health")))) {
|
|
||||||
return route_text_health(method, path, body);
|
|
||||||
}
|
|
||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) {
|
||||||
return route_create_node(method, path, body);
|
return route_create_node(method, path, body);
|
||||||
}
|
}
|
||||||
@@ -488,9 +395,6 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges")) || str_eq(clean, EL_STR("/edges")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges")) || str_eq(clean, EL_STR("/edges")))) {
|
||||||
return route_create_edge(method, path, body);
|
return route_create_edge(method, path, body);
|
||||||
}
|
}
|
||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges/batch")) || str_eq(clean, EL_STR("/edges/batch")))) {
|
|
||||||
return route_create_edges_batch(method, path, body);
|
|
||||||
}
|
|
||||||
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/neighbors/"))) {
|
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/neighbors/"))) {
|
||||||
return route_neighbors(method, path, body);
|
return route_neighbors(method, path, body);
|
||||||
}
|
}
|
||||||
@@ -521,12 +425,6 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
if (str_eq(method, EL_STR("GET")) && str_eq(clean, EL_STR("/api/sync"))) {
|
if (str_eq(method, EL_STR("GET")) && str_eq(clean, EL_STR("/api/sync"))) {
|
||||||
return route_sync(method, path, body);
|
return route_sync(method, path, body);
|
||||||
}
|
}
|
||||||
if (str_eq(clean, EL_STR("/api/embed-backfill"))) {
|
|
||||||
return route_embed_backfill(method, path, body);
|
|
||||||
}
|
|
||||||
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/similarity"))) {
|
|
||||||
return route_similarity(method, path, body);
|
|
||||||
}
|
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -534,10 +432,10 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
int main(int _argc, char** _argv) {
|
int main(int _argc, char** _argv) {
|
||||||
el_runtime_init_args(_argc, _argv);
|
el_runtime_init_args(_argc, _argv);
|
||||||
bind_raw = env(EL_STR("ENGRAM_BIND"));
|
bind_raw = env(EL_STR("ENGRAM_BIND"));
|
||||||
bind_str = ({ el_val_t _if_result_34 = 0; if (str_eq(bind_raw, EL_STR(""))) { _if_result_34 = (EL_STR(":8742")); } else { _if_result_34 = (bind_raw); } _if_result_34; });
|
bind_str = ({ el_val_t _if_result_25 = 0; if (str_eq(bind_raw, EL_STR(""))) { _if_result_25 = (EL_STR(":8742")); } else { _if_result_25 = (bind_raw); } _if_result_25; });
|
||||||
port = parse_port(bind_str);
|
port = parse_port(bind_str);
|
||||||
data_dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
data_dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
data_dir = ({ el_val_t _if_result_35 = 0; if (str_eq(data_dir_raw, EL_STR(""))) { _if_result_35 = (EL_STR("/tmp/engram")); } else { _if_result_35 = (data_dir_raw); } _if_result_35; });
|
data_dir = ({ el_val_t _if_result_26 = 0; if (str_eq(data_dir_raw, EL_STR(""))) { _if_result_26 = (EL_STR("/tmp/engram")); } else { _if_result_26 = (data_dir_raw); } _if_result_26; });
|
||||||
snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json"));
|
snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json"));
|
||||||
engram_load(snapshot_path);
|
engram_load(snapshot_path);
|
||||||
boot_snap = fs_read(snapshot_path);
|
boot_snap = fs_read(snapshot_path);
|
||||||
|
|||||||
+13
-238
@@ -76,38 +76,6 @@ fn route_stats(method: String, path: String, body: String) -> String {
|
|||||||
engram_stats_json()
|
engram_stats_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
// route_act_stats — GET /api/act-stats
|
|
||||||
// (2026-08-04 self-review) engram_act_stats_json() has existed since the
|
|
||||||
// 2026-07-27 review but was reachable ONLY through the soul daemon's heartbeat
|
|
||||||
// binding. Every activation-layer gauge — WM evictions, breakthroughs, embedder
|
|
||||||
// breaker state, context drift, and now the Hebbian counters — was therefore
|
|
||||||
// invisible unless the soul happened to be running and its ISEs were read back
|
|
||||||
// out of the store. Diagnosing the activation layer required a working soul,
|
|
||||||
// which is exactly backwards: the lower layer should be observable on its own.
|
|
||||||
// This review needed it to verify link formation and could not get at it. One
|
|
||||||
// line of plumbing, and the whole activation layer becomes directly diagnosable.
|
|
||||||
fn route_act_stats(method: String, path: String, body: String) -> String {
|
|
||||||
engram_act_stats_json()
|
|
||||||
}
|
|
||||||
|
|
||||||
// route_text_health — GET /api/text-health
|
|
||||||
// (2026-08-08 self-review) The daily census half of the text-integrity gauge.
|
|
||||||
// Today's review found that the JSON parser had been replacing every \uXXXX
|
|
||||||
// escape with a literal '?' for at least two months: 3,119 of 4,081
|
|
||||||
// non-telemetry nodes (76%) were damaged, including the self traversal root
|
|
||||||
// and every values node, and NOTHING detected it — because every gauge in the
|
|
||||||
// system measured whether the machinery was running, and none measured whether
|
|
||||||
// the text it carried was intact. No snapshot on disk predates the damage, so
|
|
||||||
// it cannot be undone; it can only be made impossible to repeat quietly.
|
|
||||||
//
|
|
||||||
// The parser is fixed. This route is the standing check: `damaged` should now
|
|
||||||
// hold flat at its historical floor and never climb. `write_damaged` (also on
|
|
||||||
// the heartbeat as txt_damaged) is the live regression signal — non-zero means
|
|
||||||
// a write path is mangling text right now.
|
|
||||||
fn route_text_health(method: String, path: String, body: String) -> String {
|
|
||||||
engram_text_health_json()
|
|
||||||
}
|
|
||||||
|
|
||||||
// (2026-07-18 self-review) Scoping sweep: `let` inside an if-block creates an
|
// (2026-07-18 self-review) Scoping sweep: `let` inside an if-block creates an
|
||||||
// inner scope only — it does NOT mutate the outer binding (documented with
|
// inner scope only — it does NOT mutate the outer binding (documented with
|
||||||
// evidence in awareness.el, 2026-05-25). Every default/reassignment below used
|
// evidence in awareness.el, 2026-05-25). Every default/reassignment below used
|
||||||
@@ -133,54 +101,17 @@ fn route_text_health(method: String, path: String, body: String) -> String {
|
|||||||
fn persist_canonical() -> Int {
|
fn persist_canonical() -> Int {
|
||||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
// (2026-08-10 self-review) This returned a hardcoded 1, which made every
|
engram_save(dir + "/snapshot.json")
|
||||||
// caller's `let saved: Int = persist_canonical()` a dead variable — six
|
return 1
|
||||||
// durable write paths each believed they had confirmation of a successful
|
|
||||||
// canonical persist and none of them had any. Propagate the real result.
|
|
||||||
return engram_save(dir + "/snapshot.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// INCOMPLETE-ROUTE FIX (2026-07-24 self-review): this route silently dropped
|
|
||||||
// label, importance, tier, and tags — engram_node() defaults label to content
|
|
||||||
// and importance to 0.5, so every node created over HTTP lost its metadata.
|
|
||||||
// Observed live: the soul's boot-counter write-back landed with
|
|
||||||
// label="soul:boot_count:99" (content), importance 0.5, no tags. Honor the
|
|
||||||
// full field set via engram_node_full when any of them is supplied.
|
|
||||||
// PRESENCE-AWARE DEFAULTS (2026-08-01 self-review): the old pattern
|
|
||||||
// `if x == 0.0 { default }` made a legitimate 0.0 unrepresentable — a caller
|
|
||||||
// setting salience/importance/weight to zero silently got 0.5. json_get_raw
|
|
||||||
// returns "" when the key is ABSENT and the raw token when present, so
|
|
||||||
// absence and zero are now distinguishable. Also: confidence was hardcoded
|
|
||||||
// to 1.0 regardless of input — every HTTP-created node claimed full
|
|
||||||
// epistemic confidence. Now honored from the payload (default 1.0).
|
|
||||||
fn route_create_node(method: String, path: String, body: String) -> String {
|
fn route_create_node(method: String, path: String, body: String) -> String {
|
||||||
let content: String = json_get_string(body, "content")
|
let content: String = json_get_string(body, "content")
|
||||||
let nt_raw: String = json_get_string(body, "node_type")
|
let nt_raw: String = json_get_string(body, "node_type")
|
||||||
let node_type: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw }
|
let node_type: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw }
|
||||||
let sal_present: String = json_get_raw(body, "salience")
|
let sal_raw: Float = json_get_float(body, "salience")
|
||||||
let salience: Float = if str_eq(sal_present, "") { 0.5 } else { json_get_float(body, "salience") }
|
let salience: Float = if sal_raw == 0.0 { 0.5 } else { sal_raw }
|
||||||
let label_raw: String = json_get_string(body, "label")
|
let id: String = engram_node(content, node_type, salience)
|
||||||
let label: String = if str_eq(label_raw, "") { content } else { label_raw }
|
|
||||||
let imp_present: String = json_get_raw(body, "importance")
|
|
||||||
let importance: Float = if str_eq(imp_present, "") { 0.5 } else { json_get_float(body, "importance") }
|
|
||||||
let conf_present: String = json_get_raw(body, "confidence")
|
|
||||||
let confidence: Float = if str_eq(conf_present, "") { 1.0 } else { json_get_float(body, "confidence") }
|
|
||||||
let tier_raw: String = json_get_string(body, "tier")
|
|
||||||
let tier: String = if str_eq(tier_raw, "") { "Working" } else { tier_raw }
|
|
||||||
let tags: String = json_get_string(body, "tags")
|
|
||||||
// NO el_from_float WRAPPER (2026-08-01 self-review): salience/importance/
|
|
||||||
// confidence are already Float (el_val_t) values — json_get_float and
|
|
||||||
// Float literals both encode. Wrapping them in el_from_float AGAIN
|
|
||||||
// reinterpreted the boxed bits as a raw double, producing garbage that
|
|
||||||
// failed engram_decode_score's range check and clamped every HTTP-created
|
|
||||||
// node to defaults (salience 0.9 in → 0.5 stored; confidence 0.6 in → 1.0
|
|
||||||
// stored — verified live). route_emit_ise always passed Floats bare and
|
|
||||||
// its 0.3/0.3/0.8 stored correctly; this call now does the same.
|
|
||||||
let id: String = engram_node_full(
|
|
||||||
content, node_type, label,
|
|
||||||
salience, importance, confidence,
|
|
||||||
tier, tags
|
|
||||||
)
|
|
||||||
let saved: Int = persist_canonical()
|
let saved: Int = persist_canonical()
|
||||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
||||||
}
|
}
|
||||||
@@ -246,64 +177,13 @@ fn route_create_edge(method: String, path: String, body: String) -> String {
|
|||||||
let to_id: String = json_get_string(body, "to_id")
|
let to_id: String = json_get_string(body, "to_id")
|
||||||
let rel_raw: String = json_get_string(body, "relation")
|
let rel_raw: String = json_get_string(body, "relation")
|
||||||
let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw }
|
let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw }
|
||||||
// Presence-aware (2026-08-01): weight 0.0 is a legitimate edge weight
|
let w_raw: Float = json_get_float(body, "weight")
|
||||||
// (dormant association); only default when the key is absent.
|
let weight: Float = if w_raw == 0.0 { 0.5 } else { w_raw }
|
||||||
let w_present: String = json_get_raw(body, "weight")
|
|
||||||
let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(body, "weight") }
|
|
||||||
engram_connect(from_id, to_id, weight, relation)
|
engram_connect(from_id, to_id, weight, relation)
|
||||||
let saved: Int = persist_canonical()
|
let saved: Int = persist_canonical()
|
||||||
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// route_create_edges_batch — POST /api/edges/batch {"edges":[{from_id,to_id,relation,weight}, ...]}
|
|
||||||
//
|
|
||||||
// WHY THIS EXISTS (2026-08-07 self-review). persist_canonical() writes the
|
|
||||||
// FULL canonical snapshot — 60MB at current graph size — and route_create_edge
|
|
||||||
// calls it once per edge. That is correct for the interactive one-edge case and
|
|
||||||
// ruinous for any bulk write: the soul's Hebbian consolidation path delivers
|
|
||||||
// ~14 associations per 8-minute heartbeat, which through the single-edge route
|
|
||||||
// would be ~840MB of disk writes per beat, ~150GB/day, to persist 14 edges.
|
|
||||||
//
|
|
||||||
// The fix is not to weaken durability — it is to make the unit of durability
|
|
||||||
// the BATCH. Connect every edge, then snapshot exactly once. Same guarantee
|
|
||||||
// (nothing acknowledged is lost to a restart), 1/N the writes. Empty or
|
|
||||||
// malformed entries are skipped rather than aborting the batch: a consolidation
|
|
||||||
// payload is best-effort by design, and one bad id should not cost the other 13.
|
|
||||||
//
|
|
||||||
// Returns the accepted count so the caller can tell delivery from silence.
|
|
||||||
fn route_create_edges_batch(method: String, path: String, body: String) -> String {
|
|
||||||
let arr: String = json_get_raw(body, "edges")
|
|
||||||
if str_eq(arr, "") { return err_json("missing edges array") }
|
|
||||||
let n: Int = json_array_len(arr)
|
|
||||||
if n == 0 { return "{\"ok\":true,\"accepted\":0,\"skipped\":0}" }
|
|
||||||
let i: Int = 0
|
|
||||||
let accepted: Int = 0
|
|
||||||
let skipped: Int = 0
|
|
||||||
while i < n {
|
|
||||||
let item: String = json_array_get(arr, i)
|
|
||||||
let from_id: String = json_get_string(item, "from_id")
|
|
||||||
let to_id: String = json_get_string(item, "to_id")
|
|
||||||
if str_eq(from_id, "") || str_eq(to_id, "") {
|
|
||||||
let skipped = skipped + 1
|
|
||||||
} else {
|
|
||||||
let rel_raw: String = json_get_string(item, "relation")
|
|
||||||
let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw }
|
|
||||||
let w_present: String = json_get_raw(item, "weight")
|
|
||||||
let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(item, "weight") }
|
|
||||||
engram_connect(from_id, to_id, weight, relation)
|
|
||||||
let accepted = accepted + 1
|
|
||||||
}
|
|
||||||
let i = i + 1
|
|
||||||
}
|
|
||||||
// ONE snapshot for the whole batch — the entire point of this route.
|
|
||||||
// Skip it when nothing was accepted: an all-malformed payload must not
|
|
||||||
// trigger a 60MB write.
|
|
||||||
if accepted > 0 {
|
|
||||||
let saved: Int = persist_canonical()
|
|
||||||
}
|
|
||||||
return "{\"ok\":true,\"accepted\":" + int_to_str(accepted) + ",\"skipped\":" + int_to_str(skipped) + "}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn route_neighbors(method: String, path: String, body: String) -> String {
|
fn route_neighbors(method: String, path: String, body: String) -> String {
|
||||||
let id: String = extract_id(path, "/api/neighbors/")
|
let id: String = extract_id(path, "/api/neighbors/")
|
||||||
if str_eq(id, "") { return err_json("missing id") }
|
if str_eq(id, "") { return err_json("missing id") }
|
||||||
@@ -332,15 +212,8 @@ fn route_save(method: String, path: String, body: String) -> String {
|
|||||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||||
// (2026-08-10 self-review) engram_save returns 0 on an empty path and the
|
engram_save(p)
|
||||||
// route discarded it, so the response was a literal "ok":true regardless
|
"{\"ok\":true,\"path\":\"" + p + "\"}"
|
||||||
// of whether anything was written. Report the actual result AND the counts
|
|
||||||
// that were supposed to have been written — the same move that made
|
|
||||||
// route_health honest on 2026-08-01. A caller can now tell "saved 13k
|
|
||||||
// nodes" from "saved nothing and said ok".
|
|
||||||
let sv: Int = engram_save(p)
|
|
||||||
let sv_ok: String = if sv == 0 { "false" } else { "true" }
|
|
||||||
"{\"ok\":" + sv_ok + ",\"path\":\"" + p + "\",\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_load(method: String, path: String, body: String) -> String {
|
fn route_load(method: String, path: String, body: String) -> String {
|
||||||
@@ -348,59 +221,12 @@ fn route_load(method: String, path: String, body: String) -> String {
|
|||||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||||
// (2026-08-10 self-review) This was a stub response over the single most
|
engram_load(p)
|
||||||
// destructive operation in the server. engram_load returns 0 on an empty
|
ok_json()
|
||||||
// path, an unopenable file, a zero-length file, or malloc failure — and
|
|
||||||
// this route answered ok_json() in every one of those cases.
|
|
||||||
//
|
|
||||||
// Precise failure shape (el_runtime.c:9890): the fopen guard runs BEFORE
|
|
||||||
// the store reset, so a MISSING path is genuinely safe — it returns 0 with
|
|
||||||
// the graph intact. The dangerous case is a readable-but-malformed file:
|
|
||||||
// the reset loop frees every node and edge FIRST, then parses, so a
|
|
||||||
// truncated or non-snapshot JSON leaves a hollow store — and the caller
|
|
||||||
// was told "ok":true. With 37 GB of stale dated snapshots sitting in the
|
|
||||||
// data dir as tempting restore targets, "restore reported success and
|
|
||||||
// silently emptied the graph" is a live risk, not a hypothetical one.
|
|
||||||
//
|
|
||||||
// Fix: surface the return value AND the resulting counts. node_count=0
|
|
||||||
// after a load is the unambiguous hollow-store signal (same convention
|
|
||||||
// route_health adopted 2026-08-01). Callers can now verify a restore
|
|
||||||
// instead of trusting it.
|
|
||||||
let ld: Int = engram_load(p)
|
|
||||||
let ld_ok: String = if ld == 0 { "false" } else { "true" }
|
|
||||||
let nc_after: Int = engram_node_count()
|
|
||||||
let hollow: String = if nc_after == 0 { "true" } else { "false" }
|
|
||||||
"{\"ok\":" + ld_ok + ",\"path\":\"" + p + "\",\"node_count\":" + int_to_str(nc_after) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + ",\"hollow\":" + hollow + "}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// (2026-08-01 self-review) Health previously returned a hardcoded literal —
|
|
||||||
// it reported "ok" even when the snapshot failed to load and the store was
|
|
||||||
// empty. Now reports live counts so a monitor can distinguish "up and
|
|
||||||
// loaded" from "up and hollow" (node_count=0 after boot = failed load).
|
|
||||||
fn route_health(method: String, path: String, body: String) -> String {
|
fn route_health(method: String, path: String, body: String) -> String {
|
||||||
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\",\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}"
|
||||||
}
|
|
||||||
|
|
||||||
// route_embed_backfill — GET/POST /api/embed-backfill?n=48
|
|
||||||
//
|
|
||||||
// (2026-07-25 self-review) The lazy embedding backfill runs only inside
|
|
||||||
// engram_activate, and nothing in production calls /api/activate on this
|
|
||||||
// store — the soul's curiosity loop activates its own in-process graph.
|
|
||||||
// After a restart from a snapshot without vectors, embedded_count stalled
|
|
||||||
// at 93/12175 and would never recover. This route lets the soul's
|
|
||||||
// heartbeat pump the backfill explicitly (48/min clears a 12k backlog in
|
|
||||||
// ~4h). Persists the canonical snapshot whenever new vectors were
|
|
||||||
// generated — the 2026-07-25 regression happened precisely because 3747
|
|
||||||
// in-RAM embeddings were never snapshotted before a restart. Self-
|
|
||||||
// limiting: once coverage is full, embedded=0 and no save occurs.
|
|
||||||
fn route_embed_backfill(method: String, path: String, body: String) -> String {
|
|
||||||
let n: Int = query_int(path, "n", 32)
|
|
||||||
let result: String = engram_embed_backfill(n)
|
|
||||||
let done: Float = json_get_float(result, "embedded")
|
|
||||||
if done > 0.0 {
|
|
||||||
let saved: Int = persist_canonical()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route_sync — return a snapshot of non-ISE/non-Working nodes for the soul daemon
|
// route_sync — return a snapshot of non-ISE/non-Working nodes for the soul daemon
|
||||||
@@ -423,16 +249,7 @@ fn route_sync(method: String, path: String, body: String) -> String {
|
|||||||
let snap_path: String = dir + "/.sync-export.json"
|
let snap_path: String = dir + "/.sync-export.json"
|
||||||
engram_save(snap_path)
|
engram_save(snap_path)
|
||||||
let snap: String = fs_read(snap_path)
|
let snap: String = fs_read(snap_path)
|
||||||
// 2026-08-02 self-review: this used to return {"nodes":[],"edges":[]} when
|
if str_eq(snap, "") { return "{\"nodes\":[],\"edges\":[]}" }
|
||||||
// the export/read failed. The soul's sync_ok test (awareness.el) only
|
|
||||||
// checks for "" and "{}", so that placeholder PASSED as a healthy sync:
|
|
||||||
// soul.last_sync_ok_ts got stamped, sync_age_ms stayed green, the
|
|
||||||
// sync_empty warn ISE never fired, and engram_sync reported added:0
|
|
||||||
// forever. A totally broken sync was indistinguishable from a quiet
|
|
||||||
// healthy one — the exact failure class this route was added to fix in
|
|
||||||
// the first place (see 2026-06-27 note above). Return a real error so the
|
|
||||||
// failure is loud on both sides.
|
|
||||||
if str_eq(snap, "") { return err_json("sync export failed: snapshot unreadable") }
|
|
||||||
return snap
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,25 +371,6 @@ fn route_capture_knowledge(method: String, path: String, body: String) -> String
|
|||||||
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// route_similarity — GET /api/similarity?a=<id>&b=<id>
|
|
||||||
//
|
|
||||||
// (2026-08-01 self-review) engram_cosine_sim was added 2026-07-24
|
|
||||||
// (bl-b2d1c944) with the stated purpose of exposing semantic distance to
|
|
||||||
// "EL code and the introspection API" — but it had ZERO callers anywhere:
|
|
||||||
// no route, no soul-daemon use. The activation path uses embeddings
|
|
||||||
// internally (semantic seeding, Pass-2 additive term), but there was no way
|
|
||||||
// to probe pairwise node similarity from outside. This closes that: cosine
|
|
||||||
// in [-1,1], or -2 when either node is missing or not yet embedded (so
|
|
||||||
// "not comparable" is distinguishable from "genuinely orthogonal" 0.0).
|
|
||||||
fn route_similarity(method: String, path: String, body: String) -> String {
|
|
||||||
let a: String = query_param(path, "a")
|
|
||||||
let b: String = query_param(path, "b")
|
|
||||||
if str_eq(a, "") { return err_json("missing a") }
|
|
||||||
if str_eq(b, "") { return err_json("missing b") }
|
|
||||||
let sim: Float = engram_cosine_sim(a, b)
|
|
||||||
"{\"a\":\"" + a + "\",\"b\":\"" + b + "\",\"cosine\":" + float_to_str(sim) + "}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn check_auth_ok(method: String, body: String) -> Bool {
|
fn check_auth_ok(method: String, body: String) -> Bool {
|
||||||
@@ -619,12 +417,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(method, "GET") && (str_eq(clean, "/api/stats") || str_eq(clean, "/stats")) {
|
if str_eq(method, "GET") && (str_eq(clean, "/api/stats") || str_eq(clean, "/stats")) {
|
||||||
return route_stats(method, path, body)
|
return route_stats(method, path, body)
|
||||||
}
|
}
|
||||||
if str_eq(method, "GET") && (str_eq(clean, "/api/act-stats") || str_eq(clean, "/act-stats")) {
|
|
||||||
return route_act_stats(method, path, body)
|
|
||||||
}
|
|
||||||
if str_eq(method, "GET") && (str_eq(clean, "/api/text-health") || str_eq(clean, "/text-health")) {
|
|
||||||
return route_text_health(method, path, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
|
||||||
@@ -647,13 +439,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) {
|
||||||
return route_create_edge(method, path, body)
|
return route_create_edge(method, path, body)
|
||||||
}
|
}
|
||||||
// Batch edge write — one snapshot for the whole payload. Must be tested
|
|
||||||
// BEFORE nothing else claims it; the exact-match on "/api/edges" above
|
|
||||||
// does not catch "/api/edges/batch", so order is not load-bearing here,
|
|
||||||
// but keeping the two adjacent keeps them from drifting apart.
|
|
||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/edges/batch") || str_eq(clean, "/edges/batch")) {
|
|
||||||
return route_create_edges_batch(method, path, body)
|
|
||||||
}
|
|
||||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighbors/") {
|
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighbors/") {
|
||||||
return route_neighbors(method, path, body)
|
return route_neighbors(method, path, body)
|
||||||
}
|
}
|
||||||
@@ -693,16 +478,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
return route_sync(method, path, body)
|
return route_sync(method, path, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embedding backfill — pumped by the soul heartbeat (2026-07-25)
|
|
||||||
if str_eq(clean, "/api/embed-backfill") {
|
|
||||||
return route_embed_backfill(method, path, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic similarity probe (2026-08-01)
|
|
||||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/similarity") {
|
|
||||||
return route_similarity(method, path, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,16 @@
|
|||||||
// 4. Append dep to order after all its transitive deps
|
// 4. Append dep to order after all its transitive deps
|
||||||
// 5. Deduplicate: skip already-ordered vessels
|
// 5. Deduplicate: skip already-ordered vessels
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// Defined in sibling epm modules; resolved at link time. The `extern fn` decls
|
||||||
|
// give elc the C prototypes so generated install.c compiles cleanly under strict
|
||||||
|
// compilers (gcc>=14 / clang) that reject implicit function declarations.
|
||||||
|
extern fn manifest_name(src: String) -> String // manifest.el
|
||||||
|
extern fn manifest_deps(src: String) -> String // manifest.el
|
||||||
|
extern fn registry_token() -> String // registry.el
|
||||||
|
extern fn registry_find(name: String, version: String) -> String // registry.el
|
||||||
|
extern fn registry_latest_version(name: String) -> String // registry.el
|
||||||
|
|
||||||
// ── Install paths ─────────────────────────────────────────────────────────────
|
// ── Install paths ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// packages_dir returns the root directory for installed vessels.
|
// packages_dir returns the root directory for installed vessels.
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
// EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
|
// EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
|
||||||
// EPM_TOKEN — Gitea personal access token (required for publish)
|
// EPM_TOKEN — Gitea personal access token (required for publish)
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// These symbols are defined in sibling epm modules or the El runtime and are
|
||||||
|
// resolved at link time. The `extern fn` decls give elc the C prototype so the
|
||||||
|
// generated registry.c compiles cleanly under strict compilers (gcc>=14 / clang)
|
||||||
|
// that reject implicit function declarations. Signature arity must match the
|
||||||
|
// definition; return/param types are informational (all lower to el_val_t).
|
||||||
|
extern fn config(key: String) -> String // El runtime builtin
|
||||||
|
extern fn read_installed() -> String // install.el
|
||||||
|
|
||||||
// ── Config helpers ────────────────────────────────────────────────────────────
|
// ── Config helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// registry_api_url returns the Gitea API base URL with no trailing slash.
|
// registry_api_url returns the Gitea API base URL with no trailing slash.
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
// Depends on: registry.el (registry_latest_version, registry_find),
|
// Depends on: registry.el (registry_latest_version, registry_find),
|
||||||
// install.el (read_installed, install_vessel, installed_version)
|
// install.el (read_installed, install_vessel, installed_version)
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// Defined in sibling epm modules; resolved at link time. The `extern fn` decls
|
||||||
|
// give elc the C prototypes so generated update.c compiles cleanly under strict
|
||||||
|
// compilers (gcc>=14 / clang) that reject implicit function declarations.
|
||||||
|
extern fn read_installed() -> String // install.el
|
||||||
|
extern fn installed_version(name: String) -> String // install.el
|
||||||
|
extern fn install_vessel(name: String, version: String) -> Bool // install.el
|
||||||
|
extern fn registry_latest_version(name: String) -> String // registry.el
|
||||||
|
|
||||||
// ── Semver helpers ────────────────────────────────────────────────────────────
|
// ── Semver helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// semver_part extracts the Nth dot-separated component from a semver string.
|
// semver_part extracts the Nth dot-separated component from a semver string.
|
||||||
|
|||||||
@@ -123,22 +123,9 @@ exec("EL_HTTP_TIMEOUT_MS=300000 " + SOME_BIN + " " + args + " 2>&1")
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Operator naming convention (for cognitive `.el` modules)
|
|
||||||
|
|
||||||
When you write `.el` that names a **cognitive faculty / operator** (the language
|
|
||||||
faculty, appreciation, interoception, the summon loop under `elp/`), name it for
|
|
||||||
its **functional human equivalent** — the faculty a mind would name — **not** its
|
|
||||||
linear-algebra operation. Put the math characterization in the `@impl`
|
|
||||||
doc-comment, never in the operator's public name (e.g. public `discern`/`contrast`
|
|
||||||
⟵ `@impl subtract (a−b)`; public `summon`/`recall` ⟵ `@impl LOCAL nearest-region
|
|
||||||
+ bounded spreading activation`). Full table + rationale in
|
|
||||||
`foundation/el/AGENTS.md` § *Operator naming convention*. This does **not** apply
|
|
||||||
to plain library/compiler code (a `sort` is a `sort`).
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- New library functions → write in El
|
- New library functions → write in El
|
||||||
- New OS/hardware primitives → write in C and register in `codegen.el` arity table
|
- New OS/hardware primitives → write in C and register in `codegen.el` arity table
|
||||||
- Never edit `dist/platform/elc` directly — always rebuild from source
|
- Never edit `dist/platform/elc` directly — always rebuild from source
|
||||||
- Never modify `el_seed.c` to add functionality that El can express
|
- Never modify `el_seed.c` to add functionality that El can express
|
||||||
- Cognitive-faculty `.el` → name for the mind, algebra in `@impl` (see above)
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ static inline void* el_win_dlsym(void* handle, const char* name) {
|
|||||||
#include <direct.h> /* _mkdir */
|
#include <direct.h> /* _mkdir */
|
||||||
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||||
#define timegm _mkgmtime /* UTC tm → time_t */
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||||
|
#define fsync(fd) _commit(fd) /* no fsync() on Windows; _commit() (<io.h>) is the equiv */
|
||||||
|
|
||||||
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||||
static inline int setenv(const char* name, const char* value, int overwrite) {
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1072,6 +1072,7 @@ el_val_t __engram_save(el_val_t path) { return engram_save
|
|||||||
el_val_t __engram_load(el_val_t path) { return engram_load(path); }
|
el_val_t __engram_load(el_val_t path) { return engram_load(path); }
|
||||||
|
|
||||||
el_val_t __engram_get_node_json(el_val_t id) { return engram_get_node_json(id); }
|
el_val_t __engram_get_node_json(el_val_t id) { return engram_get_node_json(id); }
|
||||||
|
el_val_t __engram_get_node_by_label(el_val_t label) { return engram_get_node_by_label(label); }
|
||||||
|
|
||||||
el_val_t __engram_search_json(el_val_t query, el_val_t limit) {
|
el_val_t __engram_search_json(el_val_t query, el_val_t limit) {
|
||||||
return engram_search_json(query, limit);
|
return engram_search_json(query, limit);
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ el_val_t __engram_activate(el_val_t query, el_val_t depth);
|
|||||||
el_val_t __engram_save(el_val_t path);
|
el_val_t __engram_save(el_val_t path);
|
||||||
el_val_t __engram_load(el_val_t path);
|
el_val_t __engram_load(el_val_t path);
|
||||||
el_val_t __engram_get_node_json(el_val_t id);
|
el_val_t __engram_get_node_json(el_val_t id);
|
||||||
|
el_val_t __engram_get_node_by_label(el_val_t label);
|
||||||
el_val_t __engram_search_json(el_val_t query, el_val_t limit);
|
el_val_t __engram_search_json(el_val_t query, el_val_t limit);
|
||||||
el_val_t __engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
el_val_t __engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||||
el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||||
|
|||||||
@@ -2670,6 +2670,7 @@ fn builtin_arity(name: String) -> Int {
|
|||||||
if str_eq(name, "engram_save") { return 1 }
|
if str_eq(name, "engram_save") { return 1 }
|
||||||
if str_eq(name, "engram_load") { return 1 }
|
if str_eq(name, "engram_load") { return 1 }
|
||||||
if str_eq(name, "engram_get_node_json") { return 1 }
|
if str_eq(name, "engram_get_node_json") { return 1 }
|
||||||
|
if str_eq(name, "engram_get_node_by_label") { return 1 }
|
||||||
if str_eq(name, "engram_search_json") { return 2 }
|
if str_eq(name, "engram_search_json") { return 2 }
|
||||||
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
||||||
if str_eq(name, "engram_neighbors_json") { return 3 }
|
if str_eq(name, "engram_neighbors_json") { return 3 }
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
#ifndef EL_PLATFORM_WIN_H
|
||||||
|
#define EL_PLATFORM_WIN_H
|
||||||
|
/*
|
||||||
|
* el_platform_win.h — Windows OS-boundary shim for el_runtime.c.
|
||||||
|
*
|
||||||
|
* Branch: feat/windows-el-runtime. Included ONLY when _WIN32 is defined; the POSIX build is
|
||||||
|
* untouched. Goal: let el_runtime.c (a BSD-sockets / dlfcn / fork host) compile and link with
|
||||||
|
* mingw-w64 into a native neuron.exe, with no behavioural change to the Linux/macOS build.
|
||||||
|
*
|
||||||
|
* What it maps:
|
||||||
|
* - sockets : winsock2 (same call names: socket/bind/listen/accept/recv/send/setsockopt).
|
||||||
|
* Sockets close with closesocket() (see el_closesocket), and the stack must be
|
||||||
|
* started once with WSAStartup — done automatically via a load-time constructor.
|
||||||
|
* - dlsym : el_runtime.c uses dlsym(RTLD_DEFAULT, name) to resolve callback/tool symbols
|
||||||
|
* exported by the main module. Windows equivalent: GetProcAddress on the process
|
||||||
|
* module. Link the soul with -Wl,--export-all-symbols so the symbols are findable.
|
||||||
|
* - popen : mapped to _popen/_pclose.
|
||||||
|
* - threads : UNCHANGED. mingw-w64 ships winpthreads, so <pthread.h> + -lpthread just work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <process.h>
|
||||||
|
|
||||||
|
/* Portable headers mingw-w64 provides (verified present). */
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h> /* strcasecmp */
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h> /* mingw-w64 provides gettimeofday here */
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
/* ── socket close ─────────────────────────────────────────────────────────── */
|
||||||
|
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
||||||
|
build defines the same helper as close() so the call sites are identical across platforms. */
|
||||||
|
static inline int el_closesocket(SOCKET s) { return closesocket(s); }
|
||||||
|
|
||||||
|
/* ── setsockopt optval type ───────────────────────────────────────────────── */
|
||||||
|
/* Winsock's setsockopt takes optval as (const char*); POSIX takes (const void*), so el_runtime.c
|
||||||
|
passes &int directly. GCC 14+ makes that an error under -Wincompatible-pointer-types. Wrap it so
|
||||||
|
the runtime's POSIX-style call sites compile unchanged (defined before the macro so the wrapper
|
||||||
|
itself resolves to the real winsock setsockopt). */
|
||||||
|
static inline int el_setsockopt(SOCKET s, int level, int optname, const void* optval, int optlen) {
|
||||||
|
return setsockopt(s, level, optname, (const char*)optval, optlen);
|
||||||
|
}
|
||||||
|
#define setsockopt(s, l, o, v, n) el_setsockopt((s), (l), (o), (v), (int)(n))
|
||||||
|
|
||||||
|
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
||||||
|
static void el__win_net_init(void) {
|
||||||
|
static int inited = 0;
|
||||||
|
if (!inited) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); inited = 1; }
|
||||||
|
}
|
||||||
|
__attribute__((constructor)) static void el__win_ctor(void) { el__win_net_init(); }
|
||||||
|
|
||||||
|
/* ── dlsym → GetProcAddress ───────────────────────────────────────────────── */
|
||||||
|
#ifndef RTLD_DEFAULT
|
||||||
|
#define RTLD_DEFAULT ((void*)0)
|
||||||
|
#endif
|
||||||
|
static inline void* el_win_dlsym(void* handle, const char* name) {
|
||||||
|
(void)handle;
|
||||||
|
return (void*)(uintptr_t)GetProcAddress(GetModuleHandleA(NULL), name);
|
||||||
|
}
|
||||||
|
#define dlsym(h, n) el_win_dlsym((h), (n))
|
||||||
|
|
||||||
|
/* ── popen / pclose ───────────────────────────────────────────────────────── */
|
||||||
|
#define popen _popen
|
||||||
|
#define pclose _pclose
|
||||||
|
|
||||||
|
/* ── misc POSIX → Win32 shims ─────────────────────────────────────────────── */
|
||||||
|
#include <direct.h> /* _mkdir */
|
||||||
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||||
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||||
|
|
||||||
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||||
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||||
|
(void)overwrite;
|
||||||
|
return _putenv_s(name, value ? value : "");
|
||||||
|
}
|
||||||
|
static inline int unsetenv(const char* name) {
|
||||||
|
/* _putenv_s(name, "") sets VAR="" rather than removing it.
|
||||||
|
* SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32
|
||||||
|
* env block; then we sync the CRT cache with _putenv("NAME="). */
|
||||||
|
SetEnvironmentVariableA(name, NULL);
|
||||||
|
size_t len = strlen(name);
|
||||||
|
char *buf = (char*)malloc(len + 2);
|
||||||
|
if (!buf) return -1;
|
||||||
|
memcpy(buf, name, len);
|
||||||
|
buf[len] = '=';
|
||||||
|
buf[len + 1] = '\0';
|
||||||
|
_putenv(buf);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */
|
||||||
|
static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) {
|
||||||
|
(void)rem;
|
||||||
|
DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL));
|
||||||
|
Sleep(ms ? ms : 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#define nanosleep(req, rem) el_nanosleep((req), (rem))
|
||||||
|
|
||||||
|
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
||||||
|
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
||||||
|
return localtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||||
|
}
|
||||||
|
static inline struct tm* gmtime_r(const time_t* t, struct tm* out) {
|
||||||
|
return gmtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── libcurl: degradable stubs for the curl-less Windows build ─────────────── */
|
||||||
|
/* The curl-less validation build (WITH_CURL=0) links no libcurl. el_runtime.c uses libcurl
|
||||||
|
* unconditionally for its HTTP client / LLM layer; these stubs let it compile and link so the
|
||||||
|
* runtime, HTTP *server*, graph and memory work natively on Windows. Live outbound HTTP/LLM calls
|
||||||
|
* degrade to a runtime error (curl_easy_perform returns an error) — matching the documented
|
||||||
|
* curl-less contract. When HAVE_CURL is defined (WITH_CURL=1) the real <curl/curl.h> is used and
|
||||||
|
* this whole block is compiled out. POSIX never sees this header, so the POSIX build is untouched. */
|
||||||
|
#ifndef HAVE_CURL
|
||||||
|
|
||||||
|
typedef void CURL;
|
||||||
|
typedef int CURLcode;
|
||||||
|
|
||||||
|
#define CURLE_OK 0
|
||||||
|
#define CURLE_HTTP_RETURNED_ERROR 22
|
||||||
|
#define CURL_ERROR_SIZE 256
|
||||||
|
|
||||||
|
/* Option ids: values are irrelevant to the no-op setopt below; kept distinct for readability. */
|
||||||
|
#define CURLOPT_URL 10002
|
||||||
|
#define CURLOPT_WRITEFUNCTION 20011
|
||||||
|
#define CURLOPT_WRITEDATA 10001
|
||||||
|
#define CURLOPT_POSTFIELDS 10015
|
||||||
|
#define CURLOPT_POSTFIELDSIZE 120
|
||||||
|
#define CURLOPT_POST 47
|
||||||
|
#define CURLOPT_HTTPHEADER 10023
|
||||||
|
#define CURLOPT_TIMEOUT_MS 155
|
||||||
|
#define CURLOPT_NOSIGNAL 99
|
||||||
|
#define CURLOPT_USERAGENT 10018
|
||||||
|
#define CURLOPT_FOLLOWLOCATION 52
|
||||||
|
#define CURLOPT_ERRORBUFFER 10010
|
||||||
|
#define CURLOPT_CUSTOMREQUEST 10036
|
||||||
|
#define CURLOPT_FAILONERROR 45
|
||||||
|
|
||||||
|
struct curl_slist { char* data; struct curl_slist* next; };
|
||||||
|
|
||||||
|
static inline struct curl_slist* curl_slist_append(struct curl_slist* list, const char* s) {
|
||||||
|
struct curl_slist* node = (struct curl_slist*)malloc(sizeof(struct curl_slist));
|
||||||
|
if (!node) return list;
|
||||||
|
node->data = s ? strdup(s) : NULL;
|
||||||
|
node->next = NULL;
|
||||||
|
if (!list) return node;
|
||||||
|
struct curl_slist* p = list;
|
||||||
|
while (p->next) p = p->next;
|
||||||
|
p->next = node;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
static inline void curl_slist_free_all(struct curl_slist* list) {
|
||||||
|
while (list) { struct curl_slist* n = list->next; free(list->data); free(list); list = n; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CURL* curl_easy_init(void) { return (CURL*)malloc(1); }
|
||||||
|
static inline CURLcode curl_easy_setopt(CURL* h, int opt, ...) { (void)h; (void)opt; return CURLE_OK; }
|
||||||
|
static inline CURLcode curl_easy_perform(CURL* h) { (void)h; return 7 /* CURLE_COULDNT_CONNECT */; }
|
||||||
|
static inline void curl_easy_cleanup(CURL* h) { free(h); }
|
||||||
|
static inline const char* curl_easy_strerror(CURLcode c) {
|
||||||
|
(void)c; return "libcurl not built in (curl-less build)";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !HAVE_CURL */
|
||||||
|
|
||||||
|
#endif /* EL_PLATFORM_WIN_H */
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -117,15 +117,6 @@ el_val_t el_min(el_val_t a, el_val_t b);
|
|||||||
void el_retain(el_val_t v);
|
void el_retain(el_val_t v);
|
||||||
void el_release(el_val_t v);
|
void el_release(el_val_t v);
|
||||||
|
|
||||||
/* ── Arena scoping ────────────────────────────────────────────────────────────
|
|
||||||
* el_arena_push() activates the string arena (if not already active) and
|
|
||||||
* returns a mark; el_arena_pop(mark) frees all strings allocated since that
|
|
||||||
* mark. Used by codegen for per-function/statement scoping and by long-running
|
|
||||||
* EL loops (e.g. the soul daemon's awareness tick) to reclaim per-iteration
|
|
||||||
* allocations. */
|
|
||||||
el_val_t el_arena_push(void);
|
|
||||||
el_val_t el_arena_pop(el_val_t mark);
|
|
||||||
|
|
||||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
el_val_t el_list_new(el_val_t count, ...);
|
el_val_t el_list_new(el_val_t count, ...);
|
||||||
@@ -151,7 +142,6 @@ el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
|||||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||||
el_val_t http_delete(el_val_t url);
|
el_val_t http_delete(el_val_t url);
|
||||||
el_val_t http_delete_json(el_val_t url, el_val_t json_body);
|
|
||||||
void http_serve(el_val_t port, el_val_t handler);
|
void http_serve(el_val_t port, el_val_t handler);
|
||||||
void http_set_handler(el_val_t name);
|
void http_set_handler(el_val_t name);
|
||||||
|
|
||||||
@@ -177,11 +167,6 @@ void http_set_handler(el_val_t name);
|
|||||||
void http_serve_v2(el_val_t port, el_val_t handler);
|
void http_serve_v2(el_val_t port, el_val_t handler);
|
||||||
void http_set_handler_v2(el_val_t name);
|
void http_set_handler_v2(el_val_t name);
|
||||||
|
|
||||||
/* Non-blocking variant of http_serve: runs the accept loop in a background
|
|
||||||
* pthread and returns immediately so the caller can continue (used by the
|
|
||||||
* soul daemon to run awareness_run() after starting its HTTP API). */
|
|
||||||
void http_serve_async(el_val_t port, el_val_t handler);
|
|
||||||
|
|
||||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||||
@@ -591,7 +576,6 @@ el_val_t engram_list_layers(void);
|
|||||||
el_val_t engram_get_node(el_val_t id);
|
el_val_t engram_get_node(el_val_t id);
|
||||||
void engram_strengthen(el_val_t node_id);
|
void engram_strengthen(el_val_t node_id);
|
||||||
void engram_forget(el_val_t node_id);
|
void engram_forget(el_val_t node_id);
|
||||||
el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
|
||||||
el_val_t engram_node_count(void);
|
el_val_t engram_node_count(void);
|
||||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||||
@@ -610,32 +594,12 @@ el_val_t engram_load(el_val_t path);
|
|||||||
* can pass results straight through without round-tripping ElList/ElMap
|
* can pass results straight through without round-tripping ElList/ElMap
|
||||||
* through json_stringify. */
|
* through json_stringify. */
|
||||||
el_val_t engram_get_node_json(el_val_t id);
|
el_val_t engram_get_node_json(el_val_t id);
|
||||||
el_val_t engram_get_node_by_label(el_val_t label);
|
|
||||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||||
el_val_t engram_stats_json(void);
|
el_val_t engram_stats_json(void);
|
||||||
el_val_t engram_act_stats_json(void);
|
|
||||||
el_val_t engram_text_health_json(void);
|
|
||||||
el_val_t engram_cosine_sim(el_val_t id_a, el_val_t id_b);
|
|
||||||
/* Destructively pop up to `max` newly-formed Hebbian associations as a JSON
|
|
||||||
* array of {from_id,to_id,weight,hebb}. The learning process (soul daemon) is
|
|
||||||
* not the process that owns persistence (engram HTTP server); this is how a
|
|
||||||
* self-formed association crosses that boundary. (2026-08-07 self-review.) */
|
|
||||||
el_val_t engram_hebb_drain_json(el_val_t max);
|
|
||||||
/* Document frequency of a term across node labels — term-specificity signal
|
|
||||||
* for curiosity seed selection. (2026-08-03 self-review.) */
|
|
||||||
el_val_t engram_label_df(el_val_t term);
|
|
||||||
/* Best curiosity seed from one node: argmax over idf·position·casing across
|
|
||||||
* the candidate tokens of its label, falling back to its content when the
|
|
||||||
* label is a sentinel. Excludes pipe-delimited tabu terms during selection
|
|
||||||
* and gates candidates to the df band [min_df, max_df]. Returns "" when
|
|
||||||
* nothing qualifies. (2026-08-13 self-review.) */
|
|
||||||
el_val_t engram_salient_term(el_val_t node_id, el_val_t max_df,
|
|
||||||
el_val_t min_df, el_val_t tabu);
|
|
||||||
el_val_t engram_embed_backfill(el_val_t count);
|
|
||||||
el_val_t engram_list_layers_json(void);
|
el_val_t engram_list_layers_json(void);
|
||||||
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
||||||
* Ported from el-compiler/runtime on 2026-06-30 self-review. */
|
* Ported from el-compiler/runtime on 2026-06-30 self-review. */
|
||||||
@@ -794,6 +758,18 @@ el_val_t trace_span_start(el_val_t name);
|
|||||||
el_val_t trace_span_end(el_val_t span_handle);
|
el_val_t trace_span_end(el_val_t span_handle);
|
||||||
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||||
|
|
||||||
|
/* ── Runtime symbols required by the soul modules ──────────────────────────── */
|
||||||
|
/* All implemented in el_runtime.c but omitted from this release header; the soul dist modules
|
||||||
|
* reference them directly, so the public header must export them. Declarations only — mirrors the
|
||||||
|
* mainline el_runtime.h and is platform-independent (no behavioural change to the POSIX build). */
|
||||||
|
typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path, el_val_t body, el_val_t headers);
|
||||||
|
el_val_t el_arena_push(void);
|
||||||
|
el_val_t el_arena_pop(el_val_t mark);
|
||||||
|
void http_serve_async(el_val_t port, el_val_t handler);
|
||||||
|
el_val_t engram_get_node_by_label(el_val_t label);
|
||||||
|
el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user