mu-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -0
  4. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
  5. package/dist/bundle/chunks/anthropic.js +75 -0
  6. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +2 -0
  7. package/dist/bundle/chunks/bedrock-converse-stream.js +83 -0
  8. package/dist/bundle/chunks/chunk-4L3WN2XY.js +2 -0
  9. package/dist/bundle/chunks/chunk-6CYCI3EJ.js +12 -0
  10. package/dist/bundle/chunks/chunk-6R7UI4IY.js +2 -0
  11. package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  12. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  13. package/dist/bundle/chunks/chunk-DRQDKNXA.js +2 -0
  14. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  15. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  16. package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
  17. package/dist/bundle/chunks/chunk-NUHFSC37.js +25 -0
  18. package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
  19. package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
  20. package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
  21. package/dist/bundle/chunks/chunk-QB4HYZZE.js +1561 -0
  22. package/dist/bundle/chunks/chunk-RCZIEVGO.js +11 -0
  23. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  24. package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
  25. package/dist/bundle/chunks/chunk-TRDNDS6A.js +4 -0
  26. package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  27. package/dist/bundle/chunks/chunk-WIUZI2CJ.js +11 -0
  28. package/dist/bundle/chunks/chunk-XDUWOHPL.js +6 -0
  29. package/dist/bundle/chunks/chunk-XNGRGP62.js +2 -0
  30. package/dist/bundle/chunks/chunk-Z2DUOAN4.js +2 -0
  31. package/dist/bundle/chunks/github-copilot.js +2 -0
  32. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  33. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  34. package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
  35. package/dist/bundle/chunks/image-resize-worker.js +2 -0
  36. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  37. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  38. package/dist/bundle/chunks/kimi-coding.js +2 -0
  39. package/dist/bundle/chunks/lib-HGQMNYVT.js +9 -0
  40. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  41. package/dist/bundle/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  42. package/dist/bundle/chunks/node-O3RIBJRH.js +16 -0
  43. package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
  44. package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
  45. package/dist/bundle/chunks/openai-codex.js +75 -0
  46. package/dist/bundle/chunks/openai-completions-KQPOPEVM.js +7 -0
  47. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
  48. package/dist/bundle/chunks/openrouter-images-N4XVVFSD.js +2 -0
  49. package/dist/bundle/chunks/openrouter.js +75 -0
  50. package/dist/bundle/chunks/pi-messages-GFKBWJHZ.js +8 -0
  51. package/dist/bundle/chunks/radius.js +75 -0
  52. package/dist/bundle/chunks/src-UK77SYZE.js +4 -0
  53. package/dist/bundle/chunks/virtual-modules-ROULVYS3.js +2 -0
  54. package/dist/bundle/chunks/xai.js +2 -0
  55. package/dist/bundle/cli.js +3 -0
  56. package/dist/bundle/index.js +2 -0
  57. package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  58. package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  59. package/dist/bundle/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  60. package/dist/bundle/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  61. package/dist/bundle/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  62. package/dist/bundle/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  63. package/dist/bundle/rpc-entry.js +3 -0
  64. package/dist/core/export-html/template.css +1066 -0
  65. package/dist/core/export-html/template.html +55 -0
  66. package/dist/core/export-html/template.js +1864 -0
  67. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  68. package/dist/core/export-html/vendor/marked.min.js +78 -0
  69. package/dist/modes/interactive/assets/clankolas.png +0 -0
  70. package/dist/modes/interactive/theme/dark.json +91 -0
  71. package/dist/modes/interactive/theme/light.json +90 -0
  72. package/dist/modes/interactive/theme/theme-controller.d.ts +41 -0
  73. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -0
  74. package/dist/modes/interactive/theme/theme-controller.js +130 -0
  75. package/dist/modes/interactive/theme/theme-controller.js.map +1 -0
  76. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  77. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  78. package/dist/modes/interactive/theme/theme-json.js +130 -0
  79. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  80. package/dist/modes/interactive/theme/theme-schema.json +356 -0
  81. package/dist/modes/interactive/theme/theme.d.ts +131 -0
  82. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  83. package/dist/modes/interactive/theme/theme.js +991 -0
  84. package/dist/modes/interactive/theme/theme.js.map +1 -0
  85. package/dist/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  86. package/dist/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  87. package/dist/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  88. package/dist/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  89. package/dist/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  90. package/dist/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  91. package/docs/compaction.md +444 -0
  92. package/docs/containerization.md +156 -0
  93. package/docs/custom-provider.md +786 -0
  94. package/docs/development.md +90 -0
  95. package/docs/docs.json +160 -0
  96. package/docs/environment-variables.md +100 -0
  97. package/docs/extensions.md +3037 -0
  98. package/docs/images/doom-extension.png +0 -0
  99. package/docs/images/exy.png +0 -0
  100. package/docs/images/interactive-mode.png +0 -0
  101. package/docs/images/tree-view.png +0 -0
  102. package/docs/index.md +84 -0
  103. package/docs/json.md +98 -0
  104. package/docs/keybindings.md +239 -0
  105. package/docs/llama-cpp.md +101 -0
  106. package/docs/models.md +605 -0
  107. package/docs/packages.md +228 -0
  108. package/docs/prompt-templates.md +96 -0
  109. package/docs/providers.md +317 -0
  110. package/docs/quickstart.md +167 -0
  111. package/docs/rpc.md +1618 -0
  112. package/docs/sdk.md +1226 -0
  113. package/docs/security.md +59 -0
  114. package/docs/session-format.md +480 -0
  115. package/docs/sessions.md +172 -0
  116. package/docs/settings.md +428 -0
  117. package/docs/shell-aliases.md +13 -0
  118. package/docs/skills.md +232 -0
  119. package/docs/terminal-setup.md +192 -0
  120. package/docs/termux.md +126 -0
  121. package/docs/themes.md +322 -0
  122. package/docs/tmux.md +63 -0
  123. package/docs/tui.md +961 -0
  124. package/docs/usage.md +312 -0
  125. package/docs/windows.md +39 -0
  126. package/examples/README.md +28 -0
  127. package/examples/extensions/README.md +212 -0
  128. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  129. package/examples/extensions/bash-spawn-hook.ts +30 -0
  130. package/examples/extensions/bookmark.ts +50 -0
  131. package/examples/extensions/border-status-editor.ts +150 -0
  132. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  133. package/examples/extensions/claude-rules.ts +86 -0
  134. package/examples/extensions/commands.ts +72 -0
  135. package/examples/extensions/confirm-destructive.ts +59 -0
  136. package/examples/extensions/custom-compaction.ts +117 -0
  137. package/examples/extensions/custom-footer.ts +64 -0
  138. package/examples/extensions/custom-header.ts +73 -0
  139. package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
  140. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  141. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  142. package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
  143. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  144. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  145. package/examples/extensions/dirty-repo-guard.ts +56 -0
  146. package/examples/extensions/doom-overlay/README.md +46 -0
  147. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  148. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  149. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  150. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  151. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  152. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  153. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  154. package/examples/extensions/doom-overlay/index.ts +74 -0
  155. package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
  156. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  157. package/examples/extensions/dynamic-resources/dynamic.json +81 -0
  158. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  159. package/examples/extensions/dynamic-resources/index.ts +15 -0
  160. package/examples/extensions/dynamic-tools.ts +74 -0
  161. package/examples/extensions/entry-renderer.ts +41 -0
  162. package/examples/extensions/event-bus.ts +43 -0
  163. package/examples/extensions/file-trigger.ts +41 -0
  164. package/examples/extensions/git-checkpoint.ts +53 -0
  165. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  166. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  167. package/examples/extensions/gondolin/index.ts +531 -0
  168. package/examples/extensions/gondolin/package-lock.json +185 -0
  169. package/examples/extensions/gondolin/package.json +19 -0
  170. package/examples/extensions/handoff.ts +190 -0
  171. package/examples/extensions/hello.ts +26 -0
  172. package/examples/extensions/hidden-thinking-label.ts +53 -0
  173. package/examples/extensions/inline-bash.ts +94 -0
  174. package/examples/extensions/input-transform-streaming.ts +39 -0
  175. package/examples/extensions/input-transform.ts +43 -0
  176. package/examples/extensions/interactive-shell.ts +196 -0
  177. package/examples/extensions/mac-system-theme.ts +47 -0
  178. package/examples/extensions/message-renderer.ts +59 -0
  179. package/examples/extensions/minimal-mode.ts +426 -0
  180. package/examples/extensions/modal-editor.ts +85 -0
  181. package/examples/extensions/model-status.ts +31 -0
  182. package/examples/extensions/notify.ts +57 -0
  183. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  184. package/examples/extensions/overlay-test.ts +153 -0
  185. package/examples/extensions/permission-gate.ts +34 -0
  186. package/examples/extensions/pirate.ts +47 -0
  187. package/examples/extensions/plan-mode/README.md +66 -0
  188. package/examples/extensions/plan-mode/index.ts +390 -0
  189. package/examples/extensions/plan-mode/utils.ts +168 -0
  190. package/examples/extensions/preset.ts +436 -0
  191. package/examples/extensions/project-trust.ts +64 -0
  192. package/examples/extensions/prompt-customizer.ts +49 -0
  193. package/examples/extensions/protected-paths.ts +30 -0
  194. package/examples/extensions/provider-payload.ts +18 -0
  195. package/examples/extensions/qna.ts +118 -0
  196. package/examples/extensions/question.ts +286 -0
  197. package/examples/extensions/questionnaire.ts +448 -0
  198. package/examples/extensions/rainbow-editor.ts +88 -0
  199. package/examples/extensions/reload-runtime.ts +37 -0
  200. package/examples/extensions/rpc-demo.ts +118 -0
  201. package/examples/extensions/sandbox/index.ts +321 -0
  202. package/examples/extensions/sandbox/package-lock.json +92 -0
  203. package/examples/extensions/sandbox/package.json +19 -0
  204. package/examples/extensions/send-user-message.ts +97 -0
  205. package/examples/extensions/session-name.ts +27 -0
  206. package/examples/extensions/shutdown-command.ts +63 -0
  207. package/examples/extensions/snake.ts +343 -0
  208. package/examples/extensions/space-invaders.ts +560 -0
  209. package/examples/extensions/ssh.ts +220 -0
  210. package/examples/extensions/status-line.ts +32 -0
  211. package/examples/extensions/structured-output.ts +65 -0
  212. package/examples/extensions/subagent/README.md +177 -0
  213. package/examples/extensions/subagent/agents/planner.md +37 -0
  214. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  215. package/examples/extensions/subagent/agents/scout.md +50 -0
  216. package/examples/extensions/subagent/agents/worker.md +24 -0
  217. package/examples/extensions/subagent/agents.ts +157 -0
  218. package/examples/extensions/subagent/index.ts +1038 -0
  219. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  220. package/examples/extensions/subagent/prompts/implement.md +10 -0
  221. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  222. package/examples/extensions/summarize.ts +199 -0
  223. package/examples/extensions/system-prompt-header.ts +17 -0
  224. package/examples/extensions/tic-tac-toe.ts +1008 -0
  225. package/examples/extensions/timed-confirm.ts +70 -0
  226. package/examples/extensions/titlebar-spinner.ts +58 -0
  227. package/examples/extensions/todo.ts +297 -0
  228. package/examples/extensions/tool-override.ts +144 -0
  229. package/examples/extensions/tools.ts +146 -0
  230. package/examples/extensions/trigger-compact.ts +50 -0
  231. package/examples/extensions/truncated-tool.ts +195 -0
  232. package/examples/extensions/widget-placement.ts +9 -0
  233. package/examples/extensions/with-deps/index.ts +32 -0
  234. package/examples/extensions/with-deps/package-lock.json +31 -0
  235. package/examples/extensions/with-deps/package.json +22 -0
  236. package/examples/extensions/working-indicator.ts +123 -0
  237. package/examples/extensions/working-message-test.ts +25 -0
  238. package/examples/plugins/pi-example-plugin/README.md +38 -0
  239. package/examples/plugins/pi-example-plugin/package.json +10 -0
  240. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  241. package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
  242. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  243. package/examples/rpc-extension-ui.ts +641 -0
  244. package/examples/sdk/01-minimal.ts +26 -0
  245. package/examples/sdk/02-custom-model.ts +49 -0
  246. package/examples/sdk/03-custom-prompt.ts +75 -0
  247. package/examples/sdk/04-skills.ts +55 -0
  248. package/examples/sdk/05-tools.ts +48 -0
  249. package/examples/sdk/06-extensions.ts +99 -0
  250. package/examples/sdk/07-context-files.ts +47 -0
  251. package/examples/sdk/08-prompt-templates.ts +51 -0
  252. package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  253. package/examples/sdk/10-settings.ts +53 -0
  254. package/examples/sdk/11-sessions.ts +52 -0
  255. package/examples/sdk/12-full-control.ts +74 -0
  256. package/examples/sdk/13-session-runtime.ts +67 -0
  257. package/examples/sdk/README.md +140 -0
  258. package/judge/THIRD_PARTY_NOTICES.md +35 -0
  259. package/judge/agents/browser.md +17 -0
  260. package/judge/agents/investigator.md +16 -0
  261. package/judge/agents/planner.md +15 -0
  262. package/judge/agents/reviewer.md +17 -0
  263. package/judge/agents/scout.md +19 -0
  264. package/judge/agents/worker.md +15 -0
  265. package/judge/dist/chrome.js +326 -0
  266. package/judge/dist/kyrn-judge.js +52542 -0
  267. package/judge/package.json +6 -0
  268. package/judge/prompts/implement-and-review.md +14 -0
  269. package/judge/prompts/implement.md +15 -0
  270. package/judge/prompts/init.md +18 -0
  271. package/judge/prompts/scout-and-plan.md +12 -0
  272. package/judge/skills/mu-browser/SKILL.md +30 -0
  273. package/judge/skills/mu-github/SKILL.md +90 -0
  274. package/kyrn/bin/kyrn-doctor +202 -0
  275. package/kyrn/bin/kyrn-judge-local +131 -0
  276. package/kyrn/bin/kyrn-ledger +90 -0
  277. package/kyrn/bin/mu.mjs +1120 -0
  278. package/kyrn/local-judge/requirements.lock.txt +26 -0
  279. package/kyrn/local-judge/server.py +332 -0
  280. package/package.json +45 -0
