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.
- package/CONCURRENCY.md +207 -0
- package/HOW_IT_WORKS.md +235 -0
- package/INDEPENDENCE.md +24 -0
- package/MYXO_PROMPT.md +139 -0
- package/README.md +494 -0
- package/ROADMAP.md +200 -0
- package/SPEC.md +181 -0
- package/VISION.md +249 -0
- package/builtins.js +361 -0
- package/command-fence.js +75 -0
- package/errors.js +45 -0
- package/examples/agent.myx +31 -0
- package/examples/fenced-agent.js +78 -0
- package/examples/fib.myx +11 -0
- package/examples/fibers.myx +57 -0
- package/examples/flow-routing.myx +12 -0
- package/examples/geo.myx +12 -0
- package/examples/hello.myx +2 -0
- package/examples/host.js +30 -0
- package/examples/james-myxo-demo.js +60 -0
- package/examples/living-mesh.myx +22 -0
- package/examples/match.myx +18 -0
- package/examples/mathlib.js +8 -0
- package/examples/mathlib.pl +11 -0
- package/examples/mathlib.py +23 -0
- package/examples/mcp-host.js +37 -0
- package/examples/nexus-mesh.myx +22 -0
- package/examples/nexus.myx +29 -0
- package/examples/ouroboros.myx +2 -0
- package/examples/outward-gate.myx +23 -0
- package/examples/physarum.myx +75 -0
- package/examples/polyglot-host.js +17 -0
- package/examples/polyglot.myx +11 -0
- package/examples/resilient.myx +28 -0
- package/examples/scheduler.myx +46 -0
- package/examples/the-law.myx +27 -0
- package/examples/use-geo.myx +9 -0
- package/format.js +206 -0
- package/interpreter.js +1092 -0
- package/lexer.js +173 -0
- package/mcp-bridge.js +77 -0
- package/mcp-framing.js +34 -0
- package/mcp-server.js +57 -0
- package/myxo-concurrent.js +110 -0
- package/myxo-live-worker.js +30 -0
- package/myxo-live.js +83 -0
- package/myxo-lsp.js +226 -0
- package/myxo-par-worker.js +39 -0
- package/myxo-plan.js +207 -0
- package/myxo-run.js +63 -0
- package/myxo.js +296 -0
- package/package.json +27 -0
- package/parser.js +662 -0
- package/polyglot-host.js +39 -0
- package/polyglot.js +191 -0
- package/receipt.js +71 -0
- package/std.myx +88 -0
- 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 };
|