<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: PerryLink</title>
    <description>The latest articles on DEV Community by PerryLink (@perrylink).</description>
    <link>https://dev.to/perrylink</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4169985%2F46e80c90-b59d-4d04-94eb-7fd34b4c3945.png</url>
      <title>DEV Community: PerryLink</title>
      <link>https://dev.to/perrylink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/perrylink"/>
    <language>en</language>
    <item>
      <title>Local 144.3M or a hosted API for your approval gate? Three questions and one honest table.</title>
      <dc:creator>PerryLink</dc:creator>
      <pubDate>Tue, 13 Oct 2026 12:05:40 +0000</pubDate>
      <link>https://dev.to/perrylink/local-1443m-or-a-hosted-api-for-your-approval-gate-three-questions-and-one-honest-table-1p56</link>
      <guid>https://dev.to/perrylink/local-1443m-or-a-hosted-api-for-your-approval-gate-three-questions-and-one-honest-table-1p56</guid>
      <description>&lt;p&gt;Half of what my coding agent "thinks" about overnight isn't thinking. It's permission questions: &lt;em&gt;can I run this command? should this ticket go to billing or support? is this output safe to post?&lt;/em&gt; Each one is a small, structured decision — and if you serve each one with a chat API, you pay in three places: tokens per call, a network round trip, and the decision context leaving your machine.&lt;/p&gt;

&lt;p&gt;Cloudflare's Clef release earlier this month put decision models on a lot of radars, and the community write-ups on dev.to are worth reading. This post is about the other lane: a small decision model you run yourself. I'll use &lt;a href="https://huggingface.co/Phocinae/Phocinae-Largha-150M-v1" rel="noopener noreferrer"&gt;Phocinae-Largha-150M-v1&lt;/a&gt; — 144.3M parameters, Apache-2.0 — as the worked example, and show the selection logic I use for an approval gate: what stays local, what still goes to a hosted model, and the numbers behind both, with their caveats attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a decision model actually does
&lt;/h2&gt;

&lt;p&gt;It doesn't chat. You give it a &lt;em&gt;state&lt;/em&gt; (a short description of the situation) plus &lt;strong&gt;typed questions&lt;/strong&gt; — &lt;code&gt;noul&lt;/code&gt; (yes/no), &lt;code&gt;choice&lt;/code&gt; (pick one of N), &lt;code&gt;score&lt;/code&gt; (2–10) — and one forward pass returns an answer per question with a confidence. The answer set exists before the call: nothing is generated, so nothing drifts. In the model card's own usage example, a decision reports &lt;code&gt;input_tokens: 146, output_tokens: 0&lt;/code&gt; — zero output tokens, because there is no output text.&lt;/p&gt;

&lt;p&gt;That's the point for a gate: deterministic, auditable, replayable decisions — the card's own phrasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions that pick the lane
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. How often will this decision fire?&lt;/strong&gt; If the same shape of decision comes up a few dozen times a day, a hosted API call is a fine trade — rare, varied, convenient. If it fires thousands of times a day — approvals, triage, screening, routing — the arithmetic changes shape. A local forward pass has no per-call fee; the cost moves to hardware you control, and it doesn't grow with volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Can the input leave the machine?&lt;/strong&gt; Approval gates see command lines, file paths, log tails, sometimes customer content. If policy — or common sense — says that context stays on the box, the decision has to be made on the box. This is the hardest filter of the three, because there's nothing to negotiate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is the answer set closed?&lt;/strong&gt; If the possible answers are enumerable in advance — allow / ask / deny, queue A or B, a 2–10 severity score — a typed decision model fits, and generating prose to pick from a closed list is waste. If the answer is genuinely open-ended — "write the incident summary", "explain this stack trace" — keep the big model. The dividing line isn't model size; it's whether the answer exists before you call.&lt;/p&gt;

&lt;p&gt;One practical footnote before the numbers: &lt;strong&gt;latency and hardware&lt;/strong&gt;. On GPU fp16 the published p50 is 21.0 ms per decision (RTX 5090). On CPU it's slower — 1.64 s per case (one state plus five questions), single-threaded — or 8–20 decisions/second with 8 threads. That's background-gate territory, not interactive-typing latency. And temper the "local is automatically faster" instinct: in the third-party benchmark below, the hosted API measured 225 ms p50 while the author's own local GPU setup measured 315 ms p50 on a 3090. Local's wins are about owning the box and skipping the network — not about the model being small.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, including the awkward ones
&lt;/h2&gt;

&lt;p&gt;Same-protocol readings, each row carrying its own annotation — all from the public model card:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;reading (typed decisions)&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;th&gt;as published&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Largha-150M-v1, en&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.906&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;400 cases / 2000 decisions; specialist fitted on that dataset's train split; the card notes this sits above the 0.735 teacher self-agreement reference, and the dataset card flags far-above-reference scores as label-specific overfitting risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laya&lt;/td&gt;
&lt;td&gt;0.766&lt;/td&gt;
&lt;td&gt;self-measured, native interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JEV&lt;/td&gt;
&lt;td&gt;0.727&lt;/td&gt;
&lt;td&gt;generalist, zero-shot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;meraGPT&lt;/td&gt;
&lt;td&gt;0.768&lt;/td&gt;
&lt;td&gt;generalist, zero-shot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Lineage note:&lt;/strong&gt; the fine-tune mix includes a 500-item error-correction pool mined from model predictions on typed-decisions test rows — full details in the card's Training-overlap disclosure (corrected 2026-10-10). We report 0.906 as a fitted reading, not a zero-shot result.&lt;/p&gt;

