<?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: Marcel Roozekrans</title>
    <description>The latest articles on DEV Community by Marcel Roozekrans (@marcelroozekrans).</description>
    <link>https://dev.to/marcelroozekrans</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%2F4208199%2F4e53da38-10e7-40e0-91cb-0dffccf41400.png</url>
      <title>DEV Community: Marcel Roozekrans</title>
      <link>https://dev.to/marcelroozekrans</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marcelroozekrans"/>
    <language>en</language>
    <item>
      <title>Minos.NET: Asking a Model a Question It Can Answer With a Number</title>
      <dc:creator>Marcel Roozekrans</dc:creator>
      <pubDate>Tue, 27 Oct 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/marcelroozekrans/minosnet-asking-a-model-a-question-it-can-answer-with-a-number-24lb</link>
      <guid>https://dev.to/marcelroozekrans/minosnet-asking-a-model-a-question-it-can-answer-with-a-number-24lb</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://roozekrans.nl/blog/minos-net-intro/" rel="noopener noreferrer"&gt;roozekrans.nl&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most of what an application asks a language model is not open-ended. Is this message urgent? Which queue should it go to? How annoyed is the customer? The usual answer is a prompt that begs for JSON, a parser that hopes it got some, and a string comparison on &lt;code&gt;"yes"&lt;/code&gt;. What it never gives you is how sure the model was, and that is the one number you need to decide whether to act on the answer without a person checking it.&lt;/p&gt;

&lt;p&gt;TypeSafe's Jev models answer exactly that kind of question, and they answer it with probabilities. Minos.NET is an unofficial .NET client for them. It is named after Minos, the judge of the dead in Greek myth, which seemed fitting for a library whose whole job is passing judgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Kinds of Question
&lt;/h2&gt;

&lt;p&gt;Jev answers three shapes of question, and Minos gives each one its own type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Noul&lt;/strong&gt; is a yes/no question. The answer is a single probability that the answer is yes. There is no separate confidence, because the probability &lt;em&gt;is&lt;/em&gt; the confidence.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Choice&lt;/strong&gt; picks one option out of several. You get the pick, a confidence, and the probability of every option, so the runner-up is one lookup away.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Score&lt;/strong&gt; is a Choice over ordered levels, such as a rubric from worst to best. Besides the most likely level, it gives the probability-weighted expected level, which moves smoothly where the top pick jumps. That makes it the better input for a threshold or a weighted sum.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every answer is a small struct, so reading one never allocates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions Are a Type
&lt;/h2&gt;

&lt;p&gt;You declare the questions as a C# type, and the options as enums:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Lane&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Criterion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payments, invoices and refunds"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Criterion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bugs, outages and integrations"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Technical&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="n"&gt;Questions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Triage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Does this convey urgency?"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;Noul&lt;/span&gt; &lt;span class="n"&gt;IsUrgent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&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="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Which queue should handle this?"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Lane&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&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;A source generator writes the request and the answer parsing at compile time. &lt;code&gt;EvaluateAsync&amp;lt;Triage&amp;gt;&lt;/code&gt; then returns a &lt;code&gt;Triage&lt;/code&gt; with typed answers, or a typed error: a rejected key, a rate limit, a time-out and an unreadable response are all values rather than exceptions. Because nothing is discovered by reflection, the client runs under Native AOT, and the AOT smoke test in CI exercises every public entry point to keep it that way.&lt;/p&gt;

&lt;p&gt;When the questions are not known until run time, a builder makes a question set from data and reads the answers through handles, through the same protocol path the generated code uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Answer Picks the Action, the Confidence Decides If It Runs
&lt;/h2&gt;

&lt;p&gt;The pattern the library is built around is confidence routing. The answer says what to do; the confidence says whether to do it on its own, ask for a check, or hand it to a person. Each action gets its own threshold, sized to what being wrong costs: showing where a parcel is can be wrong and nobody is hurt, while a refund sent by mistake is money gone.&lt;/p&gt;

&lt;p&gt;The guide works through it alongside three other patterns: asking every question you might need in one request and reading only the answers that matter, breaking a judgement into small Scores you weight yourself, and a cheap first decision that routes a request to code, a model, or a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Tied to One Vendor
&lt;/h2&gt;

&lt;p&gt;The core is provider-neutral. Questions and answers are a neutral model behind an &lt;code&gt;IDecisionClient&lt;/code&gt;, and each wire protocol is an adapter. &lt;code&gt;Minos.NET.SystemOne&lt;/code&gt; speaks Jev's &lt;code&gt;/v1/systemone&lt;/code&gt; protocol, either to TypeSafe directly, through OpenRouter, or to a server you run yourself, such as a vLLM Semantic Router. &lt;code&gt;Minos.NET.OpenAI&lt;/code&gt; targets OpenAI's Decisions API, which is in public beta; it is built from OpenAI's guide and has not yet been run against the live API, and the library says so rather than letting you find out.&lt;/p&gt;

&lt;p&gt;A conformance suite checks any decision endpoint's shapes, probabilities, errors and models, so a self-hosted server can be held to the same standard as the hosted ones.&lt;/p&gt;

&lt;p&gt;The rest is what a .NET host expects: dependency injection with keyed clients, caching and rate limiting as optional pipeline stages, OpenTelemetry traces and metrics, and analyzers with code fixes that catch a malformed question set at compile time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording Decisions
&lt;/h2&gt;

&lt;p&gt;Since 1.3, &lt;code&gt;Minos.NET.Recording&lt;/code&gt; keeps a record of every decision your app makes. One pipeline stage hands each decision to a recorder, and the recorder writes it to a JSONL file, to memory or to a store of your own. Later you attach what actually happened, such as the answer a reviewer confirmed, by the decision's id, and you erase a customer's records by a key you chose.&lt;/p&gt;

&lt;p&gt;Those records are what you turn into a labelled dataset, so you can measure a model on your own traffic instead of someone else's benchmark. The stage returns the inner client's result unchanged, so it never makes a decision fail. It runs your redaction before anything is kept, and it never puts the state, a decision id or an erasure key in a log, a trace or a metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Provider and a Threshold
&lt;/h2&gt;

&lt;p&gt;A confidence is only useful once you know where to cut. The guide has a page that runs a Banking77 sample against TypeSafe, Clef Flash and a local model and compares how accurate, how well calibrated, how costly and how fast each one was, then walks through picking a threshold from those results. The &lt;code&gt;minos&lt;/code&gt; dotnet tool runs the same calibration and report against a dataset of your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  On NuGet
&lt;/h2&gt;

&lt;p&gt;Minos.NET is at 1.x: the public API is stable and follows semantic versioning. All nine packages are on NuGet: the provider-neutral core with the source generator and analyzers, the &lt;code&gt;/v1/systemone&lt;/code&gt; and OpenAI clients, dependency injection, the caching and rate-limiting stages, labelled datasets, recording, and the &lt;code&gt;minos&lt;/code&gt; tool with &lt;code&gt;calibrate&lt;/code&gt;, &lt;code&gt;report&lt;/code&gt; and &lt;code&gt;conformance&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;dotnet add package Minos.NET.SystemOne
dotnet add package Minos.NET.DependencyInjection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The samples replay recorded answers, so they run offline without an API key.&lt;/p&gt;

&lt;p&gt;Minos.NET is built on &lt;a href="https://zeroalloc.net" rel="noopener noreferrer"&gt;ZeroAlloc&lt;/a&gt; libraries such as Results, Resilience and Validation.&lt;/p&gt;

&lt;p&gt;Minos is not affiliated with TypeSafe, which publishes official SDKs for Python and JavaScript.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
