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/CONCURRENCY.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Myxo Concurrency — `dispatch` / `gather`
|
|
2
|
+
|
|
3
|
+
> Real multi-core parallelism, built on Myxo's own bones (the worker+Atomics pattern from `myxo-live`).
|
|
4
|
+
> Honest about its shape: this is **parallelism of pure-ish, isolated tasks** — not cooperative coroutines,
|
|
5
|
+
> not shared-memory threads. The isolation is the feature: it is what makes the parallelism race-free.
|
|
6
|
+
|
|
7
|
+
## The model
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dispatch f(args...) -> an UNSTARTED task (a handle). Nothing runs yet.
|
|
11
|
+
gather [t1, t2, ...] -> runs every task on its own OS thread, in parallel,
|
|
12
|
+
blocks until all finish, returns their results IN ORDER.
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```myx
|
|
16
|
+
agent slow(n) { seed s = 0
|
|
17
|
+
for each i in range(n) { s = s + (i % 7) }
|
|
18
|
+
report s
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
emit gather [dispatch slow(8000000), dispatch slow(8000000), dispatch slow(8000000)]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`gather` is an ordinary expression — its result is a list, so it composes with everything: `gather tasks | sum`,
|
|
25
|
+
`seed [a, b] = gather [...]`, `match gather [...] { ... }`.
|
|
26
|
+
|
|
27
|
+
## Why this shape (and not threads-with-shared-memory)
|
|
28
|
+
|
|
29
|
+
Myxo's law is *"agents do not merge."* Shared mutable state across threads is exactly merging — and the bug
|
|
30
|
+
factory (data races) that comes with it. So a dispatched task runs in **its own fresh interpreter**: it sees
|
|
31
|
+
|
|
32
|
+
- the **standard library**,
|
|
33
|
+
- **its own arguments**, and
|
|
34
|
+
- **itself** (so recursion works),
|
|
35
|
+
|
|
36
|
+
and **nothing else** — not the parent's pathways, not other user agents, not captured closure state. A task
|
|
37
|
+
that reaches for a parent pathway fails *cleanly* (`unknown pathway 'x'`) rather than silently reading a stale
|
|
38
|
+
copy. Isolation by construction means there is no shared state to race on. That is the whole trick.
|
|
39
|
+
|
|
40
|
+
The cost of isolation is the **data boundary**: arguments and results cross threads *by value*, so they must be
|
|
41
|
+
plain data — `number`, `string`, `bool`, `list`, `mesh`, `void`. Passing or returning an **agent** (code closes
|
|
42
|
+
over an environment that cannot follow it across the boundary) is rejected loudly at the boundary, never shipped
|
|
43
|
+
as a meaningless blob.
|
|
44
|
+
|
|
45
|
+
## How it works (the mechanism is ours)
|
|
46
|
+
|
|
47
|
+
The interpreter is a synchronous tree-walker, and we keep it that way. `gather` gets parallelism without going
|
|
48
|
+
async by reusing the zero-dependency **worker + `Atomics` + `SharedArrayBuffer`** pattern already proven in
|
|
49
|
+
`myxo-live`:
|
|
50
|
+
|
|
51
|
+
1. `gather` spawns one `Worker` (`myxo-par-worker.js`) per task, handing each the agent's AST (`params` + `body`)
|
|
52
|
+
and its args as JSON, plus one shared `Int32Array` barrier counter and a private `MessageChannel`.
|
|
53
|
+
2. Each worker builds a fresh interpreter, defines the agent under its own name (recursion), runs it, then in a
|
|
54
|
+
`finally` — on *every* path, success or error — posts its result (or its error) and `Atomics.add`s the
|
|
55
|
+
barrier counter and notifies. Posting before ticking means a visible tick guarantees the message is queued.
|
|
56
|
+
3. The calling thread blocks on `Atomics.wait` until the counter reaches N (Node permits `Atomics.wait` on the
|
|
57
|
+
main thread; browsers do not). It then drains each channel with `receiveMessageOnPort` and returns the
|
|
58
|
+
results in input order. Workers and ports are always reclaimed in a `finally`, even on throw or timeout.
|
|
59
|
+
|
|
60
|
+
**Error handling, precisely.** A task that `fail`s, throws, hits a runtime error (e.g. reaches for a parent
|
|
61
|
+
pathway), or fails to even load its interpreter surfaces *promptly* as a single `gather: <message>` error —
|
|
62
|
+
because the worker runs its barrier-tick from a `finally`, so any worker that executes *any* JS reports back.
|
|
63
|
+
The first error wins; the whole `gather` fails (all-or-nothing, like `Promise.all`). The one case the worker
|
|
64
|
+
*can't* self-report is a true hard death (OOM, `SIGKILL`) where the `finally` never runs — that is caught by
|
|
65
|
+
the wall-clock timeout (default 30s), which terminates the stragglers and throws a `timed out` error. (We do
|
|
66
|
+
*not* rely on a parent-side `error`/`exit` callback to release the barrier: the event loop is frozen during
|
|
67
|
+
`Atomics.wait`, so those callbacks can't fire mid-gather — they exist only so a worker failure can't crash the
|
|
68
|
+
parent process.)
|
|
69
|
+
|
|
70
|
+
## What is honest about the performance
|
|
71
|
+
|
|
72
|
+
Measured on the dev laptop (i7-8750H, 6 physical / 12 logical cores, ~2GB free RAM):
|
|
73
|
+
|
|
74
|
+
- **The interpreter runs at native speed inside a worker.** Pure compute, startup excluded: main 4.70s vs
|
|
75
|
+
worker 4.65s for the same loop. There is *no* per-worker interpreter penalty.
|
|
76
|
+
- **Parallelism is real.** Two CPU-bound tasks complete in ~1.05x the time of one (18.5s vs 17.4s) — two tasks
|
|
77
|
+
for the price of one.
|
|
78
|
+
- **Speedup is bounded by the machine, not the language.** 4 tasks do **not** hit 4x on this box, because of
|
|
79
|
+
(a) only 6 physical cores, (b) ~2GB free RAM (large allocations like `range(8_000_000)` thrash), and
|
|
80
|
+
(c) aggressive thermal throttling under sustained all-core load. On a cooler, higher-core machine the curve
|
|
81
|
+
keeps climbing.
|
|
82
|
+
- **Worker startup is ~0.2s** (a fresh interpreter + stdlib parse) and overlaps across workers. Parallelize
|
|
83
|
+
*expensive* work; for trivial work the startup dominates.
|
|
84
|
+
|
|
85
|
+
## Limits (v1, on purpose)
|
|
86
|
+
|
|
87
|
+
- **One worker per task per gather** (no pool yet). Fine for coarse-grained work; a pool is a future optimization.
|
|
88
|
+
- **Tasks are isolated** — no shared state, no inter-task channels. Coordination is by *structure* (dispatch →
|
|
89
|
+
gather), not by message-passing between running tasks.
|
|
90
|
+
- **Self-contained agents only.** Calling *other* user agents from inside a dispatched agent fails (they are not
|
|
91
|
+
defined in the worker). Inline what the task needs, or pass it as data.
|
|
92
|
+
- **Not cooperative concurrency.** There is no `yield`/await; this is OS-thread parallelism with a blocking
|
|
93
|
+
barrier. A cooperative scheduler is a separate, later layer (and the road toward the Physarum scheduler).
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# The Physarum scheduler — `schedule` / `flows`
|
|
98
|
+
|
|
99
|
+
> Where `gather` runs a fixed list of tasks, `schedule` runs a **batch through a learning pool**. It is `route`
|
|
100
|
+
> (the slime-mold *selector*) lifted to parallel batches: the same reinforce/decay law that governs memory now
|
|
101
|
+
> governs **execution** — fast workers pull more flux, slow/failed ones decay and get routed around. The
|
|
102
|
+
> language *becomes* the scheduler.
|
|
103
|
+
|
|
104
|
+
```myx
|
|
105
|
+
agent worker(chunk) { # a worker takes a CHUNK (a list) and returns a results list, same length, in order
|
|
106
|
+
seed out = []
|
|
107
|
+
for each x in chunk { out = out + [x * x] }
|
|
108
|
+
report out
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
emit schedule("squares", [worker, worker], [1, 2, 3, 4, 5, 6, 7, 8]) # -> [1, 4, 9, 16, 25, 36, 49, 64]
|
|
112
|
+
emit flows("squares") # the learned conductance of each tube
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## What it does
|
|
116
|
+
|
|
117
|
+
`schedule(name, workers, items)`:
|
|
118
|
+
1. Looks up (or creates) a **named, persistent pool** of interchangeable worker-agents — `name` keys the learned
|
|
119
|
+
state, exactly like `route`. Reusing a name with different workers is rejected (no silent stale pool).
|
|
120
|
+
2. **Distributes** the items across the workers proportional to conductance, via the same credit + explore-floor
|
|
121
|
+
weighted round-robin as `route`. The credit array is **persisted on the pool** across calls, so a trailing or
|
|
122
|
+
recovered worker is sampled at a rate ∝ its conductance over successive batches (not starved by small batches).
|
|
123
|
+
3. Runs each worker's chunk on its **own worker thread, in parallel** (`gather`'s settled runner underneath),
|
|
124
|
+
blocks, and reassembles every result at its **original item index**.
|
|
125
|
+
4. Updates each worker's conductance from its **measured per-item speed** (the worker reports its own compute
|
|
126
|
+
time): `quality = 1 + 8/(ms_per_item + 1)`, `cond ← EWMA(cond, quality)`. Fast workers climb; a worker that
|
|
127
|
+
errors decays toward the floor.
|
|
128
|
+
|
|
129
|
+
`flows(name)` returns the pool's learned conductances (it also reports `route` conductances — one viewer for both).
|
|
130
|
+
|
|
131
|
+
## Failover
|
|
132
|
+
|
|
133
|
+
If a worker errors, its conductance decays hard and **its items reroute to the survivors** in one more parallel
|
|
134
|
+
round — the slime mold routing around a damaged tube. If every worker fails (or a survivor also fails during the
|
|
135
|
+
reroute), `schedule` throws rather than returning partial/silent results. Failover is **single-tier on purpose**:
|
|
136
|
+
one reroute round, then surface the error. (A multi-tier cascade is a future option; one tier covers the common
|
|
137
|
+
"some tubes are dead" case without unbounded retry.)
|
|
138
|
+
|
|
139
|
+
## What is honest about it
|
|
140
|
+
|
|
141
|
+
- **Adaptation needs more than one call.** The first batch explores (uniform-ish); conductances diverge as speed
|
|
142
|
+
is measured, so *later* batches exploit. Measured: a fast/slow pool over 4 rounds converged to roughly
|
|
143
|
+
`{fast: 7, slow: 1}` — the next batch then routes ~7× the flux to the fast tube. Same explore-then-exploit
|
|
144
|
+
honesty as `route`; the constants are hand-tuned and exploit-leaning.
|
|
145
|
+
- **Timing is nondeterministic**, so exact conductances vary run to run. The guarantees that *don't* vary:
|
|
146
|
+
every item is processed exactly once, results are in item order, and a clearly-faster worker ends higher.
|
|
147
|
+
- **Worker contract:** batch in, batch out (`worker(chunk) -> results`, one result per item, in order). This
|
|
148
|
+
spawns *N* threads (one per worker), not one per item — efficient for real batches. A wrong-length or non-list
|
|
149
|
+
return is a loud contract error. The `dispatch` data boundary applies: items and results must be plain data.
|
|
150
|
+
- **No shared state between workers** (same isolation as `gather`) — which is exactly why distributing work
|
|
151
|
+
across them is race-free.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
# Cooperative concurrency — fibers + channels (`spawn` / `give` / `take` / `yield` / `await`)
|
|
156
|
+
|
|
157
|
+
> Where `gather`/`schedule` give **parallelism** (many OS threads, isolated tasks), fibers give the other half:
|
|
158
|
+
> **concurrency** — many tasks interleaving on *one* thread, talking through **channels**. It's the
|
|
159
|
+
> law-consistent way to do *communicating* tasks: no shared memory, the value passes hand to hand.
|
|
160
|
+
|
|
161
|
+
```myx
|
|
162
|
+
seed ch = channel() # an unbounded channel; channel(n) is bounded (backpressure)
|
|
163
|
+
|
|
164
|
+
agent producer(c) { seed i = 0
|
|
165
|
+
reinforce i < 3 { give i to c # `give VALUE to CHANNEL` — blocks only if a bounded channel is full
|
|
166
|
+
i = i + 1 } }
|
|
167
|
+
|
|
168
|
+
agent consumer(c) { seed k = 0
|
|
169
|
+
reinforce k < 3 { take x from c # `take NAME from CHANNEL` — parks the fiber until a value arrives
|
|
170
|
+
emit x
|
|
171
|
+
k = k + 1 } }
|
|
172
|
+
|
|
173
|
+
spawn producer(ch) # start a fiber (cooperative, NOT a thread)
|
|
174
|
+
spawn consumer(ch) # both run, interleaved, at program end -> 0 1 2
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## The model
|
|
178
|
+
|
|
179
|
+
- `spawn f(args)` — start a **fiber** and return a handle. Fibers run **cooperatively** on the calling thread:
|
|
180
|
+
one runs until it parks on a channel (or `yield`s), then the scheduler runs the next ready fiber. This is
|
|
181
|
+
*concurrency, not parallelism* — for real multi-core work use `gather`/`schedule`.
|
|
182
|
+
- `give VALUE to CHANNEL` / `take NAME from CHANNEL` — send / receive. A `take` on an empty channel parks the
|
|
183
|
+
fiber until a value is given; a `give` to a *full bounded* channel parks until space frees. Delivery is
|
|
184
|
+
**exactly-once, FIFO**.
|
|
185
|
+
- `yield` — voluntarily give up the turn (cooperative round-robin).
|
|
186
|
+
- `await(fiber)` — run the scheduler and return that fiber's reported value (or re-raise its error). `drain()` —
|
|
187
|
+
run **all** fibers to completion. Spawned-but-unawaited fibers also run at program end; a failed fiber's error
|
|
188
|
+
**never vanishes** — it surfaces at `await`/`drain`/program end.
|
|
189
|
+
- `channel()` unbounded; `channel(n)` bounded to `n` (backpressure). Channels are not data — they can't cross the
|
|
190
|
+
`gather`/`dispatch` thread boundary (rejected loudly).
|
|
191
|
+
|
|
192
|
+
## Why it's race-free
|
|
193
|
+
|
|
194
|
+
A channel is the *only* thing shared, and a value moves through it hand to hand — there is no shared mutable
|
|
195
|
+
state for two fibers to clobber. The scheduler is single-threaded and **deterministic**, so a fiber program's
|
|
196
|
+
output is reproducible (unlike the timing-dependent parallel side).
|
|
197
|
+
|
|
198
|
+
## Honest scope (v1)
|
|
199
|
+
|
|
200
|
+
- **Cooperative, not parallel** — fibers interleave on one thread. (Compose with `gather` when you need cores.)
|
|
201
|
+
- **Suspension composes through** the fiber's own body and inside `when` / `match` / `for each` / `reinforce` /
|
|
202
|
+
`attempt`. It does **not** cross into a *called* agent's body (a helper a fiber calls can't `give`/`take`) — that
|
|
203
|
+
would need the whole call stack to be suspendable. Doing so is a **clear, honest error**, not a silent failure.
|
|
204
|
+
- `await`/`drain` can't be called from *inside* a fiber (that would re-enter the scheduler) — coordinate with
|
|
205
|
+
channels or `yield` instead; a clean error says so.
|
|
206
|
+
- A runaway fiber (e.g. `yield` forever) is bounded by a step budget and **errors**, never hangs.
|
|
207
|
+
- No `select`/timeouts/closeable channels yet; deadlocks (everyone parked) are detected and reported.
|
package/HOW_IT_WORKS.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# How Myxo Works
|
|
2
|
+
|
|
3
|
+
Myxo has two jobs:
|
|
4
|
+
|
|
5
|
+
1. Be an independent language with its own source files, runtime behavior, standard library,
|
|
6
|
+
tooling, and release path.
|
|
7
|
+
2. Connect useful external runtimes through fenced capabilities: rich bridges for runtimes like
|
|
8
|
+
Python and Node, conditional bridges such as Perl when installed, and weaker executable/CLI
|
|
9
|
+
bridges for compiled tools like C++, Rust, and Go binaries.
|
|
10
|
+
|
|
11
|
+
Those jobs are separate on purpose. Myxo can run by itself. Foreign languages extend it; they do
|
|
12
|
+
not define it.
|
|
13
|
+
|
|
14
|
+
## 1. The Standalone Language Core
|
|
15
|
+
|
|
16
|
+
A `.myx` program is real Myxo source. It is not translated into Python or C++ first.
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
.myx source
|
|
20
|
+
-> lexer.js
|
|
21
|
+
-> parser.js
|
|
22
|
+
-> AST
|
|
23
|
+
-> interpreter.js
|
|
24
|
+
-> output + value + audit ledger
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Current implementation:
|
|
28
|
+
|
|
29
|
+
- `lexer.js` tokenizes Myxo source.
|
|
30
|
+
- `parser.js` builds the AST.
|
|
31
|
+
- `interpreter.js` walks the AST and enforces Myxo semantics.
|
|
32
|
+
- `builtins.js` installs the core standard functions.
|
|
33
|
+
- `std.myx` is a self-hosted standard library written in Myxo.
|
|
34
|
+
- `myxo.js` is the CLI, REPL, formatter entrypoint, and Node embed API.
|
|
35
|
+
|
|
36
|
+
That means these are Myxo language behavior, not aliases over another runtime:
|
|
37
|
+
|
|
38
|
+
- `seed` creates a pathway.
|
|
39
|
+
- `agent` defines a callable Myxo function.
|
|
40
|
+
- `report` returns from an agent.
|
|
41
|
+
- `reinforce` loops.
|
|
42
|
+
- `decay` removes a pathway.
|
|
43
|
+
- `attempt` / `rescue` handle failures.
|
|
44
|
+
- `needs` declares external capabilities.
|
|
45
|
+
- pathway strength rises when names are read.
|
|
46
|
+
- routing and mesh behavior live in the interpreter.
|
|
47
|
+
|
|
48
|
+
The reference engine is currently written in Node. That is an implementation fact, not the
|
|
49
|
+
language identity. The independence path is to keep this Node tree-walker as the reference
|
|
50
|
+
while tightening a spec and eventually adding bytecode/native or other host runtimes.
|
|
51
|
+
|
|
52
|
+
## 2. Values And Execution
|
|
53
|
+
|
|
54
|
+
Myxo values are intentionally small and portable:
|
|
55
|
+
|
|
56
|
+
- numbers
|
|
57
|
+
- strings
|
|
58
|
+
- `live` / `dead`
|
|
59
|
+
- `void`
|
|
60
|
+
- lists
|
|
61
|
+
- meshes
|
|
62
|
+
- agents
|
|
63
|
+
|
|
64
|
+
The interpreter stores named values in environments. A named value is a pathway. Reading a
|
|
65
|
+
pathway reinforces it by increasing its strength. `mesh()`, `strength(name)`, `prune()`, and
|
|
66
|
+
`metabolize()` expose that behavior to Myxo code.
|
|
67
|
+
|
|
68
|
+
Agents are closures over their environment. The runtime can hot-promote pure agents by memoizing
|
|
69
|
+
calls it can prove safe: plain parameters, primitive arguments/results, no capabilities, no
|
|
70
|
+
observable side effects, no mutable outer-data dependency, and no stale global epoch.
|
|
71
|
+
|
|
72
|
+
## 3. Builtins vs Capabilities
|
|
73
|
+
|
|
74
|
+
Myxo has a hard distinction:
|
|
75
|
+
|
|
76
|
+
- **Builtins** are ordinary language power: math, strings, lists, meshes, output, routing.
|
|
77
|
+
- **Capabilities** are host power: database calls, Telegram, Python, C++ binaries, shell,
|
|
78
|
+
MCP tools, filesystem module loading, or anything outside the interpreter.
|
|
79
|
+
|
|
80
|
+
A capability does not exist inside a script unless the host registers it.
|
|
81
|
+
|
|
82
|
+
Even if the host registers it, production runners can require the script to declare it:
|
|
83
|
+
|
|
84
|
+
```myx
|
|
85
|
+
needs lookup, spend(max 5, total 15)
|
|
86
|
+
|
|
87
|
+
emit lookup("lead")
|
|
88
|
+
emit spend(4)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The production fence is:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
host allowlist
|
|
95
|
+
-> script `needs` manifest
|
|
96
|
+
-> value budget checks
|
|
97
|
+
-> call execution
|
|
98
|
+
-> audit ledger
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If a script calls a capability it did not declare, Myxo refuses before the host is touched. Every
|
|
102
|
+
allowed call and every refusal is recorded in the audit ledger.
|
|
103
|
+
|
|
104
|
+
Production runners also add operational limits:
|
|
105
|
+
|
|
106
|
+
- `requireManifest`: capability calls require `needs`.
|
|
107
|
+
- `maxSteps`: interpreter fuel for runaway loops.
|
|
108
|
+
- `timeoutMs`: live worker wall-clock timeout.
|
|
109
|
+
- `moduleLoader: null`: fences `weave` unless the host grants file loading.
|
|
110
|
+
|
|
111
|
+
## 4. Polyglot: How Python, C++, And Others Plug In
|
|
112
|
+
|
|
113
|
+
Polyglot support is foreign-function interface, not identity.
|
|
114
|
+
|
|
115
|
+
Myxo sees every foreign language call as a capability:
|
|
116
|
+
|
|
117
|
+
```myx
|
|
118
|
+
needs pycall(total 3)
|
|
119
|
+
|
|
120
|
+
seed result = pycall("tools.py", "score", "lead-17")
|
|
121
|
+
emit result
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The outer Myxo law is the same whether the capability is implemented in Myxo, Python, C++, Node, or
|
|
125
|
+
a remote tool:
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
Myxo script -> capability name -> host bridge -> foreign runtime -> Myxo value
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Rich Runtime Bridge
|
|
132
|
+
|
|
133
|
+
`polyglot.js` currently has rich value-mapped runners for:
|
|
134
|
+
|
|
135
|
+
- Python: `pycall`, `pyeval`
|
|
136
|
+
- Node: `jscall`, `jseval`
|
|
137
|
+
- Perl: `plcall`, `pleval` when Perl is installed
|
|
138
|
+
|
|
139
|
+
These runners use a JSON/framed-result protocol so lists, meshes, numbers, strings, booleans,
|
|
140
|
+
and `void` can cross the boundary cleanly. Non-finite numbers are rejected instead of silently
|
|
141
|
+
becoming null. In the current machine test run, Python and Node passed; Perl support is present
|
|
142
|
+
but skips when `perl` is not on PATH.
|
|
143
|
+
|
|
144
|
+
### Compiled/CLI Bridge
|
|
145
|
+
|
|
146
|
+
C++, Rust, Go, and many existing tools usually plug in first as compiled executables:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
Myxo -> bridgeExec("my_tool.exe") -> argv/stdout -> Myxo string
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
That is intentionally weaker than the rich bridge:
|
|
153
|
+
|
|
154
|
+
- stdout is the result
|
|
155
|
+
- structured values need JSON discipline from the executable
|
|
156
|
+
- Myxo can still fence, budget, and audit the call boundary
|
|
157
|
+
|
|
158
|
+
The stronger future version is a stable Myxo FFI/wire protocol so compiled languages can expose
|
|
159
|
+
structured functions instead of only command-line stdout.
|
|
160
|
+
|
|
161
|
+
## 5. MCP And JAMES Are Hosts, Not The Language
|
|
162
|
+
|
|
163
|
+
MCP tools are another capability catalog. `mcp-bridge.js` turns each MCP tool into a fenced Myxo
|
|
164
|
+
capability. `myxo-run.js` and `myxo-live.js` are production runners for agent-written scripts.
|
|
165
|
+
|
|
166
|
+
JAMES uses this by exposing a narrow `james_nx_run` surface. That is one host integration. Myxo
|
|
167
|
+
must still work without JAMES:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
node myxo.js examples/fib.myx
|
|
171
|
+
node myxo.js
|
|
172
|
+
node myxo.js fmt examples/fib.myx --check
|
|
173
|
+
npm test
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 6. Security Boundary
|
|
177
|
+
|
|
178
|
+
The fence controls which verb a script may call, how often or how much it may spend, and what
|
|
179
|
+
gets logged at the Myxo boundary.
|
|
180
|
+
|
|
181
|
+
The fence does **not** sandbox the code inside a granted arbitrary runtime.
|
|
182
|
+
|
|
183
|
+
If you grant:
|
|
184
|
+
|
|
185
|
+
- `pyeval`, the script has Python eval power.
|
|
186
|
+
- `jseval`, the script has Node eval power.
|
|
187
|
+
- `sh`, the script has shell power.
|
|
188
|
+
- a C++ executable, the script can run that executable.
|
|
189
|
+
|
|
190
|
+
Budgets are argument and call-count policies. They are not CPU, filesystem, network, or
|
|
191
|
+
subprocess sandboxes for whatever happens inside the granted runtime.
|
|
192
|
+
|
|
193
|
+
For untrusted or model-generated scripts, expose shaped capabilities instead:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
good: score_lead(id)
|
|
197
|
+
bad: pyeval(any_code)
|
|
198
|
+
good: resize_image(input, output)
|
|
199
|
+
bad: sh(any_command)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
That is where Myxo is strongest: it gives agents narrow verbs, budgets, time/fuel limits, and an
|
|
203
|
+
audit trail.
|
|
204
|
+
|
|
205
|
+
## 7. The Short Version
|
|
206
|
+
|
|
207
|
+
Myxo should become "another Python/C++" in independence, not in purpose.
|
|
208
|
+
|
|
209
|
+
It should own:
|
|
210
|
+
|
|
211
|
+
- `.myx` files
|
|
212
|
+
- syntax and semantics
|
|
213
|
+
- runtime behavior
|
|
214
|
+
- standard library
|
|
215
|
+
- CLI and REPL
|
|
216
|
+
- tests and formatter
|
|
217
|
+
- docs and spec
|
|
218
|
+
- release path
|
|
219
|
+
|
|
220
|
+
It should connect:
|
|
221
|
+
|
|
222
|
+
- Python for Python work
|
|
223
|
+
- C++/Rust/Go binaries for compiled work
|
|
224
|
+
- Node for JS work
|
|
225
|
+
- MCP/JAMES/tools for system work
|
|
226
|
+
|
|
227
|
+
The point is one independent language that can coordinate many runtimes under one law:
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
declare what you need
|
|
231
|
+
stay inside the budget
|
|
232
|
+
leave an audit trail
|
|
233
|
+
route to the best provider
|
|
234
|
+
run standalone when no foreign runtime is needed
|
|
235
|
+
```
|
package/INDEPENDENCE.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Myxo Independence Contract
|
|
2
|
+
|
|
3
|
+
Myxo must be a real language, not only a wrapper around other languages.
|
|
4
|
+
|
|
5
|
+
The target is this:
|
|
6
|
+
|
|
7
|
+
- **Standalone first.** A `.myx` program runs as Myxo source through the Myxo CLI/runtime/stdlib without requiring Python, C++, Node scripts, shell glue, or JAMES.
|
|
8
|
+
- **Own semantics.** `seed`, `agent`, `reinforce`, `decay`, `needs`, budgets, audit ledger, pathway strength, and flow routing are Myxo language behavior, not comments over another runtime.
|
|
9
|
+
- **Own toolchain.** Myxo needs a CLI, REPL, formatter, tests, docs, versioning, module rules, errors, and eventually an LSP/debugger and bytecode/native runtime path.
|
|
10
|
+
- **Foreign runtimes are FFI.** Python, Node, Perl, shell, MCP, and compiled binaries such as C++/Rust/Go tools are called through explicit fenced capabilities. They extend Myxo; they do not define Myxo.
|
|
11
|
+
- **One outer law at the boundary.** Whether a capability is written in Myxo, Python, C++, or anything else, the Myxo caller still goes through `needs`, host allowlists, budgets, fuel/timeouts where applicable, and the audit ledger. Myxo does not sandbox arbitrary side effects inside a granted runtime; hosts should expose narrow structured verbs for untrusted callers.
|
|
12
|
+
- **No hidden dependency on Nexus.** Nexus/JAMES can host Myxo, but Myxo must remain usable by itself from the command line and embeddable by other hosts.
|
|
13
|
+
|
|
14
|
+
## What "another Python/C++" means here
|
|
15
|
+
|
|
16
|
+
Myxo should become a peer in the toolchain: a language someone can install, run, learn, test,
|
|
17
|
+
package, and ship. It does not need to replace Python for data scripting or C++ for systems
|
|
18
|
+
performance. It needs to be independent the way they are independent: it owns a file format,
|
|
19
|
+
runtime, standard library, behavior, documentation, and release path.
|
|
20
|
+
|
|
21
|
+
## Boundary
|
|
22
|
+
|
|
23
|
+
Polyglot bridging is still core. Myxo connects the useful languages, but as a conductor with its
|
|
24
|
+
own score, not as a thin alias for any one of them.
|
package/MYXO_PROMPT.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Myxo — write this language (prompt card)
|
|
2
|
+
|
|
3
|
+
Myxo is a small scripting language. You will be given a task; return ONLY Myxo code. Myxo is NOT Python, NOT
|
|
4
|
+
JavaScript, NOT C — do not assume their syntax. Learn it from the rules and examples below, then write.
|
|
5
|
+
|
|
6
|
+
## The 6 rules you must not break
|
|
7
|
+
|
|
8
|
+
1. **NO SEMICOLONS. EVER.** Statements end at the end of the line — never write `;`. `seed x = 1` then a
|
|
9
|
+
newline. A `;` is a syntax error.
|
|
10
|
+
2. **Booleans are `live` and `dead`** — never `true`/`false`. `report live`, `when x { ... }`.
|
|
11
|
+
3. **Functions are `agent`s; return with `report`.** `agent add(a, b) { report a + b }`. No `function`, no
|
|
12
|
+
`def`, no `return`, no `=>` for statements.
|
|
13
|
+
4. **Declare a variable with `seed`, reassign with plain `=`.** `seed n = 0` then later `n = n + 1`.
|
|
14
|
+
5. **`==` on two lists or two meshes compares IDENTITY, not contents** — `[1,2] == [1,2]` is `dead`. Compare
|
|
15
|
+
length + elements yourself. (Inside `match`/`expect`, equality IS structural.)
|
|
16
|
+
6. **Mesh keys are strings.** `{ "a": 1 }`. The number `1` and the string `"1"` are the SAME key.
|
|
17
|
+
|
|
18
|
+
## Core syntax by example (imitate these)
|
|
19
|
+
|
|
20
|
+
```myx
|
|
21
|
+
# variables, agents, report
|
|
22
|
+
seed name = "Zero"
|
|
23
|
+
agent greet(who) { report "hi " + who }
|
|
24
|
+
|
|
25
|
+
# conditionals — `when` / `otherwise`, blocks in { }
|
|
26
|
+
agent sign(n) {
|
|
27
|
+
when n > 0 { report "pos" }
|
|
28
|
+
otherwise when n < 0 { report "neg" }
|
|
29
|
+
otherwise { report "zero" }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# loop over a list, mesh (keys), or string (chars)
|
|
33
|
+
agent total(xs) {
|
|
34
|
+
seed s = 0
|
|
35
|
+
for each x in xs { s = s + x }
|
|
36
|
+
report s
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# while-loop is `reinforce COND { }`; repeat is `reinforce N times { }`
|
|
40
|
+
agent countdown(n) {
|
|
41
|
+
reinforce n > 0 { emit n n = n - 1 }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# lists and meshes
|
|
45
|
+
seed nums = [1, 2, 3]
|
|
46
|
+
seed person = { "name": "Zero", "age": 30 }
|
|
47
|
+
emit person["name"] # index a mesh by key, a list by integer (negatives from end: xs[0-1] = last)
|
|
48
|
+
seed longer = nums + [4] # + concatenates two lists; + concatenates if EITHER side is a string
|
|
49
|
+
seed n = len(nums) # len works on list/mesh/string
|
|
50
|
+
|
|
51
|
+
# default + rest params, and closures
|
|
52
|
+
agent log(msg, level = "info", ...rest) { report level + ": " + msg }
|
|
53
|
+
agent adder(k) { report agent(x) { report x + k } } # returns a closure
|
|
54
|
+
|
|
55
|
+
# pattern matching — `match` (first arm whose shape fits runs)
|
|
56
|
+
agent describe(v) {
|
|
57
|
+
match v {
|
|
58
|
+
[] { report "empty" }
|
|
59
|
+
[x] { report "one" }
|
|
60
|
+
{ "kind": k } { report k } # mesh that HAS key "kind"; binds k
|
|
61
|
+
_ { report "other" } # wildcard
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# errors — attempt / rescue, and fail
|
|
66
|
+
agent safe_div(a, b) {
|
|
67
|
+
attempt { report a / b } rescue e { report "undefined" } # e is a mesh { message, line, value }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# pipeline: x | f == f(x) ; x | f(a) == f(x, a) (piped value is the FIRST arg)
|
|
71
|
+
agent inc(x) { report x + 1 }
|
|
72
|
+
seed y = 5 | inc | inc # 7
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Truthiness & and/or (these trip up Python/JS habits)
|
|
76
|
+
|
|
77
|
+
- **Dead (falsy):** `void`, `0`, `""`, empty list `[]`, empty mesh `{}`. Everything else is live.
|
|
78
|
+
- **`and`/`or` return a VALUE, not a bool** (and short-circuit): `a or b` → `a` if `a` is live, else `b`.
|
|
79
|
+
So `x or default` gives a fallback. `a and b` → `a` if `a` is dead, else `b`.
|
|
80
|
+
|
|
81
|
+
```myx
|
|
82
|
+
agent with_default(v, fallback) { report v or fallback } # 0/""/[] all fall back, not just void
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## The capability fence (only when the task mentions natives / a manifest / budgets)
|
|
86
|
+
|
|
87
|
+
A script reaches the host ONLY through granted natives, and must DECLARE them with `needs` at the top.
|
|
88
|
+
|
|
89
|
+
```myx
|
|
90
|
+
needs lookup, spend(max 5, total 15) # spend is budgeted: max per call, total cumulative
|
|
91
|
+
|
|
92
|
+
seed r = lookup("x")
|
|
93
|
+
emit "got: " + r
|
|
94
|
+
attempt {
|
|
95
|
+
spend(9) # over the max-5 cap -> the fence refuses it
|
|
96
|
+
emit "spent"
|
|
97
|
+
} rescue e {
|
|
98
|
+
emit "blocked" # a fence denial is catchable like any error
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Calling a native you did not `needs`-declare is refused even if the host granted it.
|
|
103
|
+
|
|
104
|
+
## Concurrency (only when the task asks for parallel / fibers / channels)
|
|
105
|
+
|
|
106
|
+
```myx
|
|
107
|
+
# parallel: dispatch builds a task, gather runs them on real threads, results in order
|
|
108
|
+
agent sq(n) { report n * n }
|
|
109
|
+
agent squares(xs) {
|
|
110
|
+
seed tasks = []
|
|
111
|
+
for each x in xs { tasks = tasks + [dispatch sq(x)] }
|
|
112
|
+
report gather tasks
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# fibers + channels (one thread, cooperative): spawn / give..to / take..from / await
|
|
116
|
+
agent producer(c, xs) { for each x in xs { give x to c } }
|
|
117
|
+
agent consumer(c, n) { seed s = 0 reinforce n times { take v from c s = s + v } report s }
|
|
118
|
+
agent sum_via_channel(xs) {
|
|
119
|
+
seed ch = channel()
|
|
120
|
+
spawn producer(ch, xs)
|
|
121
|
+
seed job = spawn consumer(ch, len(xs))
|
|
122
|
+
report await(job)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## The 5 traps (right vs wrong)
|
|
127
|
+
|
|
128
|
+
| Trap | WRONG (a Python/JS habit) | RIGHT (Myxo) |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Semicolons | `seed x = 1;` | `seed x = 1` (newline ends it) |
|
|
131
|
+
| Booleans | `report true` | `report live` |
|
|
132
|
+
| List equality | `report a == b` | compare `len` + each element in a loop |
|
|
133
|
+
| Mesh keys collide | assuming `1` ≠ `"1"` as keys | they are the SAME key (keys coerce to strings) |
|
|
134
|
+
| Fallback only on null | `when v == void { ... }` | `v or fallback` (0/""/[] are also dead) |
|
|
135
|
+
|
|
136
|
+
## How to answer
|
|
137
|
+
|
|
138
|
+
Return ONE ```myx code block. Define exactly the agent(s) the task names — no extra prose, no `main`, no
|
|
139
|
+
semicolons. Prefer the patterns above; when unsure, keep it simple and let `report` return the value.
|