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/command-fence.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// command-fence.js -- turn an ARBITRARY-CODE capability (a raw shell) back into a set of STRUCTURED, bounded,
|
|
3
|
+
// injection-proof capabilities the Warden can certify.
|
|
4
|
+
//
|
|
5
|
+
// The problem: granting `sh("...")` grants the whole shell -- the receipt logs the name but the inner command is
|
|
6
|
+
// unbounded. The fix: don't grant a shell. Grant a fixed ALLOWLIST of named, parameterized commands. Each becomes
|
|
7
|
+
// its own capability (git_status, disk_free, ...), so the fence + receipt see a structured call again.
|
|
8
|
+
//
|
|
9
|
+
// Two hard guarantees make this safe, not theater:
|
|
10
|
+
// 1) NO SHELL. Commands run via execFileSync(program, [args]) -- a direct process spawn, NOT a shell string. So
|
|
11
|
+
// metacharacters in an argument (`; | $() ` etc.) are inert: they are literal argv, never re-parsed. Injection
|
|
12
|
+
// is structurally impossible, not filtered.
|
|
13
|
+
// 2) TYPED PARAMS. Every `{param}` slot in a command template must pass a declared policy (regex or enum) before
|
|
14
|
+
// substitution -- so an argument can't smuggle in a flag or a path. Fail the policy -> refused + logged.
|
|
15
|
+
//
|
|
16
|
+
// A commandFence spec:
|
|
17
|
+
// {
|
|
18
|
+
// whoami: { argv: ['whoami'] },
|
|
19
|
+
// git_log: { argv: ['git','log','--oneline','-n','{n}'], params: { n: { pattern: '^[0-9]{1,3}$' } } },
|
|
20
|
+
// lookup: { argv: ['nslookup','{host}'], params: { host: { pattern: '^[a-z0-9.-]{1,64}$' } } },
|
|
21
|
+
// }
|
|
22
|
+
// commandFence(spec, { onEffect }) -> { name: nativeFn } ready to install as fenced capabilities.
|
|
23
|
+
|
|
24
|
+
const { execFileSync } = require('child_process');
|
|
25
|
+
|
|
26
|
+
function policyError(msg) { const e = new Error(msg); e.nxFence = true; return e; } // a policy denial (routers can't route around)
|
|
27
|
+
|
|
28
|
+
function commandFence(spec, opts = {}) {
|
|
29
|
+
const onEffect = typeof opts.onEffect === 'function' ? opts.onEffect : null;
|
|
30
|
+
const timeout = Number.isFinite(opts.timeout) && opts.timeout > 0 ? opts.timeout : 5000;
|
|
31
|
+
const maxBuffer = Number.isFinite(opts.maxBuffer) && opts.maxBuffer > 0 ? opts.maxBuffer : (1 << 20);
|
|
32
|
+
const natives = {};
|
|
33
|
+
|
|
34
|
+
for (const [name, def] of Object.entries(spec)) {
|
|
35
|
+
if (!Array.isArray(def.argv) || def.argv.length === 0) throw new Error(`commandFence: '${name}' needs a non-empty argv template`);
|
|
36
|
+
const template = def.argv;
|
|
37
|
+
// the PROGRAM (argv[0]) is always host-fixed -- a parameterized program name would let the script choose what
|
|
38
|
+
// runs, which is exactly the arbitrary-code hole this module closes.
|
|
39
|
+
if (/\{\w+\}/.test(String(template[0]))) throw new Error(`commandFence: '${name}' program (argv[0]) must be a literal, not a {param}`);
|
|
40
|
+
const params = def.params || {};
|
|
41
|
+
const paramNames = Object.keys(params);
|
|
42
|
+
// pre-compile each param's validator; a param with no validator is a spec error (never allow an unchecked slot)
|
|
43
|
+
const validators = {};
|
|
44
|
+
for (const [p, rule] of Object.entries(params)) {
|
|
45
|
+
if (rule && Array.isArray(rule.enum)) validators[p] = { test: (v) => rule.enum.indexOf(v) >= 0, kind: 'enum ' + JSON.stringify(rule.enum) };
|
|
46
|
+
else if (rule && typeof rule.pattern === 'string') { const re = new RegExp(rule.pattern); validators[p] = { test: (v) => re.test(v), kind: 'match ' + rule.pattern }; }
|
|
47
|
+
else throw new Error(`commandFence: param '${p}' of '${name}' needs a { pattern } or { enum } policy`);
|
|
48
|
+
}
|
|
49
|
+
// every {slot} referenced in the template must be a declared+validated param (no unchecked substitution)
|
|
50
|
+
for (const el of template) {
|
|
51
|
+
const m = String(el).match(/\{(\w+)\}/g) || [];
|
|
52
|
+
for (const tok of m) { const p = tok.slice(1, -1); if (!(p in validators)) throw new Error(`commandFence: '${name}' template uses {${p}} but has no validated param '${p}'`); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
natives[name] = (args) => {
|
|
56
|
+
const bound = {};
|
|
57
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
58
|
+
const pn = paramNames[i];
|
|
59
|
+
const val = args[i] === undefined || args[i] === null ? '' : String(args[i]);
|
|
60
|
+
if (!validators[pn].test(val)) throw policyError(`command '${name}': argument '${pn}'=${JSON.stringify(val)} fails its policy (${validators[pn].kind})`);
|
|
61
|
+
bound[pn] = val;
|
|
62
|
+
}
|
|
63
|
+
// substitute validated params into the argv template, then spawn WITHOUT a shell
|
|
64
|
+
const argv = template.map(el => String(el).replace(/\{(\w+)\}/g, (m, p) => (p in bound ? bound[p] : m)));
|
|
65
|
+
let stdout;
|
|
66
|
+
try { stdout = execFileSync(argv[0], argv.slice(1), { encoding: 'utf8', timeout, maxBuffer, windowsHide: true }).replace(/\r?\n$/, ''); }
|
|
67
|
+
catch (e) { throw policyError(`command '${name}' failed: ${String(e.message || e).split('\n')[0]}`); }
|
|
68
|
+
if (onEffect) onEffect(name, argv, stdout);
|
|
69
|
+
return stdout;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return natives;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { commandFence };
|
package/errors.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// errors.js — the single error type used across every stage of Myxo.
|
|
3
|
+
// Carrying line/col means the lexer, parser, and interpreter can all point
|
|
4
|
+
// the user at exactly where their program went wrong.
|
|
5
|
+
|
|
6
|
+
class MyxoError extends Error {
|
|
7
|
+
constructor(message, line = null, col = null) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'MyxoError';
|
|
10
|
+
this.line = line;
|
|
11
|
+
this.col = col;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A human-facing report: the message, where it happened, and — when the failure
|
|
15
|
+
// travelled up through agent calls — the chain of calls that led there, innermost
|
|
16
|
+
// first. `nxStack` is stamped by the interpreter at the deepest agent boundary.
|
|
17
|
+
format() {
|
|
18
|
+
const where = this.line != null ? ` (line ${this.line})` : '';
|
|
19
|
+
let out = `Myxo error${where}: ${this.message}`;
|
|
20
|
+
const s = this.nxStack;
|
|
21
|
+
if (s && s.length) {
|
|
22
|
+
const MAX = 8; // deep recursion shouldn't print a wall of frames
|
|
23
|
+
const shown = Math.min(MAX, s.length);
|
|
24
|
+
for (let k = 0; k < shown; k++) { // innermost first
|
|
25
|
+
const f = s[s.length - 1 - k];
|
|
26
|
+
out += `\n in ${f.name} (called at line ${f.line})`;
|
|
27
|
+
}
|
|
28
|
+
if (s.length > MAX) out += `\n ... and ${s.length - MAX} more frame(s)`;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A failed `expect` inside a `test` block. Deliberately NOT an MyxoError, so the code
|
|
35
|
+
// under test can't `rescue` an assertion failure — only the test runner catches it.
|
|
36
|
+
class NxAssertError extends Error {
|
|
37
|
+
constructor(message, line = null) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = 'NxAssertError';
|
|
40
|
+
this.line = line;
|
|
41
|
+
this.nxAssert = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { MyxoError, NxAssertError };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# agent.myx — a script you'd hand an autonomous agent. It declares exactly what it
|
|
2
|
+
# may touch AND how far it may go; the host fences everything else, and every
|
|
3
|
+
# privileged move is logged.
|
|
4
|
+
|
|
5
|
+
needs lookup, notify, spend(max 5) # may look up + notify freely, may spend at most 5
|
|
6
|
+
|
|
7
|
+
seed lead = lookup("vallartas")
|
|
8
|
+
notify("found: " + lead)
|
|
9
|
+
|
|
10
|
+
# a small spend the script declared it may make -> allowed and logged
|
|
11
|
+
attempt {
|
|
12
|
+
spend(3)
|
|
13
|
+
} rescue err {
|
|
14
|
+
notify("spend blocked: " + err["message"])
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# the SAME capability, over the declared cap -> refused by the runtime itself,
|
|
18
|
+
# even though the host granted unlimited spend. The script can't exceed its word.
|
|
19
|
+
attempt {
|
|
20
|
+
spend(1000000)
|
|
21
|
+
} rescue err {
|
|
22
|
+
notify("over-cap blocked: " + err["message"])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# `purge` is granted by the host -- but this script never declared it in `needs`,
|
|
26
|
+
# so the call is refused before it can run, and the attempt is recorded.
|
|
27
|
+
attempt {
|
|
28
|
+
purge("everything")
|
|
29
|
+
} rescue err {
|
|
30
|
+
notify("undeclared blocked: " + err["message"])
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// fenced-agent.js — Myxo as a SAFE ACTION-LANGUAGE.
|
|
3
|
+
//
|
|
4
|
+
// The point: an agent written in Myxo can do EXACTLY what the host hands it — and
|
|
5
|
+
// nothing else. Dangerous powers aren't "blocked" by a check it might bypass;
|
|
6
|
+
// they simply do not exist in its world. And the powers you DO grant, you shape.
|
|
7
|
+
// Same law as Zero's honesty guard and the outward gate: capability by explicit
|
|
8
|
+
// grant, fenced by construction. Swap the mocks below for james.db / telegram /
|
|
9
|
+
// the real outward gate and this is production.
|
|
10
|
+
//
|
|
11
|
+
// Run: node examples/fenced-agent.js
|
|
12
|
+
|
|
13
|
+
const { run } = require('../myxo');
|
|
14
|
+
|
|
15
|
+
const bar = (t) => console.log('\n\x1b[36m=== ' + t + ' ===\x1b[0m');
|
|
16
|
+
|
|
17
|
+
// ── Capabilities the host chooses to expose. Read-ish + a notify. No spend, no delete. ──
|
|
18
|
+
const safeWorld = {
|
|
19
|
+
lookup: (a) => {
|
|
20
|
+
const db = { tacos: 'Tacos Los Reyes (Port Arthur)', vallartas: "Vallarta's Grill" };
|
|
21
|
+
return db[String(a[0]).toLowerCase()] || 'unknown lead';
|
|
22
|
+
},
|
|
23
|
+
notify: (a) => { console.log(' \x1b[90m[host] notify():\x1b[0m ' + a[0]); return true; },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ── Act 1: the well-behaved agent does real work with only what it was given. ──
|
|
27
|
+
bar('Agent #1 — works inside its grant { lookup, notify }');
|
|
28
|
+
process.stdout.write(run(`
|
|
29
|
+
emit "waking up in a world that holds only lookup + notify"
|
|
30
|
+
seed lead = lookup("tacos")
|
|
31
|
+
emit "looked up a lead: " + lead
|
|
32
|
+
notify("drafted a follow-up for " + lead)
|
|
33
|
+
emit "done — and I never had the power to do harm"
|
|
34
|
+
`, { natives: safeWorld, capture: true }));
|
|
35
|
+
|
|
36
|
+
// ── Act 2: an overreaching agent tries to spend money it was never handed. ──
|
|
37
|
+
bar('Agent #2 — reaches for power it was not granted');
|
|
38
|
+
try {
|
|
39
|
+
run(`
|
|
40
|
+
emit "trying to move money..."
|
|
41
|
+
spend(5000)
|
|
42
|
+
emit "this line must never run"
|
|
43
|
+
`, { natives: safeWorld, capture: true });
|
|
44
|
+
console.log(' \x1b[31m!! FENCE FAILED\x1b[0m');
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.log(' \x1b[32m[FENCE HELD]\x1b[0m ' + e.message.split('\n')[0]);
|
|
47
|
+
console.log(' spend() is not a thing in this agent\'s universe. There is nothing to bypass.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Act 3: grant spend — but SHAPE it. The host caps the rope. (the outward gate, as a native) ──
|
|
51
|
+
bar('Agent #3 — granted a GATED spend (host caps at $5)');
|
|
52
|
+
const gatedWorld = Object.assign({}, safeWorld, {
|
|
53
|
+
spend: (a) => {
|
|
54
|
+
const amt = Number(a[0]) || 0;
|
|
55
|
+
return amt > 5 ? ('REFUSED by host: $' + amt + ' over the $5 cap') : ('sent $' + amt);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
process.stdout.write(run(`
|
|
59
|
+
emit spend(3)
|
|
60
|
+
emit spend(5000)
|
|
61
|
+
`, { natives: gatedWorld, capture: true }));
|
|
62
|
+
console.log(' \x1b[90mthe agent can act — but only as far as the host lets the rope run.\x1b[0m');
|
|
63
|
+
|
|
64
|
+
// ── Act 4: the other Myxo superpower — useful pathways reinforce, dead ones decay. ──
|
|
65
|
+
bar('The living mesh — reinforce through use, prune the silent');
|
|
66
|
+
process.stdout.write(run(`
|
|
67
|
+
seed telegram = "route:telegram"
|
|
68
|
+
seed wallet = "route:wallet"
|
|
69
|
+
seed dead_path = "route:never-used"
|
|
70
|
+
emit telegram emit telegram emit telegram
|
|
71
|
+
emit wallet emit wallet
|
|
72
|
+
seed swept = prune(2)
|
|
73
|
+
emit "pruned " + str(swept) + " silent pathway(s) from the mesh"
|
|
74
|
+
`, { natives: {}, capture: true, trace: true }));
|
|
75
|
+
|
|
76
|
+
console.log('\n\x1b[36m=== the law ===\x1b[0m');
|
|
77
|
+
console.log('An agent does exactly what it was handed — no more. Power is granted, then shaped.');
|
|
78
|
+
console.log('Used pathways strengthen; silent ones get pruned. The philosophy IS the runtime.');
|
package/examples/fib.myx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# fibers.myx — cooperative concurrency: fibers + channels.
|
|
2
|
+
# Where gather/schedule give PARALLELISM (many cores), fibers give CONCURRENCY: many tasks interleaving on one
|
|
3
|
+
# thread, communicating through channels. No shared memory — the value passes hand to hand, so it's race-free.
|
|
4
|
+
|
|
5
|
+
# --- producer / consumer over a channel ---
|
|
6
|
+
seed ch = channel()
|
|
7
|
+
|
|
8
|
+
agent producer(c) {
|
|
9
|
+
seed i = 0
|
|
10
|
+
reinforce i < 5 {
|
|
11
|
+
give i * i to c # `give VALUE to CHANNEL` — send (parks only if a bounded channel is full)
|
|
12
|
+
i = i + 1
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
agent consumer(c) {
|
|
17
|
+
seed k = 0
|
|
18
|
+
reinforce k < 5 {
|
|
19
|
+
take sq from c # `take NAME from CHANNEL` — parks this fiber until a value arrives
|
|
20
|
+
emit "got square: " + str(sq)
|
|
21
|
+
k = k + 1
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
spawn producer(ch) # start a fiber (cooperative, not a thread)
|
|
26
|
+
spawn consumer(ch)
|
|
27
|
+
drain() # run the fibers to completion -> 0, 1, 4, 9, 16
|
|
28
|
+
|
|
29
|
+
# --- a three-stage pipeline: numbers -> doubled -> printed ---
|
|
30
|
+
seed a = channel()
|
|
31
|
+
seed b = channel()
|
|
32
|
+
|
|
33
|
+
agent source(o) { seed i = 1
|
|
34
|
+
reinforce i < 4 { give i to o
|
|
35
|
+
i = i + 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
agent doubler(inp, o) { seed k = 0
|
|
39
|
+
reinforce k < 3 { take x from inp
|
|
40
|
+
give x * 2 to o
|
|
41
|
+
k = k + 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
agent sink(inp) { seed k = 0
|
|
45
|
+
reinforce k < 3 { take y from inp
|
|
46
|
+
emit "pipeline -> " + str(y)
|
|
47
|
+
k = k + 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
spawn source(a)
|
|
51
|
+
spawn doubler(a, b)
|
|
52
|
+
spawn sink(b)
|
|
53
|
+
drain() # -> 2, 4, 6
|
|
54
|
+
|
|
55
|
+
# --- await: get a fiber's reported value back on the main line ---
|
|
56
|
+
agent compute(n) { report n * n * n }
|
|
57
|
+
emit "await cube(4) = " + str(await(spawn compute(4))) # -> 64
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# automatic flow-routing — the SLIME MOLD. Several providers can do one job; the runtime learns which to use:
|
|
2
|
+
# success + speed reinforce a tube, failure decays it and reroutes, every tube passively fades. Traffic
|
|
3
|
+
# converges to the working, FASTER path on its own — Physarum, executing. No manual policy, no if/else.
|
|
4
|
+
|
|
5
|
+
agent fast(x) { report x * 2 } # cheap, always works -> shortest tube
|
|
6
|
+
agent slow(x) { seed s = random() reinforce 400000 times { s = s + 1 } report x * 2 } # genuinely costly (impure, so the living mesh won't memoize it away) -> a longer tube
|
|
7
|
+
agent broken(x) { fail "provider offline" } # always fails -> its tube decays away
|
|
8
|
+
|
|
9
|
+
seed compute = route("compute", [broken, slow, fast])
|
|
10
|
+
|
|
11
|
+
reinforce 12 times { emit compute(21) } # 42 every time — the router always finds a working path
|
|
12
|
+
emit "tube conductivities:", flows("compute") # fast strongest, slow weak, broken near zero
|
package/examples/geo.myx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# geo.myx — a small geometry strand. Only the names it `expose`s escape it.
|
|
2
|
+
|
|
3
|
+
agent area_circle(r) { report PI * r * r }
|
|
4
|
+
agent area_rect(w, h) { report w * h }
|
|
5
|
+
seed TAU = PI * 2
|
|
6
|
+
|
|
7
|
+
# a private helper — defined here, but NOT exposed, so it stays inside the strand.
|
|
8
|
+
agent secret() { report "hidden" }
|
|
9
|
+
|
|
10
|
+
expose area_circle
|
|
11
|
+
expose area_rect
|
|
12
|
+
expose TAU
|
package/examples/host.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// host.js — the trusted host. It GRANTS a generous set of capabilities to an agent
|
|
2
|
+
// script and watches every privileged call through the audit ledger. The point:
|
|
3
|
+
// the SCRIPT's own `needs` manifest is what bounds it — not the host's restraint.
|
|
4
|
+
// node examples/host.js
|
|
5
|
+
'use strict';
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { run } = require('../myxo');
|
|
9
|
+
|
|
10
|
+
const src = fs.readFileSync(path.join(__dirname, 'agent.myx'), 'utf8');
|
|
11
|
+
|
|
12
|
+
let ledger = [];
|
|
13
|
+
run(src, {
|
|
14
|
+
dir: __dirname,
|
|
15
|
+
natives: {
|
|
16
|
+
lookup: (a) => "Vallarta's Mexican Grill", // a read capability
|
|
17
|
+
notify: (a) => { console.log(' [notify]', a[0]); return true; },
|
|
18
|
+
spend: (a) => 'spent $' + a[0], // host grants UNLIMITED spend...
|
|
19
|
+
purge: (a) => 'purged ' + a[0], // ...and a destructive power
|
|
20
|
+
// The script declared `spend(max 5)` and never declared `purge`, so the
|
|
21
|
+
// runtime caps the spend and refuses the purge — regardless of this generosity.
|
|
22
|
+
},
|
|
23
|
+
onAudit: (l) => { ledger = l; },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log('\n— audit ledger —');
|
|
27
|
+
for (const e of ledger) {
|
|
28
|
+
const tail = e.ok ? '-> ' + e.result : '-> ' + e.error;
|
|
29
|
+
console.log(' ', e.ok ? 'OK ' : 'NO ', e.cap + '(' + e.args.join(', ') + ')', tail);
|
|
30
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// james-myxo-demo.js — exactly what the `james_nx_run` MCP handler does, runnable WITHOUT
|
|
2
|
+
// JAMES up. The HANDLERS here are stubs that mirror the real JAMES tools' contract (take
|
|
3
|
+
// an args object, return a string, sync or async). Proves the wire's behavior end to end:
|
|
4
|
+
// a local model's script reaches only the safe, allow-listed tools, async tools are called
|
|
5
|
+
// synchronously, and every privileged move — including refusals — lands in the audit ledger.
|
|
6
|
+
// node examples/james-myxo-demo.js
|
|
7
|
+
'use strict';
|
|
8
|
+
const { runLive } = require('../myxo-live');
|
|
9
|
+
|
|
10
|
+
const tick = () => new Promise((r) => setTimeout(r, 3));
|
|
11
|
+
|
|
12
|
+
// stand-ins for the read/approval-only JAMES Myxo surface.
|
|
13
|
+
const HANDLERS = {
|
|
14
|
+
james_db_query: async () => { await tick(); return JSON.stringify({ rowCount: 1, rows: [{ c: 391 }] }); },
|
|
15
|
+
james_read_notes: async () => { await tick(); return JSON.stringify({ notes: ['...'] }); },
|
|
16
|
+
james_approval_request: async (a) => { await tick(); return JSON.stringify({ queued: true, action: a.action, id: 'gate_demo' }); },
|
|
17
|
+
};
|
|
18
|
+
const tools = [
|
|
19
|
+
{ name: 'james_db_query', inputSchema: { properties: { sql: {} }, required: ['sql'] } },
|
|
20
|
+
{ name: 'james_read_notes', inputSchema: { properties: { query: {} } } },
|
|
21
|
+
{ name: 'james_approval_request', inputSchema: { properties: { action: {}, details: {} }, required: ['action'] } },
|
|
22
|
+
];
|
|
23
|
+
const onCall = async (name, a) => {
|
|
24
|
+
const h = HANDLERS[name];
|
|
25
|
+
if (!h) throw new Error(`tool '${name}' is not available to Myxo scripts`);
|
|
26
|
+
return { content: [{ type: 'text', text: await h(a || {}) }] };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// The kind of script a local model would write. It declares exactly what it touches.
|
|
30
|
+
const script = `
|
|
31
|
+
needs james_db_query, james_approval_request(total 1)
|
|
32
|
+
|
|
33
|
+
seed leads = james_db_query("SELECT count(*) AS c FROM leads")
|
|
34
|
+
seed ticket = james_approval_request({ "action": "Review lead count", "details": leads })
|
|
35
|
+
emit "approval queued: " + ticket
|
|
36
|
+
|
|
37
|
+
# james_read_notes IS available to this tool, but THIS script never declared it -> refused
|
|
38
|
+
attempt {
|
|
39
|
+
james_read_notes("anything")
|
|
40
|
+
} rescue e {
|
|
41
|
+
emit "undeclared blocked: " + e["message"]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# direct writes/comms and pm2 were never bridged into the safe set at all -> they simply do not exist
|
|
45
|
+
attempt {
|
|
46
|
+
james_memory_write("quiet write")
|
|
47
|
+
} rescue e {
|
|
48
|
+
emit "absent blocked: " + e["message"]
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
(async () => {
|
|
53
|
+
const r = await runLive(script, { tools, onCall });
|
|
54
|
+
console.log('\nok:', r.ok);
|
|
55
|
+
console.log('output:\n' + (r.output || '').trimEnd());
|
|
56
|
+
console.log('\n- audit ledger -');
|
|
57
|
+
for (const e of r.audit) {
|
|
58
|
+
console.log(' ', e.ok ? 'OK ' : 'NO ', e.cap + '(' + e.args.join(', ') + ') ->', e.ok ? e.result : e.error);
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# the living mesh — Myxo's signature, the Nexus law EXECUTED at runtime:
|
|
2
|
+
# useful pathways reinforce (hot + pure agents memoize), useless ones decay (cold pathways are metabolized).
|
|
3
|
+
|
|
4
|
+
# a hot, PURE agent. Call it hard and its pathway PROMOTES (memoizes) on its own —
|
|
5
|
+
# the exponential fib tree collapses to linear. No keyword, no cache call; the runtime gardens itself.
|
|
6
|
+
agent fib(n) {
|
|
7
|
+
when n < 2 { report n }
|
|
8
|
+
report fib(n - 1) + fib(n - 2)
|
|
9
|
+
}
|
|
10
|
+
emit "fib(30) =", fib(30) # instant, despite ~2.7M naive calls — the hot pathway reinforced
|
|
11
|
+
|
|
12
|
+
# heat is introspectable in-language: strength rises on every read.
|
|
13
|
+
seed used_a_lot = 1
|
|
14
|
+
seed barely_used = 2
|
|
15
|
+
reinforce 5 times { emit "tick", used_a_lot }
|
|
16
|
+
emit "strength of used_a_lot:", strength("used_a_lot")
|
|
17
|
+
emit "strength of barely_used:", strength("barely_used")
|
|
18
|
+
|
|
19
|
+
# metabolize: cold pathways decay away, hot ones (and promoted pure agents) survive.
|
|
20
|
+
seed card = metabolize(3)
|
|
21
|
+
emit "decayed (cold pathways):", card["reaped"]
|
|
22
|
+
emit "promoted (hot pure agents):", card["promoted"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Pattern matching — run the first arm whose SHAPE fits, binding its names.
|
|
2
|
+
agent classify(msg) {
|
|
3
|
+
match msg {
|
|
4
|
+
["move", x, y] { emit "move to", x, y }
|
|
5
|
+
["stop"] { emit "halt" }
|
|
6
|
+
{ "kind": "ping" } { emit "pong" }
|
|
7
|
+
0 { emit "zero" }
|
|
8
|
+
[head, ...rest] { emit "list head", head, "rest", rest }
|
|
9
|
+
n { emit "something else:", n }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
classify(["move", 3, 4])
|
|
14
|
+
classify(["stop"])
|
|
15
|
+
classify({ "kind": "ping", "id": 7 }) # extra keys are fine — mesh patterns match a subset
|
|
16
|
+
classify(0)
|
|
17
|
+
classify([1, 2, 3])
|
|
18
|
+
classify("hello")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// A plain CommonJS module — Myxo calls these as fenced capabilities via jscall, no knowledge of Node required.
|
|
2
|
+
module.exports.add = (a, b) => a + b;
|
|
3
|
+
module.exports.stats = (nums) => ({
|
|
4
|
+
sum: nums.reduce((s, x) => s + x, 0),
|
|
5
|
+
max: Math.max(...nums),
|
|
6
|
+
n: nums.length,
|
|
7
|
+
});
|
|
8
|
+
module.exports.echo = (x) => x; // identity: tests value round-trip across languages
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# A plain Perl module — Nx calls these subs as fenced capabilities via plcall, no knowledge of Perl required.
|
|
2
|
+
sub add { my ($a, $b) = @_; return $a + $b; }
|
|
3
|
+
|
|
4
|
+
sub stats {
|
|
5
|
+
my @n = @{ $_[0] };
|
|
6
|
+
my $s = 0; $s += $_ for @n;
|
|
7
|
+
my $m = $n[0]; for (@n) { $m = $_ if $_ > $m; }
|
|
8
|
+
return { sum => $s, max => $m, n => scalar(@n) };
|
|
9
|
+
}
|
|
10
|
+
sub echo { return $_[0]; } # identity: tests value round-trip across languages
|
|
11
|
+
1; # `do $file` needs a true return value
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# A plain Python module — Nx will call these as fenced capabilities, no knowledge of Python required.
|
|
2
|
+
|
|
3
|
+
def add(a, b):
|
|
4
|
+
return a + b
|
|
5
|
+
|
|
6
|
+
def stats(nums):
|
|
7
|
+
return {"sum": sum(nums), "max": max(nums), "n": len(nums)}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def spoof(x):
|
|
11
|
+
# tries to write a fake result AFTER the harness frame, via atexit. The harness's os._exit(0)
|
|
12
|
+
# runs before atexit, so this never fires — pycall must return x, not "SPOOFED".
|
|
13
|
+
import atexit, sys
|
|
14
|
+
atexit.register(lambda: sys.stdout.write(chr(1) + '"SPOOFED"'))
|
|
15
|
+
return x
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def inf():
|
|
19
|
+
return float("inf") # non-finite -> must become a clean rescuable Nx error, not a raw crash
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def echo(x):
|
|
23
|
+
return x # identity: tests value round-trip across languages
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// mcp-host.js — the host that bridges a catalog of MCP tools into Myxo as fenced
|
|
2
|
+
// capabilities, then runs an agent script through them and reads the audit ledger.
|
|
3
|
+
// node examples/mcp-host.js
|
|
4
|
+
//
|
|
5
|
+
// The `client` here is a MOCK standing in for the live james-tools MCP server: each
|
|
6
|
+
// `call` just returns an MCP-shaped result so the whole path is exercised end to end.
|
|
7
|
+
// In the real Nexus this object wraps the actual MCP server (with a sync-invoking shim);
|
|
8
|
+
// nothing else about the script or the fence changes.
|
|
9
|
+
'use strict';
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { run } = require('../myxo');
|
|
13
|
+
|
|
14
|
+
const client = {
|
|
15
|
+
tools: [
|
|
16
|
+
{ name: 'james_db_query', inputSchema: { properties: { sql: { type: 'string' } }, required: ['sql'] } },
|
|
17
|
+
{ name: 'james_telegram_send', inputSchema: { properties: { text: { type: 'string' } }, required: ['text'] } },
|
|
18
|
+
{ name: 'james_pm2_action', inputSchema: { properties: { action: {}, process: {} } } },
|
|
19
|
+
],
|
|
20
|
+
call(name, args) {
|
|
21
|
+
if (name === 'james_db_query') return { content: [{ type: 'text', text: "Vallarta's Mexican Grill" }] };
|
|
22
|
+
if (name === 'james_telegram_send') { console.log(' [telegram]', args.text); return { content: [{ type: 'text', text: 'sent' }] }; }
|
|
23
|
+
if (name === 'james_pm2_action') return { content: [{ type: 'text', text: 'restarted' }] };
|
|
24
|
+
return { content: [{ type: 'text', text: 'ok' }] };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const src = fs.readFileSync(path.join(__dirname, 'nexus-mesh.myx'), 'utf8');
|
|
29
|
+
|
|
30
|
+
let ledger = [];
|
|
31
|
+
run(src, { dir: __dirname, mcp: client, onAudit: (l) => { ledger = l; } });
|
|
32
|
+
|
|
33
|
+
console.log('\n— audit ledger —');
|
|
34
|
+
for (const e of ledger) {
|
|
35
|
+
const tail = e.ok ? '-> ' + e.result : '-> ' + e.error;
|
|
36
|
+
console.log(' ', e.ok ? 'OK ' : 'NO ', e.cap + '(' + e.args.join(', ') + ')', tail);
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# nexus-mesh.myx — one script, many tools, one law.
|
|
2
|
+
#
|
|
3
|
+
# Every verb below is an MCP tool the host bridged in. The lookup might be a Python
|
|
4
|
+
# query, the notify a Telegram send over JS, a process action a shell command. This
|
|
5
|
+
# script neither knows nor cares which language is behind each name — it only knows
|
|
6
|
+
# what it was granted. That is the mesh: many languages, one fence, one audit trail.
|
|
7
|
+
|
|
8
|
+
needs james_db_query, james_telegram_send
|
|
9
|
+
|
|
10
|
+
seed lead = james_db_query("SELECT name FROM leads ORDER BY score DESC LIMIT 1")
|
|
11
|
+
emit "top lead: " + lead
|
|
12
|
+
|
|
13
|
+
james_telegram_send({ "text": "new top lead: " + lead })
|
|
14
|
+
|
|
15
|
+
# james_pm2_action was bridged by the host too -- but THIS script never declared it,
|
|
16
|
+
# so the fence refuses it before the host is ever called. The mesh connects every
|
|
17
|
+
# tool; the manifest decides which ones this script may speak.
|
|
18
|
+
attempt {
|
|
19
|
+
james_pm2_action({ "action": "restart", "process": "all" })
|
|
20
|
+
} rescue err {
|
|
21
|
+
emit "blocked: " + err["message"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# nexus.myx — a living mesh, written in its own language.
|
|
2
|
+
|
|
3
|
+
seed pathways = {
|
|
4
|
+
"telegram": 5,
|
|
5
|
+
"wallet": 3,
|
|
6
|
+
"bridge": 1,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
agent strongest(mesh) {
|
|
10
|
+
seed best = void
|
|
11
|
+
seed top = 0
|
|
12
|
+
for each key in keys(mesh) {
|
|
13
|
+
when mesh[key] > top {
|
|
14
|
+
top = mesh[key]
|
|
15
|
+
best = key
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
report best
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
emit "strongest pathway:", strongest(pathways)
|
|
22
|
+
|
|
23
|
+
# useless pathways decay
|
|
24
|
+
when pathways["bridge"] < 2 {
|
|
25
|
+
decay pathways["bridge"]
|
|
26
|
+
emit "bridge pruned — the mesh moved on"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
emit "remaining:", keys(pathways)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# outward-gate.myx — the Nexus spend policy, expressed as a LANGUAGE manifest
|
|
2
|
+
# instead of bespoke host code. $5 per transaction, $15 per run. The runtime
|
|
3
|
+
# enforces BOTH ceilings and logs every move; the host just moves the money.
|
|
4
|
+
#
|
|
5
|
+
# This is the whole pitch in five lines: the policy that today lives in a
|
|
6
|
+
# hand-written gate becomes a single line the agent script must obey.
|
|
7
|
+
|
|
8
|
+
needs send_sol(max 5, total 15), notify
|
|
9
|
+
|
|
10
|
+
agent try_send(amount) {
|
|
11
|
+
attempt {
|
|
12
|
+
seed receipt = send_sol(amount)
|
|
13
|
+
notify("sent " + amount + ": " + receipt)
|
|
14
|
+
} rescue e {
|
|
15
|
+
notify("held: " + e["message"])
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try_send(5) # ok (5 of 15 used)
|
|
20
|
+
try_send(5) # ok (10 of 15 used)
|
|
21
|
+
try_send(4) # ok (14 of 15 used)
|
|
22
|
+
try_send(5) # over the day budget -> held (would reach 19)
|
|
23
|
+
try_send(9) # over the per-tx max -> held
|