@@ -0,0 +1,26 @@
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
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env python3
2
+ """mu local judge sidecar.
3
+
4
+ Serves a Laya Core ML checkpoint (https://github.com/mizorewww/laya-coreml) over
5
+ the same wire contract as the AI Gateway `evaluation-model` endpoint, so the
6
+ mu kernel can swap Jev for a local model without translating anything:
7
+
8
+ POST /evaluate {"state": ..., "questions": {id: {type, instructions, criteria?}}}
9
+ -> {"answers": {...}, "usage": {...}, "warnings": [...], "providerMetadata": {"laya": {...}}}
10
+ GET /health -> {"status": "ok" | "warming", ...}
11
+
12
+ Question types use the gateway names: `boolean` (Laya calls it `noul`), `choice`, `score`.
13
+
14
+ The server binds to loopback only, never logs state or question text, and keeps
15
+ the model behind one lock because a Core ML model is not safe to call concurrently.
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import sys
22
+ import threading
23
+ import time
24
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
25
+ from pathlib import Path
26
+
27
+ DEFAULT_HOST = "127.0.0.1"
28
+ DEFAULT_PORT = 47823
29
+ MAX_BODY_BYTES = 1024 * 1024
30
+ # Measured on an M3 Max with 10-question preflights: no floor p50 411 ms (one shape switch per request),
31
+ # 256 -> 124 ms, 384 -> 155 ms. Requests longer than the floor still work; they just pay the switch.
32
+ DEFAULT_MIN_LENGTH = 256
33
+ DEFAULT_MODEL_DIR = Path(__file__).resolve().parent / "models" / "laya-multilingual-coreml"
34
+
35
+
36
+ class BadRequest(ValueError):
37
+ pass
38
+
39
+
40
+ def setting(name, default):
41
+ """`MU_<name>`, or the `KYRN_<name>` spelling from before the rename."""
42
+ return os.environ.get(f"MU_{name}") or os.environ.get(f"KYRN_{name}") or default
43
+
44
+
45
+ def _text(value):
46
+ """Laya serializes non-string instructions with ASCII escapes, which mangles CJK. Do it here instead."""
47
+ return value if isinstance(value, str) else json.dumps(value, ensure_ascii=False)
48
+
49
+
50
+ def to_laya_question(question_id, question):
51
+ if not isinstance(question, dict):
52
+ raise BadRequest(f'Question "{question_id}" must be an object')
53
+ kind = question.get("type")
54
+ if "instructions" not in question:
55
+ raise BadRequest(f'Question "{question_id}" is missing instructions')
56
+ instructions = _text(question["instructions"])
57
+ criteria = question.get("criteria")
58
+
59
+ if kind == "boolean":
60
+ out = {"type": "noul", "instructions": instructions}
61
+ if isinstance(criteria, dict):
62
+ kept = {key: criteria[key] for key in ("false", "true") if criteria.get(key) not in (None, "")}
63
+ if kept:
64
+ out["criteria"] = kept
65
+ return out
66
+ if kind == "choice":
67
+ if not isinstance(criteria, dict) or not criteria:
68
+ raise BadRequest(f'Choice question "{question_id}" needs a non-empty criteria object')
69
+ return {"type": "choice", "instructions": instructions, "criteria": dict(criteria)}
70
+ if kind == "score":
71
+ if not isinstance(criteria, list) or len(criteria) < 2:
72
+ raise BadRequest(f'Score question "{question_id}" needs at least two levels')
73
+ return {
74
+ "type": "score",
75
+ "instructions": instructions,
76
+ "criteria": ["" if level is None else level for level in criteria],
77
+ }
78
+ raise BadRequest(f'Question "{question_id}" has unknown type {kind!r}; expected boolean, choice or score')
79
+
80
+
81
+ def from_laya_answer(answer):
82
+ kind = answer["type"]
83
+ if kind == "noul":
84
+ return {"type": "boolean", "probability": answer["noul"]}
85
+ if kind == "choice":
86
+ return {"type": "choice", "choice": answer["choice"], "probabilities": answer["probabilities"]}
87
+ return {"type": "score", "score": answer["score"], "probabilities": answer["probabilities"]}
88
+
89
+
90
+ class LocalJudge:
91
+ def __init__(self, model_dir, compute_units=None, min_length=0):
92
+ import laya_coreml as laya
93
+
94
+ self.model_dir = Path(model_dir)
95
+ self.min_length = min_length
96
+ started = time.perf_counter()
97
+ self.agent = laya.load(str(self.model_dir), local_files_only=True, compute_units=compute_units)
98
+ self.load_seconds = time.perf_counter() - started
99
+ self.lock = threading.Lock()
100
+ self.warm = False
101
+ self.requests = 0
102
+ self.started_at = time.time()
103
+ manifest = getattr(self.agent, "manifest", {})
104
+ self.model_id = "laya:" + str(manifest.get("repository") or self.model_dir.name)
105
+ self.shape = getattr(self.agent, "shape", {})
106
+
107
+ def uniform_shape(self, state, laya_questions):
108
+ """Pad every question of a request to one input length.
109
+
110
+ Core ML re-specializes the GPU program whenever the enumerated length
111
+ changes (about 350 ms on an M3 Max, against 40 ms for a steady shape),
112
+ and the questions of one request differ in length because their
113
+ options do. One bucket per request removes those switches; the floor
114
+ keeps consecutive requests on the same bucket too.
115
+ """
116
+ lengths = self.agent.shape.get("lengths")
117
+ if not lengths:
118
+ return self.agent.shape
119
+ items, _internal = self.agent.prepare(state, laya_questions)
120
+ longest = max(max(len(item["ids"]) for item in items), self.min_length)
121
+ target = next((n for n in lengths if n >= longest), lengths[-1])
122
+ return dict(self.agent.shape, lengths=[target])
123
+
124
+ def diagnostics(self, state, laya_questions):
125
+ """Report what the token budget silently cut. Never raises."""
126
+ warnings = []
127
+ try:
128
+ from laya_coreml.common import build_prefix, render_options, serialize_state
129
+
130
+ tok, cfg = self.agent.tok, self.agent.cfg
131
+ max_len, head_max_len = cfg.get("max_len", 512), cfg.get("head_max_len", 192)
132
+ state_tokens = len(tok(serialize_state(state).replace(tok.mask_token, " "))["input_ids"])
133
+ for question_id, definition in laya_questions.items():
134
+ q = self.agent._to_internal(definition)
135
+ prefix, _markers = build_prefix(tok, q, head_max_len)
136
+ full_head = len(tok("%s question: %s" % (q["t"], q["ins"]))["input_ids"])
137
+ full_options = sum(1 + len(tok(" " + option)["input_ids"]) for option in render_options(q))
138
+ full_prefix = 1 + full_head + 1 + full_options + 1
139
+ if len(prefix) < full_prefix:
140
+ warnings.append(
141
+ {
142
+ "type": "question_truncated",
143
+ "questionId": question_id,
144
+ "message": f"question and options need {full_prefix} tokens, {len(prefix)} were kept",
145
+ }
146
+ )
147
+ room = max(0, max_len - len(prefix) - 1)
148
+ if state_tokens > room:
149
+ warnings.append(
150
+ {
151
+ "type": "state_truncated",
152
+ "questionId": question_id,
153
+ "message": f"state has {state_tokens} tokens, only the first {room} were read",
154
+ }
155
+ )
156
+ except Exception as error: # noqa: BLE001 - diagnostics are best effort
157
+ warnings.append({"type": "diagnostics_failed", "message": type(error).__name__})
158
+ return warnings
159
+
160
+ def evaluate(self, state, questions):
161
+ if not isinstance(questions, dict) or not questions:
162
+ raise BadRequest("questions must be a non-empty object keyed by question id")
163
+ if not isinstance(state, (str, dict, list)):
164
+ raise BadRequest("state must be a string, an object or an array")
165
+ laya_questions = {qid: to_laya_question(qid, q) for qid, q in questions.items()}
166
+
167
+ started = time.perf_counter()
168
+ with self.lock:
169
+ waited = time.perf_counter() - started
170
+ original_shape = self.agent.shape
171
+ try:
172
+ self.agent.shape = self.uniform_shape(state, laya_questions)
173
+ result = self.agent.predict(state, laya_questions)
174
+ except ValueError as error:
175
+ raise BadRequest(str(error)) from error
176
+ finally:
177
+ self.agent.shape = original_shape
178
+ self.requests += 1
179
+ elapsed = time.perf_counter() - started
180
+
181
+ metadata = {
182
+ qid: {"confidence": answer.get("confidence"), "actProbability": answer.get("action", {}).get("act_probability")}
183
+ for qid, answer in result["answers"].items()
184
+ }
185
+ usage = result.get("usage", {})
186
+ print(
187
+ f"[{time.strftime('%H:%M:%S')}] evaluate questions={len(questions)} "
188
+ f"tokens={usage.get('input_tokens', 0)} ms={elapsed * 1000:.0f} waited_ms={waited * 1000:.0f}",
189
+ file=sys.stderr,
190
+ flush=True,
191
+ )
192
+ return {
193
+ "answers": {qid: from_laya_answer(answer) for qid, answer in result["answers"].items()},
194
+ "usage": {"inputTokens": usage.get("input_tokens", 0), "outputTokens": 0},
195
+ "warnings": self.diagnostics(state, laya_questions),
196
+ "providerMetadata": {"laya": {"model": self.model_id, "latencyMs": round(elapsed * 1000, 1), "answers": metadata}},
197
+ }
198
+
199
+ def warm_up(self):
200
+ """Core ML compiles each enumerated input length on first use (seconds). Pay that before real traffic."""
201
+ lengths = list(self.shape.get("lengths") or [self.shape.get("default_length", 128)])
202
+ # Finish on the bucket real traffic starts in, so the first request does not pay a shape switch.
203
+ resting = next((n for n in lengths if n >= self.min_length), lengths[-1])
204
+ lengths = [n for n in lengths if n != resting] + [resting]
205
+ question = {"warm": {"type": "noul", "instructions": "Is this a warm-up request?"}}
206
+ started = time.perf_counter()
207
+ for length in lengths:
208
+ # "a " is roughly one token; the prefix takes about 30, so this lands in the bucket for `length`.
209
+ state = "a " * max(1, length - 40)
210
+ try:
211
+ with self.lock:
212
+ original_shape = self.agent.shape
213
+ self.agent.shape = dict(original_shape, lengths=[length])
214
+ try:
215
+ self.agent.predict(state, question)
216
+ finally:
217
+ self.agent.shape = original_shape
218
+ except Exception as error: # noqa: BLE001 - a failed warm-up only costs latency later
219
+ print(f"warm-up for length {length} failed: {type(error).__name__}", file=sys.stderr, flush=True)
220
+ self.warm = True
221
+ print(f"warm-up finished in {time.perf_counter() - started:.1f}s ({len(lengths)} shapes)", file=sys.stderr, flush=True)
222
+
223
+ def health(self):
224
+ return {
225
+ "status": "ok" if self.warm else "warming",
226
+ "model": self.model_id,
227
+ "computeUnits": getattr(self.agent, "compute_units", None),
228
+ "maxLength": self.shape.get("max_length"),
229
+ "maxOptions": self.shape.get("max_options"),
230
+ "batchSize": self.shape.get("batch_size"),
231
+ "requests": self.requests,
232
+ "uptimeSeconds": round(time.time() - self.started_at),
233
+ "pid": os.getpid(),
234
+ }
235
+
236
+
237
+ def make_handler(judge):
238
+ class Handler(BaseHTTPRequestHandler):
239
+ server_version = "mu-local-judge/0.1"
240
+
241
+ def log_message(self, *_args):
242
+ pass
243
+
244
+ def send_json(self, status, payload):
245
+ body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
246
+ self.send_response(status)
247
+ self.send_header("Content-Type", "application/json; charset=utf-8")
248
+ self.send_header("Content-Length", str(len(body)))
249
+ self.end_headers()
250
+ self.wfile.write(body)
251
+
252
+ def send_failure(self, status, message):
253
+ self.send_json(status, {"error": {"message": message}})
254
+
255
+ def do_GET(self):
256
+ if self.path == "/health":
257
+ self.send_json(200, judge.health())
258
+ else:
259
+ self.send_failure(404, "Not found")
260
+
261
+ def do_POST(self):
262
+ if self.path != "/evaluate":
263
+ return self.send_failure(404, "Not found")
264
+ # Browsers always send Origin on cross-site POSTs; nothing legitimate calls this from a web page.
265
+ if self.headers.get("Origin"):
266
+ return self.send_failure(403, "Browser requests are not allowed")
267
+ if "application/json" not in (self.headers.get("Content-Type") or ""):
268
+ return self.send_failure(415, "Content-Type must be application/json")
269
+ try:
270
+ length = int(self.headers.get("Content-Length") or 0)
271
+ except ValueError:
272
+ return self.send_failure(400, "Invalid Content-Length")
273
+ if length <= 0 or length > MAX_BODY_BYTES:
274
+ return self.send_failure(413, f"Body must be between 1 and {MAX_BODY_BYTES} bytes")
275
+ try:
276
+ payload = json.loads(self.rfile.read(length))
277
+ except (ValueError, UnicodeDecodeError):
278
+ return self.send_failure(400, "Body is not valid JSON")
279
+ if not isinstance(payload, dict):
280
+ return self.send_failure(400, "Body must be a JSON object")
281
+ try:
282
+ self.send_json(200, judge.evaluate(payload.get("state"), payload.get("questions")))
283
+ except BadRequest as error:
284
+ self.send_failure(400, str(error)[:300])
285
+ except Exception as error: # noqa: BLE001 - the kernel fails open on a 500
286
+ print(f"evaluate failed: {type(error).__name__}: {error}", file=sys.stderr, flush=True)
287
+ self.send_failure(500, f"Local judge failed: {type(error).__name__}")
288
+
289
+ return Handler
290
+
291
+
292
+ def main():
293
+ parser = argparse.ArgumentParser(description="mu local judge sidecar (Laya on Core ML)")
294
+ parser.add_argument("--model-dir", default=setting("LOCAL_JUDGE_MODEL", str(DEFAULT_MODEL_DIR)))
295
+ parser.add_argument("--host", default=DEFAULT_HOST)
296
+ parser.add_argument("--port", type=int, default=int(setting("LOCAL_JUDGE_PORT", DEFAULT_PORT)))
297
+ parser.add_argument("--compute-units", choices=["all", "cpu", "cpu_gpu", "cpu_ne"])
298
+ parser.add_argument("--no-warmup", action="store_true")
299
+ parser.add_argument(
300
+ "--min-length",
301
+ type=int,
302
+ default=int(setting("LOCAL_JUDGE_MIN_LENGTH", DEFAULT_MIN_LENGTH)),
303
+ help="Smallest input length to pad to. A floor above the usual request size avoids shape switches between requests.",
304
+ )
305
+ args = parser.parse_args()
306
+
307
+ if args.host not in ("127.0.0.1", "localhost", "::1"):
308
+ parser.error("The local judge has no authentication and only binds to loopback")
309
+
310
+ judge = LocalJudge(args.model_dir, compute_units=args.compute_units, min_length=args.min_length)
311
+ server = ThreadingHTTPServer((args.host, args.port), make_handler(judge))
312
+ server.daemon_threads = True
313
+ print(
314
+ f"mu local judge: {judge.model_id} loaded in {judge.load_seconds:.1f}s, "
315
+ f"listening on http://{args.host}:{args.port}",
316
+ file=sys.stderr,
317
+ flush=True,
318
+ )
319
+ if args.no_warmup:
320
+ judge.warm = True
321
+ else:
322
+ threading.Thread(target=judge.warm_up, daemon=True).start()
323
+ try:
324
+ server.serve_forever()
325
+ except KeyboardInterrupt:
326
+ pass
327
+ finally:
328
+ server.server_close()
329
+
330
+
331
+ if __name__ == "__main__":
332
+ main()
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "mu-agent",
3
+ "version": "0.1.0",
4
+ "description": "mu: a coding agent that thinks before it acts. A small, fast judge makes the routine calls; the big model keeps its attention for the work. Built on pi.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mu": "kyrn/bin/mu.mjs"
8
+ },
9
+ "piConfig": {
10
+ "name": "mu",
11
+ "configDir": ".mu"
12
+ },
13
+ "keywords": [
14
+ "coding-agent",
15
+ "ai",
16
+ "llm",
17
+ "cli",
18
+ "terminal",
19
+ "agent",
20
+ "judge",
21
+ "pi"
22
+ ],
23
+ "homepage": "https://github.com/qybaihe/MU",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/qybaihe/MU.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/qybaihe/MU/issues"
30
+ },
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=22.19.0"
34
+ },
35
+ "dependencies": {
36
+ "@earendil-works/chord": "0.86.0",
37
+ "@silvia-odwyer/photon-node": "0.3.4",
38
+ "jiti": "2.7.0"
39
+ },
40
+ "muBuild": {
41
+ "pi": "0.86.0",
42
+ "judge": "0.1.0",
43
+ "commit": "d5e481d01"
44
+ }
45
+ }