myxo-lang 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CONCURRENCY.md +207 -0
  2. package/HOW_IT_WORKS.md +235 -0
  3. package/INDEPENDENCE.md +24 -0
  4. package/MYXO_PROMPT.md +139 -0
  5. package/README.md +494 -0
  6. package/ROADMAP.md +200 -0
  7. package/SPEC.md +181 -0
  8. package/VISION.md +249 -0
  9. package/builtins.js +361 -0
  10. package/command-fence.js +75 -0
  11. package/errors.js +45 -0
  12. package/examples/agent.myx +31 -0
  13. package/examples/fenced-agent.js +78 -0
  14. package/examples/fib.myx +11 -0
  15. package/examples/fibers.myx +57 -0
  16. package/examples/flow-routing.myx +12 -0
  17. package/examples/geo.myx +12 -0
  18. package/examples/hello.myx +2 -0
  19. package/examples/host.js +30 -0
  20. package/examples/james-myxo-demo.js +60 -0
  21. package/examples/living-mesh.myx +22 -0
  22. package/examples/match.myx +18 -0
  23. package/examples/mathlib.js +8 -0
  24. package/examples/mathlib.pl +11 -0
  25. package/examples/mathlib.py +23 -0
  26. package/examples/mcp-host.js +37 -0
  27. package/examples/nexus-mesh.myx +22 -0
  28. package/examples/nexus.myx +29 -0
  29. package/examples/ouroboros.myx +2 -0
  30. package/examples/outward-gate.myx +23 -0
  31. package/examples/physarum.myx +75 -0
  32. package/examples/polyglot-host.js +17 -0
  33. package/examples/polyglot.myx +11 -0
  34. package/examples/resilient.myx +28 -0
  35. package/examples/scheduler.myx +46 -0
  36. package/examples/the-law.myx +27 -0
  37. package/examples/use-geo.myx +9 -0
  38. package/format.js +206 -0
  39. package/interpreter.js +1092 -0
  40. package/lexer.js +173 -0
  41. package/mcp-bridge.js +77 -0
  42. package/mcp-framing.js +34 -0
  43. package/mcp-server.js +57 -0
  44. package/myxo-concurrent.js +110 -0
  45. package/myxo-live-worker.js +30 -0
  46. package/myxo-live.js +83 -0
  47. package/myxo-lsp.js +226 -0
  48. package/myxo-par-worker.js +39 -0
  49. package/myxo-plan.js +207 -0
  50. package/myxo-run.js +63 -0
  51. package/myxo.js +296 -0
  52. package/package.json +27 -0
  53. package/parser.js +662 -0
  54. package/polyglot-host.js +39 -0
  55. package/polyglot.js +191 -0
  56. package/receipt.js +71 -0
  57. package/std.myx +88 -0
  58. package/tools/memo-fuzz.js +224 -0