&lt;p&gt;And the awkward one, in the same breath: &lt;strong&gt;JevBench public-231 reads 0.5455 (126/231) — below its 58.4% acceptance gate, not passed.&lt;/strong&gt; That number comes from a public 231-item set measured in a different environment and protocol than the 400-case self-eval above — the two don't merge. The card publishes it as measured, and those JevBench rows were held out of training.&lt;/p&gt;

&lt;p&gt;There's a zh reading too: 0.848 — measured on machine-translated cases, and the training mix itself includes ≈2,400 machine-translated Chinese rows alongside ≈1,400 native ones. Treat it as a fitted Chinese result, not zero-shot transfer.&lt;/p&gt;

&lt;p&gt;For outside evidence, the most useful public write-up I've found so far ran 42 real decisions from one person's overnight coding agent: Jev (hosted) at 71.4% overall vs Clef-Flash (9B, run locally on his 3090) at 66.7% — third-party numbers, his protocol, link below. His framing is the one worth keeping: the exercise doesn't tell you which model is best; it tells you which calls you're wasting money on.&lt;/p&gt;

&lt;p&gt;Same post, for scale contrast: Clef-27B in bf16 is estimated at ~54 GB of VRAM (a third-party estimate), while Largha's weights are 288.6 MB with a 1.6 GB inference peak, and the project's deployment doc lists a 4 GB-RAM, no-GPU floor. Those are lane differences — different machines for different jobs — not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mix, don't replace: the escalation gate
&lt;/h2&gt;

&lt;p&gt;The design that keeps both sides honest is a confidence gate. Every decision comes back with a calibrated confidence; at τ=0.6 the gate escalated 45.0% of official-set decisions and cut LLM calls by 55.0%, with kept-subset accuracy moving 0.906 → 0.9936 (full routed accuracy 0.8135). The fine print is on the card, verbatim: &lt;em&gt;"τ swept on the eval set — re-scan per domain."&lt;/em&gt; Your traffic will want its own threshold; tune it and re-measure.&lt;/p&gt;

&lt;p&gt;So local vs hosted isn't a fork. The small model takes the steady state, and the hosted model gets exactly the calls the gate flagged as uncertain, novel, or high-stakes. Escalation isn't failure — it's routing.&lt;/p&gt;

&lt;p&gt;One honesty detail that matters if you build on the gate: on the card, the shipped confidence column carried an ECE of 0.2519 (en); the bundled recalibration column brings it to 0.0168. If you set thresholds from confidence, recalibrate first — the project ships the column and the temperature constants to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read these before you pick
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool routing is the weak spot.&lt;/strong&gt; The project's server confuses semantically close tool names — the card's reproducible case returns &lt;code&gt;fetch_url&lt;/code&gt; instead of &lt;code&gt;web_search&lt;/code&gt; at ≈0.33 confidence. Low-confidence picks are escalate-worthy, never final.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a safety oracle.&lt;/strong&gt; The card's words, and they matter at a gate: first-line signal with escalation — pair it with your own rules and a human path, never as the sole guard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-document reasoning is out of scope.&lt;/strong&gt; Base context is 8192 tokens, and "not for long-document reasoning" is on the card's "what it is not" list. Keep gate inputs to states and questions, not articles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;These are single-setup measurements.&lt;/strong&gt; Latency and quality numbers come from the release run; your hardware and your traffic will differ, and they deserve their own measurement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The card self-corrects in public.&lt;/strong&gt; A training-overlap disclosure was corrected on Oct 10, and the current numbers were re-measured on the current weights with a revision history. Read the disclosures before citing any single number — including mine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's worth trying (and what isn't)
&lt;/h2&gt;

