mlx-bun 0.0.4 → 0.0.6
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/README.md +175 -93
- package/docs/dag/training-inference-map.html +575 -0
- package/docs/design/adapters-end-to-end.md +50 -46
- package/docs/design/compat-cli-surface-design.md +10 -8
- package/docs/design/docs-reorg-plan.md +14 -1
- package/docs/design/hlg-sampling.md +12 -2
- package/docs/design/memory-system.md +193 -295
- package/docs/design/optimization_plan.md +4 -2
- package/docs/design/orpo-dynamic-lambda.md +179 -0
- package/docs/design/orpo-future-enhancements.md +340 -0
- package/docs/design/orpo-training.md +1212 -0
- package/docs/design/parallel-slots.md +34 -26
- package/docs/design/parity-tier-dag.md +143 -0
- package/docs/design/runtime-isolation.md +1 -1
- package/docs/design/segmented-backward-training.md +58 -39
- package/docs/design/tauri-desktop-app.md +127 -0
- package/docs/design/w4a16-compute-precision-spike.md +232 -0
- package/docs/investigations/orpo-base-uf-experiment-and-directions.md +120 -0
- package/docs/investigations/orpo-flash-cce-pin-leak.md +167 -0
- package/docs/investigations/orpo-uf-testing-handoff.md +126 -0
- package/docs/{segmented-backward-handoff.md → investigations/segmented-backward-handoff.md} +1 -1
- package/docs/investigations/starter-model-port-handoff.md +32 -13
- package/docs/investigations/steel-flash-cce-handoff.md +489 -0
- package/docs/investigations/trainer-validation-experiment.md +173 -0
- package/docs/planning/IDEAS.md +29 -0
- package/docs/planning/PRODUCT_ROADMAP.md +147 -27
- package/docs/planning/chunk-task-roadmap.md +1 -1
- package/docs/planning/journal.md +161 -0
- package/docs/reference/distribution.md +61 -5
- package/docs/reference/library-api.md +29 -14
- package/docs/reference/memory.md +77 -0
- package/docs/reference/orpo-quickstart.md +130 -0
- package/docs/reference/server-api.md +105 -7
- package/docs/reference/server-config.md +12 -5
- package/docs/reference/training.md +296 -0
- package/package.json +1 -1
- package/src/cli.ts +651 -18
- package/src/eval/ifeval.ts +138 -0
- package/src/eval/runner.ts +76 -6
- package/src/eval/tasks/bfcl.ts +49 -15
- package/src/eval/tasks/gsm8k.ts +21 -9
- package/src/eval/tasks/hashhop.ts +33 -4
- package/src/eval/tasks/humaneval.ts +30 -8
- package/src/eval/tasks/ifeval.ts +52 -18
- package/src/eval/tasks/mmlu.ts +85 -74
- package/src/eval/ultrafeedback.ts +60 -0
- package/src/jobs/runner.ts +6 -0
- package/src/lora.ts +12 -7
- package/src/memory/chunk.ts +25 -0
- package/src/memory/cluster.ts +24 -0
- package/src/memory/db.ts +114 -0
- package/src/memory/ingest.ts +22 -0
- package/src/memory/pipeline.ts +63 -0
- package/src/memory/prompts.ts +23 -0
- package/src/memory/schedule.ts +163 -0
- package/src/memory/synthesize.ts +29 -0
- package/src/memory/tools.ts +399 -0
- package/src/memory/vault.ts +694 -0
- package/src/memory/wikify.ts +23 -0
- package/src/mlx/metal-kernel.ts +31 -4
- package/src/model/flash-attention.ts +37 -19
- package/src/model/fused-geglu-kernel.ts +111 -0
- package/src/model/gemma4-base.ts +39 -1
- package/src/model/gemma4.ts +196 -21
- package/src/model/minicpm5.ts +44 -3
- package/src/pi-provider.ts +29 -13
- package/src/pi-session.ts +81 -0
- package/src/pi-terminal.ts +13 -24
- package/src/pi-web.ts +246 -166
- package/src/quantize/job.ts +17 -2
- package/src/quantize/quantizer.ts +26 -2
- package/src/server.ts +216 -47
- package/src/tokenizer.ts +6 -2
- package/src/train/dataset.ts +29 -6
- package/src/train/flash-cce.ts +1175 -0
- package/src/train/job.ts +64 -3
- package/src/train/lora-params.ts +53 -4
- package/src/train/loss.ts +947 -0
- package/src/train/optimizer.ts +10 -3
- package/src/train/prefix-shared.ts +435 -0
- package/src/train/rank.ts +36 -0
- package/src/train/segmented.ts +1148 -3
- package/src/train/steel-qmm-header.ts +1461 -0
- package/src/train/trainer.ts +570 -54
- package/src/train/watch.ts +542 -0
- package/src/web/app.html +285 -19
- package/src/web/skills/memory/SKILL.md +97 -0
- package/src/web/skills/web-research/SKILL.md +3 -1
- package/src/web/skills.ts +46 -8
- /package/{docs → src/assets}/curve-designer.html +0 -0
package/src/web/app.html
CHANGED
|
@@ -105,6 +105,39 @@
|
|
|
105
105
|
#chat-think.think-on .dot{background:var(--blue);box-shadow:0 0 11px var(--blue)}
|
|
106
106
|
.pill.bad .dot{background:var(--red);box-shadow:0 0 11px var(--red)}
|
|
107
107
|
.pill.warn .dot{background:var(--orange);box-shadow:0 0 11px var(--orange)}
|
|
108
|
+
/* sampling disclosure: a small pill that opens a popover above the composer */
|
|
109
|
+
#chat-sampling-wrap{position:relative;display:inline-flex}
|
|
110
|
+
#chat-sampling{cursor:pointer;gap:6px;padding:5px 11px;font-size:12px;user-select:none;-webkit-user-select:none;
|
|
111
|
+
transition:color .22s var(--ease),border-color .22s var(--ease)}
|
|
112
|
+
#chat-sampling:hover{border-color:rgba(255,255,255,.28);color:var(--ink)}
|
|
113
|
+
#chat-sampling.on{color:var(--ink);border-color:rgba(255,255,255,.28)}
|
|
114
|
+
#chat-sampling.dirty .dot{background:var(--blue);box-shadow:0 0 11px var(--blue)}
|
|
115
|
+
#chat-sampling .dot{width:7px;height:7px}
|
|
116
|
+
.samplepop{position:absolute;bottom:calc(100% + 9px);right:0;z-index:40;width:300px;
|
|
117
|
+
background:rgba(30,30,34,.98);border:1px solid rgba(255,255,255,.12);border-radius:13px;padding:13px 14px;
|
|
118
|
+
backdrop-filter:blur(22px) saturate(1.3);-webkit-backdrop-filter:blur(22px) saturate(1.3);
|
|
119
|
+
box-shadow:0 18px 50px rgba(0,0,0,.7);display:none;
|
|
120
|
+
transform:translateY(4px);opacity:0;transition:opacity .16s var(--ease),transform .16s var(--ease)}
|
|
121
|
+
.samplepop.open{display:block;transform:translateY(0);opacity:1}
|
|
122
|
+
.samplepop h4{font-size:12px;font-weight:600;color:var(--dim);letter-spacing:.02em;
|
|
123
|
+
text-transform:uppercase;margin:0 0 10px}
|
|
124
|
+
.samprow{display:flex;align-items:center;gap:9px;margin:0 0 11px}
|
|
125
|
+
.samprow label{font-size:12.5px;color:var(--ink);width:74px;flex:0 0 auto}
|
|
126
|
+
.samprow input[type=range]{flex:1 1 auto;-webkit-appearance:none;appearance:none;height:4px;margin:0;
|
|
127
|
+
border-radius:3px;background:rgba(255,255,255,.15);outline:none;cursor:pointer}
|
|
128
|
+
.samprow input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:15px;height:15px;
|
|
129
|
+
border-radius:50%;background:var(--ink);border:0;box-shadow:0 1px 4px rgba(0,0,0,.5);cursor:pointer;
|
|
130
|
+
transition:background .15s var(--ease)}
|
|
131
|
+
.samprow input[type=range]:hover::-webkit-slider-thumb,
|
|
132
|
+
.samprow input[type=range]:focus::-webkit-slider-thumb{background:var(--blue)}
|
|
133
|
+
.samp-val{width:40px;flex:0 0 auto;text-align:right;font-family:var(--mono);font-size:12px;color:var(--ink)}
|
|
134
|
+
.samp-val.auto{color:var(--dimmer)}
|
|
135
|
+
.samppop-foot{display:flex;justify-content:space-between;align-items:center;margin-top:3px}
|
|
136
|
+
.samppop-foot .note{font-size:10.5px;color:var(--dimmer);line-height:1.3;flex:1 1 auto;padding-right:8px}
|
|
137
|
+
.samppop-foot button{appearance:none;cursor:pointer;background:var(--card);border:1px solid var(--hairline);
|
|
138
|
+
color:var(--dim);border-radius:8px;padding:5px 11px;font-size:11.5px;font-weight:600;flex:0 0 auto;
|
|
139
|
+
transition:color .18s var(--ease),border-color .18s var(--ease)}
|
|
140
|
+
.samppop-foot button:hover{color:var(--ink);border-color:rgba(255,255,255,.28)}
|
|
108
141
|
#nav-download{color:var(--ink);font-family:var(--mono);font-size:11.5px;font-weight:500}
|
|
109
142
|
#nav-download .dot{background:var(--orange);box-shadow:0 0 11px var(--orange);animation:dlpulse 1.4s ease-in-out infinite}
|
|
110
143
|
@keyframes dlpulse{0%,100%{opacity:.4}50%{opacity:1}}
|
|
@@ -485,6 +518,7 @@
|
|
|
485
518
|
<a class="tab" href="#/dataset" data-tab="dataset">Build Dataset</a>
|
|
486
519
|
<a class="tab" href="#/status" data-tab="status">Status</a>
|
|
487
520
|
<a class="tab" href="/curves" data-tab="curves">Curves</a>
|
|
521
|
+
<a class="tab" href="#/routes" data-tab="routes">Routes</a>
|
|
488
522
|
</div>
|
|
489
523
|
<div class="right">
|
|
490
524
|
<button class="hf-gear" id="nav-hf" title="Hugging Face token settings" aria-label="Hugging Face settings">
|
|
@@ -552,6 +586,32 @@
|
|
|
552
586
|
<div class="chat-hint">
|
|
553
587
|
<span id="chat-status-line">Enter to send · Shift+Enter for a new line · type while it streams to steer</span>
|
|
554
588
|
<span class="hint-right">
|
|
589
|
+
<span id="chat-sampling-wrap">
|
|
590
|
+
<span id="chat-sampling" class="pill" role="button" tabindex="0" aria-haspopup="true" aria-expanded="false"
|
|
591
|
+
title="Sampling controls (temperature · top_p · top_k)"><span class="dot"></span>Sampling</span>
|
|
592
|
+
<div id="chat-sampling-pop" class="samplepop" role="dialog" aria-label="Sampling controls">
|
|
593
|
+
<h4>Sampling</h4>
|
|
594
|
+
<div class="samprow">
|
|
595
|
+
<label for="samp-temp">Temperature</label>
|
|
596
|
+
<input id="samp-temp" type="range" min="0" max="2" step="0.05" aria-label="Temperature">
|
|
597
|
+
<output id="samp-temp-val" class="samp-val auto">0.70</output>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="samprow">
|
|
600
|
+
<label for="samp-topp">top_p</label>
|
|
601
|
+
<input id="samp-topp" type="range" min="0" max="1" step="0.01" aria-label="top_p">
|
|
602
|
+
<output id="samp-topp-val" class="samp-val auto">0.95</output>
|
|
603
|
+
</div>
|
|
604
|
+
<div class="samprow">
|
|
605
|
+
<label for="samp-topk">top_k</label>
|
|
606
|
+
<input id="samp-topk" type="range" min="0" max="100" step="1" aria-label="top_k">
|
|
607
|
+
<output id="samp-topk-val" class="samp-val auto">off</output>
|
|
608
|
+
</div>
|
|
609
|
+
<div class="samppop-foot">
|
|
610
|
+
<span class="note">Sliders default to the recommended value — temperature follows the thinking toggle. Reset returns to defaults.</span>
|
|
611
|
+
<button id="samp-reset" type="button">Reset</button>
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
</span>
|
|
555
615
|
<select id="chat-adapter" class="pill" title="LoRA adapter for this chat (none = base model)">
|
|
556
616
|
<option value="">no adapter</option>
|
|
557
617
|
</select>
|
|
@@ -581,10 +641,14 @@
|
|
|
581
641
|
<p class="lead" style="margin:6px 0 22px;font-size:14.5px">Point at a Hugging Face repo id or a local snapshot path. Inspect first to confirm support and size.</p>
|
|
582
642
|
<div class="field">
|
|
583
643
|
<label>Model id or path</label>
|
|
584
|
-
<
|
|
585
|
-
|
|
644
|
+
<div id="q-drop">
|
|
645
|
+
<input type="text" id="q-model" placeholder="mlx-community/Qwen3-8B-bf16" autocomplete="off">
|
|
646
|
+
</div>
|
|
647
|
+
<input type="file" id="q-folder" webkitdirectory directory style="display:none">
|
|
648
|
+
<div class="hint">Anything on huggingface.co, an absolute path to a local snapshot, or <strong>choose / drop a folder</strong> below — we read its <code>config.json</code> and find the snapshot on disk.</div>
|
|
586
649
|
</div>
|
|
587
650
|
<div class="btnrow">
|
|
651
|
+
<button class="btn ghost" id="q-browse">Choose folder…</button>
|
|
588
652
|
<button class="btn ghost" id="q-inspect">Inspect</button>
|
|
589
653
|
</div>
|
|
590
654
|
<div id="q-inspect-out"></div>
|
|
@@ -643,7 +707,7 @@
|
|
|
643
707
|
<p class="lead" style="margin:6px 0 18px;font-size:14.5px">Your quantized model is written to disk.</p>
|
|
644
708
|
<div class="field"><label>Output directory</label>
|
|
645
709
|
<pre class="logbox" id="q-out" style="max-height:none;margin-top:0"></pre></div>
|
|
646
|
-
<div class="flash ok">
|
|
710
|
+
<div class="flash ok">Written to your model cache and registered automatically — it's already serveable from the Library and the model picker.</div>
|
|
647
711
|
<div class="grid g4" style="margin-top:14px">
|
|
648
712
|
<div class="card"><h3>Push to Hugging Face</h3><div class="cap">Publish this quantized model to a Hugging Face repo.</div>
|
|
649
713
|
<div class="btnrow" style="margin-top:14px"><button class="btn ghost sm" id="q-push">Push to HF</button></div>
|
|
@@ -671,10 +735,16 @@
|
|
|
671
735
|
<p class="lead" style="margin:6px 0 22px;font-size:14.5px">The model you're adapting. Point at a local model directory or snapshot path.</p>
|
|
672
736
|
<div class="field">
|
|
673
737
|
<label>Model directory / path</label>
|
|
674
|
-
<
|
|
675
|
-
|
|
738
|
+
<div id="f-drop">
|
|
739
|
+
<input type="text" id="f-model" placeholder="/abs/path/to/model or mlx-community/Qwen3-8B-4bit" autocomplete="off">
|
|
740
|
+
</div>
|
|
741
|
+
<input type="file" id="f-folder" webkitdirectory directory style="display:none">
|
|
742
|
+
<div class="hint">An OptiQ quant or any supported local model works — type a path/repo id, or <strong>choose / drop a folder</strong> below.</div>
|
|
743
|
+
</div>
|
|
744
|
+
<div class="btnrow">
|
|
745
|
+
<button class="btn ghost" id="f-browse">Choose folder…</button>
|
|
746
|
+
<button class="btn primary" id="f-next0">Continue</button>
|
|
676
747
|
</div>
|
|
677
|
-
<div class="btnrow"><button class="btn primary" id="f-next0">Continue</button></div>
|
|
678
748
|
</div>
|
|
679
749
|
|
|
680
750
|
<!-- step 2: dataset -->
|
|
@@ -702,6 +772,7 @@
|
|
|
702
772
|
<div class="seg" id="f-method">
|
|
703
773
|
<button data-v="sft" class="on">SFT</button>
|
|
704
774
|
<button data-v="dpo">DPO</button>
|
|
775
|
+
<button data-v="orpo">ORPO</button>
|
|
705
776
|
</div>
|
|
706
777
|
<div class="hint" id="f-method-hint">Supervised fine-tuning — data is messages or prompt+completion.</div>
|
|
707
778
|
</div>
|
|
@@ -713,7 +784,18 @@
|
|
|
713
784
|
<div class="hpgrid" style="margin-bottom:18px">
|
|
714
785
|
<div class="field"><label>DPO beta (KL strength)</label><input type="number" id="f-beta" value="0.1" step="0.01" min="0.01" max="1"></div>
|
|
715
786
|
<div class="field"><label>LR schedule</label>
|
|
716
|
-
<select id="f-sched"><option value="cosine">cosine (recommended)</option><option value="constant">constant</option
|
|
787
|
+
<select id="f-sched"><option value="cosine">cosine (recommended)</option><option value="constant">constant</option></select></div>
|
|
788
|
+
</div>
|
|
789
|
+
</div>
|
|
790
|
+
|
|
791
|
+
<div id="f-orpo-extra" style="display:none">
|
|
792
|
+
<div class="flash warn" style="margin-bottom:18px">
|
|
793
|
+
<strong>ORPO is reference-free.</strong> One monolithic loss (SFT + odds-ratio), no reference model — half the forwards of DPO. Default LR 1e-5 (lower than DPO: the loss carries a full SFT term). λ weights only the odds-ratio term. Data is {prompt, chosen, rejected}.
|
|
794
|
+
</div>
|
|
795
|
+
<div class="hpgrid" style="margin-bottom:18px">
|
|
796
|
+
<div class="field"><label>ORPO λ (odds-ratio weight)</label><input type="number" id="f-orpo-lambda" value="0.1" step="0.05" min="0.01" max="2"></div>
|
|
797
|
+
<div class="field"><label>LR schedule</label>
|
|
798
|
+
<select id="f-orpo-sched"><option value="cosine">cosine (recommended)</option><option value="constant">constant</option></select></div>
|
|
717
799
|
</div>
|
|
718
800
|
</div>
|
|
719
801
|
|
|
@@ -950,6 +1032,12 @@
|
|
|
950
1032
|
</div></div>
|
|
951
1033
|
</section>
|
|
952
1034
|
|
|
1035
|
+
<!-- ═══════════════════ ROUTES ═══════════════════ -->
|
|
1036
|
+
<section data-route="routes" id="s-routes">
|
|
1037
|
+
<iframe src="/dag" title="Training · inference route map"
|
|
1038
|
+
style="width:100%;height:100%;border:0;display:block"></iframe>
|
|
1039
|
+
</section>
|
|
1040
|
+
|
|
953
1041
|
</div>
|
|
954
1042
|
|
|
955
1043
|
<div id="toasts"></div>
|
|
@@ -1027,7 +1115,7 @@ function mdInline(t) {
|
|
|
1027
1115
|
// Pull code spans out first (any run of backticks), then escape the rest.
|
|
1028
1116
|
t = String(t).replace(/(`+)([\s\S]+?)\1/g, (_m, _ticks, c) => {
|
|
1029
1117
|
codes.push(c.replace(/^ | $/g, ""));
|
|
1030
|
-
return "
|
|
1118
|
+
return "\u0000C" + (codes.length - 1) + "\u0000";
|
|
1031
1119
|
});
|
|
1032
1120
|
let s = esc(t);
|
|
1033
1121
|
// [text](url "title") — title optional and ignored.
|
|
@@ -1045,7 +1133,7 @@ function mdInline(t) {
|
|
|
1045
1133
|
// Autolink bare URLs that markdown didn't already wrap.
|
|
1046
1134
|
s = s.replace(/(^|[\s(])(https?:\/\/[^\s<)]+[^\s<).,;:!?'"])/g,
|
|
1047
1135
|
(_m, pre, url) => pre + '<a href="' + url + '" target="_blank" rel="noopener noreferrer">' + url + "</a>");
|
|
1048
|
-
return s.replace(
|
|
1136
|
+
return s.replace(/\u0000C(\d+)\u0000/g, (_m, i) => "<code>" + esc(codes[+i]) + "</code>");
|
|
1049
1137
|
}
|
|
1050
1138
|
|
|
1051
1139
|
function mdCodeBlock(lang, code) {
|
|
@@ -1276,7 +1364,7 @@ async function pushToHub(panel, { kind, job_id, source_path }) {
|
|
|
1276
1364
|
/* ════════════════════════════════════════════════════════════════════
|
|
1277
1365
|
ROUTER — toggles section[data-route]; lazily inits each controller.
|
|
1278
1366
|
════════════════════════════════════════════════════════════════════ */
|
|
1279
|
-
const ROUTES = ["chat", "quantize", "finetune", "dataset", "status"];
|
|
1367
|
+
const ROUTES = ["chat", "quantize", "finetune", "dataset", "status", "routes"];
|
|
1280
1368
|
const inited = {};
|
|
1281
1369
|
const controllers = {}; // name -> { init, enter, leave }
|
|
1282
1370
|
|
|
@@ -1419,7 +1507,7 @@ controllers.chat = (() => {
|
|
|
1419
1507
|
el("div", "who", m).textContent = "local agent";
|
|
1420
1508
|
const bubble = el("div", "bubble", m);
|
|
1421
1509
|
const thinkBox = el("details", "thinkbox", bubble);
|
|
1422
|
-
thinkBox.open =
|
|
1510
|
+
thinkBox.open = false; // collapsed by default; the reasoning stays tucked behind "Thinking" — click to expand
|
|
1423
1511
|
el("summary", "", thinkBox).textContent = "Thinking";
|
|
1424
1512
|
const thinkBody = el("div", "thinkbody", thinkBox);
|
|
1425
1513
|
thinkBox.style.display = "none";
|
|
@@ -1726,7 +1814,15 @@ controllers.chat = (() => {
|
|
|
1726
1814
|
let attachments = []; // { id, kind:"image"|"text", name, mimeType, data, text, truncated }
|
|
1727
1815
|
let visionCapable = false; // from the `ready` frame; gates image attachments
|
|
1728
1816
|
let thinkingCapable = false; // from `ready`; gates the reasoning toggle
|
|
1729
|
-
let thinkingOn = true; // on/
|
|
1817
|
+
let thinkingOn = true; // default on: with tools (read/web_search) the reasoning step gives the small model far better tool judgment (answers math/writing itself, only searches when needed). The thinking box defaults collapsed. Users can toggle it off.
|
|
1818
|
+
// Per-request sampling. Each field is either a user override (a number) or
|
|
1819
|
+
// null = "use the recommended default" (resolved server-side). The recommended
|
|
1820
|
+
// TEMPERATURE tracks the thinking toggle (0.7 off ↔ 0.9 on); top_p/top_k recs
|
|
1821
|
+
// are fixed mirrors of the server defaults (display only). The placeholder in
|
|
1822
|
+
// each input shows the live recommended value when the field isn't overridden.
|
|
1823
|
+
const sampling = { temperature: null, top_p: null, top_k: null };
|
|
1824
|
+
const SAMP_REC = { topP: 0.95, topK: 0 }; // fixed recommended top_p / top_k (server genDefaults)
|
|
1825
|
+
function recTemp() { return thinkingOn ? 0.9 : 0.7; } // mirrors server toOptions defaultTemp
|
|
1730
1826
|
let attachSeq = 0;
|
|
1731
1827
|
const MAX_TEXT_BYTES = 256 * 1024;
|
|
1732
1828
|
const TEXT_EXTS = /\.(txt|md|markdown|csv|tsv|json|jsonl|ya?ml|toml|ini|cfg|conf|log|xml|html?|css|scss|js|mjs|cjs|ts|tsx|jsx|py|rs|go|java|c|h|cpp|hpp|cc|cs|rb|php|sh|bash|zsh|fish|sql|swift|kt|lua|r|jl|tex|svg|env|gitignore|dockerfile|makefile)$/i;
|
|
@@ -1823,6 +1919,81 @@ controllers.chat = (() => {
|
|
|
1823
1919
|
b.classList.toggle("think-on", thinkingOn);
|
|
1824
1920
|
b.title = thinkingOn ? "Reasoning on — click to answer directly" : "Reasoning off — click to let the model think";
|
|
1825
1921
|
b.setAttribute("aria-pressed", String(thinkingOn));
|
|
1922
|
+
refreshSamplingRecs(); // recommended temperature follows the thinking state
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/* — sampling controls — */
|
|
1926
|
+
// Each row is a slider + a live value readout. A field is "auto" (null
|
|
1927
|
+
// override → the server uses its mode-aware default; temperature's default
|
|
1928
|
+
// follows the thinking toggle) until the user drags it, which records an
|
|
1929
|
+
// explicit override. Reset returns every field to auto.
|
|
1930
|
+
const SAMP_FIELDS = [
|
|
1931
|
+
{ id: "samp-temp", field: "temperature", valId: "samp-temp-val", rec: () => recTemp(), fmt: (v) => v.toFixed(2) },
|
|
1932
|
+
{ id: "samp-topp", field: "top_p", valId: "samp-topp-val", rec: () => SAMP_REC.topP, fmt: (v) => v.toFixed(2) },
|
|
1933
|
+
{ id: "samp-topk", field: "top_k", valId: "samp-topk-val", rec: () => SAMP_REC.topK, fmt: (v) => (v >= 1 ? String(Math.round(v)) : "off") },
|
|
1934
|
+
];
|
|
1935
|
+
|
|
1936
|
+
// Sync every slider + readout to current state: an overridden field shows its
|
|
1937
|
+
// set value (bright readout); an auto field shows the recommended value
|
|
1938
|
+
// (dimmed) and tracks it live — e.g. temperature when the thinking toggle
|
|
1939
|
+
// flips. Called on thinking-toggle flips, popover open, and reset.
|
|
1940
|
+
function refreshSamplingRecs() {
|
|
1941
|
+
for (const f of SAMP_FIELDS) {
|
|
1942
|
+
const slider = $(f.id), val = $(f.valId); if (!slider || !val) continue;
|
|
1943
|
+
const over = sampling[f.field];
|
|
1944
|
+
const v = over != null ? over : f.rec();
|
|
1945
|
+
slider.value = String(v);
|
|
1946
|
+
val.textContent = f.fmt(v);
|
|
1947
|
+
val.classList.toggle("auto", over == null);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// User dragged a slider → record the explicit override, brighten the readout,
|
|
1952
|
+
// and push the whole sampling state to the server.
|
|
1953
|
+
function onSlide(f) {
|
|
1954
|
+
const slider = $(f.id); if (!slider) return;
|
|
1955
|
+
const v = Number(slider.value);
|
|
1956
|
+
sampling[f.field] = v;
|
|
1957
|
+
const val = $(f.valId); if (val) { val.textContent = f.fmt(v); val.classList.remove("auto"); }
|
|
1958
|
+
updateSamplingUi();
|
|
1959
|
+
send({ type: "set_sampling", temperature: sampling.temperature, top_p: sampling.top_p, top_k: sampling.top_k });
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// The pill shows a lit dot whenever any field is overridden.
|
|
1963
|
+
function updateSamplingUi() {
|
|
1964
|
+
const dirty = sampling.temperature != null || sampling.top_p != null || sampling.top_k != null;
|
|
1965
|
+
const pill = $("chat-sampling");
|
|
1966
|
+
if (pill) { pill.classList.toggle("dirty", dirty); pill.title = dirty
|
|
1967
|
+
? "Sampling overridden — click to edit or reset" : "Sampling controls (temperature · top_p · top_k)"; }
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function resetSampling() {
|
|
1971
|
+
sampling.temperature = null; sampling.top_p = null; sampling.top_k = null;
|
|
1972
|
+
refreshSamplingRecs(); updateSamplingUi();
|
|
1973
|
+
send({ type: "set_sampling", temperature: null, top_p: null, top_k: null });
|
|
1974
|
+
}
|
|
1975
|
+
function initSampling() {
|
|
1976
|
+
const pill = $("chat-sampling"), pop = $("chat-sampling-pop");
|
|
1977
|
+
if (!pill || !pop) return;
|
|
1978
|
+
const setOpen = (open) => {
|
|
1979
|
+
pop.classList.toggle("open", open);
|
|
1980
|
+
pill.classList.toggle("on", open);
|
|
1981
|
+
pill.setAttribute("aria-expanded", String(open));
|
|
1982
|
+
if (open) refreshSamplingRecs();
|
|
1983
|
+
};
|
|
1984
|
+
const toggle = () => setOpen(!pop.classList.contains("open"));
|
|
1985
|
+
pill.onclick = (e) => { e.stopPropagation(); toggle(); };
|
|
1986
|
+
pill.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(); } });
|
|
1987
|
+
pop.addEventListener("click", (e) => e.stopPropagation());
|
|
1988
|
+
// Click-away / Escape closes the popover.
|
|
1989
|
+
document.addEventListener("click", () => setOpen(false));
|
|
1990
|
+
document.addEventListener("keydown", (e) => { if (e.key === "Escape") setOpen(false); });
|
|
1991
|
+
for (const f of SAMP_FIELDS) {
|
|
1992
|
+
const e = $(f.id); if (!e) continue;
|
|
1993
|
+
e.addEventListener("input", () => onSlide(f));
|
|
1994
|
+
}
|
|
1995
|
+
const reset = $("samp-reset"); if (reset) reset.onclick = (e) => { e.stopPropagation(); resetSampling(); };
|
|
1996
|
+
updateSamplingUi(); refreshSamplingRecs();
|
|
1826
1997
|
}
|
|
1827
1998
|
|
|
1828
1999
|
/* — composer — */
|
|
@@ -1842,8 +2013,10 @@ controllers.chat = (() => {
|
|
|
1842
2013
|
addUserMsg(text, attachments);
|
|
1843
2014
|
box.value = ""; box.style.height = "auto";
|
|
1844
2015
|
const frame = imgs.length ? { text: combined, images: imgs } : { text: combined };
|
|
1845
|
-
|
|
1846
|
-
|
|
2016
|
+
// A composer submission is always a new user turn. If the model is still
|
|
2017
|
+
// streaming, the server passes it to pi with streamingBehavior:"followUp"
|
|
2018
|
+
// so it becomes the next turn instead of steering/mutating the current one.
|
|
2019
|
+
send({ type: "prompt", ...frame });
|
|
1847
2020
|
clearAttachments();
|
|
1848
2021
|
}
|
|
1849
2022
|
const scroll = () => $("chat-scroll");
|
|
@@ -1860,6 +2033,7 @@ controllers.chat = (() => {
|
|
|
1860
2033
|
$("chat-think").addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); thinkToggle(); } });
|
|
1861
2034
|
const adapterSel = $("chat-adapter");
|
|
1862
2035
|
if (adapterSel) adapterSel.onchange = () => onSelectAdapter(adapterSel);
|
|
2036
|
+
initSampling();
|
|
1863
2037
|
refreshAdapters();
|
|
1864
2038
|
const box = $("chat-box");
|
|
1865
2039
|
box.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); } });
|
|
@@ -1922,6 +2096,33 @@ controllers.quantize = (() => {
|
|
|
1922
2096
|
}));
|
|
1923
2097
|
}
|
|
1924
2098
|
|
|
2099
|
+
// --- OS folder picker / drag-drop → real on-disk snapshot path ---
|
|
2100
|
+
// The browser can't reveal an absolute path, and the HF cache uses symlinked
|
|
2101
|
+
// snapshots over a blobs/ store — so we DON'T read any files. We only take the
|
|
2102
|
+
// picked folder's NAME (which encodes the repo id) and let the server
|
|
2103
|
+
// reconstruct the snapshot path it owns. No upload, no "config.json" hunt.
|
|
2104
|
+
async function resolveByName(name, relPath) {
|
|
2105
|
+
if (!name && !relPath) return;
|
|
2106
|
+
const out = $("q-inspect-out");
|
|
2107
|
+
out.innerHTML = '<div class="flash"><span class="shimmer">locating on disk…</span></div>';
|
|
2108
|
+
const d = await api("/api/quantize/resolve-folder", { method: "POST", body: { folder_name: name || "", rel_path: relPath || "" } })
|
|
2109
|
+
.catch(() => ({ ok: false, error: "request failed" }));
|
|
2110
|
+
if (!d.ok) { out.innerHTML = '<div class="flash err">' + esc(d.error || "could not locate this folder on the server") + "</div>"; return; }
|
|
2111
|
+
$("q-model").value = d.path;
|
|
2112
|
+
inspect();
|
|
2113
|
+
}
|
|
2114
|
+
async function pickFolder() {
|
|
2115
|
+
// Native OS folder dialog with no "upload N files" prompt; we only read the
|
|
2116
|
+
// folder's .name, never its contents.
|
|
2117
|
+
if (window.showDirectoryPicker) {
|
|
2118
|
+
try {
|
|
2119
|
+
const handle = await window.showDirectoryPicker({ id: "mlx-models", mode: "read" });
|
|
2120
|
+
return resolveByName(handle.name);
|
|
2121
|
+
} catch (e) { return; } // user cancelled / denied
|
|
2122
|
+
}
|
|
2123
|
+
$("q-folder").click(); // fallback for browsers without the File System Access API
|
|
2124
|
+
}
|
|
2125
|
+
|
|
1925
2126
|
async function inspect() {
|
|
1926
2127
|
const id = $("q-model").value.trim();
|
|
1927
2128
|
if (!id) { toast("Enter a model id or path first.", "err"); return; }
|
|
@@ -1978,6 +2179,26 @@ controllers.quantize = (() => {
|
|
|
1978
2179
|
show(0);
|
|
1979
2180
|
$("q-inspect").onclick = inspect;
|
|
1980
2181
|
$("q-model").addEventListener("keydown", (e) => { if (e.key === "Enter") inspect(); });
|
|
2182
|
+
// OS folder dialog (native; resolves by folder name, no upload)
|
|
2183
|
+
$("q-browse").onclick = pickFolder;
|
|
2184
|
+
$("q-folder").addEventListener("change", (e) => {
|
|
2185
|
+
const f = e.target.files[0]; e.target.value = "";
|
|
2186
|
+
if (f) resolveByName((f.webkitRelativePath || f.name).split("/")[0], f.webkitRelativePath || "");
|
|
2187
|
+
});
|
|
2188
|
+
// drag a folder onto the box — use the directory entry's NAME (no read)
|
|
2189
|
+
const drop = $("q-drop");
|
|
2190
|
+
const hi = (on) => { drop.style.outline = on ? "2px dashed var(--blue)" : ""; drop.style.outlineOffset = on ? "4px" : ""; };
|
|
2191
|
+
["dragenter", "dragover"].forEach((ev) => drop.addEventListener(ev, (e) => { e.preventDefault(); hi(true); }));
|
|
2192
|
+
["dragleave", "drop"].forEach((ev) => drop.addEventListener(ev, (e) => {
|
|
2193
|
+
e.preventDefault(); hi(false);
|
|
2194
|
+
if (ev !== "drop" || !e.dataTransfer) return;
|
|
2195
|
+
for (const it of [...(e.dataTransfer.items || [])]) {
|
|
2196
|
+
const en = it.webkitGetAsEntry && it.webkitGetAsEntry();
|
|
2197
|
+
if (en && en.isDirectory) return resolveByName(en.name);
|
|
2198
|
+
}
|
|
2199
|
+
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
|
2200
|
+
if (f) resolveByName((f.webkitRelativePath || f.name).split("/")[0]);
|
|
2201
|
+
}));
|
|
1981
2202
|
seg($("q-bits"), (v) => { bits = +v; $("q-bits-hint").textContent = v === "4"
|
|
1982
2203
|
? "4-bit · ~4× smaller than bf16 · the recommended default for local serving."
|
|
1983
2204
|
: "8-bit · ~2× smaller than bf16 · highest fidelity quant."; });
|
|
@@ -1997,7 +2218,7 @@ controllers.quantize = (() => {
|
|
|
1997
2218
|
════════════════════════════════════════════════════════════════════ */
|
|
1998
2219
|
controllers.finetune = (() => {
|
|
1999
2220
|
const STEPS = ["Base", "Dataset", "Hyperparams", "Train", "Done"];
|
|
2000
|
-
let step = 0, method = "sft", lrTouched = false, es = null, log = [];
|
|
2221
|
+
let step = 0, method = "sft", lrTouched = false, rankTouched = false, es = null, log = [];
|
|
2001
2222
|
let datasetOk = false, adapterPath = "";
|
|
2002
2223
|
const trainPts = [], valPts = [];
|
|
2003
2224
|
|
|
@@ -2007,6 +2228,23 @@ controllers.finetune = (() => {
|
|
|
2007
2228
|
renderSteps($("f-steps"), STEPS, n);
|
|
2008
2229
|
}
|
|
2009
2230
|
|
|
2231
|
+
// OS folder picker for the base model — resolve by NAME server-side (no
|
|
2232
|
+
// upload, works with the HF cache's symlinked layout). Mirrors Quantize.
|
|
2233
|
+
async function resolveBaseByName(name, relPath) {
|
|
2234
|
+
if (!name && !relPath) return;
|
|
2235
|
+
const d = await api("/api/model/resolve-folder", { method: "POST", body: { folder_name: name || "", rel_path: relPath || "" } })
|
|
2236
|
+
.catch(() => ({ ok: false, error: "request failed" }));
|
|
2237
|
+
if (!d.ok) { toast(d.error || "could not locate this folder", "err"); return; }
|
|
2238
|
+
$("f-model").value = d.path;
|
|
2239
|
+
}
|
|
2240
|
+
async function pickBaseFolder() {
|
|
2241
|
+
if (window.showDirectoryPicker) {
|
|
2242
|
+
try { const h = await window.showDirectoryPicker({ id: "mlx-models", mode: "read" }); return resolveBaseByName(h.name); }
|
|
2243
|
+
catch (e) { return; }
|
|
2244
|
+
}
|
|
2245
|
+
$("f-folder").click();
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2010
2248
|
async function inspectDataset() {
|
|
2011
2249
|
const path = $("f-data").value.trim();
|
|
2012
2250
|
if (!path) { toast("Enter a dataset directory.", "err"); return; }
|
|
@@ -2024,10 +2262,15 @@ controllers.finetune = (() => {
|
|
|
2024
2262
|
method = m;
|
|
2025
2263
|
$("f-method").querySelectorAll("button").forEach((b) => b.classList.toggle("on", b.dataset.v === m));
|
|
2026
2264
|
$("f-dpo-extra").style.display = m === "dpo" ? "" : "none";
|
|
2265
|
+
$("f-orpo-extra").style.display = m === "orpo" ? "" : "none";
|
|
2027
2266
|
$("f-method-hint").textContent = m === "dpo"
|
|
2028
2267
|
? "Direct Preference Optimization — data is {prompt, chosen, rejected}."
|
|
2029
|
-
:
|
|
2030
|
-
|
|
2268
|
+
: m === "orpo"
|
|
2269
|
+
? "Odds Ratio Preference Optimization (reference-free) — data is {prompt, chosen, rejected}."
|
|
2270
|
+
: "Supervised fine-tuning — data is messages or prompt+completion.";
|
|
2271
|
+
if (!lrTouched) $("f-lr").value = m === "dpo" ? 0.00005 : m === "orpo" ? 0.00001 : 0.0002;
|
|
2272
|
+
// ORPO's validated LoRA recipe uses rank 16 (vs 8 for SFT); apply unless edited.
|
|
2273
|
+
if (!rankTouched) $("f-rank").value = m === "orpo" ? 16 : 8;
|
|
2031
2274
|
}
|
|
2032
2275
|
|
|
2033
2276
|
function collectHP() {
|
|
@@ -2036,11 +2279,12 @@ controllers.finetune = (() => {
|
|
|
2036
2279
|
model_dir: $("f-model").value.trim(),
|
|
2037
2280
|
data_dir: $("f-data").value.trim(),
|
|
2038
2281
|
method,
|
|
2039
|
-
rank: +$("f-rank").value, scale: +$("f-scale").value,
|
|
2282
|
+
rank: +$("f-rank").value, scale: +$("f-scale").value, lora_dropout: +$("f-dropout").value,
|
|
2040
2283
|
target_modules: modules, num_layers: +$("f-layers").value, iters: +$("f-iters").value,
|
|
2041
2284
|
batch_size: +$("f-batch").value, learning_rate: +$("f-lr").value, max_seq_length: +$("f-seq").value,
|
|
2042
2285
|
};
|
|
2043
|
-
if (method === "dpo") { hp.dpo_beta = +$("f-beta").value; hp.
|
|
2286
|
+
if (method === "dpo") { hp.dpo_beta = +$("f-beta").value; hp.dpo_lr_schedule = $("f-sched").value; }
|
|
2287
|
+
if (method === "orpo") { hp.orpo_lambda = +$("f-orpo-lambda").value; hp.orpo_lr_schedule = $("f-orpo-sched").value; }
|
|
2044
2288
|
return hp;
|
|
2045
2289
|
}
|
|
2046
2290
|
|
|
@@ -2162,12 +2406,34 @@ controllers.finetune = (() => {
|
|
|
2162
2406
|
init() {
|
|
2163
2407
|
show(0);
|
|
2164
2408
|
$("f-next0").onclick = () => { if (!$("f-model").value.trim()) { toast("Enter a base model path.", "err"); return; } show(1); };
|
|
2409
|
+
// OS folder dialog for the base model (native; resolves by name, no upload)
|
|
2410
|
+
$("f-browse").onclick = pickBaseFolder;
|
|
2411
|
+
$("f-folder").addEventListener("change", (e) => {
|
|
2412
|
+
const f = e.target.files[0]; e.target.value = "";
|
|
2413
|
+
if (f) resolveBaseByName((f.webkitRelativePath || f.name).split("/")[0], f.webkitRelativePath || "");
|
|
2414
|
+
});
|
|
2415
|
+
{
|
|
2416
|
+
const drop = $("f-drop");
|
|
2417
|
+
const hi = (on) => { drop.style.outline = on ? "2px dashed var(--blue)" : ""; drop.style.outlineOffset = on ? "4px" : ""; };
|
|
2418
|
+
["dragenter", "dragover"].forEach((ev) => drop.addEventListener(ev, (e) => { e.preventDefault(); hi(true); }));
|
|
2419
|
+
["dragleave", "drop"].forEach((ev) => drop.addEventListener(ev, (e) => {
|
|
2420
|
+
e.preventDefault(); hi(false);
|
|
2421
|
+
if (ev !== "drop" || !e.dataTransfer) return;
|
|
2422
|
+
for (const it of [...(e.dataTransfer.items || [])]) {
|
|
2423
|
+
const en = it.webkitGetAsEntry && it.webkitGetAsEntry();
|
|
2424
|
+
if (en && en.isDirectory) return resolveBaseByName(en.name);
|
|
2425
|
+
}
|
|
2426
|
+
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
|
2427
|
+
if (f) resolveBaseByName((f.webkitRelativePath || f.name).split("/")[0]);
|
|
2428
|
+
}));
|
|
2429
|
+
}
|
|
2165
2430
|
$("f-inspect").onclick = inspectDataset;
|
|
2166
2431
|
$("f-data").addEventListener("keydown", (e) => { if (e.key === "Enter") inspectDataset(); });
|
|
2167
2432
|
$("f-data").addEventListener("input", () => { datasetOk = false; $("f-next1").disabled = true; $("f-inspect-out").innerHTML = ""; });
|
|
2168
2433
|
$("f-next1").onclick = () => { if (datasetOk) show(2); };
|
|
2169
2434
|
$("f-method").querySelectorAll("button").forEach((b) => b.onclick = () => setMethod(b.dataset.v));
|
|
2170
2435
|
$("f-lr").addEventListener("input", () => { lrTouched = true; });
|
|
2436
|
+
$("f-rank").addEventListener("input", () => { rankTouched = true; });
|
|
2171
2437
|
document.querySelectorAll("#s-finetune [data-fback]").forEach((b) => b.onclick = () => show(step - 1));
|
|
2172
2438
|
$("f-submit").onclick = submit;
|
|
2173
2439
|
$("f-merge-go").onclick = mergeAdapters;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: Use when the answer depends on the user's prior conversations or personal context: explicit memory/recall requests, named personal projects, people, preferences, decisions, or history not explained in the current chat. Do not use for weather, current public facts, generic web research, or ordinary coding/file tasks when current files are sufficient. Also use when the user wants to set up, inspect, or manage memory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Memory
|
|
7
|
+
|
|
8
|
+
The user has a personal **memory** — a wiki of Markdown articles synthesized
|
|
9
|
+
from their past conversations, stored locally at `~/.mlx-bun/wiki/`. User memory
|
|
10
|
+
articles live under `articles/`. Read-only mlx-bun reference docs live under
|
|
11
|
+
`Reference/` as symlinks and are also searchable/readable, but synthesis never
|
|
12
|
+
writes them. Each user article is the consolidated, finished understanding of one
|
|
13
|
+
topic. Your job is to *use it* so the user never has to re-explain what they've
|
|
14
|
+
already told you.
|
|
15
|
+
|
|
16
|
+
It is entirely local: the articles, the synthesis, and these tools all run on
|
|
17
|
+
the user's machine. Nothing about their memory leaves the device.
|
|
18
|
+
|
|
19
|
+
## Consulting memory (the common case)
|
|
20
|
+
|
|
21
|
+
When the answer depends on personal continuity — an explicit memory/recall
|
|
22
|
+
request, or a named project, person, decision, preference, or history that is
|
|
23
|
+
not explained in the current chat — consult memory before answering or asking
|
|
24
|
+
the user to re-explain.
|
|
25
|
+
|
|
26
|
+
Do **not** use memory for weather, current public facts, generic web research,
|
|
27
|
+
or ordinary coding/file tasks when the current files or the web tools answer the
|
|
28
|
+
question. Memory is for user-specific context, not for performing familiarity.
|
|
29
|
+
|
|
30
|
+
1. **Search.** Call `memory_search` with the topic (plain phrasing is fine —
|
|
31
|
+
a name or key noun). It returns ranked articles and sample lines.
|
|
32
|
+
2. **Read enough.** For short or central articles, call `memory_read`. For long
|
|
33
|
+
articles, use `memory_toc` then `memory_section` to read the relevant part.
|
|
34
|
+
The article is the answer — read it rather than reconstructing the user's
|
|
35
|
+
position from scattered fragments. Treat it as their current view.
|
|
36
|
+
3. **Follow nearby context when useful.** Use `memory_links` if backlinks or
|
|
37
|
+
related concepts matter. `memory_list` gives an overview when search comes up
|
|
38
|
+
short. mlx-bun reference docs are separate from personal memory; use
|
|
39
|
+
`reference_search`, `reference_read`, and `reference_list` for those.
|
|
40
|
+
`memory_status` answers setup/schedule/location questions.
|
|
41
|
+
4. **Use it silently, as a prior.** Let what you find shape your response like
|
|
42
|
+
a long-time colleague would — don't announce "I searched your memory."
|
|
43
|
+
|
|
44
|
+
Use memory as an invisible prior: it should narrow relevance and improve the
|
|
45
|
+
answer, not become the topic of the answer. Avoid performative recall — do not
|
|
46
|
+
name-drop unrelated remembered facts to sound familiar.
|
|
47
|
+
|
|
48
|
+
## Opening memory for the user
|
|
49
|
+
|
|
50
|
+
The wiki is a normal Markdown/Obsidian vault. If the user asks to browse, open,
|
|
51
|
+
inspect, or edit their memory directly, you may open it for them after a brief
|
|
52
|
+
confirmation:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mlx-bun memory open
|
|
56
|
+
mlx-bun memory open <article>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The first form opens `~/.mlx-bun/wiki/` in Obsidian when available, falling back
|
|
60
|
+
to the folder in Finder. The second form opens a specific article stem such as
|
|
61
|
+
`Archie_Project` directly in Obsidian, falling back to the default Markdown app.
|
|
62
|
+
Opening the vault or article is allowed; editing articles during chat is not. If
|
|
63
|
+
the user wants a change to become durable memory, keep the conversation natural
|
|
64
|
+
and let synthesis incorporate it later, unless they explicitly ask to edit files
|
|
65
|
+
by hand in Obsidian.
|
|
66
|
+
|
|
67
|
+
## Setting up memory (if it doesn't exist yet)
|
|
68
|
+
|
|
69
|
+
If memory isn't set up (the memory tools aren't available, or the user asks to
|
|
70
|
+
turn it on), explain it plainly and set it up on agreement:
|
|
71
|
+
|
|
72
|
+
> Your memory is a folder of Markdown articles at `~/.mlx-bun/wiki/`,
|
|
73
|
+
> synthesized from your conversations so I can remember your context across
|
|
74
|
+
> sessions. It's a normal git-tracked wiki you fully own and can edit or
|
|
75
|
+
> delete — and it never leaves your machine.
|
|
76
|
+
|
|
77
|
+
On a yes, run the setup via the bash tool (the user approves it):
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
mlx-bun memory init
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This creates the vault and walks through optional steps (importing an existing
|
|
84
|
+
wiki, scheduling nightly synthesis). Confirm before each persistent action.
|
|
85
|
+
|
|
86
|
+
## What not to do
|
|
87
|
+
|
|
88
|
+
- Don't claim a fact "from memory" that you didn't read in an article — search
|
|
89
|
+
and read first, or say you don't have it.
|
|
90
|
+
- Don't write to or edit the wiki during a normal conversation; the read tools
|
|
91
|
+
are read-only by design. Synthesis is a separate, deliberate step.
|
|
92
|
+
- Do open the wiki or a specific article in Obsidian/Finder if the user asks to
|
|
93
|
+
browse or edit their memory themselves (`mlx-bun memory open [article]`).
|
|
94
|
+
- Don't dump article contents at the user verbatim — use them to inform a
|
|
95
|
+
direct, relevant answer.
|
|
96
|
+
- Don't perform familiarity. If the user asks for weather, current facts, or a
|
|
97
|
+
normal coding task, use the task-appropriate tool instead of memory.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-research
|
|
3
|
-
description: Use when the user asks you to research something, do a deep dive, fact-check a claim, compare options, or answer a question that needs current, accurate, real-world information ("what's the latest on…", prices, releases, news, specs).
|
|
3
|
+
description: Use only when the user asks you to research something, do a deep dive, fact-check a claim, compare options, or answer a question that needs current, accurate, real-world information ("what's the latest on…", prices, releases, news, specs). Do not use for ordinary conversation, ordinary coding tasks, or questions answerable from the current conversation/files.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Web Research
|
|
@@ -16,6 +16,8 @@ trustworthy, well-sourced answer — not a guess dressed up as one.
|
|
|
16
16
|
- The user wants a thorough or authoritative answer, or asks for sources.
|
|
17
17
|
- A specific claim needs verifying, or you would otherwise be speculating.
|
|
18
18
|
|
|
19
|
+
Do **not** use this for ordinary conversation that does not require external information. In that case, reply naturally instead of explaining the research workflow.
|
|
20
|
+
|
|
19
21
|
## Workflow
|
|
20
22
|
|
|
21
23
|
1. **Plan.** Restate the question in your own words and list the 2–4 concrete
|