mu-agent 0.2.3 → 0.2.5

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.
@@ -14,7 +14,6 @@ import {
14
14
  findOnPath,
15
15
  layoutOf,
16
16
  linkState,
17
- localJudgeSupport,
18
17
  packageEntries,
19
18
  parseEnvFile,
20
19
  platformName,
@@ -143,31 +142,18 @@ if (overrides.length > 0) row("--", "modes", overrides.map(([id, value]) => `${i
143
142
  const ladder = config?.features?.swarm?.models ?? [];
144
143
  row("--", "subagents", ladder.length > 0 ? `model ladder ${ladder.join(" < ")}` : "use the session's model (no ladder configured)");
145
144
 
146
- const judgeSupport = localJudgeSupport({ platform: process.platform, env: process.env, wsl });
147
- if (!asksJudge) {
148
- // Nothing asks the local judge either: whether it is up does not matter.
149
- } else if (tiers.some((tier) => tier.startsWith("laya")) && !judgeSupport.runs && !judgeSupport.url) {
150
- // Not a failure of this installation: the sidecar is Core ML, and there is none for this platform.
151
- row("--", "local judge", `not available on ${platformName({ platform: process.platform, wsl })}: the Laya sidecar needs Core ML (macOS on Apple silicon)`);
152
- row("--", "", "Laya's decision points use the next judge or their fallbacks. Use Jev, an LLM judge or MU_LOCAL_JUDGE_URL=<a server with the same API>");
153
- } else if (tiers.some((tier) => tier.startsWith("laya"))) {
154
- const base = env("LOCAL_JUDGE_URL") ?? `http://127.0.0.1:${env("LOCAL_JUDGE_PORT") ?? 47823}`;
155
- const startedAt = performance.now();
156
- try {
157
- const response = await fetch(`${base}/health`, { signal: AbortSignal.timeout(2000) });
158
- const health = await response.json();
159
- const ms = Math.round(performance.now() - startedAt);
160
- row(health.status === "ok" ? "ok" : "--", "local judge", `${base} · ${health.status} · ${health.model ?? "laya"} · ${ms} ms`);
161
- } catch {
162
- row(
163
- "fix",
164
- "local judge",
165
- judgeSupport.runs
166
- ? `${base} isn't answering: run mu judge start (first time: mu judge setup)`
167
- : `${base} (MU_LOCAL_JUDGE_URL) isn't answering`,
168
- );
169
- }
145
+ // Laya, the local judge mu had until 0.2.4, is no longer built in. The next session takes it out of mu.json (mude 1.0 in
146
+ // its place when it is set up), and skips it in MU_JUDGE (packages/kyrn-judge/src/laya-migration.ts).
147
+ if (tiers.includes("laya") || Object.values(config?.judges ?? {}).some((judge) => judge?.type === "local")) {
148
+ row(
149
+ "--",
150
+ "laya",
151
+ env("JUDGE")
152
+ ? "no longer built into mu: mu skips it in MU_JUDGE. Other decision models: mu judge model add <link>"
153
+ : "no longer built into mu: the next session takes it out of mu.json. Other decision models: mu judge model add <link>",
154
+ );
170
155
  }
156
+
171
157
  // The built-in ways to Jev, as packages/kyrn-judge/src/config.ts has them; mu.json may name others.
172
158
  const JEV_ROUTES = {
173
159
  jev: { type: "jev" },
package/kyrn/bin/mu.mjs CHANGED
@@ -22,8 +22,9 @@
22
22
  // - mu's name on the agent: PI_PACKAGE_DIR points at ~/.mu/app, a view of packages/coding-agent whose
23
23
  // package.json carries piConfig {name: mu, configDir: .mu}. No upstream file is edited.
24
24
  // - the judgment layer (packages/kyrn-judge), with judges from ~/.mu/agent/mu.json or MU_JUDGE
25
- // (e.g. "laya", "laya,luna", "laya,jev", "off"). When "laya" is among them the sidecar is started (macOS only).
26
- // kyrn.json and every KYRN_* variable are still read when the mu spelling is absent.
25
+ // (e.g. "jev", "mude,jev", "off"). When mude 1.0 or a decision model added from Hugging Face is among them, its
26
+ // server is started in the background (macOS only). kyrn.json and every KYRN_* variable are still read when the
27
+ // mu spelling is absent.
27
28
  // - the repo-root .env (the Jev key), read as data and handed to the agent without ever being printed.
28
29
  // The npm package reads ~/.mu/.env instead: there is no repository around it.
29
30
  import { spawn, spawnSync } from "node:child_process";
@@ -104,7 +105,6 @@ export function usage(platform) {
104
105
  " mu judge mude install install mude 1.0, mu's own decision model for coding, and make it the judge on this Mac (Apple silicon, 1.8 GB)",
105
106
  " enable | disable | start | stop | status | uninstall (mu judge mude help)",
106
107
  " mu judge model <cmd> other decision models from Hugging Face: add <link> | list | use | remove (mu judge model help)",
107
- " mu judge <cmd> manage the local judge, Laya (macOS on Apple silicon): setup | start | stop | status | run",
108
108
  " mu ledger [n] [--json] print the judge's verdicts from the last n sessions",
109
109
  " mu import <file>... import Claude Code, Codex and ZCode conversations; --list shows them (mu import --help)",
110
110
  " mu doctor check the installation, the judges and the connections",
@@ -422,28 +422,9 @@ export function agentDirFor({ env, muDir, platform }) {
422
422
  );
423
423
  }
424
424
 
425
- /** Whether the configuration asks for the local judge. */
426
- export function wantsLaya({ env, agentDir, platform, readFile }) {
427
- const judges = muEnv("JUDGE", env);
428
- if (judges) return `,${judges},`.includes(",laya");
429
- const path = pathFor(platform);
430
- for (const name of ["mu.json", "kyrn.json"]) {
431
- let text;
432
- try {
433
- text = readFile(path.join(agentDir, name));
434
- } catch {
435
- continue;
436
- }
437
- // Like the file itself: kyrn.json is only looked at when there is no mu.json.
438
- return text.includes('"laya');
439
- }
440
- return false;
441
- }
442
-
443
- /** The Core ML sidecar exists for macOS only. Anything else needs a server of its own behind MU_LOCAL_JUDGE_URL. */
444
425
  /**
445
426
  * Whether the configuration asks for mude (mu judge mude): MU_JUDGE naming it, else `tiers` or a route in mu.json (or
446
- * kyrn.json when there is no mu.json). Its server is then started when a session starts, as Laya's is.
427
+ * kyrn.json when there is no mu.json). Its server is then started when a session starts.
447
428
  */
448
429
  export function wantsMude({ env, agentDir, platform, readFile }) {
449
430
  const judges = muEnv("JUDGE", env);
@@ -487,30 +468,47 @@ export function wantedModel({ env, agentDir, muDir, platform, readFile, exists }
487
468
  return undefined;
488
469
  }
489
470
 
471
+ /**
472
+ * Whether mu.json (or kyrn.json when there is no mu.json) still names Laya, which mu no longer has: `laya` in `tiers`
473
+ * or a route (and no judge of that name of a type mu knows), or a judge of Laya's type, `local`. The judgment layer
474
+ * then puts mude 1.0 in its place when it is set up on this Mac (packages/kyrn-judge/src/laya-migration.ts), so mude's
475
+ * server is started for that session too. MU_JUDGE naming Laya is only skipped.
476
+ */
477
+ export function namesLaya({ env, agentDir, platform, readFile }) {
478
+ if (muEnv("JUDGE", env)) return false;
479
+ const path = pathFor(platform);
480
+ for (const name of ["mu.json", "kyrn.json"]) {
481
+ let config;
482
+ try {
483
+ config = JSON.parse(readFile(path.join(agentDir, name)));
484
+ } catch (error) {
485
+ if (error instanceof SyntaxError) return false;
486
+ continue;
487
+ }
488
+ const judges = config?.judges && typeof config.judges === "object" ? config.judges : {};
489
+ const known = ["jev", "typesafe", "clm", "gateway", "http", "llm", "classifier", "mock"];
490
+ const retired = (tier) =>
491
+ typeof tier === "string" &&
492
+ (judges[tier.trim()]?.type === "local" || (tier.trim() === "laya" && !known.includes(judges.laya?.type)));
493
+ const lists = [config?.tiers, ...Object.values(config?.routes ?? {})];
494
+ return lists.some((tiers) => (Array.isArray(tiers) ? tiers : typeof tiers === "string" ? tiers.split(",") : []).some(retired));
495
+ }
496
+ return false;
497
+ }
498
+
499
+ /** What mude 1.0 needs to answer: its environment, made by `mu judge mude install`, and the key `enable` writes to the .env. */
500
+ export function mudeSetUp({ env, muDir, platform, exists }) {
501
+ return Boolean(env.MU_JUDGE_MUDE_KEY) && exists(pathFor(platform).join(muDir, "local-judge", "mude", "venv", "bin", "python"));
502
+ }
503
+
490
504
  /** What starts the server of the decision model the configuration asks for: `mu judge mude start`, or a model's. */
491
505
  export function localModelStart({ env, agentDir, muDir, platform, readFile, exists }) {
492
506
  if (wantsMude({ env, agentDir, platform, readFile })) return ["start", "--no-wait"];
507
+ if (namesLaya({ env, agentDir, platform, readFile }) && mudeSetUp({ env, muDir, platform, exists })) return ["start", "--no-wait"];
493
508
  const model = wantedModel({ env, agentDir, muDir, platform, readFile, exists });
494
509
  return model ? ["model", "start", model, "--no-wait"] : undefined;
495
510
  }
496
511
 
497
- export function localJudgeSupport({ platform, env, wsl = false }) {
498
- if (platform === "darwin") return { runs: true };
499
- const url = muEnv("LOCAL_JUDGE_URL", env);
500
- const where = platformName({ platform, wsl });
501
- return {
502
- runs: false,
503
- url,
504
- message: [
505
- `The local judge (Laya) is a Core ML sidecar that runs only on macOS on Apple silicon, not on ${where}.`,
506
- "mu works without it: Jev, the default hosted judge, answers instead (free on OpenCode Zen, or with your own key via mu setup); you can also set an LLM judge in mu.json.",
507
- "A local judge for the desktop app is in progress. Without a judge, decision points use their fallbacks.",
508
- "To use a server that implements the same API (GET /health, POST /evaluate), set:",
509
- " MU_LOCAL_JUDGE_URL=http://host:port",
510
- ].join("\n"),
511
- };
512
- }
513
-
514
512
  /** exec replaces this process, as the bash launcher did; spawn is what Windows has. MU_LAUNCH forces either. */
515
513
  export function launchStrategy({ platform, env, canExec }) {
516
514
  const forced = muEnv("LAUNCH", env);
@@ -623,17 +621,6 @@ export function planLaunch({
623
621
  // pi reports a fresh install to pi.dev with its version. mu-agent is not a pi install; asked for, it still can be.
624
622
  if (layout === "package" && childEnv.PI_TELEMETRY === undefined) childEnv.PI_TELEMETRY = "0";
625
623
 
626
- let startJudge = false;
627
- if (wantsLaya({ env: childEnv, agentDir, platform, readFile: fs.readFile })) {
628
- const support = localJudgeSupport({ platform, env: childEnv, wsl });
629
- if (support.runs) startJudge = true;
630
- else if (!support.url) {
631
- notes.push(
632
- `mu: the configuration asks for the local judge (Laya), which runs on macOS only; on ${platformName({ platform, wsl })}, the decision points it would answer use the next judge or their fallbacks (mu judge status)`,
633
- );
634
- }
635
- }
636
-
637
624
  // mude's server, or an added model's, when the configuration asks for it: started without waiting, so a session
638
625
  // never waits for the model to load (what it cannot answer yet goes to the judges behind it). Only a Mac runs it.
639
626
  let startMude;
@@ -652,7 +639,6 @@ export function planLaunch({
652
639
  muDir,
653
640
  appDir,
654
641
  agentDir,
655
- startJudge,
656
642
  startMude,
657
643
  notes,
658
644
  preface: argv[0] === "-h" || argv[0] === "--help" ? `${usage(platform)}\n\npi's help (mu passes all of this through):\n` : undefined,
@@ -688,7 +674,6 @@ export function planHost({ platform, env, argv, root, home, fs, wsl = false, str
688
674
  muDir: launch.muDir,
689
675
  appDir: launch.appDir,
690
676
  agentDir: launch.agentDir,
691
- startJudge: launch.startJudge,
692
677
  startMude,
693
678
  notes: launch.notes,
694
679
  };
@@ -712,10 +697,6 @@ export function planHost({ platform, env, argv, root, home, fs, wsl = false, str
712
697
  };
713
698
  }
714
699
 
715
- /** What mu says when the local judge does not come up. */
716
- const LOCAL_JUDGE_NOT_STARTED =
717
- "mu: the local judge (Laya) didn't start; decision points use the next judge or their fallbacks. Run mu judge status for details.";
718
-
719
700
  /** The part of the preparation that is files: the app view of a checkout, mu's agent folder, the configuration's notes. */
720
701
  function prepareFiles({ plan, platform, root, err }) {
721
702
  const path = pathFor(platform);
@@ -728,26 +709,19 @@ function prepareFiles({ plan, platform, root, err }) {
728
709
 
729
710
  /**
730
711
  * What is done before pi starts, for the command line and the desktop app's runtime host alike: the app view that
731
- * names a checkout mu, mu's agent folder, the configuration's notes (to `err`), and the local judge when the
732
- * configuration asks for it. `bin` is this launcher's folder. The local judge is one shared sidecar: starting it is
733
- * idempotent and never blocks pi.
712
+ * names a checkout mu, mu's agent folder, the configuration's notes (to `err`), and the server of the decision model
713
+ * the configuration asks for (mude 1.0, or one added from Hugging Face), started in the background. Nothing here waits
714
+ * for a judge. `bin` (this launcher's folder) is still taken, for the hosts that pass it.
734
715
  */
735
- export function prepareLaunch({ plan, platform, root, bin, err }) {
716
+ export function prepareLaunch({ plan, platform, root, err }) {
736
717
  prepareFiles({ plan, platform, root, err });
737
718
  startMudeServer(plan);
738
- if (plan.startJudge) {
739
- const started = spawnSync(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
740
- stdio: "ignore",
741
- env: plan.env,
742
- });
743
- if (started.status !== 0) err(LOCAL_JUDGE_NOT_STARTED);
744
- }
745
719
  }
746
720
 
747
721
  /**
748
722
  * mude's server, or an added model's, started in the background (`mu judge mude start --no-wait`): nothing waits for
749
- * it, and it outlives this process, shared by every mu session like Laya. When it is not installed it does nothing (the
750
- * doctor says so).
723
+ * it, and it outlives this process, shared by every mu session. When it is not installed it does nothing (the doctor
724
+ * says so). Until it answers, the judges behind it do: its address refuses the connection at once.
751
725
  */
752
726
  function startMudeServer(plan) {
753
727
  if (!plan.startMude) return;
@@ -765,22 +739,11 @@ function startMudeServer(plan) {
765
739
  }
766
740
 
767
741
  /**
768
- * prepareLaunch for a program that must not stop while the local judge starts, such as the desktop app's main process:
769
- * the same steps and notes, with the local judge started through a child process it waits for without blocking.
742
+ * prepareLaunch for a program that must not stop, such as the desktop app's main process. It is prepareLaunch: it
743
+ * starts nothing it waits for. It stays a promise for the hosts that await it.
770
744
  */
771
- export async function prepareLaunchAsync({ plan, platform, root, bin, err }) {
772
- prepareFiles({ plan, platform, root, err });
773
- startMudeServer(plan);
774
- if (!plan.startJudge) return;
775
- const status = await new Promise((resolve) => {
776
- const child = spawn(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
777
- stdio: "ignore",
778
- env: plan.env,
779
- });
780
- child.once("error", () => resolve(null));
781
- child.once("close", (code) => resolve(code));
782
- });
783
- if (status !== 0) err(LOCAL_JUDGE_NOT_STARTED);
745
+ export async function prepareLaunchAsync(input) {
746
+ prepareLaunch(input);
784
747
  }
785
748
 
786
749
  // ---------------------------------------------------------------------------------------------------------
@@ -847,13 +810,38 @@ export function planAuth({ platform, env, argv, root, home, execPath, fs, canExe
847
810
  // mu judge
848
811
  // ---------------------------------------------------------------------------------------------------------
849
812
 
850
- /** What `mu judge <cmd>` does here: the macOS script, a look at a server of one's own, or a plain explanation. */
851
- export function planJudge({ platform, env, argv, bin, wsl = false }) {
852
- const args = argv.length > 0 ? argv : ["status"];
853
- const support = localJudgeSupport({ platform, env, wsl });
854
- if (support.runs) return { kind: "script", command: pathFor(platform).join(bin, "kyrn-judge-local"), args };
855
- if (args[0] === "status" && support.url) return { kind: "health", url: `${support.url.replace(/\/+$/, "")}/health` };
856
- return { kind: "unsupported", message: support.message };
813
+ /** `mu judge help`. `mu judge mude …` and `mu judge model …` have their own (packages/kyrn-judge/src/mude). */
814
+ export const JUDGE_USAGE = [
815
+ "mu judge: the decision model that answers mu's judgment calls on this machine.",
816
+ "",
817
+ " mu judge mude <cmd> mude 1.0, mu's own decision model for coding (a Mac with Apple silicon):",
818
+ " install | enable | disable | start | stop | status | uninstall (mu judge mude help)",
819
+ " mu judge model <cmd> other decision models from Hugging Face: add <link> | list | use | remove (mu judge model help)",
820
+ "",
821
+ "Hosted judges (Jev and others) are set with mu setup, or in ~/.mu/agent/mu.json.",
822
+ ].join("\n");
823
+
824
+ /** The commands that ran Laya's sidecar until mu 0.2.4. */
825
+ const LAYA_COMMANDS = ["setup", "start", "stop", "status", "run"];
826
+
827
+ /**
828
+ * What `mu judge <cmd>` does, besides `mude` and `model`: its usage, or a plain explanation. Laya, which `mu judge
829
+ * setup | start | stop | status | run` ran, is no longer built into mu.
830
+ */
831
+ export function planJudge({ argv }) {
832
+ const [command] = argv;
833
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") return { kind: "usage", code: 0, text: JUDGE_USAGE };
834
+ if (LAYA_COMMANDS.includes(command)) {
835
+ return {
836
+ kind: "removed",
837
+ code: 1,
838
+ text: [
839
+ `mu judge ${command} managed Laya, which is no longer built into mu.`,
840
+ "For a decision model on this machine, install mude 1.0 (mu judge mude install), or add one from Hugging Face (mu judge model add <link>).",
841
+ ].join("\n"),
842
+ };
843
+ }
844
+ return { kind: "unknown", code: 2, text: `Unknown command: mu judge ${command}\n\n${JUDGE_USAGE}` };
857
845
  }
858
846
 
859
847
  // ---------------------------------------------------------------------------------------------------------
@@ -1661,20 +1649,10 @@ export async function main(argv = process.argv.slice(2)) {
1661
1649
  return handOver({ command: plan.command, args: plan.args, env: plan.env, strategy: "spawn" });
1662
1650
  }
1663
1651
  if (command === "judge") {
1664
- const plan = planJudge({ platform, env, argv: rest, bin, wsl });
1665
- if (plan.kind === "script") return handOver({ command: plan.command, args: plan.args, env, strategy });
1666
- if (plan.kind === "unsupported") {
1667
- err(plan.message);
1668
- return 1;
1669
- }
1670
- try {
1671
- const response = await fetch(plan.url, { signal: AbortSignal.timeout(2000) });
1672
- out(await response.text());
1673
- return 0;
1674
- } catch {
1675
- out(`The local judge server isn't answering at ${plan.url}. Check that it's running.`);
1676
- return 1;
1677
- }
1652
+ const plan = planJudge({ argv: rest });
1653
+ if (plan.code === 0) out(plan.text);
1654
+ else err(plan.text);
1655
+ return plan.code;
1678
1656
  }
1679
1657
  if (command === "ledger") {
1680
1658
  return handOver({ command: process.execPath, args: [path.join(bin, "kyrn-ledger"), ...rest], env, strategy });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "mu: a coding agent that hands its routine decisions to a small, fast judge model, so the main model can keep its attention on the work. Built on pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,6 +42,6 @@
42
42
  "muBuild": {
43
43
  "pi": "1.3.0",
44
44
  "judge": "0.1.0",
45
- "commit": "d9f6ebc2c"
45
+ "commit": "b7733c26e"
46
46
  }
47
47
  }
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Manage the mu local judge sidecar (Laya on Core ML, Apple silicon only).
3
- #
4
- # kyrn/bin/kyrn-judge-local setup create the venv, install pinned deps, download the weights (~680 MB)
5
- # kyrn/bin/kyrn-judge-local start start in the background if it is not already running
6
- # kyrn/bin/kyrn-judge-local stop
7
- # kyrn/bin/kyrn-judge-local status
8
- # kyrn/bin/kyrn-judge-local run run in the foreground
9
- #
10
- # One sidecar serves every mu session on this machine. State lives in ~/.mu/local-judge
11
- # (~/.kyrn/local-judge on a machine whose home has not been moved yet).
12
- set -euo pipefail
13
-
14
- ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
15
- HOME_DIR="$ROOT/kyrn/local-judge"
16
- MODEL_REPO="aac6fef/laya-multilingual-coreml"
17
- if [[ -d "$HOME/.mu" || ! -d "$HOME/.kyrn" ]]; then MU_DIR="$HOME/.mu"; else MU_DIR="$HOME/.kyrn"; fi
18
- RUN_DIR="${MU_LOCAL_JUDGE_RUN_DIR:-${KYRN_LOCAL_JUDGE_RUN_DIR:-$MU_DIR/local-judge}}"
19
- # A checkout keeps the venv and the weights beside server.py. The npm package (mu-agent) keeps them in mu's
20
- # home instead: npm replaces the whole package folder on every update.
21
- if [[ -d "$ROOT/packages/kyrn-judge" ]]; then STATE_DIR="$HOME_DIR"; else STATE_DIR="$RUN_DIR"; fi
22
- PYTHON="$STATE_DIR/.venv/bin/python"
23
- MODEL_DIR="${MU_LOCAL_JUDGE_MODEL:-${KYRN_LOCAL_JUDGE_MODEL:-$STATE_DIR/models/laya-multilingual-coreml}}"
24
- PORT="${MU_LOCAL_JUDGE_PORT:-${KYRN_LOCAL_JUDGE_PORT:-47823}}"
25
- PID_FILE="$RUN_DIR/judge.pid"
26
- LOG_FILE="$RUN_DIR/judge.log"
27
- URL="http://127.0.0.1:$PORT"
28
-
29
- health() { curl -s --max-time 2 "$URL/health" 2>/dev/null || true; }
30
-
31
- is_ready() { [[ "$(health)" == *'"status": "ok"'* ]]; }
32
-
33
- is_up() { [[ "$(health)" == *'"status"'* ]]; }
34
-
35
- require_setup() {
36
- if [[ ! -x "$PYTHON" || ! -f "$MODEL_DIR/coreml_config.json" ]]; then
37
- echo "The local judge isn't installed yet. Run: mu judge setup" >&2
38
- exit 1
39
- fi
40
- }
41
-
42
- cmd_setup() {
43
- if [[ "$(uname -s)-$(uname -m)" != "Darwin-arm64" ]]; then
44
- echo "The local judge needs a Mac with Apple silicon and macOS 15 or later." >&2
45
- exit 1
46
- fi
47
- command -v uv >/dev/null || { echo "mu judge setup needs uv: https://docs.astral.sh/uv/" >&2; exit 1; }
48
- mkdir -p "$STATE_DIR"
49
- [[ -x "$PYTHON" ]] || uv venv --python 3.12 "$STATE_DIR/.venv"
50
- # The official index on purpose: mirrors lag behind new releases.
51
- uv pip install --python "$PYTHON" --index-url https://pypi.org/simple -r "$HOME_DIR/requirements.lock.txt"
52
- if [[ ! -f "$MODEL_DIR/coreml_config.json" ]]; then
53
- "$PYTHON" - "$MODEL_REPO" "$MODEL_DIR" <<'PY'
54
- import sys
55
- from huggingface_hub import snapshot_download
56
-
57
- snapshot_download(
58
- sys.argv[1],
59
- local_dir=sys.argv[2],
60
- allow_patterns=[
61
- "coreml_config.json", "rl_agent_config.json", "encoder/config.json", "tokenizer/*",
62
- "model.mlpackage/**", "host_weights.safetensors", "README.md", "LICENSE*", "NOTICE*",
63
- ],
64
- )
65
- PY
66
- fi
67
- "$PYTHON" - "$MODEL_DIR" <<'PY'
68
- import json, sys
69
- from pathlib import Path
70
- from laya_coreml.artifacts import verify_files
71
-
72
- directory = Path(sys.argv[1])
73
- manifest = json.loads((directory / "coreml_config.json").read_text())
74
- verify_files(directory, manifest["files"])
75
- print(f"Weights checked against the bundle's manifest ({len(manifest['files'])} files).")
76
- PY
77
- }
78
-
79
- cmd_run() {
80
- require_setup
81
- exec "$PYTHON" "$HOME_DIR/server.py" --model-dir "$MODEL_DIR" --port "$PORT" "$@"
82
- }
83
-
84
- cmd_start() {
85
- require_setup
86
- if is_up; then
87
- echo "The local judge is already running at $URL."
88
- return 0
89
- fi
90
- mkdir -p "$RUN_DIR"
91
- nohup "$PYTHON" "$HOME_DIR/server.py" --model-dir "$MODEL_DIR" --port "$PORT" >>"$LOG_FILE" 2>&1 &
92
- echo $! >"$PID_FILE"
93
- for _ in $(seq 1 120); do
94
- if is_ready; then
95
- echo "The local judge is ready at $URL (pid $(cat "$PID_FILE"), log $LOG_FILE)."
96
- return 0
97
- fi
98
- if ! kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
99
- echo "The local judge stopped while starting; see $LOG_FILE" >&2
100
- tail -5 "$LOG_FILE" >&2 || true
101
- return 1
102
- fi
103
- sleep 0.5
104
- done
105
- echo "The local judge is still warming up; see $LOG_FILE" >&2
106
- }
107
-
108
- cmd_stop() {
109
- if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
110
- kill "$(cat "$PID_FILE")"
111
- echo "Stopped the local judge (pid $(cat "$PID_FILE"))."
112
- else
113
- echo "The local judge isn't running."
114
- fi
115
- rm -f "$PID_FILE"
116
- }
117
-
118
- cmd_status() {
119
- local body
120
- body="$(health)"
121
- if [[ -z "$body" ]]; then echo "The local judge isn't running at $URL."; return 1; fi
122
- # --json: the server's own answer, for scripts.
123
- if [[ "${1:-}" == "--json" ]]; then echo "$body"; return 0; fi
124
- URL="$URL" node -e '
125
- let text = "";
126
- process.stdin.on("data", (chunk) => (text += chunk)).on("end", () => {
127
- let health;
128
- try { health = JSON.parse(text); } catch { console.log(text.trim()); return; }
129
- const up = Math.round((health.uptimeSeconds ?? 0) / 60);
130
- const uptime = up < 60 ? `${up} min` : `${Math.floor(up / 60)} h ${up % 60} min`;
131
- const state = health.status === "ok" ? "running" : `starting (${health.status})`;
132
- const model = String(health.model ?? "").split("/").pop();
133
- console.log(`The local judge is ${state} at ${process.env.URL}: ${model} on ${health.computeUnits}, pid ${health.pid}, up ${uptime}, ${health.requests} requests.`);
134
- });' <<<"$body"
135
- }
136
-
137
- usage() {
138
- cat <<'USAGE'
139
- mu judge: the local judge, Laya, on Core ML (macOS on Apple silicon).
140
-
141
- mu judge setup install it: a Python environment and the weights (about 680 MB)
142
- mu judge start start it in the background, unless it's already running
143
- mu judge stop stop it
144
- mu judge status [--json] whether it's running, and what it serves
145
- mu judge run run it in the foreground
146
-
147
- One local judge serves every mu session on this machine. To have mu ask it, add "laya" to "tiers" in
148
- mu.json, or start mu with MU_JUDGE=laya.
149
- USAGE
150
- }
151
-
152
- case "${1:-status}" in
153
- setup) cmd_setup ;;
154
- run) shift; cmd_run "$@" ;;
155
- start) cmd_start ;;
156
- stop) cmd_stop ;;
157
- status) shift; cmd_status "$@" ;;
158
- help | -h | --help) usage ;;
159
- *) usage >&2; exit 2 ;;
160
- esac
@@ -1,26 +0,0 @@
1
- anyio==4.15.1
2
- attrs==26.1.0
3
- cattrs==26.2.0
4
- certifi==2026.7.22
5
- click==8.5.0
6
- coremltools==9.0
7
- filelock==4.0.1
8
- fsspec==2026.9.0
9
- h11==0.16.0
10
- hf-xet==1.6.0
11
- httpcore==1.0.9
12
- httpx==0.28.1
13
- huggingface-hub==1.32.0
14
- idna==3.20
15
- laya-coreml==0.1.0
16
- mpmath==1.3.0
17
- numpy==2.1.3
18
- packaging==26.3
19
- protobuf==7.36.2
20
- pyaml==26.7.0
21
- pyyaml==6.0.3
22
- safetensors==0.8.0
23
- sympy==1.14.0
24
- tokenizers==0.23.2
25
- tqdm==4.70.1
26
- typing-extensions==4.16.0