&lt;p&gt;Try a local gate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the decision is &lt;strong&gt;structured and repetitive&lt;/strong&gt; — command approvals, ticket triage, output screening, document routing (the project's public gallery has 27 scenario demos along these lanes);&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;inputs can't leave the machine&lt;/strong&gt; — on-prem, air-gapped, regulated context;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;volume makes per-call fees the wrong shape&lt;/strong&gt;, or you want the steady state to work offline;&lt;/li&gt;
&lt;li&gt;or you want a &lt;strong&gt;first-line gate that escalates&lt;/strong&gt; its uncertain cases instead of answering everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep a hosted model when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the answer is &lt;strong&gt;open-ended text&lt;/strong&gt; — summaries, explanations, long-document reasoning;&lt;/li&gt;
&lt;li&gt;the decisions are &lt;strong&gt;rare and varied&lt;/strong&gt;, with no pattern to amortize;&lt;/li&gt;
&lt;li&gt;or the call is &lt;strong&gt;the escalated tail&lt;/strong&gt; — the part the gate explicitly refused to guess on.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;phocinae-server
hf download Phocinae/Phocinae-Largha-150M-v1 &lt;span class="nt"&gt;--local-dir&lt;/span&gt; ./largha
&lt;span class="nv"&gt;PHOC_MODEL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./largha python &lt;span class="nt"&gt;-m&lt;/span&gt; phocinae.main   &lt;span class="c"&gt;# 127.0.0.1:8155&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third-party benchmark referenced above: &lt;a href="https://dev.to/prodbymarcu/i-benchmarked-cloudflares-new-open-decision-model-against-the-hosted-api-its-trying-to-replace-2ded"&gt;I benchmarked Cloudflare's new open decision model against the hosted API it's trying to replace&lt;/a&gt; — 42 cases, his protocol, his caveats; read it before quoting my one-line summary of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/Phocinae/Phocinae-Largha-150M-v1" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Model card and full numbers&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;If you try it, the failure report I'd most want to read is in the tool-routing caveat above — that's where it breaks first. And if your selection math lands somewhere else, that's the interesting part: the gate is supposed to be re-scanned per domain, including yours.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transparency: this post was drafted with AI assistance; every number above comes from the public sources linked here, and no new benchmarks were run for this piece.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>localai</category>
    </item>
    <item>
      <title>We measured our plugin's "token savings". At τ=0.6, it was 8.5%.</title>
      <dc:creator>PerryLink</dc:creator>
      <pubDate>Tue, 13 Oct 2026 12:05:35 +0000</pubDate>
      <link>https://dev.to/perrylink/we-measured-our-plugins-token-savings-at-t06-it-was-85-2ln3</link>
      <guid>https://dev.to/perrylink/we-measured-our-plugins-token-savings-at-t06-it-was-85-2ln3</guid>
      <description>&lt;p&gt;"How much does it save?" is an easy question to answer badly. The same plugin, on the same 400-case workload, produced &lt;strong&gt;86.1% fewer billed tokens&lt;/strong&gt; at one threshold — and &lt;strong&gt;8.5%&lt;/strong&gt; at another. Both numbers are true, and neither means anything without its conditions.&lt;/p&gt;

&lt;p&gt;This post is about the measurement behind them: why "token savings" is hard to pin down inside an agent pipeline, what we count and at which granularity, where each digit comes from, and how to rerun the whole thing on your own traffic.&lt;/p&gt;

&lt;p&gt;The setup. dsh-phocinae is a local decision plugin for the DeepSeek Harness agent (dsh): two tools, &lt;code&gt;phocinae_ask&lt;/code&gt; and &lt;code&gt;phocinae_gate&lt;/code&gt;, backed by a small local model that answers typed decision questions in a single forward pass. The workload for this measurement is the published typed-decisions row set — 400 cases, 2,000 decisions — with the local model (Phocinae-Largha-150M-v1, CPU, fp32) paired against a flash-tier hosted model (deepseek-flash, temperature 0). Hosted-side token counts come from the API's own &lt;code&gt;usage&lt;/code&gt; counters; everything else was measured with the scripts under &lt;code&gt;bench/&lt;/code&gt;, and the raw results are committed under &lt;code&gt;bench/results/&lt;/code&gt;. The run is a snapshot — plugin 0.2.1, dated 2026-10-08. "Savings" throughout means billed hosted tokens avoided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "token savings" is hard to measure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;There is no single meter.&lt;/strong&gt; The hosted provider meters the tokens it bills you. A local decision bills nothing — but it is not free. Its tool definitions ride in every request, and each call's arguments and rendered result enter the transcript, where they get billed like any other token on the next request. "Saved" always means "not sent", and you have to be explicit about what you counted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The escalation unit is not the decision.&lt;/strong&gt; When escalation happens, the hosted model is called with a whole case — one state plus its five typed questions — so if any single decision in a case is unsure, the case is re-answered wholesale. Counting "decisions kept local" and counting "cases that never hit the API" are two different measurements that will give you two different percentages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The threshold is a product decision.&lt;/strong&gt; The plugin produces an &lt;code&gt;escalate&lt;/code&gt; signal, not a savings guarantee. τ — the confidence below which an answer is marked unsure — is chosen by the caller, and it is the difference between the 86.1% row and the 8.5% row below. A savings claim without a τ is half a sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workload decides.&lt;/strong&gt; Ours is a machine-readable question set, not a live multi-turn agent session. That is a limitation with a number attached; more on it at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement design: three counters
&lt;/h2&gt;

&lt;p&gt;We count tokens in three places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Per request — the fixed part.&lt;/strong&gt; While the plugin is loaded, both tool definitions ride in every request's tool list: 473 tokens for &lt;code&gt;phocinae_ask&lt;/code&gt; plus 212 for &lt;code&gt;phocinae_gate&lt;/code&gt;, or &lt;strong&gt;685 tokens per request&lt;/strong&gt;. These were not estimated; they were counted with the provider's own tokenizer. And note the scope: that number is the plugin's addition to a request, not the request itself.&lt;/p&gt;

&lt;p&gt;The approval gate is the exception. It runs as a pre-execute hook, not as a message, so it adds &lt;strong&gt;zero conversation tokens&lt;/strong&gt; — whatever it costs in wall time, it never enters the transcript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Per decision — the round trip.&lt;/strong&gt; One local call costs 99 tokens of arguments out and 135 tokens of rendered result back: &lt;strong&gt;234 tokens per decision&lt;/strong&gt; in steady state. A decision that is the first in its session also carries the fixed 685, for 919 total. The fixed counter amortizes from there; how fast is the next section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Per case — the unit the hosted model is actually called with.&lt;/strong&gt; Every hosted response reports &lt;code&gt;usage&lt;/code&gt;, and across the 400-case run those counters summed to 1,222,211 tokens (290,606 in, 931,605 out — about 76% of the bill is the model writing its answers). That is &lt;strong&gt;3,055.5 tokens per case&lt;/strong&gt;. The local side of the same run: no billed tokens, by construction. A decision is a forward pass, not a generation — the server's usage field reports zero output tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results, with the conditions attached
&lt;/h2&gt;

&lt;p&gt;Read every number below as one row of a table whose columns are: workload, model pair, τ, and whether the plugin's own footprint is included. All savings here are counted in billed hosted tokens, at the case level. Accuracy is reported next to savings, because a cheap wrong answer is not a saving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pure local path.&lt;/strong&gt; Every decision answered locally, nothing escalated. Against sending everything to the hosted model, the local path saves 69.9% of tokens at one decision, 90.1% at ten, and &lt;strong&gt;92.3% at a thousand&lt;/strong&gt;. It saturates near 92%, not 100% — the missing part is exactly the fixed counter: tool definitions and round trips stay in the caller's context no matter how large the session grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid at τ=0.4&lt;/strong&gt; — the row we would point you to on this workload. 27 of 400 cases were escalated (6.8%). Billed tokens: 82,499 instead of 1,222,211. Count the plugin's own footprint — 685 per request plus 234 per kept decision — and the saving is &lt;strong&gt;86.1%&lt;/strong&gt;. Accuracy: 0.7305, against 0.7085 hosted-only and 0.7320 local-only. On this workload, escalation was close to a wash. (Both accuracies come from this protocol's rendering: paired and directly comparable to each other, not the model card's standalone figures.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid at τ=0.6&lt;/strong&gt; — the honest row, and the plugin's shipped default. 363 of 400 cases were escalated (90.8%). Model-only saving: 9.2%. With the plugin's footprint included: &lt;strong&gt;8.5%&lt;/strong&gt;. Accuracy also slipped to 0.7115 — below the pure-local 0.7320 on this workload, because escalated cases are re-answered wholesale and the hosted model is weaker on this workload's choice-type questions (0.6793 vs the local model's 0.7529, on 1,400 of them).&lt;/p&gt;

&lt;p&gt;Why does 0.6 escalate so much? Because the confidence distribution is concentrated near the middle — the median decision sits at 0.6294 — and one unsure decision is enough to flag its whole case. 851 of the 2,000 decisions (42.6%) sit below 0.6; counted per case, that escalates 90.8% of cases. A threshold that sounds conservative is, on this distribution, nearly an opt-out.&lt;/p&gt;

&lt;p&gt;Cost, at the paired model's published unit prices ($0.28 per million input tokens, $1.10 per million output): the 400-case run comes to $1.1061 hosted-only and $0.0747 at τ=0.4. A local-only run has no billed tokens.&lt;/p&gt;

&lt;p&gt;One discipline note before the digits drift: two savings figures from the same run can still be incomparable if one counts decisions routed and the other counts tokens billed. Every number here carries its counter, its unit and its threshold. When you see a savings headline without those three labels, you don't yet know what question it answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did not measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live agent sessions.&lt;/strong&gt; 400 machine-readable cases is a bench, not a session trace. Real sessions have long histories, more tools, more turns. The next experiment we owe ourselves: the same task list (30+ tasks) with the plugin on and off, aggregating the API's &lt;code&gt;usage&lt;/code&gt; and counting escalations, false blocks and task success alongside tokens. Until that runs, treat the numbers above as a calibrated bench, not a promise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU latency.&lt;/strong&gt; Every latency figure in this post is CPU, fp32, one machine: 42.6 ms for a local decision's HTTP round trip, ~21 ms mean to screen a command through the gate, 0.02 ms for an allow-listed command. A GPU figure also circulates — fp16, p50 21.0 ms on an RTX 5090. That one is the model release's published number, not a measurement of ours; we have not reproduced it on this bench — that measurement is still pending.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability of τ.&lt;/strong&gt; 0.4 is not a universal constant; it is what this workload's confidence distribution produced. Re-scan on your traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce it on your own traffic
&lt;/h2&gt;

&lt;p&gt;Everything above is re-derivable from &lt;code&gt;bench/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. serve the local decision model&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; phocinae.main

&lt;span class="c"&gt;# 2. the token-savings run — point it at your rows and your model pair&lt;/span&gt;
node bench/token-savings.mjs &lt;span class="nt"&gt;--rows&lt;/span&gt; 400 &lt;span class="nt"&gt;--dataset&lt;/span&gt; ./your_rows.jsonl &lt;span class="nt"&gt;--model&lt;/span&gt; &amp;lt;hosted-model&amp;gt;

&lt;span class="c"&gt;# 3. your plugin's own footprint: tool-definition tokens, round trip, gate wall time&lt;/span&gt;
node bench/context-cost.mjs

&lt;span class="c"&gt;# 4. amortization and hybrid rows, plugin overhead included&lt;/span&gt;
node bench/economics.mjs

&lt;span class="c"&gt;# 5. the gate alone, against the annotated command set&lt;/span&gt;
node bench/gate-by-mode.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sweep τ across {0.3, 0.4, 0.5, 0.6} and read accuracy next to savings: on this workload, pushing τ up bought less saving &lt;em&gt;and&lt;/em&gt; worse accuracy. &lt;code&gt;bench/cases.json&lt;/code&gt; holds 53 annotated commands — replace it with your own traffic to measure your gate instead of ours.&lt;/p&gt;

&lt;p&gt;The measure of a savings claim is whether you can rerun it. Ours: 86.1% at τ=0.4 on 400 cases, plugin overhead included — and the 8.5% row at τ=0.6, reported because a knob you don't report is a knob you don't understand. Point the bench at your logs, and trust your own row over anyone's — including ours.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugin: &lt;code&gt;github.com/Phocinae/dsh-phocinae&lt;/code&gt; — &lt;code&gt;bench/&lt;/code&gt; in the same repo holds the scripts and raw results behind every number above&lt;/li&gt;
&lt;li&gt;Model (Apache-2.0): &lt;code&gt;huggingface.co/Phocinae/Phocinae-Largha-150M-v1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>0/31 vs 30/31: the question is the gate — wiring a local decision layer into my agent</title>
      <dc:creator>PerryLink</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:13:24 +0000</pubDate>
      <link>https://dev.to/perrylink/031-vs-3031-the-question-is-the-gate-wiring-a-local-decision-layer-into-my-agent-gp8</link>
      <guid>https://dev.to/perrylink/031-vs-3031-the-question-is-the-gate-wiring-a-local-decision-layer-into-my-agent-gp8</guid>
      <description>&lt;p&gt;&lt;code&gt;Remove-Item -Recurse -Force C:\Windows\System32&lt;/code&gt; → &lt;strong&gt;deny&lt;/strong&gt;, in 24.1 ms — CPU, fp32, on the harness machine — from a 144.3M-parameter model that has never generated a word of text in its life. Nothing left the laptop.&lt;/p&gt;

&lt;p&gt;The line is from the recorded output of &lt;code&gt;dsh-phocinae&lt;/code&gt;, a plugin for DeepSeek Harness (dsh) that bolts a small, local, non-generative decision layer onto an agent: two tools (&lt;code&gt;phocinae_ask&lt;/code&gt;, &lt;code&gt;phocinae_gate&lt;/code&gt;) and a fail-closed approval gate in front of tool calls. This is the integration log — what it is, how to mount it (first trap: &lt;code&gt;npm i&lt;/code&gt; mounts nothing), how the tools behave, and where the token savings really come from. All numbers below are the repo's recorded measurements, on one CPU (fp32) bench machine, against the released weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;dsh-phocinae is a decision layer, not a chat model. The model behind it — Phocinae-Largha-150M-v1: 144.3M parameters, bilingual, Apache-2.0 — makes one structured decision per forward pass: yes/no, one choice from an options list, or a 2–10 score. No generation, no fact recall. You send a state plus typed questions; you get answers with calibrated confidences.&lt;/p&gt;

&lt;p&gt;The plugin adds four things to a dsh host: &lt;code&gt;phocinae_ask&lt;/code&gt; (a batch of up to 64 typed questions against one state) and &lt;code&gt;phocinae_gate&lt;/code&gt; (judge one command — allow / ask / deny — without running it), both as tools; an approval gate subscribed to &lt;code&gt;tools/pre-execute&lt;/code&gt; that screens every covered call; and a &lt;code&gt;phocinae&lt;/code&gt; skill that teaches the model when a local decision is the right tool.&lt;/p&gt;

&lt;p&gt;It calls a local HTTP server, and it is &lt;strong&gt;loopback-only&lt;/strong&gt;: a non-loopback endpoint is refused at activation unless you explicitly override it — the decision state is the raw text of the tool calls your agent is about to make. No runtime dependencies beyond Node's built-ins, no keys, nothing written to disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mounting it: &lt;code&gt;npm i&lt;/code&gt; installs nothing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm i dsh-phocinae&lt;/code&gt; puts the package in the &lt;strong&gt;current directory's&lt;/strong&gt; &lt;code&gt;node_modules&lt;/code&gt; and nothing else. Profiles don't walk your project; you mount per profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dsh plugin &lt;span class="nt"&gt;--profile&lt;/span&gt; &amp;lt;name&amp;gt; add dsh-phocinae@&amp;lt;version&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin the version. At the time of writing (2026-10-12), &lt;code&gt;npm view dsh-phocinae version&lt;/code&gt; answers &lt;code&gt;0.2.5&lt;/code&gt; — paste whatever yours answers. Pinning isn't just hygiene: a bare add resolves through pnpm's supply-chain age gate (&lt;code&gt;minimumReleaseAge&lt;/code&gt;), so shortly after a publish the bare name resolves to the &lt;em&gt;previous&lt;/em&gt; version — which in this package's history was &lt;code&gt;0.1.2&lt;/code&gt;, the build that cannot activate on DSH 0.2.x at all. The repo documents the measurement: two minutes after 0.2.2 shipped, a bare add resolved to 0.1.2; &lt;code&gt;@0.2.2&lt;/code&gt; resolved to 0.2.2; 150 minutes later the bare add was fine again. A freshly published package is exactly what a supply-chain attack looks like, so name the version you want (or record an explicit &lt;code&gt;minimumReleaseAgeExclude&lt;/code&gt; and mean it).&lt;/p&gt;

&lt;p&gt;Verify: &lt;code&gt;dsh &amp;lt;profile&amp;gt; --dump-config&lt;/code&gt; must contain a &lt;code&gt;phocinae&lt;/code&gt; row with its endpoint. Requirements: Node &lt;code&gt;^22.19&lt;/code&gt; or &lt;code&gt;&amp;gt;=24&lt;/code&gt;, plus a dsh host in range. One version note — the release line keeps hardening the gate: 0.2.5, published on 2026-10-12, is a security-fix release for the auto-allow list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the decision service
&lt;/h2&gt;

&lt;p&gt;The plugin doesn't load weights itself; it calls a local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;huggingface-cli download Phocinae/Phocinae-Largha-150M-v1 &lt;span class="nt"&gt;--local-dir&lt;/span&gt; ./model   &lt;span class="c"&gt;# ~330 MB&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;phocinae-server
&lt;span class="nv"&gt;PHOC_MODEL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./model python &lt;span class="nt"&gt;-m&lt;/span&gt; phocinae.main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GET 127.0.0.1:8155/health&lt;/code&gt; → &lt;code&gt;{"status":"ok","model_loaded":true}&lt;/code&gt; means it's up. This is where fail-closed stops being a slogan: with the service down, &lt;code&gt;phocinae_ask&lt;/code&gt; returns a structured &lt;code&gt;PHOCINAE_SERVICE_UNAVAILABLE&lt;/code&gt; naming the start command, and the gate routes &lt;strong&gt;every screened call&lt;/strong&gt; to a human. Right direction to fail; real cost — the agent stops auto-running the shell until it's back.&lt;/p&gt;

&lt;h2&gt;
  
  
  First ask
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;phocinae_ask&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;the decision context, verbatim&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;risk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;choice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;severity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;noul&lt;/code&gt; is yes/no — set the threshold deliberately; 0.5 is a coin flip's edge, not a considered cut. &lt;code&gt;choice&lt;/code&gt; picks among &lt;code&gt;options&lt;/code&gt; — lead with the distinguishing words, because long options get truncated. &lt;code&gt;score&lt;/code&gt; is 2–10. One call carries the whole batch against one state, and the most important rule is: &lt;strong&gt;paste the state verbatim.&lt;/strong&gt; The model reads exactly what you send; a rewritten state is a different question. (One round trip: 42.6 ms, CPU fp32.)&lt;/p&gt;

&lt;p&gt;Answers come back keyed by id, each with a confidence and an &lt;code&gt;escalate&lt;/code&gt; flag. &lt;code&gt;escalate: false&lt;/code&gt; — confident, use it. &lt;code&gt;escalate: true&lt;/code&gt; — confidence fell below the threshold (default 0.6); still the model's best guess, but this is the case to hand to a bigger model or a human. &lt;strong&gt;Do not re-ask&lt;/strong&gt;: the model is deterministic, so a repeat returns the same verdict. And in summaries, say &lt;em&gt;three of twenty were uncertain&lt;/em&gt;, not &lt;em&gt;twenty items classified&lt;/em&gt;. The response's &lt;code&gt;usage&lt;/code&gt; reports zero output tokens — there is no generation step to bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  First gate — and the question that turned out to be the product
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;phocinae_gate&lt;/code&gt; judges one command or action — allow / ask / deny, with a reason and confidence — and &lt;strong&gt;does not run it&lt;/strong&gt;; the same judgement runs automatically on every covered tool call through the &lt;code&gt;tools/pre-execute&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;The decision order, condensed: out-of-scope tool → allow (no request); allow-listed, operator-free command → allow, &lt;strong&gt;no request at all&lt;/strong&gt;; &lt;code&gt;destructive&lt;/code&gt; → deny; &lt;code&gt;risky&lt;/code&gt; → ask; &lt;code&gt;harmless&lt;/code&gt; with confidence ≥ 0.45 → allow; below the floor → ask; unreachable or unreadable answer → ask. Routine traffic never leaves the process, and no path turns a missing answer into passage.&lt;/p&gt;

&lt;p&gt;Then the part I keep thinking about. On a 53-command labelled set (31 destructive, 22 routine), gate quality hinges on the question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;question&lt;/th&gt;
&lt;th&gt;outcome on the 31 destructive commands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;noul&lt;/code&gt; — "should this be blocked for safety?"&lt;/td&gt;
&lt;td&gt;0 of 31 caught (0.00)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tri-state&lt;/code&gt; — allow / ask / deny&lt;/td&gt;
&lt;td&gt;23 of 31 caught (0.74)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;harm&lt;/code&gt; — harmless / risky / destructive (the default)&lt;/td&gt;
&lt;td&gt;30 of 31 caught (0.97)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The boolean safety question isn't merely weak — it's inverted: it passed all 31. The harm scale forces the model to make a positive claim ("harmless") before anything passes. Its one miss, &lt;code&gt;vssadmin delete shadows /all /quiet&lt;/code&gt;, is caught by the allow-list's operator rule, not by confidence — so on this set, with the shipped defaults, none of the 31 destructive commands reached execution; 6 of the 22 routine commands went to a human; and the mean verdict time across the set was 15.7 ms (CPU, fp32).&lt;/p&gt;

&lt;p&gt;Why an allow-list at all? Because the confidence is compressed — correct &lt;code&gt;harmless&lt;/code&gt; verdicts mostly sit between 0.27 and 0.54 — so no threshold makes the model's approval a reliable general-purpose filter. A gate that asked the model about &lt;code&gt;git status&lt;/code&gt; would send &lt;code&gt;git status&lt;/code&gt; to a human. A command containing a shell operator skips the list and goes to the model; a list &lt;em&gt;entry&lt;/em&gt; containing one is refused at load time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the token savings come from — and the fine print
&lt;/h2&gt;

&lt;p&gt;The mechanism, in three sentences: the judgement runs on the local model (one forward pass, no generated tokens, nothing billed by a hosted API); the approval gate is a hook, not a conversation message — &lt;strong&gt;0 context tokens&lt;/strong&gt;; and &lt;code&gt;escalate&lt;/code&gt; only routes the uncertain minority to a bigger model — the plugin produces the signal, the caller spends.&lt;/p&gt;

&lt;p&gt;The plugin's overhead is real and small: the two tool definitions ride in every request at 473 + 212 = &lt;strong&gt;685 tokens&lt;/strong&gt;; one decision round trip costs 99 + 135 = &lt;strong&gt;234 tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Against that, the repo ships a 400-case experiment against &lt;code&gt;deepseek-flash&lt;/code&gt; with exact API metering, including the plugin overhead. At τ=0.4, the mixed setup saved &lt;strong&gt;86.1% of tokens&lt;/strong&gt;, with accuracy 0.7305 on this set versus 0.7085 for the large model alone — ahead on the choice-type questions, behind on the yes/no ones. The large model answering the same five-question batches itself cost 3,055.5 tokens per case; the amortisation curve is ahead after a single decision (69.9% saved) and sits near 92% at a thousand.&lt;/p&gt;

&lt;p&gt;The fine print marketing would delete: how much you save is decided by your τ, on your traffic. The same experiment at τ=0.6 escalated 90.8% of cases and saved 8.5%. τ is not a setting you copy from a blog post; it's a measurement you run on your own workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;p&gt;The repo's own SECURITY.md insists on this section, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A screening aid, not a security control.&lt;/strong&gt; Not a sandbox; blind to anything that doesn't dispatch through the tool registry; and a model that can be instructed in prose can be argued with in prose — prompt injection that talks the decision model into &lt;code&gt;harmless&lt;/code&gt; is explicitly outside what it protects against. Compose it with your host's sandbox and approval policy; never substitute it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-closed has a price.&lt;/strong&gt; Service down, malformed answer, missing confidence — every screened call asks a human. That's the design; budget for the interruptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single machine, labelled sets.&lt;/strong&gt; All numbers here are CPU fp32 from one bench machine, and the 53-command set was written by the plugin's author. Swap &lt;code&gt;bench/cases.json&lt;/code&gt; for your own labelled commands before trusting the gate on your traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The confidence is compressed&lt;/strong&gt;, which is why the allow-list exists — it is not a general-purpose risk filter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cautionary tale behind all the fail-closed language: the 0.1.2 build didn't activate on DSH 0.2.x at all — and had it, its gate would have allowed every command while reporting that the model judged it low risk, reading a boolean off the wrong field where &lt;code&gt;undefined&lt;/code&gt; was never &lt;code&gt;true&lt;/code&gt;. The broken build stays visible on a branch called &lt;code&gt;archive/0.1.2-broken&lt;/code&gt;, and every defect in that family now has a named regression test. A safety component whose failure mode is &lt;em&gt;looks fine&lt;/em&gt; is worse than no component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth mounting?
&lt;/h2&gt;

&lt;p&gt;If you run dsh and want routine approvals off the big model's plate, plus a second line in front of destructive commands: one profile command, one local server. If you want it to write, summarise, or think — wrong tool, by design. It decides; it does not generate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model (Apache-2.0): &lt;code&gt;huggingface.co/Phocinae/Phocinae-Largha-150M-v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Plugin: &lt;code&gt;github.com/Phocinae/dsh-phocinae&lt;/code&gt; — and &lt;code&gt;bench/&lt;/code&gt;, where the measured numbers above come from&lt;/li&gt;
&lt;li&gt;What breaks first on &lt;em&gt;your&lt;/em&gt; traffic: the allow-list, the τ, or the confidence?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Tiny model. Big decisions. — How I built a 144.3M-parameter typed decision model that routes 55.0% of agent decisions off LLMs</title>
      <dc:creator>PerryLink</dc:creator>
      <pubDate>Thu, 08 Oct 2026 03:41:32 +0000</pubDate>
      <link>https://dev.to/perrylink/tiny-model-big-decisions-how-i-built-a-144m-parameter-typed-decision-model-that-routes-82-of-23fi</link>
      <guid>https://dev.to/perrylink/tiny-model-big-decisions-how-i-built-a-144m-parameter-typed-decision-model-that-routes-82-of-23fi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-10-12):&lt;/strong&gt; following a question in the typed-decisions dataset discussion, we re-ran the model without the test-derived correction pool — both numbers are published there: clean re-score &lt;strong&gt;0.791&lt;/strong&gt; vs &lt;strong&gt;0.906&lt;/strong&gt; as submitted (both as measured). Full disclosure in the model card / BENCHMARKS.md / board notes → &lt;a href="https://huggingface.co/datasets/LocalLLaMA/typed-decisions/discussions/25" rel="noopener noreferrer"&gt;discussion #25&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;更新（2026-10-12）：&lt;/strong&gt;应 typed-decisions 数据集讨论区质询，我们已完成「去测试行派生纠错池」的清洁重跑并在原讨论帖公示两数：&lt;strong&gt;0.791&lt;/strong&gt;（清洁） vs &lt;strong&gt;0.906&lt;/strong&gt;（现役，均为实测）。完整披露见模型卡／BENCHMARKS.md／榜条目备注 → &lt;a href="https://huggingface.co/datasets/LocalLLaMA/typed-decisions/discussions/25" rel="noopener noreferrer"&gt;讨论 #25&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;版本归属（2026-10-09）&lt;/strong&gt;：本文数字为 &lt;strong&gt;v1.1 / 2026-10-09 刷新版&lt;/strong&gt; 口径：&lt;br&gt;
&lt;strong&gt;τ=0.6 时 LLM 调用 −55.0%&lt;/strong&gt;（45.0% 升级），&lt;strong&gt;τ=0.5 档为 −79.6%&lt;/strong&gt;；&lt;br&gt;
保留集准确率 0.906 → 0.9936。本文 URL 与早期分享卡片中的 "82%" 为历史读数，对应 τ=0.5 档。&lt;br&gt;
以 &lt;a href="https://github.com/Phocinae/Phocinae-Largha-150M-v1/blob/main/BENCHMARKS.md" rel="noopener noreferrer"&gt;仓库 BENCHMARKS.md&lt;/a&gt; 为准。&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Tiny model. Big decisions. — How I built a 144.3M-parameter typed decision model that routes 55.0% of agent decisions off LLMs
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Or: why your agent's "should I run this command?" does not need a 70B chat model.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every agentic workflow I build hits the same wall: the interesting logic is 10 lines, and the other 90% is a language model deciding &lt;em&gt;"allow or deny?", "which tool?", "pass or escalate?"&lt;/em&gt; — thousands of times a day, at chat-model prices and chat-model latency.&lt;/p&gt;

&lt;p&gt;So I built the opposite of a chatbot: &lt;strong&gt;Phocinae-Largha-150M-v1&lt;/strong&gt;, a 144.3M-parameter typed decision model. It cannot generate text. It takes a state plus a list of typed questions (yes/no, pick-one, 2-10 score) and returns, in one forward pass, a verdict per question with calibrated confidence. GPU: &lt;strong&gt;21.0 ms&lt;/strong&gt; p50 per decision (RTX 5090). CPU-only: ~1.64 s per case, no GPU at all. Open-source, Apache-2.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funnk1hu0jnxfvy8z3lr0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funnk1hu0jnxfvy8z3lr0.gif" alt="Latency race: local model finishes at 21.0ms (RTX 5090) while the API request is still in flight" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: decisions are not text
&lt;/h2&gt;

&lt;p&gt;A decision is a &lt;strong&gt;typed output over a closed option set&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;state:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent wants to run: rm -rf /var/log/app"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;question:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;noul&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;qid:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;options:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;answer:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prob:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;confidence:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.96&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no sentence to generate, no chain-of-thought to emit, no format to parse back. So why rent a generative model for it? A 150M-class encoder (mmBERT-small base, 256k vocab, Gemma tokenizer) does one forward pass and outputs label logits per question — that is the entire inference. Deterministic: same input, same output. No sampling, no parsing failures.&lt;/p&gt;

&lt;p&gt;The evaluation protocol is typed-decisions (the format used by Laya and others), which makes scores directly comparable across models on the same rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it measures up to
&lt;/h2&gt;

&lt;p&gt;English typed-decisions: &lt;strong&gt;0.906&lt;/strong&gt; (above the 0.735 teacher self-agreement reference — the card flags scores far above it as label-specific overfitting, so we report it as a label-agreement reading, with JevBench 0.5455 (126/231) as the generalization boundary; 400 cases / 2,000 decisions). Chinese (machine-translated eval set): &lt;strong&gt;0.848&lt;/strong&gt;. Same-protocol published scores: Laya 0.766 (self-measured, native) · TypeSafe JEV-27B 0.727 · meraGPT 0.768.&lt;/p&gt;

&lt;p&gt;The metrics most model cards skip, we publish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Option-order flip rate&lt;/strong&gt;: shuffle the options, does the answer move? reversed 0.0217 / random-mean 0.0144 / any-of-3 0.0283. Roughly one changed answer per ~46 reorders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibration&lt;/strong&gt;: shipped-column ECE 0.2519 (0.0168 with the bundled &lt;code&gt;calib/&lt;/code&gt; column) — disclosed as-is, not hidden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JevBench public-231: 0.5455 (126/231)&lt;/strong&gt; — below the 58.4% acceptance gate, published anyway. No JevBench eval row was used in training; see the model card's training-overlap disclosure for the typed-decisions side.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The economics: an escalate gate, not a replacement
&lt;/h2&gt;

&lt;p&gt;A small model does not need to be perfect — it needs to know when it is not. With a τ=0.6 confidence gate, confident decisions stay local and the rest escalate to a bigger model. Result on the en route: LLM calls &lt;strong&gt;cut 55.0% at τ=0.6&lt;/strong&gt; (100% → 45.0%; 79.6% at τ=0.5), while kept-subset accuracy went &lt;strong&gt;0.906 → 0.9936&lt;/strong&gt; — routing the hard 45.0% upward made the kept local decisions slightly &lt;em&gt;better&lt;/em&gt;, not just cheaper.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs5lh4blf6e7vubr3o2a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs5lh4blf6e7vubr3o2a.gif" alt="Decision ledger: 54 green local cells, 46 grey escalate cells" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the framing I want to leave with you: &lt;strong&gt;System 1 in BERT&lt;/strong&gt;. The two-system picture for agents is not "small model vs big model" — it is &lt;em&gt;typed, deterministic, milliseconds, free&lt;/em&gt; for the repetitive 55.0%, and &lt;em&gt;generative, expensive&lt;/em&gt; only for the ambiguous 45.0%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it (three commands)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;phocinae-server huggingface_hub
huggingface-cli download Phocinae/Phocinae-Largha-150M-v1 &lt;span class="nt"&gt;--local-dir&lt;/span&gt; ./model
&lt;span class="nv"&gt;PHOC_MODEL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./model python &lt;span class="nt"&gt;-m&lt;/span&gt; phocinae.main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:8155/v1/systemone &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"state":"The agent wants to run: rm -rf /var/log/app",
       "questions":[{"type":"noul","qid":"allow",
                     "question":"Allow this command?","options":["false","true"]}]}'&lt;/span&gt;
&lt;span class="c"&gt;# → {"allow": {"label": "false", "prob": 0.96, "confidence": 0.96}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is local-only (127.0.0.1), pure PyTorch at runtime, and the protocol (&lt;code&gt;/v1/systemone&lt;/code&gt;: noul / choice / score questions, calibrated probabilities) is fully specified in the repo. There is also a DeepSeek Harness bundle (dsh-phocinae, npm) with a PreToolUse approval gate that fails closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations (the section everyone skips — please don't)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is &lt;strong&gt;not&lt;/strong&gt; a chatbot, generator, or long-document reasoner. World-knowledge QA is not the job.&lt;/li&gt;
&lt;li&gt;Chinese rows are machine-translated English cases.&lt;/li&gt;
&lt;li&gt;Long inputs degrade: 16k/32k probes score 0.453 / 0.387.&lt;/li&gt;
&lt;li&gt;JevBench gate not passed (that is why the number is on the card).&lt;/li&gt;
&lt;li&gt;No demographic/fairness evaluation yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try, verify, reproduce
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo (Apache-2.0, full docs + 27 demo GIFs): &lt;a href="https://github.com/Phocinae/Phocinae-Largha-150M-v1" rel="noopener noreferrer"&gt;Phocinae/Phocinae-Largha-150M-v1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Weights: &lt;a href="https://huggingface.co/Phocinae/Phocinae-Largha-150M-v1" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; · &lt;a href="https://modelscope.cn/models/PerryLink/Phocinae-Largha-150M-v1" rel="noopener noreferrer"&gt;ModelScope&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reproduction: seeds, row-set hashes, and the environment file ship in the repo; the frozen eval-harness scripts (typed scoring, τ sweep, latency bench) follow in a packaged release. Row sets and protocols are public today, so every published number is checkable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a use case that is mostly repetitive decisions, I'd love to hear what breaks first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
