Instructions to use baya1116/hypernet-sp-distill with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use baya1116/hypernet-sp-distill with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir hypernet-sp-distill baya1116/hypernet-sp-distill
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
App-level handoff β composite operation of the SP model as a local assistant
This is the operational handoff: how the bounded-memory soft-prompt (SP) reasoner is wired into a
usable on-device app, what was built, what's verified, and where the edges are. For the research
handoff (training, deepKL, architecture) see HANDOFF.md; for the runbook see
OPERATING.md; for the portfolio writeup README.md.
One line: a 1.5B reasoning distill runs with O(1) bounded KV (distant context β 32 SP vectors); a tiered retrieval + memory layer (same-session β prior-session β web) feeds verbatim facts into the raw window so the lossy SP is bypassed, with a groundedness guard that resamples contaminated answers. Runs at ~36 tok/s on an 8 GB Mac (MLX 4-bit).
0. FINAL SPEC (current behaviour, after all iterations)
What tiered_rag_mlx.ChatSession.turn() does for each user message, end to end:
A. Classify the turn (intent_of) β ONE learned 6-class router: a LogisticRegression on a pure
BGE-small embedding (evals/intent_clf.joblib, ~95% CV) routes each turn to exactly one action:
- recall β a question about the USER β personal memory only (L1/L2), never web.
- lookup β a general/WORLD question β web (so "who is the emperor of Japan" goes to web, not personal L1).
- math β a self-contained problem β solve.
- command β "recompute the total" β recall-log + compute.
- fact β a stated value/attribute β log.
- chitchat β greeting / conversational remark / open-ended request β respond.
This replaces all the regex gates (_is_question, _is_factlike, _looks_mathy, compute_ref) β one
embedding model does all routing. Training = hand-labelled core + open-model-generated examples
(Qwen2.5-3B-Instruct-4bit, evals/intent_gen.py). Regex gates are only a low-confidence/no-model fallback.
(History: regex β BGE+syntactic hybrid for small data β pure BGE once data grew β split questionβrecall/
lookup after a realistic session showed world questions hitting personal memory and hallucinating. The
recall/lookup split was the key fix for that β a personal vs world question is a routing distinction,
not a retrieval-threshold one. intent_features.py is deprecated.)
B. Write to memory (so future turns can recall it):
- explicit save ("remember my β¦ is X") β persist to disk (L2) + reply instantly "Got it β saved." (no generation).
- a fact-like statement (asserts a value/attribute: "8 guests", "paint it blue") β auto-log to L1 (same-session).
- chit-chat / knowledge-requests / questions β NOT logged (logging them poisons recall).
- specific info stated INSIDE a non-recall turn (e.g. "$120/day" inside a math question) β pinned to working memory by the independent specificity head (see Bβ²), even though the turn isn't a logged fact.
Bβ². Specificity head (independent, pin working memory) β closes the gap where a specific VALUE is stated
inside a question/math turn ("if I spend $120 a dayβ¦") and is therefore never logged, so a later
"recompute" / "what was the rate" misses it. The head is one more linear probe on the SAME BGE vector
already computed for routing/retrieval (evals/specificity_clf.joblib, 5-fold CV AUC 0.996), chosen via
a bake-off (evals/specificity_probe.py) where it beat tokenizer-fragmentation/tpc (AUC 0.87) and a
form-frequency surprisal proxy (0.30 β dead). specific_spans(text) enumerates cheap candidates (money,
codes, times, measures, proper-noun runs) and keeps those the probe scores β₯0.6. In turn(): for any intent
except recall/lookup (there the specific span is what's being asked about, e.g. "who is Naruhito" β not
user info), a turn with specific spans is pinned (TieredMemory.pins, rolling cap 12). Pins feed:
command/compute as primary context (the recompute needs the value), and recall/lookup as a
strict (simβ₯0.5) fallback only β genuine logged facts win, so a pinned "$120 a day for 3 days" can't
derail "how long am I staying". Verified evals/pin_test.py (detector 9/9; pinβrecall flow pass) and the
realistic session: recompute the food total now = 480 (was un-recoverable before, the $120 lived only
in a math turn). Train/extend: python3.12 evals/specificity_train.py (+ optional specificity_gen.jsonl).
Follow-up math turns also get WM pins injected (excluding the current turn's own pin) so a multi-turn
question that references earlier context ("she pays with $50, how much change" needs turn-1's $24, which
scrolled out of the raw window) can recover it β this was a real bug (the designed RAG didn't fire on math
follow-ups, only on command).
The recall/lookup pin-fallback intercepts only pins NOT already logged in the session (p not in session)
β a LOGGED fact ("going to Kyoto") must not shadow a genuine world question ("emperor of Japan", which is
geographically near "Kyoto"); logged facts are recall-reachable anyway. POLICY COUPLING (important): this
"working-memory-only" filter assumes the app logs ONLY facts to the session β i.e. non-fact turns are called
with store="none" (cf. app_session.py / app_eval2.py). If a math/chitchat turn were logged to the
session, its pinned value would stop being WM-only and the lookupβpins safety net could not fire for it.
Evaluated end-to-end in evals/app_eval2.py (PHASE 1 architecture scorecard 15/15: routing + pin +
retrieval; PHASE 2 real generation). PHASE 1 caught the Kyoto/Japan over-interception bug above.
C. Retrieve context (only for questions / referential imperatives) β _match = BGE-small semantic
cosine (runtime/rag.py, 33M, CPU), lexical-overlap fallback if BGE unavailable. Handles paraphrase
("which spot did I leave the car?" β "parked in bay 12", "how many people?" β "guests"):
- L1 same-session β semantic top-K (cap 4) of the logged facts, kept within 0.12 cosine of the best (a strong single match drops distractors), ties broken by recency so a correction wins.
- L2 prior-session β same semantic match over the on-disk profile (survives a fresh process).
- self-contained math (has digits) β solve it, don't retrieve.
- referential imperative ("recompute the total") β inject the recent fact log + compute.
- else β L3 web (DuckDuckGo β Wikipedia, keyless).
Dβ². Recall = clean-context quote β for a recall turn that retrieved a fact chunk, the answer is generated
by ChatSession._clean_quote() from an ISOLATED context (the retrieved chunk + question only, temp 0.2, no
soft prompt, no conversation history), with the groundedness re-roll. Why: in a long multi-turn chat the
distractor turns' CoTs compressed into the SP can hijack a recall into computing/confabulating (e.g. quoting a
distractor's number); an isolated context removes that bias. Validated: deep recall after 6 distractors went
from a stochastic 0/3β3/3 to a reliable 3/3 across repeats (evals/recall_distract.py). The retrieval
(pins/L1) was already firing; this fixed the recall generation discipline. (Raw SP alone confabulates 0/3,
and even full-KV base fails to self-recall β so the external retrieval injection is essential, not a crutch.)
D. Inject + instruct β matched facts go verbatim into the raw window:
- recall β "quote the value verbatim; if it was corrected, use the most recent."
- compute β "use the fact(s) and compute." (and the groundedness check is skipped for math).
E. Generate β free generation (no think-budget), temp 0.6, rw 1024; bounded only by a 240 s wall-clock timeout + 2000-token per-turn cap + a 6-identical-token loop guard.
F. Make a real chat answer (2-pass) β think freely; if the post-</think> answer is weak
(empty / a "β¦:" preamble / just a \boxed{}), inject "Final answer:" and continue, so a clean
answer always surfaces (e.g. "4000 square meters.", not an empty reply).
G. Quality gate β if the answer is empty/timed-out, or (for a recall turn) not grounded in the
retrieved context, delete the CoT and re-roll (retries=2; cheap β the hypernet recomputes the SP).
Memory tiers: L1 same-session (in-RAM facts) β L2 prior-session (disk jsonl) β L3 web. Verified:
app_demo recall 3/3, routing 5/5, web+math hit; 5- and 10-rally consistency (corrections persist; the
oldest fact fades after ~many turns). Knobs: temp 0.6, rw 1024, retries 2, TIMEOUT_S 240,
MAX_NEW 2000. Limits: bounded-recency (turn-1 facts fade in a long chat β the specificity head pins
specific values but a rolling cap-12 buffer still ages out; broad summary-memory not built), and the 1.5B
base ceiling (mis-reads/mis-computes hard problems).
0.5 Performance vs the full-KV base (GSM8K, 2026-06-10)
Does bounded SP-evict compression cost accuracy vs keeping the whole CoT in KV? Measured base (rw=8000,
full-KV) vs SP-evict (rw=1024, the shipping window) on GSM8K, interleaved, identical decoding (evals/ gsm8k_eval.py, evals/gsm8k_multiturn.py). Yardstick = the base model's OWN accuracy in-harness (NOT 100%).
- Single-turn (n=12): base 6/12 vs SP 8/12; base-only losses = 0 (SP never lost a problem to base). SP CoT is shorter (med 1378 vs 1894 tok) and hits the 2000-tok cap less (2 vs 6) β compression keeps generation focused. (Caveat: short problems fit the 1024 window β SP barely compresses β not a hard test.)
- Multi-turn (n=10, turn-2 depends on turn-1 which scrolled out of the window β the real compression test):
base 6/10 vs SP 6/10 (TIED) once the math-turn pin injection is in. 9/10 failures are
None= TERMINATION (240s timeout / 2000-tok cap hit mid-CoT) β and they hit BASE (4Γ) as much as SP (3Γ). base has full-KV verbatim context and fails the SAME way β the bottleneck is generation TERMINATION, NOT compression. CapturedNonecases re-run correctly β the model can solve them; it just often doesn't stop in budget (R1 CoT is long). - Verdict: on the fair in-harness comparison there is no clear accuracy loss from compression β SP β base; what's left is the 1.5B's own ceiling + a termination artifact shared by base. (Low absolute % is the cap + 4-bit, not an SP deficiency.)
- Termination fix (
_gen_once, 2026-06-10): on a mid-CoT timeout/cap with no usable answer, close</think>and force "Final answer:" on a small own budget (64 tok / 45 s) to salvage an answer from the partial CoT instead of returning None. Helps base & SP equally.
1. The composite pipeline (per user turn)
user turn
β
βΌ
classify(intent) βββΊ chit-chat ββββββββββββββββΊ answer (no retrieval)
β fact βββββββββββββββββββββββΊ save (instant "Got it β saved."), persist?βdisk
βΌ question
TieredMemory.retrieve (first tier over the bar wins)
ββ L1 same-session (in-memory turns)
ββ L2 prior-session (disk jsonl β survives a fresh process)
ββ self-contained math? β solve, DON'T web-search
ββ L3 web (DuckDuckGo β Wikipedia, urllib+bs4, no headless browser)
β
βΌ inject matched text verbatim into the raw window ("answer from Context only")
bounded SP-evict generation (force-<think>, FREE gen; 2000-tok cap + 240s timeout + loop guard β see Β§6.5)
β
βΌ answer empty / timed-out / (grounded) contaminated? ββ yes βββΊ delete CoT, re-roll (retries=2)
β (gen/kept hold the convo; SP recomputed each step)
βΌ no
answer (only post-</think> shown; <think> scratchpad hidden)
2. App-level file map
| file | role |
|---|---|
tiered_rag_mlx.py |
operational core β TieredMemory (L1/L2/L3 cascade + pins working memory, fuzzy/semantic match), ChatSession (bounded SP-evict gen + injection + groundedness retry), learned router (intent_of), specificity head (specific_spans, pin gate), is_grounded. Run directly β L1/L2/L3 composite test. |
evals/specificity_train.py Β· evals/specificity_clf.joblib |
independent specificity head β train + the ~20 KB BGE linear probe that detects pin-worthy specific/proper info (CV AUC 0.996). |
evals/specificity_probe.py |
the bake-off that selected BGE-probe over tpc-fragmentation / surprisal-proxy for specificity detection. |
evals/pin_test.py |
operational test for the specificity head + pinning (detector unit 9/9; value-in-question β later-recall flow). |
app_demo_mlx.py |
two-session application simulation + readiness report (onboardingβdisk, then a fresh "today" session over a mixed 8-turn conversation). |
chat_mlx2.py |
recommended interactive chat (recipe: temp 0.6, no system prompt, force-<think>, free generation + empty-answer re-roll, large rw). |
mem_rag_mlx.py |
conversation-memory RAG A/B (recall a fact the SP compressed away; 0/2 β 2/2). |
rag_web_mlx.py |
web-grounded answer (DuckDuckGo β Wikipedia) windowed-RAG. |
runtime/web_search.py |
keyless search backends β DuckDuckGoSearch (urllib+bs4, recommended), WikipediaSearch, PlaywrightBingSearch (brittle, optional), Tavily/Brave (API). |
pooler_mlx.py Β· gen_mlx.py Β· sp_mlx.py Β· mt_mlx.py Β· build_fft_hf.py |
MLX port of the pooler + SP-evict rollout, single-turn / battery / multi-turn runners, FFTβHF builder. |
3. Run it
pip install mlx mlx-lm transformers torch huggingface_hub beautifulsoup4 lxml # no Playwright needed
python build_fft_hf.py && python -m mlx_lm convert --hf-path ./fft_hf --mlx-path ./fft_mlx4 -q --q-bits 4 --q-group-size 64
python app_demo_mlx.py # full two-session app simulation + readiness report
python tiered_rag_mlx.py # L1/L2/L3 tier-routing composite test
python chat_mlx2.py 1000 # interactive chat
Minimal integration (the whole app loop):
import tiered_rag_mlx as T
mem = T.TieredMemory("user_profile.jsonl")
chat = T.ChatSession(mem, rw=512)
def on_message(text):
if T.classify(text) == "fact": # store + instant ack, no generation
return chat.turn(text, store="persist" if T.wants_persist(text) else "session", ack_only=True)[0]
return chat.turn(text, store="none")[0] # free gen; turn() retries empty/contaminated
4. What's verified (composite tests)
| test | result |
|---|---|
Tier routing (app_demo, tiered_rag) |
5/5 deterministic β chit-chat / fact-save / math / L1 / L2 / L3 all dispatch correctly, every run |
MEMORY recall hit-rate (L1/L2, app_demo) β the architecture's metric |
reliable β the SP/memory's job (hold & surface a fact across turns) lands consistently |
| web (L3) / math β orthogonal, reported for reference | stochastic: web = retrieval quality + base extraction; math = pure base-1.5B arithmetic with the query fully visible (no compression) β NOT a memory test, out of scope |
Scoring note (don't repeat my mistake): the old "answer correctness X/5" lumped three unrelated things. What this system is measured by is MEMORY recall (L1/L2). A math miss is the base model's arithmetic, not an architecture failure β the math query is fully visible, nothing is compressed/recalled. Also: "HIT" = gold substring present (a hit-rate, a proxy), not a verified correctness rate; and any single
app_demorun is 1-sample/turn (2β4/5 swing) β use N-run averages for real numbers. | same-session recall (L1) | β "B3, spot 47" | | prior-session recall (L2, from disk, fresh process) | β "POL-55821", "Aki" | | web grounding (L3) | β "Sam Altman" (after contamination retry); Mt Fuji "3,776.24 m" | | conversation-memory RAG (mem_rag) | β 0/2 β 2/2 (recover SP-compressed facts) | | reasoning correctness (5 Dolphin-R1 word problems) | SP-evict β 3/5 vs base/full-KV β 4/5 β same ballpark; the ceiling is the base 1.5B, not compression (Β§6.5 #7). Chat-answer fix surfaces clean answers ("4000 square meters.", "2 loads.") | | typical miss | base-model mis-read / mis-compute, or verbose / weak instruction-following (not a memory or compression failure) |
5. Engineering findings & fixes (the bugs the composite tests caught)
- Headless Bing is brittle β it serves a JS shell / dictionary bot-detection page. Switched the
default web backend to DuckDuckGo via plain
urllib+bs4(server-rendered results, no Chromium). - Non-questions web-searched & contaminated ("my room is 1408" β "1408 Film Locations" β "140813") β question-gate: only information-seeking turns retrieve.
- Self-contained math got web-searched (would contaminate the calc) β math-gate: solve, don't search.
parkβparked, 1-word questions missed β fuzzy prefix overlap + trusted-local threshold.- Fact-saves produced rambly "(no answer)" β instant ack (store without generating).
- Recency bleed ("Aki" answered for the CEO) β inject "answer from the Context block only, ignore
earlier conversation" + a groundedness guard (
is_grounded) that resamples when the answer's salient novel token isn't in the retrieved context (catches out-of-context / empty / question-echo).
6.5 Generation control β the long investigation (read before touching _gen_once)
How to bound/terminate generation was the hardest sub-problem. Final state and the reasoning, so the next person doesn't re-run the same dead ends:
Current committed behaviour (tiered_rag_mlx.ChatSession):
- Free generation β no in-loop "think-budget" (we do not force
</think>after N tokens). - 2-pass chat answer (
_gen_once) β pass 0 thinks freely; if the post-</think>answer is weak (empty / ends in ':' / just a\boxed{}), pass 1 injects "Final answer: " and continues, forcing a clean chat answer. This fixed the dominant chat failure: the R1-distill routinely traps its answer inside<think>or emits only a preamble, so the user saw an empty reply even when the answer was computed. After the fix, math word-problems surface clean answers ("4000 square meters.", "2 loads.", "72 female guestsβ¦") instead of empties._extract_answeris the fallback (surfaces a boxed-in-think). - Backstops only: a 2000-token per-turn output cap (state hygiene, below) + a 240 s wall-clock timeout + a 6-identical-token degeneration guard.
- Delete-CoT re-roll (
turn(retries=2)): if an answer is empty / timed-out / (for grounded turns) contaminated, roll back the turn's tokens and resample. Cheap because the hypernet recomputes the SP from the token list each step β no KV surgery.
What we learned (and retracted):
- Termination is temperature-stochastic, NOT a model incapacity. The same prompt sometimes EOSes in ~365 tokens, sometimes rambles. So delete-CoT re-roll genuinely works for short-reasoning turns (recall/chat): a fresh sample usually terminates.
- The per-turn output cap is load-bearing for multi-turn. Fully-uncapped free-gen lets one runaway
turn (a greeting hit 3000+ tokens) flood the bounded window/SP and poison later turns (recall
bled wrong values, app dropped to 1/5). The 2000 cap stops that. It is state hygiene, not
think-shaping (it doesn't force
</think>). - DO NOT re-add
<think>-compaction via re-tokenization. An attempt to strip prior<think>spans from history bydecode β regex β re-encodeeach turn corrupted the context and caused reliable non-termination (recall turns went 13β52 s HIT β 70β153 s FAIL). It was reverted. If you want clean history, track think-span token ranges at generation time and slice ids β never round-trip through text. - Hard math fails by OVER-thinking, not non-termination. On "$1000 at 5% compound, 3 yrs" the model
computes the correct 1157 mid-
<think>, then second-guesses itself over ~4000 tokens into a wrong\boxed{1000}(and the box lands inside</think>, so the post-</think>answer is empty). The 2000 cap also truncates it before it closes. So: short-reasoning β free+re-roll is right; long-reasoning β the model self-degrades. - Temperature is load-bearing β and 0.4 was too low. A controlled single-turn sweep of the
compound-interest problem: temp 0.3 β 5632 tok, no
</think>, timeout (deterministic over-think rut); temp 0.9 β 4843 tok, no</think>, timeout + wrong\boxed{220}(wanders); temp 0.6 β 1730 tok, closes</think>in 71 s, correct\boxed{1157.63}. Both extremes over-think; 0.6 (DeepSeek-R1's recommended 0.5β0.7) is the sweet spot.TEMPwas 0.4 (set for "faithful copying"), which contributed to the math over-thinking β now 0.6. So #4's "math self-degrades" was partly a too-low-temp artifact, like the compaction bug. (Caveat: this is the isolated result; the multi-turnapp_demoscore is high-variance run-to-run β 2β4/5 β and temp is not a silver bullet there.) - No clean automatic fix for the long-reasoning tail. A fixed think-budget would catch the early-correct answer β but you can't tell a priori whether a long chain is over-thinking or genuinely necessary, so a global budget would harm problems that truly need long reasoning. Left as free-gen. Real fix = a calculator/tool for arithmetic, or a better base.
- The accuracy ceiling is the base 1.5B, NOT the SP compression. Eval on 5 Dolphin-R1 reasoning
problems (grade-school word problems), 2-pass chat-answer on: SP-evict (rw=512, compressed) β 3/5
vs base (rw=8000, nothing compressed, full-KV) β 4/5 β same ballpark, within 1-sample noise. So the
modest ~60-80% correctness is the 1.5B's own level on this corpus, not a compression artifact;
misses are the base model's mis-reads / mis-computes, not lost context. (Single sample/turn β use N-run
averages for a real gap. The chat-answer fix in
_gen_onceis what makes both surface clean answers.)
6.7 Organic multi-turn consistency (_matches, auto-log, compute-vs-quote)
A flowing conversation with dependencies and a correction β "bookshelf, 5 shelves" β "each 80 cm, total
width?" β "paint it blue" β "make it 6 shelves, redo the width" β "remind me the colour and shelf count" β
totally failed before (the model lost the specifics to the SP and hallucinated: "75 books", "gray",
"12 m wall", "15 shelves"). Root cause: organic conversational state flows only through the lossy
SP-compressed KV; it was never written to the retrievable memory. Fix (all in tiered_rag_mlx):
- Auto-log statements β
turn()writes every non-question user turn tomem.session(L1) even whenstore="none", so the conversation's facts become retrievable verbatim. - Multi-fact retrieval β
_matchesreturns the top-cap(β3) near-top-scoring chunks (not just the single best), chronological, so a 2-fact question ("colour AND count") gets BOTH, and a later correction ("6 shelves instead of 5") is read after the original. A strong single match (a unique code) still drops weak distractors (relative-score threshold). - Compute-vs-quote β a math turn that pulls a fact from context gets a "use the fact and compute" instruction (and skips the groundedness check), while a recall turn keeps "quote verbatim, use the most recent if corrected".
- rw default 1024 β wider window keeps more recent dialogue verbatim.
After the fix the same conversation: width computed from the recalled count (400, then 480 after the 5β6 correction), and the colour recalled correctly (blue, vs "gray" before). Verified no app_demo regression (routing 5/5, recall/web/math hold). Residual glitches (a trivial statement that over-thinks to timeout; "how many shelves" answered as "480 cm") are 1.5B response-quality quirks, not the memory.
Scaling to MANY rallies (10-turn party conversation, 2 corrections). Pushing further surfaced the final design β and two dead ends to avoid:
_is_factlikegate β only log statements that assert a value/attribute. Logging questions or chit-chat ("explain compound interest") poisons recall: it bled "compound interest"/"1157.63" into "where did I park?". Gate it out.- Lexical top-K, NOT full-log injection. Injecting the whole recent log on a recall makes the context
large and the model recency-bleeds the most recent number (park β "1157.63"). Keep the injected
context small (
_matches, capβ4) with ties broken by recency so corrections still win. compute_refβ a referential imperative ("recompute the total", "redo it") isn't a question and has no lexical anchor β inject the recent log + a compute instruction.- Result on the 10-rally party: a mid-conversation 2-fact check is perfect ("chocolate, 10 guests"); the final 4-fact check is 3/4 β both corrections held (themeβdinosaur, guests 8β10, cakeβchocolate) and only the oldest fact (turn-1 "Mia") faded. That tail is the fundamental bounded-recency limit: very old facts drop after ~many turns; fixing it needs fact-pinning / summarisation (not done). Verified no app_demo regression (recall 3/3, web+math hold).
6. Known limits (the app-quality boundary β intentionally not chased further)
- Math / long reasoning β the model over-thinks and self-degrades on hard chains (Β§6.5); pair with a calculator/tool. Short reasoning is fine.
- Groundedness is necessary-not-sufficient β rejects out-of-context / empty / echo answers, but can't catch a wrong in-context span (right page injected, wrong fact quoted). Needs a span-constrained extractor or NLI/LLM judge.
- Retrieval is fuzzy-lexical β paraphrased questions with no shared words can miss; swap in a BGE
sentence encoder (
runtime/) for semantic matching. - Latency β ~10β25 s/turn (always thinks); fact-saves instant; web +1β5 s. Free scrapers rate-limit bursts (back off + Wikipedia fallback; use an API key for production).
7. Extension points (if picked up later)
- Semantic retrieval: replace
_best/_overlapwith a BGE sentence encoder (theruntime/pipeline already has BGE rerank + a verify/refuse gate β promote it intoTieredMemory). - Tool use: a calculator tool for the math turns; a date-guard for "today/now" web queries.
- Stronger contamination judge: span-constrained decoding or a tiny NLI check for wrong-span answers.
- iPhone: retrieval ports directly (
URLSession+SwiftSoup); the model needs the pooler + SP-evict rollout re-implemented onmlx-swift(numerics proven; it's a port).
8. Related docs
OPERATING.mdβ full runbook (install, recipe table, runners, tiered stack, contamination guard)README.mdβ portfolio writeup (idea, models, RAG, results)RESULTS.mdβ honest verification numbersHANDOFF.mdβ research handoff (training, deepKL, architecture)