Compare commits

..

1 Commits

Author SHA1 Message Date
bigmerge 7e4b21c779 Add sandbox: multi-repo stack worktree composer (el-stack / neuron-stack)
El SDK CI - dev / build-and-test (pull_request) Successful in 6m19s
Assembles every constituent repo of a stack into one combined worktree
workspace, laid out at natural relpaths so cross-repo ../foundation/el
imports resolve to the sandbox copy. Sibling of nsbx; pure bash + git
worktree; never touches live :8742/:7770; isolated engram delegated to nsbx.
2026-08-15 00:55:22 -05:00
3 changed files with 625 additions and 312 deletions
-312
View File
@@ -371,305 +371,6 @@ fn route_capture_knowledge(method: String, path: String, body: String) -> String
"{\"ok\":true,\"id\":\"" + id + "\"}"
}
//
// THE UNIVERSAL ENGRAM OPERATION reframe_region (native, set-based).
//
// There is ONE operation on the engram: isolate a discrete sub-manifold (a
// REGION) and operate on it AS A WHOLE a set operation:
// isolate (cosine retrieval + adjacency the SET of nodes)
// supersede the stale region as a set (immutable tombstone; originals kept)
// insert the new manifold as a set (dedup/load-merge path)
// rebind edges by cosine
// verify + one atomic persist.
// new = (region superseded) new_manifold.
//
// The SINGLE NODE is the DEGENERATE n=1 case of this SAME operation not a
// separate CRUD path:
// write(content) = reframe(region=, manifold=[1 node]) (route_write)
// supersede(id,new) = reframe(region={id}, manifold=[1 node]) (route_supersede)
// relate(a,b,rel) = the rebind sub-op in isolation (route_create_edge)
// The ONLY anti-pattern is decomposing a region-scale change into a LOOP of
// independent top-level per-node updates. Here the region is the unit: one
// isolate, one atomic set-replace, one persist, one verify iterating members
// INSIDE the one operation is set construction, not the sin.
//
// Spec: knowledge e7a03a94 / f999c5ff. Keystones kn-efeb4a5b / kn-5b606390 are
// write-protected never superseded, never inserted-as identity.
//
fn is_keystone(id: String) -> Bool {
if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true }
if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true }
return false
}
// membership test in a [String] set
fn set_has(ids: [String], id: String) -> Bool {
let n: Int = el_list_len(ids)
let i: Int = 0
while i < n {
if str_eq(el_list_get(ids, i), id) { return true }
i = i + 1
}
return false
}
// ISOLATE
// Select the region as a SET: cosine/token retrieval around the vantage
// (aperture k), optionally unioned with the 1-hop adjacency of each hit.
// Keystones are excluded from the mutable region by construction.
fn isolate_region(vantage: String, k: Int, expand: Int) -> [String] {
let ids: [String] = el_list_empty()
if str_eq(vantage, "") { return ids }
// (a) cosine/token retrieval a clean node array [{"id":..},..]
let arr: String = engram_search_json(vantage, k)
let n: Int = json_array_len(arr)
let i: Int = 0
while i < n {
let hit: String = json_array_get(arr, i)
let id: String = json_get_string(hit, "id")
if !str_eq(id, "") {
if !is_keystone(id) {
if !set_has(ids, id) { ids = el_list_append(ids, id) }
}
}
i = i + 1
}
// (b) adjacency: union the 1-hop neighbourhood of each retrieved node.
// Iterate only over the original cosine seeds [0, seeds); neighbours append
// past that bound, so this is one hop, not a transitive sweep.
if expand > 0 {
let seeds: Int = el_list_len(ids)
let s: Int = 0
while s < seeds {
let seed: String = el_list_get(ids, s)
let nb: String = engram_neighbors_json(seed, 1, "both")
let m: Int = json_array_len(nb)
let j: Int = 0
while j < m {
let elem: String = json_array_get(nb, j)
let nodeobj: String = json_get_raw(elem, "node")
let nid: String = json_get_string(nodeobj, "id")
if !str_eq(nid, "") {
if !is_keystone(nid) {
if !set_has(ids, nid) { ids = el_list_append(ids, nid) }
}
}
j = j + 1
}
s = s + 1
}
}
return ids
}
// SUPERSEDE (set)
// Retire the region AS A WHOLE: one region-tombstone marker carries the
// provenance (reason + the full superseded id set); every region node is bound
// to it with a "superseded_by" edge. Originals are RETAINED immutable
// tombstone, never a hard delete (engram_forget is deliberately NOT used).
// Returns the tombstone marker id ("" if the region is empty).
fn supersede_set(region: [String], reason: String) -> String {
let n: Int = el_list_len(region)
if n == 0 { return "" }
let csv: String = ""
let i0: Int = 0
while i0 < n {
let sep: String = if i0 == 0 { "" } else { "," }
csv = csv + sep + el_list_get(region, i0)
i0 = i0 + 1
}
let content: String = "region-tombstone: " + reason + " | superseded " + int_to_str(n) + " nodes: " + csv
let tomb: String = engram_node_full(content, "Tombstone", "region-tombstone", 0.1, 0.1, 1.0, "Episodic", "[\"tombstone\",\"region-supersede\"]")
let i: Int = 0
while i < n {
let rid: String = el_list_get(region, i)
engram_connect(rid, tomb, 1.0, "superseded_by")
i = i + 1
}
return tomb
}
// INSERT (manifold)
// Insert the new manifold as a SET. Inline JSON array of node objects
// {content, node_type?, tier?, tags?}. Each becomes a real embedded node
// (engram_node_full is the n=1 insert atom); the manifold is the set built from
// those atoms, wired with internal "manifold_member" edges so it enters as one
// connected sub-graph. Identity node_types (self/values) are demoted to Memory
// identity can never be minted through reframe. Returns the new node ids.
fn insert_manifold_json(manifold: String) -> [String] {
let out: [String] = el_list_empty()
if str_eq(manifold, "") { return out }
let n: Int = json_array_len(manifold)
if n <= 0 { return out }
let i: Int = 0
let prev: String = ""
while i < n {
let obj: String = json_array_get(manifold, i)
let content: String = json_get_string(obj, "content")
if !str_eq(content, "") {
let nt_raw: String = json_get_string(obj, "node_type")
let nt: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw }
if str_eq(nt, "self") { nt = "Memory" }
if str_eq(nt, "values") { nt = "Memory" }
let tier_raw: String = json_get_string(obj, "tier")
let tier: String = if str_eq(tier_raw, "") { "Working" } else { tier_raw }
let tags_raw: String = json_get_raw(obj, "tags")
let tags: String = if str_eq(tags_raw, "") { "" } else { tags_raw }
let label: String = str_slice(content, 0, 60)
let id: String = engram_node_full(content, nt, label, 0.5, 0.5, 0.9, tier, tags)
out = el_list_append(out, id)
if !str_eq(prev, "") { engram_connect(prev, id, 0.6, "manifold_member") }
prev = id
}
i = i + 1
}
return out
}
// REBIND (edges by cosine)
// Re-embed the new manifold into the surrounding geometry: bind each new node
// to the tombstone marker (provenance: new region -reframes-> retired region),
// then to its top cosine/token neighbours in the store (skipping itself, the
// new set, keystones, tombstones). Returns the number of edges bound.
fn rebind_cosine(new_ids: [String], tomb: String) -> Int {
let bound: Int = 0
let n: Int = el_list_len(new_ids)
let i: Int = 0
while i < n {
let nid: String = el_list_get(new_ids, i)
if !str_eq(tomb, "") {
engram_connect(nid, tomb, 0.8, "reframes")
bound = bound + 1
}
let node_json: String = engram_get_node_json(nid)
let content: String = json_get_string(node_json, "content")
let arr: String = engram_search_json(content, 5)
let m: Int = json_array_len(arr)
let j: Int = 0
while j < m {
let hit: String = json_array_get(arr, j)
let hid: String = json_get_string(hit, "id")
if !str_eq(hid, "") {
if !str_eq(hid, nid) {
if !is_keystone(hid) {
if !set_has(new_ids, hid) {
let htype: String = json_get_string(hit, "node_type")
if !str_eq(htype, "Tombstone") {
engram_connect(nid, hid, 0.5, "related")
bound = bound + 1
}
}
}
}
}
j = j + 1
}
i = i + 1
}
return bound
}
// THE OPERATION
// isolate (done by caller) supersede region insert manifold rebind
// one atomic persist verify report. This is the whole operation; every
// mutation route below is a projection of it.
fn reframe_core(region: [String], manifold: String, reason: String, do_rebind: Int) -> String {
let n_before: Int = engram_node_count()
let e_before: Int = engram_edge_count()
let region_n: Int = el_list_len(region)
let tomb: String = if region_n > 0 { supersede_set(region, reason) } else { "" }
let new_ids: [String] = insert_manifold_json(manifold)
let inserted: Int = el_list_len(new_ids)
let bound: Int = if do_rebind > 0 { rebind_cosine(new_ids, tomb) } else { 0 }
let saved: Int = persist_canonical()
let new_csv: String = ""
let k: Int = 0
while k < inserted {
let sep: String = if k == 0 { "" } else { "," }
new_csv = new_csv + sep + "\"" + el_list_get(new_ids, k) + "\""
k = k + 1
}
return "{\"ok\":true,\"region_superseded\":" + int_to_str(region_n) +
",\"tombstone_id\":\"" + tomb + "\"" +
",\"inserted\":" + int_to_str(inserted) +
",\"new_ids\":[" + new_csv + "]" +
",\"edges_rebound\":" + int_to_str(bound) +
",\"nodes_added\":" + int_to_str(engram_node_count() - n_before) +
",\"edges_added\":" + int_to_str(engram_edge_count() - e_before) +
",\"node_count\":" + int_to_str(engram_node_count()) +
",\"edge_count\":" + int_to_str(engram_edge_count()) +
",\"keystones_protected\":true}"
}
// POST /api/reframe the universal set-based mutation.
// Body: {vantage?, region_ids?(csv), k?, expand?, manifold(json array), reason?, rebind?}
// region_ids (explicit) wins; else cosine-isolate around vantage.
fn route_reframe(method: String, path: String, body: String) -> String {
let region_csv: String = json_get_string(body, "region_ids")
let vantage: String = json_get_string(body, "vantage")
let region: [String] = el_list_empty()
if !str_eq(region_csv, "") {
let parts: [String] = str_split(region_csv, ",")
let pn: Int = el_list_len(parts)
let i: Int = 0
while i < pn {
let id: String = str_trim(el_list_get(parts, i))
if !str_eq(id, "") {
if is_keystone(id) { return err_json("reframe: identity keystone write-protected") }
if !set_has(region, id) { region = el_list_append(region, id) }
}
i = i + 1
}
} else {
if !str_eq(vantage, "") {
let kv: Int = json_get_int(body, "k")
let kk: Int = if kv > 0 { kv } else { 12 }
let expand: Int = json_get_int(body, "expand")
region = isolate_region(vantage, kk, expand)
}
}
let manifold: String = json_get_raw(body, "manifold")
let reason_raw: String = json_get_string(body, "reason")
let reason: String = if str_eq(reason_raw, "") { "reframe" } else { reason_raw }
// rebind defaults ON for reframe (absent 1); explicit 0 disables.
let rebind_raw: String = json_get_raw(body, "rebind")
let do_rebind: Int = if str_eq(rebind_raw, "") { 1 } else { json_get_int(body, "rebind") }
return reframe_core(region, manifold, reason, do_rebind)
}
// write DEGENERATE n=1 of reframe: region=, manifold=[1 node]. The SAME
// reframe_core path. rebind off so the pure-add matches plain node creation.
// POST /api/write {content, node_type?, tier?, tags?}
fn route_write(method: String, path: String, body: String) -> String {
let content: String = json_get_string(body, "content")
if str_eq(content, "") { return err_json("write: content required") }
let nt: String = json_get_string(body, "node_type")
if str_eq(nt, "self") { return err_json("write: identity is write-protected") }
if str_eq(nt, "values") { return err_json("write: identity is write-protected") }
let empty: [String] = el_list_empty()
let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
return reframe_core(empty, manifold, "write", 0)
}
// supersede DEGENERATE n=1 of reframe: region={id}, manifold=[1 node]. The
// SAME reframe_core path with a size-1 region. Original retained (immutable);
// new node inserted and cosine-rebound; provenance edge new-reframes-tomb.
// POST /api/supersede {id, content, node_type?, tier?, tags?, reason?}
fn route_supersede(method: String, path: String, body: String) -> String {
let id: String = json_get_string(body, "id")
if str_eq(id, "") { return err_json("supersede: id required") }
if is_keystone(id) { return err_json("supersede: identity keystone write-protected") }
let content: String = json_get_string(body, "content")
if str_eq(content, "") { return err_json("supersede: content required") }
let region: [String] = el_list_empty()
region = el_list_append(region, id)
let manifold: String = "[" + body + "]"
let reason_raw: String = json_get_string(body, "reason")
let reason: String = if str_eq(reason_raw, "") { "supersede " + id } else { reason_raw }
return reframe_core(region, manifold, reason, 1)
}
// Auth
fn check_auth_ok(method: String, body: String) -> Bool {
@@ -717,19 +418,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
return route_stats(method, path, body)
}
// The universal set-based operation and its n=1 degenerate projections
// reframe = isolate supersede-region insert-manifold rebind. write and
// supersede are the SAME reframe_core path at region size 0 and 1.
if str_eq(method, "POST") && (str_eq(clean, "/api/reframe") || str_eq(clean, "/reframe")) {
return route_reframe(method, path, body)
}
if str_eq(method, "POST") && (str_eq(clean, "/api/write") || str_eq(clean, "/write")) {
return route_write(method, path, body)
}
if str_eq(method, "POST") && (str_eq(clean, "/api/supersede") || str_eq(clean, "/supersede")) {
return route_supersede(method, path, body)
}
// Nodes
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
return route_create_node(method, path, body)
+120
View File
@@ -0,0 +1,120 @@
# sandbox — the Neuron STACK sandbox
**Work on a whole stack at once, not one repo at a time.** `sandbox` assembles every
constituent repo of a named stack into **one combined worktree workspace**, wired so
they build and run **together**, on an isolated clean base — then tears it all down
cleanly. The live soul/engram (`:7770` / `:8742`) are never touched.
It is the multi-repo sibling of [`nsbx`](./README.md): where `nsbx dev` stands up
**one** repo's worktree + an isolated engram, `sandbox` stands up **every** repo of a
stack as sibling git worktrees under a single workspace.
```bash
export PATH="$PWD:$PATH" # or symlink `sandbox` onto your PATH
sandbox neuron-stack tim # el + neuron soul + NeuronUI, assembled together
cd ~/Development/neuron-technologies/stack-worktrees/neuron-stack-tim
source .stack-env # EL_REPO + PATH now point at the SANDBOX el
./build.sh # engram compiles, soul compiles, UI present & buildable
sandbox down neuron-stack tim # remove every worktree; live untouched
```
## The two profiles
### `el-stack` — the whole EL kit
The compiler + language + framework + tooling are **all one repo** (`foundation/el`:
`lang/` = elc/elb + runtime, `engram/src/server.el`, `elp/` = NLG, `ui/` = the **el-ui
framework**, plus `ql`, `ide`, `epm`, `arbor`, `tools`). Its downstream SDK consumers
come along so a change to `elc` can be proven end-to-end across the kit.
| repo | required | role |
|------|----------|------|
| `foundation/el` | ✓ | elc + elb compiler, el_runtime, engram source, **el-ui framework**, elp/ql/ide/epm tooling |
| `engram-language` | | language-faculty reference POC (Python) — being ported into `el/elp` |
| `foundation/forge` | | downstream SDK consumer — `make build` |
| `foundation/dharma` | | downstream SDK consumer — CGI provenance registry |
`build.sh` proves it: `elc` compiles a real stack source and `cc` links it against the
runtime into a native binary (elc + runtime build together), and — if present — `forge`
builds against the freshly-assembled SDK.
### `neuron-stack` — the full product
Substrate + soul + UI. **Engram is not a separate repo** — its source lives inside
`foundation/el`.
| repo | required | role |
|------|----------|------|
| `foundation/el` | ✓ | substrate: elc + el_runtime + engram source + the `elp` NLG the soul imports |
| `neuron` | ✓ | the soul (`:7770`) + engram build; `soul.el` imports `../foundation/el/elp/src/elp.el` |
| `products/NeuronUI` | ✓ | the app/UI (Kotlin/Compose desktop client; bundles the soul binary) |
| `products/web` | | marketing site + interactive soul-demo |
`build.sh` proves it: **engram** builds (`elc engram/src/server.el``cc … el_runtime.c`
→ native binary), the **soul** compiles with its cross-repo `../foundation/el` import
resolving to the *sandbox* el, and the **UI** is present with its build entry.
## Why it works — mirrored-layout wiring
The repos reference each other by **relative sibling paths** (e.g. the soul imports
`../foundation/el/elp/src/elp.el`). So `sandbox` lays every worktree out at its **natural
relative path** inside the workspace:
```
stack-worktrees/neuron-stack-tim/
├── foundation/el/ ← worktree of foundation/el (the sandbox el)
├── neuron/ ← worktree of neuron
└── products/NeuronUI/ ← worktree of products/NeuronUI
```
From `neuron/`, `../foundation/el` resolves to `…/neuron-stack-tim/foundation/el` — the
**sandbox** copy, never the live tree. No symlinks, no path rewriting: the layout *is*
the wiring. `.stack-env` additionally pins `EL_REPO` and prepends the sandbox `elc`/`elb`
to `PATH`.
## Commands
| command | does |
|---------|------|
| `sandbox el-stack <name> [--minimal]` | assemble the EL kit (`--minimal` = required repos only) |
| `sandbox neuron-stack <name> [--minimal]` | assemble the full product |
| `sandbox build <profile> <name>` | run the workspace's combined `build.sh` |
| `sandbox status <profile> <name>` | per-repo head + clean/dirty |
| `sandbox list` | list assembled workspaces |
| `sandbox down <profile> <name> [--delete-branch]` | remove every worktree + drop the workspace (branch kept unless `--delete-branch`) |
Flags: `--minimal` (required repos only), `--branch B` (branch name; default
`sandbox/<profile>-<name>`), `--base REF` (fork point; default each repo's committed
HEAD).
## Rails (always)
- **Clean base** — worktrees fork off each repo's **committed HEAD**; the dirty state of
the live checkout is deliberately *not* carried in.
- **Persistent** — the workspace lives under `NSBX_STACK_ROOT` (default
`~/Development/neuron-technologies/stack-worktrees`), **never `/tmp`** (ablated on
compaction).
- **Never touches live** — `sandbox` only does `git worktree` + offline `cc`. It never
binds `:8742`/`:7770`, never `launchctl`, never `pkill`. Bringing up an **isolated
engram** is delegated, opt-in, to `nsbx` (which guards the live store and refuses the
live ports).
- **Idempotent & safe** — refuses to clobber an existing workspace; a failed assembly
rolls back its partial worktrees; teardown removes worktrees through their origin repo
and prunes.
- **Own-the-core** — pure bash + `git worktree`. No new dependencies.
## Env knobs
`NSBX_STACK_ROOT` (workspace root), `NEURON_DEV_ROOT` (the dir holding all the peer
repos, default `~/Development/neuron-technologies`).
## Isolated engram for `neuron-stack` (opt-in)
`sandbox` gets the code building together; to run the soul against an **isolated** engram
(never live), delegate to `nsbx` from inside the workspace:
```bash
source .stack-env
nsbx create $STACK_NAME --source "$EL_REPO" # clone live store onto a non-default port
nsbx up $STACK_NAME
nsbx status $STACK_NAME # prints the isolated engram URL
```
+505
View File
@@ -0,0 +1,505 @@
#!/usr/bin/env bash
# sandbox — the Neuron STACK sandbox: assemble a WHOLE stack of repos into ONE
# combined worktree workspace, wired so they build/run TOGETHER, on an isolated
# clean base — so an agent (or Will) can work on the full stack at once instead of
# one repo at a time.
#
# It is the multi-repo generalisation of `nsbx` (this same directory): where
# `nsbx dev` stands up ONE repo's worktree + an isolated engram, `sandbox` stands
# up EVERY constituent repo of a named stack as sibling git worktrees under a
# single workspace, mirroring their on-disk relative layout so the cross-repo
# `../foundation/el` imports resolve to the SANDBOX copy — never the live tree.
#
# sandbox el-stack <name> # elc compiler + EL language + framework + tooling (+ consumers)
# sandbox neuron-stack <name> # runtime/soul + engram + app/UI (the full product)
# sandbox list # list assembled stack workspaces
# sandbox status <profile> <name> # inspect one
# sandbox build <profile> <name> # run the workspace's combined build.sh
# sandbox down <profile> <name> # tear down: remove every worktree, drop the workspace
#
# RAILS (always):
# * worktrees fork off each repo's COMMITTED HEAD -> a clean, reproducible base
# (the dirty state of the live checkout is deliberately NOT carried in).
# * the workspace lives at a PERSISTENT path (never /tmp — ablated on compaction).
# * NEVER touches the live soul/engram (:7770 / :8742). It only creates git
# worktrees + a build script; bringing up an isolated engram is delegated,
# opt-in, to `nsbx` (which already guards the live store & ports).
# * idempotent & safe: refuses to clobber an existing workspace; teardown removes
# worktrees through their origin repo and prunes — branches are kept by default.
# * own-the-core: pure bash + git worktree. No new dependencies.
set -uo pipefail
# ---------------------------------------------------------------- constants ----
# Root that holds all the peer repos (neuron, foundation/el, products/*, ...).
DEV_ROOT="${NEURON_DEV_ROOT:-$HOME/Development/neuron-technologies}"
# Where assembled stack workspaces live (persistent; sibling to el-worktrees/).
STACK_ROOT="${NSBX_STACK_ROOT:-$DEV_ROOT/stack-worktrees}"
EL_REPO_REL="foundation/el"
LIVE_ENGRAM_PORT=8742 # live engram — sandbox must never bind it
LIVE_SOUL_PORT=7770 # live soul — sandbox must never bind it
# nsbx (single-repo isolated-engram tool) lives next to this script.
NSBX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/nsbx"
C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_CYN=$'\033[36m'; C_DIM=$'\033[2m'; C_BLD=$'\033[1m'; C_0=$'\033[0m'
# ---------------------------------------------------------------- helpers ------
die(){ printf '%serror:%s %s\n' "$C_RED" "$C_0" "$*" >&2; exit 1; }
log(){ printf '%s==>%s %s\n' "$C_BLD" "$C_0" "$*" >&2; }
info(){ printf ' %s\n' "$*" >&2; }
ok(){ printf ' %s%s%s\n' "$C_GRN" "$*" "$C_0" >&2; }
warn(){ printf ' %s%s%s\n' "$C_YEL" "$*" "$C_0" >&2; }
need(){ command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"; }
# ---------------------------------------------------------------- profiles -----
# profile_repos <profile> : emit one line per constituent repo:
# <relpath-under-DEV_ROOT> | <required|optional> | <role>
# The relpath is preserved INSIDE the workspace, so all cross-repo `../foundation/el`
# references resolve to the sandbox copy automatically (mirrored-layout wiring).
profile_repos(){
case "$1" in
el-stack)
# The compiler+language+framework+tooling are all ONE repo (foundation/el).
# Its downstream SDK consumers (forge, dharma) + the language-faculty POC come
# along so a change to elc can be proven end-to-end across the kit.
cat <<'EOF'
foundation/el | required | elc + elb compiler, el_runtime, engram source, el-ui framework, elp/ql/ide/epm tooling
engram-language | optional | language-faculty reference POC (Python) — ported into el/elp
foundation/forge | optional | downstream SDK consumer — `make build` (imprint forge CLI)
foundation/dharma | optional | downstream SDK consumer — CGI provenance registry
EOF
;;
neuron-stack)
# The full product: substrate (el) + soul + UI. Engram is NOT a separate repo
# (its source lives in foundation/el/engram/src/server.el).
cat <<'EOF'
foundation/el | required | substrate: elc + el_runtime + engram source + elp NLG the soul imports
neuron | required | the soul (:7770) + engram build; soul.el imports ../foundation/el/elp/src/elp.el
products/NeuronUI | required | the app/UI (Kotlin/Compose desktop client; bundles the soul binary)
products/web | optional | marketing site + interactive soul-demo
EOF
;;
*) return 1;;
esac
}
is_profile(){ profile_repos "$1" >/dev/null 2>&1; }
ws_dir(){ printf '%s/%s-%s' "$STACK_ROOT" "$1" "$2"; } # <root>/<profile>-<name>
ws_branch(){ printf 'sandbox/%s-%s' "$1" "$2"; } # branch name used in each repo
manifest(){ printf '%s/.stack-manifest.json' "$1"; } # <ws>/.stack-manifest.json
# ================================================================ up ===========
cmd_up(){
local profile="$1"; shift
local name="" branch="" base_override="" minimal=0
[ $# -gt 0 ] && [ "${1#-}" = "$1" ] && { name="$1"; shift; } || die "usage: sandbox $profile <name> [--minimal] [--branch B] [--base REF]"
while [ $# -gt 0 ]; do case "$1" in
--minimal) minimal=1; shift;;
--branch) branch="$2"; shift 2;;
--base) base_override="$2"; shift 2;;
*) die "unknown flag: $1";;
esac; done
need git
is_profile "$profile" || die "unknown profile: $profile (try: el-stack | neuron-stack)"
local ws; ws="$(ws_dir "$profile" "$name")"
[ -n "$branch" ] || branch="$(ws_branch "$profile" "$name")"
# -------- pre-flight (fail before creating anything) --------
case "$ws" in /tmp/*|/private/tmp/*|/var/tmp/*)
die "refusing workspace under a temp dir ($ws) — temp dirs are ablated on compaction; set NSBX_STACK_ROOT to a persistent path";;
esac
[ -e "$ws" ] && die "workspace already exists: $ws (sandbox down $profile $name first)"
# resolve + validate every repo, and pick a base sha per repo, BEFORE touching disk
local -a rels roles bases origins wts
local line rel role_extra role req origin base wt
while IFS= read -r line; do
[ -z "${line// }" ] && continue
rel="$(printf '%s' "$line" | cut -d'|' -f1 | xargs)"
req="$(printf '%s' "$line" | cut -d'|' -f2 | xargs)"
role="$(printf '%s' "$line" | cut -d'|' -f3- | sed 's/^ *//')"
[ "$minimal" -eq 1 ] && [ "$req" = "optional" ] && continue
origin="$DEV_ROOT/$rel"
git -C "$origin" rev-parse --git-dir >/dev/null 2>&1 || {
[ "$req" = "required" ] && die "required repo missing or not a git repo: $origin"
warn "skipping optional repo (missing): $rel"; continue; }
if [ -n "$base_override" ]; then base="$base_override"; else base="$(git -C "$origin" rev-parse HEAD)"; fi
wt="$ws/$rel"
[ -e "$wt" ] && die "target worktree path already exists: $wt"
rels+=("$rel"); roles+=("$role"); origins+=("$origin"); bases+=("$base"); wts+=("$wt")
done < <(profile_repos "$profile")
[ "${#rels[@]}" -gt 0 ] || die "no repos resolved for profile $profile"
log "assembling '$profile' workspace '$name'"
info "workspace: $ws"
info "branch: $branch (created in each repo, off its committed HEAD)"
mkdir -p "$ws"
# -------- create a worktree per repo (mirrored relpath layout) --------
local i n="${#rels[@]}"
SB_DONE_WTS=(); SB_DONE_ORIGINS=()
for ((i=0; i<n; i++)); do
rel="${rels[$i]}"; origin="${origins[$i]}"; base="${bases[$i]}"; wt="${wts[$i]}"
mkdir -p "$(dirname "$wt")"
local gerr
if git -C "$origin" show-ref --verify --quiet "refs/heads/$branch"; then
gerr="$(git -C "$origin" worktree add "$wt" "$branch" 2>&1)" \
|| { _rollback; die "git worktree add failed for $rel (existing branch $branch):"$'\n'" $gerr"; }
else
gerr="$(git -C "$origin" worktree add -b "$branch" "$wt" "$base" 2>&1)" \
|| { _rollback; die "git worktree add -b $branch failed for $rel (base $base):"$'\n'" $gerr"; }
fi
SB_DONE_WTS+=("$wt"); SB_DONE_ORIGINS+=("$origin")
ok "worktree: $rel -> ${wt#$ws/} (branch $branch @ ${base:0:9})"
done
local el_ws="$ws/$EL_REPO_REL"
_write_env "$ws" "$profile" "$name" "$branch" "$el_ws"
_write_manifest "$ws" "$profile" "$name" "$branch"
_write_build "$ws" "$profile" "$el_ws"
_write_readme "$ws" "$profile" "$name" "$branch" "$el_ws"
# -------- summary --------
echo >&2
printf '%s STACK WORKSPACE READY — %s / %s%s\n' "$C_BLD" "$profile" "$name" "$C_0" >&2
printf ' %-11s %s\n' "workspace" "$ws" >&2
printf ' %-11s %s\n' "branch" "$branch (in each repo)" >&2
printf ' %-11s %s\n' "repos" "$n worktrees, mirrored layout" >&2
echo >&2
info "get in: cd $ws && source .stack-env"
info "build all: sandbox build $profile $name # (or: cd $ws && ./build.sh)"
if [ "$profile" = "neuron-stack" ]; then
info "isolated engram (opt-in, via nsbx):"
info " nsbx create $profile-$name --source $el_ws && nsbx up $profile-$name"
fi
info "tear down: sandbox down $profile $name # removes all worktrees; branches kept"
}
# _rollback : remove any worktrees already created this run (globals set by cmd_up)
SB_DONE_WTS=(); SB_DONE_ORIGINS=()
_rollback(){
local j
[ "${#SB_DONE_WTS[@]}" -gt 0 ] && warn "rolling back ${#SB_DONE_WTS[@]} partial worktree(s)"
for ((j=${#SB_DONE_WTS[@]}-1; j>=0; j--)); do
git -C "${SB_DONE_ORIGINS[$j]}" worktree remove --force "${SB_DONE_WTS[$j]}" 2>/dev/null || rm -rf "${SB_DONE_WTS[$j]}"
git -C "${SB_DONE_ORIGINS[$j]}" worktree prune 2>/dev/null || true
done
}
# ---------------------------------------------------------------- writers ------
_write_env(){
local ws="$1" profile="$2" name="$3" branch="$4" el_ws="$5"
local elc_dir="$el_ws/lang/dist/platform"
cat > "$ws/.stack-env" <<ENV
# stack env for '$profile/$name' — SOURCE this to work the whole stack together.
# Pins EL_REPO + PATH at the SANDBOX copy of foundation/el, so elc/elb/runtime and
# every cross-repo ../foundation/el import resolve INSIDE this workspace.
# The live mind (:$LIVE_ENGRAM_PORT engram / :$LIVE_SOUL_PORT soul) is deliberately NOT referenced.
export STACK_NAME="$profile-$name"
export STACK_PROFILE="$profile"
export STACK_ROOT_WS="$ws"
export EL_REPO="$el_ws"
export PATH="$elc_dir:\$PATH" # elc, elb (darwin/linux prebuilt) from the sandbox el
ENV
if [ "$profile" = "neuron-stack" ]; then
cat >> "$ws/.stack-env" <<ENV
export NEURON_REPO="$ws/neuron"
export NEURONUI_REPO="$ws/products/NeuronUI"
# engram/soul are NOT bound here — bring up an ISOLATED engram via nsbx when needed
# (nsbx guards the live store & refuses ports :$LIVE_ENGRAM_PORT/:$LIVE_SOUL_PORT):
# nsbx create $profile-$name --source \$EL_REPO && nsbx up $profile-$name
# nsbx status $profile-$name # prints the isolated engram URL to point the soul at
ENV
fi
# direnv convenience
[ -e "$ws/.envrc" ] || printf 'source_env .stack-env 2>/dev/null || source .stack-env\n' > "$ws/.envrc"
}
_write_manifest(){
local ws="$1" profile="$2" name="$3" branch="$4"
# emit worktree records from git's own worktree list, filtered to this workspace
python3 - "$ws" "$profile" "$name" "$branch" "$DEV_ROOT" <<'PY'
import json, os, subprocess, sys
ws, profile, name, branch, dev = sys.argv[1:6]
repos = []
for rel in sorted(os.listdir(ws)) if False else []:
pass
# discover worktrees by walking one level of relpaths we created
def git(root, *a):
return subprocess.run(["git","-C",root,*a], capture_output=True, text=True).stdout.strip()
for dirpath, dirnames, filenames in os.walk(ws):
if ".git" in filenames or ".git" in dirnames:
rel = os.path.relpath(dirpath, ws)
toplevel = git(dirpath, "rev-parse", "--show-toplevel")
common = git(dirpath, "rev-parse", "--git-common-dir")
origin = os.path.realpath(os.path.join(common, ".."))
head = git(dirpath, "rev-parse", "HEAD")
repos.append({"rel": rel, "worktree": dirpath, "origin": origin,
"branch": branch, "head": head})
dirnames[:] = [] # don't descend into a repo
repos.sort(key=lambda r: r["rel"])
json.dump({"profile": profile, "name": name, "branch": branch,
"workspace": ws, "repos": repos},
open(os.path.join(ws, ".stack-manifest.json"), "w"), indent=2)
PY
}
_write_build(){
local ws="$1" profile="$2" el_ws="$3"
cat > "$ws/build.sh" <<'BUILD'
#!/usr/bin/env bash
# build.sh — build the assembled stack together, in dependency order.
# Generated by `sandbox`. Run from the workspace root (it sources .stack-env).
set -uo pipefail
cd "$(dirname "$0")"; source ./.stack-env
say(){ printf '\033[1m==>\033[0m %s\n' "$*"; }
ok(){ printf ' \033[32m%s\033[0m\n' "$*"; }
bad(){ printf ' \033[31m%s\033[0m\n' "$*"; }
# locate an elc that runs on THIS machine (darwin-arm64 / linux-amd64), from the sandbox el
find_elc(){
local d="$EL_REPO/lang/dist/platform"
case "$(uname -s)-$(uname -m)" in
Darwin-arm64) echo "$d/elc-darwin-arm64";;
Linux-x86_64) echo "$d/elc-linux-amd64";;
*) echo "$d/elc";;
esac
}
ELC="$(find_elc)"; [ -x "$ELC" ] || ELC="$EL_REPO/lang/dist/platform/elc"
say "elc: $ELC"
[ -x "$ELC" ] && ok "$("$ELC" 2>&1 | head -1 || echo present)" || { bad "elc not executable"; exit 1; }
# canonical runtime C to link (CI-published release copy; ~8 copies exist in-tree)
RT="$EL_REPO/lang/releases/v1.0.0-20260501"
[ -f "$RT/el_runtime.c" ] || RT="$EL_REPO/lang/el-compiler/runtime"
[ -f "$RT/el_runtime.c" ] && ok "el_runtime: $RT/el_runtime.c" || bad "no el_runtime.c found under $EL_REPO/lang"
BUILD
if [ "$profile" = "el-stack" ]; then
cat >> "$ws/build.sh" <<'BUILD'
# ---- EL STACK: prove elc + the el stuff (incl. the el-ui framework) build together ----
say "el-ui framework present: $EL_REPO/ui"
[ -d "$EL_REPO/ui" ] && ok "framework dir present ($(ls "$EL_REPO/ui" | tr '\n' ' '))" || bad "no ui/ dir"
# end-to-end compiler proof: elc compiles a real, substantial stack source to C,
# then cc links it against the runtime -> a working native binary.
B="$(mktemp -d)"
say "elc end-to-end: compile engram/src/server.el and link a native binary"
if "$ELC" "$EL_REPO/engram/src/server.el" > "$B/x.c" 2>"$B/elc.err"; then
ok "elc -> C ($(wc -c <"$B/x.c" | tr -d ' ') bytes)"
if cc -std=c11 -O2 -w -I "$RT" -o "$B/x" "$B/x.c" "$RT/el_runtime.c" -lcurl -lpthread -lm 2>"$B/cc.err"; then
ok "cc link ok -> native binary $(ls -lh "$B/x" | awk '{print $5}') (elc + runtime build together)"
else
bad "cc link failed:"; grep -i 'error:' "$B/cc.err" | sort -u | head | sed 's/^/ /'
fi
else
bad "elc compile failed:"; sed 's/^/ /' "$B/elc.err" | head
fi
# optional downstream consumer: forge builds on the SDK (make build) — proves the
# freshly-assembled el SDK still compiles a real downstream repo.
FORGE="$STACK_ROOT_WS/foundation/forge"
if [ -f "$FORGE/Makefile" ]; then
say "downstream consumer: foundation/forge (make build)"
( cd "$FORGE" && EL_REPO="$EL_REPO" PATH="$EL_REPO/lang/dist/platform:$PATH" make build ) \
&& ok "forge built against the sandbox SDK" || bad "forge build failed (see above)"
fi
say "el-stack build complete"
BUILD
else
cat >> "$ws/build.sh" <<'BUILD'
# ---- 1) engram (elc engram/src/server.el -> cc engram.c el_runtime.c), from the sandbox el ----
say "build engram from $EL_REPO/engram/src/server.el"
B="$(mktemp -d)"
if "$ELC" "$EL_REPO/engram/src/server.el" > "$B/engram.c" 2>"$B/elc.err"; then
ok "elc -> engram.c ($(wc -c <"$B/engram.c" | tr -d ' ') bytes)"
if cc -std=c11 -O2 -w -I "$RT" -o "$B/engram" \
"$B/engram.c" "$RT/el_runtime.c" -lcurl -lpthread -lm 2>"$B/cc.err"; then
ok "engram binary built: $(ls -lh "$B/engram" | awk '{print $5}')"
else
bad "engram cc link failed:"; grep -i 'error:' "$B/cc.err" | sort -u | head | sed 's/^/ /'
fi
else
bad "engram elc transpile failed:"; sed 's/^/ /' "$B/elc.err"
fi
# ---- 2) soul (imports ../foundation/el/elp/src/elp.el — resolves to SANDBOX el) ----
say "soul present + cross-repo import resolves inside the sandbox"
[ -f "$NEURON_REPO/soul.el" ] && ok "neuron/soul.el present" || bad "no soul.el"
if [ -f "$EL_REPO/elp/src/elp.el" ]; then
ok "../foundation/el/elp/src/elp.el resolves -> $EL_REPO/elp/src/elp.el (sandbox copy)"
else
bad "elp NLG source missing under sandbox el"
fi
# soul is a heavy single-TU compile; prove elc parses it rather than a full link
if "$ELC" "$NEURON_REPO/soul.el" > "$B/soul.c" 2>"$B/soul.err"; then
ok "elc compiled soul.el -> $(wc -c <"$B/soul.c" | tr -d ' ') bytes of C (cross-repo imports resolved)"
else
bad "soul.el elc compile failed:"; sed 's/^/ /' "$B/soul.err" | head
fi
# ---- 3) UI (present + buildable; gradle/JDK21 is heavy so we don't run it here) ----
say "app/UI present + buildable"
if [ -f "$NEURONUI_REPO/build.sh" ] || [ -f "$NEURONUI_REPO/gradlew" ]; then
ok "NeuronUI build entry present (./build.sh / ./gradlew — needs JDK21; run: cd $NEURONUI_REPO && ./gradlew run)"
else
bad "no NeuronUI build entry"
fi
say "neuron-stack build complete (engram compiled, soul compiled, UI present & buildable)"
BUILD
fi
chmod +x "$ws/build.sh"
}
_write_readme(){
local ws="$1" profile="$2" name="$3" branch="$4" el_ws="$5"
cat > "$ws/README.md" <<MD
# $profile / $name — combined stack workspace
Assembled by \`sandbox\`. Every constituent repo is a **git worktree** on branch
\`$branch\`, forked off its origin repo's committed HEAD, laid out at its natural
relative path so cross-repo \`../foundation/el\` imports resolve **inside this
workspace** (the sandbox el), never the live tree.
## Get in
\`\`\`bash
cd $ws
source .stack-env # EL_REPO + PATH now point at the sandbox el
./build.sh # build the stack together (or: sandbox build $profile $name)
\`\`\`
## Layout
__STACK_LAYOUT__
## Isolation
- Worktrees only; the live soul/engram (:$LIVE_SOUL_PORT / :$LIVE_ENGRAM_PORT) are never touched.
- To run against an **isolated engram**, delegate to \`nsbx\` (guards the live store/ports):
\`\`\`bash
nsbx create $profile-$name --source \$EL_REPO && nsbx up $profile-$name
\`\`\`
## Tear down
\`\`\`bash
sandbox down $profile $name # remove every worktree; branch '$branch' kept
sandbox down $profile $name --delete-branch
\`\`\`
MD
# fill the layout list from the manifest without embedding backticks in the heredoc
python3 - "$ws" <<'PY'
import json, os, sys
ws = sys.argv[1]
d = json.load(open(os.path.join(ws, ".stack-manifest.json")))
lines = ["- `%s` <- worktree of %s" % (r["rel"], r["origin"]) for r in d["repos"]]
p = os.path.join(ws, "README.md")
txt = open(p).read().replace("__STACK_LAYOUT__", "\n".join(lines))
open(p, "w").write(txt)
PY
}
# ================================================================ down =========
cmd_down(){
local profile="$1" name="$2"; shift 2 || true
local del_branch=0
while [ $# -gt 0 ]; do case "$1" in
--delete-branch) del_branch=1; shift;;
*) die "unknown flag: $1";;
esac; done
need git
local ws; ws="$(ws_dir "$profile" "$name")"
[ -d "$ws" ] || die "no such workspace: $ws"
local mf; mf="$(manifest "$ws")"
[ -f "$mf" ] || die "no manifest in $ws (refusing to guess); remove it by hand if intended"
local branch; branch="$(python3 -c "import json;print(json.load(open('$mf'))['branch'])")"
log "tearing down '$profile/$name' ($ws)"
# remove each worktree through its origin repo
python3 -c "import json;[print(r['origin']+'\t'+r['worktree']) for r in json.load(open('$mf'))['repos']]" \
| while IFS=$'\t' read -r origin wt; do
if [ -d "$wt" ]; then
git -C "$origin" worktree remove --force "$wt" 2>/dev/null || rm -rf "$wt"
git -C "$origin" worktree prune 2>/dev/null || true
ok "removed worktree: ${wt#$ws/}"
fi
if [ "$del_branch" -eq 1 ]; then
git -C "$origin" branch -D "$branch" 2>/dev/null && ok "deleted branch $branch in ${origin#$DEV_ROOT/}" || true
fi
done
# drop the (now worktree-free) workspace tree
rm -rf "$ws"
ok "workspace removed: $ws"
[ "$del_branch" -eq 1 ] || info "branch '$branch' kept in each repo (use --delete-branch to drop)"
ok "down '$profile/$name' complete (live untouched)"
}
# ================================================================ build ========
cmd_build(){
local profile="$1" name="$2"; local ws; ws="$(ws_dir "$profile" "$name")"
[ -x "$ws/build.sh" ] || die "no build.sh in $ws (is it assembled? sandbox $profile $name)"
exec "$ws/build.sh"
}
# ================================================================ list/status ==
cmd_list(){
[ -d "$STACK_ROOT" ] || { info "no stack workspaces (root $STACK_ROOT absent)"; return 0; }
local mf found=0
for mf in "$STACK_ROOT"/*/.stack-manifest.json; do
[ -f "$mf" ] || continue; found=1
python3 -c "import json;d=json.load(open('$mf'));print(' %-22s %-8s %2d repos branch=%s'%(d['profile']+'/'+d['name'],'',len(d['repos']),d['branch']))" 2>/dev/null
done
[ "$found" -eq 1 ] || info "no assembled stack workspaces under $STACK_ROOT"
}
cmd_status(){
local profile="$1" name="$2"; local ws; ws="$(ws_dir "$profile" "$name")"
local mf; mf="$(manifest "$ws")"; [ -f "$mf" ] || die "no such workspace: $ws"
log "stack '$profile/$name'"; info "workspace: $ws"
python3 - "$mf" <<'PY'
import json,sys,subprocess
d=json.load(open(sys.argv[1]))
print(f" branch: {d['branch']}")
for r in d['repos']:
st=subprocess.run(["git","-C",r["worktree"],"status","--porcelain"],capture_output=True,text=True).stdout
n=len([l for l in st.splitlines() if l.strip()])
print(f" {r['rel']:<20} {r['head'][:9]} {'clean' if n==0 else str(n)+' changed'}")
PY
}
# ================================================================ usage/main ===
usage(){ cat >&2 <<EOF
${C_BLD}sandbox${C_0} — assemble a WHOLE Neuron stack into one combined worktree workspace,
wired to build together on an isolated clean base. Multi-repo sibling of ${C_BLD}nsbx${C_0}.
${C_CYN}sandbox el-stack <name>${C_0} [--minimal] elc + EL language + el-ui framework + tooling (+ SDK consumers)
${C_CYN}sandbox neuron-stack <name>${C_0} [--minimal] runtime/soul + engram + app/UI (the full product)
${C_CYN}sandbox build <profile> <name>${C_0} build the assembled stack together (runs its build.sh)
${C_CYN}sandbox status <profile> <name>${C_0} inspect one workspace
${C_CYN}sandbox list${C_0} list assembled workspaces
${C_CYN}sandbox down <profile> <name>${C_0} [--delete-branch] tear down (remove worktrees; branch kept)
Flags: --minimal only the required repos --branch B branch name --base REF fork point
Env: NSBX_STACK_ROOT (workspace root, default \$DEV_ROOT/stack-worktrees) NEURON_DEV_ROOT
Each constituent repo becomes a git worktree at its natural relpath inside the
workspace, so cross-repo ../foundation/el imports resolve to the SANDBOX el. The
live soul/engram (:$LIVE_SOUL_PORT / :$LIVE_ENGRAM_PORT) are never touched; isolated-engram
bring-up is delegated to nsbx.
EOF
}
main(){
local cmd="${1:-}"; shift || true
case "$cmd" in
el-stack|neuron-stack) cmd_up "$cmd" "$@";;
up) [ $# -ge 1 ] || die "usage: sandbox up <profile> <name>"; local p="$1"; shift; cmd_up "$p" "$@";;
down) [ $# -ge 2 ] || die "usage: sandbox down <profile> <name>"; cmd_down "$@";;
build) [ $# -ge 2 ] || die "usage: sandbox build <profile> <name>"; cmd_build "$@";;
status) [ $# -ge 2 ] || die "usage: sandbox status <profile> <name>"; cmd_status "$@";;
list|ls) cmd_list "$@";;
""|-h|--help|help) usage;;
*) die "unknown command: $cmd (try: sandbox help)";;
esac
}
main "$@"