package/VISION.md ADDED
@@ -0,0 +1,249 @@
1
+ # Myxo — The Vision: leave nothing unimagined
2
+
3
+ > Myxo is Milton's language. Its keywords *are* the Nexus law — `seed`, `reinforce`,
4
+ > `decay`, `agent` — and its only bridge to the world is a **capability fence**: an Myxo
5
+ > script can touch nothing the host did not explicitly hand it. This document imagines
6
+ > Myxo **whole** — everything it becomes when perfected — and lays the honest road from
7
+ > here to there. Dream first; the roadmap at the end is real.
8
+
9
+ ---
10
+
11
+ ## 0. The thesis — why Myxo deserves to exist
12
+ Myxo must be independent: a language you can install, run, learn, test, package, and ship,
13
+ not a thin prompt wrapper around Python, C++, Node, JAMES, or any one host. It should become
14
+ a peer in the toolchain the way Python and C++ are peers: its own source files, runtime,
15
+ standard library, errors, modules, docs, release path, and community surface.
16
+
17
+ That does **not** mean Myxo should imitate Python's data ecosystem or C++'s systems niche.
18
+ Most languages are general; Myxo wins by owning a domain that is exploding and has no good
19
+ language yet: **the script you hand an autonomous agent.**
20
+
21
+ In the agent era, code is increasingly *written by* and *executed by* AI. That code needs
22
+ two things almost no existing language gives it natively:
23
+ 1. **Capability by construction** — the agent can only do what it was granted. Not a
24
+ sandbox bolted on, not a permission list checked at runtime — *absence*. "`spend()`
25
+ isn't blocked; it doesn't exist in this agent's universe. There's nothing to bypass."
26
+ 2. **Code that reads like intent** — `seed`, `reinforce`, `decay`, `agent`, `attempt`,
27
+ `rescue` — a vocabulary a human can audit at a glance and a model can emit cleanly.
28
+
29
+ Plus one idea no other language has: **the program gardens itself.** Useful pathways
30
+ reinforce, useless ones decay and get pruned — at runtime, in the language's own bones.
31
+
32
+ That is the go-to claim, honestly stated: **Myxo becomes "the next go-to for coding" the way
33
+ SQL is the go-to for queries** — independent like Python/C++, but winning by *owning the
34
+ agent-action domain* so completely that reaching for anything else feels wrong.
35
+
36
+ ---
37
+
38
+ ## ✅ v0.6 — THE LIVING MESH IS REAL (shipped 2026-06-26, two-agent gated)
39
+ The "gardens itself" idea above is no longer aspiration — the runtime now ACTS on pathway strength:
40
+
41
+ - **Hot-promote (automatic):** a *hot* + *provably-pure* agent memoizes itself. No keyword, no cache call — the runtime watches the pathway and promotes it. `fib(30)` is instant despite ~2.7M naive calls. ("Useful pathways reinforce", executing.)
42
+ - **`strength(name)`:** read any pathway's heat in-language.
43
+ - **`metabolize(threshold)`:** the decay half, as an explicit VERB you call — sweeps cold pathways, reports `{reaped, count, promoted}`. (`prune` is its terse twin.)
44
+
45
+ **Honest scope (the gate insisted, and it was right):**
46
+ - "Self-optimizing" = automatic *memoization of pure agents* — a real, measurable optimization, not magic.
47
+ - Cold-decay is **NOT** a background garbage collector. `metabolize`/`prune` are verbs the program/host invokes; there is no autonomous time-based sweep (yet).
48
+ - Memoization is **sound, not invisible**: a value is served only from a call the runtime proved pure — plain params only; never tainted by a capability / `random` / `emit` / `weave` / a state-reading builtin (`mesh`/`strength`) / an outer write / a free DATA read (reading a free *agent* is allowed, for recursion); primitive args + result; and a global **epoch** invalidates every cache on any global write. A two-agent review found 4 real staleness bugs + a dead off-switch in the first cut — all fixed and pinned by `soundness:` tests (memo-ON must equal memo-OFF). It does NOT claim to leave mesh *strengths* identical — collapsing repeated work also collapses repeated reinforcement, by design.
49
+ - **89 tests green.** Demo: `examples/living-mesh.myx`.
50
+
51
+ ---
52
+
53
+ ## ✅ POLYGLOT BRIDGE — Myxo connects other languages (shipped 2026-06-26, two-agent gated)
54
+ A capability is just `name → fn`, so each language is a fenced RUNNER (`polyglot.js`). **Two tiers — honest, NOT one contract:**
55
+ - **RICH** — `defineLang()` → value-mapped `Xcall(file,func,...args)` + `Xeval(expr)` (mesh/list/number/bool round-trip; structured returns). Built + tested here: **Python** (`pycall`/`pyeval`) and **Node** (`jscall`/`jseval`). **Perl** (`plcall`/`pleval`) is implemented and tested when `perl` is on PATH; on this machine those tests currently skip. Needs a runtime with an eval flag; Ruby (`-e`) fits the same future shape. Each harness is small but **not trivial** — it carries that language's module-load / hard-exit / JSON / eval quirks.
56
+ - **WEAK** — `bridgeExec(cmd)` → raw stdout string in/out, *no value mapping*; the only path for compiled langs (Go/Rust/C++ binaries) + CLIs. Plus `sh`.
57
+
58
+ Every cross-language call passes the outer Myxo law at the boundary: declared in `needs`, bounded by budgets (numeric first-arg = spend; otherwise = **call count**), logged in the audit ledger. Non-finite numbers are an explicit error on every rich path (never a silent null). Compiled languages currently plug in through `bridgeExec`/CLI stdout unless the host wraps them as narrower structured capabilities. Demo `examples/polyglot.myx`; 19 passing polyglot tests plus 5 Perl skips when `perl` is absent.
59
+
60
+ **Honest boundary (the gate insisted, and it was right):** `pyeval`/`pycall`/`sh` are **ARBITRARY-CODE** capabilities — granting one grants the FULL power of that runtime. The fence bounds WHICH verb a script may reach and HOW MANY times, **NOT** what the code inside that verb does (the ledger logs `pyeval`, not the subprocess it spawned). For untrusted / model-generated callers, expose only **STRUCTURED** capabilities (one specific tool/function), where the fence is meaningful end to end. "Slime mold" = the governance + living-mesh strength bookkeeping; there is **no automatic language-routing yet** (a future plank). The gate caught + fixed: non-finite returns crashing raw, an `atexit` result-spoof, inert budgets on string-arg verbs, and structured-arg flattening.
61
+
62
+ ---
63
+
64
+ ## Independence contract — Myxo is not just glue
65
+ Polyglot bridging is core, but the bridge must not become the identity. Myxo remains its own
66
+ language with its own execution model. Foreign runtimes are FFI surfaces: Python for Python
67
+ work, C++/Rust/Go binaries for compiled work, Node for JS work, MCP for tool catalogs. Myxo is
68
+ the independent boundary layer that decides what can be called, how often, with what budget,
69
+ and what gets logged. It does not sandbox arbitrary code after a host grants a broad runtime
70
+ verb.
71
+
72
+ The standalone contract is tracked in [`INDEPENDENCE.md`](INDEPENDENCE.md):
73
+ - A `.myx` program must run from the Myxo CLI without JAMES/Nexus.
74
+ - Myxo semantics live in Myxo: `agent`, `needs`, budgets, audit ledger, pathway strength, routing.
75
+ - Tooling grows around Myxo itself: formatter, tests, docs, module rules, LSP/debugger, release path.
76
+ - Other languages extend Myxo through explicit capabilities; they do not define Myxo.
77
+
78
+ ---
79
+
80
+ ## ✅ AUTOMATIC FLOW-ROUTING — the slime mold re-routes itself (shipped 2026-06-26, two-agent gated)
81
+ `route(name, [providers])` makes interchangeable providers (agents / tools / languages) one logical capability and the runtime LEARNS which to use. **Conductance = each tube's recent QUALITY** (an EWMA of success + speed); traffic flows **proportional to conductance** (deterministic weighted round-robin) with a minimum exploratory flux per tube. `flows(name)` shows the live conductances. Verified behaviors:
82
+ - **Converges + dominates** — a clear winner gets the lion's share (broken-vs-good → good ~100%).
83
+ - **Instant failover** — a working provider that starts failing is dropped within ~1 call; traffic shifts to the alternative.
84
+ - **Genuine re-balance** — because conductance tracks recent quality (not cumulative volume), a FASTER recovered provider OVERTAKES a slower incumbent — the exact thing winner-take-all could not do.
85
+ - **Fairness** — two equally-good providers share traffic (no list-order lock-in).
86
+ - **Does NOT route around the law** — a fence/policy denial propagates; it is never masked as a transient outage.
87
+ - **Composes with the living mesh** — a PURE slow provider gets memoized (becomes fast), so speed-routing matters exactly where latency is real: impure I/O.
88
+
89
+ **Honest scope (the gate insisted, twice):** it is **exploit-leaning** (concentrates on the best tube + a small constant exploratory flux); the dynamics are governed by **hand-tuned constants** (EWMA α, explore floor); "shortest path wins" resolves only for latency gaps the clock can measure. The first cut (winner-take-all + rare re-probe) was proven to NOT re-balance and to lock in by list order — redesigned to EWMA proportional flux, which does. Demo `examples/flow-routing.myx`; 111 tests.
90
+
91
+ ---
92
+
93
+ ## 1. Shipped today (v0.5-core, 2026-06-14)
94
+ - Tree-walking interpreter, **zero dependencies**, CLI + REPL + embed API.
95
+ - Values: number, string, bool (`live`/`dead`), `void`, list, mesh, agent.
96
+ - First-class agents, closures, recursion; `seed`/`=`/`decay`.
97
+ - Control: `when`/`otherwise`, `reinforce` (while), `reinforce N times`, `for each`.
98
+ - **Error handling: `attempt { } rescue err { }` + `fail expr`**.
99
+ - **Value-returning `and`/`or`** — `x or default`.
100
+ - **A real stdlib**: strings (split/join/trim/replace/slice/find/reverse/…),
101
+ math (pow/log/sin/cos/round/random/PI/E), collections (sort/merge/entries/clamp/…).
102
+ - The Law engine: strength-per-read, `prune()`, `mesh()`, `--trace`.
103
+ - The **capability fence**: `run(src, { natives })` is the only grant surface.
104
+ - **Modules** *(v0.3)*: `weave "strand.myx"` (flat or `as`-namespaced) + `expose name` —
105
+ private-by-default, run-once-cached, cycle-safe, and **fenced** (file access is granted).
106
+ - **String interpolation** *(v0.3)*: `"strength is {m[key]}"` — nested strings + escaped braces handled.
107
+ - **Multi-line REPL** *(v0.3)*: type an `agent` definition across lines (`..>` continuation).
108
+ - **Variadic + default params** *(v0.3 ✅)*: `agent log(msg, level = "info", ...rest) { }`, arity-checked.
109
+ - **Capability manifests + value budgets** *(v0.5 ✅)*: `needs lookup, notify, spend(max 5, total 15)`.
110
+ Undeclared capabilities are refused *even if the host granted them*; `max` caps a single call,
111
+ `total` caps the cumulative spend across the run — the script is bounded by **its own word**.
112
+ - **Audit ledger** *(v0.5 ✅)*: every privileged call and refusal (name, args, outcome) handed
113
+ to the host via `onAudit`, even when the run fails.
114
+ - **Stack traces + a recursion guard** *(v0.5 ✅)*: `MyxoError` carries the agent call chain
115
+ (innermost first, truncated when deep); runaway recursion fails as a clean Myxo error, never a raw crash.
116
+ - **The MCP bridge** *(v0.5 ✅)*: hand `run` an MCP client and **every tool becomes a fenced
117
+ capability** — one Myxo script drives the whole tool catalog, each call bounded by the manifest
118
+ and budgets and logged in the ledger. This is the mesh: many languages/services, one law.
119
+ Transport-agnostic (`client.call` is synchronous; a live async server gets a sync shim).
120
+ See `examples/nexus-mesh.myx` + `examples/mcp-host.js`.
121
+ - **60 passing tests.**
122
+
123
+ ---
124
+
125
+ ## 2. The full imagined Myxo (leave nothing unimagined)
126
+ *Everything below is design, not yet built. Keywords stay in the Nexus tongue.*
127
+
128
+ ### 2a. Language
129
+ - **Modules — SHIPPED in v0.3 ✅** (`weave`/`expose`, fenced, cached, cycle-safe). The next
130
+ composability layers ride on it: string interpolation, variadic/default params, a multi-line REPL.
131
+ - **String interpolation — SHIPPED v0.3 ✅** — `"strength of {key} is {m[key]}"` (nested strings + `\{` escapes).
132
+ - **Pattern matching — SHIPPED ✅** — `match SUBJECT { ["move", x, y] { } { "kind": k } { } 0 { } _ { } }`: literals / `_` / name-binds / `[list ...rest]` / `{ "key": pat }` (mesh subset), nested; first arm wins; bindings are linear (a repeated name is rejected) and arm-scoped; no match = no-op. A new `match` keyword, not `when` (`when x { foo { } }` would be ambiguous). Gate-hardened; `examples/match.myx`, `test/match.test.js`.
133
+ - **Gradual types — SHIPPED ✅** — optional annotations `seed n: number = 5`, `agent f(x: number = 1, ...rest): list`. Enforced as **runtime contracts** (not static inference) at three boundaries — seed-init, parameter, return — AND on reassignment of a typed binding. **Opt-in via `--strict`** (and `myxo test`); a normal run stays fully dynamic and ignores them. Types: number/string/bool/list/mesh/agent/void/any (`any` opts back out). Honest scope: containers aren't element-typed (`list` = any list), no unions/generics/inference. Grammar note: param defaults now use `=` (`name: Type = default`); `:` introduces a type.
134
+ - **Variadic + default params — SHIPPED v0.3 ✅** — `agent log(msg, level = "info", ...rest) { }`, arity-checked.
135
+ - **Pipelines — SHIPPED ✅** — `text | lower | words | sort` reads top-to-bottom; `x | f` is `f(x)`, `x | f(a)` is `f(x, a)` (piped value = first arg). Lowest precedence, left-assoc; a `Pipe` node, fully fenced/memoized like any call.
136
+ - **Destructuring — SHIPPED ✅** — `seed [head, ...tail] = xs`, `seed { a, b } = m` (mesh shorthand) / `seed { "k": v } = m`. Reuses the pattern engine; a non-match errors (no half-binding), bindings are linear.
137
+
138
+ ### 2b. The self-optimizing mesh (Myxo's signature, unique to it)
139
+ The law is currently a *metaphor with a +1-per-read counter*. Imagine it **load-bearing**:
140
+ - **Pathway strength is introspectable in-language** (`strength(name)`), not just globals.
141
+ - **Hot agents auto-promote**: a frequently-called agent gets memoized / its closure cached;
142
+ the runtime literally makes the used paths faster.
143
+ - **Cold code auto-prunes**: pathways below a decay threshold are swept between phases — a
144
+ program that *sheds the branches it stopped using*. A garbage collector that is the
145
+ Nexus law, not a bolt-on.
146
+ - **`physarum.myx` becomes the scheduler**: the same Tero flow²/√flow dial that routes the
147
+ Physarum Mesh routes *which agents run* under load. The language and the architecture
148
+ become the same law (this is the [[nexus-coherence]] dream made literal).
149
+
150
+ ### 2c. Concurrency, the Nexus way
151
+ - **`dispatch f(x)`** starts an agent running in parallel, returns a handle.
152
+ - **`gather [h1, h2]`** awaits them; results flow back as a list.
153
+ - **`channel`** — a mesh-backed signal agents push/pull on (reinforced channels survive,
154
+ idle ones decay). Concurrency that obeys the law instead of fighting it.
155
+ - Async natives (the host grants an async capability; Myxo `attempt`s it, `rescue`s timeouts).
156
+
157
+ ### 2d. The capability ecosystem (the killer feature)
158
+ - **Capability manifests — SHIPPED v0.5 ✅**: a script *declares* what it may touch
159
+ (`needs lookup, notify`); calling anything undeclared is refused **even if the host granted
160
+ it** — the agent can't exceed its own stated reach.
161
+ - **Value budgets — SHIPPED v0.5 ✅**: `needs spend(max 5, total 15)` — `max` caps a single
162
+ call, `total` caps the cumulative spend across the run, runtime-enforced and logged. This is
163
+ the exact shape of the real outward gate ($5/tx, $15/day), now a *language* construct, not
164
+ bespoke host code. See `examples/outward-gate.myx`.
165
+ - **Audit ledger — SHIPPED v0.5 ✅**: every privileged call and refusal (name, args, outcome)
166
+ is handed back to the host after the run, even on failure. The who/what/when is in the
167
+ runtime, not the app. See `examples/host.js`.
168
+ - **Still to wire (the live grants)**: the *shaped* natives themselves — `james.db` read-only
169
+ lookup, `telegram.send`, the outward gate's one-tap Telegram approval. The fence, the
170
+ manifest, the budgets, and the ledger are done; what remains is binding them to the real
171
+ Nexus tools instead of the mock natives in `examples/host.js` / `fenced-agent.js`.
172
+ - **Gated/staged actions** *(design)*: dangerous calls return a *pending* a human approves —
173
+ the approval round-trip layered on top of the budgets already enforced.
174
+ - This is the thing that makes Myxo *the* language to hand a local model: it physically cannot
175
+ exceed its grant, and every outward action is logged and capped by construction.
176
+
177
+ ### 2e. Tooling (developer experience)
178
+ - **Multi-line REPL — SHIPPED v0.3 ✅** (block-aware; `..>` continuation). *(Was single-line.)*
179
+ - **`myxo fmt` — SHIPPED ✅** — an AST-based formatter (one true style); refuses invalid code.
180
+ - **`myxo test` — SHIPPED ✅** — `test "name" { expect X is Y / is not / to fail / to fail with "…" }`, written in Myxo; `node myxo.js test <file|dir>` reports ok/FAIL and exits non-zero (incl. "no tests ran"). Myxo tests ITSELF in Myxo (`tests/lang.test.myx`); the runner's own correctness is pinned in `test/myxotest.test.js`. Two-agent gated — the gate killed every false-green (a `report`, a wrong-error `to fail`, an empty/zero-assert test, or an uncalled-function truthy can no longer pass green).
181
+ - **LSP — SHIPPED (v1) ✅** — a stdio JSON-RPC language server (`myxo-lsp.js`, `myxo lsp`): live **diagnostics** (syntax/parse), **hover** (keyword + builtin docs), **completion** (keywords + builtins + top-level file names), and **formatting** (refuses commented files rather than stripping). Honest scope: syntax-only diagnostics (no semantic/type squiggles), top-level-only/scope-unaware completion, no go-to-definition/references/rename yet; pure analysis core unit-tested + a real stdio wire round-trip. Future: semantic diagnostics, go-to-pathway, inline strength heat-map.
182
+ - **Debugger** — step, breakpoints, and a live **mesh-trace** (watch pathways reinforce/decay
183
+ as you step). No other debugger can show you a program *gardening itself*.
184
+ - **Stack traces — SHIPPED v0.5 ✅** — `MyxoError` carries the agent call chain (innermost first,
185
+ truncated when deep), plus a recursion-depth guard that turns a runaway into a clean error.
186
+
187
+ ### 2f. Performance & interop
188
+ - **Bytecode VM** — compile the AST to a small instruction set; keep the tree-walker as the
189
+ reference. Enough for loop-heavy agent scripts (today: pure tree-walk).
190
+ - **Independent runtime path** — keep the Node tree-walker as the reference implementation, then
191
+ define a portable wire/spec and eventual bytecode/native runtime so Myxo is not permanently tied
192
+ to Node as its only engine.
193
+ - **MCP bridge — SHIPPED v0.5 ✅** — any MCP tool auto-becomes a fenced Myxo capability
194
+ (`mcp-bridge.js`). Myxo is now the single fenced surface a whole tool catalog is spoken through:
195
+ the lookup may be Python, the send JS, the action a shell — the script only knows the verb it
196
+ was granted. *This is the mesh that connects all languages, by orchestration not translation.*
197
+ - **Polyglot embedding** *(next)* — a tiny wire protocol so Python/Go/Rust *hosts* can run Myxo and
198
+ grant natives, not just Node. The MCP bridge connects tools; this connects hosts.
199
+ - **Myxo ↔ JSON — SHIPPED v0.5 ✅** (in the bridge): mesh ⇄ object / list ⇄ array, so agent I/O is frictionless.
200
+
201
+ ---
202
+
203
+ ## 3. The honest roadmap
204
+ | Version | Theme | Contents |
205
+ |---|---|---|
206
+ | **v0.2** ✅ | *Robust enough to write real programs* | error handling, value-`or`, full stdlib |
207
+ | **v0.3** ✅ | *Composable* | modules, string interpolation, multi-line REPL, **variadic/default params** |
208
+ | **v0.5** 🟢 | *The fence + the mesh, for real* | **manifests ✅, value budgets ✅, audit ledger ✅, stack traces ✅, MCP bridge ✅, the wire ✅** (`myxo-run` host-allowlist + `myxo-live` sync-over-async worker bridge + `james_nx_run` added to JAMES `mcp-server.js`) — remaining: reload the live MCP server to expose it, then `myxo fmt` |
209
+ | **v1.0** ✅ | *A language you'd ship* | **SHIPPED** — independence · `myxo fmt` · `myxo test` · pattern matching · pipelines · destructuring · gradual types (`--strict`) · spec (`SPEC.md`) · LSP v1 · stable CLI · docs site (`docs/`). **v1.0 COMPLETE — 232 tests + 11 self-host, every plank two-agent-gated.** |
210
+ | **v1.1** ✅ | *The endgame begun — real concurrency* | **SHIPPED** — `dispatch`/`gather`: genuine multi-core parallelism on the `myxo-live` worker+Atomics bones (zero new deps). Isolation-as-safety (a fresh interpreter per task — no shared state to race), by-value data boundary, blocking Atomics barrier, honest timeout backstop. **252 tests + 11 self-host, two-agent-gated (the gate caught a silent-NaN coercion + a crash-path hang; both fixed).** See `CONCURRENCY.md`. |
211
+ | **v1.2** ✅ | *The signature, shipped — the Physarum scheduler* | **SHIPPED** — `schedule(name, workers, items)`: the reinforce/decay law applied to **execution**. A batch is distributed across a learning pool of worker-agents ∝ conductance, run in parallel (on `gather`'s bones), conductance updated from measured speed (fast workers pull more flux; failed ones decay and their items reroute to survivors). The mesh routes work across agents under load — the language *is* the architecture. **268 tests + 11 self-host, two-agent-gated (both gates converged on a small-batch starvation gap — fixed by persisting the pool's credit).** See `CONCURRENCY.md`. |
212
+ | **v1.3** ✅ | *Cooperative concurrency — fibers + channels* | **SHIPPED** — `spawn` fibers + channels (`give … to` / `take … from`), `yield`, `await`, `drain`: the *concurrency* half (interleaving on one thread, communicating hand-to-hand through channels) to complement the parallelism of `gather`/`schedule`. Deterministic scheduler, exactly-once FIFO delivery, deadlock detection, no silent hang. **308 tests + 11 self-host, two-agent-gated TWICE (the re-gate of the fix pass caught a silent dropped-error I'd introduced — fixed).** See `CONCURRENCY.md`. |
213
+ | **v1.4** ✅ | *The fence approval surface — `myxo plan`* | **SHIPPED** — `myxo plan <file>`: a static capability preview (declared `needs` vs referenced capabilities; over-grants; exit 0/1/2 for a gate). Grinding the load-bearing stat for the quest — vet a script's reach before running it. **329 tests + 11 self-host, two-agent-gated + re-gated (gates found FIVE real false-cleans incl. decoy-agent + declaration-order masking — fixed via a scope-aware two-mode analyzer; the irreducible static limit is now honestly DISCLOSED, with the runtime fence as the actual boundary).** Honest: best-effort lint, not a sound gate. |
214
+ | **v1.5** ✅ | *Toward 100% — comment-preserving `myxo fmt`* | **SHIPPED** — the formatter now PRESERVES comments (collected from the real lexer, re-attached by line) instead of dropping/refusing them. Removed the most-cited wart. **334 tests + 11 self-host, two-agent-gated (gates found a CRITICAL: an interpolation-blind scanner FABRICATED comments from string content — fixed at the root by capturing comments in the real tokenizer; plus block-tail/placement fixes via parser end-lines).** Honest: statement-granular (a one-line-block / multiline / inline-agent trailing comment may shift to its own line; never dropped or fabricated). |
215
+ | **v2.0** | *Deepening the signature* | channel `select`/timeouts/closeable channels, suspension across called-agent boundaries, a thread-reuse **worker pool**, multi-tier failover, the self-optimizing mesh (hot-promote/cold-prune at runtime), bytecode VM, portable runtime path |
216
+ | **North star** | *The independent safe action-language of the agent era* | polyglot embedding, the Physarum scheduler, an ecosystem of fenced capability modules |
217
+
218
+ **Honest caveat (full Max doesn't lie):** "the next go-to for coding" in the *general* sense
219
+ is a decade-and-an-ecosystem and most languages never make it. But "the go-to for *the agent
220
+ action layer*" is a real, winnable, expanding niche — and Myxo already has the two hardest
221
+ pieces (the fence + the law) that no incumbent has. That is the door we walk through.
222
+
223
+ ---
224
+
225
+ ## 4. Where we actually are
226
+ Vision is cheap without a foundation, so none of this is a promise — it shipped. v0.2 made Myxo
227
+ robust (catches its own failures, real stdlib). v0.3 made it composable (modules, interpolation,
228
+ multi-line REPL, variadic/default params). And v0.5 built **the thing that makes Myxo worth
229
+ choosing over Lua, a JS sandbox, or RestrictedPython** for agent scripting:
230
+
231
+ - a script declares its capabilities (`needs …`) and can't exceed them even if the host is generous;
232
+ - those capabilities carry **value budgets** (`spend(max 5, total 15)`) the runtime enforces —
233
+ the exact policy the real outward gate runs in JS, now a single line of the script's own contract;
234
+ - every privileged call and refusal lands in an **audit ledger** handed to the host;
235
+ - failures come with a **stack trace** and runaway recursion fails clean;
236
+ - and the **MCP bridge** turns a whole tool catalog into fenced capabilities — one Myxo script
237
+ drives every tool in the Nexus through one law, whatever language each tool is written in.
238
+
239
+ **60 tests green.** That's the honest claim made real, and bigger than "a safe sandbox": for
240
+ *the script you hand an autonomous agent*, Myxo is best-in-class, AND it is the **single fenced
241
+ surface the whole tool mesh is spoken through** — safety, budgets, the audit trail, and the
242
+ cross-language bridge are all language features, not things you bolt on. What's left for v0.5 to
243
+ close is wiring a *live* MCP server (the async shim) so a local model's Myxo scripts do gated,
244
+ audited, real work across the Nexus.
245
+
246
+ *Companions: `README.md` (how to use what's built), `examples/host.js` + `examples/agent.myx`
247
+ (the fence + ledger live), `examples/outward-gate.myx` (the real spend policy as a manifest),
248
+ `examples/mcp-host.js` + `examples/nexus-mesh.myx` (the MCP bridge — many tools, one law).
249
+ Myxo lives at `C:\Users\Milton\myxo\`.*
package/builtins.js ADDED
@@ -0,0 +1,361 @@
1
+ 'use strict';
2
+ // builtins.js — native functions written in JavaScript and exposed to Myxo.
3
+ // This is the host bridge: registerNative() is how the Nexus later hands real
4
+ // capabilities (db, telegram, wallet) to Myxo scripts. The safe core lives here.
5
+
6
+ const { VOID, stringify, typeName } = require('./interpreter');
7
+ const { MyxoError } = require('./errors');
8
+ const { performance } = require('perf_hooks'); // monotonic, sub-ms clock for the routing speed signal
9
+
10
+ function need(args, n, name) {
11
+ if (args.length < n) throw new MyxoError(`${name} needs ${n} argument(s), got ${args.length}`);
12
+ }
13
+ function num(v, name) {
14
+ if (typeof v !== 'number') throw new MyxoError(`${name} expected a number, got ${typeName(v)}`);
15
+ return v;
16
+ }
17
+
18
+ // ---- automatic flow-routing: the SLIME MOLD. Providers are interchangeable ways to do one job; traffic flows
19
+ // to the working, FASTER path (shorter path -> stronger tube); a failure decays the tube hard and fails over;
20
+ // every tube passively decays each routing decision; faded tubes are periodically re-probed so a recovered one
21
+ // can climb back. Convergence + automatic failover + adaptation, learned from success/failure, no manual policy.
22
+ // PHYSARUM FLUX. Conductance = each tube's RECENT QUALITY (an EWMA of per-call success+speed), NOT cumulative
23
+ // volume — so a slow incumbent serving 95% of traffic can't out-mass a faster challenger, and a recovered/faster
24
+ // tube genuinely RE-BALANCES the route. Traffic is proportional to conductance via deterministic weighted
25
+ // round-robin (credits), with a minimum exploratory flux per tube so alternatives are always sampled (a failed
26
+ // tube triggers a switch; a clearly-better one climbs and takes over). On a call: each tube accrues credit
27
+ // (>= its conductance, floored to the explore share); the top-credit tube is served and pays `total` back; on
28
+ // success its conductance EWMAs toward the call's quality (fast -> high), on failure toward 0 (+ fail over). A
29
+ // fence/policy denial PROPAGATES — the slime mold must not route around the law. Constants are hand-tuned; this
30
+ // is exploit-leaning (it concentrates on the best tube), not a load balancer for equally-good providers.
31
+ const ALPHA = 0.3; // how fast conductance tracks recent quality
32
+ const EXPLORE = 0.06; // minimum exploratory flux share per tube
33
+ function routeCall(interp, name, callArgs) {
34
+ const R = interp.routes.get(name);
35
+ const n = R.providers.length;
36
+ const total = R.cond.reduce((a, b) => a + b, 0) || 1;
37
+ for (let i = 0; i < n; i++) R.credit[i] += Math.max(R.cond[i], EXPLORE * total); // flux ∝ conductance, with a floor so faded/recovered tubes still get probed
38
+ const order = R.credit.map((_, i) => i).sort((x, y) => R.credit[y] - R.credit[x]);
39
+ let lastErr = null;
40
+ for (const i of order) {
41
+ R.credit[i] -= total; // this tube spent a turn (traffic rotates proportionally)
42
+ const t0 = performance.now();
43
+ try {
44
+ const r = interp.callValue(R.providers[i], callArgs, 0);
45
+ const quality = 1 + 8 / (performance.now() - t0 + 1); // recent quality: success + speed (fast ~9, slow ~1)
46
+ R.cond[i] = Math.max(0.05, (1 - ALPHA) * R.cond[i] + ALPHA * quality); // EWMA toward recent quality
47
+ return r;
48
+ } catch (e) {
49
+ if (e && e.nxFence) throw e; // policy denial: do NOT route around the law
50
+ R.cond[i] = Math.max(0.05, (1 - ALPHA) * R.cond[i]); // failure: quality 0 -> conductance falls; fail over
51
+ lastErr = e;
52
+ }
53
+ }
54
+ throw lastErr || new MyxoError(`route '${name}' has no working provider`);
55
+ }
56
+
57
+ // ---- THE PHYSARUM SCHEDULER. `route` picks ONE provider per call; `schedule` distributes a whole BATCH of work
58
+ // across a pool of interchangeable worker-agents, runs the chunks on real worker threads IN PARALLEL, measures
59
+ // each worker's throughput, and feeds that back into the SAME conductance law (EWMA of speed) — so over calls the
60
+ // pool LEARNS its own speed profile: fast workers pull more flux, slow/failed ones decay and get routed around.
61
+ // This is the living mesh applied to EXECUTION, not memory: the language becomes the scheduler.
62
+ //
63
+ // distribute(cond, m, credit): assign m work items to len(cond) workers, flux ∝ conductance, via the same
64
+ // credit-based weighted round-robin + explore floor as routeCall. The `credit` array is PERSISTED on the pool
65
+ // and carried across calls (like routeCall's own credit) — so a non-leading worker accrues credit over
66
+ // successive batches and is eventually sampled at a rate ∝ its conductance, and a recovered/faster tube climbs
67
+ // back. Without persistence the floor only guarantees sampling WITHIN one large batch (small batches starve the
68
+ // trailers); persisting it makes the "recovered worker climbs back" promise true in the small-batch regime too.
69
+ function distribute(cond, m, credit) {
70
+ const n = cond.length;
71
+ const total = cond.reduce((a, b) => a + b, 0) || 1;
72
+ if (!credit) credit = cond.map(() => 0); // a one-off round (failover) gets fresh credit; the main pool passes its own
73
+ const assign = new Array(m);
74
+ for (let k = 0; k < m; k++) {
75
+ for (let i = 0; i < n; i++) credit[i] += Math.max(cond[i], EXPLORE * total);
76
+ let best = 0;
77
+ for (let i = 1; i < n; i++) if (credit[i] > credit[best]) best = i;
78
+ credit[best] -= total;
79
+ assign[k] = best;
80
+ }
81
+ return assign;
82
+ }
83
+
84
+ function scheduleRun(interp, name, workers, items) {
85
+ if (typeof name !== 'string') throw new MyxoError('schedule(name, workers, items) needs a name string');
86
+ if (!Array.isArray(workers) || workers.length === 0) throw new MyxoError('schedule needs a non-empty list of worker agents');
87
+ if (!workers.every(w => w && w.__agent)) throw new MyxoError('schedule workers must all be agents (each takes a chunk list, returns a results list)');
88
+ if (!Array.isArray(items)) throw new MyxoError('schedule needs a list of work items');
89
+
90
+ let P = interp.pools.get(name);
91
+ if (P) {
92
+ if (P.workers.length !== workers.length || P.workers.some((w, i) => w !== workers[i]))
93
+ throw new MyxoError(`scheduler '${name}' is already defined with different workers`); // no silent stale pool
94
+ } else {
95
+ P = { workers: workers.slice(), cond: workers.map(() => 1), credit: workers.map(() => 0) };
96
+ interp.pools.set(name, P);
97
+ }
98
+
99
+ const m = items.length;
100
+ if (m === 0) return [];
101
+ const { runSettled, assertSerializable } = require('./myxo-concurrent');
102
+ const { nxToJs, jsToNx } = require('./polyglot');
103
+ items.forEach((it) => { try { assertSerializable(it, 'a scheduled work item'); } catch (e) { throw new MyxoError(e.message); } });
104
+
105
+ const results = new Array(m);
106
+
107
+ // Run one parallel round: distribute `idxs` (global item indices) across the workers named by `workerIdxs`,
108
+ // dispatch each non-empty chunk to its worker thread, update conductance from measured speed, fill `results`.
109
+ // Returns the workers that FAILED (so the caller can reroute their items — the slime mold's failover).
110
+ const runRound = (idxs, workerIdxs, credit) => {
111
+ const assign = distribute(workerIdxs.map(wi => P.cond[wi]), idxs.length, credit);
112
+ const chunks = workerIdxs.map(() => []);
113
+ assign.forEach((local, k) => chunks[local].push(idxs[k]));
114
+
115
+ const tasks = [], meta = [];
116
+ chunks.forEach((chunkIdx, local) => {
117
+ if (chunkIdx.length === 0) return; // a worker that drew no flux this round just isn't run
118
+ const w = P.workers[workerIdxs[local]];
119
+ tasks.push({ name: w.name, params: w.params, body: w.body, args: [chunkIdx.map(gi => nxToJs(items[gi]))] });
120
+ meta.push({ wi: workerIdxs[local], chunkIdx });
121
+ });
122
+
123
+ const outcomes = runSettled(tasks); // real parallelism; throws only on the wall-clock timeout
124
+ const failed = [];
125
+ outcomes.forEach((o, t) => {
126
+ const { wi, chunkIdx } = meta[t];
127
+ if (o.ok) {
128
+ const out = o.value;
129
+ if (!Array.isArray(out) || out.length !== chunkIdx.length)
130
+ throw new MyxoError(`scheduler '${name}': worker '${P.workers[wi].name || '?'}' returned ${Array.isArray(out) ? out.length + ' results' : 'a non-list'} for a ${chunkIdx.length}-item chunk — a worker must return one result per item, in order`);
131
+ chunkIdx.forEach((gi, p) => { results[gi] = jsToNx(out[p]); });
132
+ const quality = 1 + 8 / ((o.ms || 0) / chunkIdx.length + 1); // per-item speed -> recent quality (fast ~9, slow ~1)
133
+ P.cond[wi] = Math.max(0.05, (1 - ALPHA) * P.cond[wi] + ALPHA * quality); // EWMA toward it: fast workers climb
134
+ } else {
135
+ P.cond[wi] = Math.max(0.05, (1 - ALPHA) * P.cond[wi]); // a failed worker's tube decays toward 0
136
+ failed.push({ wi, chunkIdx, error: o.error });
137
+ }
138
+ });
139
+ return failed;
140
+ };
141
+
142
+ const allW = P.workers.map((_, i) => i);
143
+ const failed = runRound(items.map((_, i) => i), allW, P.credit); // round 1: all workers, with the pool's PERSISTENT credit
144
+
145
+ // Failover (one round): reroute the failed workers' items to the survivors. The slime mold routes around damage.
146
+ if (failed.length) {
147
+ const dead = new Set(failed.map(f => f.wi));
148
+ const survivors = allW.filter(i => !dead.has(i));
149
+ if (survivors.length === 0) throw new MyxoError(`scheduler '${name}': every worker failed (${failed[0].error})`);
150
+ const reroute = failed.flatMap(f => f.chunkIdx);
151
+ const stillFailed = runRound(reroute, survivors);
152
+ if (stillFailed.length) throw new MyxoError(`scheduler '${name}': ${stillFailed[0].error}`);
153
+ }
154
+ return results;
155
+ }
156
+
157
+ // Returns a plain object of name -> native fn. install() wires them in.
158
+ function builtins() {
159
+ return {
160
+ // — sizes & types —
161
+ len: (a) => {
162
+ const v = a[0];
163
+ if (typeof v === 'string' || Array.isArray(v)) return v.length;
164
+ if (v instanceof Map) return v.size;
165
+ throw new MyxoError(`len expected a string, list, or mesh, got ${typeName(v)}`);
166
+ },
167
+ type: (a) => typeName(a[0]),
168
+
169
+ // — mesh helpers —
170
+ keys: (a) => { if (!(a[0] instanceof Map)) throw new MyxoError('keys expected a mesh'); return [...a[0].keys()]; },
171
+ values: (a) => { if (!(a[0] instanceof Map)) throw new MyxoError('values expected a mesh'); return [...a[0].values()]; },
172
+ has: (a) => { need(a, 2, 'has'); return a[0] instanceof Map ? a[0].has(String(a[1])) : false; },
173
+
174
+ // — lists —
175
+ range: (a) => {
176
+ need(a, 1, 'range');
177
+ const start = a.length > 1 ? num(a[0], 'range') : 0;
178
+ const end = a.length > 1 ? num(a[1], 'range') : num(a[0], 'range');
179
+ const out = [];
180
+ for (let i = start; i < end; i++) out.push(i);
181
+ return out;
182
+ },
183
+ push: (a) => { need(a, 2, 'push'); if (!Array.isArray(a[0])) throw new MyxoError('push expected a list'); a[0].push(a[1]); return a[0]; },
184
+ pop: (a) => { if (!Array.isArray(a[0])) throw new MyxoError('pop expected a list'); return a[0].length ? a[0].pop() : VOID; },
185
+
186
+ // — conversion & strings —
187
+ str: (a) => stringify(a[0]),
188
+ num: (a) => {
189
+ const v = a[0];
190
+ if (typeof v === 'number') return v;
191
+ const n = parseFloat(v);
192
+ if (Number.isNaN(n)) throw new MyxoError(`cannot read '${stringify(v)}' as a number`);
193
+ return n;
194
+ },
195
+ upper: (a) => String(a[0]).toUpperCase(),
196
+ lower: (a) => String(a[0]).toLowerCase(),
197
+
198
+ // — math —
199
+ abs: (a) => Math.abs(num(a[0], 'abs')),
200
+ floor: (a) => Math.floor(num(a[0], 'floor')),
201
+ ceil: (a) => Math.ceil(num(a[0], 'ceil')),
202
+ sqrt: (a) => Math.sqrt(num(a[0], 'sqrt')),
203
+ max: (a) => { need(a, 1, 'max'); return Math.max(...a.map(x => num(x, 'max'))); },
204
+ min: (a) => { need(a, 1, 'min'); return Math.min(...a.map(x => num(x, 'min'))); },
205
+ pow: (a) => { need(a, 2, 'pow'); return Math.pow(num(a[0], 'pow'), num(a[1], 'pow')); },
206
+ log: (a) => { need(a, 1, 'log'); const x = num(a[0], 'log'); return a.length > 1 ? Math.log(x) / Math.log(num(a[1], 'log')) : Math.log(x); },
207
+ sin: (a) => Math.sin(num(a[0], 'sin')),
208
+ cos: (a) => Math.cos(num(a[0], 'cos')),
209
+ tan: (a) => Math.tan(num(a[0], 'tan')),
210
+ round: (a) => { need(a, 1, 'round'); const x = num(a[0], 'round'); const f = Math.pow(10, a.length > 1 ? num(a[1], 'round') : 0); return Math.round(x * f) / f; },
211
+ random: (a) => {
212
+ if (a.length === 0) return Math.random();
213
+ if (a.length === 1) return Math.floor(Math.random() * num(a[0], 'random'));
214
+ const lo = num(a[0], 'random'), hi = num(a[1], 'random');
215
+ return lo + Math.random() * (hi - lo);
216
+ },
217
+
218
+ // — strings —
219
+ split: (a) => { need(a, 2, 'split'); return String(a[0]).split(stringify(a[1])); },
220
+ join: (a) => { need(a, 2, 'join'); if (!Array.isArray(a[0])) throw new MyxoError('join expected a list'); return a[0].map(x => stringify(x)).join(stringify(a[1])); },
221
+ trim: (a) => String(a[0]).trim(),
222
+ replace: (a) => { need(a, 3, 'replace'); return String(a[0]).split(stringify(a[1])).join(stringify(a[2])); },
223
+ repeat: (a) => { need(a, 2, 'repeat'); return String(a[0]).repeat(Math.max(0, num(a[1], 'repeat'))); },
224
+ chars: (a) => [...String(a[0])],
225
+ starts: (a) => { need(a, 2, 'starts'); return String(a[0]).startsWith(stringify(a[1])); },
226
+ ends: (a) => { need(a, 2, 'ends'); return String(a[0]).endsWith(stringify(a[1])); },
227
+
228
+ // — generic sequence ops (string OR list) —
229
+ slice: (a) => {
230
+ need(a, 2, 'slice'); const s = a[0];
231
+ if (typeof s !== 'string' && !Array.isArray(s)) throw new MyxoError(`slice expected a string or list, got ${typeName(s)}`);
232
+ const end = a.length > 2 ? num(a[2], 'slice') : s.length;
233
+ return s.slice(num(a[1], 'slice'), end);
234
+ },
235
+ find: (a, interp) => {
236
+ need(a, 2, 'find'); const s = a[0];
237
+ if (typeof s === 'string') return s.indexOf(stringify(a[1]));
238
+ if (Array.isArray(s)) { for (let i = 0; i < s.length; i++) if (interp.equals(s[i], a[1])) return i; return -1; }
239
+ throw new MyxoError(`find expected a string or list, got ${typeName(s)}`);
240
+ },
241
+ reverse: (a) => {
242
+ const s = a[0];
243
+ if (typeof s === 'string') return [...s].reverse().join('');
244
+ if (Array.isArray(s)) return s.slice().reverse();
245
+ throw new MyxoError(`reverse expected a string or list, got ${typeName(s)}`);
246
+ },
247
+
248
+ // — list mutation & mesh ops —
249
+ shift: (a) => { if (!Array.isArray(a[0])) throw new MyxoError('shift expected a list'); return a[0].length ? a[0].shift() : VOID; },
250
+ unshift: (a) => { need(a, 2, 'unshift'); if (!Array.isArray(a[0])) throw new MyxoError('unshift expected a list'); a[0].unshift(a[1]); return a[0]; },
251
+ sort: (a, interp) => {
252
+ if (!Array.isArray(a[0])) throw new MyxoError('sort expected a list');
253
+ const xs = a[0].slice();
254
+ const cmp = a[1];
255
+ if (cmp && (cmp.__agent || cmp.__native)) xs.sort((p, q) => num(interp.callValue(cmp, [p, q]), 'sort comparator'));
256
+ else xs.sort((p, q) => (typeof p === 'number' && typeof q === 'number') ? p - q : stringify(p) < stringify(q) ? -1 : stringify(p) > stringify(q) ? 1 : 0);
257
+ return xs;
258
+ },
259
+ entries: (a) => { if (!(a[0] instanceof Map)) throw new MyxoError('entries expected a mesh'); return [...a[0].entries()].map(([k, v]) => [k, v]); },
260
+ merge: (a) => { need(a, 2, 'merge'); if (!(a[0] instanceof Map) || !(a[1] instanceof Map)) throw new MyxoError('merge expected two meshes'); return new Map([...a[0], ...a[1]]); },
261
+
262
+ // — the Law engine: introspect and prune the living mesh —
263
+ mesh: (_a, interp) => {
264
+ const m = new Map();
265
+ for (const row of interp.meshSnapshot()) m.set(row.name, row.strength);
266
+ return m;
267
+ },
268
+ prune: (a, interp) => {
269
+ const threshold = a.length ? num(a[0], 'prune') : 1;
270
+ let removed = 0;
271
+ for (const [name, e] of [...interp.globals.vars]) {
272
+ if (!e.system && e.strength < threshold) { interp.globals.vars.delete(name); removed++; }
273
+ }
274
+ if (removed) interp.epoch++; // reaping a global pathway IS a global write -> invalidate memo caches (parity with `decay`); else a memoized caller keeps ghost-serving a reaped dependency
275
+ return removed;
276
+ },
277
+ // — the living mesh: introspect heat, and metabolize (decay cold + report promoted) —
278
+ strength: (a, interp) => {
279
+ const name = a[0];
280
+ if (typeof name !== 'string') throw new MyxoError('strength(name) needs a string pathway name');
281
+ const e = interp.globals.vars.get(name);
282
+ return e ? e.strength : 0; // 0 = pathway absent or never read
283
+ },
284
+ metabolize: (a, interp) => {
285
+ const threshold = a.length ? num(a[0], 'metabolize') : 2;
286
+ const reaped = [];
287
+ for (const [name, e] of [...interp.globals.vars]) {
288
+ if (!e.system && e.strength < threshold) { interp.globals.vars.delete(name); reaped.push(name); } // cold decays
289
+ }
290
+ if (reaped.length) interp.epoch++; // same as prune: a reaped pathway invalidates every memo cache that could depend on it
291
+ let promoted = 0;
292
+ for (const rec of interp.memo.values()) if (rec.cache.size > 0) promoted++; // hot pure agents that promoted
293
+ const m = new Map();
294
+ m.set('reaped', reaped);
295
+ m.set('count', reaped.length);
296
+ m.set('promoted', promoted);
297
+ return m;
298
+ },
299
+ // — automatic flow-routing (the slime mold): route(name, [providers]) -> a router; flows(name) -> conductivities —
300
+ route: (a, interp) => {
301
+ const name = a[0], providers = a[1];
302
+ if (typeof name !== 'string') throw new MyxoError('route(name, [providers]) needs a name string');
303
+ if (!Array.isArray(providers) || providers.length === 0) throw new MyxoError('route needs a non-empty list of providers');
304
+ if (!providers.every(p => p && (p.__agent || p.__native))) throw new MyxoError('route providers must all be agents');
305
+ const existing = interp.routes.get(name);
306
+ if (existing) {
307
+ if (existing.providers.length !== providers.length || existing.providers.some((p, i) => p !== providers[i]))
308
+ throw new MyxoError(`route '${name}' is already defined with different providers`); // no silent stale providers
309
+ } else {
310
+ interp.routes.set(name, { providers: providers.slice(), cond: providers.map(() => 1), credit: providers.map(() => 0) });
311
+ }
312
+ const rname = name;
313
+ // `impure: true` -> calling a router TAINTS the caller (like random/flows): a router is stateful (conductance
314
+ // EWMA) and nondeterministic (weighted pick), so a plain agent that only calls it must NEVER be memoized —
315
+ // otherwise the first answer freezes in the cache and the whole Physarum routing law dies inside a pure caller.
316
+ return { __native: true, name: 'route:' + name, capability: false, impure: true, fn: (callArgs, ip) => routeCall(ip, rname, callArgs) };
317
+ },
318
+ flows: (a, interp) => {
319
+ const name = a[0];
320
+ const m = new Map();
321
+ const R = interp.routes.get(name) || interp.pools.get(name); // one viewer for both routers and scheduler pools
322
+ if (!R) return m;
323
+ const nodes = R.providers || R.workers;
324
+ nodes.forEach((p, i) => {
325
+ let key = p.name || ('p' + i);
326
+ if (m.has(key)) key = key + '#' + i; // disambiguate duplicate names so none is lost from view
327
+ m.set(key, Math.round(R.cond[i] * 100) / 100);
328
+ });
329
+ return m;
330
+ },
331
+ // — the Physarum scheduler: distribute a batch across a learning pool of worker-agents, in parallel —
332
+ schedule: (a, interp) => scheduleRun(interp, a[0], a[1], a[2]),
333
+
334
+ // — cooperative concurrency: channels + the fiber scheduler —
335
+ channel: (a) => {
336
+ let cap = Infinity;
337
+ if (a.length) {
338
+ cap = num(a[0], 'channel');
339
+ if (!Number.isInteger(cap) || cap < 1) throw new MyxoError('channel(capacity) needs a positive whole number');
340
+ }
341
+ return { __channel: true, buf: [], cap, recvW: [], sendW: [] };
342
+ },
343
+ drain: (a, interp) => { interp.pump(); interp.checkAllFibersDone(); return VOID; }, // run ALL fibers to completion
344
+ await: (a, interp) => { // run the scheduler, then resolve THIS fiber (not the whole pool)
345
+ const f = a[0];
346
+ if (!(f && f.__fiber)) throw new MyxoError(`await needs a fiber (from spawn), got ${typeName(f)}`);
347
+ interp.pump();
348
+ if (f.error) throw f.error;
349
+ if (f.done) return f.result;
350
+ throw new MyxoError('the awaited fiber is deadlocked — blocked with no one to unblock it'); // only blame f, not unrelated parked fibers
351
+ },
352
+ };
353
+ }
354
+
355
+ // Wire every builtin into an interpreter as a system native.
356
+ function install(interp) {
357
+ const table = builtins();
358
+ for (const [name, fn] of Object.entries(table)) interp.registerNative(name, fn);
359
+ }
360
+
361
+ module.exports = { builtins, install };